diff --git a/testing/drivers/drivertest/CMakeLists.txt b/testing/drivers/drivertest/CMakeLists.txt index 2492842304b..d72dce649f9 100644 --- a/testing/drivers/drivertest/CMakeLists.txt +++ b/testing/drivers/drivertest/CMakeLists.txt @@ -20,7 +20,23 @@ # # ############################################################################## -if(CONFIG_TESTING_DRIVER_TEST) +if(CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY) + nuttx_add_application( + NAME + cmocka_driver_watchdog + PRIORITY + ${CONFIG_TESTING_DRIVER_TEST_PRIORITY} + STACKSIZE + ${CONFIG_TESTING_DRIVER_TEST_STACKSIZE} + MODULE + ${CONFIG_TESTING_DRIVER_TEST} + DEPENDS + cmocka + SRCS + drivertest_watchdog.c) +endif() + +if(CONFIG_TESTING_DRIVER_TEST AND NOT CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY) if(CONFIG_TESTING_DRIVER_TEST_SIMPLE) nuttx_add_application( NAME diff --git a/testing/drivers/drivertest/Kconfig b/testing/drivers/drivertest/Kconfig index c0975e9b889..24fcbe525b0 100644 --- a/testing/drivers/drivertest/Kconfig +++ b/testing/drivers/drivertest/Kconfig @@ -24,6 +24,14 @@ config TESTING_DRIVER_TEST_SIMPLE bool "Enable cmocka driver simple test" default n +config TESTING_DRIVER_TEST_WATCHDOG_ONLY + bool "Build only the watchdog notifier cmocka test" + default n + depends on WATCHDOG + ---help--- + Build only cmocka_driver_watchdog. This is useful for a minimal + simulator validation configuration without unrelated driver tests. + config TESTING_ONESHOT_TEST bool "Enable cmocka oneshot test" default n diff --git a/testing/drivers/drivertest/Makefile b/testing/drivers/drivertest/Makefile index 470c9151297..003d3d52af5 100644 --- a/testing/drivers/drivertest/Makefile +++ b/testing/drivers/drivertest/Makefile @@ -26,6 +26,11 @@ PRIORITY = $(CONFIG_TESTING_DRIVER_TEST_PRIORITY) STACKSIZE = $(CONFIG_TESTING_DRIVER_TEST_STACKSIZE) MODULE = $(CONFIG_TESTING_DRIVER_TEST) +ifeq ($(CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY),y) +MAINSRC += drivertest_watchdog.c +PROGNAME += cmocka_driver_watchdog +else + ifneq ($(CONFIG_TESTING_DRIVER_TEST_SIMPLE),) MAINSRC += drivertest_simple.c PROGNAME += cmocka_driver_simple @@ -158,4 +163,6 @@ MAINSRC += drivertest_pm_runtime.c PROGNAME += cmocka_driver_pm_runtime endif +endif + include $(APPDIR)/Application.mk diff --git a/testing/drivers/drivertest/drivertest_watchdog.c b/testing/drivers/drivertest/drivertest_watchdog.c index 72f5abe0e2e..2a1924ac30d 100644 --- a/testing/drivers/drivertest/drivertest_watchdog.c +++ b/testing/drivers/drivertest/drivertest_watchdog.c @@ -43,8 +43,11 @@ #include #include #include +#include #include +#include +#include #include /**************************************************************************** @@ -57,6 +60,12 @@ #define WDG_DEFAULT_TIMEOUT 2000 #define WDG_DEFAULT_TESTCASE 0 #define WDG_DEFAULT_DEVIATION 20 +#ifndef BOARDIOC_RESETCAUSE_SYS_CHIPPOR +# define BOARDIOC_RESETCAUSE_SYS_CHIPPOR 0 +#endif +#ifndef BOARDIOC_RESETCAUSE_SYS_RWDT +# define BOARDIOC_RESETCAUSE_SYS_RWDT 0 +#endif #if defined(CONFIG_ARCH_ARMV7A) && defined(CONFIG_ARCH_HAVE_TRUSTZONE) #define WDG_COUNT_TESTCASE 3 #else @@ -88,9 +97,17 @@ struct wdg_state_s uint32_t deviation; int test_case; bool test_getstatus; +#ifdef CONFIG_WATCHDOG_TIMEOUT_NOTIFIER + unsigned int notifier_calls; + unsigned long notifier_action[8]; + FAR void *notifier_data[8]; + int notifier_id[8]; +#endif }; +#ifndef CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY static sem_t g_semaphore; +#endif /**************************************************************************** * Private Data @@ -100,6 +117,8 @@ static sem_t g_semaphore; * Private Functions ****************************************************************************/ +#ifndef CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY + /**************************************************************************** * Name: get_timestamp ****************************************************************************/ @@ -163,6 +182,8 @@ static int wdg_init(FAR struct wdg_state_s *state) return dev_fd; } +#endif /* CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY */ + /**************************************************************************** * Name: show_usage ****************************************************************************/ @@ -281,6 +302,8 @@ static void parse_commandline(FAR struct wdg_state_s *wdg_state, int argc, } } +#ifndef CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY + /**************************************************************************** * Name: capture_callback ****************************************************************************/ @@ -291,6 +314,214 @@ static int capture_callback(int irq, FAR void *context, FAR void *arg) return OK; } +#endif /* CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY */ + +#ifdef CONFIG_WATCHDOG_TIMEOUT_NOTIFIER + +struct watchdog_notifier_test_nb_s +{ + struct notifier_block nb; + int id; +}; + +static FAR struct wdg_state_s *g_notifier_test_state; + +static int watchdog_notifier_test_callback(FAR struct notifier_block *nb, + unsigned long action, + FAR void *data) +{ + FAR struct watchdog_notifier_test_nb_s *test_nb; + unsigned int index; + + test_nb = container_of(nb, struct watchdog_notifier_test_nb_s, nb); + index = g_notifier_test_state->notifier_calls; + if (index < 8) + { + g_notifier_test_state->notifier_action[index] = action; + g_notifier_test_state->notifier_data[index] = data; + g_notifier_test_state->notifier_id[index] = test_nb->id; + } + + g_notifier_test_state->notifier_calls++; + return OK; +} + +static unsigned long watchdog_notifier_test_expected_action(void) +{ +#if defined(CONFIG_WATCHDOG_AUTOMONITOR_BY_ONESHOT) + return WATCHDOG_KEEPALIVE_BY_ONESHOT; +#elif defined(CONFIG_WATCHDOG_AUTOMONITOR_BY_TIMER) + return WATCHDOG_KEEPALIVE_BY_TIMER; +#elif defined(CONFIG_WATCHDOG_AUTOMONITOR_BY_WDOG) + return WATCHDOG_KEEPALIVE_BY_WDOG; +#elif defined(CONFIG_WATCHDOG_AUTOMONITOR_BY_WORKER) + return WATCHDOG_KEEPALIVE_BY_WORKER; +#elif defined(CONFIG_WATCHDOG_AUTOMONITOR_BY_CAPTURE) + return WATCHDOG_KEEPALIVE_BY_CAPTURE; +#elif defined(CONFIG_WATCHDOG_AUTOMONITOR_BY_IDLE) + return WATCHDOG_KEEPALIVE_BY_IDLE; +#else +# error "An automonitor source must be selected" +#endif +} + +static void drivertest_watchdog_notifier(FAR void **state) +{ + FAR struct wdg_state_s *wdg_state = *state; + + struct watchdog_notifier_test_nb_s low = + { + .nb = + { + .notifier_call = watchdog_notifier_test_callback, + .priority = 10 + }, + .id = 1 + }; + + struct watchdog_notifier_test_nb_s high = + { + .nb = + { + .notifier_call = watchdog_notifier_test_callback, + .priority = 20 + }, + .id = 2 + }; + + unsigned long expected_action; + + UNUSED(state); + expected_action = watchdog_notifier_test_expected_action(); + wdg_state->notifier_calls = 0; + g_notifier_test_state = wdg_state; + + /* Registration is priority ordered, and duplicate registration of the + * same notifier must not result in a duplicate callback. + */ + + watchdog_notifier_chain_register(&low.nb); + watchdog_notifier_chain_register(&low.nb); + watchdog_notifier_chain_register(&high.nb); + + watchdog_automonitor_timeout(); + + assert_int_equal(wdg_state->notifier_calls, 2); + assert_int_equal(wdg_state->notifier_id[0], high.id); + assert_int_equal(wdg_state->notifier_id[1], low.id); + assert_int_equal(wdg_state->notifier_action[0], expected_action); + assert_int_equal(wdg_state->notifier_action[1], expected_action); + assert_null(wdg_state->notifier_data[0]); + assert_null(wdg_state->notifier_data[1]); + + /* Every timeout notification is delivered to all currently registered + * callbacks. + */ + + watchdog_automonitor_timeout(); + assert_int_equal(wdg_state->notifier_calls, 4); + assert_int_equal(wdg_state->notifier_id[2], high.id); + assert_int_equal(wdg_state->notifier_id[3], low.id); + + /* Unregistering one callback removes only that callback. */ + + watchdog_notifier_chain_unregister(&high.nb); + watchdog_automonitor_timeout(); + assert_int_equal(wdg_state->notifier_calls, 5); + assert_int_equal(wdg_state->notifier_id[4], low.id); + + watchdog_notifier_chain_unregister(&low.nb); + watchdog_automonitor_timeout(); + assert_int_equal(wdg_state->notifier_calls, 5); + + g_notifier_test_state = NULL; +} + +struct watchdog_notifier_race_s +{ + struct watchdog_notifier_test_nb_s nb; + volatile bool stop; +}; + +static volatile unsigned int g_notifier_race_callbacks; + +static int watchdog_notifier_race_callback(FAR struct notifier_block *nb, + unsigned long action, + FAR void *data) +{ + UNUSED(nb); + UNUSED(action); + UNUSED(data); + g_notifier_race_callbacks++; + return OK; +} + +static FAR void *watchdog_notifier_race_worker(FAR void *arg) +{ + FAR struct watchdog_notifier_race_s *race = arg; + unsigned int count; + + for (count = 0; count < 2000 && !race->stop; count++) + { + watchdog_notifier_chain_register(&race->nb.nb); + watchdog_automonitor_timeout(); + watchdog_notifier_chain_unregister(&race->nb.nb); + } + + return NULL; +} + +static void drivertest_watchdog_notifier_race(FAR void **state) +{ + struct watchdog_notifier_race_s race = + { + .nb = + { + .nb = + { + .notifier_call = watchdog_notifier_race_callback, + .priority = 10 + }, + .id = 3 + } + }; + + pthread_t thread; + unsigned int count; + int ret; + + UNUSED(state); + g_notifier_race_callbacks = 0; + watchdog_notifier_chain_register(&race.nb.nb); + ret = pthread_create(&thread, NULL, watchdog_notifier_race_worker, &race); + assert_int_equal(ret, 0); + usleep(1000); + + /* Interleave timeout delivery with registration and unregistration from + * another task. The test is successful if the notifier chain remains + * usable and no callback observes a non-NULL payload. + */ + + for (count = 0; count < 2000; count++) + { + watchdog_automonitor_timeout(); + if ((count & 0x3f) == 0) + { + usleep(1000); + } + } + + race.stop = true; + ret = pthread_join(thread, NULL); + assert_int_equal(ret, 0); + watchdog_notifier_chain_unregister(&race.nb.nb); + assert_true(g_notifier_race_callbacks > 0); +} + +#endif /* CONFIG_WATCHDOG_TIMEOUT_NOTIFIER */ + +#ifndef CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY + /**************************************************************************** * Name: drivertest_watchdog_feeding * @@ -305,7 +536,9 @@ static void drivertest_watchdog_feeding(FAR void **state) int ret; uint32_t start_ms; FAR struct wdg_state_s *wdg_state; +#ifdef CONFIG_BOARDCTL_RESET_CAUSE struct boardioc_reset_cause_s reset_cause; +#endif wdg_state = (FAR struct wdg_state_s *)*state; @@ -316,8 +549,10 @@ static void drivertest_watchdog_feeding(FAR void **state) return; } +#ifdef CONFIG_BOARDCTL_RESET_CAUSE boardctl(BOARDIOC_RESET_CAUSE, (uintptr_t)&reset_cause); assert_int_equal(reset_cause.cause, BOARDIOC_RESETCAUSE_SYS_CHIPPOR); +#endif dev_fd = wdg_init(wdg_state); @@ -361,7 +596,9 @@ static void drivertest_watchdog_feeding(FAR void **state) static void drivertest_watchdog_interrupts(FAR void **state) { FAR struct wdg_state_s *wdg_state; +#ifdef CONFIG_BOARDCTL_RESET_CAUSE struct boardioc_reset_cause_s reset_cause; +#endif wdg_state = (FAR struct wdg_state_s *)*state; @@ -370,8 +607,10 @@ static void drivertest_watchdog_interrupts(FAR void **state) return; } +#ifdef CONFIG_BOARDCTL_RESET_CAUSE boardctl(BOARDIOC_RESET_CAUSE, (uintptr_t)&reset_cause); assert_int_equal(reset_cause.cause, BOARDIOC_RESETCAUSE_SYS_RWDT); +#endif wdg_init(wdg_state); @@ -406,7 +645,9 @@ static void drivertest_watchdog_loop(FAR void **state) int ret; static struct wdog_s wdog; FAR struct wdg_state_s *wdg_state; +#ifdef CONFIG_BOARDCTL_RESET_CAUSE struct boardioc_reset_cause_s reset_cause; +#endif wdg_state = (FAR struct wdg_state_s *)*state; @@ -415,8 +656,10 @@ static void drivertest_watchdog_loop(FAR void **state) return; } +#ifdef CONFIG_BOARDCTL_RESET_CAUSE boardctl(BOARDIOC_RESET_CAUSE, (uintptr_t)&reset_cause); assert_int_equal(reset_cause.cause, BOARDIOC_RESETCAUSE_SYS_RWDT); +#endif wdg_init(wdg_state); @@ -445,15 +688,19 @@ static void drivertest_watchdog_api(FAR void **state) uint32_t start_ms; FAR struct wdg_state_s *wdg_state; struct watchdog_status_s status; +#ifdef CONFIG_BOARDCTL_RESET_CAUSE struct boardioc_reset_cause_s reset_cause; +#endif struct watchdog_capture_s watchdog_capture; wdg_state = (FAR struct wdg_state_s *)*state; assert_int_equal(wdg_state->test_case, 3); +#ifdef CONFIG_BOARDCTL_RESET_CAUSE boardctl(BOARDIOC_RESET_CAUSE, (uintptr_t)&reset_cause); assert_int_equal(reset_cause.cause, BOARDIOC_RESETCAUSE_SYS_RWDT); +#endif dev_fd = wdg_init(wdg_state); @@ -520,6 +767,8 @@ static void drivertest_watchdog_api(FAR void **state) assert_return_code(ret, OK); } +#endif /* CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY */ + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -545,11 +794,17 @@ int main(int argc, FAR char *argv[]) const struct CMUnitTest tests[] = { +#ifndef CONFIG_TESTING_DRIVER_TEST_WATCHDOG_ONLY cmocka_unit_test_prestate(drivertest_watchdog_feeding, &wdg_state), cmocka_unit_test_prestate(drivertest_watchdog_interrupts, &wdg_state), cmocka_unit_test_prestate(drivertest_watchdog_loop, &wdg_state), #if !defined(CONFIG_ARCH_ARMV7A) || !defined(CONFIG_ARCH_HAVE_TRUSTZONE) - cmocka_unit_test_prestate(drivertest_watchdog_api, &wdg_state) + cmocka_unit_test_prestate(drivertest_watchdog_api, &wdg_state), +#endif +#endif +#ifdef CONFIG_WATCHDOG_TIMEOUT_NOTIFIER + cmocka_unit_test_prestate(drivertest_watchdog_notifier, &wdg_state), + cmocka_unit_test_prestate(drivertest_watchdog_notifier_race, &wdg_state) #endif };