| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdlib.h> |
| #include <ztest.h> |
| #include <zephyr/types.h> |
| |
| struct timer_data { |
| int expire_cnt; |
| int stop_cnt; |
| int64_t timestamp; |
| }; |
| |
| #define DURATION 100 |
| #define PERIOD 50 |
| #define EXPIRE_TIMES 4 |
| #define WITHIN_ERROR(var, target, epsilon) (abs((target) - (var)) <= (epsilon)) |
| |
| /* ms can be converted precisely to ticks only when a ms is exactly |
| * represented by an integral number of ticks. If the conversion is |
| * not precise, then the reverse conversion of a difference in ms can |
| * end up being off by a tick depending on the relative error between |
| * the first and second ms conversion, and we need to adjust the |
| * tolerance interval. |
| */ |
| #define INEXACT_MS_CONVERT ((CONFIG_SYS_CLOCK_TICKS_PER_SEC % MSEC_PER_SEC) != 0) |
| |
| #if CONFIG_NRF_RTC_TIMER |
| /* On Nordic SOCs one or both of the tick and busy-wait clocks may |
| * derive from sources that have slews that sum to +/- 13%. |
| */ |
| #define BUSY_TICK_SLEW_PPM 130000U |
| #else |
| /* On other platforms assume the clocks are perfectly aligned. */ |
| #define BUSY_TICK_SLEW_PPM 0U |
| #endif |
| #define PPM_DIVISOR 1000000U |
| |
| /* If the tick clock is faster or slower than the busywait clock the |
| * remaining time for a partially elapsed timer in ticks will be |
| * larger or smaller than expected by a value that depends on the slew |
| * between the two clocks. Produce a maximum error for a given |
| * duration in microseconds. |
| */ |
| #define BUSY_SLEW_THRESHOLD_TICKS(_us) \ |
| k_us_to_ticks_ceil32((_us) * BUSY_TICK_SLEW_PPM \ |
| / PPM_DIVISOR) |
| |
| static void duration_expire(struct k_timer *timer); |
| static void duration_stop(struct k_timer *timer); |
| |
| /** TESTPOINT: init timer via K_TIMER_DEFINE */ |
| K_TIMER_DEFINE(ktimer, duration_expire, duration_stop); |
| |
| static struct k_timer duration_timer; |
| static struct k_timer period0_timer; |
| static struct k_timer expire_timer; |
| static struct k_timer sync_timer; |
| static struct k_timer periodicity_timer; |
| static struct k_timer status_timer; |
| static struct k_timer status_anytime_timer; |
| static struct k_timer status_sync_timer; |
| static struct k_timer remain_timer; |
| |
| static ZTEST_BMEM struct timer_data tdata; |
| |
| extern void test_time_conversions(void); |
| |
| #define TIMER_ASSERT(exp, tmr) \ |
| do { \ |
| if (!(exp)) { \ |
| k_timer_stop(tmr); \ |
| zassert_true(exp, NULL); \ |
| } \ |
| } while (0) |
| |
| static void init_timer_data(void) |
| { |
| tdata.expire_cnt = 0; |
| tdata.stop_cnt = 0; |
| |
| if (IS_ENABLED(CONFIG_MULTITHREADING)) { |
| k_usleep(1); /* align to tick */ |
| } |
| |
| tdata.timestamp = k_uptime_get(); |
| } |
| |
| static bool interval_check(int64_t interval, int64_t desired) |
| { |
| int64_t slop = INEXACT_MS_CONVERT ? 1 : 0; |
| |
| /* Tickless kernels will advance time inside of an ISR, so it |
| * is always possible (especially with high tick rates and |
| * slow CPUs) for us to arrive at the uptime check above too |
| * late to see a full period elapse before the next period. |
| * We can alias at both sides of the interval, so two |
| * one-ticks deltas (NOT one two-tick delta!) |
| */ |
| if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| slop += 2 * k_ticks_to_ms_ceil32(1); |
| } |
| |
| if (abs(interval - desired) > slop) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* entry routines */ |
| static void duration_expire(struct k_timer *timer) |
| { |
| /** TESTPOINT: expire function */ |
| int64_t interval = k_uptime_delta(&tdata.timestamp); |
| |
| tdata.expire_cnt++; |
| if (tdata.expire_cnt == 1) { |
| TIMER_ASSERT(interval_check(interval, DURATION), timer); |
| } else { |
| TIMER_ASSERT(interval_check(interval, PERIOD), timer); |
| } |
| |
| if (tdata.expire_cnt >= EXPIRE_TIMES) { |
| k_timer_stop(timer); |
| } |
| } |
| |
| static void duration_stop(struct k_timer *timer) |
| { |
| tdata.stop_cnt++; |
| } |
| |
| static void period0_expire(struct k_timer *timer) |
| { |
| tdata.expire_cnt++; |
| } |
| |
| static void status_expire(struct k_timer *timer) |
| { |
| /** TESTPOINT: status get upon timer expired */ |
| TIMER_ASSERT(k_timer_status_get(timer) == 1, timer); |
| /** TESTPOINT: remaining get upon timer expired */ |
| TIMER_ASSERT(k_timer_remaining_get(timer) >= PERIOD, timer); |
| |
| if (tdata.expire_cnt >= EXPIRE_TIMES) { |
| k_timer_stop(timer); |
| } |
| } |
| |
| static void busy_wait_ms(int32_t ms) |
| { |
| k_busy_wait(ms*1000); |
| } |
| |
| static void status_stop(struct k_timer *timer) |
| { |
| /** TESTPOINT: remaining get upon timer stopped */ |
| TIMER_ASSERT(k_timer_remaining_get(timer) == 0, timer); |
| } |
| |
| /** |
| * @brief Tests for the Timer kernel object |
| * @defgroup kernel_timer_tests Timer |
| * @ingroup all_tests |
| * @{ |
| * @} |
| */ |
| |
| /** |
| * @brief Test duration and period of Timer |
| * |
| * Validates initial duration and period of timer. |
| * |
| * It initializes the timer with k_timer_init(), then starts the timer |
| * using k_timer_start() with specific initial duration and period. |
| * Stops the timer using k_timer_stop() and checks for proper completion |
| * of duration and period. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_stop(), k_uptime_get(), |
| * k_busy_wait() |
| */ |
| void test_timer_duration_period(void) |
| { |
| init_timer_data(); |
| /** TESTPOINT: init timer via k_timer_init */ |
| k_timer_start(&duration_timer, K_MSEC(DURATION), K_MSEC(PERIOD)); |
| busy_wait_ms(DURATION + PERIOD * EXPIRE_TIMES + PERIOD / 2); |
| /** TESTPOINT: check expire and stop times */ |
| TIMER_ASSERT(tdata.expire_cnt == EXPIRE_TIMES, &duration_timer); |
| TIMER_ASSERT(tdata.stop_cnt == 1, &duration_timer); |
| |
| k_timer_start(&duration_timer, K_FOREVER, K_MSEC(PERIOD)); |
| TIMER_ASSERT(tdata.stop_cnt == 1, &duration_timer); |
| /* cleanup environment */ |
| k_timer_stop(&duration_timer); |
| } |
| |
| /** |
| * |
| * @brief Test restart the timer |
| * |
| * @details Validates initial duration and period of timer. Start the timer with |
| * specific duration and period. Then starts the timer again, and check |
| * the status of timer. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_stop, k_uptime_get(), |
| * k_busy_wait() |
| * |
| */ |
| void test_timer_restart(void) |
| { |
| init_timer_data(); |
| k_timer_start(&status_anytime_timer, K_MSEC(DURATION), |
| K_MSEC(PERIOD)); |
| busy_wait_ms(DURATION + PERIOD * (EXPIRE_TIMES - 1) + PERIOD / 2); |
| |
| /** TESTPOINT: restart the timer */ |
| k_timer_start(&status_anytime_timer, K_MSEC(DURATION), |
| K_MSEC(PERIOD)); |
| /* Restart timer, timer's status is reset to zero */ |
| TIMER_ASSERT(k_timer_status_get(&status_anytime_timer) == 0, |
| &status_anytime_timer); |
| |
| /* cleanup environment */ |
| k_timer_stop(&status_anytime_timer); |
| } |
| |
| |
| /** |
| * @brief Test Timer with zero period value |
| * |
| * Validates initial timer duration, keeping timer period to zero. |
| * Basically, acting as one-shot timer. |
| * It initializes the timer with k_timer_init(), then starts the timer |
| * using k_timer_start() with specific initial duration and period as |
| * zero. Stops the timer using k_timer_stop() and checks for proper |
| * completion. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_stop(), k_uptime_get(), |
| * k_busy_wait() |
| */ |
| void test_timer_period_0(void) |
| { |
| init_timer_data(); |
| /** TESTPOINT: set period 0 */ |
| k_timer_start(&period0_timer, |
| K_TICKS(k_ms_to_ticks_floor32(DURATION) |
| - BUSY_SLEW_THRESHOLD_TICKS(DURATION |
| * USEC_PER_MSEC)), |
| K_NO_WAIT); |
| /* Need to wait at least 2 durations to ensure one-shot behavior. */ |
| busy_wait_ms(2 * DURATION + 1); |
| |
| /** TESTPOINT: ensure it is one-shot timer */ |
| TIMER_ASSERT((tdata.expire_cnt == 1) |
| || (INEXACT_MS_CONVERT |
| && (tdata.expire_cnt == 0)), &period0_timer); |
| TIMER_ASSERT(tdata.stop_cnt == 0, &period0_timer); |
| |
| /* cleanup environment */ |
| k_timer_stop(&period0_timer); |
| } |
| |
| /** |
| * @brief Test Timer with K_FOREVER period value |
| * |
| * Validates initial timer duration, keeping timer period to K_FOREVER. |
| * Basically, acting as one-shot timer. |
| * It initializes the timer with k_timer_init(), then starts the timer |
| * using k_timer_start() with specific initial duration and period as |
| * zero. Stops the timer using k_timer_stop() and checks for proper |
| * completion. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_stop(), k_uptime_get(), |
| * k_busy_wait() |
| */ |
| void test_timer_period_k_forever(void) |
| { |
| init_timer_data(); |
| /** TESTPOINT: set period 0 */ |
| k_timer_start( |
| &period0_timer, |
| K_TICKS(k_ms_to_ticks_floor32(DURATION) - |
| BUSY_SLEW_THRESHOLD_TICKS(DURATION * USEC_PER_MSEC)), |
| K_FOREVER); |
| tdata.timestamp = k_uptime_get(); |
| |
| /* Need to wait at least 2 durations to ensure one-shot behavior. */ |
| busy_wait_ms(2 * DURATION + 1); |
| |
| /** TESTPOINT: ensure it is one-shot timer */ |
| TIMER_ASSERT((tdata.expire_cnt == 1) || |
| (INEXACT_MS_CONVERT && (tdata.expire_cnt == 0)), |
| &period0_timer); |
| TIMER_ASSERT(tdata.stop_cnt == 0, &period0_timer); |
| |
| /* cleanup environment */ |
| k_timer_stop(&period0_timer); |
| } |
| |
| /** |
| * @brief Test Timer without any timer expiry callback function |
| * |
| * Validates timer without any expiry_fn(set to NULL). expiry_fn() is a |
| * function that is invoked each time the timer expires. |
| * |
| * It initializes the timer with k_timer_init(), then starts the timer |
| * using k_timer_start(). Stops the timer using k_timer_stop() and |
| * checks for expire_cnt to zero, as expiry_fn was not defined at all. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_stop(), k_uptime_get(), |
| * k_busy_wait() |
| */ |
| void test_timer_expirefn_null(void) |
| { |
| init_timer_data(); |
| /** TESTPOINT: expire function NULL */ |
| k_timer_start(&expire_timer, K_MSEC(DURATION), K_MSEC(PERIOD)); |
| busy_wait_ms(DURATION + PERIOD * EXPIRE_TIMES + PERIOD / 2); |
| |
| k_timer_stop(&expire_timer); |
| /** TESTPOINT: expire handler is not invoked */ |
| TIMER_ASSERT(tdata.expire_cnt == 0, &expire_timer); |
| /** TESTPOINT: stop handler is invoked */ |
| TIMER_ASSERT(tdata.stop_cnt == 1, &expire_timer); |
| |
| /* cleanup environment */ |
| k_timer_stop(&expire_timer); |
| } |
| |
| /* Wait for the next expiration of an OS timer tick, to synchronize |
| * test start |
| */ |
| static void tick_sync(void) |
| { |
| k_timer_start(&sync_timer, K_NO_WAIT, K_MSEC(1)); |
| k_timer_status_sync(&sync_timer); |
| k_timer_stop(&sync_timer); |
| } |
| |
| /** |
| * @brief Test to check timer periodicity |
| * |
| * Timer test to check for the predictability with which the timer |
| * expires depending on the period configured. |
| * |
| * It initializes the timer with k_timer_init(), then starts the timer |
| * using k_timer_start() with specific period. It resets the timer’s |
| * status to zero with k_timer_status_sync and identifies the delta |
| * between each timer expiry to check for the timer expiration period |
| * correctness. Finally, stops the timer using k_timer_stop(). |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_status_sync(), |
| * k_timer_stop(), k_uptime_get(), k_uptime_delta() |
| */ |
| void test_timer_periodicity(void) |
| { |
| uint64_t period_ms = k_ticks_to_ms_floor64(k_ms_to_ticks_ceil32(PERIOD)); |
| int64_t delta; |
| |
| /* Start at a tick boundary, otherwise a tick expiring between |
| * the unlocked (and unlockable) start/uptime/sync steps below |
| * will throw off the math. |
| */ |
| tick_sync(); |
| |
| init_timer_data(); |
| /** TESTPOINT: set duration 0 */ |
| k_timer_start(&periodicity_timer, K_NO_WAIT, K_MSEC(PERIOD)); |
| |
| /* clear the expiration that would have happened due to |
| * whatever duration that was set. Since timer is likely |
| * to fire before call to k_timer_status_sync(), we have |
| * to synchronize twice to ensure that the timestamp will |
| * be fetched as soon as possible after timer firing. |
| */ |
| k_timer_status_sync(&periodicity_timer); |
| k_timer_status_sync(&periodicity_timer); |
| tdata.timestamp = k_uptime_get(); |
| |
| for (int i = 0; i < EXPIRE_TIMES; i++) { |
| /** TESTPOINT: expired times returned by status sync */ |
| TIMER_ASSERT(k_timer_status_sync(&periodicity_timer) == 1, |
| &periodicity_timer); |
| |
| delta = k_uptime_delta(&tdata.timestamp); |
| |
| /** TESTPOINT: check if timer fired within 1ms of the |
| * expected period (firing time). |
| * |
| * Please note, that expected firing time is not the |
| * one requested, as the kernel uses the ticks to manage |
| * time. The actual period will be equal to [tick time] |
| * multiplied by k_ms_to_ticks_ceil32(PERIOD). |
| * |
| * In the case of inexact conversion the delta will |
| * occasionally be one less than the expected number. |
| */ |
| TIMER_ASSERT(WITHIN_ERROR(delta, period_ms, 1) |
| || (INEXACT_MS_CONVERT |
| && (delta == period_ms - 1)), |
| &periodicity_timer); |
| } |
| |
| /* cleanup environment */ |
| k_timer_stop(&periodicity_timer); |
| } |
| |
| /** |
| * @brief Test Timer status and time remaining before next expiry |
| * |
| * Timer test to validate timer status and next trigger expiry time |
| * |
| * It initializes the timer with k_timer_init(), then starts the timer |
| * using k_timer_start() and checks for timer current status with |
| * k_timer_status_get() and remaining time before next expiry using |
| * k_timer_remaining_get(). Stops the timer using k_timer_stop(). |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_status_get(), |
| * k_timer_remaining_get(), k_timer_stop() |
| */ |
| void test_timer_status_get(void) |
| { |
| init_timer_data(); |
| k_timer_start(&status_timer, K_MSEC(DURATION), K_MSEC(PERIOD)); |
| /** TESTPOINT: status get upon timer starts */ |
| TIMER_ASSERT(k_timer_status_get(&status_timer) == 0, &status_timer); |
| /** TESTPOINT: remaining get upon timer starts */ |
| TIMER_ASSERT(k_timer_remaining_get(&status_timer) >= DURATION / 2, |
| &status_timer); |
| |
| /* cleanup environment */ |
| k_timer_stop(&status_timer); |
| } |
| |
| /** |
| * @brief Test Timer status randomly after certain duration |
| * |
| * Validate timer status function using k_timer_status_get(). |
| * |
| * It initializes the timer with k_timer_init(), then starts the timer |
| * using k_timer_start() with specific initial duration and period. |
| * Checks for timer status randomly after certain duration. |
| * Stops the timer using k_timer_stop(). |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_status_get(), |
| * k_timer_stop(), k_busy_wait() |
| */ |
| void test_timer_status_get_anytime(void) |
| { |
| init_timer_data(); |
| k_timer_start(&status_anytime_timer, K_MSEC(DURATION), |
| K_MSEC(PERIOD)); |
| busy_wait_ms(DURATION + PERIOD * (EXPIRE_TIMES - 1) + PERIOD / 2); |
| |
| /** TESTPOINT: status get at any time */ |
| TIMER_ASSERT(k_timer_status_get(&status_anytime_timer) == EXPIRE_TIMES, |
| &status_anytime_timer); |
| busy_wait_ms(PERIOD); |
| TIMER_ASSERT(k_timer_status_get(&status_anytime_timer) == 1, |
| &status_anytime_timer); |
| |
| /* cleanup environment */ |
| k_timer_stop(&status_anytime_timer); |
| } |
| |
| /** |
| * @brief Test Timer thread synchronization |
| * |
| * Validate thread synchronization by blocking the calling thread until |
| * the timer expires. |
| * |
| * It initializes the timer with k_timer_init(), then starts the timer |
| * using k_timer_start() and checks timer status with |
| * k_timer_status_sync() for thread synchronization with expiry count. |
| * Stops the timer using k_timer_stop. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_status_sync(), |
| * k_timer_stop() |
| */ |
| void test_timer_status_sync(void) |
| { |
| init_timer_data(); |
| k_timer_start(&status_sync_timer, K_MSEC(DURATION), K_MSEC(PERIOD)); |
| |
| for (int i = 0; i < EXPIRE_TIMES; i++) { |
| /** TESTPOINT: check timer not expire */ |
| TIMER_ASSERT(tdata.expire_cnt == i, &status_sync_timer); |
| /** TESTPOINT: expired times returned by status sync */ |
| TIMER_ASSERT(k_timer_status_sync(&status_sync_timer) == 1, |
| &status_sync_timer); |
| /** TESTPOINT: check timer not expire */ |
| TIMER_ASSERT(tdata.expire_cnt == (i + 1), &status_sync_timer); |
| } |
| |
| init_timer_data(); |
| k_timer_start(&status_sync_timer, K_MSEC(DURATION), K_MSEC(PERIOD)); |
| busy_wait_ms(PERIOD*2); |
| zassert_true(k_timer_status_sync(&status_sync_timer), NULL); |
| |
| /* cleanup environment */ |
| k_timer_stop(&status_sync_timer); |
| zassert_false(k_timer_status_sync(&status_sync_timer), NULL); |
| } |
| |
| /** |
| * @brief Test statically defined Timer init |
| * |
| * Validate statically defined timer init using K_TIMER_DEFINE |
| * |
| * It creates prototype of K_TIMER_DEFINE to statically define timer |
| * init and starts the timer with k_timer_start() with specific initial |
| * duration and period. Stops the timer using k_timer_stop() and checks |
| * for proper completion of duration and period. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_start(), K_TIMER_DEFINE(), k_timer_stop() |
| * k_uptime_get(), k_busy_wait() |
| */ |
| void test_timer_k_define(void) |
| { |
| init_timer_data(); |
| /** TESTPOINT: init timer via k_timer_init */ |
| k_timer_start(&ktimer, K_MSEC(DURATION), K_MSEC(PERIOD)); |
| busy_wait_ms(DURATION + PERIOD * EXPIRE_TIMES + PERIOD / 2); |
| |
| /** TESTPOINT: check expire and stop times */ |
| TIMER_ASSERT(tdata.expire_cnt == EXPIRE_TIMES, &ktimer); |
| TIMER_ASSERT(tdata.stop_cnt == 1, &ktimer); |
| |
| /* cleanup environment */ |
| k_timer_stop(&ktimer); |
| |
| init_timer_data(); |
| /** TESTPOINT: init timer via k_timer_init */ |
| k_timer_start(&ktimer, K_MSEC(DURATION), K_MSEC(PERIOD)); |
| |
| /* Call the k_timer_start() again to make sure that |
| * the initial timeout request gets cancelled and new |
| * one will get added. |
| */ |
| busy_wait_ms(DURATION / 2); |
| k_timer_start(&ktimer, K_MSEC(DURATION), K_MSEC(PERIOD)); |
| tdata.timestamp = k_uptime_get(); |
| busy_wait_ms(DURATION + PERIOD * EXPIRE_TIMES + PERIOD / 2); |
| |
| /** TESTPOINT: check expire and stop times */ |
| TIMER_ASSERT(tdata.expire_cnt == EXPIRE_TIMES, &ktimer); |
| TIMER_ASSERT(tdata.stop_cnt == 1, &ktimer); |
| |
| /* cleanup environment */ |
| k_timer_stop(&ktimer); |
| } |
| |
| static void user_data_timer_handler(struct k_timer *timer); |
| |
| K_TIMER_DEFINE(timer0, user_data_timer_handler, NULL); |
| K_TIMER_DEFINE(timer1, user_data_timer_handler, NULL); |
| K_TIMER_DEFINE(timer2, user_data_timer_handler, NULL); |
| K_TIMER_DEFINE(timer3, user_data_timer_handler, NULL); |
| K_TIMER_DEFINE(timer4, user_data_timer_handler, NULL); |
| |
| static ZTEST_DMEM struct k_timer *user_data_timer[5] = { |
| &timer0, &timer1, &timer2, &timer3, &timer4 |
| }; |
| |
| static const intptr_t user_data[5] = { 0x1337, 0xbabe, 0xd00d, 0xdeaf, 0xfade }; |
| |
| static ZTEST_BMEM int user_data_correct[5]; |
| |
| static void user_data_timer_handler(struct k_timer *timer) |
| { |
| int timer_num = timer == user_data_timer[0] ? 0 : |
| timer == user_data_timer[1] ? 1 : |
| timer == user_data_timer[2] ? 2 : |
| timer == user_data_timer[3] ? 3 : |
| timer == user_data_timer[4] ? 4 : -1; |
| |
| if (timer_num == -1) { |
| return; |
| } |
| |
| intptr_t data_retrieved = (intptr_t)k_timer_user_data_get(timer); |
| |
| user_data_correct[timer_num] = user_data[timer_num] == data_retrieved; |
| } |
| |
| /** |
| * @brief Test user-specific data associated with timer |
| * |
| * Validate user-specific data associated with timer |
| * |
| * It creates prototype of K_TIMER_DEFINE and starts the timer using |
| * k_timer_start() with specific initial duration, along with associated |
| * user data using k_timer_user_data_set and k_timer_user_data_get(). |
| * Stops the timer using k_timer_stop() and checks for correct data |
| * retrieval after timer completion. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see K_TIMER_DEFINE(), k_timer_user_data_set(), k_timer_start(), |
| * k_timer_user_data_get(), k_timer_stop() |
| */ |
| void test_timer_user_data(void) |
| { |
| int ii; |
| |
| for (ii = 0; ii < 5; ii++) { |
| intptr_t check; |
| |
| k_timer_user_data_set(user_data_timer[ii], |
| (void *)user_data[ii]); |
| check = (intptr_t)k_timer_user_data_get(user_data_timer[ii]); |
| |
| zassert_true(check == user_data[ii], NULL); |
| } |
| |
| for (ii = 0; ii < 5; ii++) { |
| k_timer_start(user_data_timer[ii], K_MSEC(50 + ii * 50), |
| K_NO_WAIT); |
| } |
| |
| uint32_t wait_ms = 50 * ii + 50; |
| |
| if (IS_ENABLED(CONFIG_MULTITHREADING)) { |
| k_msleep(wait_ms); |
| } else { |
| uint32_t wait_us = 1000 * wait_ms; |
| |
| k_busy_wait(wait_us + (wait_us * BUSY_TICK_SLEW_PPM) / PPM_DIVISOR); |
| } |
| |
| for (ii = 0; ii < 5; ii++) { |
| k_timer_stop(user_data_timer[ii]); |
| } |
| |
| for (ii = 0; ii < 5; ii++) { |
| zassert_true(user_data_correct[ii], NULL); |
| } |
| } |
| |
| /** |
| * @brief Test accuracy of k_timer_remaining_get() |
| * |
| * Validate countdown of time to expiration |
| * |
| * Starts a timer, busy-waits for half the DURATION, then checks the |
| * remaining time to expiration and stops the timer. The remaining time |
| * should reflect the passage of at least the busy-wait interval. |
| * |
| * @ingroup kernel_timer_tests |
| * |
| * @see k_timer_init(), k_timer_start(), k_timer_stop(), |
| * k_timer_remaining_get() |
| */ |
| |
| void test_timer_remaining(void) |
| { |
| uint32_t dur_ticks = k_ms_to_ticks_ceil32(DURATION); |
| uint32_t target_rem_ticks = k_ms_to_ticks_ceil32(DURATION / 2); |
| uint32_t rem_ms, rem_ticks, exp_ticks; |
| int32_t delta_ticks; |
| uint32_t slew_ticks; |
| uint64_t now; |
| |
| |
| init_timer_data(); |
| k_timer_start(&remain_timer, K_MSEC(DURATION), K_NO_WAIT); |
| busy_wait_ms(DURATION / 2); |
| rem_ticks = k_timer_remaining_ticks(&remain_timer); |
| now = k_uptime_ticks(); |
| rem_ms = k_timer_remaining_get(&remain_timer); |
| exp_ticks = k_timer_expires_ticks(&remain_timer); |
| k_timer_stop(&remain_timer); |
| TIMER_ASSERT(tdata.expire_cnt == 0, &remain_timer); |
| TIMER_ASSERT(tdata.stop_cnt == 1, &remain_timer); |
| |
| /* |
| * While the busy_wait_ms() works with the maximum possible resolution, |
| * the k_timer api is limited by the system tick abstraction. As result |
| * the value obtained through k_timer_remaining_get() could be larger |
| * than actual remaining time with maximum error equal to one tick. |
| */ |
| zassert_true(rem_ms <= (DURATION / 2) + k_ticks_to_ms_floor64(1), |
| NULL); |
| |
| /* Half the value of DURATION in ticks may not be the value of |
| * half DURATION in ticks, when DURATION/2 is not an integer |
| * multiple of ticks, so target_rem_ticks is used rather than |
| * dur_ticks/2. Also set a threshold based on expected clock |
| * skew. |
| */ |
| delta_ticks = (int32_t)(rem_ticks - target_rem_ticks); |
| slew_ticks = BUSY_SLEW_THRESHOLD_TICKS(DURATION * USEC_PER_MSEC / 2U); |
| zassert_true(abs(delta_ticks) <= MAX(slew_ticks, 1U), |
| "tick/busy slew %d larger than test threshold %u", |
| delta_ticks, slew_ticks); |
| |
| /* Note +1 tick precision: even though we're calculating in |
| * ticks, we're waiting in k_busy_wait(), not for a timer |
| * interrupt, so it's possible for that to take 1 tick longer |
| * than expected on systems where the requested microsecond |
| * delay cannot be exactly represented as an integer number of |
| * ticks. |
| */ |
| zassert_true(((int64_t)exp_ticks - (int64_t)now) <= (dur_ticks / 2) + 1, |
| NULL); |
| } |
| |
| void test_timeout_abs(void) |
| { |
| #ifdef CONFIG_TIMEOUT_64BIT |
| const uint64_t exp_ms = 10000000; |
| uint64_t rem_ticks; |
| uint64_t exp_ticks = k_ms_to_ticks_ceil64(exp_ms); |
| k_timeout_t t = K_TIMEOUT_ABS_TICKS(exp_ticks), t2; |
| uint64_t t0, t1; |
| |
| /* Check the other generator macros to make sure they produce |
| * the same (whiteboxed) converted values |
| */ |
| t2 = K_TIMEOUT_ABS_MS(exp_ms); |
| zassert_true(t2.ticks == t.ticks, NULL); |
| |
| t2 = K_TIMEOUT_ABS_US(1000 * exp_ms); |
| zassert_true(t2.ticks == t.ticks, NULL); |
| |
| t2 = K_TIMEOUT_ABS_NS(1000 * 1000 * exp_ms); |
| zassert_true(t2.ticks == t.ticks, NULL); |
| |
| t2 = K_TIMEOUT_ABS_CYC(k_ms_to_cyc_ceil64(exp_ms)); |
| zassert_true(t2.ticks == t.ticks, NULL); |
| |
| /* Now set the timeout and make sure the expiration time is |
| * correct vs. current time. Tick units and tick alignment |
| * makes this math exact, no slop is needed. Note that time |
| * is advancing always, so we add a retry condition to be sure |
| * that a tick advance did not happen between our reads of |
| * "now" and "expires". |
| */ |
| init_timer_data(); |
| k_timer_start(&remain_timer, t, K_FOREVER); |
| |
| if (IS_ENABLED(CONFIG_MULTITHREADING)) { |
| k_usleep(1); |
| } |
| |
| do { |
| t0 = k_uptime_ticks(); |
| rem_ticks = k_timer_remaining_ticks(&remain_timer); |
| t1 = k_uptime_ticks(); |
| } while (t0 != t1); |
| |
| zassert_true(t0 + rem_ticks == exp_ticks, |
| "Wrong remaining: now %lld rem %lld expires %lld (%d)", |
| (uint64_t)t0, (uint64_t)rem_ticks, (uint64_t)exp_ticks, |
| t0+rem_ticks-exp_ticks); |
| |
| k_timer_stop(&remain_timer); |
| #endif |
| } |
| |
| void test_sleep_abs(void) |
| { |
| if (!IS_ENABLED(CONFIG_MULTITHREADING)) { |
| /* k_sleep is not supported when multithreading is off. */ |
| return; |
| } |
| |
| const int sleep_ticks = 50; |
| int64_t start, end; |
| |
| k_usleep(1); /* tick align */ |
| |
| start = k_uptime_ticks(); |
| k_sleep(K_TIMEOUT_ABS_TICKS(start + sleep_ticks)); |
| end = k_uptime_ticks(); |
| |
| /* Systems with very high tick rates and/or slow idle resume |
| * (I've seen this on intel_adsp) can occasionally take more |
| * than a tick to return from k_sleep(). Set a 100us real |
| * time slop or more depending on the time to resume |
| */ |
| k_ticks_t late = end - (start + sleep_ticks); |
| |
| zassert_true(late >= 0 && late < k_us_to_ticks_ceil32(250), |
| "expected wakeup at %lld, got %lld (late %lld)", |
| start + sleep_ticks, end, late); |
| } |
| |
| static void timer_init(struct k_timer *timer, k_timer_expiry_t expiry_fn, |
| k_timer_stop_t stop_fn) |
| { |
| if (IS_ENABLED(CONFIG_MULTITHREADING)) { |
| k_object_access_grant(timer, k_current_get()); |
| } |
| |
| k_timer_init(timer, expiry_fn, stop_fn); |
| } |
| |
| void test_main(void) |
| { |
| timer_init(&duration_timer, duration_expire, duration_stop); |
| timer_init(&period0_timer, period0_expire, NULL); |
| timer_init(&expire_timer, NULL, duration_stop); |
| timer_init(&sync_timer, NULL, NULL); |
| timer_init(&periodicity_timer, NULL, NULL); |
| timer_init(&status_timer, status_expire, status_stop); |
| timer_init(&status_anytime_timer, NULL, NULL); |
| timer_init(&status_sync_timer, duration_expire, duration_stop); |
| timer_init(&remain_timer, duration_expire, duration_stop); |
| |
| if (IS_ENABLED(CONFIG_MULTITHREADING)) { |
| k_thread_access_grant(k_current_get(), &ktimer, &timer0, &timer1, |
| &timer2, &timer3, &timer4); |
| } |
| |
| ztest_test_suite(timer_api, |
| ztest_unit_test(test_time_conversions), |
| ztest_user_unit_test(test_timer_duration_period), |
| ztest_user_unit_test(test_timer_restart), |
| ztest_user_unit_test(test_timer_period_0), |
| ztest_user_unit_test(test_timer_period_k_forever), |
| ztest_user_unit_test(test_timer_expirefn_null), |
| ztest_user_unit_test(test_timer_periodicity), |
| ztest_user_unit_test(test_timer_status_get), |
| ztest_user_unit_test(test_timer_status_get_anytime), |
| ztest_user_unit_test(test_timer_status_sync), |
| ztest_user_unit_test(test_timer_k_define), |
| ztest_user_unit_test(test_timer_user_data), |
| ztest_user_unit_test(test_timer_remaining), |
| ztest_user_unit_test(test_timeout_abs), |
| ztest_user_unit_test(test_sleep_abs)); |
| ztest_run_test_suite(timer_api); |
| } |