From ed2e6735ff907eaacc1d8a254613ec650e949774 Mon Sep 17 00:00:00 2001 From: Ondrej Kosta Date: Wed, 18 Feb 2026 16:15:26 +0100 Subject: [PATCH] feat(esp_libc): refactored POSIX timers get/set functions - Added sys/timex.h and clock_adjtime API --- components/esp_eth/CMakeLists.txt | 4 + components/esp_eth/Kconfig | 22 + components/esp_eth/include/esp_eth_clock.h | 81 ++++ components/esp_eth/include/esp_eth_mac_esp.h | 27 ++ components/esp_eth/src/esp_eth.c | 2 +- .../esp_eth/src/eth_clock/esp_eth_clock.c | 293 +++++++++++++ components/esp_eth/src/mac/esp_eth_mac_esp.c | 17 +- components/esp_hal_emac/emac_hal.c | 20 + .../esp32p4/include/hal/emac_ll.h | 7 +- .../esp_hal_emac/include/hal/emac_hal.h | 20 +- components/esp_libc/CMakeLists.txt | 3 +- .../platform_include/esp_libc_clock.h | 60 +++ .../esp_libc/platform_include/sys/timex.h | 62 +++ .../priv_include/esp_libc_timekeeping.h | 43 ++ components/esp_libc/src/esp_libc_clock.lf | 15 + components/esp_libc/src/time.c | 397 +++++++++-------- components/esp_libc/src/timekeeping.c | 132 ++++++ .../test_apps/newlib/main/test_time.c | 402 ++++++++++++++++-- .../components/esp_eth_time/CMakeLists.txt | 3 - .../ptp/components/esp_eth_time/README.md | 3 - .../components/esp_eth_time/esp_eth_time.c | 153 ------- .../components/esp_eth_time/esp_eth_time.h | 133 ------ .../ptp/components/ptpd/Kconfig.projbuild | 18 +- .../ptp/components/ptpd/idf_component.yml | 3 - .../ptp/components/ptpd/port/esp_ptpd.c | 0 examples/ethernet/ptp/components/ptpd/ptpd.c | 39 +- examples/ethernet/ptp/main/idf_component.yml | 4 +- examples/ethernet/ptp/main/ptp_main.c | 10 +- 28 files changed, 1428 insertions(+), 545 deletions(-) create mode 100644 components/esp_eth/include/esp_eth_clock.h create mode 100644 components/esp_eth/src/eth_clock/esp_eth_clock.c create mode 100644 components/esp_libc/platform_include/esp_libc_clock.h create mode 100644 components/esp_libc/platform_include/sys/timex.h create mode 100644 components/esp_libc/priv_include/esp_libc_timekeeping.h create mode 100644 components/esp_libc/src/esp_libc_clock.lf create mode 100644 components/esp_libc/src/timekeeping.c delete mode 100644 examples/ethernet/ptp/components/esp_eth_time/CMakeLists.txt delete mode 100644 examples/ethernet/ptp/components/esp_eth_time/README.md delete mode 100644 examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.c delete mode 100644 examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.h delete mode 100644 examples/ethernet/ptp/components/ptpd/idf_component.yml delete mode 100644 examples/ethernet/ptp/components/ptpd/port/esp_ptpd.c diff --git a/components/esp_eth/CMakeLists.txt b/components/esp_eth/CMakeLists.txt index 3a90334ed5..44028df899 100644 --- a/components/esp_eth/CMakeLists.txt +++ b/components/esp_eth/CMakeLists.txt @@ -32,6 +32,10 @@ if(CONFIG_ETH_ENABLED) "src/phy/esp_eth_phy_generic.c") endif() + if(CONFIG_SOC_EMAC_IEEE1588V2_SUPPORTED) + list(APPEND srcs "src/eth_clock/esp_eth_clock.c") + endif() + if(CONFIG_ETH_USE_OPENETH) list(APPEND srcs "src/openeth/esp_eth_mac_openeth.c" "src/phy/esp_eth_phy_generic.c") diff --git a/components/esp_eth/Kconfig b/components/esp_eth/Kconfig index 1a83f83081..2ec19d68bd 100644 --- a/components/esp_eth/Kconfig +++ b/components/esp_eth/Kconfig @@ -56,6 +56,28 @@ menu "Ethernet" If enabled, functions related to RX/TX are placed into IRAM. It can improve Ethernet throughput. If disabled, all functions are placed into FLASH. + menu "Ethernet Clock" + depends on SOC_EMAC_IEEE1588V2_SUPPORTED + + config ETH_CLOCK_ADJTIME_PERIOD_MS + int "Period over which adjtime() applies corrections (ms)" + default 1000 + range 100 10000 + help + The time period over which a clock_adjtime() correction is applied + by adjusting the hardware clock rate. The PTP clock's tick rate (ppb) + is computed as: ppb = delta_ns * 1000 / ETH_CLOCK_ADJTIME_PERIOD_MS. + + config ETH_CLOCK_ADJTIME_SLEWLIMIT_PPB + int "Maximum clock slew rate (ppb)" + default 500000 + range 1000 5000000 + help + Drift estimates exceeding this limit (in ppb) are rejected as + out of range. Typical crystal oscillators drift less than 50 ppm + (50000 ppb). + endmenu + endif # ETH_USE_ESP32_EMAC menuconfig ETH_USE_SPI_ETHERNET bool "Support SPI to Ethernet Module" diff --git a/components/esp_eth/include/esp_eth_clock.h b/components/esp_eth/include/esp_eth_clock.h new file mode 100644 index 0000000000..f9187219e7 --- /dev/null +++ b/components/esp_eth/include/esp_eth_clock.h @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "esp_eth_driver.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef SOC_EMAC_IEEE1588V2_SUPPORTED + +/** + * @brief PTP clock identifier + * + */ +#define CLOCK_PTP_SYSTEM ((clockid_t) 19) + +/** + * @brief Configuration of clock during initialization + * + */ +typedef struct { + clockid_t clock_id; +} esp_eth_clock_cfg_t; + +/** + * @brief Set the target time for the PTP clock. + * + * @param clk_id Identifier of the clock to set the target time for + * @param tp Pointer to the target time + * + * @return + * - 0: Success + * - -1: Failure + */ +int esp_eth_clock_set_target_time(clockid_t clock_id, struct timespec *tp); + +/** + * @brief Register callback function invoked on Time Stamp target time exceeded interrupt + * + * @param clock_id Identifier of the clock + * @param ts_callback callback function to be registered + * @return + * - 0: Success + * - -1: Failure + */ +int esp_eth_clock_register_target_cb(clockid_t clock_id, + ts_target_exceed_cb_from_isr_t ts_callback); + +/** + * @brief Initialize the Ethernet PTP clock subsystem + * + * Enables PTP timestamping on the Ethernet MAC and registers the PTP clock + * with the POSIX time subsystem. After initialization, the clock is accessible + * via standard clock_gettime(CLOCK_PTP_SYSTEM, ...), + * clock_settime(CLOCK_PTP_SYSTEM, ...), and + * clock_adjtime(CLOCK_PTP_SYSTEM, ...) with ADJ_OFFSET / ADJ_FREQUENCY. + * + * @param eth_hndl Ethernet handle to initialize the clock for + * @param cfg Pointer to the configuration structure + * + * @return + * - ESP_OK: Success + * - ESP_FAIL: Failure + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Clock is already initialized + */ +esp_err_t esp_eth_clock_init(esp_eth_handle_t eth_hndl, const esp_eth_clock_cfg_t *cfg); + +#endif // SOC_EMAC_IEEE1588V2_SUPPORTED + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_eth/include/esp_eth_mac_esp.h b/components/esp_eth/include/esp_eth_mac_esp.h index 53cdbff68d..5e287ec87b 100644 --- a/components/esp_eth/include/esp_eth_mac_esp.h +++ b/components/esp_eth/include/esp_eth_mac_esp.h @@ -339,6 +339,23 @@ esp_err_t esp_eth_mac_adj_ptp_freq(esp_eth_mac_t *mac, double scale_factor); */ esp_err_t esp_eth_mac_adj_ptp_time(esp_eth_mac_t *mac, int32_t adj_ppb); +/** + * @brief Adjust PTP frequency relative to its current addend value by ppb + * + * Unlike esp_eth_mac_adj_ptp_time (absolute from base) or esp_eth_mac_adj_ptp_freq + * (relative by double scale factor), this adjusts the current addend by ppb: + * addend_new = current * (1 + adj_ppb / 10^9). Calling with adj_ppb=0 is a no-op. + * + * @param mac: Ethernet MAC instance + * @param adj_ppb: relative frequency adjustment in parts per billion + * + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_ARG: invalid argument + * - ESP_FAIL: failure + */ +esp_err_t esp_eth_mac_adj_ptp_freq_ppb(esp_eth_mac_t *mac, int32_t adj_ppb); + /** * @brief Set Target Time at which interrupt is invoked when PTP time exceeds this value * @@ -404,6 +421,16 @@ esp_err_t esp_eth_mac_set_pps_out_gpio(esp_eth_mac_t *mac, int gpio_num); * - ESP_FAIL: failure */ esp_err_t esp_eth_mac_set_pps_out_freq(esp_eth_mac_t *mac, uint32_t freq_hz); + +/** + * @brief Get PTP timestamp resolution + * + * @param mac: Ethernet MAC instance + * + * @return + * - PTP timestamp resolution in nanoseconds + */ +uint32_t esp_eth_mac_get_ts_resolution(esp_eth_mac_t *mac); #endif // SOC_EMAC_IEEE1588V2_SUPPORTED #endif // CONFIG_ETH_USE_ESP32_EMAC diff --git a/components/esp_eth/src/esp_eth.c b/components/esp_eth/src/esp_eth.c index f5df137014..76dab829d2 100644 --- a/components/esp_eth/src/esp_eth.c +++ b/components/esp_eth/src/esp_eth.c @@ -414,13 +414,13 @@ esp_err_t esp_eth_transmit_ctrl_vargs(esp_eth_handle_t hdl, void *ctrl, uint32_t goto err; } - va_list args; esp_eth_mac_t *mac = eth_driver->mac; #if CONFIG_ETH_TRANSMIT_MUTEX if (xSemaphoreTake(eth_driver->transmit_mutex, pdMS_TO_TICKS(ESP_ETH_TX_TIMEOUT_MS)) == pdFALSE) { return ESP_ERR_TIMEOUT; } #endif // CONFIG_ETH_TRANSMIT_MUTEX + va_list args = {0}; va_start(args, argc); ret = mac->transmit_ctrl_vargs(mac, ctrl, argc, args); #if CONFIG_ETH_TRANSMIT_MUTEX diff --git a/components/esp_eth/src/eth_clock/esp_eth_clock.c b/components/esp_eth/src/eth_clock/esp_eth_clock.c new file mode 100644 index 0000000000..6b7d2e8c38 --- /dev/null +++ b/components/esp_eth/src/eth_clock/esp_eth_clock.c @@ -0,0 +1,293 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_timer.h" +#include "esp_task.h" +#include "esp_eth_clock.h" +#include "esp_eth_mac_esp.h" +#include "esp_libc_clock.h" +#include "sdkconfig.h" + +static esp_eth_mac_t *s_mac; +static portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED; + +/** PTP clock offset-slew state. */ +static struct { + int32_t slew_ppb; + int64_t remaining_ns; // remaining offset in nanoseconds + int64_t mono_start_us; // start time of the slew in microseconds +} s_ptp_adj; + +static int esp_eth_clock_esp_err_to_errno(esp_err_t esp_err) +{ + switch (esp_err) + { + case ESP_ERR_INVALID_ARG: + return EINVAL; + case ESP_ERR_INVALID_STATE: + return EBUSY; + case ESP_ERR_TIMEOUT: + return ETIME; + } + // default "no err" when error cannot be isolated + return 0; +} + +static void ptp_adj_clear_state(void) +{ + s_ptp_adj.slew_ppb = 0; + s_ptp_adj.remaining_ns = 0; + s_ptp_adj.mono_start_us = 0; +} + +static int esp_eth_clock_ptp_gettime(struct timespec *tp, void *ctx) +{ + (void)ctx; + if (s_mac == NULL) { + errno = ENODEV; + return -1; + } + + eth_mac_time_t ptp_time; + esp_err_t ret = esp_eth_mac_get_ptp_time(s_mac, &ptp_time); + if (ret != ESP_OK) { + errno = esp_eth_clock_esp_err_to_errno(ret); + return -1; + } + tp->tv_sec = ptp_time.seconds; + tp->tv_nsec = ptp_time.nanoseconds; + + return 0; +} + +static int esp_eth_clock_ptp_settime(const struct timespec *tp, void *ctx) +{ + (void)ctx; + if (s_mac == NULL) { + errno = ENODEV; + return -1; + } + + eth_mac_time_t ptp_time = { + .seconds = tp->tv_sec, + .nanoseconds = tp->tv_nsec + }; + esp_err_t ret = ESP_OK; + portENTER_CRITICAL_SAFE(&s_time_update_lock); + if ((s_ptp_adj.slew_ppb != 0) || (s_ptp_adj.remaining_ns != 0)) { + ret = esp_eth_mac_adj_ptp_time(s_mac, 0); + if (ret == ESP_OK) { + ptp_adj_clear_state(); + } + } + if (ret == ESP_OK) { + ret = esp_eth_mac_set_ptp_time(s_mac, &ptp_time); + } + portEXIT_CRITICAL_SAFE(&s_time_update_lock); + if (ret != ESP_OK) { + errno = esp_eth_clock_esp_err_to_errno(ret); + return -1; + } + + return 0; +} + +static int64_t ptp_adj_update_remaining(int64_t now_us) +{ + if (s_ptp_adj.slew_ppb != 0) { + int64_t elapsed_us = now_us - s_ptp_adj.mono_start_us; + s_ptp_adj.mono_start_us = now_us; + + int64_t corrected_ns = (int64_t)s_ptp_adj.slew_ppb * elapsed_us / 1000000LL; + s_ptp_adj.remaining_ns -= corrected_ns; + + bool done = (s_ptp_adj.slew_ppb > 0) + ? (s_ptp_adj.remaining_ns <= 0) + : (s_ptp_adj.remaining_ns >= 0); + if (done) { + ptp_adj_clear_state(); + esp_eth_mac_adj_ptp_time(s_mac, 0); + } + } + return s_ptp_adj.remaining_ns; +} + +static esp_err_t ptp_adj_apply_slew(int64_t now_us, int32_t slew_ppb, + int64_t offset_ns, int64_t *out_remaining_ns) +{ + ptp_adj_update_remaining(now_us); + esp_err_t ret = esp_eth_mac_adj_ptp_time(s_mac, slew_ppb); + if (ret == ESP_OK) { + if (slew_ppb == 0) { + ptp_adj_clear_state(); + } else { + s_ptp_adj.remaining_ns = offset_ns; + s_ptp_adj.slew_ppb = slew_ppb; + s_ptp_adj.mono_start_us = now_us; + } + } + *out_remaining_ns = s_ptp_adj.remaining_ns; + return ret; +} + + /** + * @brief Adjust the PTP clock. + * + * @param buf Pointer to struct timex with modes and values to apply. + * @return 0 on success, -1 on error (with errno set). + * + * Modes: + * ADJ_FREQUENCY -- relative ppb via esp_eth_mac_adj_ptp_freq_ppb() + * (emac_hal_ptp_adj_freq: relative to current addend) + * ADJ_OFFSET -- temporary offset slewing via esp_eth_mac_adj_ptp_time() + * over a period of CONFIG_ETH_CLOCK_ADJTIME_PERIOD_MS milliseconds + * (emac_hal_ptp_adj_inc: absolute ppb from base addend) + * + * esp_timer time is the reference for measuring slew progress + * because the PTP clock itself is being adjusted + * + * Note: Adjusting time by ADJ_FREQUENCY is more accurate than ADJ_OFFSET. + */ +static int esp_eth_clock_ptp_adjtime(struct timex *buf, void *ctx) +{ + (void)ctx; + if (s_mac == NULL) { + errno = ENODEV; + return -1; + } + + if (buf->modes & ADJ_FREQUENCY) { + portENTER_CRITICAL_SAFE(&s_time_update_lock); + esp_err_t ret = esp_eth_mac_adj_ptp_freq_ppb(s_mac, (int32_t)buf->freq); + portEXIT_CRITICAL_SAFE(&s_time_update_lock); + if (ret != ESP_OK) { + errno = esp_eth_clock_esp_err_to_errno(ret); + return -1; + } + return 0; + } + + int64_t remaining_ns; + int64_t now_us; + if ((buf->modes & ADJ_OFFSET) && buf->modes != ADJ_OFFSET_SS_READ) { + bool offset_in_ns = (buf->modes & ADJ_NANO) == ADJ_NANO; + int64_t offset_ns = offset_in_ns ? (int64_t)buf->offset : (int64_t)buf->offset * 1000LL; + int32_t slew_ppb = 0; + if (offset_ns != 0) { + // ppb = (offset / period) * 10^9 => ns / ms => 10^(9+3) / 10^3 == 10^9 + int64_t ppb64 = offset_ns * 1000 / CONFIG_ETH_CLOCK_ADJTIME_PERIOD_MS; + // clamp to slew limit + slew_ppb = (ppb64 > CONFIG_ETH_CLOCK_ADJTIME_SLEWLIMIT_PPB) ? CONFIG_ETH_CLOCK_ADJTIME_SLEWLIMIT_PPB + : (ppb64 < -CONFIG_ETH_CLOCK_ADJTIME_SLEWLIMIT_PPB) ? -CONFIG_ETH_CLOCK_ADJTIME_SLEWLIMIT_PPB + : (int32_t)ppb64; + } + now_us = esp_timer_get_time(); + esp_err_t ret; + portENTER_CRITICAL_SAFE(&s_time_update_lock); + ret = ptp_adj_apply_slew(now_us, slew_ppb, offset_ns, &remaining_ns); + portEXIT_CRITICAL_SAFE(&s_time_update_lock); + buf->offset = offset_in_ns ? (long)remaining_ns : (long)(remaining_ns / 1000); + if (ret != ESP_OK) { + errno = esp_eth_clock_esp_err_to_errno(ret); + return -1; + } + return 0; + } + + // Read-only: ADJ_OFFSET_SS_READ or unrecognized mode + now_us = esp_timer_get_time(); + portENTER_CRITICAL_SAFE(&s_time_update_lock); + remaining_ns = ptp_adj_update_remaining(now_us); + portEXIT_CRITICAL_SAFE(&s_time_update_lock); + // ADJ_OFFSET_SS_READ always returns microseconds; otherwise respect ADJ_NANO + if (buf->modes != ADJ_OFFSET_SS_READ && (buf->modes & ADJ_NANO)) { + buf->offset = (long)remaining_ns; + } else { + buf->offset = (long)(remaining_ns / 1000); + } + return 0; +} + +static int esp_eth_clock_ptp_getres(struct timespec *res, void *ctx) +{ + (void)ctx; + if (s_mac == NULL) { + errno = ENODEV; + return -1; + } + + res->tv_sec = 0; + res->tv_nsec = esp_eth_mac_get_ts_resolution(s_mac); + if (res->tv_nsec == 0) { + errno = EINVAL; + return -1; + } + return 0; +} + +static const esp_libc_clock_ops_t s_ptp_system_ops = { + .gettime = esp_eth_clock_ptp_gettime, + .settime = esp_eth_clock_ptp_settime, + .adjtime = esp_eth_clock_ptp_adjtime, + .getres = esp_eth_clock_ptp_getres +}; + +ESP_LIBC_CLOCK_REGISTER(ptp_system, CLOCK_PTP_SYSTEM, s_ptp_system_ops, NULL); + +int esp_eth_clock_set_target_time(clockid_t clock_id, struct timespec *tp) +{ + eth_mac_time_t mac_target_time = { + .seconds = tp->tv_sec, + .nanoseconds = tp->tv_nsec + }; + esp_err_t ret = esp_eth_mac_set_target_time(s_mac, &mac_target_time); + if (ret != ESP_OK) { + errno = esp_eth_clock_esp_err_to_errno(ret); + return -1; + } + return 0; +} + +int esp_eth_clock_register_target_cb(clockid_t clock_id, + ts_target_exceed_cb_from_isr_t ts_callback) +{ + esp_err_t ret = esp_eth_mac_set_target_time_cb(s_mac, ts_callback); + if (ret != ESP_OK) { + errno = esp_eth_clock_esp_err_to_errno(ret); + return -1; + } + return 0; +} + +esp_err_t esp_eth_clock_init(esp_eth_handle_t eth_hndl, const esp_eth_clock_cfg_t *cfg) +{ + if (s_mac != NULL) { + return ESP_ERR_INVALID_STATE; + } + if (cfg == NULL) { + return ESP_ERR_INVALID_ARG; + } + switch (cfg->clock_id) { + case CLOCK_PTP_SYSTEM: { + eth_mac_ptp_config_t ptp_cfg = ETH_MAC_ESP_PTP_DEFAULT_CONFIG(); + esp_eth_mac_t *mac; + if (esp_eth_get_mac_instance(eth_hndl, &mac) != ESP_OK) { + return ESP_FAIL; + } + if (esp_eth_mac_ptp_enable(mac, &ptp_cfg) != ESP_OK) { + return ESP_FAIL; + } + s_mac = mac; + break; + } + default: + return ESP_FAIL; + } + return ESP_OK; +} diff --git a/components/esp_eth/src/mac/esp_eth_mac_esp.c b/components/esp_eth/src/mac/esp_eth_mac_esp.c index a1664fc055..aa979d1c7a 100644 --- a/components/esp_eth/src/mac/esp_eth_mac_esp.c +++ b/components/esp_eth/src/mac/esp_eth_mac_esp.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -889,6 +889,14 @@ esp_err_t esp_eth_mac_adj_ptp_time(esp_eth_mac_t *mac, int32_t adj_ppb) return ESP_OK; } +esp_err_t esp_eth_mac_adj_ptp_freq_ppb(esp_eth_mac_t *mac, int32_t adj_ppb) +{ + ESP_RETURN_ON_FALSE(mac, ESP_ERR_INVALID_ARG, TAG, "invalid argument, can't be NULL"); + emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent); + ESP_RETURN_ON_ERROR(emac_hal_ptp_adj_freq(&emac->hal, adj_ppb), TAG, "failed to adjust PTP frequency"); + return ESP_OK; +} + esp_err_t esp_eth_mac_set_target_time(esp_eth_mac_t *mac, const eth_mac_time_t *target) { ESP_RETURN_ON_FALSE(mac && target, ESP_ERR_INVALID_ARG, TAG, "invalid argument, can't be NULL"); @@ -929,6 +937,13 @@ esp_err_t esp_eth_mac_set_pps_out_freq(esp_eth_mac_t *mac, uint32_t freq_hz) ESP_RETURN_ON_ERROR(emac_hal_set_pps0_out_freq(&emac->hal, freq_hz), TAG, "failed to set PPS0 output frequency"); return ESP_OK; } + +uint32_t esp_eth_mac_get_ts_resolution(esp_eth_mac_t *mac) +{ + ESP_RETURN_ON_FALSE(mac, ESP_ERR_INVALID_ARG, TAG, "invalid argument, can't be NULL"); + emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent); + return emac_hal_get_ts_resolution(&emac->hal); +} #endif // SOC_EMAC_IEEE1588V2_SUPPORTED esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_esp32_emac_config_t *esp32_config, const eth_mac_config_t *config) diff --git a/components/esp_hal_emac/emac_hal.c b/components/esp_hal_emac/emac_hal.c index ee907c2e59..b9153164d4 100644 --- a/components/esp_hal_emac/emac_hal.c +++ b/components/esp_hal_emac/emac_hal.c @@ -504,6 +504,21 @@ esp_err_t emac_hal_adj_freq_factor(emac_hal_context_t *hal, double scale_factor) return ESP_OK; } +esp_err_t emac_hal_ptp_adj_freq(emac_hal_context_t *hal, int32_t adj_ppb) +{ + if (emac_ll_get_ts_update_method(hal->ptp_regs) != ETH_PTP_UPDATE_METHOD_FINE || + !emac_ll_is_ts_addend_update_done(hal->ptp_regs)) { + return ESP_ERR_INVALID_STATE; + } + uint32_t current = emac_ll_get_ts_addend_val(hal->ptp_regs); + int64_t addend_new = (int64_t)current * (1000000000ll + adj_ppb); + addend_new /= 1000000000ll; + + emac_ll_set_ts_addend_val(hal->ptp_regs, (uint32_t)addend_new); + emac_ll_ts_addend_do_update(hal->ptp_regs); + return ESP_OK; +} + esp_err_t emac_hal_ptp_time_add(emac_hal_context_t *hal, uint32_t off_sec, uint32_t off_nsec, bool sign) { emac_ll_set_ts_update_second_val(hal->ptp_regs, off_sec); @@ -570,6 +585,11 @@ esp_err_t emac_hal_set_pps0_out_freq(emac_hal_context_t *hal, uint32_t freq_hz) emac_ll_set_pps0_out_freq(hal->ptp_regs, n); return ESP_OK; } + +uint32_t emac_hal_get_ts_resolution(emac_hal_context_t *hal) +{ + return subsecond2nanosecond(hal, emac_ll_get_ts_sub_second_incre_val(hal->ptp_regs)); +} #endif // SOC_EMAC_IEEE1588V2_SUPPORTED void emac_hal_start(emac_hal_context_t *hal) diff --git a/components/esp_hal_emac/esp32p4/include/hal/emac_ll.h b/components/esp_hal_emac/esp32p4/include/hal/emac_ll.h index 9b3a1feb11..85470d441f 100644 --- a/components/esp_hal_emac/esp32p4/include/hal/emac_ll.h +++ b/components/esp_hal_emac/esp32p4/include/hal/emac_ll.h @@ -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 */ @@ -769,6 +769,11 @@ static inline void emac_ll_set_ts_sub_second_incre_val(emac_ptp_dev_t *ptp_regs, HAL_FORCE_MODIFY_U32_REG_FIELD(ptp_regs->sub_sec_incre, sub_second_incre_value, increment); } +static inline uint8_t emac_ll_get_ts_sub_second_incre_val(emac_ptp_dev_t *ptp_regs) +{ + return ptp_regs->sub_sec_incre.sub_second_incre_value; +} + /* addend control */ static inline void emac_ll_set_ts_addend_val(emac_ptp_dev_t *ptp_regs, uint32_t val) { diff --git a/components/esp_hal_emac/include/hal/emac_hal.h b/components/esp_hal_emac/include/hal/emac_hal.h index 466358afec..acdc545aec 100644 --- a/components/esp_hal_emac/include/hal/emac_hal.h +++ b/components/esp_hal_emac/include/hal/emac_hal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -372,6 +372,22 @@ esp_err_t emac_hal_ptp_adj_inc(emac_hal_context_t *hal, int32_t adj_ppb); */ esp_err_t emac_hal_adj_freq_factor(emac_hal_context_t *hal, double ratio); +/** + * @brief Adjust PTP addend register relative to its current value by ppb + * + * Unlike emac_hal_ptp_adj_inc (absolute from base) or emac_hal_adj_freq_factor + * (relative by double scale factor), this function adjusts the current addend + * by a ppb offset: addend_new = current * (1 + adj_ppb / 10^9). + * Calling with adj_ppb=0 is a no-op. + * + * @param hal EMAC HAL context infostructure + * @param adj_ppb relative frequency adjustment in parts per billion + * @return + * - ESP_OK: on success + * - ESP_ERR_INVALID_STATE: on PTP block is busy + */ +esp_err_t emac_hal_ptp_adj_freq(emac_hal_context_t *hal, int32_t adj_ppb); + /** * @brief Adds or subtracts to the PTP system time. * @@ -460,6 +476,8 @@ esp_err_t emac_hal_get_txdesc_timestamp(emac_hal_context_t *hal, eth_dma_tx_desc esp_err_t emac_hal_set_pps0_out_freq(emac_hal_context_t *hal, uint32_t freq_hz); +uint32_t emac_hal_get_ts_resolution(emac_hal_context_t *hal); + #endif // SOC_EMAC_IEEE1588V2_SUPPORTED #endif // SOC_EMAC_SUPPORTED diff --git a/components/esp_libc/CMakeLists.txt b/components/esp_libc/CMakeLists.txt index b02b5faae5..5c9462ca51 100644 --- a/components/esp_libc/CMakeLists.txt +++ b/components/esp_libc/CMakeLists.txt @@ -26,6 +26,7 @@ set(srcs "src/termios.c" "src/stdatomic.c" "src/time.c" + "src/timekeeping.c" "src/sysconf.c" "src/realpath.c" "src/scandir.c" @@ -87,7 +88,7 @@ else() endif() set(ldfragments "") -list(APPEND ldfragments "src/esp_libc.lf" "src/system_libs.lf") +list(APPEND ldfragments "src/esp_libc.lf" "src/esp_libc_clock.lf" "src/system_libs.lf") if(CONFIG_LIBC_NEWLIB) list(APPEND ldfragments src/libc.lf) diff --git a/components/esp_libc/platform_include/esp_libc_clock.h b/components/esp_libc/platform_include/esp_libc_clock.h new file mode 100644 index 0000000000..b73a8712e9 --- /dev/null +++ b/components/esp_libc/platform_include/esp_libc_clock.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Operations for a clock source + * + * Callbacks return 0 on success, -1 on error (with errno set). + */ +typedef struct { + int (*gettime)(struct timespec *tp, void *ctx); /*!< Get current time (required) */ + int (*settime)(const struct timespec *tp, void *ctx); /*!< Set current time (optional, may be NULL) */ + int (*adjtime)(struct timex *buf, void *ctx); /*!< Adjust clock via struct timex (optional, may be NULL) */ + int (*getres)(struct timespec *res, void *ctx); /*!< Get clock resolution (required) */ +} esp_libc_clock_ops_t; + +/** + * @brief Link-time clock descriptor (placed in .esp_libc_clock_desc) + */ +typedef struct { + clockid_t clk_id; + esp_libc_clock_ops_t ops; + void *ctx; +} esp_libc_clock_desc_t; + +/** + * @brief Register a clock at link time + * + * Place this macro at file scope after defining the clock operation functions. + * The linker collects all descriptors into a contiguous array; clock_gettime(), + * clock_settime(), clock_getres(), and clock_adjtime() dispatch by clock ID. + * + * @note The entity registering the clock is responsible for ensuring the + * clock operations functions are thread safe. + * + * @param name_token Unique C identifier token for the static descriptor symbol + * @param clk_id_val Clock ID (e.g. CLOCK_REALTIME or a custom clockid_t) + * @param ops_symbol lvalue of type esp_libc_clock_ops_t (e.g. s_my_ops) + * @param ctx_val Context pointer passed to callbacks (may be NULL) + */ +#define ESP_LIBC_CLOCK_REGISTER(name_token, clk_id_val, ops_symbol, ctx_val) \ + static const esp_libc_clock_desc_t _esp_libc_clock_desc_##name_token \ + __attribute__((used, section(".esp_libc_clock_desc"))) = { \ + .clk_id = (clk_id_val), .ops = (ops_symbol), .ctx = (ctx_val) } + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_libc/platform_include/sys/timex.h b/components/esp_libc/platform_include/sys/timex.h new file mode 100644 index 0000000000..93584347a4 --- /dev/null +++ b/components/esp_libc/platform_include/sys/timex.h @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Minimal subset of for clock_adjtime() support. + * Original struct timex and ADJ_* constants by David L. Mills (1993). + * + * Only the modes, offset, and freq fields are implemented. + * freq uses ppb (parts per billion) units instead of Linux's scaled ppm. + */ + +#define ADJ_OFFSET 0x0001 /* time offset; microseconds by default, nanoseconds with ADJ_NANO */ +#define ADJ_FREQUENCY 0x0002 /* frequency offset in ppb (parts per billion) */ +#define ADJ_MICRO 0x1000 /* select microsecond resolution */ +#define ADJ_NANO 0x2000 /* select nanosecond resolution */ +#define ADJ_OFFSET_SINGLESHOT 0x8001 /* old-fashioned adjtime (offset in microseconds)*/ +#define ADJ_OFFSET_SS_READ 0xa001 /* read-only adjtime (remaining offset in microseconds) */ + +struct timex { + int modes; /*!< Mode selector (ADJ_OFFSET, ADJ_FREQUENCY, etc.) */ + long offset; /*!< Time offset: microseconds by default, nanoseconds when ADJ_NANO is set */ + long freq; /*!< Frequency offset in ppb (parts per billion) */ +}; + +/** + * @brief Adjust a clock identified by clk_id + * + * Uses struct timex.modes to select which fields to apply: + * - ADJ_OFFSET: gradually slew by buf->offset + * - ADJ_FREQUENCY: set frequency trim to buf->freq ppb + * - ADJ_OFFSET_SINGLESHOT: old-style adjtime(), buf->offset + * - ADJ_OFFSET_SS_READ: read remaining adjtime() correction into buf->offset + * + * buf->offset uses microseconds by default. Set ADJ_NANO in modes to use + * nanoseconds instead. ADJ_MICRO may be used to explicitly select the default. + * + * ADJ_OFFSET and ADJ_FREQUENCY are mutually exclusive; do not OR them together. + * + * @param clk_id Clock identifier (CLOCK_REALTIME, or a custom registered clock) + * @param buf Pointer to struct timex with modes and values to apply. + * On return, buf->offset may be updated (e.g. ADJ_OFFSET_SS_READ). + * + * @return + * - 0: Success + * - -1: Failure (errno is set) + */ +int clock_adjtime(clockid_t clk_id, struct timex *buf); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_libc/priv_include/esp_libc_timekeeping.h b/components/esp_libc/priv_include/esp_libc_timekeeping.h new file mode 100644 index 0000000000..d3b53e42fe --- /dev/null +++ b/components/esp_libc/priv_include/esp_libc_timekeeping.h @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get current realtime in microseconds (adjusted boot time + time_since_boot). + */ +uint64_t esp_libc_timekeeping_get_realtime_us(void); + +/** + * @brief Set realtime to the given epoch in microseconds (stops any slew, sets boot time). + */ +void esp_libc_timekeeping_set_realtime_us(uint64_t us); + +/** + * @brief Get remaining adjtime correction in microseconds. + * + * @return Remaining slew in microseconds. + */ +int64_t esp_libc_timekeeping_adjtime_get_remaining_us(void); + +/** + * @brief Apply an adjtime offset (slew) in microseconds. + * + * @param offset_us Offset to apply in microseconds (positive or negative). + * @param prev_remaining_us On success, set to previous remaining correction in microseconds. + * @return 0 on success, -1 on error (e.g. invalid offset magnitude). + */ +int esp_libc_timekeeping_adjtime_apply(int64_t offset_us, int64_t *prev_remaining_us); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_libc/src/esp_libc_clock.lf b/components/esp_libc/src/esp_libc_clock.lf new file mode 100644 index 0000000000..c582b65758 --- /dev/null +++ b/components/esp_libc/src/esp_libc_clock.lf @@ -0,0 +1,15 @@ +# Static registration of clock descriptors at link time + +[sections:esp_libc_clock_desc] +entries: + .esp_libc_clock_desc + +[scheme:esp_libc_clock_desc_default] +entries: + esp_libc_clock_desc -> flash_rodata + +[mapping:esp_libc_clock_desc] +archive: * +entries: + * (esp_libc_clock_desc_default); + esp_libc_clock_desc -> flash_rodata KEEP() SORT(name) SURROUND(esp_libc_clock_array) diff --git a/components/esp_libc/src/time.c b/components/esp_libc/src/time.c index 1f609422fb..991d58dc55 100644 --- a/components/esp_libc/src/time.c +++ b/components/esp_libc/src/time.c @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include -#include +#include +#include #include #include #include @@ -13,8 +14,8 @@ #include #include #include +#include #include -#include #include "esp_system.h" #include "esp_attr.h" @@ -27,123 +28,192 @@ #include "soc/rtc.h" #include "esp_time_impl.h" +#include "esp_libc_timekeeping.h" #include "sdkconfig.h" +#include "esp_libc_clock.h" #if !CONFIG_ESP_TIME_FUNCS_USE_NONE #define IMPL_NEWLIB_TIME_FUNCS 1 #endif #if IMPL_NEWLIB_TIME_FUNCS +extern const esp_libc_clock_desc_t _esp_libc_clock_array_start[]; +extern const esp_libc_clock_desc_t _esp_libc_clock_array_end[]; + // time functions are implemented -- they should not be weak #define WEAK_UNLESS_TIMEFUNC_IMPL -// stores the start time of the slew -static uint64_t s_adjtime_start_us; -// is how many microseconds total to slew -static int64_t s_adjtime_total_correction_us; - -static _lock_t s_time_lock; - -// This function gradually changes boot_time to the correction value and immediately updates it. -static uint64_t adjust_boot_time(void) +/* Built-in CLOCK_REALTIME: thin wrappers calling esp_libc_timekeeping (µs) API; unit conversion here. */ +static int realtime_gettime(struct timespec *tp, void *ctx) { -#define ADJTIME_CORRECTION_FACTOR 6 + (void)ctx; + uint64_t microseconds = esp_libc_timekeeping_get_realtime_us(); + tp->tv_sec = microseconds / 1000000; + tp->tv_nsec = (microseconds % 1000000) * 1000L; + return 0; +} - uint64_t boot_time = esp_time_impl_get_boot_time(); - if ((boot_time == 0) || (esp_time_impl_get_time_since_boot() < s_adjtime_start_us)) { - s_adjtime_start_us = 0; +static int realtime_settime(const struct timespec *tp, void *ctx) +{ + (void)ctx; + uint64_t us = ((uint64_t) tp->tv_sec) * 1000000LL + (tp->tv_nsec / 1000L); + esp_libc_timekeeping_set_realtime_us(us); + return 0; +} + +static int realtime_adjtime(struct timex *buf, void *ctx) +{ + (void)ctx; + bool offset_in_ns = (buf->modes & ADJ_NANO) == ADJ_NANO; + if (buf->modes & ADJ_OFFSET) { + if (buf->modes == ADJ_OFFSET_SS_READ) { + // ADJ_OFFSET_SS_READ is always in microseconds + buf->offset = (long)esp_libc_timekeeping_adjtime_get_remaining_us(); + return 0; + } + int64_t offset_us = offset_in_ns ? (int64_t)buf->offset / 1000 : (int64_t)buf->offset; + int64_t prev_us; + if (esp_libc_timekeeping_adjtime_apply(offset_us, &prev_us) != 0) { + return -1; + } + buf->offset = offset_in_ns ? (long)(prev_us * 1000) : (long)prev_us; + return 0; } - if (s_adjtime_start_us > 0) { - uint64_t since_boot = esp_time_impl_get_time_since_boot(); - // If to call this function once per second, then (since_boot - s_adjtime_start_us) will be 1_000_000 (1 second), - // and the correction will be equal to (1_000_000us >> 6) = 15_625 us. - // The minimum possible correction step can be (64us >> 6) = 1us. - // Example: if the time error is 1 second, then it will be compensate for 1 sec / 0,015625 = 64 seconds. - int64_t correction = (since_boot >> ADJTIME_CORRECTION_FACTOR) - (s_adjtime_start_us >> ADJTIME_CORRECTION_FACTOR); - if (correction > 0) { - s_adjtime_start_us = since_boot; - if (s_adjtime_total_correction_us < 0) { - if ((s_adjtime_total_correction_us + correction) >= 0) { - boot_time = boot_time + s_adjtime_total_correction_us; - s_adjtime_start_us = 0; - } else { - s_adjtime_total_correction_us += correction; - boot_time -= correction; - } - } else { - if ((s_adjtime_total_correction_us - correction) <= 0) { - boot_time = boot_time + s_adjtime_total_correction_us; - s_adjtime_start_us = 0; - } else { - s_adjtime_total_correction_us -= correction; - boot_time += correction; - } - } - esp_time_impl_set_boot_time(boot_time); + if (buf->modes & ADJ_FREQUENCY) { + errno = EOPNOTSUPP; + return -1; + } + // if no known mode specified, just populate the timex structure with the current state + buf->offset = (long)esp_libc_timekeeping_adjtime_get_remaining_us(); + buf->offset = offset_in_ns ? buf->offset * 1000 : buf->offset; + buf->freq = 0; + return 0; +} + +static int realtime_getres(struct timespec *res, void *ctx) +{ + (void)ctx; + res->tv_sec = 0; + res->tv_nsec = esp_system_get_time_resolution(); + return 0; +} + +/* Built-in CLOCK_MONOTONIC (time since boot) */ +static int monotonic_gettime(struct timespec *tp, void *ctx) +{ + (void)ctx; + uint64_t monotonic_time_us = esp_time_impl_get_time(); + tp->tv_sec = monotonic_time_us / 1000000LL; + tp->tv_nsec = (monotonic_time_us % 1000000LL) * 1000L; + return 0; +} + +static int monotonic_getres(struct timespec *res, void *ctx) +{ + (void)ctx; + res->tv_sec = 0; + res->tv_nsec = esp_system_get_time_resolution(); + return 0; +} + +static const esp_libc_clock_ops_t s_realtime_ops = { + .gettime = realtime_gettime, + .settime = realtime_settime, + .adjtime = realtime_adjtime, + .getres = realtime_getres +}; + +static const esp_libc_clock_ops_t s_monotonic_ops = { + .gettime = monotonic_gettime, + .settime = NULL, + .adjtime = NULL, + .getres = monotonic_getres +}; + +ESP_LIBC_CLOCK_REGISTER(_realtime, CLOCK_REALTIME, s_realtime_ops, NULL); +ESP_LIBC_CLOCK_REGISTER(_monotonic, CLOCK_MONOTONIC, s_monotonic_ops, NULL); + +static void esp_clock_registry_check_duplicates(void) +{ + for (const esp_libc_clock_desc_t *it = _esp_libc_clock_array_start; + it < _esp_libc_clock_array_end; ++it) { + for (const esp_libc_clock_desc_t *jt = it + 1; + jt < _esp_libc_clock_array_end; ++jt) { + assert(it->clk_id != jt->clk_id); } } - return boot_time; } -// Get the adjusted boot time. -static uint64_t get_adjusted_boot_time(void) +static const esp_libc_clock_desc_t *esp_clock_find_entry(clockid_t clk_id) { - _lock_acquire(&s_time_lock); - uint64_t adjust_time = adjust_boot_time(); - _lock_release(&s_time_lock); - return adjust_time; -} - -// Applying the accumulated correction to base_time and stopping the smooth time adjustment. -static void adjtime_corr_stop(void) -{ - _lock_acquire(&s_time_lock); - if (s_adjtime_start_us != 0) { - adjust_boot_time(); - s_adjtime_start_us = 0; + for (const esp_libc_clock_desc_t *it = _esp_libc_clock_array_start; + it < _esp_libc_clock_array_end; ++it) { + if (it->clk_id == clk_id) { + return it; + } } - _lock_release(&s_time_lock); + return NULL; } #else - // no time functions are actually implemented -- allow users to override them #define WEAK_UNLESS_TIMEFUNC_IMPL __attribute__((weak)) +#endif // IMPL_NEWLIB_TIME_FUNCS + +int clock_adjtime(clockid_t clk_id, struct timex *buf) +{ +#if IMPL_NEWLIB_TIME_FUNCS + if (buf == NULL) { + errno = EINVAL; + return -1; + } + + const esp_libc_clock_desc_t *entry = esp_clock_find_entry(clk_id); + if (entry == NULL) { + errno = EINVAL; + return -1; + } + if (entry->ops.adjtime != NULL) { + return entry->ops.adjtime(buf, entry->ctx); + } + errno = EOPNOTSUPP; + return -1; +#else + errno = ENOSYS; + return -1; #endif +} WEAK_UNLESS_TIMEFUNC_IMPL int adjtime(const struct timeval *delta, struct timeval *outdelta) { -#if IMPL_NEWLIB_TIME_FUNCS - if (outdelta != NULL) { - _lock_acquire(&s_time_lock); - adjust_boot_time(); - if (s_adjtime_start_us != 0) { - outdelta->tv_sec = s_adjtime_total_correction_us / 1000000L; - outdelta->tv_usec = s_adjtime_total_correction_us % 1000000L; - } else { - outdelta->tv_sec = 0; - outdelta->tv_usec = 0; - } - _lock_release(&s_time_lock); - } + struct timex tx = {0}; + if (delta != NULL) { - int64_t sec = delta->tv_sec; - int64_t usec = delta->tv_usec; - if (llabs(sec) > ((INT_MAX / 1000000L) - 1L)) { - errno = EINVAL; - return -1; - } - /* - * If adjusting the system clock by adjtime () is already done during the second call adjtime (), - * and the delta of the second call is not NULL, the earlier tuning is stopped, - * but the already completed part of the adjustment is not canceled. - */ - _lock_acquire(&s_time_lock); - // If correction is already in progress (s_adjtime_start_time_us != 0), then apply accumulated corrections. - adjust_boot_time(); - s_adjtime_start_us = esp_time_impl_get_time_since_boot(); - s_adjtime_total_correction_us = sec * 1000000L + usec; - _lock_release(&s_time_lock); + tx.modes = ADJ_OFFSET_SINGLESHOT; + tx.offset = delta->tv_sec * 1000000L + delta->tv_usec; + } else { + tx.modes = ADJ_OFFSET_SS_READ; + } + + int ret = realtime_adjtime(&tx, NULL); + + if (ret == 0 && outdelta != NULL) { + outdelta->tv_sec = tx.offset / 1000000L; + outdelta->tv_usec = tx.offset % 1000000L; + } + + return ret; +} + +WEAK_UNLESS_TIMEFUNC_IMPL int settimeofday(const struct timeval *tv, const struct timezone *tz) +{ + (void) tz; +#if IMPL_NEWLIB_TIME_FUNCS + if (tv) { + struct timespec ts; + ts.tv_sec = tv->tv_sec; + ts.tv_nsec = tv->tv_usec * 1000L; + return realtime_settime(&ts, NULL); } return 0; #else @@ -170,9 +240,10 @@ WEAK_UNLESS_TIMEFUNC_IMPL int _gettimeofday_r(struct _reent *r, struct timeval * #if IMPL_NEWLIB_TIME_FUNCS if (tv) { - uint64_t microseconds = get_adjusted_boot_time() + esp_time_impl_get_time_since_boot(); - tv->tv_sec = microseconds / 1000000; - tv->tv_usec = microseconds % 1000000; + struct timespec ts; + realtime_gettime(&ts, NULL); + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000L; } return 0; #else @@ -181,17 +252,62 @@ WEAK_UNLESS_TIMEFUNC_IMPL int _gettimeofday_r(struct _reent *r, struct timeval * #endif } -WEAK_UNLESS_TIMEFUNC_IMPL int settimeofday(const struct timeval *tv, const struct timezone *tz) +WEAK_UNLESS_TIMEFUNC_IMPL int clock_settime(clockid_t clock_id, const struct timespec *tp) { - (void) tz; #if IMPL_NEWLIB_TIME_FUNCS - if (tv) { - adjtime_corr_stop(); - uint64_t now = ((uint64_t) tv->tv_sec) * 1000000LL + tv->tv_usec; - uint64_t since_boot = esp_time_impl_get_time_since_boot(); - esp_time_impl_set_boot_time(now - since_boot); + if (tp == NULL) { + errno = EINVAL; + return -1; } - return 0; + const esp_libc_clock_desc_t *entry = esp_clock_find_entry(clock_id); + if (entry == NULL) { + errno = EINVAL; + return -1; + } + if (entry->ops.settime != NULL) { + return entry->ops.settime(tp, entry->ctx); + } + errno = EOPNOTSUPP; + return -1; +#else + errno = ENOSYS; + return -1; +#endif +} + +WEAK_UNLESS_TIMEFUNC_IMPL int clock_gettime(clockid_t clock_id, struct timespec *tp) +{ +#if IMPL_NEWLIB_TIME_FUNCS + if (tp == NULL) { + errno = EINVAL; + return -1; + } + + const esp_libc_clock_desc_t *entry = esp_clock_find_entry(clock_id); + if (entry == NULL) { + errno = EINVAL; + return -1; + } + return entry->ops.gettime(tp, entry->ctx); +#else + errno = ENOSYS; + return -1; +#endif +} + +WEAK_UNLESS_TIMEFUNC_IMPL int clock_getres(clockid_t clock_id, struct timespec *res) +{ +#if IMPL_NEWLIB_TIME_FUNCS + if (res == NULL) { + errno = EINVAL; + return -1; + } + const esp_libc_clock_desc_t *entry = esp_clock_find_entry(clock_id); + if (entry == NULL) { + errno = EINVAL; + return -1; + } + return entry->ops.getres(res, entry->ctx); #else errno = ENOSYS; return -1; @@ -237,83 +353,12 @@ unsigned int sleep(unsigned int seconds) return 0; } -WEAK_UNLESS_TIMEFUNC_IMPL int clock_settime(clockid_t clock_id, const struct timespec *tp) -{ -#if IMPL_NEWLIB_TIME_FUNCS - if (tp == NULL) { - errno = EINVAL; - return -1; - } - struct timeval tv; - switch (clock_id) { - case CLOCK_REALTIME: - tv.tv_sec = tp->tv_sec; - tv.tv_usec = tp->tv_nsec / 1000L; - settimeofday(&tv, NULL); - break; - default: - errno = EINVAL; - return -1; - } - return 0; -#else - errno = ENOSYS; - return -1; -#endif -} - -WEAK_UNLESS_TIMEFUNC_IMPL int clock_gettime(clockid_t clock_id, struct timespec *tp) -{ -#if IMPL_NEWLIB_TIME_FUNCS - if (tp == NULL) { - errno = EINVAL; - return -1; - } - struct timeval tv; - uint64_t monotonic_time_us = 0; - switch (clock_id) { - case CLOCK_REALTIME: - _gettimeofday_r(NULL, &tv, NULL); - tp->tv_sec = tv.tv_sec; - tp->tv_nsec = tv.tv_usec * 1000L; - break; - case CLOCK_MONOTONIC: - monotonic_time_us = esp_time_impl_get_time(); - tp->tv_sec = monotonic_time_us / 1000000LL; - tp->tv_nsec = (monotonic_time_us % 1000000LL) * 1000L; - break; - default: - errno = EINVAL; - return -1; - } - return 0; -#else - errno = ENOSYS; - return -1; -#endif -} - -WEAK_UNLESS_TIMEFUNC_IMPL int clock_getres(clockid_t clock_id, struct timespec *res) -{ -#if IMPL_NEWLIB_TIME_FUNCS - if (res == NULL) { - errno = EINVAL; - return -1; - } - - res->tv_sec = 0; - res->tv_nsec = esp_system_get_time_resolution(); - - return 0; -#else - errno = ENOSYS; - return -1; -#endif -} - /* TODO IDF-11226 */ void esp_newlib_time_init(void) __attribute__((alias("esp_libc_time_init"))); void esp_libc_time_init(void) { +#if IMPL_NEWLIB_TIME_FUNCS + esp_clock_registry_check_duplicates(); +#endif esp_set_time_from_rtc(); } diff --git a/components/esp_libc/src/timekeeping.c b/components/esp_libc/src/timekeeping.c new file mode 100644 index 0000000000..6fd81b3725 --- /dev/null +++ b/components/esp_libc/src/timekeeping.c @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" + +#if !CONFIG_ESP_TIME_FUNCS_USE_NONE +#define IMPL_NEWLIB_TIME_FUNCS 1 +#endif + +#if IMPL_NEWLIB_TIME_FUNCS + +#include +#include +#include +#include + +#include "esp_time_impl.h" +#include "esp_libc_timekeeping.h" + +/* Slew state for realtime adjtime (all in microseconds). */ +static uint64_t s_adjtime_start_us; +static int64_t s_adjtime_total_correction_us; +static _lock_t s_adjtime_lock; + +#define ADJTIME_CORRECTION_FACTOR 6 + +static uint64_t esp_libc_timekeeping_adjust_boot_time(void) +{ + uint64_t boot_time = esp_time_impl_get_boot_time(); + if ((boot_time == 0) || (esp_time_impl_get_time_since_boot() < s_adjtime_start_us)) { + s_adjtime_start_us = 0; + } + if (s_adjtime_start_us > 0) { + uint64_t since_boot = esp_time_impl_get_time_since_boot(); + // If to call this function once per second, then (since_boot - s_adjtime_start_us) will be 1_000_000 (1 second), + // and the correction will be equal to (1_000_000us >> 6) = 15_625 us. + // The minimum possible correction step can be (64us >> 6) = 1us. + // Example: if the time error is 1 second, then it will be compensate for 1 sec / 0,015625 = 64 seconds. + int64_t correction = (since_boot >> ADJTIME_CORRECTION_FACTOR) - (s_adjtime_start_us >> ADJTIME_CORRECTION_FACTOR); + if (correction > 0) { + s_adjtime_start_us = since_boot; + if (s_adjtime_total_correction_us < 0) { + if ((s_adjtime_total_correction_us + correction) >= 0) { + boot_time = boot_time + s_adjtime_total_correction_us; + s_adjtime_start_us = 0; + } else { + s_adjtime_total_correction_us += correction; + boot_time -= correction; + } + } else { + if ((s_adjtime_total_correction_us - correction) <= 0) { + boot_time = boot_time + s_adjtime_total_correction_us; + s_adjtime_start_us = 0; + } else { + s_adjtime_total_correction_us -= correction; + boot_time += correction; + } + } + esp_time_impl_set_boot_time(boot_time); + } + } + return boot_time; +} + +static uint64_t esp_libc_timekeeping_get_adjusted_boot_time(void) +{ + _lock_acquire(&s_adjtime_lock); + uint64_t adjust_time = esp_libc_timekeeping_adjust_boot_time(); + _lock_release(&s_adjtime_lock); + return adjust_time; +} + +static void esp_libc_timekeeping_adjtime_corr_stop(void) +{ + _lock_acquire(&s_adjtime_lock); + if (s_adjtime_start_us != 0) { + esp_libc_timekeeping_adjust_boot_time(); + s_adjtime_start_us = 0; + } + _lock_release(&s_adjtime_lock); +} + +uint64_t esp_libc_timekeeping_get_realtime_us(void) +{ + return esp_libc_timekeeping_get_adjusted_boot_time() + esp_time_impl_get_time_since_boot(); +} + +void esp_libc_timekeeping_set_realtime_us(uint64_t us) +{ + esp_libc_timekeeping_adjtime_corr_stop(); + uint64_t since_boot = esp_time_impl_get_time_since_boot(); + esp_time_impl_set_boot_time(us - since_boot); +} + +int64_t esp_libc_timekeeping_adjtime_get_remaining_us(void) +{ + int64_t remaining_us; + _lock_acquire(&s_adjtime_lock); + esp_libc_timekeeping_adjust_boot_time(); + remaining_us = (s_adjtime_start_us != 0) ? s_adjtime_total_correction_us : 0; + _lock_release(&s_adjtime_lock); + return remaining_us; +} + +int esp_libc_timekeeping_adjtime_apply(int64_t offset_us, int64_t *prev_remaining_us) +{ + if (prev_remaining_us == NULL) { + errno = EINVAL; + return -1; + } + if (llabs(offset_us) > ((INT_MAX / 1000000L) - 1L) * 1000000L) { + errno = EINVAL; + return -1; + } + /* + * If a new correction is started while one is already in progress, + * the earlier tuning is stopped but the already completed part is + * not canceled. The previous remaining correction is returned. + */ + _lock_acquire(&s_adjtime_lock); + *prev_remaining_us = (s_adjtime_start_us != 0) ? s_adjtime_total_correction_us : 0; + esp_libc_timekeeping_adjust_boot_time(); + s_adjtime_start_us = esp_time_impl_get_time_since_boot(); + s_adjtime_total_correction_us = offset_us; + _lock_release(&s_adjtime_lock); + return 0; +} + +#endif /* IMPL_NEWLIB_TIME_FUNCS */ diff --git a/components/esp_libc/test_apps/newlib/main/test_time.c b/components/esp_libc/test_apps/newlib/main/test_time.c index 3f44d45e32..5b885dffb7 100644 --- a/components/esp_libc/test_apps/newlib/main/test_time.c +++ b/components/esp_libc/test_apps/newlib/main/test_time.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,8 @@ #include "unity.h" #include #include +#include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" @@ -29,11 +31,23 @@ #include "esp_private/esp_clk.h" #include "esp_rtc_time.h" +#include "esp_libc_clock.h" #if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE #include "hal/cache_ll.h" #endif +typedef enum { + TEST_ADJTIME_MODE_LEGACY = 0, + TEST_ADJTIME_MODE_CLOCK_ADJTIME, +} test_adjtime_mode_t; + +typedef enum { + TEST_CLOCK_ADJTIME_UNITS_NOT_APPLICABLE = 0, + TEST_CLOCK_ADJTIME_UNITS_NS = 1, + TEST_CLOCK_ADJTIME_UNITS_US = 2, +} test_clock_adjtime_units_t; + #if (CONFIG_FREERTOS_NUMBER_OF_CORES == 2) && CONFIG_IDF_TARGET_ARCH_XTENSA // https://github.com/espressif/arduino-esp32/issues/120 /* Test for hardware bug, not needed for newer chips */ @@ -99,13 +113,63 @@ TEST_CASE("test usleep basic functionality", "[newlib]") TEST_ASSERT_GREATER_OR_EQUAL(long_sleep_us, end - start); } -TEST_CASE("test adjtime function", "[newlib]") +int realtime_adjtime_wrapper(const struct timeval *delta, struct timeval *outdelta, test_adjtime_mode_t mode, test_clock_adjtime_units_t units) { - struct timeval tv_time; - struct timeval tv_delta; - struct timeval tv_outdelta; + int ret = -1; + if (mode == TEST_ADJTIME_MODE_LEGACY) { + return adjtime(delta, outdelta); + } else if (mode == TEST_ADJTIME_MODE_CLOCK_ADJTIME) { + struct timex tx = {0}; + if (delta) { + if (units == TEST_CLOCK_ADJTIME_UNITS_NS) { + tx.modes = ADJ_OFFSET | ADJ_NANO; + int64_t offset_ns = delta->tv_sec * 1000000000L + delta->tv_usec * 1000L; + // timex buffer `offset` member is a long type, so it's limited to INT_MAX and INT_MIN + if (offset_ns > INT_MAX || offset_ns < INT_MIN) { + TEST_FAIL_MESSAGE("Invalid clock_adjtime offset it's out of range, this is test problem!"); + } + tx.offset = (long)offset_ns; + } else if (units == TEST_CLOCK_ADJTIME_UNITS_US) { + tx.modes = ADJ_OFFSET_SINGLESHOT; + tx.offset = delta->tv_sec * 1000000L + delta->tv_usec; + } else { + TEST_FAIL_MESSAGE("Invalid clock_adjtime units"); + } + } else { // Read-only mode + if (units == TEST_CLOCK_ADJTIME_UNITS_NS) { + tx.modes = ADJ_NANO; + } else if (units == TEST_CLOCK_ADJTIME_UNITS_US) { + tx.modes = 0; + } else { + TEST_FAIL_MESSAGE("Invalid clock_adjtime units"); + } + } + ret = clock_adjtime(CLOCK_REALTIME, &tx); + if (ret == 0 && outdelta != NULL) { + if (units == TEST_CLOCK_ADJTIME_UNITS_NS) { + outdelta->tv_sec = tx.offset / 1000000000L; + outdelta->tv_usec = (tx.offset % 1000000000L) / 1000L; + } else { + outdelta->tv_sec = tx.offset / 1000000L; + outdelta->tv_usec = tx.offset % 1000000L; + } + } + } + return ret; +} - TEST_ASSERT_EQUAL(adjtime(NULL, NULL), 0); +void test_adjtime_function(test_adjtime_mode_t mode, test_clock_adjtime_units_t units) +{ + struct timeval tv_time = {0}; + struct timeval tv_delta = {0}; + struct timeval tv_outdelta = {0}; + const char *units_str = units == TEST_CLOCK_ADJTIME_UNITS_NS ? "NS" + : units == TEST_CLOCK_ADJTIME_UNITS_US ? "US" + : "N/A"; + printf("test_adjtime_function: mode = %s, units = %s\n", + mode == TEST_ADJTIME_MODE_LEGACY ? "LEGACY" : "CLOCK_ADJTIME", + units_str); + TEST_ASSERT_EQUAL_INT(realtime_adjtime_wrapper(NULL, NULL, mode, units), 0); tv_time.tv_sec = 5000; tv_time.tv_usec = 5000; @@ -113,69 +177,74 @@ TEST_CASE("test adjtime function", "[newlib]") tv_outdelta.tv_sec = 5; tv_outdelta.tv_usec = 5; - TEST_ASSERT_EQUAL(adjtime(NULL, &tv_outdelta), 0); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(NULL, &tv_outdelta, mode, units), 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_usec, 0); - tv_delta.tv_sec = INT_MAX / 1000000L; - TEST_ASSERT_EQUAL(adjtime(&tv_delta, &tv_outdelta), -1); + // when use nanoseconds, the offset is out of range + if (units == TEST_CLOCK_ADJTIME_UNITS_US) { + tv_delta.tv_sec = INT_MAX / 1000000L; + tv_delta.tv_usec = 0; + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, &tv_outdelta, mode, units), -1); - tv_delta.tv_sec = INT_MIN / 1000000L; - TEST_ASSERT_EQUAL(adjtime(&tv_delta, &tv_outdelta), -1); + tv_delta.tv_sec = INT_MIN / 1000000L; + tv_delta.tv_usec = 0; + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, &tv_outdelta, mode, units), -1); + } tv_delta.tv_sec = 0; tv_delta.tv_usec = -900000; - TEST_ASSERT_EQUAL(adjtime(&tv_delta, &tv_outdelta), 0); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, &tv_outdelta, mode, units), 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_usec, 0); - TEST_ASSERT_EQUAL(adjtime(NULL, &tv_outdelta), 0); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(NULL, &tv_outdelta, mode, units), 0); TEST_ASSERT_LESS_THAN(-800000, tv_outdelta.tv_usec); - tv_delta.tv_sec = -4; - tv_delta.tv_usec = -900000; - TEST_ASSERT_EQUAL(adjtime(&tv_delta, NULL), 0); - TEST_ASSERT_EQUAL(adjtime(NULL, &tv_outdelta), 0); - TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, -4); - TEST_ASSERT_LESS_THAN(-800000, tv_outdelta.tv_usec); + tv_delta.tv_sec = -2; + tv_delta.tv_usec = -90000; + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, NULL, mode, units), 0); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(NULL, &tv_outdelta, mode, units), 0); + TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, -2); + TEST_ASSERT_LESS_THAN(-80000, tv_outdelta.tv_usec); // after settimeofday() adjtime() is stopped - tv_delta.tv_sec = 15; - tv_delta.tv_usec = 900000; - TEST_ASSERT_EQUAL(adjtime(&tv_delta, &tv_outdelta), 0); - TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, -4); - TEST_ASSERT_LESS_THAN(-800000, tv_outdelta.tv_usec); - TEST_ASSERT_EQUAL(adjtime(NULL, &tv_outdelta), 0); - TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 15); - TEST_ASSERT_GREATER_OR_EQUAL(800000, tv_outdelta.tv_usec); + tv_delta.tv_sec = 1; + tv_delta.tv_usec = 90000; + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, &tv_outdelta, mode, units), 0); + TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, -2); + TEST_ASSERT_LESS_THAN(-80000, tv_outdelta.tv_usec); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(NULL, &tv_outdelta, mode, units), 0); + TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 1); + TEST_ASSERT_GREATER_OR_EQUAL(80000, tv_outdelta.tv_usec); TEST_ASSERT_EQUAL(gettimeofday(&tv_time, NULL), 0); TEST_ASSERT_EQUAL(settimeofday(&tv_time, NULL), 0); - TEST_ASSERT_EQUAL(adjtime(NULL, &tv_outdelta), 0); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(NULL, &tv_outdelta, mode, units), 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_usec, 0); // after gettimeofday() adjtime() is not stopped - tv_delta.tv_sec = 15; - tv_delta.tv_usec = 900000; - TEST_ASSERT_EQUAL(adjtime(&tv_delta, &tv_outdelta), 0); + tv_delta.tv_sec = 1; + tv_delta.tv_usec = 90000; + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, &tv_outdelta, mode, units), 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_usec, 0); - TEST_ASSERT_EQUAL(adjtime(NULL, &tv_outdelta), 0); - TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 15); - TEST_ASSERT_GREATER_OR_EQUAL(800000, tv_outdelta.tv_usec); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(NULL, &tv_outdelta, mode, units), 0); + TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 1); + TEST_ASSERT_GREATER_OR_EQUAL(80000, tv_outdelta.tv_usec); TEST_ASSERT_EQUAL(gettimeofday(&tv_time, NULL), 0); - TEST_ASSERT_EQUAL(adjtime(NULL, &tv_outdelta), 0); - TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 15); - TEST_ASSERT_GREATER_OR_EQUAL(800000, tv_outdelta.tv_usec); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(NULL, &tv_outdelta, mode, units), 0); + TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 1); + TEST_ASSERT_GREATER_OR_EQUAL(80000, tv_outdelta.tv_usec); tv_delta.tv_sec = 1; tv_delta.tv_usec = 0; - TEST_ASSERT_EQUAL(adjtime(&tv_delta, NULL), 0); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, NULL, mode, units), 0); vTaskDelay(1000 / portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(adjtime(NULL, &tv_outdelta), 0); + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(NULL, &tv_outdelta, mode, units), 0); TEST_ASSERT_EQUAL(tv_outdelta.tv_sec, 0); // the correction will be equal to (1_000_000us >> 6) = 15_625 us. @@ -183,6 +252,17 @@ TEST_CASE("test adjtime function", "[newlib]") TEST_ASSERT_TRUE(1000000L - tv_outdelta.tv_usec <= 15650); } +TEST_CASE("test adjtime function", "[newlib]") +{ + test_adjtime_function(TEST_ADJTIME_MODE_LEGACY, TEST_CLOCK_ADJTIME_UNITS_NOT_APPLICABLE); +} + +TEST_CASE("test clock_adjtime function", "[newlib]") +{ + test_adjtime_function(TEST_ADJTIME_MODE_CLOCK_ADJTIME, TEST_CLOCK_ADJTIME_UNITS_US); + test_adjtime_function(TEST_ADJTIME_MODE_CLOCK_ADJTIME, TEST_CLOCK_ADJTIME_UNITS_NS); +} + static volatile bool exit_flag; static void adjtimeTask2(void *pvParameters) @@ -390,6 +470,7 @@ void test_posix_timers_clock(void) TEST_ASSERT(clock_settime(CLOCK_REALTIME, NULL) == -1); TEST_ASSERT(clock_gettime(CLOCK_REALTIME, NULL) == -1); TEST_ASSERT(clock_getres(CLOCK_REALTIME, NULL) == -1); + TEST_ASSERT(clock_adjtime(CLOCK_REALTIME, NULL) == -1); TEST_ASSERT(clock_settime(CLOCK_MONOTONIC, NULL) == -1); TEST_ASSERT(clock_gettime(CLOCK_MONOTONIC, NULL) == -1); @@ -403,10 +484,12 @@ void test_posix_timers_clock(void) TEST_ASSERT(gettimeofday(&now, NULL) == 0); struct timespec ts = {0}; + struct timex tx = {0}; TEST_ASSERT(clock_settime(0xFFFFFFFF, &ts) == -1); TEST_ASSERT(clock_gettime(0xFFFFFFFF, &ts) == -1); - TEST_ASSERT(clock_getres(0xFFFFFFFF, &ts) == 0); + TEST_ASSERT(clock_getres(0xFFFFFFFF, &ts) == -1); + TEST_ASSERT(clock_adjtime(0xFFFFFFFF, &tx) == -1); TEST_ASSERT(clock_gettime(CLOCK_REALTIME, &ts) == 0); TEST_ASSERT(now.tv_sec == ts.tv_sec); @@ -465,6 +548,245 @@ TEST_CASE("test posix_timers clock_... functions", "[newlib]") test_posix_timers_clock(); } +#define TEST_USE_CPU_CYCLES 1 +#if TEST_USE_CPU_CYCLES && CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE +#include "esp_cpu.h" +typedef esp_cpu_cycle_count_t benchmark_tick_t; /* unit: CPU cycles */ +#define get_start() esp_cpu_get_cycle_count() +#define get_end() esp_cpu_get_cycle_count() +#define benchmark_print_units(type, x) printf("%s cpu cycles: %lu\n", type, (unsigned long)(x)) +#else +typedef uint64_t benchmark_tick_t; /* unit: microseconds */ +#define get_start() esp_timer_get_time() +#define get_end() esp_timer_get_time() +#define benchmark_print_units(type, x) printf("%s us: %lu\n", type, (unsigned long)(x)) +#endif + +#define N 4096 +const uint32_t ro_tbl[N] = { 2 }; +uint32_t scan_tbl(const volatile uint32_t *p, size_t n) +{ + uint32_t sum = 0; + for (size_t i = 0; i < n; i++) { + sum += p[i]; + } + return sum; +} + +void test_posix_timers_clock_performance(clockid_t clock_id) +{ + const int MEASUREMENTS = 5000; + const int PHASE_COUNT = 2; /* must be at least 2 (half without table scan, half with) */ + struct timespec ts = {0}; + + benchmark_tick_t start; + benchmark_tick_t end; + benchmark_tick_t delta; + benchmark_tick_t delta_sum = 0; + for (int i = 0; i < PHASE_COUNT; i++) { + volatile uint32_t table_sum = 0; + if (i == PHASE_COUNT / 2) { + printf("With table scan...\n"); + } + for (int j = 0; j < MEASUREMENTS; j++) { + if (i >= PHASE_COUNT / 2) { + table_sum += scan_tbl(ro_tbl, N); + } + start = get_start(); + clock_settime(clock_id, &ts); + end = get_end(); + delta = end - start; + delta_sum += delta; + } + benchmark_print_units("average", delta_sum / MEASUREMENTS); + delta_sum = 0; + } +} + +TEST_CASE("test posix_timers clock_... performance", "[newlib]") +{ + printf("Testing CLOCK_REALTIME performance...\n"); + test_posix_timers_clock_performance(CLOCK_REALTIME); + printf("Testing CLOCK_MONOTONIC performance...\n"); + test_posix_timers_clock_performance(CLOCK_MONOTONIC); +} + +/* + * Stub clocks for testing link-time ESP_LIBC_CLOCK_REGISTER and + * clock_gettime / clock_settime / clock_adjtime. + */ +#define TEST_CLOCK_STUB_ID ((clockid_t)18) +#define TEST_CLOCK_STUB_CTX_ID ((clockid_t)17) + +static struct timespec s_stub_clock_time; +static int64_t s_stub_clock_adj_remaining_ns; + +static int stub_clock_gettime(struct timespec *tp, void *ctx) +{ + (void)ctx; + if (tp == NULL) { + errno = EINVAL; + return -1; + } + *tp = s_stub_clock_time; + return 0; +} + +static int stub_clock_settime(const struct timespec *tp, void *ctx) +{ + (void)ctx; + if (tp == NULL) { + errno = EINVAL; + return -1; + } + s_stub_clock_time = *tp; + return 0; +} + +static int stub_clock_adjtime(struct timex *buf, void *ctx) +{ + (void)ctx; + if (buf == NULL) { + errno = EINVAL; + return -1; + } + if (buf->modes == ADJ_OFFSET_SS_READ) { + buf->offset = (long)(s_stub_clock_adj_remaining_ns / 1000); + return 0; + } + if (buf->modes & ADJ_OFFSET) { + if (buf->modes == ADJ_OFFSET_SINGLESHOT) { + s_stub_clock_adj_remaining_ns = (int64_t)buf->offset * 1000; + } else { + s_stub_clock_adj_remaining_ns = (int64_t)buf->offset; + } + } + if (buf->modes & ADJ_FREQUENCY) { + (void)buf->freq; /* stub: ignore frequency */ + } + return 0; +} + +static int stub_clock_getres(struct timespec *res, void *ctx) +{ + (void)ctx; + if (res == NULL) { + errno = EINVAL; + return -1; + } + res->tv_sec = 0; + res->tv_nsec = 1000; + return 0; +} + +static int stub_clock_gettime_context(struct timespec *tp, void *ctx) +{ + uint32_t *ctx_ptr = (uint32_t *)ctx; + if (ctx_ptr == NULL) { + errno = EINVAL; + return -1; + } + *ctx_ptr = 0xFEEDBABE; + return stub_clock_gettime(tp, NULL); +} + +static void stub_clock_reset(void) +{ + s_stub_clock_time.tv_sec = 0; + s_stub_clock_time.tv_nsec = 0; + s_stub_clock_adj_remaining_ns = 0; +} + +static const esp_libc_clock_ops_t s_test_stub_ops = { + .gettime = stub_clock_gettime, + .settime = stub_clock_settime, + .adjtime = stub_clock_adjtime, + .getres = stub_clock_getres +}; + +ESP_LIBC_CLOCK_REGISTER(test_stub, TEST_CLOCK_STUB_ID, s_test_stub_ops, NULL); + +static uint32_t s_stub_ctx_test_word; + +static const esp_libc_clock_ops_t s_test_stub_ctx_ops = { + .gettime = stub_clock_gettime_context, + .settime = NULL, + .adjtime = NULL, + .getres = stub_clock_getres +}; + +ESP_LIBC_CLOCK_REGISTER(test_stub_ctx, TEST_CLOCK_STUB_CTX_ID, s_test_stub_ctx_ops, &s_stub_ctx_test_word); + +TEST_CASE("link-time custom clock functionality", "[newlib]") +{ + stub_clock_reset(); + + struct timespec ts; + /* gettime after init: 0,0 */ + TEST_ASSERT_EQUAL(0, clock_gettime(TEST_CLOCK_STUB_ID, &ts)); + TEST_ASSERT_EQUAL(0, ts.tv_sec); + TEST_ASSERT_EQUAL(0, ts.tv_nsec); + + /* settime */ + ts.tv_sec = 1700000000; + ts.tv_nsec = 123456789; + TEST_ASSERT_EQUAL(0, clock_settime(TEST_CLOCK_STUB_ID, &ts)); + + /* gettime matches */ + TEST_ASSERT_EQUAL(0, clock_gettime(TEST_CLOCK_STUB_ID, &ts)); + TEST_ASSERT_EQUAL(1700000000, ts.tv_sec); + TEST_ASSERT_EQUAL(123456789, ts.tv_nsec); + + /* getres */ + TEST_ASSERT_EQUAL(0, clock_getres(TEST_CLOCK_STUB_ID, &ts)); + TEST_ASSERT_EQUAL(0, ts.tv_sec); + TEST_ASSERT_EQUAL(1000, ts.tv_nsec); + + /* clock_adjtime: ADJ_OFFSET_SS_READ */ + struct timex tx = { .modes = ADJ_OFFSET_SS_READ }; + TEST_ASSERT_EQUAL(0, clock_adjtime(TEST_CLOCK_STUB_ID, &tx)); + TEST_ASSERT_EQUAL(0, tx.offset); + + /* clock_adjtime: ADJ_OFFSET_SINGLESHOT (offset in microseconds in buf->offset) */ + tx.modes = ADJ_OFFSET_SINGLESHOT; + tx.offset = 50000; /* 50 ms */ + TEST_ASSERT_EQUAL(0, clock_adjtime(TEST_CLOCK_STUB_ID, &tx)); + tx.modes = ADJ_OFFSET_SS_READ; + tx.offset = 0; + TEST_ASSERT_EQUAL(0, clock_adjtime(TEST_CLOCK_STUB_ID, &tx)); + TEST_ASSERT_EQUAL(50000, tx.offset); + + /* Test link-time stub clock passes ctx to gettime callback */ + stub_clock_reset(); + s_stub_ctx_test_word = 0xDEADBEEF; + errno = 0; + TEST_ASSERT_EQUAL(0, clock_gettime(TEST_CLOCK_STUB_CTX_ID, &ts)); + TEST_ASSERT_EQUAL(0, errno); + TEST_ASSERT_EQUAL(0xFEEDBABE, s_stub_ctx_test_word); + + /* TEST_CLOCK_STUB_CTX_ID uses s_test_stub_ctx_ops: .settime = NULL, .adjtime = NULL */ + stub_clock_reset(); + ts.tv_sec = 1; + ts.tv_nsec = 2; + + errno = 0; + TEST_ASSERT_EQUAL(-1, clock_settime(TEST_CLOCK_STUB_CTX_ID, &ts)); + TEST_ASSERT_EQUAL(EOPNOTSUPP, errno); + + tx.modes = ADJ_OFFSET_SS_READ; + errno = 0; + TEST_ASSERT_EQUAL(-1, clock_adjtime(TEST_CLOCK_STUB_CTX_ID, &tx)); + TEST_ASSERT_EQUAL(EOPNOTSUPP, errno); + + /* gettime / getres still work */ + errno = 0; + TEST_ASSERT_EQUAL(0, clock_gettime(TEST_CLOCK_STUB_CTX_ID, &ts)); + TEST_ASSERT_EQUAL(0, errno); + errno = 0; + TEST_ASSERT_EQUAL(0, clock_getres(TEST_CLOCK_STUB_CTX_ID, &ts)); + TEST_ASSERT_EQUAL(0, errno); +} + #ifndef _USE_LONG_TIME_T static struct timeval get_time(const char *desc, char *buffer) diff --git a/examples/ethernet/ptp/components/esp_eth_time/CMakeLists.txt b/examples/ethernet/ptp/components/esp_eth_time/CMakeLists.txt deleted file mode 100644 index b80be4eafb..0000000000 --- a/examples/ethernet/ptp/components/esp_eth_time/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -idf_component_register(SRCS "esp_eth_time.c" - PRIV_REQUIRES esp_eth - INCLUDE_DIRS ".") diff --git a/examples/ethernet/ptp/components/esp_eth_time/README.md b/examples/ethernet/ptp/components/esp_eth_time/README.md deleted file mode 100644 index b369992b16..0000000000 --- a/examples/ethernet/ptp/components/esp_eth_time/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# ESP Ethernet Time Control Component Example - -This example component provides a wrapper around management of the internal Ethernet MAC Time (Time Stamping) system which is normally accessed via `esp_eth_ioctl` commands. The component is offering a more intuitive API mimicking POSIX `clock_settime`, `clock_gettime` group of time functions and so making it easier to integrate with existing systems. diff --git a/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.c b/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.c deleted file mode 100644 index 0b0da13efb..0000000000 --- a/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include "esp_eth_time.h" -#include "esp_eth_mac_esp.h" - -static esp_eth_mac_t *s_mac; - -static int esp_eth_clock_esp_err_to_errno(esp_err_t esp_err) -{ - switch (esp_err) - { - case ESP_ERR_INVALID_ARG: - return EINVAL; - case ESP_ERR_INVALID_STATE: - return EBUSY; - case ESP_ERR_TIMEOUT: - return ETIME; - } - // default "no err" when error cannot be isolated - return 0; -} - -int esp_eth_clock_adjtime(clockid_t clk_id, esp_eth_clock_adj_param_t *adj) -{ - switch (clk_id) { - case CLOCK_PTP_SYSTEM: - if (adj->mode == ETH_CLK_ADJ_FREQ_SCALE) { - esp_err_t ret = esp_eth_mac_adj_ptp_freq(s_mac, adj->freq_scale); - if (ret != ESP_OK) { - errno = esp_eth_clock_esp_err_to_errno(ret); - return -1; - } - } else { - errno = EINVAL; - return -1; - } - break; - default: - errno = EINVAL; - return -1; - } - return 0; -} - -int esp_eth_clock_settime(clockid_t clock_id, const struct timespec *tp) -{ - switch (clock_id) { - case CLOCK_PTP_SYSTEM: { - if (s_mac) { - eth_mac_time_t ptp_time = { - .seconds = tp->tv_sec, - .nanoseconds = tp->tv_nsec - }; - esp_err_t ret = esp_eth_mac_set_ptp_time(s_mac, &ptp_time); - if (ret != ESP_OK) { - errno = esp_eth_clock_esp_err_to_errno(ret); - return -1; - } - } else { - errno = ENODEV; - return -1; - } - break; - } - default: - errno = EINVAL; - return -1; - } - return 0; -} - -int esp_eth_clock_gettime(clockid_t clock_id, struct timespec *tp) -{ - switch (clock_id) { - case CLOCK_PTP_SYSTEM: { - if (s_mac) { - eth_mac_time_t ptp_time; - esp_err_t ret = esp_eth_mac_get_ptp_time(s_mac, &ptp_time); - if (ret != ESP_OK) { - errno = esp_eth_clock_esp_err_to_errno(ret); - return -1; - } - tp->tv_sec = ptp_time.seconds; - tp->tv_nsec = ptp_time.nanoseconds; - } else { - errno = ENODEV; - return -1; - } - break; - } - default: - errno = EINVAL; - return -1; - } - return 0; -} - -int esp_eth_clock_set_target_time(clockid_t clock_id, struct timespec *tp) -{ - eth_mac_time_t mac_target_time = { - .seconds = tp->tv_sec, - .nanoseconds = tp->tv_nsec - }; - esp_err_t ret = esp_eth_mac_set_target_time(s_mac, &mac_target_time); - if (ret != ESP_OK) { - errno = esp_eth_clock_esp_err_to_errno(ret); - return -1; - } - return 0; -} - -int esp_eth_clock_register_target_cb(clockid_t clock_id, - ts_target_exceed_cb_from_isr_t ts_callback) -{ - esp_err_t ret = esp_eth_mac_set_target_time_cb(s_mac, ts_callback); - if (ret != ESP_OK) { - errno = esp_eth_clock_esp_err_to_errno(ret); - return -1; - } - return 0; -} - -esp_err_t esp_eth_clock_init(clockid_t clock_id, esp_eth_clock_cfg_t *cfg) -{ - if (s_mac != NULL) { - return ESP_ERR_INVALID_STATE; - } - if (cfg == NULL) { - return ESP_ERR_INVALID_ARG; - } - switch (clock_id) { - case CLOCK_PTP_SYSTEM: - // PTP Clock is part of Ethernet system - eth_mac_ptp_config_t ptp_cfg = ETH_MAC_ESP_PTP_DEFAULT_CONFIG(); - esp_eth_mac_t *mac; - if (esp_eth_get_mac_instance(cfg->eth_hndl, &mac) != ESP_OK) { - return ESP_FAIL; - } - if (esp_eth_mac_ptp_enable(mac, &ptp_cfg) != ESP_OK) { - return ESP_FAIL; - } - s_mac = mac; - break; - default: - return ESP_FAIL; - } - return ESP_OK; -} diff --git a/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.h b/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.h deleted file mode 100644 index daef942ee7..0000000000 --- a/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include -#include -#include "esp_err.h" -#include "esp_eth_driver.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define CLOCK_PTP_SYSTEM ((clockid_t) 19) - -/** - * @brief Configuration of clock during initialization - * - */ -typedef struct { - esp_eth_handle_t eth_hndl; -} esp_eth_clock_cfg_t; - -/** - * @brief The mode of clock adjustment. - * - */ -typedef enum { - ETH_CLK_ADJ_FREQ_SCALE, -} esp_eth_clock_adj_mode_t; - -/** - * @brief Structure containing parameters for adjusting the Ethernet clock. - * - */ -typedef struct { - /** - * @brief The mode of clock adjustment. - * - */ - esp_eth_clock_adj_mode_t mode; - - /** - * @brief The frequency scale factor when in ETH_CLK_ADJ_FREQ_SCALE mode. - * - * This value represents the ratio of the desired frequency to the actual - * frequency. A value greater than 1 increases the frequency, while a value - * less than 1 decreases the frequency. - */ - double freq_scale; -} esp_eth_clock_adj_param_t; - -/** - * @brief Adjust the system clock frequency - * - * @param clk_id Identifier of the clock to adjust - * @param buf Pointer to the adjustment parameters - * - * @return - * - 0: Success - * - -1: Failure - */ -int esp_eth_clock_adjtime(clockid_t clk_id, esp_eth_clock_adj_param_t *adj); - -/** - * @brief Set the system clock time - * - * @param clk_id Identifier of the clock to set - * @param tp Pointer to the new time - * - * @return - * - 0: Success - * - -1: Failure - */ -int esp_eth_clock_settime(clockid_t clock_id, const struct timespec *tp); - -/** - * @brief Get the current system clock time - * - * @param clk_id Identifier of the clock to query - * @param tp Pointer to the buffer to store the current time - * - * @return - * - 0: Success - * - -1: Failure - */ -int esp_eth_clock_gettime(clockid_t clock_id, struct timespec *tp); - -/** - * @brief Set the target time for the system clock. - * - * @param clk_id Identifier of the clock to set the target time for - * @param tp Pointer to the target time - * - * @return - * - 0: Success - * - -1: Failure - */ -int esp_eth_clock_set_target_time(clockid_t clock_id, struct timespec *tp); - -/** - * @brief Register callback function invoked on Time Stamp target time exceeded interrupt - * - * @param clock_id Identifier of the clock - * @param ts_callback callback function to be registered - * @return - * - 0: Success - * - -1: Failure - */ -int esp_eth_clock_register_target_cb(clockid_t clock_id, - ts_target_exceed_cb_from_isr_t ts_callback); - -/** - * @brief Initialize the Ethernet clock subsystem - * - * @param clk_id Identifier of the clock to initialize - * @param cfg Pointer to the configuration structure - * - * @return - * - ESP_OK: Success - * - ESP_FAIL: Failure - * - ESP_ERR_INVALID_ARG: Invalid argument - * - ESP_ERR_INVALID_STATE: Clock is already initialized - */ -esp_err_t esp_eth_clock_init(clockid_t clock_id, esp_eth_clock_cfg_t *cfg); - -#ifdef __cplusplus -} -#endif diff --git a/examples/ethernet/ptp/components/ptpd/Kconfig.projbuild b/examples/ethernet/ptp/components/ptpd/Kconfig.projbuild index 6fbcfbd685..e47d262127 100644 --- a/examples/ethernet/ptp/components/ptpd/Kconfig.projbuild +++ b/examples/ethernet/ptp/components/ptpd/Kconfig.projbuild @@ -180,6 +180,15 @@ menu "PTP Daemon Configuration" time is reset with settimeofday() instead of changing the rate with adjtime(). + config NETUTILS_PTPD_DRIFT_AVERAGE_S + int "PTP client clock drift rate averaging time (s)" + default 600 + range 10 86400 + help + Clock drift rate is averaged over this time period. Larger value + gives more stable estimate but reacts slower to crystal oscillator speed + changes (such as caused by temperature changes). + # Commented options not used by ESP_PTP #config NETUTILS_PTPD_MULTICAST_TIMEOUT_MS # int "PTP client timeout to rejoin multicast group (ms)" @@ -191,15 +200,6 @@ menu "PTP Daemon Configuration" # depending on hardware, after some error recovery events. # Set to 0 to disable. - #config NETUTILS_PTPD_DRIFT_AVERAGE_S - # int "PTP client clock drift rate averaging time (s)" - # default 600 - # range 10 86400 - # help - # Clock drift rate is averaged over this time pediod. Larger value - # gives more stable estimate but reacts slower to crystal oscillator speed - # changes (such as caused by temperature changes). - config NETUTILS_PTPD_SEND_DELAYREQ bool "PTP client enable delay requests" default n diff --git a/examples/ethernet/ptp/components/ptpd/idf_component.yml b/examples/ethernet/ptp/components/ptpd/idf_component.yml deleted file mode 100644 index 97451ea972..0000000000 --- a/examples/ethernet/ptp/components/ptpd/idf_component.yml +++ /dev/null @@ -1,3 +0,0 @@ -dependencies: - esp_eth_time: - path: ${IDF_PATH}/examples/ethernet/ptp/components/esp_eth_time diff --git a/examples/ethernet/ptp/components/ptpd/port/esp_ptpd.c b/examples/ethernet/ptp/components/ptpd/port/esp_ptpd.c deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/ethernet/ptp/components/ptpd/ptpd.c b/examples/ethernet/ptp/components/ptpd/ptpd.c index 7fd47a5714..c406651791 100644 --- a/examples/ethernet/ptp/components/ptpd/ptpd.c +++ b/examples/ethernet/ptp/components/ptpd/ptpd.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 * - * SPDX-FileContributor: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2024-2026 Espressif Systems (Shanghai) CO LTD */ /**************************************************************************** @@ -79,7 +79,8 @@ #include "esp_err.h" #include "lwip/prot/ethernet.h" // Ethernet headers -#include "esp_eth_time.h" +#include +#include "esp_eth_clock.h" #define ETH_TYPE_PTP 0x88F7 @@ -98,6 +99,9 @@ #define NSEC_PER_MSEC 1000000ll #define NSEC_PER_SEC 1000000000ll +#define CONFIG_CLOCK_ADJTIME_PERIOD_MS (CONFIG_ETH_CLOCK_ADJTIME_PERIOD_MS) +#define CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM (CONFIG_ETH_CLOCK_ADJTIME_SLEWLIMIT_PPB / 1000) + // To able to set either only server or only client #ifndef CONFIG_NETUTILS_PTPD_TIMEOUT_MS #define CONFIG_NETUTILS_PTPD_TIMEOUT_MS 0 @@ -542,7 +546,7 @@ static int ptp_gettime(FAR struct ptp_state_s *state, { UNUSED(state); #ifdef ESP_PTP - return esp_eth_clock_gettime(CLOCK_PTP_SYSTEM, ts); + return clock_gettime(CLOCK_PTP_SYSTEM, ts); #else return clock_gettime(CLOCK_REALTIME, ts); #endif // ESP_PTP @@ -555,25 +559,30 @@ static int ptp_settime(FAR struct ptp_state_s *state, { UNUSED(state); #ifdef ESP_PTP - return esp_eth_clock_settime(CLOCK_PTP_SYSTEM, ts); + return clock_settime(CLOCK_PTP_SYSTEM, ts); #else return clock_settime(CLOCK_REALTIME, ts); #endif // ESP_PTP } -#ifndef ESP_PTP /* Smoothly adjust timestamp. */ static int ptp_adjtime(FAR struct ptp_state_s *state, int64_t delta_ns) { +#ifdef ESP_PTP + struct timex tx = { + .modes = ADJ_OFFSET | ADJ_NANO, + .offset = (long)delta_ns, + }; + return clock_adjtime(CLOCK_PTP_SYSTEM, &tx); +#else struct timeval delta; - delta.tv_sec = delta_ns / NSEC_PER_SEC; delta_ns -= (int64_t)delta.tv_sec * NSEC_PER_SEC; delta.tv_usec = delta_ns / NSEC_PER_USEC; return adjtime(&delta, NULL); +#endif } -#endif // !ESP_PTP #ifndef ESP_PTP /* Get timestamp of latest received packet */ @@ -647,9 +656,12 @@ static int ptp_initialize_state(FAR struct ptp_state_s *state, return ERROR; } esp_eth_clock_cfg_t clk_cfg = { - .eth_hndl = eth_handle, + .clock_id = CLOCK_PTP_SYSTEM, }; - esp_eth_clock_init(CLOCK_PTP_SYSTEM, &clk_cfg); + if (esp_eth_clock_init(eth_handle, &clk_cfg) != ESP_OK) { + ptperr("failed to initialize PTP clock"); + return ERROR; + } // Enable time stamping in L2TAP if(ioctl(state->ptp_socket, L2TAP_S_TIMESTAMP_EN) < 0) @@ -1262,11 +1274,12 @@ static void ptp_lock_local_clock_freq(FAR struct ptp_state_s *state, // compute how to scale the slave frequency to lock with master frequency and also try to catch-up the offset double freq_scale = ((double)(remote_delta_ns /*+ tick_diff*/ + adj)) / (double)local_delta_ns; - esp_eth_clock_adj_param_t clk_adj_param = { - .mode = ETH_CLK_ADJ_FREQ_SCALE, - .freq_scale = freq_scale + long freq_ppb = (long)((freq_scale - 1.0) * 1e9); + struct timex tx = { + .modes = ADJ_FREQUENCY, + .freq = freq_ppb, }; - esp_eth_clock_adjtime(CLOCK_PTP_SYSTEM, &clk_adj_param); + clock_adjtime(CLOCK_PTP_SYSTEM, &tx); state->remote_time_ns_prev = remote_time_ns; state->local_time_ns_prev = local_time_ns; diff --git a/examples/ethernet/ptp/main/idf_component.yml b/examples/ethernet/ptp/main/idf_component.yml index d386512160..adbd517769 100644 --- a/examples/ethernet/ptp/main/idf_component.yml +++ b/examples/ethernet/ptp/main/idf_component.yml @@ -1,7 +1,5 @@ dependencies: espressif/ethernet_init: - version: "^1.3.0" - esp_eth_time: - path: ${IDF_PATH}/examples/ethernet/ptp/components/esp_eth_time + version: "~1.3.0" ptpd: path: ${IDF_PATH}/examples/ethernet/ptp/components/ptpd diff --git a/examples/ethernet/ptp/main/ptp_main.c b/examples/ethernet/ptp/main/ptp_main.c index 2e45e30641..1f4ec2aa44 100644 --- a/examples/ethernet/ptp/main/ptp_main.c +++ b/examples/ethernet/ptp/main/ptp_main.c @@ -14,7 +14,9 @@ #include "driver/gpio.h" #include "ptpd.h" -#include "esp_eth_time.h" +#if CONFIG_EXAMPLE_PTP_PULSE_CALLBACK +#include "esp_eth_clock.h" // for esp_eth_clock_set_target_time and esp_eth_clock_register_target_cb +#endif //CONFIG_EXAMPLE_PTP_PULSE_CALLBACK #include "esp_eth_mac_esp.h" #define ETH_IF_KEY "ETH_0" @@ -78,7 +80,7 @@ IRAM_ATTR bool ts_callback(esp_eth_mediator_t *eth, void *user_args) timespecadd(&s_next_time, &interval, &s_next_time); struct timespec curr_time; - esp_eth_clock_gettime(CLOCK_PTP_SYSTEM, &curr_time); + clock_gettime(CLOCK_PTP_SYSTEM, &curr_time); // check the next time is in the future if (timespeccmp(&s_next_time, &curr_time, >)) { esp_eth_clock_set_target_time(CLOCK_PTP_SYSTEM, &s_next_time); @@ -105,7 +107,7 @@ void app_main(void) #elif CONFIG_EXAMPLE_PTP_PULSE_CALLBACK struct timespec cur_time; // wait for the clock to be available - while (esp_eth_clock_gettime(CLOCK_PTP_SYSTEM, &cur_time) == -1) { + while (clock_gettime(CLOCK_PTP_SYSTEM, &cur_time) == -1) { vTaskDelay(pdMS_TO_TICKS(500)); } // register callback function which will toggle output pin @@ -149,7 +151,7 @@ void app_main(void) if ((clock_source_valid == true && clock_source_valid_last == false) || first_pass) { first_pass = false; // get the most recent (now synced) time - esp_eth_clock_gettime(CLOCK_PTP_SYSTEM, &cur_time); + clock_gettime(CLOCK_PTP_SYSTEM, &cur_time); // compute the next target time s_next_time.tv_sec = 1; timespecadd(&s_next_time, &cur_time, &s_next_time);