diff --git a/testing/drivers/drivertest/CMakeLists.txt b/testing/drivers/drivertest/CMakeLists.txt index 2492842304b..ccf43d4ea8d 100644 --- a/testing/drivers/drivertest/CMakeLists.txt +++ b/testing/drivers/drivertest/CMakeLists.txt @@ -119,7 +119,8 @@ if(CONFIG_TESTING_DRIVER_TEST) drivertest_posix_timer.c) endif() - if(CONFIG_WATCHDOG AND CONFIG_BOARDCTL_RESET_CAUSE) + if(CONFIG_WATCHDOG AND (CONFIG_BOARDCTL_RESET_CAUSE + OR CONFIG_WATCHDOG_TIMEOUT_NOTIFIER)) nuttx_add_application( NAME cmocka_driver_watchdog diff --git a/testing/drivers/drivertest/Makefile b/testing/drivers/drivertest/Makefile index 470c9151297..786d30d1dcb 100644 --- a/testing/drivers/drivertest/Makefile +++ b/testing/drivers/drivertest/Makefile @@ -62,7 +62,7 @@ endif endif ifneq ($(CONFIG_WATCHDOG),) -ifneq ($(CONFIG_BOARDCTL_RESET_CAUSE),) +ifneq ($(CONFIG_BOARDCTL_RESET_CAUSE)$(CONFIG_WATCHDOG_TIMEOUT_NOTIFIER),) MAINSRC += drivertest_watchdog.c PROGNAME += cmocka_driver_watchdog endif diff --git a/testing/drivers/drivertest/drivertest_watchdog.c b/testing/drivers/drivertest/drivertest_watchdog.c index 72f5abe0e2e..630b824146f 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 /**************************************************************************** @@ -88,9 +91,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 }; +#ifdef CONFIG_BOARDCTL_RESET_CAUSE static sem_t g_semaphore; +#endif /**************************************************************************** * Private Data @@ -100,6 +111,8 @@ static sem_t g_semaphore; * Private Functions ****************************************************************************/ +#ifdef CONFIG_BOARDCTL_RESET_CAUSE + /**************************************************************************** * Name: get_timestamp ****************************************************************************/ @@ -163,6 +176,8 @@ static int wdg_init(FAR struct wdg_state_s *state) return dev_fd; } +#endif /* CONFIG_BOARDCTL_RESET_CAUSE */ + /**************************************************************************** * Name: show_usage ****************************************************************************/ @@ -281,6 +296,8 @@ static void parse_commandline(FAR struct wdg_state_s *wdg_state, int argc, } } +#ifdef CONFIG_BOARDCTL_RESET_CAUSE + /**************************************************************************** * Name: capture_callback ****************************************************************************/ @@ -291,6 +308,214 @@ static int capture_callback(int irq, FAR void *context, FAR void *arg) return OK; } +#endif /* CONFIG_BOARDCTL_RESET_CAUSE */ + +#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 */ + +#ifdef CONFIG_BOARDCTL_RESET_CAUSE + /**************************************************************************** * Name: drivertest_watchdog_feeding * @@ -520,6 +745,8 @@ static void drivertest_watchdog_api(FAR void **state) assert_return_code(ret, OK); } +#endif /* CONFIG_BOARDCTL_RESET_CAUSE */ + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -545,11 +772,17 @@ int main(int argc, FAR char *argv[]) const struct CMUnitTest tests[] = { +#ifdef CONFIG_BOARDCTL_RESET_CAUSE 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 };