From f6f24af00aa83887adbd388e5ac320d046d6ac3e Mon Sep 17 00:00:00 2001 From: Konstantin Kondrashov Date: Mon, 8 Jun 2026 15:37:28 +0300 Subject: [PATCH] feat(esp_timer): Adds support Linux target --- components/esp_timer/CMakeLists.txt | 62 +-- components/esp_timer/Kconfig | 8 +- .../private_include/esp_timer_impl.h | 12 +- components/esp_timer/src/esp_timer.c | 25 +- .../esp_timer/src/esp_timer_impl_apple.c | 180 ++++++++ .../esp_timer/src/esp_timer_impl_common.c | 15 +- .../esp_timer/src/esp_timer_impl_linux.c | 383 ++++++++++++++++++ components/esp_timer/src/system_time.c | 4 + .../esp_timer/test_apps/.build-test-rules.yml | 5 + components/esp_timer/test_apps/CMakeLists.txt | 4 + components/esp_timer/test_apps/README.md | 4 +- .../esp_timer/test_apps/main/CMakeLists.txt | 23 +- .../esp_timer/test_apps/main/test_app_main.c | 8 +- .../esp_timer/test_apps/main/test_esp_timer.c | 92 ++++- .../test_apps/pytest_esp_timer_ut.py | 9 +- .../esp_timer/test_apps/sdkconfig.ci.default | 1 + .../esp_timer/test_apps/sdkconfig.defaults | 1 - .../test_apps/sdkconfig.defaults.linux | 2 + .../test_apps/sdkconfig.defaults.no_linux | 1 + examples/system/.build-test-rules.yml | 2 + examples/system/esp_timer/CMakeLists.txt | 6 + examples/system/esp_timer/README.md | 50 +-- examples/system/esp_timer/main/CMakeLists.txt | 3 +- .../esp_timer/main/esp_timer_example_main.c | 26 +- examples/system/esp_timer/pytest_esp_timer.py | 137 ++++--- examples/system/esp_timer/sdkconfig.defaults | 7 - .../esp_timer/sdkconfig.defaults.esp32c2 | 3 + .../esp_timer/sdkconfig.defaults.no_linux | 2 + .../components/test_utils/CMakeLists.txt | 13 +- .../test_utils/ref_clock_impl_linux.c | 28 ++ .../components/test_utils/test_runner.c | 4 + .../components/test_utils/test_utils.c | 4 + 32 files changed, 965 insertions(+), 159 deletions(-) create mode 100644 components/esp_timer/src/esp_timer_impl_apple.c create mode 100644 components/esp_timer/src/esp_timer_impl_linux.c create mode 100644 components/esp_timer/test_apps/sdkconfig.ci.default create mode 100644 components/esp_timer/test_apps/sdkconfig.defaults.linux create mode 100644 components/esp_timer/test_apps/sdkconfig.defaults.no_linux create mode 100644 examples/system/esp_timer/sdkconfig.defaults.esp32c2 create mode 100644 examples/system/esp_timer/sdkconfig.defaults.no_linux create mode 100644 tools/test_apps/components/test_utils/ref_clock_impl_linux.c diff --git a/components/esp_timer/CMakeLists.txt b/components/esp_timer/CMakeLists.txt index 8ff75b2c1b6..c0d9a6932c3 100644 --- a/components/esp_timer/CMakeLists.txt +++ b/components/esp_timer/CMakeLists.txt @@ -1,29 +1,43 @@ idf_build_get_property(target IDF_TARGET) -if(${target} STREQUAL "linux") - idf_component_register(INCLUDE_DIRS include) -else() - set(srcs "src/esp_timer.c" - "src/esp_timer_init.c" - "src/ets_timer_legacy.c" - "src/system_time.c" - "src/esp_timer_impl_common.c") +set(priv_requires "") - if(CONFIG_ESP_TIMER_IMPL_TG0_LAC) - list(APPEND srcs "src/esp_timer_impl_lac.c") - elseif(CONFIG_ESP_TIMER_IMPL_SYSTIMER) - list(APPEND srcs "src/esp_timer_impl_systimer.c") +set(srcs "src/esp_timer.c" + "src/esp_timer_init.c" + "src/system_time.c" + "src/esp_timer_impl_common.c") + +if(CONFIG_ESP_TIMER_IMPL_TG0_LAC) + list(APPEND srcs "src/esp_timer_impl_lac.c") +elseif(CONFIG_ESP_TIMER_IMPL_SYSTIMER) + list(APPEND srcs "src/esp_timer_impl_systimer.c") +elseif(CONFIG_ESP_TIMER_IMPL_LINUX) + if(APPLE) + list(APPEND srcs "src/esp_timer_impl_apple.c") + else() + list(APPEND srcs "src/esp_timer_impl_linux.c") endif() - - if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM) - list(APPEND srcs "src/esp_timer_etm.c") - endif() - - idf_component_register(SRCS "${srcs}" - INCLUDE_DIRS include - PRIV_REQUIRES esp_hal_timg esp_hal_systimer - PRIV_INCLUDE_DIRS private_include) - - # Forces the linker to include esp_timer_init.c - target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_timer_init_include_func") +endif() + +if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM) + list(APPEND srcs "src/esp_timer_etm.c") +endif() + +if(NOT ${target} STREQUAL "linux") + list(APPEND srcs "src/ets_timer_legacy.c") + list(APPEND priv_requires esp_hal_timg esp_hal_systimer) +endif() + + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS include + PRIV_REQUIRES "${priv_requires}" + PRIV_INCLUDE_DIRS private_include) + +# Forces the linker to include esp_timer_init.c +target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_timer_init_include_func") + +if(${target} STREQUAL "linux" AND NOT APPLE) + # Link with librt, which contains POSIX real-time APIs, for example: clock_gettime, clock_settime, etc. + target_link_libraries(${COMPONENT_LIB} PRIVATE "rt") endif() diff --git a/components/esp_timer/Kconfig b/components/esp_timer/Kconfig index 9ffe0f5d1e8..5a0e6ff1f13 100644 --- a/components/esp_timer/Kconfig +++ b/components/esp_timer/Kconfig @@ -120,6 +120,12 @@ menu "ESP Timer (High Resolution Timer)" config ESP_TIMER_IMPL_SYSTIMER bool default y - depends on !IDF_TARGET_ESP32 + depends on !IDF_TARGET_ESP32 && !IDF_TARGET_LINUX + + config ESP_TIMER_IMPL_LINUX + bool + default y + depends on IDF_TARGET_LINUX + select ESP_TIME_FUNCS_USE_ESP_TIMER endmenu # esp_timer diff --git a/components/esp_timer/private_include/esp_timer_impl.h b/components/esp_timer/private_include/esp_timer_impl.h index 2e32f43d903..e41174e8b81 100644 --- a/components/esp_timer/private_include/esp_timer_impl.h +++ b/components/esp_timer/private_include/esp_timer_impl.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,6 +19,8 @@ #include #include "esp_err.h" #include "esp_intr_alloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "sdkconfig.h" #if CONFIG_ESP_TIMER_IN_IRAM @@ -148,3 +150,11 @@ void esp_timer_impl_init_system_time(void); */ void esp_timer_impl_try_to_set_next_alarm(void); #endif + +/** + * @brief Get the task handle used to dispatch esp_timer task callbacks. + * + * This is used by linux timer implementations to notify the esp_timer task + * without exposing the task handle outside the component implementation. + */ +TaskHandle_t esp_timer_impl_get_timer_task_handle(void); diff --git a/components/esp_timer/src/esp_timer.c b/components/esp_timer/src/esp_timer.c index cf8d8abd83c..5be84f50aa2 100644 --- a/components/esp_timer/src/esp_timer.c +++ b/components/esp_timer/src/esp_timer.c @@ -343,7 +343,7 @@ esp_err_t esp_timer_stop_blocking(esp_timer_handle_t timer, uint32_t timeout_tic TickType_t start_time = xTaskGetTickCount(); while (is_callback_running(timer, dispatch_method)) { - if (timeout_ticks != portMAX_DELAY) { + if (timeout_ticks != (uint32_t) portMAX_DELAY) { TickType_t elapsed = xTaskGetTickCount() - start_time; if (elapsed >= timeout_ticks) { return ESP_ERR_TIMEOUT; @@ -551,6 +551,12 @@ static void timer_task(void* arg) while (true) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // all deferred events are processed at a time +#if CONFIG_ESP_TIMER_IMPL_LINUX && CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD + esp_timer_impl_try_to_set_next_alarm(); + if (timer_process_alarm(ESP_TIMER_ISR)) { + continue; + } +#endif timer_process_alarm(ESP_TIMER_TASK); } } @@ -558,11 +564,14 @@ static void timer_task(void* arg) #ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD ESP_TIMER_IRAM_ATTR void esp_timer_isr_dispatch_need_yield(void) { +#ifndef CONFIG_ESP_TIMER_IMPL_LINUX assert(xPortInIsrContext()); +#endif s_isr_dispatch_need_yield = pdTRUE; } #endif +#ifndef CONFIG_ESP_TIMER_IMPL_LINUX static void ESP_TIMER_IRAM_ATTR timer_alarm_handler(void* arg) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; @@ -583,12 +592,18 @@ static void ESP_TIMER_IRAM_ATTR timer_alarm_handler(void* arg) portYIELD_FROM_ISR(); } } +#endif // !CONFIG_ESP_TIMER_IMPL_LINUX static ESP_TIMER_IRAM_ATTR inline bool is_initialized(void) { return s_timer_task != NULL; } +TaskHandle_t esp_timer_impl_get_timer_task_handle(void) +{ + return s_timer_task; +} + static esp_err_t init_timer_task(void) { esp_err_t err = ESP_OK; @@ -631,7 +646,11 @@ esp_err_t esp_timer_init(void) } #endif // CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY if (err == ESP_OK) { +#ifndef CONFIG_ESP_TIMER_IMPL_LINUX err = esp_timer_impl_init(&timer_alarm_handler); +#else + err = esp_timer_impl_init(NULL); +#endif if (err != ESP_OK) { ESP_EARLY_LOGE(TAG, "ISR init failed"); deinit_timer_task(); @@ -706,13 +725,13 @@ static void print_timer_info(esp_timer_handle_t t, char** dst, size_t* dst_size) cb = snprintf(*dst, *dst_size, "timer@%-10p ", t); } - cb += snprintf(*dst + cb, *dst_size - cb, "%-10lld %-12lld %-12d %-12d %-12d %-12lld\n", + cb += snprintf(*dst + cb, *dst_size - cb, "%-10" PRIu64" %-12" PRIu64" %-12zu %-12zu %-12zu %-12" PRIu64"\n", (uint64_t)t->period, t->alarm, t->times_armed, t->times_triggered, t->times_skipped, t->total_callback_run_time); /* keep this in sync with the format string, used in esp_timer_dump */ #define TIMER_INFO_LINE_LEN 103 #else - size_t cb = snprintf(*dst, *dst_size, "timer@%-14p %-10lld %-12lld\n", t, (uint64_t)t->period, t->alarm); + size_t cb = snprintf(*dst, *dst_size, "timer@%-14p %-10" PRIu64" %-12" PRIu64"\n", t, (uint64_t)t->period, t->alarm); #define TIMER_INFO_LINE_LEN 47 #endif *dst += cb; diff --git a/components/esp_timer/src/esp_timer_impl_apple.c b/components/esp_timer/src/esp_timer_impl_apple.c new file mode 100644 index 00000000000..50222f5cfdb --- /dev/null +++ b/components/esp_timer/src/esp_timer_impl_apple.c @@ -0,0 +1,180 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sys/param.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_timer_impl.h" +#include "esp_timer.h" +#include "esp_log.h" + +#include + +static const char *TAG = "esp_timer_impl"; + +/* Alarm values to generate interrupt on match */ +extern uint64_t timestamp_id[2]; + +/* GCD queue and timer source used as "hardware timer" on macOS */ +static dispatch_queue_t s_queue = NULL; +static dispatch_source_t s_timer_source = NULL; + +/* Flag to track if timer is armed */ +static volatile bool s_timer_armed = false; +static _Atomic int64_t s_time_offset_us; + +/* -------------------------------------------------------------------------- */ +/* Time base */ +/* -------------------------------------------------------------------------- */ + +uint64_t esp_timer_impl_get_counter_reg(void) +{ + return (uint64_t) esp_timer_impl_get_time(); +} + +static int64_t get_monotonic_time_us(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL; +} + +int64_t esp_timer_impl_get_time(void) +{ + return get_monotonic_time_us() + atomic_load_explicit(&s_time_offset_us, memory_order_relaxed); +} + +int64_t esp_timer_get_time(void) +{ + return esp_timer_impl_get_time(); +} + +static void timer_alarm_dispatch_handler(void *ctx) +{ + (void) ctx; + TaskHandle_t timer_task = esp_timer_impl_get_timer_task_handle(); + if (timer_task != NULL) { + xTaskNotifyGive(timer_task); + } +} + +static void setup_alarm(uint64_t alarm_us) +{ + int64_t now = esp_timer_impl_get_time(); + int64_t delta_ns = (alarm_us > now) ? (alarm_us - now) * 1000LL : 0; + + if (delta_ns < 50000LL) { + delta_ns = 50000LL; /* Minimum 50us delay to avoid busy loop */ + } + + dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, delta_ns); + dispatch_source_set_timer(s_timer_source, when, 0, 0); + + s_timer_armed = true; +} + +void esp_timer_impl_set_alarm_id(uint64_t timestamp_us, unsigned alarm_id) +{ + esp_timer_impl_lock(); + timestamp_id[alarm_id] = timestamp_us; + uint64_t min_alarm_us = MIN(timestamp_id[0], timestamp_id[1]); + + if (min_alarm_us != UINT64_MAX) { + setup_alarm(min_alarm_us); + } else if (s_timer_armed) { + dispatch_source_set_timer(s_timer_source, DISPATCH_TIME_FOREVER, 0, 0); + s_timer_armed = false; + } + + esp_timer_impl_unlock(); +} + +void esp_timer_impl_set(uint64_t new_us) +{ + esp_timer_impl_lock(); + atomic_store_explicit(&s_time_offset_us, (int64_t)new_us - get_monotonic_time_us(), memory_order_relaxed); + uint64_t min_alarm_us = MIN(timestamp_id[0], timestamp_id[1]); + if (min_alarm_us != UINT64_MAX) { + setup_alarm(min_alarm_us); + } + esp_timer_impl_unlock(); +} + +void esp_timer_impl_advance(int64_t time_diff_us) +{ + esp_timer_impl_lock(); + atomic_fetch_add_explicit(&s_time_offset_us, time_diff_us, memory_order_relaxed); + uint64_t min_alarm_us = MIN(timestamp_id[0], timestamp_id[1]); + if (min_alarm_us != UINT64_MAX) { + setup_alarm(min_alarm_us); + } + esp_timer_impl_unlock(); +} + +void esp_timer_private_set(uint64_t new_us) +{ + esp_timer_impl_set(new_us); +} + +void esp_timer_private_advance(int64_t time_diff_us) +{ + esp_timer_impl_advance(time_diff_us); +} + +esp_err_t esp_timer_impl_early_init(void) +{ + return ESP_OK; +} + +esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler) +{ + (void) alarm_handler; + + timestamp_id[0] = UINT64_MAX; + timestamp_id[1] = UINT64_MAX; + atomic_store_explicit(&s_time_offset_us, 0, memory_order_relaxed); + + s_queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0); + if (s_queue == NULL) { + ESP_LOGE(TAG, "Failed to get GCD queue"); + return ESP_FAIL; + } + + s_timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, s_queue); + if (s_timer_source == NULL) { + ESP_LOGE(TAG, "Failed to create GCD timer source"); + return ESP_FAIL; + } + + dispatch_set_context(s_timer_source, NULL); + dispatch_source_set_event_handler_f(s_timer_source, timer_alarm_dispatch_handler); + dispatch_resume(s_timer_source); + + ESP_LOGI(TAG, "esp_timer initialized successfully"); + return ESP_OK; +} + +void esp_timer_impl_deinit(void) +{ + if (s_timer_source != NULL) { + dispatch_source_cancel(s_timer_source); + dispatch_release(s_timer_source); + s_timer_source = NULL; + } +} + +uint64_t esp_timer_impl_get_alarm_reg(void) +{ + esp_timer_impl_lock(); + uint64_t min_alarm_us = MIN(timestamp_id[0], timestamp_id[1]); + esp_timer_impl_unlock(); + return min_alarm_us; +} diff --git a/components/esp_timer/src/esp_timer_impl_common.c b/components/esp_timer/src/esp_timer_impl_common.c index 94eef0bfc92..03d129e408b 100644 --- a/components/esp_timer/src/esp_timer_impl_common.c +++ b/components/esp_timer/src/esp_timer_impl_common.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,8 +29,21 @@ void esp_timer_impl_unlock(void) portEXIT_CRITICAL(&s_time_update_lock); } +#ifndef CONFIG_IDF_TARGET_LINUX void esp_timer_private_lock(void) __attribute__((alias("esp_timer_impl_lock"))); void esp_timer_private_unlock(void) __attribute__((alias("esp_timer_impl_unlock"))); +#else // CONFIG_IDF_TARGET_LINUX +// Avoid using __attribute(alias) here since linux target builds on MacOS fail to compile. +void esp_timer_private_lock(void) +{ + esp_timer_impl_lock(); +} + +void esp_timer_private_unlock(void) +{ + esp_timer_impl_unlock(); +} +#endif // CONFIG_IDF_TARGET_LINUX void ESP_TIMER_IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp) { diff --git a/components/esp_timer/src/esp_timer_impl_linux.c b/components/esp_timer/src/esp_timer_impl_linux.c new file mode 100644 index 00000000000..a4f63e53d1a --- /dev/null +++ b/components/esp_timer/src/esp_timer_impl_linux.c @@ -0,0 +1,383 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys/param.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_timer_impl.h" +#include "esp_timer.h" +#include "esp_log.h" + +static const char *TAG = "esp_timer_impl"; + +/* + * Linux host backend for esp_timer. + * + * This implementation emulates a hardware alarm using Linux timerfd. + * + * The esp_timer time domain is: + * + * esp_time = CLOCK_MONOTONIC + s_time_offset_us + * + * Therefore, when esp_timer asks us to arm an alarm at deadline T, + * the corresponding CLOCK_MONOTONIC absolute deadline is: + * + * mono_deadline = T - s_time_offset_us + * + * The alarm is programmed as an absolute CLOCK_MONOTONIC timerfd deadline + * using timerfd_settime(..., TFD_TIMER_ABSTIME, ...). + * + * A dedicated pthread waits on: + * + * - timerfd: alarm expiration + * - eventfd: shutdown notification + * + * When timerfd expires, the thread notifies the common esp_timer task with + * xTaskNotifyGive(), preserving the common esp_timer callback flow. + * + * timerfd expiration -> alarm pthread wakeup -> xTaskNotifyGive() -> esp_timer task execution + */ + +/* Alarm values to generate interrupt on match */ +extern uint64_t timestamp_id[2]; + +/* Dedicated alarm thread used as the Linux "hardware timer". */ +static pthread_t s_alarm_thread; +static bool s_alarm_thread_created; + +/* Linux timer/event descriptors. */ +static int s_timer_fd = -1; +static int s_shutdown_fd = -1; + +/* Adjustable offset between CLOCK_MONOTONIC and esp_timer time. */ +static _Atomic int64_t s_time_offset_us; + +/* -------------------------------------------------------------------------- */ +/* Time base */ +/* -------------------------------------------------------------------------- */ + +static int64_t get_monotonic_time_us(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL; +} + +uint64_t esp_timer_impl_get_counter_reg(void) +{ + return (uint64_t) esp_timer_impl_get_time(); +} + +int64_t esp_timer_impl_get_time(void) +{ + return get_monotonic_time_us() + atomic_load_explicit(&s_time_offset_us, memory_order_relaxed); +} + +int64_t esp_timer_get_time(void) +{ + return esp_timer_impl_get_time(); +} + +/* -------------------------------------------------------------------------- */ +/* timerfd helpers */ +/* -------------------------------------------------------------------------- */ + +static void us_to_timespec_abs(int64_t us, struct timespec *ts) +{ + if (us < 0) { + us = 0; + } + + ts->tv_sec = us / 1000000LL; + ts->tv_nsec = (us % 1000000LL) * 1000LL; +} + +static int64_t deadline_to_monotonic_us(uint64_t deadline_us) +{ + int64_t offset_us = atomic_load_explicit(&s_time_offset_us, memory_order_relaxed); + return (int64_t)deadline_us - offset_us; +} + +static esp_err_t program_timerfd(uint64_t deadline_us) +{ + struct itimerspec its = { 0 }; + + if (s_timer_fd < 0) { + return ESP_ERR_INVALID_STATE; + } + + if (deadline_us == UINT64_MAX) { + /* + * Disarm timerfd. + * For timerfd_settime(), zero it_value disarms the timer. + */ + if (timerfd_settime(s_timer_fd, TFD_TIMER_ABSTIME, &its, NULL) != 0) { + ESP_LOGE(TAG, "timerfd disarm failed: %s", strerror(errno)); + return ESP_FAIL; + } + + return ESP_OK; + } + + int64_t mono_deadline_us = deadline_to_monotonic_us(deadline_us); + int64_t now_mono_us = get_monotonic_time_us(); + + if (mono_deadline_us <= now_mono_us) { + // Using now + 1 us avoids zero it_value, because zero disarms timerfd. + mono_deadline_us = now_mono_us + 1; + } + + us_to_timespec_abs(mono_deadline_us, &its.it_value); + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + + if (timerfd_settime(s_timer_fd, TFD_TIMER_ABSTIME, &its, NULL) != 0) { + ESP_LOGE(TAG, "timerfd_settime failed: %s", strerror(errno)); + return ESP_FAIL; + } + + return ESP_OK; +} + +/* -------------------------------------------------------------------------- */ +/* Alarm thread */ +/* -------------------------------------------------------------------------- */ + +static void *alarm_thread_func(void *arg) +{ + (void)arg; + +#ifdef PR_SET_TIMERSLACK + // Set timer slack to 1 ns for this thread. + // Linux coalesces nearby wakeups to save power; slack controls the allowed delay. + // Default is 50 us. Setting it to 1 ns minimises timerfd wakeup jitter. + prctl(PR_SET_TIMERSLACK, 1, 0, 0, 0); +#endif + + struct pollfd fds[2] = { + { + .fd = s_timer_fd, + .events = POLLIN, + .revents = 0, + }, + { + .fd = s_shutdown_fd, + .events = POLLIN, + .revents = 0, + }, + }; + + while (true) { + int ret = poll(fds, 2, -1); + if (ret < 0) { + if (errno == EINTR) { + continue; + } + + ESP_LOGE(TAG, "alarm poll failed: %s", strerror(errno)); + continue; + } + + if (fds[1].revents & POLLIN) { + uint64_t val; + ssize_t n = read(s_shutdown_fd, &val, sizeof(val)); + (void)n; + break; + } + + if (fds[0].revents & POLLIN) { + uint64_t expirations; + ssize_t n = read(s_timer_fd, &expirations, sizeof(expirations)); + if (n != sizeof(expirations)) { + if (n < 0 && (errno == EINTR || errno == EAGAIN)) { + continue; + } + + if (n < 0) { + ESP_LOGE(TAG, "timerfd read failed: %s", strerror(errno)); + } else { + ESP_LOGE(TAG, "timerfd short read: %zd", n); + } + continue; + } + + TaskHandle_t timer_task = esp_timer_impl_get_timer_task_handle(); + if (timer_task != NULL) { + xTaskNotifyGive(timer_task); + } + } + } + + return NULL; +} + +/* -------------------------------------------------------------------------- */ +/* Alarm programming API */ +/* -------------------------------------------------------------------------- */ + +void esp_timer_impl_set_alarm_id(uint64_t timestamp_us, unsigned alarm_id) +{ + esp_timer_impl_lock(); + + if (alarm_id < 2) { + timestamp_id[alarm_id] = timestamp_us; + } else { + ESP_LOGE(TAG, "Invalid alarm_id: %u", alarm_id); + esp_timer_impl_unlock(); + return; + } + + uint64_t min_alarm_us = MIN(timestamp_id[0], timestamp_id[1]); + program_timerfd(min_alarm_us); + + esp_timer_impl_unlock(); +} + +void esp_timer_impl_set(uint64_t new_us) +{ + esp_timer_impl_lock(); + + atomic_store_explicit(&s_time_offset_us, (int64_t)new_us - get_monotonic_time_us(), memory_order_relaxed); + + // Offset changed, so the same esp_timer deadline now maps to a different + // CLOCK_MONOTONIC absolute deadline. Reprogram timerfd. + uint64_t min_alarm_us = MIN(timestamp_id[0], timestamp_id[1]); + program_timerfd(min_alarm_us); + + esp_timer_impl_unlock(); +} + +void esp_timer_impl_advance(int64_t time_diff_us) +{ + esp_timer_impl_lock(); + + atomic_fetch_add_explicit(&s_time_offset_us, time_diff_us, memory_order_relaxed); + + // Offset changed, so reprogram the host timer. + uint64_t min_alarm_us = MIN(timestamp_id[0], timestamp_id[1]); + program_timerfd(min_alarm_us); + + esp_timer_impl_unlock(); +} + +void esp_timer_private_set(uint64_t new_us) +{ + esp_timer_impl_set(new_us); +} + +void esp_timer_private_advance(int64_t time_diff_us) +{ + esp_timer_impl_advance(time_diff_us); +} + +/* -------------------------------------------------------------------------- */ +/* Init/deinit */ +/* -------------------------------------------------------------------------- */ + +esp_err_t esp_timer_impl_early_init(void) +{ + // No initialization required to call esp_timer_impl_get_time(). + return ESP_OK; +} + +esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler) +{ + (void)alarm_handler; + + timestamp_id[0] = UINT64_MAX; + timestamp_id[1] = UINT64_MAX; + + atomic_store_explicit(&s_time_offset_us, 0, memory_order_relaxed); + + s_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + if (s_timer_fd < 0) { + ESP_LOGE(TAG, "timerfd_create failed: %s", strerror(errno)); + return ESP_FAIL; + } + + s_shutdown_fd = eventfd(0, EFD_CLOEXEC); + if (s_shutdown_fd < 0) { + ESP_LOGE(TAG, "eventfd failed: %s", strerror(errno)); + close(s_timer_fd); + s_timer_fd = -1; + return ESP_FAIL; + } + + int err = pthread_create(&s_alarm_thread, NULL, alarm_thread_func, NULL); + if (err != 0) { + ESP_LOGE(TAG, "Failed to create alarm thread: %s", strerror(err)); + + close(s_shutdown_fd); + close(s_timer_fd); + + s_shutdown_fd = -1; + s_timer_fd = -1; + + return ESP_FAIL; + } + + s_alarm_thread_created = true; + + ESP_LOGI(TAG, "esp_timer initialized successfully"); + return ESP_OK; +} + +void esp_timer_impl_deinit(void) +{ + if (!s_alarm_thread_created) { + return; + } + + // Disarm timer first, then wake alarm thread through eventfd. + if (s_timer_fd >= 0) { + struct itimerspec its = { 0 }; + (void)timerfd_settime(s_timer_fd, TFD_TIMER_ABSTIME, &its, NULL); + } + + if (s_shutdown_fd >= 0) { + uint64_t one = 1; + ssize_t n = write(s_shutdown_fd, &one, sizeof(one)); + (void)n; + } + + pthread_join(s_alarm_thread, NULL); + + if (s_shutdown_fd >= 0) { + close(s_shutdown_fd); + s_shutdown_fd = -1; + } + + if (s_timer_fd >= 0) { + close(s_timer_fd); + s_timer_fd = -1; + } + + s_alarm_thread_created = false; +} + +uint64_t esp_timer_impl_get_alarm_reg(void) +{ + esp_timer_impl_lock(); + uint64_t min_alarm_us = MIN(timestamp_id[0], timestamp_id[1]); + esp_timer_impl_unlock(); + + return min_alarm_us; +} diff --git a/components/esp_timer/src/system_time.c b/components/esp_timer/src/system_time.c index b95abcdb510..27f29bd792d 100644 --- a/components/esp_timer/src/system_time.c +++ b/components/esp_timer/src/system_time.c @@ -12,7 +12,9 @@ #include "esp_timer.h" #include "esp_timer_impl.h" #include "esp_system.h" +#ifndef CONFIG_IDF_TARGET_LINUX #include "esp_newlib.h" +#endif #include "esp_private/startup_internal.h" #include "esp_rtc_time.h" @@ -31,7 +33,9 @@ ESP_SHUTDOWN_HANDLER_REGISTER(esp_sync_timekeeping_timers_shutdown, 100) void esp_timer_impl_init_system_time(void) { +#ifndef CONFIG_IDF_TARGET_LINUX s_correction_us = esp_rtc_get_time_us() - g_startup_time - esp_timer_impl_get_time(); +#endif // !CONFIG_IDF_TARGET_LINUX } int64_t ESP_TIMER_IRAM_ATTR esp_system_get_time(void) diff --git a/components/esp_timer/test_apps/.build-test-rules.yml b/components/esp_timer/test_apps/.build-test-rules.yml index 131939a2258..833b701ccc0 100644 --- a/components/esp_timer/test_apps/.build-test-rules.yml +++ b/components/esp_timer/test_apps/.build-test-rules.yml @@ -1,10 +1,15 @@ # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps components/esp_timer/test_apps: + enable: + - if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux" + reason: run existing default build targets and include Linux depends_components: - esp_timer - esp_hal_timg disable: + - if: IDF_TARGET == "linux" and CONFIG_NAME != "default" + reason: run only the default Linux config - if: CONFIG_NAME == "dfs" and SOC_CLK_XTAL32K_SUPPORTED != 1 reason: The test requires the XTAL32K clock to measure the esp_timer timing accuracy - if: CONFIG_NAME == "dfs" and SOC_PM_SUPPORTED != 1 diff --git a/components/esp_timer/test_apps/CMakeLists.txt b/components/esp_timer/test_apps/CMakeLists.txt index a700bd65e62..3e6d70454ef 100644 --- a/components/esp_timer/test_apps/CMakeLists.txt +++ b/components/esp_timer/test_apps/CMakeLists.txt @@ -3,6 +3,10 @@ cmake_minimum_required(VERSION 3.22) list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults") +if(NOT "${IDF_TARGET}" STREQUAL "linux") + list(APPEND SDKCONFIG_DEFAULTS "sdkconfig.defaults.no_linux") +endif() + # "Trim" the build. Include the minimal set of components, main, and anything it depends on. set(COMPONENTS main) set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components") diff --git a/components/esp_timer/test_apps/README.md b/components/esp_timer/test_apps/README.md index 6bcfea86348..9ae4df32277 100644 --- a/components/esp_timer/test_apps/README.md +++ b/components/esp_timer/test_apps/README.md @@ -1,3 +1,3 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 | Linux | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | ----- | diff --git a/components/esp_timer/test_apps/main/CMakeLists.txt b/components/esp_timer/test_apps/main/CMakeLists.txt index 53c4d8eed69..2d96fdf311d 100644 --- a/components/esp_timer/test_apps/main/CMakeLists.txt +++ b/components/esp_timer/test_apps/main/CMakeLists.txt @@ -1,7 +1,6 @@ -set(srcs - "test_app_main.c" - "test_ets_timer.c" -) +idf_build_get_property(target IDF_TARGET) + +set(srcs "test_app_main.c") if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED) list(APPEND srcs "test_esp_timer_light_sleep.c") @@ -11,15 +10,23 @@ if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM) list(APPEND srcs "test_esp_timer_etm.c") endif() -if(CONFIG_SOC_GPTIMER_SUPPORTED) +if(CONFIG_SOC_GPTIMER_SUPPORTED OR CONFIG_IDF_TARGET_LINUX) list(APPEND srcs "test_esp_timer.c") endif() -if(CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD) - list(APPEND srcs "test_esp_timer_dfs.c") +if(NOT ${target} STREQUAL "linux") + + if(CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD) + list(APPEND srcs "test_esp_timer_dfs.c") + endif() + + set(priv_requires cmock test_utils esp_timer spi_flash esp_psram esp_driver_gpio esp_pm) + list(APPEND srcs "test_ets_timer.c") +else() + set(priv_requires cmock test_utils esp_timer) endif() idf_component_register(SRCS ${srcs} PRIV_INCLUDE_DIRS "../../private_include" "../include" - PRIV_REQUIRES cmock test_utils esp_timer spi_flash esp_psram esp_driver_gpio esp_pm + PRIV_REQUIRES "${priv_requires}" WHOLE_ARCHIVE) diff --git a/components/esp_timer/test_apps/main/test_app_main.c b/components/esp_timer/test_apps/main/test_app_main.c index a2be45ba15d..0473c408f1a 100644 --- a/components/esp_timer/test_apps/main/test_app_main.c +++ b/components/esp_timer/test_apps/main/test_app_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -7,10 +7,11 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_task_wdt.h" +#include "esp_private/esp_timer_private.h" // On C2 due to only have a single HW WDT we use the timer // so some tests will interfere with the WDT -#if !CONFIG_IDF_TARGET_ESP32C2 +#if !CONFIG_IDF_TARGET_ESP32C2 && !CONFIG_IDF_TARGET_LINUX #define WDT_MONITOR_UNITY_TASK #endif @@ -28,6 +29,9 @@ void tearDown(void) esp_task_wdt_reset(); esp_task_wdt_delete(NULL); #endif + + // Give some time for the esp_timer task to clean up between test runs, to avoid interference with the next test run. + vTaskDelay(pdMS_TO_TICKS(100)); } void app_main(void) diff --git a/components/esp_timer/test_apps/main/test_esp_timer.c b/components/esp_timer/test_apps/main/test_esp_timer.c index 0abb827027f..a265b588970 100644 --- a/components/esp_timer/test_apps/main/test_esp_timer.c +++ b/components/esp_timer/test_apps/main/test_esp_timer.c @@ -6,6 +6,7 @@ #include "sdkconfig.h" #include #include +#include #include #include #include @@ -14,6 +15,9 @@ #include "esp_timer_impl.h" #include "unity.h" #include "soc/soc_caps.h" +#if !CONFIG_IDF_TARGET_LINUX +#include "soc/timer_group_reg.h" +#endif #include "esp_heap_caps.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -30,6 +34,8 @@ #define WITH_PROFILING 1 #endif +#define ESP_TIMER_TEST_LINUX_TOLERANCE_MS 50 + static void dummy_cb(void* arg) { } @@ -42,7 +48,8 @@ TEST_CASE("esp_timer orders timers correctly", "[esp_timer]") esp_timer_handle_t handles[num_timers]; char* names[num_timers]; for (size_t i = 0; i < num_timers; ++i) { - asprintf(&names[i], "timer%d", i); + int ret = asprintf(&names[i], "timer%zu", i); + assert(ret != -1); esp_timer_create_args_t args = { .callback = &dummy_cb, .name = names[i] @@ -50,7 +57,8 @@ TEST_CASE("esp_timer orders timers correctly", "[esp_timer]") TEST_ESP_OK(esp_timer_create(&args, &handles[i])); TEST_ESP_OK(esp_timer_start_once(handles[i], timeouts[i] * 100)); } - char* stream_str[1024]; + static char stream_str[2048]; + memset(stream_str, 0, sizeof(stream_str)); FILE* stream = fmemopen(stream_str, sizeof(stream_str), "r+"); TEST_ESP_OK(esp_timer_dump(stream)); for (size_t i = 0; i < num_timers; ++i) { @@ -150,12 +158,19 @@ TEST_CASE("esp_timer produces correct delay", "[esp_timer]") TEST_ESP_OK(esp_timer_start_once(timer1, delays_ms[i] * 1000)); - vTaskDelay(delays_ms[i] * 2 / portTICK_PERIOD_MS); +#if CONFIG_IDF_TARGET_LINUX + const int wait_multiplier = 4; + const int32_t tolerance_ms = ESP_TIMER_TEST_LINUX_TOLERANCE_MS; +#else + const int wait_multiplier = 2; + const int32_t tolerance_ms = portTICK_PERIOD_MS; +#endif + vTaskDelay(pdMS_TO_TICKS(delays_ms[i] * wait_multiplier)); TEST_ASSERT(t_end != 0); int32_t ms_diff = (t_end - t_start) / 1000; printf("%d %"PRIi32"\n", delays_ms[i], ms_diff); - TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, delays_ms[i], ms_diff); + TEST_ASSERT_INT32_WITHIN(tolerance_ms, delays_ms[i], ms_diff); } ref_clock_deinit(); @@ -181,7 +196,7 @@ static void test_periodic_correct_delays_timer_func(void* arg) test_periodic_correct_delays_args_t* p_args = (test_periodic_correct_delays_args_t*) arg; int64_t t_end = ref_clock_get(); int32_t ms_diff = (t_end - p_args->t_start) / 1000; - printf("timer #%d %"PRIi32"ms\n", p_args->cur_interval, ms_diff); + printf("timer #%zu %"PRIi32"ms\n", p_args->cur_interval, ms_diff); p_args->intervals[p_args->cur_interval++] = ms_diff; // Deliberately make timer handler run longer. // We check that this doesn't affect the result. @@ -213,8 +228,13 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]") TEST_ASSERT(xSemaphoreTake(args.done, delay_ms * NUM_INTERVALS * 2)); TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, args.cur_interval); +#if CONFIG_IDF_TARGET_LINUX + const int32_t tolerance_ms = ESP_TIMER_TEST_LINUX_TOLERANCE_MS; +#else + const int32_t tolerance_ms = portTICK_PERIOD_MS; +#endif for (size_t i = 0; i < NUM_INTERVALS; ++i) { - TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, (i + 1) * delay_ms, args.intervals[i]); + TEST_ASSERT_INT32_WITHIN(tolerance_ms, (i + 1) * delay_ms, args.intervals[i]); } ref_clock_deinit(); TEST_ESP_OK(esp_timer_dump(stdout)); @@ -250,7 +270,7 @@ static void test_timers_ordered_correctly_timer_func(void* arg) size_t count = p_args->common->count; int expected_index = p_args->common->order[count]; int ms_since_start = (ref_clock_get() - p_args->t_start) / 1000; - printf("Time %dms, at count %d, expected timer %d, got timer %d\n", + printf("Time %dms, at count %zu, expected timer %d, got timer %d\n", ms_since_start, count, expected_index, p_args->timer_index); if (expected_index != p_args->timer_index) { p_args->pass = false; @@ -265,7 +285,7 @@ static void test_timers_ordered_correctly_timer_func(void* arg) return; } int next_interval = p_args->intervals[p_args->intervals_count]; - printf("starting timer %d interval #%d, %d ms\n", + printf("starting timer %d interval #%zu, %d ms\n", p_args->timer_index, p_args->intervals_count, next_interval); esp_timer_start_once(p_args->timer, next_interval * 1000); } @@ -373,12 +393,17 @@ TEST_CASE("esp_timer for very short intervals", "[esp_timer]") ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer1)); ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer2)); const int timeout_ms = 10; + TickType_t wait_timeout_ticks = pdMS_TO_TICKS(timeout_ms * 2); +#if CONFIG_IDF_TARGET_LINUX + // Use a longer timeout for Linux to avoid test instability due to scheduling delays. + wait_timeout_ticks *= 10; +#endif for (int timeout_delta_us = -150; timeout_delta_us < 150; timeout_delta_us++) { printf("delta=%d", timeout_delta_us); ESP_ERROR_CHECK(esp_timer_start_once(timer1, timeout_ms * 1000)); ESP_ERROR_CHECK(esp_timer_start_once(timer2, timeout_ms * 1000 + timeout_delta_us)); - TEST_ASSERT_EQUAL(pdPASS, xSemaphoreTake(semaphore, timeout_ms * 2)); - TEST_ASSERT_EQUAL(pdPASS, xSemaphoreTake(semaphore, timeout_ms * 2)); + TEST_ASSERT_EQUAL(pdPASS, xSemaphoreTake(semaphore, wait_timeout_ticks)); + TEST_ASSERT_EQUAL(pdPASS, xSemaphoreTake(semaphore, wait_timeout_ticks)); printf("\n"); TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_timer_stop(timer1)); TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_timer_stop(timer2)); @@ -455,7 +480,7 @@ static void timer_test_monotonic_values_task(void* arg) error_repeat_cnt = 0; } if (error_repeat_cnt > 2) { - printf("diff=%lld\n", diff); + printf("diff=%" PRId64 "\n", diff); state->pass = false; } state->avg_diff += diff; @@ -622,12 +647,12 @@ TEST_CASE("esp_timer_impl_advance moves time base correctly", "[esp_timer]") esp_timer_impl_advance(diff_us); int64_t t1 = esp_timer_get_time(); int64_t t_delta = t1 - t0; - printf("diff_us=%lld t0=%lld t1=%lld t1-t0=%lld\n", diff_us, t0, t1, t_delta); + printf("diff_us=%" PRId64 " t0=%" PRId64 " t1=%" PRId64 " t1-t0=%" PRId64 "\n", diff_us, t0, t1, t_delta); TEST_ASSERT_INT_WITHIN(1000, diff_us, (int) t_delta); } typedef struct { - int64_t cb_time; + volatile int64_t cb_time; } test_run_when_expected_state_t; static void test_run_when_expected_timer_func(void* varg) @@ -649,8 +674,15 @@ TEST_CASE("after esp_timer_impl_advance, timers run when expected", "[esp_timer] esp_timer_handle_t timer; TEST_ESP_OK(esp_timer_create(&timer_args, &timer)); +#if CONFIG_IDF_TARGET_LINUX + const int64_t interval = 50000; + const int64_t advance = 30000; + const int32_t tolerance_us = 10000; +#else const int64_t interval = 10000; const int64_t advance = 2000; + const int32_t tolerance_us = portTICK_PERIOD_MS * 1000; +#endif printf("test 1\n"); int64_t t_start = ref_clock_get(); @@ -658,22 +690,25 @@ TEST_CASE("after esp_timer_impl_advance, timers run when expected", "[esp_timer] esp_timer_impl_advance(advance); vTaskDelay(2 * interval / 1000 / portTICK_PERIOD_MS); - TEST_ASSERT_INT_WITHIN(portTICK_PERIOD_MS * 1000, interval - advance, state.cb_time - t_start); + TEST_ASSERT_INT_WITHIN(tolerance_us, interval - advance, state.cb_time - t_start); printf("test 2\n"); state.cb_time = 0; t_start = ref_clock_get(); esp_timer_start_once(timer, interval); esp_timer_impl_advance(interval); - vTaskDelay(1); + while (state.cb_time == 0) { + vTaskDelay(1); + } - TEST_ASSERT(state.cb_time > t_start); + TEST_ASSERT_GREATER_THAN(t_start, state.cb_time); ref_clock_deinit(); TEST_ESP_OK(esp_timer_delete(timer)); vTaskDelay(3); // wait for the esp_timer task to delete all timers } +#if CONFIG_FREERTOS_USE_TICK_HOOK static esp_timer_handle_t timer1; static SemaphoreHandle_t sem; static void IRAM_ATTR test_tick_hook(void) @@ -711,6 +746,7 @@ TEST_CASE("Can start/stop timer from ISR context", "[esp_timer]") vSemaphoreDelete(sem); vTaskDelay(3); // wait for the esp_timer task to delete all timers } +#endif // CONFIG_FREERTOS_USE_TICK_HOOK #if !defined(CONFIG_FREERTOS_UNICORE) && SOC_DPORT_WORKAROUND @@ -841,6 +877,7 @@ TEST_CASE("esp_timer_impl_set_alarm and using start_once do not lead that the Sy #endif // !defined(CONFIG_FREERTOS_UNICORE) && SOC_DPORT_WORKAROUND +#ifdef CONFIG_IDF_TARGET_ESP32 TEST_CASE("Test case when esp_timer_impl_set_alarm needs set timer < now_time", "[esp_timer]") { esp_timer_impl_advance(50331648); // 0xefffffff/80 = 50331647 @@ -855,9 +892,10 @@ TEST_CASE("Test case when esp_timer_impl_set_alarm needs set timer < now_time", const uint32_t offset = 2; - printf("alarm_reg = 0x%llx, count_reg 0x%llx\n", alarm_reg, count_reg); + printf("alarm_reg = 0x%" PRIx64 ", count_reg 0x%" PRIx64 "\n", alarm_reg, count_reg); TEST_ASSERT(alarm_reg <= (count_reg + offset)); } +#endif // CONFIG_IDF_TARGET_ESP32 static void timer_callback5(void* arg) { @@ -887,11 +925,18 @@ TEST_CASE("Test a latency between a call of callback and real event", "[esp_time } int diff = callback_time - expected_time; esp_rom_printf(DRAM_STR("%d us\n"), diff); + #ifndef CONFIG_IDF_ENV_FPGA if (i != 0) { +#if CONFIG_IDF_TARGET_LINUX + // It may be very big due to OS scheduling, the value is taken with big margin + const int max_latency_us = 5000; +#else + const int max_latency_us = 50; +#endif // skip the first measurement // if CPU_FREQ = 240MHz. 14 - 16us - TEST_ASSERT_LESS_OR_EQUAL(50, diff); + TEST_ASSERT_LESS_OR_EQUAL(max_latency_us, diff); } #endif // not CONFIG_IDF_ENV_FPGA } @@ -997,7 +1042,9 @@ static void timer_isr_callback(void* arg) old_time[num_timer] = now; if (num_timer == 0) { esp_rom_printf("(%lld): \t\t\t\t timer ISR, dt: %lld us\n", now, dt); +#ifndef CONFIG_IDF_TARGET_LINUX assert(xPortInIsrContext()); +#endif } else { esp_rom_printf("(%lld): timer TASK, dt: %lld us\n", now, dt); assert(!xPortInIsrContext()); @@ -1056,7 +1103,9 @@ static void dump_task(void* arg) static void isr_callback(void* arg) { +#ifndef CONFIG_IDF_TARGET_LINUX assert(xPortInIsrContext()); +#endif } static void task_callback(void* arg) @@ -1108,7 +1157,9 @@ TEST_CASE("Test ESP_TIMER_ISR dispatch method is not blocked", "[esp_timer]") static void isr_callback1(void* arg) { +#ifndef CONFIG_IDF_TARGET_LINUX assert(xPortInIsrContext()); +#endif BaseType_t xHigherPriorityTaskWoken = pdFALSE; esp_rom_printf("isr_callback1: timer ISR\n"); SemaphoreHandle_t done = *(SemaphoreHandle_t*) arg; @@ -1262,6 +1313,8 @@ TEST_CASE("Test that CPU1 can handle esp_timer ISR even when CPU0 is blocked", " } #endif // not CONFIG_FREERTOS_UNICORE +#ifndef CONFIG_IDF_TARGET_LINUX + volatile uint64_t task_t1; volatile uint64_t isr_t1; const uint64_t period_task_ms = 200; @@ -1332,6 +1385,7 @@ TEST_CASE("Test ISR dispatch callbacks are not blocked even if TASK callbacks ta TEST_ESP_OK(esp_timer_delete(isr_timer_handle)); vTaskDelay(3); // wait for the esp_timer task to delete all timers } +#endif // CONFIG_IDF_TARGET_LINUX #endif // CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD @@ -1420,7 +1474,7 @@ static void test_stop_blocking_callback_stop_inside(void* arg) // Call stop_blocking from the timer's own callback: // This must NOT block (otherwise we deadlock waiting for ourselves). - TEST_ESP_OK(esp_timer_stop_blocking(state->timer, portMAX_DELAY)); + TEST_ESP_OK(esp_timer_stop_blocking(state->timer, (uint32_t) portMAX_DELAY)); // Signal to the test that stop_blocking has returned from inside callback xSemaphoreGive(state->stop_called_from_cb); diff --git a/components/esp_timer/test_apps/pytest_esp_timer_ut.py b/components/esp_timer/test_apps/pytest_esp_timer_ut.py index 1c3c991fbe0..fee4cd10596 100644 --- a/components/esp_timer/test_apps/pytest_esp_timer_ut.py +++ b/components/esp_timer/test_apps/pytest_esp_timer_ut.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut @@ -26,6 +26,13 @@ def test_esp_timer(dut: Dut) -> None: dut.run_all_single_board_cases(timeout=120) +@pytest.mark.host_test +@pytest.mark.parametrize('config', ['default'], indirect=True) +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_esp_timer_linux(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=180) + + @pytest.mark.generic @pytest.mark.psram @pytest.mark.parametrize( diff --git a/components/esp_timer/test_apps/sdkconfig.ci.default b/components/esp_timer/test_apps/sdkconfig.ci.default new file mode 100644 index 00000000000..cd6e3546ce3 --- /dev/null +++ b/components/esp_timer/test_apps/sdkconfig.ci.default @@ -0,0 +1 @@ +# Default CI config uses sdkconfig.defaults plus target-specific defaults. diff --git a/components/esp_timer/test_apps/sdkconfig.defaults b/components/esp_timer/test_apps/sdkconfig.defaults index f358ad545fa..fc419263c53 100644 --- a/components/esp_timer/test_apps/sdkconfig.defaults +++ b/components/esp_timer/test_apps/sdkconfig.defaults @@ -1,7 +1,6 @@ # General options for additional checks CONFIG_HEAP_POISONING_COMPREHENSIVE=y CONFIG_COMPILER_WARN_WRITE_STRINGS=y -CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y CONFIG_COMPILER_STACK_CHECK=y diff --git a/components/esp_timer/test_apps/sdkconfig.defaults.linux b/components/esp_timer/test_apps/sdkconfig.defaults.linux new file mode 100644 index 00000000000..052b801bf78 --- /dev/null +++ b/components/esp_timer/test_apps/sdkconfig.defaults.linux @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=n +CONFIG_ESP_INT_WDT=n diff --git a/components/esp_timer/test_apps/sdkconfig.defaults.no_linux b/components/esp_timer/test_apps/sdkconfig.defaults.no_linux new file mode 100644 index 00000000000..788cdd0166e --- /dev/null +++ b/components/esp_timer/test_apps/sdkconfig.defaults.no_linux @@ -0,0 +1 @@ +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index dd7ee32fb88..4156050e179 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -60,6 +60,8 @@ examples/system/esp_event/user_event_loops: - esp_event examples/system/esp_timer: + enable: + - if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux" depends_components: - esp_timer diff --git a/examples/system/esp_timer/CMakeLists.txt b/examples/system/esp_timer/CMakeLists.txt index 491f83b2b8e..4689acc026f 100644 --- a/examples/system/esp_timer/CMakeLists.txt +++ b/examples/system/esp_timer/CMakeLists.txt @@ -2,6 +2,12 @@ # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.22) +list(APPEND SDKCONFIG_DEFAULTS "sdkconfig.defaults") + +if(NOT "${IDF_TARGET}" STREQUAL "linux") + list(APPEND SDKCONFIG_DEFAULTS "sdkconfig.defaults.no_linux") +endif() + include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) diff --git a/examples/system/esp_timer/README.md b/examples/system/esp_timer/README.md index dbbdc9159bf..f0657506766 100644 --- a/examples/system/esp_timer/README.md +++ b/examples/system/esp_timer/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 | Linux | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | ----- | # ESP Timer Example (High Resolution Timer) @@ -90,8 +90,8 @@ I (266) example: Started timers, time since boot: 30808 us Timer stats: Name Period Alarm Times_armed Times_trigg Times_skip Cb_exec_time periodic 500000 530773 1 0 0 0 -timed periodic 500000 3000000 1 0 0 0 -one-shot 0 5030792 1 0 0 0 +timed periodic 500000 3050000 1 0 0 0 +one-shot 0 5130792 1 0 0 0 timed one-shot 0 6000000 1 0 0 0 I (766) example: Periodic timer called, time since boot: 530850 us I (1266) example: Periodic timer called, time since boot: 1030802 us @@ -100,58 +100,58 @@ I (2266) example: Periodic timer called, time since boot: 2030802 us Timer stats: Name Period Alarm Times_armed Times_trigg Times_skip Cb_exec_time periodic 500000 2530773 1 4 0 999 -timed periodic 500000 3000000 1 0 0 0 -one-shot 0 5030792 1 0 0 0 +timed periodic 500000 3050000 1 0 0 0 +one-shot 0 5130792 1 0 0 0 timed one-shot 0 6000000 1 0 0 0 I (2766) example: Periodic timer called, time since boot: 2530826 us -I (3236) example: Timed periodic timer called, time since boot: 3000029 us +I (3286) example: Timed periodic timer called, time since boot: 3050029 us I (3266) example: Periodic timer called, time since boot: 3030802 us -I (3736) example: Timed periodic timer called, time since boot: 3500029 us +I (3786) example: Timed periodic timer called, time since boot: 3550029 us I (3766) example: Periodic timer called, time since boot: 3530802 us -I (4236) example: Timed periodic timer called, time since boot: 4000029 us +I (4286) example: Timed periodic timer called, time since boot: 4050029 us I (4266) example: Periodic timer called, time since boot: 4030802 us Timer stats: Name Period Alarm Times_armed Times_trigg Times_skip Cb_exec_time -timed periodic 500000 4500000 1 3 0 709 +timed periodic 500000 4550000 1 3 0 709 periodic 500000 4530773 1 8 0 1947 -one-shot 0 5030792 1 0 0 0 +one-shot 0 5130792 1 0 0 0 timed one-shot 0 6000000 1 0 0 0 -I (4736) example: Timed periodic timer called, time since boot: 4500053 us +I (4786) example: Timed periodic timer called, time since boot: 4550053 us I (4766) example: Periodic timer called, time since boot: 4530802 us -I (5236) example: Timed periodic timer called, time since boot: 5000030 us +I (5286) example: Timed periodic timer called, time since boot: 5050030 us I (5266) example: Periodic timer called, time since boot: 5030803 us -I (5266) example: One-shot timer called, time since boot: 5031025 us -I (5266) example: Restarted periodic timer with 1s period, time since boot: 5031920 us -I (5736) example: Timed periodic timer called, time since boot: 5500029 us +I (5366) example: One-shot timer called, time since boot: 5131025 us +I (5366) example: Restarted periodic timer with 1s period, time since boot: 5131920 us +I (5786) example: Timed periodic timer called, time since boot: 5550029 us I (6236) example: Timed one-shot timer called, time since boot: 6000028 us I (6236) example: Restarted timed periodic timer with 1s period, time since boot: 6000268 us -I (6266) example: Periodic timer called, time since boot: 6031945 us +I (6366) example: Periodic timer called, time since boot: 6131945 us Timer stats: Name Period Alarm Times_armed Times_trigg Times_skip Cb_exec_time timed periodic 1000000 7000000 1 6 0 1438 -periodic 1000000 7031916 2 11 0 2644 +periodic 1000000 7131916 2 11 0 2644 timed one-shot 0 0 1 1 0 3722 one-shot 0 0 1 1 0 8517 I (7236) example: Timed periodic timer called, time since boot: 7000053 us -I (7266) example: Periodic timer called, time since boot: 7031945 us +I (7366) example: Periodic timer called, time since boot: 7131945 us I (8236) example: Timed periodic timer called, time since boot: 8000029 us -I (8266) example: Periodic timer called, time since boot: 8031945 us +I (8366) example: Periodic timer called, time since boot: 8131945 us Timer stats: Name Period Alarm Times_armed Times_trigg Times_skip Cb_exec_time timed periodic 1000000 9000000 1 8 0 1940 -periodic 1000000 9031916 2 13 0 3106 +periodic 1000000 9131916 2 13 0 3106 timed one-shot 0 0 1 1 0 3722 one-shot 0 0 1 1 0 8517 I (9236) example: Timed periodic timer called, time since boot: 9000053 us -I (9266) example: Periodic timer called, time since boot: 9031945 us +I (9366) example: Periodic timer called, time since boot: 9131945 us I (10236) example: Timed periodic timer called, time since boot: 10000029 us -I (10266) example: Periodic timer called, time since boot: 10031945 us +I (10366) example: Periodic timer called, time since boot: 10131945 us I (10476) example: Entering light sleep for 0.5s, time since boot: 10239360 us I (10476) example: Woke up from light sleep, time since boot: 10739673 us I (10736) example: Timed periodic timer called, time since boot: 11000033 us -I (10766) example: Periodic timer called, time since boot: 11031945 us +I (10866) example: Periodic timer called, time since boot: 11131945 us I (11736) example: Timed periodic timer called, time since boot: 12000029 us -I (11766) example: Periodic timer called, time since boot: 12031945 us +I (11866) example: Periodic timer called, time since boot: 12131945 us I (12486) example: Stopped and deleted timers ... ``` diff --git a/examples/system/esp_timer/main/CMakeLists.txt b/examples/system/esp_timer/main/CMakeLists.txt index 6e9e5b7f6e5..3b515913da2 100644 --- a/examples/system/esp_timer/main/CMakeLists.txt +++ b/examples/system/esp_timer/main/CMakeLists.txt @@ -1,2 +1,3 @@ idf_component_register(SRCS "esp_timer_example_main.c" - INCLUDE_DIRS ".") + INCLUDE_DIRS "." + PRIV_REQUIRES esp_timer) diff --git a/examples/system/esp_timer/main/esp_timer_example_main.c b/examples/system/esp_timer/main/esp_timer_example_main.c index 4f1b59f35fd..9617b87b8c9 100644 --- a/examples/system/esp_timer/main/esp_timer_example_main.c +++ b/examples/system/esp_timer/main/esp_timer_example_main.c @@ -12,7 +12,9 @@ #include #include "esp_timer.h" #include "esp_log.h" +#if SOC_LIGHT_SLEEP_SUPPORTED #include "esp_sleep.h" +#endif #include "sdkconfig.h" static void periodic_timer_callback(void* arg); @@ -26,13 +28,13 @@ void app_main(void) { /* Create four timers: * 1. a periodic timer which will run every 0.5s, and print a message - * 2. a periodic timer which will run every 0.5s, starting at time 3s from - * boot, and print a message - * 3. a one-shot timer which will fire after 5s, and re-start periodic + * 2. a periodic timer which will run every 0.5s, starting 3.05s from now + * using an absolute timestamp, and print a message + * 3. a one-shot timer which will fire after 5.1s, and re-start periodic * timer 1 with period of 1s. - * 4. a one-shot timer which will fire at time 6s from boot, and re-start - * periodic timer 2 with period of 1s, firing for the first time at 7s - * after boot. + * 4. a one-shot timer which will fire 6s from now using an absolute + * timestamp, and re-start periodic timer 2 with period of 1s, firing + * for the first time 1s later. */ const esp_timer_create_args_t periodic_timer_args = { @@ -69,10 +71,14 @@ void app_main(void) ESP_ERROR_CHECK(esp_timer_create(&timed_oneshot_timer_args, &timed_oneshot_timer)); /* Start the timers */ + int64_t start_time = esp_timer_get_time(); ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 500000)); - ESP_ERROR_CHECK(esp_timer_start_periodic_at(timed_periodic_timer, 500000, 3000000)); - ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5000000)); - ESP_ERROR_CHECK(esp_timer_start_once_at(timed_oneshot_timer, 6000000)); + /* Keep callback times staggered so logging/restart work in one callback + * does not delay another callback and make the example test flaky. + */ + ESP_ERROR_CHECK(esp_timer_start_periodic_at(timed_periodic_timer, 500000, start_time + 3050000)); + ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5100000)); + ESP_ERROR_CHECK(esp_timer_start_once_at(timed_oneshot_timer, start_time + 6000000)); ESP_LOGI(TAG, "Started timers, time since boot: %lld us", esp_timer_get_time()); /* Print debugging information about timers to console every 2 seconds */ @@ -141,7 +147,7 @@ static void timed_oneshot_timer_callback(void* arg) ESP_LOGI(TAG, "Timed one-shot timer called, time since boot: %lld us", time_since_boot); esp_timer_handle_t timed_periodic_timer_handle = (esp_timer_handle_t) arg; /* To start the timer which is running, need to stop it first */ - ESP_ERROR_CHECK(esp_timer_restart_at(timed_periodic_timer_handle, 1000000, 7000000)); + ESP_ERROR_CHECK(esp_timer_restart_at(timed_periodic_timer_handle, 1000000, time_since_boot + 1000000)); time_since_boot = esp_timer_get_time(); ESP_LOGI(TAG, "Restarted timed periodic timer with 1s period, time since boot: %lld us", time_since_boot); diff --git a/examples/system/esp_timer/pytest_esp_timer.py b/examples/system/esp_timer/pytest_esp_timer.py index a20b8f120d0..3b1fbeb672f 100644 --- a/examples/system/esp_timer/pytest_esp_timer.py +++ b/examples/system/esp_timer/pytest_esp_timer.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import logging @@ -13,6 +13,7 @@ TIMER_DUMP_LINE_REGEX = r'([\w-]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d PERIODIC_TIMER_REGEX = r'Periodic timer called, time since boot: (\d+) us' TIMED_PERIODIC_TIMER_REGEX = r'Timed periodic timer called, time since boot: (\d+) us' +ANY_PERIODIC_TIMER_REGEX = r'(Timed periodic timer called|Periodic timer called), time since boot: (\d+) us' LIGHT_SLEEP_ENTER_REGEX = r'Entering light sleep for 0\.5s, time since boot: (\d+) us' LIGHT_SLEEP_EXIT_REGEX = r'Woke up from light sleep, time since boot: (\d+) us' @@ -28,25 +29,46 @@ STOP_REGEX = r'Stopped and deleted timers' INITIAL_TIMER_PERIOD = 500000 FINAL_TIMER_PERIOD = 1000000 LIGHT_SLEEP_TIME = 500000 -ONE_SHOT_TIMER_PERIOD = 5000000 -TIMED_PERIODIC_START_TIME = 3000000 +ONE_SHOT_TIMER_PERIOD = 5100000 +TIMED_PERIODIC_START_TIME = 3050000 TIMED_ONE_SHOT_TIME = 6000000 -TIMED_RESTART_TIME = 7000000 -@pytest.mark.generic -@pytest.mark.parametrize( - 'config', - [ - 'rtc', - ], - indirect=True, -) -@idf_parametrize('target', ['supported_targets'], indirect=['target']) -def test_esp_timer(dut: Dut) -> None: +def expect_periodic_callbacks( + dut: Dut, + expected_periodic_time: int, + expected_timed_periodic_time: int, + timer_tolerance: int, + callback_num: int, +) -> None: + seen: set[str] = set() + for _ in range(2): + match = dut.expect(ANY_PERIODIC_TIMER_REGEX, timeout=2) + timer_name = match.group(1).decode('utf8') + cur_time = int(match.group(2)) + + if timer_name == 'Timed periodic timer called': + diff = expected_timed_periodic_time - cur_time + seen.add('timed') + logging.info(f'Timed Callback #{callback_num}, time: {cur_time} us, diff: {diff} us') + else: + diff = expected_periodic_time - cur_time + seen.add('periodic') + logging.info(f'Callback #{callback_num}, time: {cur_time} us, diff: {diff} us') + + assert abs(diff) < timer_tolerance + + assert seen == {'periodic', 'timed'} + + +def run_esp_timer_example(dut: Dut) -> None: + # Linux timers are not exact, so we need to increase the tolerance for them + timer_tolerance = 20000 if dut.app.target == 'linux' else 100 + one_shot_tolerance = 20000 if dut.app.target == 'linux' else 400 + match = dut.expect(STARTING_TIMERS_REGEX) - start_time = int(match.group(1)) - logging.info(f'Start time: {start_time} us') + initial_start_time = int(match.group(1)) + logging.info(f'Start time: {initial_start_time} us') match = dut.expect(TIMER_DUMP_LINE_REGEX, timeout=2) assert match.group(1).decode('utf8') == 'periodic' and int(match.group(2)) == INITIAL_TIMER_PERIOD @@ -60,31 +82,32 @@ def test_esp_timer(dut: Dut) -> None: for i in range(0, 5): match = dut.expect(PERIODIC_TIMER_REGEX, timeout=2) cur_time = int(match.group(1)) - diff = start_time + (i + 1) * INITIAL_TIMER_PERIOD - cur_time + diff = initial_start_time + (i + 1) * INITIAL_TIMER_PERIOD - cur_time logging.info(f'Callback #{i}, time: {cur_time} us, diff: {diff} us') - assert abs(diff) < 100 + assert abs(diff) < timer_tolerance for i in range(0, 5): match = dut.expect(TIMED_PERIODIC_TIMER_REGEX, timeout=2) cur_time = int(match.group(1)) - diff = TIMED_PERIODIC_START_TIME + i * INITIAL_TIMER_PERIOD - cur_time + diff = initial_start_time + TIMED_PERIODIC_START_TIME + i * INITIAL_TIMER_PERIOD - cur_time logging.info(f'Callback #{i}, time: {cur_time} us, diff: {diff} us') - assert abs(diff) < 100 + assert abs(diff) < timer_tolerance match = dut.expect(ONE_SHOT_REGEX, timeout=3) one_shot_timer_time = int(match.group(1)) - diff = start_time + ONE_SHOT_TIMER_PERIOD - one_shot_timer_time + diff = initial_start_time + ONE_SHOT_TIMER_PERIOD - one_shot_timer_time logging.info(f'One-shot timer, time: {one_shot_timer_time} us, diff: {diff}') - assert abs(diff) < 400 + assert abs(diff) < one_shot_tolerance match = dut.expect(RESTART_REGEX, timeout=3) - start_time = int(match.group(1)) - logging.info(f'Timer restarted, time: {start_time} us') + restart_time = int(match.group(1)) + logging.info(f'Timer restarted, time: {restart_time} us') match = dut.expect(TIMED_ONE_SHOT_REGEX, timeout=3) timed_one_shot_timer_time = int(match.group(1)) - diff = TIMED_ONE_SHOT_TIME - timed_one_shot_timer_time + diff = initial_start_time + TIMED_ONE_SHOT_TIME - timed_one_shot_timer_time logging.info(f'Timed one-shot timer, time: {timed_one_shot_timer_time} us, diff: {diff}') + assert abs(diff) < one_shot_tolerance match = dut.expect(TIMED_RESTART_REGEX, timeout=3) timed_start_time = int(match.group(1)) @@ -93,23 +116,19 @@ def test_esp_timer(dut: Dut) -> None: # First callback after restart match = dut.expect(PERIODIC_TIMER_REGEX, timeout=2) cur_time = int(match.group(1)) - diff = start_time + FINAL_TIMER_PERIOD - cur_time + diff = restart_time + FINAL_TIMER_PERIOD - cur_time logging.info(f'Callback #{0}, time: {cur_time} us, diff: {diff} us') - assert abs(diff) < 100 + assert abs(diff) < timer_tolerance # Callbacks 2 to 5 after restart (now both timers are running) for i in range(1, 5): - timed_match = dut.expect(TIMED_PERIODIC_TIMER_REGEX, timeout=2) - timed_cur_time = int(timed_match.group(1)) - timed_diff = TIMED_RESTART_TIME + (i - 1) * FINAL_TIMER_PERIOD - timed_cur_time - logging.info(f'Timed Callback #{i}, time: {timed_cur_time} us, diff: {timed_diff} us') - assert abs(timed_diff) < 100 - - match = dut.expect(PERIODIC_TIMER_REGEX, timeout=2) - cur_time = int(match.group(1)) - diff = start_time + (i + 1) * FINAL_TIMER_PERIOD - cur_time - logging.info(f'Callback #{i}, time: {cur_time} us, diff: {diff} us') - assert abs(diff) < 100 + expect_periodic_callbacks( + dut, + restart_time + (i + 1) * FINAL_TIMER_PERIOD, + timed_one_shot_timer_time + i * FINAL_TIMER_PERIOD, + timer_tolerance, + i, + ) if dut.app.sdkconfig.get('SOC_LIGHT_SLEEP_SUPPORTED'): match = dut.expect(LIGHT_SLEEP_ENTER_REGEX, timeout=2) @@ -122,17 +141,35 @@ def test_esp_timer(dut: Dut) -> None: assert -2000 < sleep_time - LIGHT_SLEEP_TIME < 1000 - for i in range(5, 7): - timed_match = dut.expect(TIMED_PERIODIC_TIMER_REGEX, timeout=2) - timed_cur_time = int(timed_match.group(1)) - timed_diff = TIMED_RESTART_TIME + (i - 1) * FINAL_TIMER_PERIOD - timed_cur_time - logging.info(f'Timed Callback #{i}, time: {timed_cur_time} us, diff: {timed_diff} us') - assert abs(timed_diff) < 100 - - match = dut.expect(PERIODIC_TIMER_REGEX, timeout=2) - cur_time = int(match.group(1)) - diff = abs(start_time + (i + 1) * FINAL_TIMER_PERIOD - cur_time) - logging.info(f'Callback #{i}, time: {cur_time} us, diff: {diff} us') - assert diff < 100 + # The example stops before the very last periodic callback that would pair + # with the final timed periodic callback, so only one more paired step is + # expected here. + for i in range(5, 6): + expect_periodic_callbacks( + dut, + restart_time + (i + 1) * FINAL_TIMER_PERIOD, + timed_one_shot_timer_time + i * FINAL_TIMER_PERIOD, + timer_tolerance, + i, + ) dut.expect(STOP_REGEX, timeout=2) + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'rtc', + ], + indirect=True, +) +@idf_parametrize('target', ['supported_targets'], indirect=['target']) +def test_esp_timer(dut: Dut) -> None: + run_esp_timer_example(dut) + + +@pytest.mark.host_test +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_esp_timer_linux(dut: Dut) -> None: + run_esp_timer_example(dut) diff --git a/examples/system/esp_timer/sdkconfig.defaults b/examples/system/esp_timer/sdkconfig.defaults index de24c037098..c6ef889be5e 100644 --- a/examples/system/esp_timer/sdkconfig.defaults +++ b/examples/system/esp_timer/sdkconfig.defaults @@ -1,9 +1,2 @@ # With this option enabled, esp_timer_dump() prints more data about timers in the output log CONFIG_ESP_TIMER_PROFILING=y - -# LIBC_NEWLIB_NANO_FORMAT is enabled by default on ESP32-C2 -# This example needs 64-bit integer formatting, this is why this option is disabled -CONFIG_LIBC_NEWLIB_NANO_FORMAT=n - -# Put sleep related source code in IRAM -CONFIG_PM_SLP_IRAM_OPT=y diff --git a/examples/system/esp_timer/sdkconfig.defaults.esp32c2 b/examples/system/esp_timer/sdkconfig.defaults.esp32c2 new file mode 100644 index 00000000000..79caecf29e9 --- /dev/null +++ b/examples/system/esp_timer/sdkconfig.defaults.esp32c2 @@ -0,0 +1,3 @@ +# LIBC_NEWLIB_NANO_FORMAT is enabled by default on ESP32-C2 +# This example needs 64-bit integer formatting, this is why this option is disabled +CONFIG_LIBC_NEWLIB_NANO_FORMAT=n diff --git a/examples/system/esp_timer/sdkconfig.defaults.no_linux b/examples/system/esp_timer/sdkconfig.defaults.no_linux new file mode 100644 index 00000000000..dbb9a51b6c9 --- /dev/null +++ b/examples/system/esp_timer/sdkconfig.defaults.no_linux @@ -0,0 +1,2 @@ +# Put sleep related source code in IRAM +CONFIG_PM_SLP_IRAM_OPT=y diff --git a/tools/test_apps/components/test_utils/CMakeLists.txt b/tools/test_apps/components/test_utils/CMakeLists.txt index 5a56bbf3b5b..24165eaa8d5 100644 --- a/tools/test_apps/components/test_utils/CMakeLists.txt +++ b/tools/test_apps/components/test_utils/CMakeLists.txt @@ -1,3 +1,4 @@ +idf_build_get_property(target IDF_TARGET) idf_build_get_property(arch IDF_TARGET_ARCH) set(srcs "memory_checks.c" @@ -13,12 +14,18 @@ if(CONFIG_IDF_TARGET_ESP32) list(APPEND srcs "ref_clock_impl_rmt_pcnt.c") elseif(CONFIG_SOC_GPTIMER_SUPPORTED) list(APPEND srcs "ref_clock_impl_timergroup.c") +elseif(CONFIG_IDF_TARGET_LINUX) + list(APPEND srcs "ref_clock_impl_linux.c") endif() -if("${arch}" STREQUAL "xtensa") - set(priv_requires perfmon esp_driver_pcnt esp_driver_gptimer esp_driver_rmt esp_netif) +if(${target} STREQUAL "linux") + set(priv_requires "") else() - set(priv_requires esp_driver_pcnt esp_driver_gptimer esp_driver_rmt esp_netif) + if("${arch}" STREQUAL "xtensa") + set(priv_requires perfmon esp_driver_pcnt esp_driver_gptimer esp_driver_rmt esp_netif) + else() + set(priv_requires esp_driver_pcnt esp_driver_gptimer esp_driver_rmt esp_netif) + endif() endif() idf_component_register(SRCS ${srcs} diff --git a/tools/test_apps/components/test_utils/ref_clock_impl_linux.c b/tools/test_apps/components/test_utils/ref_clock_impl_linux.c new file mode 100644 index 00000000000..f2a0b653d2a --- /dev/null +++ b/tools/test_apps/components/test_utils/ref_clock_impl_linux.c @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +void ref_clock_init(void) +{ + +} + +void ref_clock_deinit(void) +{ + +} + +uint64_t ref_clock_get(void) +{ + struct timespec current_time; + assert(clock_gettime(CLOCK_MONOTONIC, ¤t_time) == 0); + + uint64_t ref_ticks = current_time.tv_sec * 1000000; + ref_ticks += (current_time.tv_nsec / 1000); + return ref_ticks; +} diff --git a/tools/test_apps/components/test_utils/test_runner.c b/tools/test_apps/components/test_utils/test_runner.c index b010a50214b..fa7c8744313 100644 --- a/tools/test_apps/components/test_utils/test_runner.c +++ b/tools/test_apps/components/test_utils/test_runner.c @@ -12,7 +12,9 @@ #include "unity.h" #include "unity_test_runner.h" #include "test_utils.h" +#ifndef CONFIG_IDF_TARGET_LINUX #include "esp_newlib.h" +#endif // !CONFIG_IDF_TARGET_LINUX #include "memory_checks.h" #ifdef CONFIG_HEAP_TRACING @@ -99,8 +101,10 @@ void tearDown(void) /* some FreeRTOS stuff is cleaned up by idle task */ vTaskDelay(5); +#ifndef CONFIG_IDF_TARGET_LINUX /* clean up some of the newlib's lazy allocations */ esp_reent_cleanup(); +#endif // !CONFIG_IDF_TARGET_LINUX /* We want the teardown to have this file in the printout if TEST_ASSERT fails */ const char *real_testfile = Unity.TestFile; diff --git a/tools/test_apps/components/test_utils/test_utils.c b/tools/test_apps/components/test_utils/test_utils.c index e09a07c2f86..2fa522280f9 100644 --- a/tools/test_apps/components/test_utils/test_utils.c +++ b/tools/test_apps/components/test_utils/test_utils.c @@ -9,8 +9,10 @@ #include "test_utils.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#ifndef CONFIG_IDF_TARGET_LINUX #include "esp_netif.h" #include "lwip/sockets.h" +#endif // !CONFIG_IDF_TARGET_LINUX #include "sdkconfig.h" #include "memory_checks.h" @@ -23,6 +25,7 @@ const esp_partition_t *get_test_data_partition(void) return result; } +#ifndef CONFIG_IDF_TARGET_LINUX void test_case_uses_tcpip(void) { // Can be called more than once, does nothing on subsequent calls @@ -50,6 +53,7 @@ void test_case_uses_tcpip(void) test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL); test_utils_set_leak_level(CONFIG_UNITY_CRITICAL_LEAK_LEVEL_LWIP, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_LWIP); } +#endif // !CONFIG_IDF_TARGET_LINUX // wait user to send "Enter" key or input parameter static void wait_user_control(char* parameter_buf, uint8_t buf_len)