feat(esp_eth): added HW Time Stamping support for ESP32P4

Added mechanism to L2 TAP to retreive time stamp

Added PTP time synchronization example
This commit is contained in:
Ondrej Kosta 2024-06-26 15:40:32 +02:00
parent 269322191a
commit d2b1202d5a
57 changed files with 5653 additions and 403 deletions

View File

@ -92,6 +92,20 @@ struct esp_eth_mediator_s {
*/
esp_err_t (*stack_input)(esp_eth_mediator_t *eth, uint8_t *buffer, uint32_t length);
/**
* @brief Deliver packet to upper stack with additional information about reception
*
* @param[in] eth: mediator of Ethernet driver
* @param[in] buffer: packet buffer
* @param[in] length: length of the packet
* @param[in] info: info associated with reception (e.g. time stamp)
*
* @return
* - ESP_OK: deliver packet to upper stack successfully
* - ESP_FAIL: deliver packet failed because some error occurred
*/
esp_err_t (*stack_input_info)(esp_eth_mediator_t *eth, uint8_t *buffer, uint32_t length, void *info);
/**
* @brief Callback on Ethernet state changed
*

View File

@ -56,6 +56,7 @@ typedef struct {
* @param[in] eth_handle: handle of Ethernet driver
* @param[in] buffer: frame buffer that will get input to upper stack
* @param[in] length: length of the frame buffer
* @param[in] priv pointer to private resource, defined when registering the input function
*
* @return
* - ESP_OK: input frame buffer to upper stack successfully
@ -64,6 +65,22 @@ typedef struct {
*/
esp_err_t (*stack_input)(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t length, void *priv);
/**
* @brief Input frame buffer to user's stack with additional information about received frame
*
* @param[in] eth_handle: handle of Ethernet driver
* @param[in] buffer: frame buffer that will get input to upper stack
* @param[in] length: length of the frame buffer
* @param[in] priv pointer to private resource, defined when registering the input function
* @param[in] info: extra information about received Ethernet frame (may be timestamp, CRC offload check result, etc.)
*
* @return
* - ESP_OK: input frame buffer to upper stack successfully
* - ESP_FAIL: error occurred when inputting buffer to upper stack
*
*/
esp_err_t (*stack_input_info)(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t length, void *priv, void *info);
/**
* @brief Callback function invoked when lowlevel initialization is finished
*
@ -173,6 +190,7 @@ typedef enum {
.phy = ephy, \
.check_link_period_ms = 2000, \
.stack_input = NULL, \
.stack_input_info = NULL, \
.on_lowlevel_init_done = NULL, \
.on_lowlevel_deinit_done = NULL, \
.read_phy_reg = NULL, \
@ -257,6 +275,31 @@ esp_err_t esp_eth_update_input_path(
esp_err_t (*stack_input)(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv),
void *priv);
/**
* @brief Update Ethernet data input path with input function which consumes extra info about received frame.
*
* @note Extra information may include but is not limited to such info like Time Stamp, CRC check offload result, etc.
* The MAC layer of the Ethernet driver of the particular device must provide extra information using
* `stack_input_info()` function. Otherwise, input path function registered by this API is not invoked. If this
* is the case, register `stack_input` function by `esp_eth_update_input_path()` instead.
*
* @note After install driver, Ethernet still don't know where to deliver the input buffer.
* In fact, this API registers a callback function which get invoked when Ethernet received new packets.
*
* @param[in] hdl handle of Ethernet driver
* @param[in] stack_input_info function pointer, which does the actual process on incoming packets
* @param[in] priv private resource, which gets passed to `stack_input_info` callback without any modification
*
* @return
* - ESP_OK: update input path successfully
* - ESP_ERR_INVALID_ARG: update input path failed because of some invalid argument
* - ESP_FAIL: update input path failed because some other error occurred
*/
esp_err_t esp_eth_update_input_path_info(
esp_eth_handle_t hdl,
esp_err_t (*stack_input_info)(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv, void *info),
void *priv);
/**
* @brief General Transmit
*
@ -267,25 +310,45 @@ esp_err_t esp_eth_update_input_path(
* @return
* - ESP_OK: transmit frame buffer successfully
* - ESP_ERR_INVALID_ARG: transmit frame buffer failed because of some invalid argument
* - ESP_ERR_INVALID_STATE: invalid driver state (e.i. driver is not started)
* - ESP_ERR_INVALID_STATE: invalid driver state (e.i., driver is not started)
* - ESP_ERR_TIMEOUT: transmit frame buffer failed because HW was not get available in predefined period
* - ESP_FAIL: transmit frame buffer failed because some other error occurred
*/
esp_err_t esp_eth_transmit(esp_eth_handle_t hdl, void *buf, size_t length);
/**
* @brief Special Transmit with variable number of arguments
* @brief Extended Transmit with variable number of arguments
*
* @note Typical intended use case of this function is to assemble Ethernet frame from multiple input buffers
* at lower layer of the driver (MAC layer) to avoid unnecessary buffer reallocation and copy.
*
* @param hdl handle of Ethernet driver
* @param ctrl optional transmit control structure (MAC specific), set to NULL when not required
* @param argc number variable arguments
* @param ... variable arguments
* @return
* - ESP_OK: transmit successful
* - ESP_ERR_INVALID_STATE: invalid driver state (e.i., driver is not started)
* - ESP_ERR_TIMEOUT: transmit frame buffer failed because HW was not get available in predefined period
* - ESP_FAIL: transmit frame buffer failed because some other error occurred
*/
esp_err_t esp_eth_transmit_ctrl_vargs(esp_eth_handle_t hdl, void *ctrl, uint32_t argc, ...);
/**
* @brief Wrapper over Extended Transmit function to ensure backward compatibility.
*
* @param[in] hdl handle of Ethernet driver
* @note For new implementations, it is recommended to use `esp_eth_transmit_ctrl_vargs()` directly.
*
* @param[in] eth_hdl handle of Ethernet driver
* @param[in] argc number variable arguments
* @param ... variable arguments
* @return
* - ESP_OK: transmit successful
* - ESP_ERR_INVALID_STATE: invalid driver state (e.i. driver is not started)
* - ESP_ERR_INVALID_STATE: invalid driver state (e.i., driver is not started)
* - ESP_ERR_TIMEOUT: transmit frame buffer failed because HW was not get available in predefined period
* - ESP_FAIL: transmit frame buffer failed because some other error occurred
*/
esp_err_t esp_eth_transmit_vargs(esp_eth_handle_t hdl, uint32_t argc, ...);
#define esp_eth_transmit_vargs(eth_hdl, argc, ...) esp_eth_transmit_ctrl_vargs(eth_hdl, NULL, (argc) * 2, ##__VA_ARGS__)
/**
* @brief Misc IO function of Ethernet driver

View File

@ -104,9 +104,32 @@ struct esp_eth_mac_s {
*/
esp_err_t (*transmit)(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length);
/**
* @brief Transmit packet with extended control from Ethernet MAC and constructed with special parameters at Layer2.
*
* @param[in] mac: Ethernet MAC instance
* @param[in] ctrl: optional transmit control structure (chip specific), set to NULL when not required
* @param[in] argc: number variable arguments
* @param[in] args: variable arguments
*
* @note Typical intended use case is to make possible to construct a frame from multiple higher layer
* buffers without a need of buffer reallocations. However, other use cases are not limited.
*
* @return
* - ESP_OK: transmit packet successfully
* - ESP_ERR_INVALID_SIZE: number of actually sent bytes differs to expected
* - ESP_FAIL: transmit packet failed because some other error occurred
*
* @note Returned error codes may differ for each specific MAC chip.
*
*/
esp_err_t (*transmit_ctrl_vargs)(esp_eth_mac_t *mac, void *ctrl, uint32_t argc, va_list args);
/**
* @brief Transmit packet from Ethernet MAC constructed with special parameters at Layer2.
*
* @warning Deprecated, use `transmit_ctrl_vargs()` function instead.
*
* @param[in] mac: Ethernet MAC instance
* @param[in] argc: number variable arguments
* @param[in] args: variable arguments
@ -122,7 +145,7 @@ struct esp_eth_mac_s {
* @note Returned error codes may differ for each specific MAC chip.
*
*/
esp_err_t (*transmit_vargs)(esp_eth_mac_t *mac, uint32_t argc, va_list args);
esp_err_t (*transmit_vargs)(esp_eth_mac_t *mac, uint32_t argc, va_list args) __attribute__((deprecated("Use transmit_ctrl_vargs instead")));
/**
* @brief Receive packet from Ethernet MAC
@ -318,6 +341,15 @@ struct esp_eth_mac_s {
esp_err_t (*del)(esp_eth_mac_t *mac);
};
/**
* @brief Ethernet MAC Time Stamp
*
*/
typedef struct {
uint32_t seconds; /*!< Seconds */
uint32_t nanoseconds; /*!< Nanoseconds */
} eth_mac_time_t;
/**
* @brief Configuration of Ethernet MAC object
*

View File

@ -192,8 +192,30 @@ typedef enum {
ETH_MAC_ESP_CMD_SET_TDES0_CFG_BITS = ETH_CMD_CUSTOM_MAC_CMDS_OFFSET, /*!< Set Transmit Descriptor Word 0 control bit mask (debug option)*/
ETH_MAC_ESP_CMD_CLEAR_TDES0_CFG_BITS, /*!< Clear Transmit Descriptor Word 0 control bit mask (debug option)*/
ETH_MAC_ESP_CMD_PTP_ENABLE, /*!< Enable IEEE1588 Time stamping */
ETH_MAC_ESP_CMD_S_PTP_TIME, /*!< Set PTP time in the module */
ETH_MAC_ESP_CMD_G_PTP_TIME, /*!< Get PTP time from the module */
ETH_MAC_ESP_CMD_ADJ_PTP_FREQ, /*!< Adjust current PTP time frequency increment by scale factor */
ETH_MAC_ESP_CMD_ADJ_PTP_TIME, /*!< Adjust base PTP time frequency increment by PPS */
ETH_MAC_ESP_CMD_S_TARGET_TIME, /*!< Set Target Time at which interrupt is invoked when PTP time exceeds this value*/
ETH_MAC_ESP_CMD_S_TARGET_CB /*!< Set pointer to a callback function invoked when PTP time exceeds Target Time */
} eth_mac_esp_io_cmd_t;
#ifdef SOC_EMAC_IEEE1588V2_SUPPORTED
/**
* @brief Type of callback function invoked under Time Stamp target time exceeded interrupt
*
* @warning Time stamping is currently Experimental Feature! Be aware that API may change.
*
* @param eth: mediator of Ethernet driver
* @param user_args user specific arguments (placeholder - IDF-11429)
*
* @return
* - TRUE when high priority task has been woken by this function
* - FALSE no high priority task was woken by this function
*/
typedef bool (*ts_target_exceed_cb_from_isr_t)(esp_eth_mediator_t *eth, void *user_args);
#endif // SOC_EMAC_IEEE1588V2_SUPPORTED
/**
* @brief Default ESP32's EMAC specific configuration
*
@ -242,7 +264,7 @@ typedef enum {
.smi_gpio = \
{ \
.mdc_num = 31, \
.mdio_num = 27 \
.mdio_num = 52 \
}, \
.interface = EMAC_DATA_INTERFACE_RMII, \
.clock_config = \

View File

@ -9,7 +9,9 @@
extern "C" {
#endif
#include <stdbool.h>
#include "esp_err.h"
#include "esp_eth_mac.h"
/**
* @brief Indicate to ::emac_esp_dma_receive_frame that receive frame buffer was allocated by ::emac_esp_dma_alloc_recv_buf
@ -31,6 +33,15 @@ typedef struct emac_esp_dma_t *emac_esp_dma_handle_t;
*/
typedef struct emac_esp_dma_config_t emac_esp_dma_config_t;
/**
* @brief Supplementary properties for the ESP EMAC DMA transmit buffer
*
*/
typedef struct {
uint8_t* buf;
uint32_t size;
} emac_esp_dma_transmit_buff_t;
/**
* @brief Reset DMA
* @note This function should be called prior each EMAC start
@ -49,22 +60,23 @@ void emac_esp_dma_reset(emac_esp_dma_handle_t emac_esp_dma);
* zero on fail
*/
uint32_t emac_esp_dma_transmit_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t length);
/**
* @brief Transmit data from multiple buffers over EMAC in single Ethernet frame. Data will be joint into
* single frame in order in which the buffers are stored in input array.
* @brief Extended version of Transmit data function. It is capable to transmit from multiple buffers to appear as single Ethernet frame.
* The function also provides hardware time stamp of the transmission on supported targets.
*
* @note Data is joint into single frame in order in which the buffers are stored in input array.
*
* @param[in] emac_esp_dma EMAC DMA handle
* @param[in] buffs array of pointers to buffers to be transmitted
* @param[in] lengths array of lengths of the buffers
* @param[in] inbuffs_cnt number of buffers (i.e. input arrays size)
* @param[in] buffs_array array of buffers to be transmitted
* @param[in] buffs_cnt number of buffers (i.e. buffs array sizes can be 1 to n)
* @param[out] ts time stamp at which the frame was transmitted by EMAC. Valid time stamp returned only on supported targets. Pass NULL
* if time stamp is not required.
*
* @return number of transmitted bytes on success
* zero on fail
*
* @pre @p lengths array must have the same size as @p buffs array and their elements need to be stored in the same
* order, i.e. lengths[1] is a length associated with data buffer referenced at buffs[1] position.
*/
uint32_t emac_esp_dma_transmit_multiple_buf_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t **buffs, uint32_t *lengths, uint32_t buffs_cnt);
uint32_t emac_esp_dma_transmit_frame_ext(emac_esp_dma_handle_t emac_esp_dma, emac_esp_dma_transmit_buff_t *buffs_array, uint32_t buffs_cnt, eth_mac_time_t *ts);
/**
* @brief Allocate buffer with size equal to actually received Ethernet frame size.
@ -89,6 +101,8 @@ uint8_t *emac_esp_dma_alloc_recv_buf(emac_esp_dma_handle_t emac_esp_dma, uint32_
* @param[in] buf buffer into which the Ethernet frame is to be copied
* @param[in] size buffer size. When buffer was allocated by ::emac_esp_dma_alloc_recv_buf, this parameter needs to be set
* to @c EMAC_DMA_BUF_SIZE_AUTO
* @param[out] ts time stamp at which the frame was received by EMAC. Only available on supported targets. Can be NULL
* when time stamp is not required.
*
* @return - number of copied bytes when success
* - number of bytes of received Ethernet frame when maximum allowed buffer @p size is less than actual size of
@ -101,7 +115,7 @@ uint8_t *emac_esp_dma_alloc_recv_buf(emac_esp_dma_handle_t emac_esp_dma, uint32_
* is less than actual size of received Ethernet frame, the frame will be truncated.
* @note FCS field is never copied
*/
uint32_t emac_esp_dma_receive_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t size);
uint32_t emac_esp_dma_receive_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t size, eth_mac_time_t *ts);
/**
* @brief Flush frame stored in Rx DMA
@ -135,6 +149,14 @@ void emac_esp_dma_set_tdes0_ctrl_bits(emac_esp_dma_handle_t emac_esp_dma, uint32
*/
void emac_esp_dma_clear_tdes0_ctrl_bits(emac_esp_dma_handle_t emac_esp_dma, uint32_t bit_mask);
/**
* @brief Enables DMA time stamping feature
*
* @param[in] emac_esp_dma EMAC DMA handle
* @param[in] enable enable when true
*/
void emac_esp_dma_ts_enable(emac_esp_dma_handle_t emac_esp_dma, bool enable);
/**
* @brief Creates a new instance of the ESP EMAC DMA
*

View File

@ -3,12 +3,12 @@ archive: libesp_eth.a
entries:
if ETH_IRAM_OPTIMIZATION = y:
esp_eth:esp_eth_transmit (noflash_text)
esp_eth:esp_eth_transmit_vargs (noflash_text)
esp_eth:esp_eth_transmit_ctrl_vargs (noflash_text)
esp_eth_mac_esp:emac_esp32_transmit (noflash_text)
esp_eth_mac_esp:emac_esp32_transmit_multiple_bufs (noflash_text)
esp_eth_mac_esp:emac_esp32_transmit_ctrl_vargs (noflash_text)
esp_eth_mac_esp:emac_esp32_receive (noflash_text)
esp_eth_mac_esp:emac_esp32_rx_task (noflash_text)
esp_eth_mac_esp_dma:emac_esp_dma_transmit_frame (noflash_text)
esp_eth_mac_esp_dma:emac_esp_dma_transmit_multiple_buf_frame (noflash_text)
esp_eth_mac_esp_dma:emac_esp_dma_transmit_frame_ext (noflash_text)
esp_eth_mac_esp_dma:emac_esp_dma_alloc_recv_buf (noflash_text)
esp_eth_mac_esp_dma:emac_esp_dma_receive_frame (noflash_text)

View File

@ -58,6 +58,7 @@ typedef struct {
SemaphoreHandle_t transmit_mutex;
#endif // CONFIG_ETH_TRANSMIT_MUTEX
esp_err_t (*stack_input)(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t length, void *priv);
esp_err_t (*stack_input_info)(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t length, void *priv, void *info);
esp_err_t (*on_lowlevel_init_done)(esp_eth_handle_t eth_handle);
esp_err_t (*on_lowlevel_deinit_done)(esp_eth_handle_t eth_handle);
esp_err_t (*customized_read_phy_reg)(esp_eth_handle_t eth_handle, uint32_t phy_addr, uint32_t phy_reg, uint32_t *reg_value);
@ -102,9 +103,28 @@ static esp_err_t eth_stack_input(esp_eth_mediator_t *eth, uint8_t *buffer, uint3
esp_eth_driver_t *eth_driver = __containerof(eth, esp_eth_driver_t, mediator);
if (eth_driver->stack_input) {
return eth_driver->stack_input((esp_eth_handle_t)eth_driver, buffer, length, eth_driver->priv);
// try to pass traffic using extended `stack_input_info`. It's for compatibility reasons since older MAC drivers may
// still use `stack_input` but higher level API registered extended version.
} else if (eth_driver->stack_input_info) {
return eth_driver->stack_input_info((esp_eth_handle_t)eth_driver, buffer, length, eth_driver->priv, NULL);
}
// No stack input path has been installed, just drop the incoming packets
free(buffer);
free(buffer); // IDF-11444
return ESP_OK;
}
static esp_err_t eth_stack_input_info(esp_eth_mediator_t *eth, uint8_t *buffer, uint32_t length, void *info)
{
esp_eth_driver_t *eth_driver = __containerof(eth, esp_eth_driver_t, mediator);
if (eth_driver->stack_input_info) {
return eth_driver->stack_input_info((esp_eth_handle_t)eth_driver, buffer, length, eth_driver->priv, info);
// try using simple `stack_input`. It's for compatibility reasons since higher level API may still register original `stack_input`.
// Additional frame info is silently lost of course.
} else if (eth_driver->stack_input) {
return eth_driver->stack_input((esp_eth_handle_t)eth_driver, buffer, length, eth_driver->priv);
}
// No stack input path has been installed, just drop the incoming packets
free(buffer); // IDF-11444
return ESP_OK;
}
@ -209,6 +229,7 @@ esp_err_t esp_eth_driver_install(const esp_eth_config_t *config, esp_eth_handle_
eth_driver->duplex = ETH_DUPLEX_HALF;
eth_driver->speed = ETH_SPEED_10M;
eth_driver->stack_input = config->stack_input;
eth_driver->stack_input_info = config->stack_input_info;
eth_driver->on_lowlevel_init_done = config->on_lowlevel_init_done;
eth_driver->on_lowlevel_deinit_done = config->on_lowlevel_deinit_done;
eth_driver->check_link_period_ms = config->check_link_period_ms;
@ -217,6 +238,7 @@ esp_err_t esp_eth_driver_install(const esp_eth_config_t *config, esp_eth_handle_
eth_driver->mediator.phy_reg_read = eth_phy_reg_read;
eth_driver->mediator.phy_reg_write = eth_phy_reg_write;
eth_driver->mediator.stack_input = eth_stack_input;
eth_driver->mediator.stack_input_info = eth_stack_input_info;
eth_driver->mediator.on_state_changed = eth_on_state_changed;
// set mediator for both mac and phy object, so that mac and phy are connected to each other via mediator
mac->set_mediator(mac, &eth_driver->mediator);
@ -331,11 +353,27 @@ esp_err_t esp_eth_update_input_path(
esp_eth_driver_t *eth_driver = (esp_eth_driver_t *)hdl;
ESP_GOTO_ON_FALSE(eth_driver, ESP_ERR_INVALID_ARG, err, TAG, "ethernet driver handle can't be null");
eth_driver->priv = priv;
eth_driver->stack_input_info = NULL;
eth_driver->stack_input = stack_input;
err:
return ret;
}
esp_err_t esp_eth_update_input_path_info(
esp_eth_handle_t hdl,
esp_err_t (*stack_input_info)(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv, void *info),
void *priv)
{
esp_err_t ret = ESP_OK;
esp_eth_driver_t *eth_driver = (esp_eth_driver_t *)hdl;
ESP_GOTO_ON_FALSE(eth_driver, ESP_ERR_INVALID_ARG, err, TAG, "ethernet driver handle can't be null");
eth_driver->priv = priv;
eth_driver->stack_input = NULL;
eth_driver->stack_input_info = stack_input_info;
err:
return ret;
}
esp_err_t esp_eth_transmit(esp_eth_handle_t hdl, void *buf, size_t length)
{
esp_err_t ret = ESP_OK;
@ -365,7 +403,7 @@ err:
return ret;
}
esp_err_t esp_eth_transmit_vargs(esp_eth_handle_t hdl, uint32_t argc, ...)
esp_err_t esp_eth_transmit_ctrl_vargs(esp_eth_handle_t hdl, void *ctrl, uint32_t argc, ...)
{
esp_err_t ret = ESP_OK;
esp_eth_driver_t *eth_driver = (esp_eth_driver_t *)hdl;
@ -384,7 +422,7 @@ esp_err_t esp_eth_transmit_vargs(esp_eth_handle_t hdl, uint32_t argc, ...)
}
#endif // CONFIG_ETH_TRANSMIT_MUTEX
va_start(args, argc);
ret = mac->transmit_vargs(mac, argc, args);
ret = mac->transmit_ctrl_vargs(mac, ctrl, argc, args);
#if CONFIG_ETH_TRANSMIT_MUTEX
xSemaphoreGive(eth_driver->transmit_mutex);
#endif // CONFIG_ETH_TRANSMIT_MUTEX

View File

@ -29,11 +29,11 @@ struct esp_eth_netif_glue_t {
esp_event_handler_instance_t get_ip_ctx_handler;
};
static esp_err_t eth_input_to_netif(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t length, void *priv)
static esp_err_t eth_input_to_netif(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t length, void *priv, void *info)
{
#if CONFIG_ESP_NETIF_L2_TAP
esp_err_t ret = ESP_OK;
ret = esp_vfs_l2tap_eth_filter(eth_handle, buffer, (size_t *)&length);
ret = esp_vfs_l2tap_eth_filter_frame(eth_handle, buffer, (size_t *)&length, info);
if (length == 0) {
return ret;
}
@ -52,7 +52,7 @@ static esp_err_t esp_eth_post_attach(esp_netif_t *esp_netif, void *args)
esp_eth_netif_glue_t *netif_glue = (esp_eth_netif_glue_t *)args;
netif_glue->base.netif = esp_netif;
esp_eth_update_input_path(netif_glue->eth_driver, eth_input_to_netif, esp_netif);
esp_eth_update_input_path_info(netif_glue->eth_driver, eth_input_to_netif, esp_netif);
// set driver related config to esp-netif
esp_netif_driver_ifconfig_t driver_ifconfig = {

View File

@ -66,7 +66,6 @@ typedef struct {
uint32_t flow_control_high_water_mark;
uint32_t flow_control_low_water_mark;
uint8_t addr[ETH_ADDR_LEN];
bool isr_need_yield;
bool flow_ctrl_enabled; // indicates whether the user want to do flow control
bool do_flow_ctrl; // indicates whether we need to do software flow control
bool use_pll; // Only use (A/M)PLL in EMAC_DATA_INTERFACE_RMII && EMAC_CLK_OUT
@ -79,6 +78,9 @@ typedef struct {
#ifdef CONFIG_IDF_TARGET_ESP32
esp_clock_output_mapping_handle_t rmii_clk_hdl; // we use the esp_clock_output driver to output a pre-configured APLL clock as the RMII reference clock
#endif
#ifdef SOC_EMAC_IEEE1588V2_SUPPORTED
ts_target_exceed_cb_from_isr_t ts_target_exceed_cb_from_isr;
#endif
} emac_esp32_t;
static esp_err_t emac_esp_alloc_driver_obj(const eth_mac_config_t *config, emac_esp32_t **emac_out_hdl);
@ -167,15 +169,12 @@ err:
static esp_err_t emac_esp32_set_link(esp_eth_mac_t *mac, eth_link_t link)
{
esp_err_t ret = ESP_OK;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
switch (link) {
case ETH_LINK_UP:
ESP_GOTO_ON_ERROR(esp_intr_enable(emac->intr_hdl), err, TAG, "enable interrupt failed");
emac_esp32_start(mac);
ESP_LOGD(TAG, "emac started");
break;
case ETH_LINK_DOWN:
ESP_GOTO_ON_ERROR(esp_intr_disable(emac->intr_hdl), err, TAG, "disable interrupt failed");
emac_esp32_stop(mac);
ESP_LOGD(TAG, "emac stopped");
break;
@ -260,9 +259,78 @@ esp_err_t emac_esp_custom_ioctl(esp_eth_mac_t *mac, int cmd, void *data)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
switch (cmd) {
switch (cmd)
{
#ifdef SOC_EMAC_IEEE1588V2_SUPPORTED
case ETH_MAC_ESP_CMD_PTP_ENABLE: {
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "PTP enable invalid argument, cant' be NULL");
bool enable = *((bool *)data);
if (enable) {
EMAC_IF_RCC_ATOMIC() {
emac_hal_clock_enable_ptp(&emac->hal, EMAC_PTP_CLK_SRC_XTAL, true);
}
emac_hal_ptp_config_t ptp_config = {
.upd_method = ETH_PTP_UPDATE_METHOD_FINE,
.roll = ETH_PTP_DIGITAL_ROLLOVER,
.ptp_clk_src_period_ns = 25, // = 1 / 40MHz
.ptp_req_accuracy_ns = 40 // required accuracy (must be worse than ptp_ref_clk)
};
ESP_RETURN_ON_ERROR(emac_hal_ptp_start(&emac->hal, &ptp_config), TAG, "failed to start PTP module");
emac_esp_dma_ts_enable(emac->emac_dma_hndl, true);
} else {
ESP_RETURN_ON_ERROR(emac_hal_ptp_stop(&emac->hal), TAG, "failed to stop PTP module");
emac_esp_dma_ts_enable(emac->emac_dma_hndl, false);
EMAC_IF_RCC_ATOMIC() {
emac_hal_clock_enable_ptp(&emac->hal, 0, false);
}
}
break;
}
case ETH_MAC_ESP_CMD_S_PTP_TIME: {
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "PTP set time invalid argument, cant' be NULL");
eth_mac_time_t *time = (eth_mac_time_t *)data;
ESP_RETURN_ON_ERROR(emac_hal_ptp_set_sys_time(&emac->hal, time->seconds, time->nanoseconds), TAG, "failed to set PTP time");
break;
}
case ETH_MAC_ESP_CMD_G_PTP_TIME: {
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "PTP get time invalid argument, cant' be NULL");
eth_mac_time_t *time = (eth_mac_time_t *)data;
ESP_RETURN_ON_ERROR(emac_hal_ptp_get_sys_time(&emac->hal, &time->seconds, &time->nanoseconds), TAG, "failed to get PTP time");
break;
}
case ETH_MAC_ESP_CMD_ADJ_PTP_TIME: {
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "PTP adjust time invalid argument, cant' be NULL");
int32_t adj_ppb = *((int32_t *)data);
ESP_RETURN_ON_ERROR(emac_hal_ptp_adj_inc(&emac->hal, adj_ppb), TAG, "failed to adjust PTP time base");
break;
}
case ETH_MAC_ESP_CMD_ADJ_PTP_FREQ: {
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "PTP adjust frequency invalid argument, cant' be NULL");
double scale_factor = *((double *)data);
ESP_RETURN_ON_ERROR(emac_hal_adj_freq_factor(&emac->hal, scale_factor), TAG, "failed to aject PTP time base by scale factor");
break;
}
case ETH_MAC_ESP_CMD_S_TARGET_CB:
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "PTP set target callback function invalid argument, cant' be NULL");
emac->ts_target_exceed_cb_from_isr = (ts_target_exceed_cb_from_isr_t)data;
break;
case ETH_MAC_ESP_CMD_S_TARGET_TIME: {
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "PTP set target time invalid argument, cant' be NULL");
eth_mac_time_t *start_time = (eth_mac_time_t *)data;
ESP_RETURN_ON_ERROR(emac_hal_ptp_set_target_time(&emac->hal, start_time->seconds, start_time->nanoseconds), TAG,
"failed to set PTP target time");
break;
}
#else
case ETH_MAC_ESP_CMD_PTP_ENABLE:
case ETH_MAC_ESP_CMD_S_PTP_TIME:
case ETH_MAC_ESP_CMD_G_PTP_TIME:
case ETH_MAC_ESP_CMD_ADJ_PTP_TIME:
case ETH_MAC_ESP_CMD_ADJ_PTP_FREQ:
case ETH_MAC_ESP_CMD_S_TARGET_CB:
case ETH_MAC_ESP_CMD_S_TARGET_TIME:
return ESP_ERR_NOT_SUPPORTED;
#endif
case ETH_MAC_ESP_CMD_SET_TDES0_CFG_BITS:
ESP_RETURN_ON_FALSE(data != NULL, ESP_ERR_INVALID_ARG, TAG, "cannot set DMA tx desc flag to null");
emac_esp_dma_set_tdes0_ctrl_bits(emac->emac_dma_hndl, *(uint32_t *)data);
@ -288,23 +356,28 @@ static esp_err_t emac_esp32_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t
return ESP_OK;
}
static esp_err_t emac_esp32_transmit_multiple_bufs(esp_eth_mac_t *mac, uint32_t argc, va_list args)
static esp_err_t emac_esp32_transmit_ctrl_vargs(esp_eth_mac_t *mac, void *ctrl, uint32_t argc, va_list args)
{
esp_err_t ret = ESP_OK;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
uint8_t *bufs[argc];
uint32_t len[argc];
uint32_t buf_num = argc / 2;
emac_esp_dma_transmit_buff_t buff_array[buf_num];
uint32_t exp_len = 0;
for (int i = 0; i < argc; i++) {
bufs[i] = va_arg(args, uint8_t *);
len[i] = va_arg(args, uint32_t);
exp_len += len[i];
for (int i = 0; i < buf_num; i++) {
buff_array[i].buf = va_arg(args, uint8_t *);
buff_array[i].size = va_arg(args, uint32_t);
exp_len += buff_array[i].size;
}
eth_mac_time_t *ts = (eth_mac_time_t *)ctrl;
uint32_t sent_len = emac_esp_dma_transmit_frame_ext(emac->emac_dma_hndl, buff_array, buf_num, ts);
if(sent_len != exp_len) {
ESP_LOGD(TAG, "insufficient TX buffer size");
return ESP_ERR_NO_MEM;
}
uint32_t sent_len = emac_esp_dma_transmit_multiple_buf_frame(emac->emac_dma_hndl, bufs, len, argc);
ESP_GOTO_ON_FALSE(sent_len == exp_len, ESP_ERR_INVALID_SIZE, err, TAG, "insufficient TX buffer size");
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length)
@ -313,7 +386,7 @@ static esp_err_t emac_esp32_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *
uint32_t expected_len = *length;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
ESP_GOTO_ON_FALSE(buf && length, ESP_ERR_INVALID_ARG, err, TAG, "can't set buf and length to null");
uint32_t receive_len = emac_esp_dma_receive_frame(emac->emac_dma_hndl, buf, expected_len);
uint32_t receive_len = emac_esp_dma_receive_frame(emac->emac_dma_hndl, buf, expected_len, NULL);
emac_esp_dma_get_remain_frames(emac->emac_dma_hndl, &emac->frames_remain, &emac->free_rx_descriptor);
/* we need to check the return value in case the buffer size is not enough */
ESP_GOTO_ON_FALSE(expected_len >= receive_len, ESP_ERR_INVALID_SIZE, err, TAG, "received buffer longer than expected");
@ -337,23 +410,29 @@ static void emac_esp32_rx_task(void *arg)
buffer = emac_esp_dma_alloc_recv_buf(emac->emac_dma_hndl, &frame_len);
/* we have memory to receive the frame of maximal size previously defined */
if (buffer != NULL) {
uint32_t recv_len = emac_esp_dma_receive_frame(emac->emac_dma_hndl, buffer, EMAC_DMA_BUF_SIZE_AUTO);
#ifdef SOC_EMAC_IEEE1588V2_SUPPORTED
eth_mac_time_t ts;
eth_mac_time_t *p_ts = &ts;
#else
eth_mac_time_t *p_ts = NULL;
#endif
uint32_t recv_len = emac_esp_dma_receive_frame(emac->emac_dma_hndl, buffer, EMAC_DMA_BUF_SIZE_AUTO, p_ts);
if (recv_len == 0) {
ESP_LOGE(TAG, "frame copy error");
free(buffer);
/* ensure that interface to EMAC does not get stuck with unprocessed frames */
/* ensures that interface to EMAC does not get stuck with unprocessed frames */
emac_esp_dma_flush_recv_frame(emac->emac_dma_hndl);
} else if (frame_len > recv_len) {
ESP_LOGE(TAG, "received frame was truncated");
free(buffer);
} else {
ESP_LOGD(TAG, "receive len= %" PRIu32, recv_len);
emac->eth->stack_input(emac->eth, buffer, recv_len);
emac->eth->stack_input_info(emac->eth, buffer, recv_len, (void *)p_ts);
}
/* if allocation failed and there is a waiting frame */
/* if allocation failed and there is a waiting frame */
} else if (frame_len) {
ESP_LOGE(TAG, "no mem for receive buffer");
/* ensure that interface to EMAC does not get stuck with unprocessed frames */
/* ensures that interface to EMAC does not get stuck with unprocessed frames */
emac_esp_dma_flush_recv_frame(emac->emac_dma_hndl);
}
emac_esp_dma_get_remain_frames(emac->emac_dma_hndl, &emac->frames_remain, &emac->free_rx_descriptor);
@ -498,18 +577,33 @@ IRAM_ATTR void emac_isr_default_handler(void *args)
emac_hal_context_t *hal = (emac_hal_context_t *)args;
uint32_t intr_stat = emac_hal_get_intr_status(hal);
emac_hal_clear_corresponding_intr(hal, intr_stat);
emac_esp32_t *emac = __containerof(hal, emac_esp32_t, hal);
bool high_task_woken = false;
#if SOC_EMAC_IEEE1588V2_SUPPORTED && EMAC_LL_CONFIG_ENABLE_MAC_INTR_MASK & EMAC_LL_MAC_INTR_TIME_STAMP_ENABLE
if (intr_stat & EMAC_LL_DMA_TIMESTAMP_TRIGGER_INTR) {
uint32_t ts_stat = emac_hal_get_ts_status(hal);
if (ts_stat & EMAC_LL_TS_TARGET_TIME_REACHED) {
if (emac->ts_target_exceed_cb_from_isr) {
bool ts_high_task_woken = emac->ts_target_exceed_cb_from_isr(emac->eth, NULL);
high_task_woken |= ts_high_task_woken;
}
}
}
#endif // SOC_EMAC_IEEE1588V2_SUPPORTED
#if EMAC_LL_CONFIG_ENABLE_INTR_MASK & EMAC_LL_INTR_RECEIVE_ENABLE
if (intr_stat & EMAC_LL_DMA_RECEIVE_FINISH_INTR) {
emac_esp32_t *emac = __containerof(hal, emac_esp32_t, hal);
BaseType_t high_task_wakeup = pdFALSE;
BaseType_t rx_high_task_woken = pdFALSE;
/* notify receive task */
vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup);
if (high_task_wakeup == pdTRUE) {
portYIELD_FROM_ISR();
}
vTaskNotifyGiveFromISR(emac->rx_task_hdl, &rx_high_task_woken);
high_task_woken |= (bool)rx_high_task_woken;
}
#endif // EMAC_LL_CONFIG_ENABLE_INTR_MASK & EMAC_LL_INTR_RECEIVE_ENABLE
if (high_task_woken) {
portYIELD_FROM_ISR();
}
#endif
}
static void emac_esp_free_driver_obj(emac_esp32_t *emac)
@ -684,6 +778,8 @@ esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_esp32_emac_config_t *esp32_config
ret_code = esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, isr_flags,
emac_isr_default_handler, &emac->hal, &(emac->intr_hdl));
ESP_GOTO_ON_FALSE(ret_code == ESP_OK, NULL, err, TAG, "alloc emac interrupt failed");
ret_code = esp_intr_enable(emac->intr_hdl);
ESP_GOTO_ON_FALSE(ret_code == ESP_OK, NULL, err, TAG, "enable interrupt failed");
/* init GPIO used by SMI interface */
ret_code = emac_esp_gpio_init_smi(&esp32_config->smi_gpio);
@ -714,9 +810,12 @@ esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_esp32_emac_config_t *esp32_config
emac->parent.set_peer_pause_ability = emac_esp32_set_peer_pause_ability;
emac->parent.enable_flow_ctrl = emac_esp32_enable_flow_ctrl;
emac->parent.transmit = emac_esp32_transmit;
emac->parent.transmit_vargs = emac_esp32_transmit_multiple_bufs;
emac->parent.transmit_ctrl_vargs = emac_esp32_transmit_ctrl_vargs;
emac->parent.receive = emac_esp32_receive;
emac->parent.custom_ioctl = emac_esp_custom_ioctl;
#ifdef SOC_EMAC_IEEE1588V2_SUPPORTED
emac->ts_target_exceed_cb_from_isr = NULL;
#endif // SOC_EMAC_IEEE1588V2_SUPPORTED
return &(emac->parent);
err:

