mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 11:03:11 +00:00
feat(esp_libc): refactored POSIX timers get/set functions
- Added sys/timex.h and clock_adjtime API
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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,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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user