From 2204c12c2f8a25c8908a711fa09f2ee0a9a5254b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Chru=C5=9Bci=C5=84ski?= Date: Mon, 15 Jul 2024 11:40:56 +0200 Subject: [PATCH] tests: kernel: timer: behavior: Add test for premature timeouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When k_timer_start() is called with relative timeout then it shall never expire before current time + that relative requested timeout. When k_timer_start() is called with absolute timeout then it shall never expire before that absolute timeout is reached. Add test suite which checks if timer do not expire before requested time. Test is checking 3 cases: - starting a timer from thread - starting a timer from timer expiration callback with variable delay before timer is started. - starting a timer from a timer expiration callback with variable delay after timer is started. In all 3 cases it is expected that timer will never expire before requested relative timeout (timeout is relative to k_timer_start call). Same set of scenarios are tested with absolute timers. Signed-off-by: Krzysztof Chruściński --- tests/kernel/timer/timer_behavior/src/main.c | 193 +++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/tests/kernel/timer/timer_behavior/src/main.c b/tests/kernel/timer/timer_behavior/src/main.c index 427b88b75d2aee0..64c3f2f6e35856c 100644 --- a/tests/kernel/timer/timer_behavior/src/main.c +++ b/tests/kernel/timer/timer_behavior/src/main.c @@ -6,9 +6,202 @@ #include +#define REPEAT 8 + +static const k_timeout_t test_timeout = K_MSEC(10); +static int64_t exp_cyc; + +enum timer_premature_test_mode { + FROM_THREAD, + FROM_IRQ, + FROM_IRQ_POST_DELAY, +}; + +/* Test data structure. */ +struct test_data { + struct k_timer timer; + uint32_t delay; + struct k_sem sem; + + /* In relative timeouts it hold timestamp when k_timer_start was called. */ + uint32_t start_ts; + + /* Ticks value when next time shall expire. */ + k_ticks_t ticks; + + /* Counting down number of iterations. */ + uint32_t iter; + + /* Run details when test failed. */ + uint32_t error_val; + uint32_t error_iter; + + /* Test mode. */ + enum timer_premature_test_mode mode; + + /* if true absolute timers are used. */ + bool abs; +}; + +static void timer_start(struct test_data *data) +{ + k_timeout_t t; + + if (data->abs) { + /* Get absolute timeout for test ticks from now. */ + data->ticks = k_cyc_to_ticks_near32(k_cycle_get_32()) + test_timeout.ticks; + t = K_TIMEOUT_ABS_TICKS(data->ticks); + } else { + /* Store the moment when clock is started. */ + data->start_ts = k_cycle_get_32(); + t = test_timeout; + } + + k_timer_start(&data->timer, t, K_NO_WAIT); +} + +/* Expiration callback */ +static void test_premature_handler(struct k_timer *timer) +{ + static const uint32_t delay_inc = k_ticks_to_us_near32(2) / REPEAT; + struct test_data *data = CONTAINER_OF(timer, struct test_data, timer); + int64_t now = k_cycle_get_32(); + int64_t diff = now - data->start_ts; + bool ok; + + /* Check if timer did not expire prematurely. */ + if (data->abs) { + ok = k_cyc_to_ticks_near32(now) >= (uint32_t)data->ticks; + } else { + ok = diff >= exp_cyc; + } + + if (!ok) { + /* Timeout occurred earlier than expected. Don't use zassert + * here because we are in the interrupt context. + */ + data->error_val = diff; + data->error_iter = REPEAT - data->iter; + data->iter = 0; + } else { + data->iter--; + } + + /* Busy wait simulates delay between kernel timeout expiration and the moment + * when next timer is started. In real life application it may occur due to + * multiple timers expiring simultaneously, some processing happens in + * the timer handler or higher priority interrupt preempting current context. + */ + if (data->mode != FROM_IRQ_POST_DELAY) { + k_busy_wait(data->delay); + } + + data->delay += delay_inc; + + if (data->iter == 0) { + /* Test end. Wake up test thread. */ + k_sem_give(&data->sem); + } else if ((data->mode == FROM_IRQ) || (data->mode == FROM_IRQ_POST_DELAY)) { + timer_start(data); + if (data->mode == FROM_IRQ_POST_DELAY) { + /* Simulated delay. */ + k_busy_wait(data->delay); + } + } +} + +/* Test starts same single shot timer number of times. Depending on the test mode + * next timer is started from thread or from the expiration callback. + * + * @param mode Test mode. + * @param abs If true then absolute timers are started else relative. + */ +static void test_timer_premature(enum timer_premature_test_mode mode, bool abs) +{ + int err; + static struct test_data tdata; + + memset(&tdata, 0, sizeof(tdata)); + tdata.abs = abs; + tdata.mode = mode; + tdata.iter = REPEAT; + exp_cyc = k_ticks_to_cyc_near64(test_timeout.ticks); + k_timer_init(&tdata.timer, test_premature_handler, NULL); + k_sem_init(&tdata.sem, 0, 1); + + if (mode == FROM_THREAD) { + for (int i = 0; i < REPEAT; i++) { + timer_start(&tdata); + k_msleep(k_ticks_to_ms_ceil64(test_timeout.ticks) + 5); + } + } else { + timer_start(&tdata); + } + + uint64_t ticks = test_timeout.ticks; + uint32_t total_timeout = (uint32_t)k_ticks_to_ms_ceil64(ticks) * REPEAT + 10; + + err = k_sem_take(&tdata.sem, K_MSEC(total_timeout)); + zassert_equal(err, 0); + + zassert_equal(tdata.error_val, 0, + "Test failed, on %d iteration timer expired earlier than expected %d, exp:%d", + tdata.error_iter, tdata.error_val, exp_cyc); + +} + +/* Relative timer started from the expiration handler with variable delay added + * after timer start. + */ +ZTEST(timer_premature, test_timer_from_irq_post_delay) +{ + test_timer_premature(FROM_IRQ_POST_DELAY, false); +} + +/* Relative timer started from the expiration handler with variable delay added + * before timer start. + */ +ZTEST(timer_premature, test_timer_from_irq) +{ + test_timer_premature(FROM_IRQ, false); +} + +/* Relative timer started from the thread. + */ +ZTEST(timer_premature, test_timer_from_thread) +{ + test_timer_premature(FROM_THREAD, false); +} + +/* Absolute timer started from the expiration handler with variable delay added + * after timer start. + */ +ZTEST(timer_premature, test_abs_timer_from_irq_post_delay) +{ + test_timer_premature(FROM_IRQ_POST_DELAY, true); +} + +/* Absolute timer started from the expiration handler with variable delay added + * before timer start. + */ +ZTEST(timer_premature, test_abs_timer_from_irq) +{ + test_timer_premature(FROM_IRQ, true); +} + +/* Absolute timer started from the thread. + */ +ZTEST(timer_premature, test_abs_timer_from_thread) +{ + test_timer_premature(FROM_THREAD, true); +} + +ZTEST_SUITE(timer_premature, NULL, NULL, NULL, NULL, NULL); + void test_main(void) { ztest_run_test_suite(timer_jitter_drift, false, 1, 1); + ztest_run_test_suite(timer_premature, false, 1, 1); #ifndef CONFIG_TIMER_EXTERNAL_TEST ztest_run_test_suite(timer_tick_train, false, 1, 1); #endif