View File

@ -19,6 +19,8 @@
#define EMAC_TDES0_FS_CTRL_FLAGS_MASK 0x0FCC0000 // modifiable bits mask associated with the First Segment
#define EMAC_TDES0_LS_CTRL_FLAGS_MASK 0x40000000 // modifiable bits mask associated with the Last Segment
#define PTP_TX_TIMESTAMP_TO 50 // maximum loops observed on P4 was 31 @ETH frame 1500B
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#define DMA_CACHE_WB(addr, size) do { \
esp_err_t msync_ret = esp_cache_msync((void *)addr, size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); \
@ -116,6 +118,15 @@ void emac_esp_dma_clear_tdes0_ctrl_bits(emac_esp_dma_handle_t emac_esp_dma, uint
emac_esp_dma->tx_desc_flags &= ~flag;
}
void emac_esp_dma_ts_enable(emac_esp_dma_handle_t emac_esp_dma, bool enable)
{
if (enable) {
emac_esp_dma_set_tdes0_ctrl_bits(emac_esp_dma, EMAC_HAL_TDES0_TX_TS_ENABLE);
} else {
emac_esp_dma_clear_tdes0_ctrl_bits(emac_esp_dma, EMAC_HAL_TDES0_TX_TS_ENABLE);
}
}
uint32_t emac_esp_dma_transmit_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t length)
{
/* Get the number of Tx buffers to use for the frame */
@ -183,16 +194,19 @@ err:
return 0;
}
uint32_t emac_esp_dma_transmit_multiple_buf_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t **buffs, uint32_t *lengths, uint32_t buffs_cnt)
uint32_t emac_esp_dma_transmit_frame_ext(emac_esp_dma_handle_t emac_esp_dma, emac_esp_dma_transmit_buff_t *buffs_array, uint32_t buffs_cnt, eth_mac_time_t *ts)
{
/* Get the number of Tx buffers to use for the frame */
uint32_t dma_bufcount = 0;
uint32_t sentout = 0;
uint8_t *ptr = buffs[0];
uint32_t lastlen = lengths[0];
uint32_t avail_len = CONFIG_ETH_DMA_BUFFER_SIZE;
uint8_t *ptr = buffs_array->buf;
uint32_t lastlen = buffs_array->size;
eth_dma_tx_descriptor_t *desc_iter = emac_esp_dma->tx_desc;
#if SOC_EMAC_IEEE1588V2_SUPPORTED
eth_dma_tx_descriptor_t *desc_last = desc_iter;
#endif
/* A frame is transmitted in multiple descriptor */
while (dma_bufcount < CONFIG_ETH_DMA_TX_BUFFER_NUM) {
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
@ -222,8 +236,9 @@ uint32_t emac_esp_dma_transmit_multiple_buf_frame(emac_esp_dma_handle_t emac_esp
/* Update processed input buffers info */
buffs_cnt--;
ptr = *(++buffs);
lastlen = *(++lengths);
buffs_array++;
ptr = buffs_array->buf;
lastlen = buffs_array->size;
/* There is only limited available space in the current descriptor, use it all */
} else {
/* copy data from uplayer stack buffer */
@ -237,8 +252,9 @@ uint32_t emac_esp_dma_transmit_multiple_buf_frame(emac_esp_dma_handle_t emac_esp
} else {
/* Update processed input buffers info */
buffs_cnt--;
ptr = *(++buffs);
lastlen = *(++lengths);
buffs_array++;
ptr = buffs_array->buf;
lastlen = buffs_array->size;
}
avail_len = CONFIG_ETH_DMA_BUFFER_SIZE;
desc_iter->TDES1.TransmitBuffer1Size = CONFIG_ETH_DMA_BUFFER_SIZE;
@ -255,6 +271,9 @@ uint32_t emac_esp_dma_transmit_multiple_buf_frame(emac_esp_dma_handle_t emac_esp
/* Setting the last segment bit */
desc_iter->TDES0.LastSegment = 1;
desc_iter->TDES0.Value |= emac_esp_dma->tx_desc_flags & EMAC_TDES0_LS_CTRL_FLAGS_MASK;
#if SOC_EMAC_IEEE1588V2_SUPPORTED
desc_last = desc_iter;
#endif
break;
}
@ -270,6 +289,23 @@ uint32_t emac_esp_dma_transmit_multiple_buf_frame(emac_esp_dma_handle_t emac_esp
}
emac_hal_transmit_poll_demand(&emac_esp_dma->hal);
#if SOC_EMAC_IEEE1588V2_SUPPORTED
if (ts != NULL) {
uint32_t timeout = 0;
do {
timeout++;
DMA_CACHE_INVALIDATE(desc_last, EMAC_HAL_DMA_DESC_SIZE);
} while (emac_hal_get_txdesc_timestamp(&emac_esp_dma->hal, desc_last, &ts->seconds, &ts->nanoseconds) == ESP_ERR_INVALID_STATE &&
timeout < PTP_TX_TIMESTAMP_TO);
if (timeout >= PTP_TX_TIMESTAMP_TO) {
/* zeros indicate invalid time stamp since it is not possible to ever get "zero time" under normal conditions */
ts->seconds = 0;
ts->nanoseconds = 0;
}
}
#endif
return sentout;
err:
return 0;
@ -360,7 +396,7 @@ uint8_t *emac_esp_dma_alloc_recv_buf(emac_esp_dma_handle_t emac_esp_dma, uint32_
return buf;
}
uint32_t emac_esp_dma_receive_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t size)
uint32_t emac_esp_dma_receive_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t size, eth_mac_time_t *ts)
{
uint32_t ret_len = 0;
uint32_t copy_len = 0;
@ -395,14 +431,25 @@ uint32_t emac_esp_dma_receive_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t
}
DMA_CACHE_INVALIDATE(desc_iter->Buffer1Addr, CONFIG_ETH_DMA_BUFFER_SIZE);
memcpy(buf, (void *)(desc_iter->Buffer1Addr), copy_len);
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* `copy_len` does not include CRC (which may be stored in separate buffer), hence check if we reached the last descriptor */
while (!desc_iter->RDES0.LastDescriptor) {
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
}
#if SOC_EMAC_IEEE1588V2_SUPPORTED
if (ts != NULL) {
if (emac_hal_get_rxdesc_timestamp(&emac_esp_dma->hal, desc_iter, &ts->seconds, &ts->nanoseconds) != ESP_OK) {
/* zeros indicate invalid time stamp since it is not possible to ever get "zero time" under normal conditions */
ts->seconds = 0;
ts->nanoseconds = 0;
}
}
#endif
/* return last descriptor to DMA */
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* update rxdesc */
emac_esp_dma->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
/* poll rx demand */

View File

@ -5,6 +5,7 @@
*/
#include <string.h>
#include <inttypes.h>
#include "time.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
@ -29,7 +30,7 @@ typedef struct
uint16_t expected_size_3;
} recv_esp_emac_check_info_t;
static esp_err_t eth_recv_esp_emac_check_cb(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv)
static esp_err_t eth_recv_esp_emac_check_cb(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv, void *info)
{
emac_frame_t *pkt = (emac_frame_t *)buffer;
recv_esp_emac_check_info_t *recv_info = (recv_esp_emac_check_info_t *)priv;
@ -107,7 +108,7 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
bool loopback_en = true;
esp_eth_ioctl(eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en);
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, eth_recv_esp_emac_check_cb, &recv_info));
TEST_ESP_OK(esp_eth_update_input_path_info(eth_handle, eth_recv_esp_emac_check_cb, &recv_info));
// start the driver
TEST_ESP_OK(esp_eth_start(eth_handle));
@ -191,7 +192,46 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_pkt, transmit_size));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
ESP_LOGI(TAG, "-- Verify transmission from multiple buffers --");
ESP_LOGI(TAG, "-- Verify transmission using extended Tx fnc using one buffer--");
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE;
ESP_LOGI(TAG, "transmit frame size: %" PRIu16, transmit_size);
recv_info.expected_size = transmit_size;
eth_mac_time_t ts;
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, &ts, 2, test_pkt, transmit_size));
printf("test %lu.%lu sec\n", ts.seconds, ts.nanoseconds); // TODO finish the test
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE - 1;
ESP_LOGI(TAG, "transmit frame size: %" PRIu16, transmit_size);
recv_info.expected_size = transmit_size;
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 2, test_pkt, transmit_size));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE + 1;
ESP_LOGI(TAG, "transmit frame size: %" PRIu16, transmit_size);
recv_info.expected_size = transmit_size;
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 2, test_pkt, transmit_size));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = 2 * CONFIG_ETH_DMA_BUFFER_SIZE;
ESP_LOGI(TAG, "transmit frame size: %" PRIu16, transmit_size);
recv_info.expected_size = transmit_size;
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 2, test_pkt, transmit_size));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = 2 * CONFIG_ETH_DMA_BUFFER_SIZE - 1;
ESP_LOGI(TAG, "transmit frame size: %" PRIu16, transmit_size);
recv_info.expected_size = transmit_size;
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 2, test_pkt, transmit_size));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = 2 * CONFIG_ETH_DMA_BUFFER_SIZE + 1;
ESP_LOGI(TAG, "transmit frame size: %" PRIu16, transmit_size);
recv_info.expected_size = transmit_size;
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 2, test_pkt, transmit_size));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
ESP_LOGI(TAG, "-- Verify transmission using extended Tx func with multiple buffers --");
uint16_t transmit_size_2;
// allocated the second buffer
uint8_t *pkt_data_2 = malloc(ETH_MAX_PAYLOAD_LEN);
@ -211,11 +251,11 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
recv_info.expected_size_2 = transmit_size_2;
for (int32_t i = 0; i < config_eth_dma_max_buffer_num*2; i++) {
ESP_LOGI(TAG, "transmit joint frame size: %" PRIu16 ", i = %" PRIi32, transmit_size + transmit_size_2, i);
TEST_ESP_OK(esp_eth_transmit_vargs(eth_handle, 2, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 4, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
}
ESP_LOGI(TAG, "Verify boundary conditions");
ESP_LOGI(TAG, "Verify backwards compatibility");
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE;
transmit_size_2 = CONFIG_ETH_DMA_BUFFER_SIZE;
recv_info.expected_size = transmit_size;
@ -224,12 +264,21 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
TEST_ESP_OK(esp_eth_transmit_vargs(eth_handle, 2, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
ESP_LOGI(TAG, "Verify boundary conditions");
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE;
transmit_size_2 = CONFIG_ETH_DMA_BUFFER_SIZE;
recv_info.expected_size = transmit_size;
recv_info.expected_size_2 = transmit_size_2;
ESP_LOGI(TAG, "transmit joint frame size: %" PRIu16, transmit_size + transmit_size_2);
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 4, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE - 1;
transmit_size_2 = CONFIG_ETH_DMA_BUFFER_SIZE;
recv_info.expected_size = transmit_size;
recv_info.expected_size_2 = transmit_size_2;
ESP_LOGI(TAG, "transmit joint frame size: %" PRIu16, transmit_size + transmit_size_2);
TEST_ESP_OK(esp_eth_transmit_vargs(eth_handle, 2, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 4, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE + 1;
@ -237,7 +286,7 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
recv_info.expected_size = transmit_size;
recv_info.expected_size_2 = transmit_size_2;
ESP_LOGI(TAG, "transmit joint frame size: %" PRIu16, transmit_size + transmit_size_2);
TEST_ESP_OK(esp_eth_transmit_vargs(eth_handle, 2, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 4, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
uint16_t transmit_size_3 = 256;
@ -256,7 +305,7 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
recv_info.expected_size_2 = transmit_size_2;
recv_info.expected_size_3 = transmit_size_3;
ESP_LOGI(TAG, "transmit joint frame size (3 buffs): %" PRIu16, transmit_size + transmit_size_2 + transmit_size_3);
TEST_ESP_OK(esp_eth_transmit_vargs(eth_handle, 3, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 6, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE - 1;
@ -266,7 +315,7 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
recv_info.expected_size_2 = transmit_size_2;
recv_info.expected_size_3 = transmit_size_3;
ESP_LOGI(TAG, "transmit joint frame size (3 buffs): %" PRIu16, transmit_size + transmit_size_2 + transmit_size_3);
TEST_ESP_OK(esp_eth_transmit_vargs(eth_handle, 3, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 6, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE + 1;
@ -276,7 +325,7 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
recv_info.expected_size_2 = transmit_size_2;
recv_info.expected_size_3 = transmit_size_3;
ESP_LOGI(TAG, "transmit joint frame size (3 buffs): %" PRIu16, transmit_size + transmit_size_2 + transmit_size_3);
TEST_ESP_OK(esp_eth_transmit_vargs(eth_handle, 3, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
TEST_ESP_OK(esp_eth_transmit_ctrl_vargs(eth_handle, NULL, 6, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
free(test_pkt);
@ -363,7 +412,7 @@ TEST_CASE("internal emac interrupt priority", "[esp_emac]")
static uint8_t *s_recv_frames[TEST_FRAMES_NUM];
static uint8_t s_recv_frames_cnt = 0;
static esp_err_t eth_recv_esp_emac_err_check_cb(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv)
static esp_err_t eth_recv_esp_emac_err_check_cb(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv, void *info)
{
SemaphoreHandle_t mutex = (SemaphoreHandle_t)priv;
s_recv_frames[s_recv_frames_cnt++] = buffer;
@ -397,7 +446,7 @@ TEST_CASE("internal emac erroneous frames", "[esp_emac]")
bool loopback_en = true;
esp_eth_ioctl(eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en);
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, eth_recv_esp_emac_err_check_cb, mutex));
TEST_ESP_OK(esp_eth_update_input_path_info(eth_handle, eth_recv_esp_emac_err_check_cb, mutex));
// start the driver
TEST_ESP_OK(esp_eth_start(eth_handle));

View File

@ -1,107 +0,0 @@
# ESP-NETIF architecture
| (A) USER CODE |
| |
.................| init settings events |
. +----------------------------------------+
. . | *
. . | *
--------+ +===========================+ * +-----------------------+
| | new/config get/set | * | |
| | |...*.....| init |
| |---------------------------| * | |
init | | |**** | |
start |************| event handler |*********| DHCP |
stop | | | | |
| |---------------------------| | |
| | | | NETIF |
+-----| | | +-----------------+ |
| glue|---<----|---| esp_netif_transmit |--<------| netif_output | |
| | | | | | | |
| |--->----|---| esp_netif_receive |-->------| netif_input | |
| | | | | + ----------------+ |
| |...<....|...| esp_netif_free_rx_buffer |...<.....| packet buffer |
+-----| | | | | | |
| | | | | | (D) |
(B) | | | | (C) | +-----------------------+
--------+ | | +===========================+
communication | | NETWORK STACK
DRIVER | | ESP-NETIF
| | +------------------+
| | +---------------------------+.........| open/close |
| | | | | |
| -<--| l2tap_write |-----<---| write |
| | | | |
---->--| esp_vfs_l2tap_eth_filter |----->---| read |
| | | |
| (E) | +------------------+
+---------------------------+
USER CODE
ESP-NETIF L2 TAP
## Data/event flow:
* `........` Initialization line from user code to esp-netif and comm driver
* `--<--->--` Data packets going from communication media to TCP/IP stack and back
* `********` Events agregated in ESP-NETIP propagates to driver, user code and network stack
* `|` User settings and runtime configuration
## Components:
### A) User code, boiler plate
Overall application interaction with communication media and network stack
* initialization code
- create a new instance of ESP-NETIF
- configure the object with
1) netif specific options (flags, behaviour, name)
2) network stack options (netif init and input functions, not publicly available)
3) IO driver specific options (transmit, tx_free functions, IO driver handle)
- setup event handlers
- use default handlers for common interfaces defined in IO drivers; or define a specific handlers
for customised behaviour/new interfaces
- register handlers for app related events (such as IP lost/acquired)
- interact with network interfaces using ESP-NETIF API
### B) Communication driver, IO driver, media driver
* event handler
- define behaviour patterns of interaction with ESP-NETIF (example: ehternet link-up -> turn netif on)
* glue IO layer: adapt the input/output functions to use esp-netif transmit/input/free_rx
- install driver_transmit to appropriate ESP-NETIF object, so that outgoing packets from
network stack are passed to the IO driver
- calls esp_netif_receive to pass incoming data to network stack
### C) ESP-NETIF
* init API (new, configure)
* IO API: for passing data between IO driver and network stack
* event/action API (esp-netif lifecycle management)
- building blocks for designing event handlers
* setters, getters
* network stack abstraction: enabling user interaction with TCP/IP stack
- netif up/down
- DHCP server, client
- DNS API
* driver conversion utilities
### D) Network stack: no public interaction with user code (wrtt interfaces)
### E) ESP-NETIF L2 TAP Interface
The ESP-NETIF L2 TAP interface is ESP-IDF mechanism utilized to access Data Link Layer (L2 per OSI/ISO) for frame reception and
transmission from user application. Its typical usage in embedded world might be implementation of non-IP related protocols
such as PTP, Wake on LAN and others. Note that only Ethernet (IEEE 802.3)
is currently supported.
From user perspective, the ESP-NETIF L2 TAP interface is accessed using file descriptors of VFS which provides a file-like interfacing
(using functions like ``open()``, ``read()``, ``write()``, etc).
There is only one ESP-NETIF L2 TAP interface device (path name) available. However multiple file descriptors with different configuration
can be opened at a time since the ESP-NETIF L2 TAP interface can be understood as generic entry point to the NETIF internal structure.
Important is then specific configuration of particular file descriptor. It can be configured to give an access to specific Network Interface
identified by ``if_key`` (e.g. `ETH_DEF`) and to filter only specific frames based on their type (e.g. Ethernet type in case of IEEE 802.3).
Filtering only specific frames is crucial since the ESP-NETIF L2 TAP needs to work along with IP stack and so the IP related traffic
(IP, ARP, etc.) should not be passed directly to the user application. Even though such option is still configurable, it is not recommended in
standard use cases. Filtering is also advantageous from a perspective the users application gets access only to frame types it is interested
in and the remaining traffic is either passed to other L2 TAP file descriptors or to IP stack.

View File

@ -1,13 +1,15 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdalign.h>
#include "esp_err.h"
#define L2TAP_VFS_DEFAULT_PATH "/dev/net/tap"
#define L2TAP_VFS_CONFIG_DEFAULT() \
{ \
@ -29,20 +31,76 @@ typedef struct {
} l2tap_vfs_config_t;
typedef enum {
L2TAP_S_RCV_FILTER,
L2TAP_G_RCV_FILTER,
L2TAP_S_INTF_DEVICE,
L2TAP_G_INTF_DEVICE,
L2TAP_S_DEVICE_DRV_HNDL,
L2TAP_G_DEVICE_DRV_HNDL
L2TAP_S_RCV_FILTER, /*!< Set Ethertype filter, frames with this type to be passed to the file descriptor. */
L2TAP_G_RCV_FILTER, /*!< Get current Ethertype filter. */
L2TAP_S_INTF_DEVICE, /*!< Bound the file descriptor to a specific Network Interface is identified by its ``if_key``. */
L2TAP_G_INTF_DEVICE, /*!< Get the Network Interface ``if_key`` the file descriptor is bound to. */
L2TAP_S_DEVICE_DRV_HNDL, /*!< Bound the file descriptor to a specific Network Interface identified by IO Driver handle. */
L2TAP_G_DEVICE_DRV_HNDL, /*!< Get the Network Interface IO Driver handle the file descriptor is bound to. */
L2TAP_S_TIMESTAMP_EN, /*!< Enables the hardware Time Stamping (TS) processing by the file descriptor. TS needs to be supported by hardware and enabled in the IO driver. */
} l2tap_ioctl_opt_t;
/**
* @brief Information Record (IREC) Header Type indicates expected type of Header Data
*
*/
typedef enum {
L2TAP_IREC_INVALID = -1, /*!< Indicate invalid IREC (data is not valid)*/
L2TAP_IREC_TIME_STAMP = 1 /*!< To retrieve time stamp in `struct timespec` format */
} l2tap_irec_type_t;
/**
* @brief Information Record (IREC)
*
*/
typedef struct
{
size_t len; /*!< Length of the record including header and data*/
l2tap_irec_type_t type; /*!< Type of the record */
alignas(long long) uint8_t data[]; /*!< Records Data aligned to double word */
} l2tap_irec_hdr_t;
/**
* @brief Extended Buffer
*
* @attention Use macros when allocating buffer for Information Records and when manipulating with data in the records
* to ensure proper memory alignment
*
*/
typedef struct {
size_t info_recs_len; /*!< Length of Information Records buffer */
void *info_recs_buff; /*!< Buffer holding extended information (IRECs) related to IO frames */
size_t buff_len; /*!< Length of the actual IO Frame buffer */
void *buff; /*!< Pointer to the IO Frame buffer */
} l2tap_extended_buff_t;
/**
* @brief Macros for operations with Information Records
*
* Align to double word size to each info record starts aligned in memory even if not aligned
* info data size is used by previous record. Double word alignment (at 32-bit arch) is needed when accessing
* double word variables or structs containing double word variables.
*
*/
#define L2TAP_ALIGN(size) (((size) + sizeof(long long) - 1U) & ~(sizeof(long long) - 1U))
#define L2TAP_IREC_LEN(size) (sizeof(l2tap_irec_hdr_t) + size)
#define L2TAP_IREC_SPACE(size) (L2TAP_ALIGN(L2TAP_IREC_LEN(size)))
#define L2TAP_IREC_FIRST(ext_buff) (ext_buff)->info_recs_len >= sizeof(l2tap_irec_hdr_t) ? (l2tap_irec_hdr_t *)(ext_buff)->info_recs_buff : NULL
#define L2TAP_IREC_NEXT(ext_buff, curr_rec) (((curr_rec) == NULL) ? L2TAP_IREC_FIRST(ext_buff) : \
(uint8_t *)(curr_rec) + L2TAP_ALIGN((curr_rec)->len) + L2TAP_ALIGN(sizeof(l2tap_irec_hdr_t)) > \
(uint8_t *)(ext_buff)->info_recs_buff + (ext_buff)->info_recs_len ? \
NULL : \
(l2tap_irec_hdr_t *)(void *)(((uint8_t *)(curr_rec) + L2TAP_ALIGN((curr_rec)->len))))
/**
* @brief Add L2 TAP virtual filesystem driver
*
* This function must be called prior usage of ESP-NETIF L2 TAP Interface
*
* @param config L2 TAP virtual filesystem driver configuration. Default base path /dev/net/tap is used when this paramenter is NULL.
* @param config L2 TAP virtual filesystem driver configuration. Default base path /dev/net/tap is used when this parameter is NULL.
* @return esp_err_t
* - ESP_OK on success
*/
@ -51,7 +109,7 @@ esp_err_t esp_vfs_l2tap_intf_register(l2tap_vfs_config_t *config);
/**
* @brief Removes L2 TAP virtual filesystem driver
*
* @param base_path Base path to the L2 TAP virtual filesystem driver. Default path /dev/net/tap is used when this paramenter is NULL.
* @param base_path Base path to the L2 TAP virtual filesystem driver. Default path /dev/net/tap is used when this parameter is NULL.
* @return esp_err_t
* - ESP_OK on success
*/
@ -63,10 +121,25 @@ esp_err_t esp_vfs_l2tap_intf_unregister(const char *base_path);
* @param driver_handle handle of driver at which the frame was received
* @param buff received L2 frame
* @param size input length of the L2 frame which is set to 0 when frame is filtered into L2 TAP
* @param info extra information about received Ethernet frame
* @return esp_err_t
* - ESP_OK is always returned
*/
esp_err_t esp_vfs_l2tap_eth_filter(l2tap_iodriver_handle driver_handle, void *buff, size_t *size);
esp_err_t esp_vfs_l2tap_eth_filter_frame(l2tap_iodriver_handle driver_handle, void *buff, size_t *size, void *info);
/**
* @brief Wrapper over L2 TAP filter function to ensure backward compatibility.
*
* This macro is provided for backward compatibility with the original `esp_vfs_l2tap_eth_filter` function.
* It calls `esp_vfs_l2tap_eth_filter_frame()` with the `info` parameter set to `NULL`, which means
* L2 TAP features that depend on extra information about the received Ethernet frame (e.g., timestamps)
* will not work as expected.
*
* @note For new implementations, it is recommended to use `esp_vfs_l2tap_eth_filter_frame()` directly to
* take advantage of the extended functionality.
*/
#define esp_vfs_l2tap_eth_filter(drv_hndl, buf, size) esp_vfs_l2tap_eth_filter_frame(drv_hndl, buf, size, NULL)
#ifdef __cplusplus
}

View File

@ -13,9 +13,9 @@ components/esp_netif/test_apps/test_app_esp_netif:
components/esp_netif/test_apps/test_app_vfs_l2tap:
disable:
- if: IDF_TARGET not in ["esp32"]
- if: IDF_TARGET not in ["esp32", "esp32p4"]
temporary: true
reason: Ethernet runners currently use only ESP32
reason: Not needed to test on all targets (chosen two, one for each architecture plus P4 tests time stamping)
depends_components:
- esp_netif
- lwip

View File

@ -1,2 +1,2 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
| Supported Targets | ESP32 | ESP32-P4 |
| ----------------- | ----- | -------- |

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -24,6 +24,7 @@
#include "sdkconfig.h"
#include "arpa/inet.h" // for ntohs, etc.
#include "lwip/prot/ethernet.h" // Ethernet headers
#include "soc/soc_caps.h"
#include "unity.h"
#include "test_utils.h"
@ -32,6 +33,7 @@
#define ETH_FILTER_LE 0x7A05
#define ETH_FILTER_BE 0x057A
#define ETH_TYPE_PTP 0x88F7
#define ETH_START_BIT BIT(0)
#define ETH_STOP_BIT BIT(1)
@ -68,6 +70,33 @@ typedef struct {
};
} test_vfs_eth_tap_msg_t;
// PTPv2 header
typedef struct {
uint8_t message_type; // 4 bits: Message Type
uint8_t version; // 4 bits: PTP version
uint16_t message_length; // 16 bits: Total length of the PTP message
uint8_t domain_number; // 8 bits: Domain number
uint8_t reserved1; // Reserved (8 bits)
uint16_t flags; // 16 bits: Flags field
int64_t correction_field; // 64 bits: Correction field
uint32_t reserved2; // Reserved (32 bits)
uint64_t clock_identity; // 64 bits: Clock identity
uint16_t port_number; // 16 bits: Port number
uint16_t sequence_id; // 16 bits: Sequence ID
uint8_t control_field; // 8 bits: Control field (deprecated)
int8_t log_message_interval; // 8 bits: Log message interval
} __attribute__((packed)) ptpv2_hdr_t;
typedef struct {
ptpv2_hdr_t ptp_hdr;
uint64_t timestamp;
} __attribute__((packed)) ptp_msg_t;
typedef struct {
struct eth_hdr eth_hdr;
ptp_msg_t ptp_msg;
} __attribute__((packed)) test_eth_ptp_msg_t;
/* =============================================================================
* Common Routines
* ============================================================================= */
@ -163,6 +192,9 @@ static void ethernet_init(test_vfs_eth_network_t *network_hndls)
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
network_hndls->mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
#ifdef CONFIG_IDF_TARGET_ESP32P4
phy_config.reset_gpio_num = 51;
#endif // CONFIG_IDF_TARGET_ESP32P4
network_hndls->phy = esp_eth_phy_new_ip101(&phy_config);
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(network_hndls->mac, network_hndls->phy);
network_hndls->eth_handle = NULL;
@ -205,7 +237,7 @@ static void ethernet_deinit(test_vfs_eth_network_t *network_hndls)
TEST_ESP_OK(esp_event_loop_delete_default());
}
// Global test message send by "send_task"
// Global test message
static test_vfs_eth_tap_msg_t s_test_msg = {
.header = {
.src.addr = {0},
@ -297,6 +329,7 @@ typedef struct {
int eth_tap_fd;
SemaphoreHandle_t sem;
bool on_select;
int queue_frames_num;
} open_close_task_ctrl_t;
static void open_read_task(void *task_param)
@ -318,30 +351,35 @@ static void open_read_task(void *task_param)
uint16_t eth_type_filter = ETH_FILTER_LE;
TEST_ASSERT_NOT_EQUAL(-1, ioctl(task_control->eth_tap_fd, L2TAP_S_RCV_FILTER, &eth_type_filter));
xSemaphoreGive(task_control->sem);
if (task_control->on_select == true) {
ESP_LOGI(TAG, "task1: going to block on select...");
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(task_control->eth_tap_fd, &rfds);
// it is expected that blocking select is not unblocked by close and it timeouts (the fd number may be reused later
// though and so select released but that's not tested here)
TEST_ASSERT_EQUAL(0, select(task_control->eth_tap_fd + 1, &rfds, NULL, NULL, &tv));
ESP_LOGI(TAG, "task1: select timeout");
// get an error when try to use closed fd
TEST_ASSERT_EQUAL(-1, read(task_control->eth_tap_fd, in_buffer, in_buf_size));
if (task_control->queue_frames_num > 0) {
for (int i = 0; i < task_control->queue_frames_num; i++) {
TEST_ASSERT_NOT_EQUAL(-1, write(task_control->eth_tap_fd, &s_test_msg, sizeof(s_test_msg)));
}
} else {
ESP_LOGI(TAG, "task1: going to block on read...");
// it is expected that blocking read is unblocked by close
TEST_ASSERT_EQUAL(-1, read(task_control->eth_tap_fd, in_buffer, in_buf_size));
ESP_LOGI(TAG, "task1: unblocked");
xSemaphoreGive(task_control->sem);
if (task_control->on_select == true) {
ESP_LOGI(TAG, "task1: going to block on select...");
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(task_control->eth_tap_fd, &rfds);
// it is expected that blocking select is not unblocked by close and it timeouts (the fd number may be reused later
// though and so select released but that's not tested here)
TEST_ASSERT_EQUAL(0, select(task_control->eth_tap_fd + 1, &rfds, NULL, NULL, &tv));
ESP_LOGI(TAG, "task1: select timeout");
// get an error when try to use closed fd
TEST_ASSERT_EQUAL(-1, read(task_control->eth_tap_fd, in_buffer, in_buf_size));
} else {
ESP_LOGI(TAG, "task1: going to block on read...");
// it is expected that blocking read is unblocked by close and we read zero bytes
TEST_ASSERT_EQUAL(0, read(task_control->eth_tap_fd, in_buffer, in_buf_size));
ESP_LOGI(TAG, "task1: unblocked");
}
}
xSemaphoreGive(task_control->sem);
@ -355,6 +393,10 @@ static void close_task(void *task_param)
ESP_LOGI(TAG, "task2: closing...");
TEST_ASSERT_EQUAL(0, close(task_control->eth_tap_fd));
if (task_control->queue_frames_num > 0) {
// since there is no blocking "read" task in this scenario, we need to signal that close finished
xSemaphoreGive(task_control->sem);
}
vTaskDelete(NULL);
}
@ -368,6 +410,7 @@ TEST_CASE("esp32 l2tap - open/close", "[ethernet]")
open_close_task_ctrl_t task_control;
task_control.sem = xSemaphoreCreateBinary();
task_control.on_select = false;
task_control.queue_frames_num = 0;
// ==========================================================
// Close when blocking on read
@ -381,7 +424,6 @@ TEST_CASE("esp32 l2tap - open/close", "[ethernet]")
ESP_LOGI(TAG, "Verify closing blocking read from lower priority task...");
xTaskCreate(open_read_task, "open_read_task", 4096, &task_control, 10, NULL);
TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(1000)));
// Close blocking read from lower priority task
xTaskCreate(close_task, "close_task", 4096, &task_control, 5, NULL);
TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(1000)));
@ -398,7 +440,23 @@ TEST_CASE("esp32 l2tap - open/close", "[ethernet]")
ESP_LOGI(TAG, "Verify closing blocking select from lower priority task...");
xTaskCreate(open_read_task, "open_read_task", 4096, &task_control, 10, NULL);
TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000)));
// Close blocking read from lower priority task
xTaskCreate(close_task, "close_task", 4096, &task_control, 5, NULL);
TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000)));
// ==========================================================
// Close when buffered frames pending in L2 TAP
// ==========================================================
// indicate to queue frames
task_control.queue_frames_num = 3;
ESP_LOGI(TAG, "Verify closing from higher priority task when when buffered frames pending...");
xTaskCreate(open_read_task, "open_read_task", 4096, &task_control, 5, NULL);
TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000)));
xTaskCreate(close_task, "close_task", 4096, &task_control, 10, NULL);
TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000)));
ESP_LOGI(TAG, "Verify closing from lower priority task when when buffered frames pending...");
xTaskCreate(open_read_task, "open_read_task", 4096, &task_control, 10, NULL);
TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000)));
xTaskCreate(close_task, "close_task", 4096, &task_control, 5, NULL);
TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000)));
@ -759,6 +817,227 @@ TEST_CASE("esp32 l2tap - read/write multiple fd's used by multiple tasks", "[eth
ethernet_deinit(&eth_network_hndls);
}
/* ============================================================================= */
/**
* @brief Verifies time stamping feature
*
*/
#if SOC_EMAC_IEEE1588V2_SUPPORTED
TEST_CASE("esp32 l2tap - time stamping", "[ethernet]")
{
test_vfs_eth_network_t eth_network_hndls;
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
ethernet_init(&eth_network_hndls);
int eth_tap_fd = open("/dev/net/tap", 0);
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
// Set Ethernet interface on which to get raw frames
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF"));
// Check the Ethernet interface was assigned
char *if_key_str;
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str));
TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str);
// Set the Ethertype filter (frames with this type will be available through the eth_tap_fd)
uint16_t eth_type_filter = ETH_TYPE_PTP;
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, &eth_type_filter));
// Enable time stamping in driver
bool ts_enable = true;
TEST_ESP_OK(esp_eth_ioctl(eth_network_hndls.eth_handle, ETH_MAC_ESP_CMD_PTP_ENABLE, &ts_enable));
test_eth_ptp_msg_t test_ptp_msg = {
.eth_hdr = {
// Note that PTPv2 MAC 01:80:C2:00:00:0E is reserved for "Peer delay messages" which are currently not
// enabled to be snapped by internal EMAC, hence not tested
.dest.addr = {0x01, 0x1b, 0x19, 0x0, 0x0, 0x0},
.type = htons(ETH_TYPE_PTP)
},
.ptp_msg = {
.ptp_hdr = {
.message_type = 1,
.version = 2,
.message_length = htons(sizeof(ptp_msg_t)),
.sequence_id = 0
},
.timestamp = 0,
}
};
uint16_t exp_sequence_id = test_ptp_msg.ptp_msg.ptp_hdr.sequence_id;
TEST_ESP_OK(esp_eth_ioctl(eth_network_hndls.eth_handle, ETH_CMD_G_MAC_ADDR, &test_ptp_msg.eth_hdr.src.addr));
// wrap "Info Records Buffer" into union to ensure proper alignment of data (this is typically needed when
// accessing double word variables or structs containing double word variables)
union {
uint8_t info_recs_buff[L2TAP_IREC_SPACE(sizeof(struct timespec))];
l2tap_irec_hdr_t align;
} u;
l2tap_extended_buff_t ptp_msg_ext_buff;
ptp_msg_ext_buff.info_recs_len = sizeof(u.info_recs_buff);
ptp_msg_ext_buff.info_recs_buff = u.info_recs_buff;
l2tap_irec_hdr_t *ts_info = L2TAP_IREC_FIRST(&ptp_msg_ext_buff);
ts_info->len = L2TAP_IREC_LEN(sizeof(struct timespec));
ts_info->type = L2TAP_IREC_TIME_STAMP;
ESP_LOGI(TAG, "Verify response to read TS when not enabled in TAP");
test_ptp_msg.ptp_msg.ptp_hdr.sequence_id++;
exp_sequence_id++;
ptp_msg_ext_buff.buff = &test_ptp_msg;
ptp_msg_ext_buff.buff_len = sizeof(test_ptp_msg);
int n = write(eth_tap_fd, &ptp_msg_ext_buff, 0);
// when input len is 0 and no special function of tap => expected standard behavior, i.e. nothing was written
TEST_ASSERT_EQUAL(0, n);
ptp_msg_ext_buff.buff = in_buffer;
ptp_msg_ext_buff.buff_len = IN_BUFFER_SIZE;
n = read(eth_tap_fd, &ptp_msg_ext_buff, 0);
// when input len is 0 and no special function of tap => expected standard behavior, i.e. nothing was read
TEST_ASSERT_EQUAL(0, n);
// Enable time stamping in L2TAP, since now we can read TS
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_TIMESTAMP_EN));
ESP_LOGI(TAG, "Verify response when trying to write/read in standard way (input len > 0) but tap configured as TS enabled");
test_ptp_msg.ptp_msg.ptp_hdr.sequence_id++;
exp_sequence_id++;
n = write(eth_tap_fd, &test_ptp_msg, sizeof(test_ptp_msg));
TEST_ASSERT_EQUAL(-1, n);
TEST_ASSERT_EQUAL(EINVAL, errno);
n = read(eth_tap_fd, &in_buffer, sizeof(test_ptp_msg));
TEST_ASSERT_EQUAL(-1, n);
TEST_ASSERT_EQUAL(EINVAL, errno);
ESP_LOGI(TAG, "Verify response to invalid info record type for write");
ts_info->type = 0xFF;
test_ptp_msg.ptp_msg.ptp_hdr.sequence_id++;
exp_sequence_id++;
ptp_msg_ext_buff.buff = &test_ptp_msg;
ptp_msg_ext_buff.buff_len = sizeof(test_ptp_msg);
n = write(eth_tap_fd, &ptp_msg_ext_buff, 0);
TEST_ASSERT_EQUAL(sizeof(test_ptp_msg), n); // invalid info record is ignored and write is successful
// since write was successful, empty L2 TAP queue
ptp_msg_ext_buff.buff = in_buffer;
ptp_msg_ext_buff.buff_len = IN_BUFFER_SIZE;
n = read(eth_tap_fd, &ptp_msg_ext_buff, 0);
int exp_n = sizeof(test_ptp_msg) < 60 ? 60 : sizeof(test_ptp_msg);
TEST_ASSERT_EQUAL(exp_n, n);
TEST_ASSERT_EQUAL(exp_sequence_id, ((test_eth_ptp_msg_t *)in_buffer)->ptp_msg.ptp_hdr.sequence_id);
ESP_LOGI(TAG, "Verify response to invalid record type for read (first need to write correctly)");
ts_info->type = L2TAP_IREC_TIME_STAMP;
test_ptp_msg.ptp_msg.ptp_hdr.sequence_id++;
exp_sequence_id++;
ptp_msg_ext_buff.buff = &test_ptp_msg;
ptp_msg_ext_buff.buff_len = sizeof(test_ptp_msg);
n = write(eth_tap_fd, &ptp_msg_ext_buff, 0);
TEST_ASSERT_EQUAL(sizeof(test_ptp_msg), n);
ts_info->type = 0xFF;
ptp_msg_ext_buff.buff = in_buffer;
ptp_msg_ext_buff.buff_len = IN_BUFFER_SIZE;
n = read(eth_tap_fd, &ptp_msg_ext_buff, 0);
exp_n = sizeof(test_ptp_msg) < 60 ? 60 : sizeof(test_ptp_msg); // minimum Ethernet frame has size of 60B
TEST_ASSERT_EQUAL(exp_n, n); // invalid info record is ignored and read is successful
ESP_LOGI(TAG, "Verify response to invalid record len for write");
ts_info->type = L2TAP_IREC_TIME_STAMP;
ts_info->len = L2TAP_IREC_LEN(1);
test_ptp_msg.ptp_msg.ptp_hdr.sequence_id++;
exp_sequence_id++;
ptp_msg_ext_buff.buff = &test_ptp_msg;
ptp_msg_ext_buff.buff_len = sizeof(test_ptp_msg);
n = write(eth_tap_fd, &ptp_msg_ext_buff, 0);
TEST_ASSERT_EQUAL(sizeof(test_ptp_msg), n); // write is successful
TEST_ASSERT_EQUAL(L2TAP_IREC_INVALID, ts_info->type); // but the TS record is marked invalid
// since write was successful, empty L2 TAP queue
ptp_msg_ext_buff.buff = in_buffer;
ptp_msg_ext_buff.buff_len = IN_BUFFER_SIZE;
n = read(eth_tap_fd, &ptp_msg_ext_buff, 0);
exp_n = sizeof(test_ptp_msg) < 60 ? 60 : sizeof(test_ptp_msg);
TEST_ASSERT_EQUAL(exp_n, n);
TEST_ASSERT_EQUAL(exp_sequence_id, ((test_eth_ptp_msg_t *)in_buffer)->ptp_msg.ptp_hdr.sequence_id);
ESP_LOGI(TAG, "Verify response to invalid record len for read (first we need write correctly)");
ts_info->type = L2TAP_IREC_TIME_STAMP;
ts_info->len = L2TAP_IREC_LEN(sizeof(struct timespec));
test_ptp_msg.ptp_msg.ptp_hdr.sequence_id++;
exp_sequence_id++;
ptp_msg_ext_buff.buff = &test_ptp_msg;
ptp_msg_ext_buff.buff_len = sizeof(test_ptp_msg);
n = write(eth_tap_fd, &ptp_msg_ext_buff, 0);
TEST_ASSERT_EQUAL(sizeof(test_ptp_msg), n);
ts_info->type = L2TAP_IREC_TIME_STAMP;
ts_info->len = L2TAP_IREC_LEN(1);
ptp_msg_ext_buff.buff = in_buffer;
ptp_msg_ext_buff.buff_len = IN_BUFFER_SIZE;
n = read(eth_tap_fd, &ptp_msg_ext_buff, 0);
exp_n = sizeof(test_ptp_msg) < 60 ? 60 : sizeof(test_ptp_msg);
TEST_ASSERT_EQUAL(exp_n, n); // read is successful
TEST_ASSERT_EQUAL(L2TAP_IREC_INVALID, ts_info->type); // but the TS record is marked invalid
ESP_LOGI(TAG, "Verify response to Info Record buffer is NULL for write");
ts_info->type = L2TAP_IREC_TIME_STAMP;
ts_info->len = L2TAP_IREC_LEN(sizeof(struct timespec));
ptp_msg_ext_buff.buff = NULL;
ptp_msg_ext_buff.buff_len = sizeof(test_ptp_msg);
n = write(eth_tap_fd, &ptp_msg_ext_buff, 0);
TEST_ASSERT_EQUAL(-1, n);
TEST_ASSERT_EQUAL(EFAULT, errno);
ESP_LOGI(TAG, "Verify response to Info Record buffer is NULL for read");
ts_info->type = L2TAP_IREC_TIME_STAMP;
ts_info->len = L2TAP_IREC_LEN(1);
ptp_msg_ext_buff.buff = NULL;
ptp_msg_ext_buff.buff_len = IN_BUFFER_SIZE;
n = read(eth_tap_fd, &ptp_msg_ext_buff, 0);
TEST_ASSERT_EQUAL(-1, n);
TEST_ASSERT_EQUAL(EFAULT, errno);
eth_mac_time_t ptp_time = {
.seconds = 10,
.nanoseconds = 412000
};
esp_eth_ioctl(eth_network_hndls.eth_handle, ETH_MAC_ESP_CMD_S_PTP_TIME, &ptp_time);
ESP_LOGI(TAG, "Verify retrieval of Tx and Rx time stamps");
for (int i = 0; i < 4; i++) {
ts_info->type = L2TAP_IREC_TIME_STAMP;
ts_info->len = L2TAP_IREC_LEN(sizeof(struct timespec));
test_ptp_msg.ptp_msg.ptp_hdr.sequence_id++;
exp_sequence_id++;
ptp_msg_ext_buff.buff = &test_ptp_msg;
ptp_msg_ext_buff.buff_len = sizeof(test_ptp_msg);
n = write(eth_tap_fd, &ptp_msg_ext_buff, 0);
TEST_ASSERT_EQUAL(sizeof(test_ptp_msg), n);
TEST_ASSERT_EQUAL(L2TAP_IREC_TIME_STAMP, ts_info->type);
struct timespec *ts = (struct timespec *)ts_info->data;
printf("tap tx TS: %lli.%09li\n", ts->tv_sec, ts->tv_nsec);
TEST_ASSERT_NOT_EQUAL(0, ts->tv_sec);
TEST_ASSERT_NOT_EQUAL(0, ts->tv_nsec);
ptp_msg_ext_buff.buff = in_buffer;
ptp_msg_ext_buff.buff_len = IN_BUFFER_SIZE;
n = read(eth_tap_fd, &ptp_msg_ext_buff, 0);
exp_n = sizeof(test_ptp_msg) < 60 ? 60 : sizeof(test_ptp_msg);
TEST_ASSERT_EQUAL(exp_n, n);
TEST_ASSERT_EQUAL(exp_sequence_id, ((test_eth_ptp_msg_t *)in_buffer)->ptp_msg.ptp_hdr.sequence_id);
TEST_ASSERT_EQUAL(exp_n, ptp_msg_ext_buff.buff_len);
TEST_ASSERT_EQUAL(L2TAP_IREC_TIME_STAMP, ts_info->type);
printf("tap rx TS: %lli.%09li\n", ts->tv_sec, ts->tv_nsec);
TEST_ASSERT_NOT_EQUAL(0, ts->tv_sec);
TEST_ASSERT_NOT_EQUAL(0, ts->tv_nsec);
vTaskDelay(pdMS_TO_TICKS(100));
}
TEST_ASSERT_EQUAL(0, close(eth_tap_fd));
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL));
ethernet_deinit(&eth_network_hndls);
}
#endif // SOC_EMAC_IEEE1588V2_SUPPORTED
/* ============================================================================= */
/**
* @brief Verifies proper functionality of ioctl RCV_FILTER option
@ -1063,10 +1342,18 @@ TEST_CASE("esp32 l2tap - fcntl", "[ethernet]")
TEST_ASSERT_EQUAL(0, loop_cnt);
// Try to use unsupported operation
int new_fd = fcntl(eth_tap_fd, F_DUPFD, 0);
TEST_ASSERT_EQUAL(-1, new_fd);
flags = fcntl(eth_tap_fd, F_DUPFD, 0);
TEST_ASSERT_EQUAL(-1, flags);
TEST_ASSERT_EQUAL(ENOSYS, errno);
// Try to set unsupported flag
flags = fcntl(eth_tap_fd, F_SETFL, O_TRUNC);
TEST_ASSERT_EQUAL(-1, flags);
TEST_ASSERT_EQUAL(EINVAL, errno);
flags = fcntl(eth_tap_fd, F_SETFL, O_TRUNC | O_NONBLOCK);
TEST_ASSERT_EQUAL(-1, flags);
TEST_ASSERT_EQUAL(EINVAL, errno);
TEST_ASSERT_EQUAL(0, close(eth_tap_fd));
vTaskDelay(pdMS_TO_TICKS(50)); // just for sure to give some time to send task close fd

View File

@ -6,5 +6,17 @@ from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.ethernet
@pytest.mark.parametrize('config', [
'defaults',
], indirect=True)
def test_esp_netif_vfs_l2tp(dut: Dut) -> None:
dut.run_all_single_board_cases()
@pytest.mark.esp32p4
@pytest.mark.eth_ip101
@pytest.mark.parametrize('config', [
'defaults',
], indirect=True)
def test_esp_netif_vfs_l2tp_p4(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -9,6 +9,7 @@
#include <sys/fcntl.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <time.h>
#include "arpa/inet.h" // for ntohs, etc.
#include "errno.h"
@ -25,7 +26,6 @@
#include "freertos/semphr.h"
#include "freertos/queue.h"
#define INVALID_FD (-1)
#define L2TAP_MAX_FDS CONFIG_ESP_NETIF_L2_TAP_MAX_FDS
@ -33,25 +33,35 @@
typedef enum {
L2TAP_SOCK_STATE_READY,
L2TAP_SOCK_STATE_OPENING,
L2TAP_SOCK_STATE_OPENED,
L2TAP_SOCK_STATE_CLOSING
} l2tap_socket_state_t;
typedef enum {
L2TAP_FLAG_NON_BLOCK = BIT(0),
L2TAP_FLAG_TS = BIT(1)
} l2tap_socket_flags_t;
typedef struct {
_Atomic l2tap_socket_state_t state;
bool non_blocking;
l2tap_socket_flags_t flags;
l2tap_iodriver_handle driver_handle;
uint16_t ethtype_filter;
QueueHandle_t rx_queue;
SemaphoreHandle_t close_done_sem;
esp_err_t (*driver_transmit)(l2tap_iodriver_handle io_handle, void *buffer, size_t len);
SemaphoreHandle_t close_done_sem;
union {
esp_err_t (*driver_transmit)(l2tap_iodriver_handle io_handle, void *buffer, size_t len);
esp_err_t (*driver_transmit_ctrl_vargs)(l2tap_iodriver_handle io_handle, void *ctrl, uint32_t argc, ...);
};
void (*driver_free_rx_buffer)(l2tap_iodriver_handle io_handle, void* buffer);
} l2tap_context_t;
typedef struct {
void *buff;
size_t len;
eth_mac_time_t ts;
} frame_queue_entry_t;
typedef struct {
@ -87,51 +97,90 @@ static void l2tap_select_notify(int fd, l2tap_select_notif_e select_notif);
static esp_err_t init_rx_queue(l2tap_context_t *l2tap_socket)
{
l2tap_socket->rx_queue = xQueueCreate(RX_QUEUE_MAX_SIZE, sizeof(frame_queue_entry_t));
ESP_RETURN_ON_FALSE(l2tap_socket->rx_queue, ESP_ERR_NO_MEM, TAG, "create work queue failed");
ESP_RETURN_ON_FALSE(l2tap_socket->rx_queue, ESP_ERR_NO_MEM, TAG, "create Rx queue failed");
return ESP_OK;
}
static esp_err_t push_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t len)
static esp_err_t push_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t len, eth_mac_time_t *ts)
{
frame_queue_entry_t frame_info;
frame_queue_entry_t rx_frame_info;
frame_info.buff = buff;
frame_info.len = len;
rx_frame_info.buff = buff;
rx_frame_info.len = len;
if (ts) {
rx_frame_info.ts = *ts;
}
// try send to queue and check if the queue is full
if (xQueueSend(l2tap_socket->rx_queue, &frame_info, 0) != pdTRUE) {
if (xQueueSend(l2tap_socket->rx_queue, &rx_frame_info, 0) != pdTRUE) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
static ssize_t pop_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t len)
static esp_err_t pop_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t len, ssize_t *copy_len)
{
uint8_t *copy_buff;
TickType_t timeout = portMAX_DELAY;
if (l2tap_socket->non_blocking) {
if (l2tap_socket->flags & L2TAP_FLAG_NON_BLOCK) {
timeout = 0;
}
*copy_len = -1;
frame_queue_entry_t frame_info;
if (xQueueReceive(l2tap_socket->rx_queue, &frame_info, timeout) == pdTRUE) {
frame_queue_entry_t rx_frame_info;
if (xQueueReceive(l2tap_socket->rx_queue, &rx_frame_info, timeout) == pdTRUE) {
// empty queue was issued indicating the fd is going to be closed
if (frame_info.len == 0) {
if (rx_frame_info.len == 0) {
// indicate to "clean_task" that task waiting for queue was unblocked
push_rx_queue(l2tap_socket, NULL, 0);
goto err;
push_rx_queue(l2tap_socket, NULL, 0, NULL);
*copy_len = 0;
return ESP_OK;
}
if (len > frame_info.len) {
len = frame_info.len;
// when len == 0, extended buffer is going to be used
if (len == 0) {
l2tap_extended_buff_t *ext_buff = (l2tap_extended_buff_t *)buff;
copy_buff = ext_buff->buff;
if (ext_buff->buff_len > rx_frame_info.len) {
*copy_len = rx_frame_info.len;
} else {
*copy_len = ext_buff->buff_len;
}
ext_buff->buff_len = *copy_len;
// check if fd has TS enabled
if (l2tap_socket->flags & L2TAP_FLAG_TS) {
// find the record allocated for the time stamp info
l2tap_irec_hdr_t *info_rec = L2TAP_IREC_FIRST(ext_buff);
while(info_rec != NULL) {
if (info_rec->type == L2TAP_IREC_TIME_STAMP) {
break;
}
info_rec = L2TAP_IREC_NEXT(ext_buff, info_rec);
}
if (info_rec != NULL) {
// check if there is enough space to store TS
if (info_rec->len - sizeof(l2tap_irec_hdr_t) >= sizeof(struct timespec)) {
struct timespec *ts = (struct timespec *)info_rec->data;
ts->tv_sec = rx_frame_info.ts.seconds;
ts->tv_nsec = rx_frame_info.ts.nanoseconds;
} else {
info_rec->type = L2TAP_IREC_INVALID;
}
}
}
} else {
copy_buff = buff;
if (len > rx_frame_info.len) {
*copy_len = rx_frame_info.len;
} else {
*copy_len = len;
}
}
memcpy(buff, frame_info.buff, len);
l2tap_socket->driver_free_rx_buffer(l2tap_socket->driver_handle, frame_info.buff);
memcpy(copy_buff, rx_frame_info.buff, *copy_len);
l2tap_socket->driver_free_rx_buffer(l2tap_socket->driver_handle, rx_frame_info.buff);
} else {
goto err;
return ESP_ERR_TIMEOUT;
}
return len;
err:
return -1;
return ESP_OK;
}
static bool rx_queue_empty(l2tap_context_t *l2tap_socket)
@ -141,10 +190,10 @@ static bool rx_queue_empty(l2tap_context_t *l2tap_socket)
static void flush_rx_queue(l2tap_context_t *l2tap_socket)
{
frame_queue_entry_t frame_info;
while (xQueueReceive(l2tap_socket->rx_queue, &frame_info, 0) == pdTRUE) {
if (frame_info.len > 0) {
free(frame_info.buff);
frame_queue_entry_t rx_frame_info;
while (xQueueReceive(l2tap_socket->rx_queue, &rx_frame_info, 0) == pdTRUE) {
if (rx_frame_info.len > 0) {
free(rx_frame_info.buff);
}
}
}
@ -155,12 +204,12 @@ static void delete_rx_queue(l2tap_context_t *l2tap_socket)
l2tap_socket->rx_queue = NULL;
}
static inline void l2tap_lock(void)
static inline void l2tap_enter_critical(void)
{
portENTER_CRITICAL(&s_critical_section_lock);
}
static inline void l2tap_unlock(void)
static inline void l2tap_exit_critical(void)
{
portEXIT_CRITICAL(&s_critical_section_lock);
}
@ -171,32 +220,38 @@ static inline void default_free_rx_buffer(l2tap_iodriver_handle io_handle, void*
}
/* ================== ESP NETIF L2 TAP intf ====================== */
esp_err_t esp_vfs_l2tap_eth_filter(l2tap_iodriver_handle driver_handle, void *buff, size_t *size)
esp_err_t esp_vfs_l2tap_eth_filter_frame(l2tap_iodriver_handle driver_handle, void *buff, size_t *size, void *info)
{
struct eth_hdr *eth_header = buff;
uint16_t eth_type = ntohs(eth_header->type);
for (int i = 0; i < L2TAP_MAX_FDS; i++) {
if (atomic_load(&s_l2tap_sockets[i].state) == L2TAP_SOCK_STATE_OPENED) {
l2tap_lock(); // read of socket config needs to be atomic since it can be manipulated from other task
l2tap_enter_critical(); // read of socket config needs to be atomic since it can be manipulated from other task
if (s_l2tap_sockets[i].driver_handle == driver_handle && (s_l2tap_sockets[i].ethtype_filter == eth_type ||
// IEEE 802.2 Frame is identified based on its length which is less than IEEE802.3 max length (Ethernet II Types IDs start over this value)
// Note that IEEE 802.2 LLC resolution is expected to be performed by upper stream app
(s_l2tap_sockets[i].ethtype_filter <= ETH_IEEE802_3_MAX_LEN && eth_type <= ETH_IEEE802_3_MAX_LEN))) {
l2tap_unlock();
if (push_rx_queue(&s_l2tap_sockets[i], buff, *size) != ESP_OK) {
l2tap_exit_critical();
eth_mac_time_t *ts;
if (s_l2tap_sockets[i].flags & L2TAP_FLAG_TS) {
ts = (eth_mac_time_t *)info;
} else {
ts = NULL;
}
if (push_rx_queue(&s_l2tap_sockets[i], buff, *size, ts) != ESP_OK) {
// just tail drop when queue is full
s_l2tap_sockets[i].driver_free_rx_buffer(s_l2tap_sockets[i].driver_handle, buff);
ESP_LOGD(TAG, "fd %d rx queue is full", i);
}
l2tap_lock();
l2tap_enter_critical();
if (s_registered_select_cnt) {
l2tap_select_notify(i, L2TAP_SELECT_READ_NOTIF);
}
l2tap_unlock();
l2tap_exit_critical();
*size = 0; // the frame is not passed to IP stack when size set to 0
} else {
l2tap_unlock();
l2tap_exit_critical();
}
}
}
@ -212,44 +267,118 @@ static int l2tap_open(const char *path, int flags, int mode)
for (fd = 0; fd < L2TAP_MAX_FDS; fd++) {
l2tap_socket_state_t exp_state = L2TAP_SOCK_STATE_READY;
if (atomic_compare_exchange_strong(&s_l2tap_sockets[fd].state, &exp_state,
L2TAP_SOCK_STATE_OPENED)) {
L2TAP_SOCK_STATE_OPENING)) {
if (init_rx_queue(&s_l2tap_sockets[fd]) != ESP_OK) {
atomic_store(&s_l2tap_sockets[fd].state, L2TAP_SOCK_STATE_READY);
goto err;
}
s_l2tap_sockets[fd].ethtype_filter = 0x0;
s_l2tap_sockets[fd].flags = 0;
s_l2tap_sockets[fd].driver_handle = NULL;
s_l2tap_sockets[fd].non_blocking = ((flags & O_NONBLOCK) == O_NONBLOCK);
s_l2tap_sockets[fd].flags |= ((flags & O_NONBLOCK) == O_NONBLOCK) ? L2TAP_FLAG_NON_BLOCK : 0;
s_l2tap_sockets[fd].driver_transmit = esp_eth_transmit;
s_l2tap_sockets[fd].driver_free_rx_buffer = default_free_rx_buffer;
atomic_store(&s_l2tap_sockets[fd].state, L2TAP_SOCK_STATE_OPENED);
return fd;
}
}
err:
if (fd < L2TAP_MAX_FDS) {
if (s_l2tap_sockets[fd].rx_queue) {
delete_rx_queue(&s_l2tap_sockets[fd]);
}
atomic_store(&s_l2tap_sockets[fd].state, L2TAP_SOCK_STATE_READY);
}
return INVALID_FD;
}
static int l2tap_tx_esp_err_to_errno(esp_err_t esp_err)
{
switch(esp_err) {
case ESP_ERR_INVALID_ARG:
return EINVAL;
case ESP_ERR_TIMEOUT:
return EBUSY;
case ESP_ERR_NO_MEM:
return ENOBUFS;
case ESP_ERR_INVALID_STATE:
// fall through
default:
return EIO;
}
}
static ssize_t l2tap_write(int fd, const void *data, size_t size)
{
void *eth_buff;
l2tap_extended_buff_t *ext_buff;
ssize_t ret = -1;
esp_err_t esp_ret;
// for certain fd modes, size 0 indicates to use a size from extended buffer header
int flags_set = s_l2tap_sockets[fd].flags & L2TAP_FLAG_TS;
if ((flags_set && size != 0) || (!flags_set && size == 0)) {
if (flags_set) {
// Invalid argument
errno = EINVAL;
return -1;
} else {
return 0;
}
}
if (size == 0) {
return 0;
ext_buff = (l2tap_extended_buff_t *)data;
// check if extended buffer holds pointer to valid IO frame buffer
if (ext_buff->buff == NULL) {
errno = EFAULT;
goto err;
}
eth_buff = ext_buff->buff;
size = ext_buff->buff_len;
} else {
eth_buff = (void *)data;
ext_buff = NULL;
}
if (atomic_load(&s_l2tap_sockets[fd].state) == L2TAP_SOCK_STATE_OPENED) {
if (s_l2tap_sockets[fd].ethtype_filter > ETH_IEEE802_3_MAX_LEN &&
((struct eth_hdr *)data)->type != htons(s_l2tap_sockets[fd].ethtype_filter)) {
((struct eth_hdr *)eth_buff)->type != htons(s_l2tap_sockets[fd].ethtype_filter)) {
// bad message
errno = EBADMSG;
goto err;
}
if (s_l2tap_sockets[fd].driver_transmit(s_l2tap_sockets[fd].driver_handle, (void *)data, size) == ESP_OK) {
ret = size;
if (s_l2tap_sockets[fd].flags & L2TAP_FLAG_TS) {
eth_mac_time_t eth_ts;
if ((esp_ret = s_l2tap_sockets[fd].driver_transmit_ctrl_vargs(s_l2tap_sockets[fd].driver_handle, &eth_ts, 2, eth_buff, size)) == ESP_OK){
// find the record allocated for the time stamp info
l2tap_irec_hdr_t *info_rec = L2TAP_IREC_FIRST(ext_buff);
while(info_rec != NULL) {
if (info_rec->type == L2TAP_IREC_TIME_STAMP) {
break;
}
info_rec = L2TAP_IREC_NEXT(ext_buff, info_rec);
}
// if there is a record to retrieve time stamp
if (info_rec != NULL) {
if (info_rec->len - sizeof(l2tap_irec_hdr_t) >= sizeof(struct timespec)) {
struct timespec *ts = (struct timespec *)info_rec->data;
ts->tv_sec = eth_ts.seconds;
ts->tv_nsec = eth_ts.nanoseconds;
} else {
info_rec->type = L2TAP_IREC_INVALID;
}
}
ret = size;
} else {
errno = l2tap_tx_esp_err_to_errno(esp_ret);
}
} else {
// I/O error
errno = EIO;
if ((esp_ret = s_l2tap_sockets[fd].driver_transmit(s_l2tap_sockets[fd].driver_handle, eth_buff, size)) == ESP_OK) {
ret = size;
} else {
errno = l2tap_tx_esp_err_to_errno(esp_ret);
}
}
} else {
// bad file desc
@ -259,22 +388,54 @@ err:
return ret;
}
static int l2tap_rx_esp_err_to_errno(esp_err_t esp_err)
{
switch(esp_err) {
case ESP_ERR_INVALID_ARG:
return EINVAL;
case ESP_ERR_TIMEOUT:
return EAGAIN;
case ESP_ERR_INVALID_STATE:
return EPERM;
default:
return EIO;
}
}
static ssize_t l2tap_read(int fd, void *data, size_t size)
{
// fd might be in process of closing (close was already called but preempted)
// fd might be in process of opening/closing (close was already called but preempted)
if (atomic_load(&s_l2tap_sockets[fd].state) != L2TAP_SOCK_STATE_OPENED) {
// bad file desc
errno = EBADF;
return -1;
}
if (size == 0) {
return 0;
// for certain fd modes, size 0 indicates to use a size from extended buffer header
int flags_set = s_l2tap_sockets[fd].flags & L2TAP_FLAG_TS;
if ((flags_set && size != 0) || (!flags_set && size == 0)) {
if (flags_set) {
// Invalid argument
errno = EINVAL;
return -1;
} else {
return 0;
}
}
ssize_t actual_size = -1;
if ((actual_size = pop_rx_queue(&s_l2tap_sockets[fd], data, size)) < 0) {
errno = EAGAIN;
if (size == 0) {
l2tap_extended_buff_t *ext_buff = (l2tap_extended_buff_t *)data;
// check if extended buffer holds pointer to valid IO frame buffer
if (ext_buff->buff == NULL) {
errno = EFAULT;
return -1;
}
}
esp_err_t esp_ret;
ssize_t actual_size;
if ((esp_ret = pop_rx_queue(&s_l2tap_sockets[fd], data, size, &actual_size)) != ESP_OK) {
errno = l2tap_rx_esp_err_to_errno(esp_ret);
}
return actual_size;
@ -284,14 +445,17 @@ void l2tap_clean_task(void *task_param)
{
l2tap_context_t *l2tap_socket = (l2tap_context_t *)task_param;
// push empty queue to unblock possibly blocking task
push_rx_queue(l2tap_socket, NULL, 0);
// wait for the indication that blocking task was executed (unblocked)
pop_rx_queue(l2tap_socket, NULL, 0);
// now, all higher priority tasks should finished their execution and new accesses to the queue were prevended
// by L2TAP_SOCK_STATE_CLOSING => we are free to free queue resources
// flush queued frames to not affect "empty queue" signalling below
flush_rx_queue(l2tap_socket);
// push empty queue to unblock possibly blocking task
push_rx_queue(l2tap_socket, NULL, 0, NULL);
// wait for the indication that blocking task was executed (unblocked)
ssize_t actual_size;
pop_rx_queue(l2tap_socket, NULL, 0, &actual_size);
// now, all higher priority tasks should finished their execution and new accesses to the queue were prevented
// by L2TAP_SOCK_STATE_CLOSING => we are free to free queue resources
delete_rx_queue(l2tap_socket);
// unblock task which originally called close
@ -343,14 +507,14 @@ static int l2tap_ioctl(int fd, int cmd, va_list args)
{
esp_netif_t *esp_netif;
switch (cmd) {
case L2TAP_S_RCV_FILTER: ;
case L2TAP_S_RCV_FILTER:{
uint16_t *new_ethtype_filter = va_arg(args, uint16_t *);
l2tap_lock();
l2tap_enter_critical();
// socket needs to be assigned to interface at first
if (s_l2tap_sockets[fd].driver_handle == NULL) {
// Permission denied (filter change is denied at this state)
errno = EACCES;
l2tap_unlock();
l2tap_exit_critical();
goto err;
}
// do nothing when same filter is to be set
@ -362,19 +526,21 @@ static int l2tap_ioctl(int fd, int cmd, va_list args)
s_l2tap_sockets[i].ethtype_filter == *new_ethtype_filter) {
// invalid argument
errno = EINVAL;
l2tap_unlock();
l2tap_exit_critical();
goto err;
}
}
s_l2tap_sockets[fd].ethtype_filter = *new_ethtype_filter;
}
l2tap_unlock();
l2tap_exit_critical();
break;
case L2TAP_G_RCV_FILTER: ;
}
case L2TAP_G_RCV_FILTER:{
uint16_t *ethtype_filter_dest = va_arg(args, uint16_t *);
*ethtype_filter_dest = s_l2tap_sockets[fd].ethtype_filter;
break;
case L2TAP_S_INTF_DEVICE: ;
}
case L2TAP_S_INTF_DEVICE:{
const char *str = va_arg(args, const char *);
esp_netif = esp_netif_get_handle_from_ifkey(str);
if (esp_netif == NULL) {
@ -382,32 +548,42 @@ static int l2tap_ioctl(int fd, int cmd, va_list args)
errno = ENODEV;
goto err;
}
l2tap_lock();
l2tap_enter_critical();
s_l2tap_sockets[fd].driver_handle = esp_netif_get_io_driver(esp_netif);
l2tap_unlock();
l2tap_exit_critical();
break;
case L2TAP_G_INTF_DEVICE: ;
}
case L2TAP_G_INTF_DEVICE:{
const char **str_p = va_arg(args, const char **);
*str_p = NULL;
if ((esp_netif = esp_netif_find_if(netif_driver_matches, s_l2tap_sockets[fd].driver_handle)) != NULL) {
*str_p = esp_netif_get_ifkey(esp_netif);
}
break;
case L2TAP_S_DEVICE_DRV_HNDL: ;
}
case L2TAP_S_DEVICE_DRV_HNDL:{
l2tap_iodriver_handle set_driver_hdl = va_arg(args, l2tap_iodriver_handle);
if (set_driver_hdl == NULL) {
// No such device (not valid driver handle)
errno = ENODEV;
goto err;
}
l2tap_lock();
l2tap_enter_critical();
s_l2tap_sockets[fd].driver_handle = set_driver_hdl;
l2tap_unlock();
l2tap_exit_critical();
break;
case L2TAP_G_DEVICE_DRV_HNDL: ;
}
case L2TAP_G_DEVICE_DRV_HNDL:{
l2tap_iodriver_handle *get_driver_hdl = va_arg(args, l2tap_iodriver_handle*);
*get_driver_hdl = s_l2tap_sockets[fd].driver_handle;
break;
}
case L2TAP_S_TIMESTAMP_EN:
l2tap_enter_critical();
s_l2tap_sockets[fd].flags |= L2TAP_FLAG_TS;
s_l2tap_sockets[fd].driver_transmit_ctrl_vargs = esp_eth_transmit_ctrl_vargs;
l2tap_exit_critical();
break;
default:
// unsupported operation
errno = ENOSYS;
@ -421,15 +597,32 @@ err:
return -1;
}
static void l2tap_set_nonblocking(l2tap_context_t *l2tap_socket, bool nonblock)
{
l2tap_enter_critical();
if (nonblock) {
l2tap_socket->flags |= L2TAP_FLAG_NON_BLOCK;
} else {
l2tap_socket->flags &= ~L2TAP_FLAG_NON_BLOCK;
}
l2tap_exit_critical();
}
static int l2tap_fcntl(int fd, int cmd, int arg)
{
int result = 0;
if (cmd == F_GETFL) {
if (s_l2tap_sockets[fd].non_blocking) {
if (s_l2tap_sockets[fd].flags & L2TAP_FLAG_NON_BLOCK) {
result |= O_NONBLOCK;
}
} else if (cmd == F_SETFL) {
s_l2tap_sockets[fd].non_blocking = (arg & O_NONBLOCK) != 0;
// only O_NONBLOCK is supported
if ((arg & ~O_NONBLOCK) == 0) {
l2tap_set_nonblocking(&s_l2tap_sockets[fd], (arg & O_NONBLOCK) == O_NONBLOCK);
} else {
result = -1;
errno = EINVAL;
}
} else {
// unsupported operation
result = -1;
@ -537,7 +730,7 @@ static esp_err_t l2tap_start_select(int nfds, fd_set *readfds, fd_set *writefds,
FD_ZERO(writefds);
FD_ZERO(exceptfds);
l2tap_lock();
l2tap_enter_critical();
for (int i = 0; i < max_fds; i++) {
if (FD_ISSET(i, &args->readfds_orig)) {
@ -551,12 +744,12 @@ static esp_err_t l2tap_start_select(int nfds, fd_set *readfds, fd_set *writefds,
esp_err_t ret = register_select(args);
if (ret != ESP_OK) {
l2tap_unlock();
l2tap_exit_critical();
free(args);
return ret;
}
l2tap_unlock();
l2tap_exit_critical();
*end_select_args = args;
@ -570,9 +763,9 @@ static esp_err_t l2tap_end_select(void *end_select_args)
return ESP_OK;
}
l2tap_lock();
l2tap_enter_critical();
esp_err_t ret = unregister_select(args);
l2tap_unlock();
l2tap_exit_critical();
if (args) {
free(args);

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -8,6 +8,10 @@
#include "esp_attr.h"
#include "hal/emac_hal.h"
#include "hal/emac_ll.h"
#if SOC_EMAC_IEEE1588V2_SUPPORTED
#include "esp_rom_sys.h"
#define EMAC_PTP_INIT_TIMEOUT_US (10)
#endif // SOC_EMAC_IEEE1588V2_SUPPORTED
static esp_err_t emac_hal_flush_trans_fifo(emac_hal_context_t *hal)
{
@ -27,6 +31,11 @@ void emac_hal_init(emac_hal_context_t *hal)
hal->mac_regs = &EMAC_MAC;
#if CONFIG_IDF_TARGET_ESP32
hal->ext_regs = &EMAC_EXT;
#else
hal->ext_regs = NULL;
#endif
#if SOC_EMAC_IEEE1588V2_SUPPORTED
hal->ptp_regs = &EMAC_PTP;
#endif
}
@ -75,11 +84,11 @@ void emac_hal_init_mac_default(emac_hal_context_t *hal)
emac_ll_set_duplex(hal->mac_regs, ETH_DUPLEX_FULL);
/* Select the checksum mode for received frame payload's TCP/UDP/ICMP headers */
emac_ll_checksum_offload_mode(hal->mac_regs, ETH_CHECKSUM_HW);
/* Enable MAC retry transmission when a colision occurs in half duplex mode */
/* Enable MAC retry transmission when a collision occurs in half duplex mode */
emac_ll_retry_enable(hal->mac_regs, true);
/* MAC passes all incoming frames to host, without modifying them */
emac_ll_auto_pad_crc_strip_enable(hal->mac_regs, false);
/* Set Back-Off limit time before retry a transmittion after a collision */
/* Set Back-Off limit time before retry a transmission after a collision */
emac_ll_set_back_off_limit(hal->mac_regs, EMAC_LL_BACKOFF_LIMIT_10);
/* Disable deferral check, MAC defers until the CRS signal goes inactive */
emac_ll_deferral_check_enable(hal->mac_regs, false);
@ -193,6 +202,249 @@ void emac_hal_set_address(emac_hal_context_t *hal, uint8_t *mac_addr)
}
}
#if SOC_EMAC_IEEE1588V2_SUPPORTED
static inline uint32_t subsecond2nanosecond(emac_hal_context_t *hal, uint32_t subsecond)
{
if (emac_ll_is_ts_digital_roll_set(hal->ptp_regs)) {
return subsecond;
}
uint64_t val = subsecond * 1000000000ll; // 1 s = 10e9 ns
val >>= 31; // Sub-Second register is 31 bit
return (uint32_t)val;
}
static inline uint32_t nanosecond2subsecond(emac_hal_context_t *hal, uint32_t nanosecond)
{
if (emac_ll_is_ts_digital_roll_set(hal->ptp_regs)) {
return nanosecond;
}
uint64_t val = (uint64_t)nanosecond << 31;
val /= 1000000000ll;
return (uint32_t)val;
}
esp_err_t emac_hal_get_rxdesc_timestamp(emac_hal_context_t *hal, eth_dma_rx_descriptor_t *rxdesc, uint32_t *seconds, uint32_t *nano_seconds)
{
if (!rxdesc->RDES0.TSAvailIPChecksumErrGiantFrame) {
return ESP_ERR_INVALID_STATE;
}
if (seconds) {
*seconds = rxdesc->TimeStampHigh;
}
if (nano_seconds) {
*nano_seconds = subsecond2nanosecond(hal, rxdesc->TimeStampLow);
}
rxdesc->RDES0.TSAvailIPChecksumErrGiantFrame = 0;
return ESP_OK;
}
esp_err_t emac_hal_get_txdesc_timestamp(emac_hal_context_t *hal, eth_dma_tx_descriptor_t *txdesc, uint32_t *seconds, uint32_t *nano_seconds)
{
if (txdesc->TDES0.Own == EMAC_LL_DMADESC_OWNER_DMA || !txdesc->TDES0.TxTimestampStatus) {
return ESP_ERR_INVALID_STATE;
}
if (seconds) {
*seconds = txdesc->TimeStampHigh;
}
if (nano_seconds) {
*nano_seconds = subsecond2nanosecond(hal, txdesc->TimeStampLow);
}
txdesc->TDES0.TxTimestampStatus = 0;
return ESP_OK;
}
esp_err_t emac_hal_ptp_start(emac_hal_context_t *hal, const emac_hal_ptp_config_t *config)
{
uint8_t base_increment;
// Enable time stamping frame filtering (applicable to receive)
emac_ll_ts_ptp_ether_enable(hal->ptp_regs, true);
// Process frames with v2 format
emac_ll_ptp_v2_proc_enable(hal->ptp_regs, true);
/* Un-mask the Time stamp trigger interrupt */
emac_ll_enable_corresponding_emac_intr(hal->mac_regs, EMAC_LL_CONFIG_ENABLE_MAC_INTR_MASK);
/* Enable the timestamp feature */
emac_ll_ts_enable(hal->ptp_regs, true);
/* Set digital or binary rollover */
if (config->roll == ETH_PTP_DIGITAL_ROLLOVER) {
emac_ll_ts_digital_roll_enable(hal->ptp_regs, true);
} else {
emac_ll_ts_digital_roll_enable(hal->ptp_regs, false);
}
/* Set sub second increment based on the required PTP accuracy */
if (emac_ll_is_ts_digital_roll_set(hal->ptp_regs)) {
/**
* tick(ns) 10^9
* = ==> Increment = tick
* Increment 10^9
*/
base_increment = config->ptp_req_accuracy_ns;
} else {
/**
* tick(ns) 10^9 tick * 2^31 tick
* = ==> Increment =
* Increment 2^31 10^9 0.465
*/
base_increment = config->ptp_req_accuracy_ns / 0.465;
}
emac_ll_set_ts_sub_second_incre_val(hal->ptp_regs, base_increment);
/* Set Update Mode */
emac_ll_set_ts_update_method(hal->ptp_regs, config->upd_method);
int32_t to = 0;
/* If you are using the Fine correction method */
if (config->upd_method == ETH_PTP_UPDATE_METHOD_FINE) {
/**
* 2^32 2^32 TsysClk(ns)
* Addend = = = 2^32 *
* ratio SysClk(MHz)/PTPaccur(MHz) Taccur(ns)
*/
uint32_t base_addend = (1ll << 32) * config->ptp_clk_src_period_ns / config->ptp_req_accuracy_ns;
emac_ll_set_ts_addend_val(hal->ptp_regs, base_addend);
emac_ll_ts_addend_do_update(hal->ptp_regs);
while (!emac_ll_is_ts_addend_update_done(hal->ptp_regs) && to < EMAC_PTP_INIT_TIMEOUT_US) {
esp_rom_delay_us(1);
to++;
}
if (to >= EMAC_PTP_INIT_TIMEOUT_US) {
return ESP_ERR_TIMEOUT;
}
}
/* Initialize timestamp */
emac_ll_set_ts_update_second_val(hal->ptp_regs, 0);
emac_ll_set_ts_update_sub_second_val(hal->ptp_regs, 0);
emac_ll_ts_init_do(hal->ptp_regs);
to = 0;
while (!emac_ll_is_ts_init_done(hal->ptp_regs) && to < EMAC_PTP_INIT_TIMEOUT_US) {
esp_rom_delay_us(1);
to++;
}
if (to >= EMAC_PTP_INIT_TIMEOUT_US) {
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
}
esp_err_t emac_hal_ptp_stop(emac_hal_context_t *hal)
{
/* Disable the timestamp feature */
emac_ll_ts_enable(hal->ptp_regs, false);
return ESP_OK;
}
esp_err_t emac_hal_ptp_adj_inc(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;
}
/**
* Sysclk(MHz) * ppb Sysclk * ppb
* var = =
* 10^9 10^9
*
* 2^32 * PTPClk(MHz) 2^32 * PTPClk(MHz)
* old = => SysClk =
* SysClk(MHz) old
*
* 2^32 * PTPClk(MHz) 2^32 * PTPClk(MHz) 2^32 * PTPClk(MHz)
* new = = = =
* SysClk(MHz) - var Sysclk * ppb 2^32 * PTPClk(MHz) ( ppb )
* SysClk - - (1 - )
* 10^9 old ( 10^9 )
*
* old old * 10^9
* = =
* ppb 10^9 - ppb
* 1 -
* 10^9
*/
static uint32_t addend_base = 0;
if (addend_base == 0) {
addend_base = emac_ll_get_ts_addend_val(hal->ptp_regs);
}
if (adj_ppb > 5120000) {
adj_ppb = 5120000;
}
if (adj_ppb < -5120000) {
adj_ppb = -5120000;
}
/* calculate the rate by which you want to speed up or slow down the system time increments */
int64_t addend_new = (int64_t)addend_base * 1000000000ll;
addend_new /= 1000000000ll - adj_ppb;
emac_ll_set_ts_addend_val(hal->ptp_regs, addend_new);
emac_ll_ts_addend_do_update(hal->ptp_regs);
return ESP_OK;
}
esp_err_t emac_hal_adj_freq_factor(emac_hal_context_t *hal, double scale_factor)
{
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 addend_new = (emac_ll_get_ts_addend_val(hal->ptp_regs) * scale_factor);
emac_ll_set_ts_addend_val(hal->ptp_regs, 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);
emac_ll_set_ts_update_sub_second_val(hal->ptp_regs, nanosecond2subsecond(hal, off_nsec));
if (sign) {
emac_ll_ts_update_time_add(hal->ptp_regs);
} else {
emac_ll_ts_update_time_sub(hal->ptp_regs);
}
if (!emac_ll_is_ts_update_time_done(hal->ptp_regs)) {
return ESP_ERR_INVALID_STATE;
}
emac_ll_ts_update_time_do(hal->ptp_regs);
return ESP_OK;
}
esp_err_t emac_hal_ptp_set_sys_time(emac_hal_context_t *hal, uint32_t seconds, uint32_t nano_seconds)
{
emac_ll_set_ts_update_second_val(hal->ptp_regs, seconds);
emac_ll_set_ts_update_sub_second_val(hal->ptp_regs, nanosecond2subsecond(hal, nano_seconds));
if (!emac_ll_is_ts_init_done(hal->ptp_regs)) {
return ESP_ERR_INVALID_STATE;
}
emac_ll_ts_init_do(hal->ptp_regs);
return ESP_OK;
}
esp_err_t emac_hal_ptp_get_sys_time(emac_hal_context_t *hal, uint32_t *seconds, uint32_t *nano_seconds)
{
if (seconds == NULL || nano_seconds == NULL) {
return ESP_ERR_INVALID_ARG;
}
*seconds = emac_ll_get_ts_seconds_val(hal->ptp_regs);
*nano_seconds = subsecond2nanosecond(hal, emac_ll_get_ts_sub_seconds_val(hal->ptp_regs));
return ESP_OK;
}
esp_err_t emac_hal_ptp_set_target_time(emac_hal_context_t *hal, uint32_t seconds, uint32_t nano_seconds)
{
emac_ll_set_ts_target_second_val(hal->ptp_regs, seconds);
emac_ll_set_ts_target_sub_second_val(hal->ptp_regs, nanosecond2subsecond(hal, nano_seconds));
/* Enable the PTP Time Stamp interrupt trigger */
emac_ll_ts_target_int_trig_enable(hal->ptp_regs);
return ESP_OK;
}
#endif // SOC_EMAC_IEEE1588V2_SUPPORTED
void emac_hal_start(emac_hal_context_t *hal)
{
/* Enable Ethernet MAC and DMA Interrupt */

View File

@ -88,19 +88,6 @@ extern "C" {
#define EMAC_LL_DMA_ARBITRATION_ROUNDROBIN_RXTX_3_1 (2)
#define EMAC_LL_DMA_ARBITRATION_ROUNDROBIN_RXTX_4_1 (3)
/* PTP register bits */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_SYNC 0x00000100U /* SYNC message (all clock types) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_FOLLOWUP 0x00000200U /* FollowUp message (all clock types) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_DELAYREQ 0x00000300U /* DelayReq message (all clock types) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_DELAYRESP 0x00000400U /* DelayResp message (all clock types) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_PDELAYREQ_ANNOUNCE 0x00000500U /* PdelayReq message (peer-to-peer transparent clock) or Announce message (Ordinary or Boundary clock) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_PDELAYRESP_MANAG 0x00000600U /* PdelayResp message (peer-to-peer transparent clock) or Management message (Ordinary or Boundary clock) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_PDELAYRESPFOLLOWUP_SIGNAL 0x00000700U /* PdelayRespFollowUp message (peer-to-peer transparent clock) or Signaling message (Ordinary or Boundary clock) */
#define EMAC_LL_DMAPTPRXDESC_IPPT_UDP 0x00000001U /* UDP payload encapsulated in the IP datagram */
#define EMAC_LL_DMAPTPRXDESC_IPPT_TCP 0x00000002U /* TCP payload encapsulated in the IP datagram */
#define EMAC_LL_DMAPTPRXDESC_IPPT_ICMP 0x00000003U /* ICMP payload encapsulated in the IP datagram */
#define EMAC_LL_DMADESC_OWNER_CPU (0)
#define EMAC_LL_DMADESC_OWNER_DMA (1)

View File

@ -20,6 +20,8 @@
#include "hal/eth_types.h"
#include "soc/emac_dma_struct.h"
#include "soc/emac_mac_struct.h"
#include "soc/emac_ptp_struct.h"
#include "soc/clk_tree_defs.h"
#include "soc/hp_system_struct.h"
#include "soc/hp_sys_clkrst_struct.h"
@ -90,22 +92,14 @@ extern "C" {
#define EMAC_LL_DMA_ARBITRATION_ROUNDROBIN_RXTX_3_1 (2)
#define EMAC_LL_DMA_ARBITRATION_ROUNDROBIN_RXTX_4_1 (3)
/* PTP register bits */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_SYNC 0x00000100U /* SYNC message (all clock types) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_FOLLOWUP 0x00000200U /* FollowUp message (all clock types) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_DELAYREQ 0x00000300U /* DelayReq message (all clock types) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_DELAYRESP 0x00000400U /* DelayResp message (all clock types) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_PDELAYREQ_ANNOUNCE 0x00000500U /* PdelayReq message (peer-to-peer transparent clock) or Announce message (Ordinary or Boundary clock) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_PDELAYRESP_MANAG 0x00000600U /* PdelayResp message (peer-to-peer transparent clock) or Management message (Ordinary or Boundary clock) */
#define EMAC_LL_DMAPTPRXDESC_PTPMT_PDELAYRESPFOLLOWUP_SIGNAL 0x00000700U /* PdelayRespFollowUp message (peer-to-peer transparent clock) or Signaling message (Ordinary or Boundary clock) */
#define EMAC_LL_DMAPTPRXDESC_IPPT_UDP 0x00000001U /* UDP payload encapsulated in the IP datagram */
#define EMAC_LL_DMAPTPRXDESC_IPPT_TCP 0x00000002U /* TCP payload encapsulated in the IP datagram */
#define EMAC_LL_DMAPTPRXDESC_IPPT_ICMP 0x00000003U /* ICMP payload encapsulated in the IP datagram */
#define EMAC_LL_DMADESC_OWNER_CPU (0)
#define EMAC_LL_DMADESC_OWNER_DMA (1)
/* Time stamp status flags */
#define EMAC_LL_TS_SECONDS_OVERFLOW 0x00000001U
#define EMAC_LL_TS_TARGET_TIME_REACHED 0x00000002U
#define EMAC_LL_TS_TARGET_TIME_ERROR 0x00000008U
/* Interrupt flags (referring to dmastatus register in emac_dma_struct.h) */
#define EMAC_LL_DMA_TRANSMIT_FINISH_INTR 0x00000001U
#define EMAC_LL_DMA_TRANSMIT_STOP_INTR 0x00000002U
@ -125,7 +119,7 @@ extern "C" {
#define EMAC_LL_DMA_POWER_MANAGE_INTR 0x10000000U
#define EMAC_LL_DMA_TIMESTAMP_TRIGGER_INTR 0x20000000U
/* Interrupt enable (referring to dmain_en register in emac_dma_struct.h) */
/* DMA Interrupt enable (referring to dmain_en register in emac_dma_struct.h) */
#define EMAC_LL_INTR_TRANSMIT_ENABLE 0x00000001U
#define EMAC_LL_INTR_TRANSMIT_STOP_ENABLE 0x00000002U
#define EMAC_LL_INTR_TRANSMIT_BUFF_UNAVAILABLE_ENABLE 0x00000004U
@ -142,9 +136,17 @@ extern "C" {
#define EMAC_LL_INTR_ABNORMAL_SUMMARY_ENABLE 0x00008000U
#define EMAC_LL_INTR_NORMAL_SUMMARY_ENABLE 0x00010000U
/* Enable needed interrupts (recv/recv_buf_unavailabal/normal must be enabled to make eth work) */
/* EMAC interrupt enable (referring to emacintmask register in emac_mac_struct.h)*/
#define EMAC_LL_MAC_INTR_LOW_POWER_IDLE_ENABLE 0x00000400U
#define EMAC_LL_MAC_INTR_TIME_STAMP_ENABLE 0x00000200U
#define EMAC_LL_MAC_INTR_POWER_MANAGEMENT_MOD_ENABLE 0x00000008U
/* Enable needed DMA interrupts (recv/recv_buf_unavailabal/normal must be enabled to make eth work) */
#define EMAC_LL_CONFIG_ENABLE_INTR_MASK (EMAC_LL_INTR_RECEIVE_ENABLE | EMAC_LL_INTR_NORMAL_SUMMARY_ENABLE)
/* Enable needed MAC interrupts */
#define EMAC_LL_CONFIG_ENABLE_MAC_INTR_MASK (EMAC_LL_MAC_INTR_TIME_STAMP_ENABLE)
/************** Start of mac regs operation ********************/
/* emacgmiiaddr */
static inline void emac_ll_set_csr_clock_division(emac_mac_dev_t *mac_regs, uint32_t div_mode)
@ -373,6 +375,22 @@ static inline void emac_ll_set_addr(emac_mac_dev_t *mac_regs, const uint8_t *add
HAL_FORCE_MODIFY_U32_REG_FIELD(mac_regs->emacaddr0high, address0_hi, (addr[5] << 8) | addr[4]);
mac_regs->emacaddr0low = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | (addr[0]);
}
/* emacintmask */
static inline void emac_ll_enable_corresponding_emac_intr(emac_mac_dev_t *mac_regs, uint32_t mask)
{
uint32_t temp_mask = mac_regs->emacintmask.val;
temp_mask &= ~mask;
mac_regs->emacintmask.val = temp_mask;
}
static inline void emac_ll_disable_corresponding_emac_intr(emac_mac_dev_t *mac_regs, uint32_t mask)
{
uint32_t temp_mask = mac_regs->emacintmask.val;
temp_mask |= mask;
mac_regs->emacintmask.val = temp_mask;
}
/*************** End of mac regs operation *********************/
@ -535,12 +553,17 @@ static inline void emac_ll_disable_all_intr(emac_dma_dev_t *dma_regs)
static inline void emac_ll_enable_corresponding_intr(emac_dma_dev_t *dma_regs, uint32_t mask)
{
dma_regs->dmain_en.val |= mask;
uint32_t temp_mask = dma_regs->dmain_en.val;
temp_mask |= mask;
dma_regs->dmain_en.val = temp_mask;
}
static inline void emac_ll_disable_corresponding_intr(emac_dma_dev_t *dma_regs, uint32_t mask)
{
dma_regs->dmain_en.val &= ~mask;
uint32_t temp_mask = dma_regs->dmain_en.val;
temp_mask &= ~mask;
dma_regs->dmain_en.val = temp_mask;
}
static inline uint32_t emac_ll_get_intr_enable_status(emac_dma_dev_t *dma_regs)
@ -577,6 +600,175 @@ static inline void emac_ll_receive_poll_demand(emac_dma_dev_t *dma_regs, uint32_
/*************** End of dma regs operation *********************/
/************** Start of ptp regs operation ********************/
static inline uint32_t emac_ll_get_ts_status(emac_ptp_dev_t *ptp_regs)
{
return ptp_regs->status.val;
}
/* basic control and setting */
static inline void emac_ll_ts_enable(emac_ptp_dev_t *ptp_regs, bool enable)
{
ptp_regs->timestamp_ctrl.en_timestamp = enable;
}
static inline void emac_ll_ts_ptp_ip4_enable(emac_ptp_dev_t *ptp_regs, bool enable)
{
ptp_regs->timestamp_ctrl.en_proc_ptp_ipv4_udp = enable;
}
static inline void emac_ll_ts_ptp_ether_enable(emac_ptp_dev_t *ptp_regs, bool enable)
{
ptp_regs->timestamp_ctrl.en_proc_ptp_ether_frm = enable;
}
static inline void emac_ll_ts_ptp_snap_type_sel(emac_ptp_dev_t *ptp_regs, uint8_t sel)
{
ptp_regs->timestamp_ctrl.sel_snap_type = sel;
}
static inline void emac_ll_ts_ptp_snap_master_only_enable(emac_ptp_dev_t *ptp_regs, bool enable)
{
ptp_regs->timestamp_ctrl.en_snap_msg_relevant_master = enable;
}
static inline void emac_ll_ts_ptp_snap_event_only_enable(emac_ptp_dev_t *ptp_regs, bool enable)
{
ptp_regs->timestamp_ctrl.en_ts_snap_event_msg = enable;
}
static inline void emac_ll_ts_all_enable(emac_ptp_dev_t *ptp_regs, bool enable)
{
ptp_regs->timestamp_ctrl.en_ts4all = enable;
}
static inline void emac_ll_ptp_v2_proc_enable(emac_ptp_dev_t *ptp_regs, bool enable) {
ptp_regs->timestamp_ctrl.en_ptp_pkg_proc_ver2_fmt = enable;
}
static inline void emac_ll_ts_digital_roll_enable(emac_ptp_dev_t *ptp_regs, bool enable)
{
ptp_regs->timestamp_ctrl.ts_digit_bin_roll_ctrl = enable;
}
static inline bool emac_ll_is_ts_digital_roll_set(emac_ptp_dev_t *ptp_regs)
{
return ptp_regs->timestamp_ctrl.ts_digit_bin_roll_ctrl;
}
static inline void emac_ll_set_ts_update_method(emac_ptp_dev_t *ptp_regs, eth_mac_ptp_update_method_t method)
{
if (method == ETH_PTP_UPDATE_METHOD_COARSE) {
ptp_regs->timestamp_ctrl.ts_fine_coarse_update = 0;
} else {
ptp_regs->timestamp_ctrl.ts_fine_coarse_update = 1;
}
}
static inline eth_mac_ptp_update_method_t emac_ll_get_ts_update_method(emac_ptp_dev_t *ptp_regs)
{
if (ptp_regs->timestamp_ctrl.ts_fine_coarse_update == 0) {
return ETH_PTP_UPDATE_METHOD_COARSE;
}
return ETH_PTP_UPDATE_METHOD_FINE;
}
static inline void emac_ll_ts_init_do(emac_ptp_dev_t *ptp_regs)
{
ptp_regs->timestamp_ctrl.ts_initialize = 1;
}
static inline bool emac_ll_is_ts_init_done(emac_ptp_dev_t *ptp_regs)
{
return !ptp_regs->timestamp_ctrl.ts_initialize;
}
/* increment value */
static inline void emac_ll_set_ts_sub_second_incre_val(emac_ptp_dev_t *ptp_regs, uint8_t increment)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(ptp_regs->sub_sec_incre, sub_second_incre_value, increment);
}
/* addend control */
static inline void emac_ll_set_ts_addend_val(emac_ptp_dev_t *ptp_regs, uint32_t val)
{
ptp_regs->timestamp_addend.ts_addend_val = val;
}
static inline uint32_t emac_ll_get_ts_addend_val(emac_ptp_dev_t *ptp_regs)
{
return ptp_regs->timestamp_addend.ts_addend_val;
}
static inline void emac_ll_ts_addend_do_update(emac_ptp_dev_t *ptp_regs)
{
ptp_regs->timestamp_ctrl.addend_reg_update = 1;
}
static inline bool emac_ll_is_ts_addend_update_done(emac_ptp_dev_t *ptp_regs)
{
return !ptp_regs->timestamp_ctrl.addend_reg_update;
}
/* time update */
static inline void emac_ll_set_ts_update_second_val(emac_ptp_dev_t *ptp_regs, uint32_t val)
{
ptp_regs->sys_seconds_update.ts_second = val;
}
static inline void emac_ll_set_ts_update_sub_second_val(emac_ptp_dev_t *ptp_regs, uint32_t val)
{
ptp_regs->sys_nanosec_update.ts_sub_seconds = val;
}
static inline void emac_ll_ts_update_time_add(emac_ptp_dev_t *ptp_regs)
{
ptp_regs->sys_nanosec_update.add_sub = 0;
}
static inline void emac_ll_ts_update_time_sub(emac_ptp_dev_t *ptp_regs)
{
ptp_regs->sys_nanosec_update.add_sub = 1;
}
static inline void emac_ll_ts_update_time_do(emac_ptp_dev_t *ptp_regs)
{
ptp_regs->timestamp_ctrl.ts_update = 1;
}
static inline bool emac_ll_is_ts_update_time_done(emac_ptp_dev_t *ptp_regs)
{
return !ptp_regs->timestamp_ctrl.ts_update;
}
/* get time */
static inline uint32_t emac_ll_get_ts_seconds_val(emac_ptp_dev_t *ptp_regs)
{
return ptp_regs->sys_seconds.ts_second;
}
static inline uint32_t emac_ll_get_ts_sub_seconds_val(emac_ptp_dev_t *ptp_regs)
{
return ptp_regs->sys_nanosec.ts_sub_seconds;
}
/* target time control */
static inline void emac_ll_set_ts_target_second_val(emac_ptp_dev_t *ptp_regs, uint32_t val)
{
ptp_regs->tgt_seconds.tgt_time_second_val = val;
}
static inline void emac_ll_set_ts_target_sub_second_val(emac_ptp_dev_t *ptp_regs, uint32_t val)
{
ptp_regs->tgt_nanosec.tgt_ts_low_reg = val;
}
static inline void emac_ll_ts_target_int_trig_enable(emac_ptp_dev_t *ptp_regs)
{
ptp_regs->timestamp_ctrl.en_ts_int_trig = 1;
}
/************** End of ptp regs operation ********************/
/**
* @brief Enable the bus clock for the EMAC module
@ -655,7 +847,7 @@ static inline void emac_ll_clock_enable_rmii_input(void *ext_regs)
HP_SYS_CLKRST.peri_clk_ctrl00.reg_emac_rx_clk_src_sel = 0; // 0-pad_emac_txrx_clk, 1-pad_emac_rx_clk
HAL_FORCE_MODIFY_U32_REG_FIELD(HP_SYS_CLKRST.peri_clk_ctrl01, reg_emac_rx_clk_div_num, 1); // set default divider
HP_SYS_CLKRST.peri_clk_ctrl01.reg_emac_tx_clk_en = 1;
HP_SYS_CLKRST.peri_clk_ctrl01.reg_emac_tx_clk_en = 1;
HP_SYS_CLKRST.peri_clk_ctrl01.reg_emac_tx_clk_src_sel = 0; // 0-pad_emac_txrx_clk, 1-pad_emac_tx_clk
HAL_FORCE_MODIFY_U32_REG_FIELD(HP_SYS_CLKRST.peri_clk_ctrl01, reg_emac_tx_clk_div_num, 1); // set default divider
@ -689,6 +881,30 @@ static inline void emac_ll_clock_enable_rmii_output(void *ext_regs)
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define emac_ll_clock_enable_rmii_output(...) (void)__DECLARE_RCC_ATOMIC_ENV; emac_ll_clock_enable_rmii_output(__VA_ARGS__)
static inline void emac_ll_clock_enable_ptp(void *ext_regs, soc_periph_emac_ptp_clk_src_t clk_src, bool enable)
{
uint8_t clk_src_val;
switch (clk_src)
{
case EMAC_PTP_CLK_SRC_XTAL:
clk_src_val = 0;
break;
case EMAC_PTP_CLK_SRC_PLL_F80M:
clk_src_val = 1;
break;
default:
clk_src_val = 0;
break;
}
HP_SYS_CLKRST.peri_clk_ctrl01.reg_emac_ptp_ref_clk_src_sel = clk_src_val;
HP_SYS_CLKRST.peri_clk_ctrl01.reg_emac_ptp_ref_clk_en = enable;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define emac_ll_clock_enable_ptp(...) (void)__DECLARE_RCC_ATOMIC_ENV; emac_ll_clock_enable_ptp(__VA_ARGS__)
static inline void emac_ll_pause_frame_enable(void *ext_regs, bool enable)
{
HP_SYSTEM.sys_gmac_ctrl0.sys_phy_intf_sel = enable;

View File

@ -188,6 +188,7 @@ typedef struct {
ASSERT_TYPE_SIZE(eth_dma_rx_descriptor_t, EMAC_HAL_DMA_DESC_SIZE);
typedef struct emac_mac_dev_s *emac_mac_soc_regs_t;
typedef struct emac_dma_dev_s *emac_dma_soc_regs_t;
#if CONFIG_IDF_TARGET_ESP32
@ -195,11 +196,17 @@ typedef struct emac_ext_dev_s *emac_ext_soc_regs_t;
#else
typedef void *emac_ext_soc_regs_t;
#endif
#if SOC_EMAC_IEEE1588V2_SUPPORTED
typedef struct emac_ptp_dev_s *emac_ptp_soc_regs_t;
#endif
typedef struct {
emac_mac_soc_regs_t mac_regs;
emac_dma_soc_regs_t dma_regs;
emac_ext_soc_regs_t ext_regs;
#if SOC_EMAC_IEEE1588V2_SUPPORTED
emac_ptp_soc_regs_t ptp_regs;
#endif
} emac_hal_context_t;
/**
@ -209,6 +216,18 @@ typedef struct {
eth_mac_dma_burst_len_t dma_burst_len; /*!< eth-type enum of chosen dma burst-len */
} emac_hal_dma_config_t;
#if SOC_EMAC_IEEE1588V2_SUPPORTED
/**
* @brief EMAC PTP configuration parameters
*/
typedef struct {
eth_mac_ptp_update_method_t upd_method;
eth_mac_ptp_roll_type_t roll;
uint32_t ptp_clk_src_period_ns; /*!< 1/ptp_ref_clk */
uint32_t ptp_req_accuracy_ns; /*!< required PTP accuracy in ns, must be greater than clk_src period */
} emac_hal_ptp_config_t;
#endif
void emac_hal_init(emac_hal_context_t *hal);
#define emac_hal_get_phy_intf(hal) emac_ll_get_phy_intf((hal)->ext_regs)
@ -288,6 +307,130 @@ void emac_hal_set_rx_tx_desc_addr(emac_hal_context_t *hal, eth_dma_rx_descriptor
#define emac_hal_transmit_poll_demand(hal) emac_ll_transmit_poll_demand((hal)->dma_regs, 0)
#if SOC_EMAC_IEEE1588V2_SUPPORTED
#define emac_hal_get_ts_status(hal) emac_ll_get_ts_status((hal)->ptp_regs);
#define emac_hal_clock_enable_ptp(hal, clk_src, enable) emac_ll_clock_enable_ptp((hal)->ext_regs, clk_src, enable);
/**
* @brief Start Ethernet PTP timestamp for transmit and receive frames
*
* @param hal EMAC HAL context infostructure
* @return
* - ESP_OK: on success
* - ESP_ERR_TIMEOUT: on PTP block is busy
*/
esp_err_t emac_hal_ptp_start(emac_hal_context_t *hal, const emac_hal_ptp_config_t *config);
/**
* @brief Stop Ethernet PTP timestamp
*
* @param hal EMAC HAL context infostructure
* @return
* Always return ESP_OK
*/
esp_err_t emac_hal_ptp_stop(emac_hal_context_t *hal);
/**
* @brief Updates time stamp addend register relatively to the base value
*
* @param hal EMAC HAL context infostructure
* @param adj_ppb Correction value in ppb(parts per billion) (adj*10^9).
* For example, if the crystal used is 5 Hz off, then this value should be 5000.
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_STATE: on PTP block is busy
*/
esp_err_t emac_hal_ptp_adj_inc(emac_hal_context_t *hal, int32_t adj_ppb);
/**
* @brief Updates time stamp addend register relatively to the previous value
*
* @param hal EMAC HAL context infostructure
* @param scale_factor scale factor with which the addend register value is updated
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_STATE: on PTP block is busy
*/
esp_err_t emac_hal_adj_freq_factor(emac_hal_context_t *hal, double ratio);
/**
* @brief Adds or subtracts to the PTP system time.
*
* @param hal EMAC HAL context infostructure
* @param off_sec the PTP Time update second value
* @param off_nsec the PTP Time update nano-second value
* @param sign specifies the PTP Time update value sign(true means positive, false means negative)
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_STATE: on waiting for previous update to end
*/
esp_err_t emac_hal_ptp_time_add(emac_hal_context_t *hal, uint32_t off_sec, uint32_t off_nsec, bool sign);
/**
* @brief Initialize the PTP time base
*
* @param hal EMAC HAL context infostructure
* @param seconds specifies the PTP Time init second value
* @param nano_seconds specifies the PTP Time init nano-second value
* @return
* - ESP_OK: on success,
* - ESP_ERR_INVALID_STATE: on waiting for previous init to end
*/
esp_err_t emac_hal_ptp_set_sys_time(emac_hal_context_t *hal, uint32_t seconds, uint32_t nano_seconds);
/**
* @brief Get the current value of the system time maintained by the MAC
*
* @param hal EMAC HAL context infostructure
* @param seconds get the PTP system time second value
* @param nano_seconds get the PTP system time nano-second value
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_ARG: on invalid argument
*/
esp_err_t emac_hal_ptp_get_sys_time(emac_hal_context_t *hal, uint32_t *seconds, uint32_t *nano_seconds);
/**
* @brief Set target time to trigger event when the system time exceeds the target time
*
* @param hal EMAC HAL context infostructure
* @param seconds specifies the PTP target time second value
* @param nano_seconds specifies the PTP target Time nano-second value
* @return
* - ESP_OK on success, ESP_ERR_TIMEOUT on busy
*/
esp_err_t emac_hal_ptp_set_target_time(emac_hal_context_t *hal, uint32_t seconds, uint32_t nano_seconds);
/**
* @brief Get timestamp from receive descriptor
*
* @param hal EMAC HAL context infostructure
* @param rxdesc Pointer to receive descriptor
* @param seconds Pointer to store seconds part of timestamp
* @param nano_seconds Pointer to store nanoseconds part of timestamp
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_STATE: Descriptor does not contain time stamp information (frame might be filtered)
*/
esp_err_t emac_hal_get_rxdesc_timestamp(emac_hal_context_t *hal, eth_dma_rx_descriptor_t *rxdesc, uint32_t *seconds, uint32_t *nano_seconds);
/**
* @brief Get timestamp from transmit descriptor
*
* @param hal EMAC HAL context infostructure
* @param txdesc Pointer to transmit descriptor
* @param seconds Pointer to store seconds part of timestamp
* @param nano_seconds Pointer to store nanoseconds part of timestamp
*
* @return
* - ESP_OK: On success
* - ESP_ERR_INVALID_STATE: descriptor is still owned by DMA or time stamp is not ready yet
*/
esp_err_t emac_hal_get_txdesc_timestamp(emac_hal_context_t *hal, eth_dma_tx_descriptor_t *txdesc, uint32_t *seconds, uint32_t *nano_seconds);
#endif // SOC_EMAC_IEEE1588V2_SUPPORTED
#endif // SOC_EMAC_SUPPORTED
#ifdef __cplusplus

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -66,6 +66,24 @@ typedef enum {
ETH_DMA_BURST_LEN_1,
} eth_mac_dma_burst_len_t;
/**
* @brief EMAC System timestamp update update method
*
*/
typedef enum {
ETH_PTP_UPDATE_METHOD_COARSE, /*!< EMAC System timestamp update using the Coarse method */
ETH_PTP_UPDATE_METHOD_FINE /*!< EMAC System timestamp update using the Fine method */
} eth_mac_ptp_update_method_t;
/**
* @brief EMAC System Timestamp Rollover
*
*/
typedef enum {
ETH_PTP_DIGITAL_ROLLOVER, /*!< Digital - subseconds register rolls over after 999999999 value (1 nanosecond accuracy) */
ETH_PTP_BINARY_ROLLOVER /*!< Binary - subseconds register rolls over after 0x7FFFFFFF value */
} eth_mac_ptp_roll_type_t;
#ifdef __cplusplus
}
#endif

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

View File

@ -1999,7 +1999,7 @@ config SOC_ASYNCHRONOUS_BUS_ERROR_MODE
bool
default y
config SOC_EMAC_IEEE_1588_SUPPORT
config SOC_EMAC_IEEE1588V2_SUPPORTED
bool
default y

View File

@ -735,6 +735,19 @@ typedef enum {
TEMPERATURE_SENSOR_CLK_SRC_DEFAULT = SOC_MOD_CLK_LP_PERI, /*!< Select LP_PERI as the default choice */
} soc_periph_temperature_sensor_clk_src_t;
//////////////////////////////////////////////////EMAC PTP///////////////////////////////////////////////////////////////
/**
* @brief Array initializer for all supported clock sources of EMAC PTP
*/
#define SOC_EMAC_PTP_CLK {SOC_MOD_CLK_XTAL, SOC_MOD_CLK_PLL_F80M}
typedef enum {
EMAC_PTP_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL,
EMAC_PTP_CLK_SRC_PLL_F80M = SOC_MOD_CLK_PLL_F80M,
EMAC_PTP_CLK_SRC_DEFAULT = SOC_MOD_CLK_XTAL,
} soc_periph_emac_ptp_clk_src_t;
//////////////////////////////////////////////CLOCK OUTPUT///////////////////////////////////////////////////////////
typedef enum {
CLKOUT_SIG_MPLL = 0, /*!< MPLL is from 40MHz XTAL oscillator frequency multipliers */

View File

@ -0,0 +1,268 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct emac_ptp_dev_s {
volatile union{
struct {
uint32_t en_timestamp : 1; /* Timestamp Enable */
uint32_t ts_fine_coarse_update : 1; /* Timestamp Fine or Coarse Update */
uint32_t ts_initialize : 1; /* Timestamp Initialize */
uint32_t ts_update : 1; /* Timestamp Update */
uint32_t en_ts_int_trig : 1; /* Timestamp Interrupt Trigger Enable */
uint32_t addend_reg_update : 1; /* Addend Reg Update */
uint32_t reserved1 : 2; /* Reserved */
uint32_t en_ts4all : 1; /* Enable Timestamp for All Frames */
uint32_t ts_digit_bin_roll_ctrl : 1; /* Timestamp Digital or Binary Rollover Control */
uint32_t en_ptp_pkg_proc_ver2_fmt : 1; /* Enable PTP packet Processing for Version 2 Format */
uint32_t en_proc_ptp_ether_frm : 1; /* Enable Processing of PTP over Ethernet Frames */
uint32_t en_proc_ptp_ipv6_udp : 1; /* Enable Processing of PTP Frames Sent over IPv6-UDP */
uint32_t en_proc_ptp_ipv4_udp : 1; /* Enable Processing of PTP Frames Sent over IPv4-UDP */
uint32_t en_ts_snap_event_msg : 1; /* Enable Timestamp Snapshot for Event Messages */
uint32_t en_snap_msg_relevant_master : 1; /* Enable Snapshot for Messages Relevant to Master */
uint32_t sel_snap_type : 2; /* Select PTP packets for Taking Snapshots */
uint32_t en_mac_addr_filter : 1; /* Enable MAC address for PTP Frame Filtering */
uint32_t reserved2 : 5; /* Reserved */
uint32_t aux_snap_fifo_clear : 1; /* Auxiliary Snapshot FIFO Clear */
uint32_t en_aux_snap0 : 1; /* Auxiliary Snapshot 0 Enable */
uint32_t en_aux_snap1 : 1; /* Auxiliary Snapshot 1 Enable */
uint32_t en_aux_snap2 : 1; /* Auxiliary Snapshot 2 Enable */
uint32_t en_aux_snap3 : 1; /* Auxiliary Snapshot 3 Enable */
uint32_t reserved3 : 3; /* Reserved */
};
uint32_t val;
} timestamp_ctrl;
volatile union{
struct {
uint32_t sub_second_incre_value : 8; /* Sub-second Increment Value */
uint32_t reserved : 24; /* Reserved */
};
uint32_t val;
} sub_sec_incre;
volatile union{
struct {
uint32_t ts_second : 32; /* Timestamp Second */
};
uint32_t val;
} sys_seconds;
volatile union{
struct {
uint32_t ts_sub_seconds : 31; /* Timestamp Sub Seconds */
uint32_t reserved: 1; /* Reserved */
};
uint32_t val;
} sys_nanosec;
volatile union{
struct {
uint32_t ts_second : 32; /* Timestamp Second */
};
uint32_t val;
} sys_seconds_update;
volatile union{
struct {
uint32_t ts_sub_seconds : 31; /* Timestamp Sub Seconds */
uint32_t add_sub : 1; /* Add or Subtract Time */
};
uint32_t val;
} sys_nanosec_update;
volatile union{
struct {
uint32_t ts_addend_val: 32; /* Timestamp Addend Register */
};
uint32_t val;
} timestamp_addend;
volatile union{
struct {
uint32_t tgt_time_second_val : 32; /* Target Time Seconds Register */
};
uint32_t val;
} tgt_seconds;
volatile union{
struct {
uint32_t tgt_ts_low_reg : 31; /* Target Timestamp Low Register */
uint32_t tgt_time_reg_busy : 1; /* Target Time Register Busy */
};
uint32_t val;
} tgt_nanosec;
volatile union{
struct {
uint32_t ts_higher_word : 16; /* Timestamp Higher Word Register */
uint32_t reserved : 16; /* Reserved */
};
uint32_t val;
} sys_seconds_high;
volatile union{
struct {
uint32_t ts_secons_ovf : 1; /* Timestamp Seconds Overflow */
uint32_t ts_tgt_time_reach : 1; /* Timestamp Target Time Reached */
uint32_t aux_ts_trig_snap : 1; /* Auxiliary Timestamp Trigger Snapshot */
uint32_t ts_tgt_time_err : 1; /* Timestamp Target Time Error */
uint32_t ts_tgt_time_reach_pps1 : 1; /* Timestamp Target Time Reached for Target Time PPS1 */
uint32_t ts_tgt_time_err1 : 1; /* Timestamp Target Time Error */
uint32_t ts_tgt_time_reach_pps2 : 1; /* Timestamp Target Time Reached for Target Time PPS2 */
uint32_t ts_tgt_time_err2 : 1; /* Timestamp Target Time Error */
uint32_t ts_tgt_time_reach_pps3 : 1; /* Timestamp Target Time Reached for Target Time PPS3 */
uint32_t ts_tgt_time_err3 : 1; /* Timestamp Target Time Error */
uint32_t reserved1 : 6; /* Reserved */
uint32_t aux_ts_snap_trig_identify : 4; /* Auxiliary Timestamp Snapshot Trigger Identifier */
uint32_t reserved2 : 4; /* Reserved */
uint32_t aux_tx_snap_trig_miss : 1; /* Auxiliary Timestamp Snapshot Trigger Missed */
uint32_t aux_ts_snap_num : 5; /* Number of Auxiliary Timestamp Snapshots */
uint32_t reserved : 2; /* Reserved */
};
uint32_t val;
} status;
volatile union{
struct {
uint32_t pps_cmd0 : 4; /* Flexible PPS0 Output Control */
uint32_t en_pps0 : 1; /* Flexible PPS Output Mode Enable */
uint32_t tgt_mode_sel0 : 2; /* Target Time Register Mode for PPS0 Output */
uint32_t reserved1 : 1; /* Reserved */
uint32_t pps_cmd1 : 3; /* Flexible PPS1 Output Control */
uint32_t reserved2 : 2; /* Reserved */
uint32_t tgt_mode_sel1 : 2; /* Target Time Register Mode for PPS1 Output */
uint32_t reserved3 : 1; /* Reserved */
uint32_t pps_cmd2 : 3; /* Flexible PPS2 Output Control */
uint32_t reserved4 : 2; /* Reserved */
uint32_t tgt_mode_sel2 : 2; /* Target Time Register Mode for PPS2 Output */
uint32_t reserved5 : 1; /* Reserved */
uint32_t pps_cmd3 : 3; /* Flexible PPS3 Output Control */
uint32_t reserved6 : 2; /* Reserved */
uint32_t tgt_mode_sel3 : 2; /* Target Time Register Mode for PPS3 Output */
uint32_t reserved7 : 1; /* Reserved */
};
uint32_t val;
} pps_ctrl;
volatile union{
struct {
uint32_t aux_ts_low : 31; /* Contains the lower 31 bits (nano-seconds field) of the auxiliary timestamp. */
uint32_t reserved : 1; /* Reserved */
};
uint32_t val;
} aux_nanosec;
volatile union{
struct {
uint32_t aux_tx_high : 32; /* Contains the lower 32 bits of the Seconds field of the auxiliary timestamp. */
};
uint32_t val;
} aux_seconds;
volatile union{
struct {
uint32_t av_ethertype_val : 16; /* AV EtherType Value */
uint32_t ac_queue_pri : 3; /* AV Priority for Queuing */
uint32_t en_queue_non_av_pkt : 1; /* VLAN Tagged Non-AV Packets Queueing Enable */
uint32_t dis_av_chann : 1; /* AV Channel Disable */
uint32_t queue_av_ctrl_pkt_chann : 2; /* Channel for Queuing the AV Control Packets */
uint32_t reserved1 : 1; /* Reserved */
uint32_t queue_ptp_pkt_chann : 2; /* Channel for Queuing the PTP Packets */
uint32_t reserved2 : 6; /* Reserved */
};
uint32_t val;
} av_mac_ctrl;
uint32_t reserved1[9]; /* Reserved */
volatile union{
struct {
uint32_t pps0_interval : 32; /* PPS0 Output Signal Interval */
};
uint32_t val;
} pps0_interval;
volatile union{
struct {
uint32_t pps0_width : 32; /* PPS0 Output Signal Width */
};
uint32_t val;
} pps0_width;
uint32_t reserved2[6]; /* Reserved */
volatile union{
struct {
uint32_t pps1_tgt_seconds : 32; /* PPS1 Target Time Seconds Register */
};
uint32_t val;
} pps1_tgt_seconds;
volatile union{
struct {
uint32_t pps1_tgt_nanosec : 31; /* Target Time Low for PPS1 Register */
uint32_t pps1_tgt_time_busy : 1; /* PPS1 Target Time Register Busy */
};
uint32_t val;
} pps1_tgt_nanosec;
volatile union{
struct {
uint32_t pps1_interval : 32; /* PPS1 Output Signal Interval */
};
uint32_t val;
} pps1_interval;
volatile union{
struct {
uint32_t pps1_width : 32; /* PPS1 Output Signal Width */
};
uint32_t val;
} pps1_width;
uint32_t reserved3[4]; /* Reserved */
volatile union{
struct {
uint32_t pps2_tgt_seconds : 32; /* PPS2 Target Time Seconds Register */
};
uint32_t val;
} pps2_tgt_seconds;
volatile union{
struct {
uint32_t pps2_tgt_nanosec : 31; /* Target Time Low for PPS2 Register */
uint32_t pps2_tgt_time_busy : 1; /* PPS2 Target Time Register Busy */
};
uint32_t val;
} pps2_tgt_nanosec;
volatile union{
struct {
uint32_t pps2_interval : 32; /* PPS2 Output Signal Interval */
};
uint32_t val;
} pps2_interval;
volatile union{
struct {
uint32_t pps2_width : 32; /* PPS2 Output Signal Width */
};
uint32_t val;
} pps2_width;
uint32_t reserved4[4]; /* Reserved */
volatile union{
struct {
uint32_t pps3_tgt_seconds : 32; /* PPS3 Target Time Seconds Register */
};
uint32_t val;
} pps3_tgt_seconds;
volatile union{
struct {
uint32_t pps3_tgt_nanosec : 31; /* Target Time Low for PPS3 Register */
uint32_t pps3_tgt_time_busy : 1; /* PPS3 Target Time Register Busy */
};
uint32_t val;
} pps3_tgt_nanosec;
volatile union{
struct {
uint32_t pps3_interval : 32; /* PPS3 Output Signal Interval */
};
uint32_t val;
} pps3_interval;
volatile union{
struct {
uint32_t pps3_width : 32; /* PPS3 Output Signal Width */
};
uint32_t val;
} pps3_width;
} emac_ptp_dev_t;
extern emac_ptp_dev_t EMAC_PTP;
#ifdef __cplusplus
}
#endif

View File

@ -754,7 +754,7 @@
#define SOC_MEM_NON_CONTIGUOUS_SRAM (1)
#define SOC_ASYNCHRONOUS_BUS_ERROR_MODE (1)
/*--------------------------- EMAC --------------------------------*/
#define SOC_EMAC_IEEE_1588_SUPPORT (1) /*!< EMAC Supports IEEE1588 time stamping */
#define SOC_EMAC_IEEE1588V2_SUPPORTED (1) /*!< EMAC Supports IEEE1588v2 time stamping */
#define SOC_EMAC_USE_MULTI_IO_MUX (1) /*!< Multiple GPIO pad options exist to connect EMAC signal via IO_MUX */
#define SOC_EMAC_MII_USE_GPIO_MATRIX (1) /*!< EMAC MII signals are connected to GPIO pads via GPIO Matrix */

View File

@ -116,6 +116,7 @@ PROVIDE ( USB_DWC_FS = 0x50040000 );
PROVIDE ( USB_UTMI = 0x5009C000 );
PROVIDE ( EMAC_MAC = 0x50098000 );
PROVIDE ( EMAC_PTP = 0x50098700 );
PROVIDE ( EMAC_DMA = 0x50099000 );
PROVIDE ( CACHE = 0x3FF10000);

View File

@ -382,7 +382,7 @@ To install the Ethernet driver, we need to combine the instance of MAC and PHY a
* :cpp:member:`esp_eth_config_t::check_link_period_ms`: Ethernet driver starts an OS timer to check the link status periodically, this field is used to set the interval, in milliseconds.
* :cpp:member:`esp_eth_config_t::stack_input`: In most Ethernet IoT applications, any Ethernet frame received by a driver should be passed to the upper layer (e.g., TCP/IP stack). This field is set to a function that is responsible to deal with the incoming frames. You can even update this field at runtime via function :cpp:func:`esp_eth_update_input_path` after driver installation.
* :cpp:member:`esp_eth_config_t::stack_input` or :cpp:member:`esp_eth_config_t::stack_input_info`: In most Ethernet IoT applications, any Ethernet frame received by a driver should be passed to the upper layer (e.g., TCP/IP stack). This field is set to a function that is responsible to deal with the incoming frames. You can even update this field at runtime via function :cpp:func:`esp_eth_update_input_path` after driver installation.
* :cpp:member:`esp_eth_config_t::on_lowlevel_init_done` and :cpp:member:`esp_eth_config_t::on_lowlevel_deinit_done`: These two fields are used to specify the hooks which get invoked when low-level hardware has been initialized or de-initialized.
@ -517,6 +517,57 @@ The following functions should only be invoked after the Ethernet driver has bee
esp_eth_ioctl(eth_handle, ETH_CMD_G_PHY_ADDR, &phy_addr);
ESP_LOGI(TAG, "Ethernet PHY Address: %d", phy_addr);
.. _time-stamping:
.. only:: SOC_EMAC_IEEE1588V2_SUPPORTED
EMAC Hardware Time Stamping
---------------------------
Time stamping in EMAC allows precise tracking of when Ethernet frames are transmitted or received. Hardware time stamping is crucial for applications like Precision Time Protocol (PTP) because it minimizes jitter and inaccuracies that can occur when relying on software-based time stamps. By embedding time stamps directly in hardware, delays introduced by software layers or processing overhead are avoided, ensuring nanosecond-level precision.
.. warning::
Time stamp associated API is currently in **"Experimental Feature"** state so be aware it may change with future releases.
The basic way how to enable time stamping, get and set time in the EMAC is demonstrated below.
.. highlight:: c
::
// Enable hardware time stamping
bool ptp_enable = true;
esp_eth_ioctl(eth_hndl, ETH_MAC_ESP_CMD_PTP_ENABLE, &ptp_enable);
// Get current EMAC time
eth_mac_time_t ptp_time;
esp_eth_ioctl(eth_hndl, ETH_MAC_ESP_CMD_G_PTP_TIME, &ptp_time);
// Set EMAC time
ptp_time = {
.seconds = 42,
.nanoseconds = 0
};
esp_eth_ioctl(eth_hndl, ETH_MAC_ESP_CMD_S_PTP_TIME, &ptp_time);
You have an option to schedule event at precise point in time by registering callback function and configuring a target time when the event is supposed to be fired. Note that the callback function is then called from ISR context so it should be as brief as possible.
.. highlight:: c
::
// Register the callback function
esp_eth_ioctl(eth_hndl, ETH_MAC_ESP_CMD_S_TARGET_CB, ts_callback);
// Set time when event is triggered
eth_mac_time_t mac_target_time = {
.seconds = 42,
.nanoseconds = 0
};
esp_eth_ioctl(s_eth_hndl, ETH_MAC_ESP_CMD_S_TARGET_TIME, &mac_target_time);
Time stamps for transmitted and received frames can be accessed via the last argument of the registered :cpp:member:`esp_eth_config_t::stack_input_info` function for the receive path, and via the ``ctrl`` argument of the :cpp:func:`esp_eth_transmit_ctrl_vargs` function for the transmit path. However, a more user-friendly approach to retrieve time stamp information in user space is by utilizing the L2 TAP :ref:`Extended Buffer <esp_netif_l2tap_ext_buff>` mechanism.
.. _flow-control:
Flow Control
@ -546,6 +597,8 @@ Application Examples
* :example:`ethernet/iperf` demonstrates how to use the Ethernet capabilities to measure the throughput/bandwidth using iPerf.
* :example:`ethernet/ptp` demonstrates the use of Precision Time Protocol (PTP) for time synchronization over Ethernet.
* :example:`network/vlan_support` demonstrates how to create virtual network interfaces over Ethernet, including VLAN and non-VLAN interfaces.
* :example:`network/sta2eth` demonstrates how to create a 1-to-1 bridge using a Wi-Fi station and a wired interface such as Ethernet or USB.

View File

@ -148,42 +148,42 @@ ESP-NETIF Architecture
| (A) USER CODE |
| Apps |
.................| init settings events |
| Apps |
.................| init settings events |
. +----------------------------------------+
. . | *
. . | *
--------+ +===========================+ * +-----------------------+
| | new/config get/set/apps | * | init |
| | |...*.....| Apps (DHCP, SNTP) |
| |---------------------------| * | |
init | | |**** | |
start |************| event handler |*********| DHCP |
stop | | | | |
| |---------------------------| | |
| | | | NETIF |
+-----| | | +-----------------+ |
| glue|---<----|---| esp_netif_transmit |--<------| netif_output | |
| | | | | | | |
| |--->----|---| esp_netif_receive |-->------| netif_input | |
| | | | | + ----------------+ |
| |...<....|...| esp_netif_free_rx_buffer |...<.....| packet buffer |
+-----| | | | | | |
| | | | | | (D) |
(B) | | | | (C) | +-----------------------+
--------+ | | +===========================+ NETWORK STACK
. . | *
. . | *
--------+ +================================+ * +-----------------------+
| | new/config get/set/apps | * | init |
| | |...*.....| Apps (DHCP, SNTP) |
| |--------------------------------| * | |
init | | |**** | |
start |************| event handler |*********| DHCP |
stop | | | | |
| |--------------------------------| | |
| | | | NETIF |
+-----| | | +-----------------+ |
| glue|---<----|---| esp_netif_transmit |--<------| netif_output | |
| | | | | | | |
| |--->----|---| esp_netif_receive |-->------| netif_input | |
| | | | | + ----------------+ |
| |...<....|...| esp_netif_free_rx_buffer |...<.....| packet buffer |
+-----| | | | | | |
| | | | | | (D) |
(B) | | | | (C) | +-----------------------+
--------+ | | +================================+ NETWORK STACK
NETWORK | | ESP-NETIF
INTERFACE | |
DRIVER | | +---------------------------+ +------------------+
| | | |.........| open/close |
| | | | | |
| -<--| l2tap_write |-----<---| write |
| | | | |
---->--| esp_vfs_l2tap_eth_filter |----->---| read |
| | | (A) |
| (E) | +------------------+
+---------------------------+ USER CODE
ESP-NETIF L2 TAP
DRIVER | | +--------------------------------+ +------------------+
| | | |.........| open/close |
| | | | | |
| -<--| l2tap_write |-----<---| write |
| | | | |
---->--| esp_vfs_l2tap_eth_filter_frame |----->---| read |
| | | (A) |
| (E) | +------------------+
+--------------------------------+ USER CODE
ESP-NETIF L2 TAP
Data and Event Flow in the Diagram

View File

@ -132,8 +132,9 @@ The newly opened ESP-NETIF L2 TAP file descriptor needs to be configured prior t
* ``L2TAP_S_INTF_DEVICE`` - bounds the file descriptor to a specific Network Interface that is identified by its ``if_key``. ESP-NETIF Network Interface ``if_key`` is passed to ``ioctl()`` as the third parameter. Note that default Network Interfaces ``if_key``'s used in ESP-IDF can be found in :component_file:`esp_netif/include/esp_netif_defaults.h`.
* ``L2TAP_S_DEVICE_DRV_HNDL`` - is another way to bound the file descriptor to a specific Network Interface. In this case, the Network interface is identified directly by IO Driver handle (e.g., :cpp:type:`esp_eth_handle_t` in case of Ethernet). The IO Driver handle is passed to ``ioctl()`` as the third parameter.
* ``L2TAP_S_RCV_FILTER`` - sets the filter to frames with the type to be passed to the file descriptor. In the case of Ethernet frames, the frames are to be filtered based on the Length and Ethernet type field. In case the filter value is set less than or equal to 0x05DC, the Ethernet type field is considered to represent IEEE802.3 Length Field, and all frames with values in interval <0, 0x05DC> at that field are passed to the file descriptor. The IEEE802.2 logical link control (LLC) resolution is then expected to be performed by the user's application. In case the filter value is set greater than 0x05DC, the Ethernet type field is considered to represent protocol identification and only frames that are equal to the set value are to be passed to the file descriptor.
* ``L2TAP_S_TIMESTAMP_EN`` - enables the hardware Time Stamping processing inside the file descriptor. The Time Stamps are retrieved to user space by using :ref:`Extended Buffer <esp_netif_l2tap_ext_buff>` mechanism when accessing the file descriptor by ``read()`` and ``write()`` functions. Hardware time stamping needs to be supported by target and needs to be enabled in IO Driver to this option work as expected.
All above-set configuration options have a getter counterpart option to read the current settings.
All above-set configuration options have a getter counterpart option to read the current settings except for ``L2TAP_S_TIMESTAMP_EN``.
.. warning::
The file descriptor needs to be firstly bounded to a specific Network Interface by ``L2TAP_S_INTF_DEVICE`` or ``L2TAP_S_DEVICE_DRV_HNDL`` to make ``L2TAP_S_RCV_FILTER`` option available.
@ -153,14 +154,14 @@ All above-set configuration options have a getter counterpart option to read the
``fcntl()``
^^^^^^^^^^^
``fcntl()`` is used to manipulate with properties of opened ESP-NETIF L2 TAP file descriptor.
The ``fcntl()`` is used to manipulate with properties of opened ESP-NETIF L2 TAP file descriptor.
The following commands manipulate the status flags associated with the file descriptor:
* ``F_GETFD`` - the function returns the file descriptor flags, and the third argument is ignored.
* ``F_SETFD`` - sets the file descriptor flags to the value specified by the third argument. Zero is returned.
| On success, ``ioctl()`` returns 0. On error, -1 is returned, and ``errno`` is set to indicate the error.
| On success, ``fcntl()`` returns 0. On error, -1 is returned, and ``errno`` is set to indicate the error.
| * EBADF - not a valid file descriptor.
| * ENOSYS - unsupported command.
@ -172,23 +173,24 @@ Opened and configured ESP-NETIF L2 TAP file descriptor can be accessed by ``read
| * EBADF - not a valid file descriptor.
| * EAGAIN - the file descriptor has been marked non-blocking (``O_NONBLOCK``), and the read would block.
.. note::
ESP-NETIF L2 TAP ``read()`` implementation extends the standard and offers Extended Buffer mechanism to retrieve additional information about received frame. See :ref:`Extended Buffer <esp_netif_l2tap_ext_buff>` section for more information.
``write()``
^^^^^^^^^^^
A raw Data Link Layer frame can be sent to Network Interface via opened and configured ESP-NETIF L2 TAP file descriptor. The user's application is responsible to construct the whole frame except for fields which are added automatically by the physical interface device. The following fields need to be constructed by the user's application in case of an Ethernet link: source/destination MAC addresses, Ethernet type, actual protocol header, and user data. The length of these fields is as follows:
.. list-table::
:header-rows: 1
:widths: 20 20 20 30
:align: center
.. packetdiag::
* - Destination MAC
- Source MAC
- Type/Length
- Payload (protocol header/data)
* - 6 B
- 6 B
- 2 B
- 0-1486 B
packetdiag {
colwidth = 16;
node_width = 38;
0-5: Destination MAC (6B) [color = "#ffcccc"];
6-11: Source MAC Port (6B) [color = "#ffcccc"];
12-13: Type/Length (2B) [color = "#ccccff"];
14-15: [color = "#ffffcc"];
16-31: Payload (protocol header/data - 1486B) [color = "#ffffcc", colheight = 3];
}
In other words, there is no additional frame processing performed by the ESP-NETIF L2 TAP interface. It only checks the Ethernet type of the frame is the same as the filter configured in the file descriptor. If the Ethernet type is different, an error is returned and the frame is not sent. Note that the ``write()`` may block in the current implementation when accessing a Network interface since it is a shared resource among multiple ESP-NETIF L2 TAP file descriptors and IP stack, and there is currently no queuing mechanism deployed.
@ -197,9 +199,12 @@ In other words, there is no additional frame processing performed by the ESP-NET
| * EBADMSG - The Ethernet type of the frame is different from the file descriptor configured filter.
| * EIO - Network interface not available or busy.
.. note::
ESP-NETIF L2 TAP ``write()`` implementation extends the standard and offers Extended Buffer mechanism to retrieve additional information about transmitted frame. See :ref:`Extended Buffer <esp_netif_l2tap_ext_buff>` section for more information.
``close()``
^^^^^^^^^^^
Opened ESP-NETIF L2 TAP file descriptor can be closed by the ``close()`` to free its allocated resources. The ESP-NETIF L2 TAP implementation of ``close()`` may block. On the other hand, it is thread-safe and can be called from a different task than the file descriptor is actually used. If such a situation occurs and one task is blocked in the I/O operation and another task tries to close the file descriptor, the first task is unblocked. The first's task read operation then ends with an error.
Opened ESP-NETIF L2 TAP file descriptor can be closed by the ``close()`` to free its allocated resources. The ESP-NETIF L2 TAP implementation of ``close()`` may block. On the other hand, it is thread-safe and can be called from a different task than the file descriptor is actually used. If such a situation occurs and one task is blocked in the I/O operation and another task tries to close the file descriptor, the first task is unblocked. The first's task ``read`` operation then ends with returning `0` bytes was read.
| On success, ``close()`` returns zero. On error, -1 is returned, and ``errno`` is set to indicate the error.
| * EBADF - not a valid file descriptor.
@ -208,6 +213,77 @@ Opened ESP-NETIF L2 TAP file descriptor can be closed by the ``close()`` to free
^^^^^^^^^^^^
Select is used in a standard way, just :ref:`CONFIG_VFS_SUPPORT_SELECT` needs to be enabled to make the ``select()`` function available.
.. _esp_netif_l2tap_ext_buff:
Extended Buffer
^^^^^^^^^^^^^^^
The Extended Buffer is ESP-NETIF L2 TAP's mechanism of how to retrieve additional information about transmitted or received IO frame via ``write()`` or ``read()`` functions. The Extended Buffer must be only used when specific functionality is enabled in the file descriptor (such as ``L2TAP_S_TIMESTAMP_EN``) and you want to access the additional data (such as Time Stamp) or control the frame processing.
The **Extended Buffer** is a structure with fields which serve as arguments to drive underlying functionality in the ESP-NETIF L2 TAP file descriptor. The structure is defined as follows:
.. code-block:: c
typedef struct {
size_t info_recs_len; /*!< Length of Information Records buffer */
void *info_recs_buff; /*!< Buffer holding extended information (IRECs) related to IO frames */
size_t buff_len; /*!< Length of the actual IO Frame buffer */
void *buff; /*!< Pointer to the IO Frame buffer */
} l2tap_extended_buff_t;
One Extended buffer may hold multiple **Information Records** (IRECs). These are variable data typed (and sized) records which may hold any datatype of additional information associated with the IO frame. The IREC structure is defined as follows:
.. code-block:: c
typedef struct
{
size_t len; /*!< Length of the record including header and data*/
l2tap_irec_type_t type; /*!< Type of the record */
alignas(long long) uint8_t data[]; /*!< Records Data aligned to double word */
} l2tap_irec_hdr_t;
Currently implement and used IREC data types are defined in :cpp:type:`l2tap_irec_type_t`.
Since the flexible array to hold data is used, proper memory alignment of multiple IRECs in the records buffer is required to correctly access memory. Improper alignment can result in slower memory access due to misaligned read/write operations, or in the worst case, cause undefined behavior on certain architectures. Therefore it is strictly recommended to use the below macros when manipulating with IRECs:
* ``L2TAP_IREC_SPACE()`` - determines the space required for an IREC, ensuring that it is properly aligned.
* ``L2TAP_IREC_LEN()`` - calculates the total length of one IREC, including the header and the data section of the record.
* ``L2TAP_IREC_FIRST()`` - retrieves the first IREC from the :cpp:member:`l2tap_extended_buff_t::info_recs_buff` pool of Extended Buffer. If the :cpp:member:`l2tap_extended_buff_t::info_recs_len` is smaller than the size of a record header, it returns NULL.
* ``L2TAP_IREC_NEXT()`` - retrieves the next IREC in the Extended Buffer after the current record. If the current record is NULL, it returns the first record.
Extended Buffer Usage
"""""""""""""""""""""
Prior any Extended Buffer IO operation (either ``write()`` or ``read()``), you first need to fully populate the Extended Buffer and its IREC fields. For example, when you want to retrieve Time Stamp, you need to set type of the IREC to :cpp:enumerator:`L2TAP_IREC_TIME_STAMP` and configure appropriate length. If you don't set the type correctly, the frame is still received or transmitted but information to be retrieved is lost. Similarly, when the IREC length is less than expected length, the frame is still received or transmitted but the type of affected IREC is marked to :cpp:enumerator:`L2TAP_IREC_INVALID` by the ESP-NETIF L2 TAP and information to be retrieved is lost.
When accessing the file descriptor using Extended Buffer, ``size`` parameter of ``write()`` or ``read()`` function must be set equal to ``0``. Failing to do so (i.e. accessing such file descriptor in a standard way with ``size`` parameter set to data length) will result in an -1 error and ``errno`` set to EINVAL.
.. code-block:: c
// wrap "Info Records Buffer" into union to ensure proper alignment of data (this is typically needed when
// accessing double word variables or structs containing double word variables)
union {
uint8_t info_recs_buff[L2TAP_IREC_SPACE(sizeof(struct timespec))];
l2tap_irec_hdr_t align;
} u;
l2tap_extended_buff_t ptp_msg_ext_buff;
ptp_msg_ext_buff.info_recs_len = sizeof(u.info_recs_buff);
ptp_msg_ext_buff.info_recs_buff = u.info_recs_buff;
ptp_msg_ext_buff.buff = eth_frame;
ptp_msg_ext_buff.buff_len = sizeof(eth_frame);
l2tap_irec_hdr_t *ts_info = L2TAP_IREC_FIRST(&ptp_msg_ext_buff);
ts_info->len = L2TAP_IREC_LEN(sizeof(struct timespec));
ts_info->type = L2TAP_IREC_TIME_STAMP;
int ret = write(state->ptp_socket, &ptp_msg_ext_buff, 0);
// check if write was successful and ts_info is valid
if (ret > 0 && ts_info->type == L2TAP_IREC_TIME_STAMP) {
*ts = *(struct timespec *)ts_info->data;
}
.. _esp_netif_other_events:

View File

@ -31,3 +31,9 @@ examples/ethernet/iperf:
- cmd_system
- ethernet_init
- protocol_examples_common
examples/ethernet/ptp:
enable:
- if: SOC_EMAC_IEEE1588V2_SUPPORTED == 1
depends_components:
- esp_eth
- esp_netif

View File

@ -1,5 +1,5 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
# Ethernet Examples

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ethernet_ptp)

View File

@ -0,0 +1,123 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |
# Time Synchronization over PTP
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## Overview
This example demonstrates the use of Precision Time Protocol (PTP) for time synchronization over Ethernet in ESP-IDF. PTP allows precise time synchronization between different nodes in a network. The example initializes Ethernet, starts a PTP daemon (based on a [Nuttx implementation](https://github.com/apache/nuttx-apps/tree/master/netutils/ptpd) ported to ESP-IDF), and showcases synchronization accuracy by toggling a GPIO pin.
The PTP protocol is transported over **Ethernet at Layer 2 (L2)**, following the guidelines set forth in Annex F of the IEEE 1588-2008 standard (also known as PTPv2). The **timestamps for synchronization are provided by internal Ethernet MAC (EMAC)** and are attached to Ethernet frames at the hardware level. These hardware-generated timestamps are then passed to the software via the **L2 TAP interface**, allowing precise clock synchronization with minimal latency.
The example is designed to run at least with two ESP32P4 boards, where one acts as **the master** and the other as **the slave**. Both devices will begin toggling a GPIO pin once they are synchronized. By measuring alignment of the rising edges of the GPIO pulse on both devices using an oscilloscope, you can observe the synchronization precision. The pulse width and toggle frequency can be configured using ``CONFIG_EXAMPLE_PTP_PULSE_WIDTH_NS``.
## How to use example
### Hardware Required
* It's recommended that you have two official ESP32P4 boards with Ethernet capabilities - [ESP32-P4-Function-EV-Board](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/user_guide.html).
* Oscilloscope to measure the GPIO pulse alignment between the master and slave devices.
#### Pin Assignment
See common pin assignments for Ethernet examples from [upper level](../README.md#common-pin-assignments).
### Configure the project
Run the:
```
idf.py menuconfig
```
and configure the following parameters:
* **PTP Pulse GPIO Pin**: Set the GPIO pin number for pulse toggling.
* **Pulse Width (ns)**: Set the pulse width (in nanoseconds).
* **PTP Daemon Configuration**: Select either Master or Slave and configure all the associated parameters per your application needs. To achieve more precise synchronization, enable ``PTP Client delay requests``.
* **Ethernet**: See common configurations for Ethernet examples from [upper level](../README.md#common-configurations).
### Build, Flash, and Run
Build the project for both boards and flash it, then run monitor tool to view serial output:
```
idf.py -p PORT build flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
### PTP Slave
```
I (10107) ptpd: Got announce packet, seq 3120
I (10107) ptpd: Switching to better PTP time source
I (10107) gpio: GPIO[20]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (10107) ptpd: Got sync packet, seq 31198
I (10127) ptpd: Waiting for follow-up
I (10127) ptpd: Got follow-up packet, seq 31198
I (10137) ptpd: Local time: 7.633787680, remote time 31996.904013440
I (10137) ptpd: Jumped to timestamp 31996.935345720 s
I (9866) ptp_example: Starting Pulse train
I (9866) ptp_example: curr time: 230.312127120
I (9876) ptp_example: next time: 231.500000000
I (9876) main_task: Returned from app_main()
I (10836) ptpd: Got sync packet, seq 229
I (10836) ptpd: Waiting for follow-up
I (10836) ptpd: Got follow-up packet, seq 229
I (10846) ptpd: Local time: 231.286076880, remote time 231.286195640
I (10846) ptpd: remote_delta_ns 231286195640, local_delta_ns 231286076880, tick_diff 118760
I (10856) ptpd: offset_ns 118760, adj 130636, drift_acc 11876
...
I (59686) ptpd: Waiting for follow-up
I (59686) ptpd: Got follow-up packet, seq 277
I (59696) ptpd: Local time: 280.136196920, remote time 280.136196120
I (59696) ptpd: remote_delta_ns 1030000440, local_delta_ns 1030000440, tick_diff 0
I (59706) ptpd: offset_ns -186, adj 2, drift_acc 188
I (59716) ptpd: Sent delay req, seq 19
I (59716) ptpd: Got delay-resp, seq 19
I (59726) ptpd: Path delay: 847 ns (avg: 626 ns)
```
### Synchronization Pulses
The below figure shows synchronization pulses generated by master and slave device measured on oscilloscope.
![sync_puls](./docs/sync_osc.jpg)
## Troubleshooting
See common troubleshooting for Ethernet examples from [upper level](../README.md#common-troubleshooting).
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "esp_eth_time.c"
PRIV_REQUIRES esp_eth
INCLUDE_DIRS ".")

View File

@ -0,0 +1,3 @@
# 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.

View File

@ -0,0 +1,142 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include "esp_eth_time.h"
static esp_eth_handle_t s_eth_hndl;
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_ioctl(s_eth_hndl, ETH_MAC_ESP_CMD_ADJ_PTP_FREQ, &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_eth_hndl) {
eth_mac_time_t ptp_time = {
.seconds = tp->tv_sec,
.nanoseconds = tp->tv_nsec
};
esp_err_t ret = esp_eth_ioctl(s_eth_hndl, ETH_MAC_ESP_CMD_S_PTP_TIME, &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_eth_hndl) {
eth_mac_time_t ptp_time;
esp_err_t ret = esp_eth_ioctl(s_eth_hndl, ETH_MAC_ESP_CMD_G_PTP_TIME, &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_ioctl(s_eth_hndl, ETH_MAC_ESP_CMD_S_TARGET_TIME, &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_ioctl(s_eth_hndl, ETH_MAC_ESP_CMD_S_TARGET_CB, 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)
{
switch (clock_id) {
case CLOCK_PTP_SYSTEM:
// PTP Clock is part of Ethernet system
bool ptp_enable = true;
if (esp_eth_ioctl(cfg->eth_hndl, ETH_MAC_ESP_CMD_PTP_ENABLE, &ptp_enable) != ESP_OK) {
return ESP_FAIL;
}
s_eth_hndl = cfg->eth_hndl;
break;
default:
return ESP_FAIL;
}
return ESP_OK;
}

View File

@ -0,0 +1,131 @@
/*
* 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_t esp_eth_clock_init(clockid_t clock_id, esp_eth_clock_cfg_t *cfg);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,6 @@
idf_component_register(SRCS "ptpd.c"
PRIV_REQUIRES esp_eth esp_netif
INCLUDE_DIRS "." "./include")
target_sources(${COMPONENT_LIB} PRIVATE "ptpd.c")
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-function)

View File

@ -0,0 +1,239 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
menu "PTP Daemon Configuration"
config NETUTILS_PTPD
bool "PTPD client/server"
default y
#depends on NET_IPv4
#depends on NET_IGMP
#depends on NET_UDP
help
Build a minimal implementation of IEEE-1588 precision time protocol.
Uses system gettimeofday() and adjtime() calls to synchronize clock
with a master clock through network, or to provide a master clock to
other systems.
if NETUTILS_PTPD
config NETUTILS_PTPD_DEBUG
bool "Enable PTP debug messages"
default n
depends on DEBUG_INFO
help
Enable PTP debug messages even if CONFIG_DEBUG_NET_INFO is not enabled.
config NETUTILS_PTPD_CLIENT
bool "Enable client support"
default y
help
Act as a PTP client, synchronizing the NuttX clock to a remote master
clock.
config NETUTILS_PTPD_SERVER
bool "Enable server support"
default n
help
Act as a PTP server, providing NuttX clock time to other systems.
Both server and client can be simultaneously enabled. NuttX will then
synchronize to a higher priority master clock, or act as a master
clock itself if it has the highest priority.
Refer to Best Master Clock algorithm in IEEE-1588 for details.
config NETUTILS_PTPD_STACKSIZE
int "PTP daemon stack stack size"
default 4096 # DEFAULT_TASK_STACKSIZE
config NETUTILS_PTPD_SERVERPRIO
int "PTP daemon priority"
default 100
config NETUTILS_PTPD_DOMAIN
int "PTP domain selection"
default 0
range 0 127
help
Set PTP domain to participate in. Default domain is 0, other domains
can be used to isolate reference clocks from each other.
if NETUTILS_PTPD_SERVER
config NETUTILS_PTPD_PRIORITY1
int "PTP server priority1"
default 128
range 0 255
help
Set clock priority to announce when acting as a PTP server.
Lower value is higher priority.
A higher priority1 clock will be selected without regard to announced
clock quality fields.
Refer to Best Master Clock algorithm in IEEE-1588 for details.
config NETUTILS_PTPD_PRIORITY2
int "PTP server priority2"
default 128
range 0 255
help
Set clock subpriority to announce when acting as a PTP server.
This will distinguish between two clocks that are equivalent in
priority1, class and accuracy values.
Lower value is higher priority.
config NETUTILS_PTPD_CLASS
int "PTP server class"
default 248
range 0 255
help
Set master clock class to announce when acting as a PTP server.
Lower value means higher quality clock source.
248 is the default for unknown class.
config NETUTILS_PTPD_ACCURACY
int "PTP server accuracy"
default 254
range 0 255
help
Set master clock accuracy to announce when acting as a PTP server.
Logarithmic scale is defined in IEEE-1588:
32: +- 25 ns
33: +- 100 ns
34: +- 250 ns
35: +- 1 us
36: +- 2.5 us
37: +- 10 us
38: +- 25 us
39: +- 100 us
40: +- 250 us
41: +- 1 ms
42: +- 2.5 ms
43: +- 10 ms
44: +- 25 ms
45: +- 100 ms
46: +- 250 ms
47: +- 1 s
48: +- 10 s
49: +- more than 10 s
254: Unknown
config NETUTILS_PTPD_CLOCKSOURCE
int "PTP server clock source type"
default 160
range 0 255
help
Set clock source type to announce when acting as a PTP server.
Common values:
32: GPS
64: PTP
80: NTP
144: Other
160: Internal oscillator
config NETUTILS_PTPD_SYNC_INTERVAL_MSEC
int "PTP server sync transmit interval (ms)"
default 1000
help
How often to transmit sync packets in server mode.
config NETUTILS_PTPD_ANNOUNCE_INTERVAL_MSEC
int "PTP server announce transmit interval (ms)"
default 10000
help
How often to transmit announce packets in server mode.
config NETUTILS_PTPD_TWOSTEP_SYNC
bool "PTP server sends two-step synchronization packets"
default y
help
If enabled, sends a follow-up packet after every sync packet.
This helps compensate for the time taken to initiate the transmission.
config NETUTILS_PTPD_DELAYRESP_INTERVAL
int "PTP server suggested interval of delay requests"
range 0 255
default 4
help
When responding to a delay request, the server can inform the client
how often it should test path delay. This is done using header field
logMessageInterval. The delay will be 2^N seconds.
Default value 4 results in 16 second interval.
endif # NETUTILS_PTPD_SERVER
if NETUTILS_PTPD_CLIENT
config NETUTILS_PTPD_TIMEOUT_MS
int "PTP client timeout for changing clock source (ms)"
default 60000
help
If no packets are being received from currently chosen clock source,
fall back to next best clock source after this many seconds.
config NETUTILS_PTPD_SETTIME_THRESHOLD_MS
int "PTP client threshold for changing system time (ms)"
default 1000
help
If difference between local and remote clock exceeds this threshold,
time is reset with settimeofday() instead of changing the rate with
adjtime().
# Commented options not used by ESP_PTP
#config NETUTILS_PTPD_MULTICAST_TIMEOUT_MS
# int "PTP client timeout to rejoin multicast group (ms)"
# default 30000 #esp32
# default 0
# help
# If no PTP multicast packets are being received, attempt to rejoin the
# multicast group. This can be necessary if network topology changes, or
# 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
help
If enabled, sends delay request messages to measure the network delay
to server. If disabled, assumes zero delay.
if NETUTILS_PTPD_SEND_DELAYREQ
config NETUTILS_PTPD_MAX_PATH_DELAY_NS
int "PTP client maximum path delay (ns)"
default 100000
range 1 1000000000
help
Measured path delay longer than this is ignored. Delay requests are
also not transmitted until clock synchronization is better than this.
config NETUTILS_PTPD_DELAYREQ_AVGCOUNT
int "PTP client path delay averaging count"
default 100
help
Measured path delay is averaged over this many samples.
config NETUTILS_PTPD_PATH_DELAY_STABILITY_NS
int "PTP client send delay request when clock stability (ns)"
default 250
help
Sends path delay request only once the internal clock is stable and skews only
in defined interval.
endif # NETUTILS_PTPD_SEND_DELAYREQ
endif # NETUTILS_PTPD_CLIENT
endif # NETUTILS_PTPD
endmenu # PTP Daemon Configuration

View File

@ -0,0 +1,23 @@
############################################################################
# apps/netutils/ptpd/Make.defs
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
ifneq ($(CONFIG_NETUTILS_PTPD),)
CONFIGURED_APPS += $(APPDIR)/netutils/ptpd
endif

View File

@ -0,0 +1,27 @@
############################################################################
# apps/netutils/ptpd/Makefile
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
include $(APPDIR)/Make.defs
# PTP server/client implementation
CSRCS = ptpd.c
include $(APPDIR)/Application.mk

View File

@ -0,0 +1,3 @@
# NuttX PTP Daemon Port Example
This example component presents port of [Nuttx PTP Daemon](https://github.com/apache/nuttx-apps/tree/master/netutils/ptpd) for ESP-IDF.

View File

@ -0,0 +1,3 @@
dependencies:
esp_eth_time:
path: ${IDF_PATH}/examples/ethernet/ptp/components/esp_eth_time

View File

@ -0,0 +1,188 @@
/*
* SPDX-FileCopyrightText: 2020-2024 The Apache Software Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD
*/
/****************************************************************************
* apps/include/netutils/ptpd.h
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __APPS_INCLUDE_NETUTILS_PTPD_H
#define __APPS_INCLUDE_NETUTILS_PTPD_H
// ESP_PTP
#include <time.h>
#ifndef FAR
#define FAR
#endif
/****************************************************************************
* Included Files
****************************************************************************/
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Public Types
****************************************************************************/
/* PTPD status information structure */
struct ptpd_status_s
{
/* Is there a valid remote clock source active? */
bool clock_source_valid;
/* Information about selected best clock source */
struct
{
uint8_t id[8]; /* Clock identity */
int utcoffset; /* Offset between clock time and UTC time (seconds) */
int priority1; /* Main priority field */
int clockclass; /* Clock class (IEEE-1588, lower is better) */
int accuracy; /* Clock accuracy (IEEE-1588, lower is better) */
int variance; /* Clock variance (IEEE-1588, lower is better) */
int priority2; /* Secondary priority field */
uint8_t gm_id[8]; /* Grandmaster clock identity */
int stepsremoved; /* How many steps from grandmaster clock */
int timesource; /* Type of time source (IEEE-1588) */
} clock_source_info;
/* When was clock last updated or adjusted (CLOCK_REALTIME).
* Matches last_received_sync but in different clock.
*/
struct timespec last_clock_update;
/* Details of clock adjustment made at last_clock_update */
int64_t last_delta_ns; /* Latest measured clock error */
int64_t last_adjtime_ns; /* Previously applied adjtime() offset */
/* Averaged clock drift estimate (parts per billion).
* Positive means remote clock runs faster than local clock before
* adjustment.
*/
long drift_ppb;
/* Averaged path delay */
long path_delay_ns;
/* Timestamps of latest received packets (CLOCK_MONOTONIC) */
struct timespec last_received_multicast; /* Any multicast packet */
struct timespec last_received_announce; /* Announce from any server */
struct timespec last_received_sync; /* Sync from selected source */
/* Timestamps of latest transmitted packets (CLOCK_MONOTONIC) */
struct timespec last_transmitted_sync;
struct timespec last_transmitted_announce;
struct timespec last_transmitted_delayresp;
struct timespec last_transmitted_delayreq;
};
/****************************************************************************
* Public Data
****************************************************************************/
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: ptpd_start
*
* Description:
* Start the PTP daemon and bind it to specified interface.
*
* Input Parameters:
* interface - Name of the network interface to bind to, e.g. "eth0"
*
* Returned Value:
* On success, the non-negative task ID of the PTP daemon is returned;
* On failure, a negated errno value is returned.
*
****************************************************************************/
int ptpd_start(FAR const char *interface);
/****************************************************************************
* Name: ptpd_status
*
* Description:
* Query status from a running PTP daemon.
*
* Input Parameters:
* pid - Process ID previously returned by ptpd_start()
* status - Pointer to storage for status information.
*
* Returned Value:
* On success, returns OK.
* On failure, a negated errno value is returned.
*
* Assumptions/Limitations:
* Multiple threads with priority less than CONFIG_NETUTILS_PTPD_SERVERPRIO
* can request status simultaneously. If higher priority threads request
* status simultaneously, some of the requests may timeout.
*
****************************************************************************/
int ptpd_status(int pid, FAR struct ptpd_status_s *status);
/****************************************************************************
* Name: ptpd_stop
*
* Description:
* Stop PTP daemon
*
* Input Parameters:
* pid - Process ID previously returned by ptpd_start()
*
* Returned Value:
* On success, returns OK.
* On failure, a negated errno value is returned.
*
****************************************************************************/
int ptpd_stop(int pid);
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __APPS_INCLUDE_NETUTILS_PTPD_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
/*
* SPDX-FileCopyrightText: 2020-2024 The Apache Software Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD
*/
/****************************************************************************
* apps/netutils/ptpd/ptpv2.h
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __APPS_NETUTILS_PTPD_PTPV2_H
#define __APPS_NETUTILS_PTPD_PTPV2_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdint.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Time-critical messages (id < 8) are sent to port 319,
* other messages to port 320.
*/
#define PTP_UDP_PORT_EVENT 319
#define PTP_UDP_PORT_INFO 320
/* Multicast address to send to: 224.0.1.129 */
#define PTP_MULTICAST_ADDR ((in_addr_t)0xE0000181)
/* Message types */
#define PTP_MSGTYPE_MASK 0x0F
#define PTP_MSGTYPE_SYNC 0
#define PTP_MSGTYPE_DELAY_REQ 1
#define PTP_MSGTYPE_FOLLOW_UP 8
#define PTP_MSGTYPE_DELAY_RESP 9
#define PTP_MSGTYPE_ANNOUNCE 11
/* Message flags */
#define PTP_FLAGS0_TWOSTEP (1 << 1)
/****************************************************************************
* Public Types
****************************************************************************/
/* Defined in IEEE 1588-2008 Precision Time Protocol
* All multi-byte fields are big-endian.
*/
/* Common header for all message types */
struct ptp_header_s
{
uint8_t messagetype;
uint8_t version;
uint8_t messagelength[2];
uint8_t domain;
uint8_t reserved1;
uint8_t flags[2];
uint8_t correction[8];
uint8_t reserved2[4];
uint8_t sourceidentity[8];
uint8_t sourceportindex[2];
uint8_t sequenceid[2];
uint8_t controlfield;
uint8_t logmessageinterval;
};
/* Announce a master clock */
struct ptp_announce_s
{
struct ptp_header_s header;
uint8_t origintimestamp[10];
uint8_t utcoffset[2];
uint8_t reserved;
uint8_t gm_priority1;
uint8_t gm_quality[4];
uint8_t gm_priority2;
uint8_t gm_identity[8];
uint8_t stepsremoved[2];
uint8_t timesource;
};
/* Sync: transmit timestamp from master clock */
struct ptp_sync_s
{
struct ptp_header_s header;
uint8_t origintimestamp[10];
};
/* FollowUp: actual timestamp of when sync message was sent */
struct ptp_follow_up_s
{
struct ptp_header_s header;
uint8_t origintimestamp[10];
};
/* DelayReq: request delay measurement */
struct ptp_delay_req_s
{
struct ptp_header_s header;
uint8_t origintimestamp[10];
};
/* DelayResp: response to DelayReq */
struct ptp_delay_resp_s
{
struct ptp_header_s header;
uint8_t receivetimestamp[10];
uint8_t reqidentity[8];
uint8_t reqportindex[2];
};
#endif /* __APPS_NETUTILS_PTPD_PTPV2_H */

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "ptp_main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,22 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config EXAMPLE_PTP_PULSE_GPIO
int "PPS GPIO number"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 20
help
Set the GPIO number at which the Pulse signal is outputted.
config EXAMPLE_PTP_PULSE_WIDTH_NS
int "Pulse width (ns)"
range 5000 999999999
default 500000000
help
Set pulse width in ns.
WARNING: Since the new pulse target time is set programmatically in the callback
function, width accuracy may vary or it may be lost completely when you select
very short pulse width.
endmenu

View File

@ -0,0 +1,7 @@
dependencies:
ethernet_init:
path: ${IDF_PATH}/examples/ethernet/basic/components/ethernet_init
esp_eth_time:
path: ${IDF_PATH}/examples/ethernet/ptp/components/esp_eth_time
ptpd:
path: ${IDF_PATH}/examples/ethernet/ptp/components/ptpd

View File

@ -0,0 +1,150 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_event.h"
#include "esp_eth.h"
#include "esp_netif.h"
#include "ethernet_init.h"
#include "esp_vfs_l2tap.h"
#include "driver/gpio.h"
#include "ptpd.h"
#include "esp_eth_time.h"
static const char *TAG = "ptp_example";
static struct timespec s_next_time;
static bool s_gpio_level;
void init_ethernet_and_netif(void)
{
uint8_t eth_port_cnt;
esp_eth_handle_t *eth_handles;
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_eth_init(&eth_handles, &eth_port_cnt));
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_vfs_l2tap_intf_register(NULL));
esp_netif_inherent_config_t esp_netif_base_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
esp_netif_config_t esp_netif_config = {
.base = &esp_netif_base_config,
.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH
};
char if_key_str[10];
char if_desc_str[10];
char num_str[3];
for (int i = 0; i < eth_port_cnt; i++) {
itoa(i, num_str, 10);
strcat(strcpy(if_key_str, "ETH_"), num_str);
strcat(strcpy(if_desc_str, "eth"), num_str);
esp_netif_base_config.if_key = if_key_str;
esp_netif_base_config.if_desc = if_desc_str;
esp_netif_base_config.route_prio -= i*5;
esp_netif_t *eth_netif = esp_netif_new(&esp_netif_config);
// attach Ethernet driver to TCP/IP stack
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handles[i])));
}
for (int i = 0; i < eth_port_cnt; i++) {
ESP_ERROR_CHECK(esp_eth_start(eth_handles[i]));
}
}
IRAM_ATTR bool ts_callback(esp_eth_mediator_t *eth, void *user_args)
{
gpio_set_level(CONFIG_EXAMPLE_PTP_PULSE_GPIO, s_gpio_level ^= 1);
// Set the next target time
struct timespec interval = {
.tv_sec = 0,
.tv_nsec = CONFIG_EXAMPLE_PTP_PULSE_WIDTH_NS
};
timespecadd(&s_next_time, &interval, &s_next_time);
struct timespec curr_time;
esp_eth_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);
}
return false;
}
void app_main(void)
{
init_ethernet_and_netif();
int pid = ptpd_start("ETH_0");
struct timespec cur_time;
// wait for the clock to be available
while (esp_eth_clock_gettime(CLOCK_PTP_SYSTEM, &cur_time) == -1) {
vTaskDelay(pdMS_TO_TICKS(500));
}
// register callback function which will toggle output pin
esp_eth_clock_register_target_cb(CLOCK_PTP_SYSTEM, ts_callback);
// initialize output pin
gpio_config_t gpio_out_cfg = {
.pin_bit_mask = (1ULL << CONFIG_EXAMPLE_PTP_PULSE_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&gpio_out_cfg);
gpio_set_level(CONFIG_EXAMPLE_PTP_PULSE_GPIO, 0);
bool first_pass = true;
bool clock_source_valid = false;
bool clock_source_valid_last = false;
int32_t clock_source_valid_cnt = 0;
while (1) {
struct ptpd_status_s ptp_status;
// if valid PTP status
if (ptpd_status(pid, &ptp_status) == 0) {
if (ptp_status.clock_source_valid) {
clock_source_valid_cnt++;
} else {
clock_source_valid_cnt = 0;
}
} else {
clock_source_valid_cnt = 0;
}
// consider the source valid only after n consequent intervals to be sure clock was synced
if (clock_source_valid_cnt > 2) {
clock_source_valid = true;
} else {
clock_source_valid = false;
}
// source validity changed => resync the pulse for ptp slave OR when the first pass to PTP master
// starts generating its pulses
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);
// compute the next target time
s_next_time.tv_sec = 1;
timespecadd(&s_next_time, &cur_time, &s_next_time);
s_next_time.tv_nsec = CONFIG_EXAMPLE_PTP_PULSE_WIDTH_NS;
ESP_LOGI(TAG, "Starting Pulse train");
ESP_LOGI(TAG, "curr time: %llu.%09lu", cur_time.tv_sec, cur_time.tv_nsec);
ESP_LOGI(TAG, "next time: %llu.%09lu", s_next_time.tv_sec, s_next_time.tv_nsec);
s_gpio_level = 0;
gpio_set_level(CONFIG_EXAMPLE_PTP_PULSE_GPIO, s_gpio_level);
esp_eth_clock_set_target_time(CLOCK_PTP_SYSTEM, &s_next_time);
}
clock_source_valid_last = clock_source_valid;
}
}

View File

@ -0,0 +1,10 @@
CONFIG_IDF_TARGET="esp32p4"
CONFIG_ESP_NETIF_L2_TAP=y
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_NETUTILS_PTPD=y
CONFIG_NETUTILS_PTPD_CLIENT=y
CONFIG_NETUTILS_PTPD_SERVER=y