diff --git a/testing/drivertest/Makefile b/testing/drivertest/Makefile index acf6370e6..954292f70 100644 --- a/testing/drivertest/Makefile +++ b/testing/drivertest/Makefile @@ -44,4 +44,9 @@ MAINSRC += drivertest_pwm.c PROGNAME += cmocka_driver_pwm endif +ifneq ($(CONFIG_WATCHDOG),) +MAINSRC += drivertest_watchdog.c +PROGNAME += cmocka_driver_watchdog +endif + include $(APPDIR)/Application.mk diff --git a/testing/drivertest/drivertest_watchdog.c b/testing/drivertest/drivertest_watchdog.c new file mode 100644 index 000000000..403d6806e --- /dev/null +++ b/testing/drivertest/drivertest_watchdog.c @@ -0,0 +1,522 @@ +/**************************************************************************** + * apps/testing/drivertest/drivertest_watchdog.c + * + * 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 + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define WDG_DEFAULT_DEV_PATH "/dev/watchdog0" +#define WDG_DEFAULT_INFO_PATH "/data/wdg_info" +#define WDG_DEFAULT_PINGTIMER 5000 +#define WDG_DEFAULT_PINGDELAY 500 +#define WDG_DEFAULT_TIMEOUT 2000 + +#define OPTARG_TO_VALUE(value, type, base) \ + do \ + { \ + FAR char *ptr; \ + value = (type)strtoul(optarg, &ptr, base); \ + if (*ptr != '\0') \ + { \ + printf("Parameter error: -%c %s\n", ch, optarg); \ + show_usage(argv[0], wdg_state, EXIT_FAILURE); \ + } \ + } while (0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +enum wdg_reset_cause_e +{ + WDG_RESET_CAUSE_NONE, + WDG_RESET_CAUSE_NO_KEEP_ALIVE, + WDG_RESET_CAUSE_CRITICAL_SECTION_BUSY_LOOP, + WDG_RESET_CAUSE_IRQ_BUSY_LOOP +}; + +struct wdg_state_s +{ + char devpath[PATH_MAX]; + char infopath[PATH_MAX]; + uint32_t pingtime; + uint32_t pingdelay; + uint32_t timeout; + enum wdg_reset_cause_e reset_cause; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: get_timestamp + ****************************************************************************/ + +static uint32_t get_timestamp(void) +{ + struct timespec ts; + uint32_t ms; + clock_gettime(CLOCK_MONOTONIC, &ts); + ms = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + return ms; +} + +/**************************************************************************** + * Name: get_time_elaps + ****************************************************************************/ + +static uint32_t get_time_elaps(uint32_t prev_tick) +{ + uint32_t act_time = get_timestamp(); + + /* If there is no overflow in sys_time simple subtract */ + + if (act_time >= prev_tick) + { + prev_tick = act_time - prev_tick; + } + else + { + prev_tick = UINT32_MAX - prev_tick + 1; + prev_tick += act_time; + } + + return prev_tick; +} + +/**************************************************************************** + * Name: wdg_write_reset_cause + ****************************************************************************/ + +static void wdg_write_reset_cause(FAR struct wdg_state_s *state) +{ + int fd; + ssize_t size; + + fd = open(state->infopath, O_WRONLY | O_CREAT); + assert_true(fd > 0); + + size = write(fd, &state->reset_cause, sizeof(state->reset_cause)); + assert_int_equal(size, sizeof(state->reset_cause)); + + close(fd); +} + +/**************************************************************************** + * Name: wdg_read_reset_cause + ****************************************************************************/ + +static void wdg_read_reset_cause(FAR struct wdg_state_s *state) +{ + int fd; + ssize_t size; + + fd = open(state->infopath, O_RDONLY); + if (fd < 0) + { + state->reset_cause = WDG_RESET_CAUSE_NONE; + return; + } + + size = read(fd, &state->reset_cause, sizeof(state->reset_cause)); + assert_int_equal(size, sizeof(state->reset_cause)); + + close(fd); +} + +/**************************************************************************** + * Name: wdg_init + ****************************************************************************/ + +static int wdg_init(FAR struct wdg_state_s *state) +{ + int dev_fd; + int ret; + + /* Open the watchdog device for reading */ + + dev_fd = open(state->devpath, O_RDONLY); + assert_true(dev_fd > 0); + + /* Set the watchdog timeout */ + + ret = ioctl(dev_fd, WDIOC_SETTIMEOUT, state->timeout); + assert_return_code(ret, OK); + + /* Then start the watchdog timer. */ + + ret = ioctl(dev_fd, WDIOC_START, 0); + assert_return_code(ret, OK); + + return dev_fd; +} + +/**************************************************************************** + * Name: show_usage + ****************************************************************************/ + +static void show_usage(FAR const char *progname, + FAR struct wdg_state_s *wdg_state, int exitcode) +{ + printf("Usage: %s -d -p -t " + "-l -o \n", progname); + printf(" [-d devpath] selects the WATCHDOG device.\n" + " Default: %s Current: %s\n", WDG_DEFAULT_DEV_PATH, + wdg_state->devpath); + printf(" [-p infopath] selects the WATCHDOG reset cause path.\n" + " Default: %s Current: %s\n", WDG_DEFAULT_INFO_PATH, + wdg_state->infopath); + printf(" [-t pingtime] Selects the time in milliseconds.\n" + " Default: %d Current: %" PRIu32 "\n", + WDG_DEFAULT_PINGTIMER, wdg_state->pingtime); + printf(" [-l pingdelay] Time delay between pings in milliseconds.\n" + " Default: %d Current: %" PRIu32 "\n", + WDG_DEFAULT_PINGDELAY, wdg_state->pingdelay); + printf(" [-o timeout] Time in milliseconds that the testcase will\n" + " Default: %d Current: %" PRIu32 "\n", + WDG_DEFAULT_TIMEOUT, wdg_state->timeout); + printf(" [-h] = Shows this message and exits\n"); + + exit(exitcode); +} + +/**************************************************************************** + * Name: parse_commandline + ****************************************************************************/ + +static void parse_commandline(FAR struct wdg_state_s *wdg_state, int argc, + FAR char **argv) +{ + int ch; + int converted; + + while ((ch = getopt(argc, argv, "d:p:t:l:o:h")) != ERROR) + { + switch (ch) + { + case 'd': + strncpy(wdg_state->devpath, optarg, sizeof(wdg_state->devpath)); + wdg_state->devpath[sizeof(wdg_state->devpath) - 1] = '\0'; + break; + + case 'p': + strncpy(wdg_state->infopath, optarg, + sizeof(wdg_state->infopath)); + wdg_state->infopath[sizeof(wdg_state->infopath) - 1] = '\0'; + break; + + case 't': + OPTARG_TO_VALUE(converted, uint32_t, 10); + if (converted < 1 || converted > INT_MAX) + { + printf("signal out of range: %d\n", converted); + show_usage(argv[0], wdg_state, EXIT_FAILURE); + } + + wdg_state->pingtime = (uint32_t)converted; + break; + + case 'l': + OPTARG_TO_VALUE(converted, uint32_t, 10); + if (converted < 1 || converted > INT_MAX) + { + printf("signal out of range: %d\n", converted); + show_usage(argv[0], wdg_state, EXIT_FAILURE); + } + + wdg_state->pingdelay = (uint32_t)converted; + break; + + case 'o': + OPTARG_TO_VALUE(converted, uint32_t, 10); + if (converted < 1 || converted > INT_MAX) + { + printf("signal out of range: %d", converted); + show_usage(argv[0], wdg_state, EXIT_FAILURE); + } + + wdg_state->timeout = (uint32_t)converted; + break; + + case '?': + printf("Unsupported option: %s\n", optarg); + show_usage(argv[0], wdg_state, EXIT_FAILURE); + break; + } + } +} + +/**************************************************************************** + * Name: test_case_wdog_01 + * + * Description: + * This function is used to test whether the watchdog can take effect after + * timeout without relying on automatic feeding + ****************************************************************************/ + +static void test_case_wdog_01(FAR void **state) +{ + int dev_fd; + int ret; + uint32_t start_ms; + FAR struct wdg_state_s *wdg_state; + + wdg_state = (FAR struct wdg_state_s *)*state; + + wdg_read_reset_cause(wdg_state); + + /* If it's not the first step, skip... */ + + if (wdg_state->reset_cause != WDG_RESET_CAUSE_NONE) + { + return; + } + + dev_fd = wdg_init(wdg_state); + + /* Get the starting time */ + + start_ms = get_timestamp(); + + /* Then ping */ + + while (get_time_elaps(start_ms) < wdg_state->pingtime) + { + /* Sleep for the requested amount of time */ + + usleep(wdg_state->pingdelay * 1000); + + /* Then ping */ + + ret = ioctl(dev_fd, WDIOC_KEEPALIVE, 0); + assert_return_code(ret, OK); + } + + /* write the reset cause */ + + wdg_state->reset_cause = WDG_RESET_CAUSE_NO_KEEP_ALIVE; + wdg_write_reset_cause(wdg_state); + + /* Then stop pinging */ + + /* Sleep for the requested amount of time */ + + usleep(2 * wdg_state->timeout * 1000); + + assert_true(false); +} + +/**************************************************************************** + * Name: test_case_wdog_02 + * + * Description: + * This function is used to test whether the watchdog takes effect when + * the interrupt is turned off. + ****************************************************************************/ + +static void test_case_wdog_02(FAR void **state) +{ + FAR struct wdg_state_s *wdg_state; + + wdg_state = (FAR struct wdg_state_s *)*state; + + wdg_read_reset_cause(wdg_state); + + if (wdg_state->reset_cause != WDG_RESET_CAUSE_NO_KEEP_ALIVE) + { + return; + } + + wdg_init(wdg_state); + + wdg_state->reset_cause = WDG_RESET_CAUSE_CRITICAL_SECTION_BUSY_LOOP; + wdg_write_reset_cause(wdg_state); + + enter_critical_section(); + + /* Busy loop when disable interrupts */ + + while (1); +} + +/**************************************************************************** + * Name: wdg_wdentry + ****************************************************************************/ + +static void wdg_wdentry(wdparm_t arg) +{ + /* Busy loop in interrupts */ + + while (1); +} + +/**************************************************************************** + * Name: test_case_wdog_03 + * + * Description: + * This function is used to test whether the infinite loop will start + * watchdog after opening the interrupt. + ****************************************************************************/ + +static void test_case_wdog_03(FAR void **state) +{ + int ret; + static struct wdog_s wdog; + FAR struct wdg_state_s *wdg_state; + + wdg_state = (FAR struct wdg_state_s *)*state; + + wdg_read_reset_cause(wdg_state); + + if (wdg_state->reset_cause != WDG_RESET_CAUSE_CRITICAL_SECTION_BUSY_LOOP) + { + return; + } + + wdg_init(wdg_state); + + wdg_state->reset_cause = WDG_RESET_CAUSE_IRQ_BUSY_LOOP; + wdg_write_reset_cause(wdg_state); + + ret = wd_start(&wdog, 1, wdg_wdentry, (wdparm_t)0); + assert_return_code(ret, OK); + + /* Waiting for the reset... */ + + while (1) + { + sleep(1); + } +} + +/**************************************************************************** + * Name: test_case_wdog_04 + * + * Description: + * This function is used to test the watchdog driver interface. + ****************************************************************************/ + +static void test_case_wdog_04(FAR void **state) +{ + int dev_fd; + int ret; + uint32_t start_ms; + FAR struct wdg_state_s *wdg_state; + + wdg_state = (FAR struct wdg_state_s *)*state; + + wdg_read_reset_cause(wdg_state); + assert_int_equal(wdg_state->reset_cause, WDG_RESET_CAUSE_IRQ_BUSY_LOOP); + + remove(wdg_state->infopath); + + dev_fd = wdg_init(wdg_state); + + /* Get the starting time */ + + start_ms = get_timestamp(); + + /* Then ping */ + + while (get_time_elaps(start_ms) < wdg_state->pingtime) + { + /* Sleep for the requested amount of time */ + + usleep(wdg_state->pingdelay * 1000); + + /* Then ping */ + + ret = ioctl(dev_fd, WDIOC_KEEPALIVE, 0); + assert_return_code(ret, OK); + } + + /* Then stop pinging */ + + ret = ioctl(dev_fd, WDIOC_STOP, 0); + assert_return_code(ret, OK); + + close(dev_fd); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: main + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + struct wdg_state_s wdg_state = + { + .devpath = WDG_DEFAULT_DEV_PATH, + .infopath = WDG_DEFAULT_INFO_PATH, + .pingtime = WDG_DEFAULT_PINGTIMER, + .pingdelay = WDG_DEFAULT_PINGDELAY, + .timeout = WDG_DEFAULT_TIMEOUT + }; + + parse_commandline(&wdg_state, argc, argv); + + const struct CMUnitTest tests[] = + { + cmocka_unit_test_prestate(test_case_wdog_01, &wdg_state), + cmocka_unit_test_prestate(test_case_wdog_02, &wdg_state), + cmocka_unit_test_prestate(test_case_wdog_03, &wdg_state), + cmocka_unit_test_prestate(test_case_wdog_04, &wdg_state) + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +}