| /**************************************************************************** |
| * apps/testing/ostest/wdog.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/wdog.h> |
| |
| #include <assert.h> |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define WDOGTEST_RAND_ITER 1024 |
| #define WDOGTEST_THREAD_NR (CONFIG_SMP_NCPUS * 4) |
| #define WDOGTEST_TOLERENT_TICK 10 |
| |
| #define wdtest_assert(x) _ASSERT(x, __FILE__, __LINE__) |
| |
| #define wdtest_printf(...) printf(__VA_ARGS__) |
| |
| #define wdtest_delay(delay_ns) usleep(delay_ns / 1000 + 1) |
| |
| /**************************************************************************** |
| * Private Type |
| ****************************************************************************/ |
| |
| typedef struct wdtest_param_s |
| { |
| FAR struct wdog_s *wdog; |
| sclock_t interval; |
| uint64_t callback_cnt; |
| clock_t triggered_tick; |
| } wdtest_param_t; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_BUILD_FLAT |
| static void wdtest_callback(wdparm_t param) |
| { |
| FAR wdtest_param_t *wdtest_param = (FAR wdtest_param_t *)param; |
| |
| /* Increment the callback count */ |
| |
| wdtest_param->callback_cnt += 1; |
| |
| /* Record the system tick at which the callback was triggered */ |
| |
| wdtest_param->triggered_tick = clock_systime_ticks(); |
| } |
| |
| static void wdtest_checkdelay(sclock_t diff, sclock_t delay_tick) |
| { |
| /* Ensure the watchdog trigger time is not earlier than expected. */ |
| |
| wdtest_assert(diff - delay_tick >= 0); |
| |
| /* If the timer latency exceeds the tolerance, print a warning. */ |
| |
| if (diff - delay_tick > WDOGTEST_TOLERENT_TICK) |
| { |
| wdtest_printf("WARNING: wdog latency ticks %lld " |
| "(> %u may indicate timing error)\n", |
| (long long)diff - delay_tick, |
| WDOGTEST_TOLERENT_TICK); |
| } |
| } |
| |
| static void wdtest_once(FAR struct wdog_s *wdog, FAR wdtest_param_t *param, |
| sclock_t delay_ns) |
| { |
| uint64_t cnt; |
| sclock_t diff; |
| clock_t wdset_tick; |
| irqstate_t flags; |
| sclock_t delay_tick = (sclock_t)NSEC2TICK((clock_t)delay_ns); |
| |
| wdtest_printf("wdtest_once %lld ns\n", (long long)delay_ns); |
| |
| /* Save the current callback count. */ |
| |
| cnt = param->callback_cnt; |
| |
| /* Enter a critical section to prevent interruptions. */ |
| |
| flags = enter_critical_section(); |
| |
| /* Record the current system tick before setting the watchdog. */ |
| |
| wdset_tick = clock_systime_ticks(); |
| |
| wdtest_assert(wd_start(wdog, delay_tick, wdtest_callback, |
| (wdparm_t)param) == OK); |
| |
| leave_critical_section(flags); |
| |
| /* Wait until the callback is triggered exactly once. */ |
| |
| while (cnt + 1 != param->callback_cnt) |
| { |
| wdtest_delay(delay_ns); |
| } |
| |
| /* Check if the delay is within the acceptable tolerance. */ |
| |
| diff = (sclock_t)(param->triggered_tick - wdset_tick); |
| |
| wdtest_checkdelay(diff, delay_tick); |
| } |
| |
| static void wdtest_rand(FAR struct wdog_s *wdog, FAR wdtest_param_t *param, |
| sclock_t rand_ns) |
| { |
| uint64_t cnt; |
| int idx; |
| sclock_t delay_ns; |
| clock_t wdset_tick; |
| sclock_t delay_tick; |
| sclock_t diff; |
| irqstate_t flags; |
| |
| /* Perform multiple iterations with random delays. */ |
| |
| for (idx = 0; idx < WDOGTEST_RAND_ITER; idx++) |
| { |
| cnt = param->callback_cnt; |
| |
| /* Generate a random delay within the specified range. */ |
| |
| delay_ns = rand() % rand_ns; |
| delay_tick = NSEC2TICK(delay_ns); |
| |
| /* Enter critical section if the callback count is odd. */ |
| |
| if (cnt % 2) |
| { |
| flags = enter_critical_section(); |
| } |
| |
| wdset_tick = clock_systime_ticks(); |
| wdtest_assert(wd_start(wdog, delay_tick, wdtest_callback, |
| (wdparm_t)param) == 0); |
| if (cnt % 2) |
| { |
| leave_critical_section(flags); |
| } |
| |
| /* Decide to wait for the callback or cancel the watchdog. */ |
| |
| if (delay_ns % 2) |
| { |
| /* Wait for the callback. */ |
| |
| while (cnt + 1 != param->callback_cnt) |
| { |
| wdtest_delay(delay_ns); |
| } |
| |
| /* Check the delay if the callback count is odd. */ |
| |
| if (cnt % 2) |
| { |
| diff = (sclock_t)(param->triggered_tick - wdset_tick); |
| wdtest_checkdelay(diff, delay_tick); |
| } |
| } |
| else |
| { |
| wd_cancel(wdog); |
| } |
| } |
| } |
| |
| static void wdtest_callback_recursive(wdparm_t param) |
| { |
| FAR wdtest_param_t *wdtest_param = (FAR wdtest_param_t *)param; |
| sclock_t interval = wdtest_param->interval; |
| |
| wdtest_param->callback_cnt += 1; |
| wdtest_param->triggered_tick = clock_systime_ticks(); |
| |
| wd_start(wdtest_param->wdog, interval, |
| wdtest_callback_recursive, param); |
| } |
| |
| static void wdtest_recursive(FAR struct wdog_s *wdog, |
| FAR wdtest_param_t *param, |
| sclock_t delay_ns, |
| unsigned int times) |
| { |
| uint64_t cnt; |
| clock_t wdset_tick; |
| irqstate_t flags; |
| |
| wdtest_printf("wdtest_recursive %lldns\n", (long long)delay_ns); |
| |
| cnt = param->callback_cnt; |
| |
| param->wdog = wdog; |
| param->interval = (sclock_t)NSEC2TICK((clock_t)delay_ns); |
| |
| wdtest_assert(param->interval >= 0); |
| |
| flags = enter_critical_section(); |
| |
| wdset_tick = clock_systime_ticks(); |
| |
| wdtest_assert(wd_start(param->wdog, param->interval, |
| wdtest_callback_recursive, |
| (wdparm_t)param) == OK); |
| |
| leave_critical_section(flags); |
| |
| wdtest_delay(times * delay_ns); |
| |
| wd_cancel(param->wdog); |
| |
| wdtest_printf("recursive wdog triggered %llu times, elapsed tick %lld\n", |
| (unsigned long long)(param->callback_cnt - cnt), |
| (long long)(param->triggered_tick - wdset_tick)); |
| } |
| |
| static void wdog_test_run(FAR wdtest_param_t *param) |
| { |
| uint64_t cnt; |
| sclock_t rest; |
| clock_t delay; |
| struct wdog_s test_wdog = |
| { |
| 0 |
| }; |
| |
| /* Wrong arguments of the wd_start */ |
| |
| wdtest_assert(wd_start(NULL, 0, NULL, (wdparm_t)NULL) != OK); |
| wdtest_assert(wd_start(NULL, 0, wdtest_callback, (wdparm_t)NULL) != OK); |
| wdtest_assert(wd_start(NULL, -1, NULL, (wdparm_t)NULL) != OK); |
| wdtest_assert(wd_start(NULL, -1, wdtest_callback, (wdparm_t)NULL) != OK); |
| wdtest_assert(wd_start(&test_wdog, 0, NULL, (wdparm_t)NULL) != OK); |
| wdtest_assert(wd_start(&test_wdog, -1, NULL, (wdparm_t)NULL) != OK); |
| wdtest_assert(wd_start(&test_wdog, -1, wdtest_callback, (wdparm_t)NULL) |
| != OK); |
| |
| /* Wrong arguments of the wd_cancel */ |
| |
| wdtest_assert(wd_cancel(NULL) != OK); |
| wdtest_assert(wd_cancel(&test_wdog) != OK); |
| |
| /* Delay = 0 */ |
| |
| wdtest_once(&test_wdog, param, 0); |
| |
| /* Delay > 0, small */ |
| |
| wdtest_once(&test_wdog, param, 1); |
| wdtest_once(&test_wdog, param, 10); |
| wdtest_once(&test_wdog, param, 100); |
| wdtest_once(&test_wdog, param, 1000); |
| wdtest_once(&test_wdog, param, 10000); |
| |
| /* Delay > 0, middle 100us */ |
| |
| wdtest_once(&test_wdog, param, 100000); |
| wdtest_once(&test_wdog, param, 1000000); |
| |
| /* Delay > 0, maximum */ |
| |
| cnt = param->callback_cnt; |
| |
| /* Maximum */ |
| |
| delay = CLOCK_MAX >> 2; |
| wdtest_assert(wd_start(&test_wdog, delay, |
| wdtest_callback, (wdparm_t)param) == OK); |
| |
| /* Sleep for 1s */ |
| |
| wdtest_delay(USEC_PER_SEC); |
| |
| /* Testing wd_gettime */ |
| |
| wdtest_assert(wd_gettime(NULL) == 0); |
| wdtest_assert(wd_cancel(NULL) != 0); |
| |
| /* Ensure watchdog not alarmed */ |
| |
| wdtest_assert(cnt == param->callback_cnt); |
| |
| rest = wd_gettime(&test_wdog); |
| |
| wdtest_assert(rest < delay); |
| |
| wdtest_assert(wd_cancel(&test_wdog) == 0); |
| |
| wdtest_printf("wd_start with maximum delay, cancel OK, rest %lld\n", |
| (long long)rest); |
| |
| /* Recursive wdog delay from 1000us to 10000us */ |
| |
| wdtest_recursive(&test_wdog, param, 1000000, 100); |
| wdtest_recursive(&test_wdog, param, 10000000, 10); |
| |
| /* Random delay ~12us */ |
| |
| wdtest_rand(&test_wdog, param, 12345); |
| |
| /* Finally, cancel the wdog. */ |
| |
| wd_cancel(&test_wdog); |
| } |
| |
| /* Multi threaded */ |
| |
| static FAR void *wdog_test_thread(FAR void *param) |
| { |
| wdog_test_run(param); |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| void wdog_test(void) |
| { |
| unsigned int thread_id; |
| pthread_attr_t attr; |
| pthread_t pthreads[WDOGTEST_THREAD_NR]; |
| wdtest_param_t params[WDOGTEST_THREAD_NR] = |
| { |
| 0 |
| }; |
| |
| printf("wdog_test start...\n"); |
| |
| wdtest_assert(pthread_attr_init(&attr) == 0); |
| |
| /* Create wdog test thread */ |
| |
| for (thread_id = 0; thread_id < WDOGTEST_THREAD_NR; thread_id++) |
| { |
| wdtest_assert(pthread_create(&pthreads[thread_id], &attr, |
| wdog_test_thread, ¶ms[thread_id]) |
| == 0); |
| } |
| |
| for (thread_id = 0; thread_id < WDOGTEST_THREAD_NR; thread_id++) |
| { |
| pthread_join(pthreads[thread_id], NULL); |
| } |
| |
| wdtest_assert(pthread_attr_destroy(&attr) == 0); |
| |
| printf("wdog_test end...\n"); |
| } |
| #endif |