feat(esp_libc): refactored POSIX timers get/set functions

- Added sys/timex.h and clock_adjtime API
This commit is contained in:
Ondrej Kosta
2026-02-18 16:15:26 +01:00
parent 4ca48c370e
commit ed2e6735ff
28 changed files with 1428 additions and 545 deletions
+4
View File
@@ -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")
+22
View File
@@ -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"
@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <sys/time.h>
#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
@@ -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
+1 -1
View File
@@ -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
@@ -0,0 +1,293 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <sys/time.h>
#include <sys/timex.h>
#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;
}
+16 -1
View File
@@ -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)
+20
View File
@@ -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)
@@ -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)
{
+19 -1
View File
@@ -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
+2 -1
View File
@@ -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)
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#include <time.h>
#include <sys/timex.h>
#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
@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Minimal subset of <sys/timex.h> 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
@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#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
+15
View File
@@ -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)
+221 -176
View File
@@ -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 <errno.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <time.h>
#include <limits.h>
#include <reent.h>
@@ -13,8 +14,8 @@
#include <sys/types.h>
#include <sys/reent.h>
#include <sys/time.h>
#include <sys/timex.h>
#include <sys/times.h>
#include <sys/lock.h>
#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();
}
+132
View File
@@ -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 <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/lock.h>
#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 */
@@ -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 <time.h>
#include <sys/time.h>
#include <sys/timex.h>
#include <errno.h>
#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)
@@ -1,3 +0,0 @@
idf_component_register(SRCS "esp_eth_time.c"
PRIV_REQUIRES esp_eth
INCLUDE_DIRS ".")
@@ -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.
@@ -1,153 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#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;
}
@@ -1,133 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <sys/time.h>
#include <stdint.h>
#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
@@ -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
@@ -1,3 +0,0 @@
dependencies:
esp_eth_time:
path: ${IDF_PATH}/examples/ethernet/ptp/components/esp_eth_time
+26 -13
View File
@@ -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 <sys/timex.h>
#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;
+1 -3
View File
@@ -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
+6 -4
View File
@@ -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);