diff --git a/components/esp_eth/include/esp_eth_com.h b/components/esp_eth/include/esp_eth_com.h index a2c4945943..5435fddb32 100644 --- a/components/esp_eth/include/esp_eth_com.h +++ b/components/esp_eth/include/esp_eth_com.h @@ -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 * diff --git a/components/esp_eth/include/esp_eth_driver.h b/components/esp_eth/include/esp_eth_driver.h index 3a253f71c4..0c5f183cac 100644 --- a/components/esp_eth/include/esp_eth_driver.h +++ b/components/esp_eth/include/esp_eth_driver.h @@ -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 diff --git a/components/esp_eth/include/esp_eth_mac.h b/components/esp_eth/include/esp_eth_mac.h index cabfb12723..740442dcbb 100644 --- a/components/esp_eth/include/esp_eth_mac.h +++ b/components/esp_eth/include/esp_eth_mac.h @@ -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 * diff --git a/components/esp_eth/include/esp_eth_mac_esp.h b/components/esp_eth/include/esp_eth_mac_esp.h index f18bf4d75c..bb62cf7719 100644 --- a/components/esp_eth/include/esp_eth_mac_esp.h +++ b/components/esp_eth/include/esp_eth_mac_esp.h @@ -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 = \ diff --git a/components/esp_eth/include/esp_private/eth_mac_esp_dma.h b/components/esp_eth/include/esp_private/eth_mac_esp_dma.h index 3fc496534c..2445379a8e 100644 --- a/components/esp_eth/include/esp_private/eth_mac_esp_dma.h +++ b/components/esp_eth/include/esp_private/eth_mac_esp_dma.h @@ -9,7 +9,9 @@ extern "C" { #endif +#include #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 * diff --git a/components/esp_eth/linker.lf b/components/esp_eth/linker.lf index 3bb1d9c54c..8dcdca709f 100644 --- a/components/esp_eth/linker.lf +++ b/components/esp_eth/linker.lf @@ -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) diff --git a/components/esp_eth/src/esp_eth.c b/components/esp_eth/src/esp_eth.c index d9439ca316..3f9b63a067 100644 --- a/components/esp_eth/src/esp_eth.c +++ b/components/esp_eth/src/esp_eth.c @@ -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, ð_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 diff --git a/components/esp_eth/src/esp_eth_netif_glue.c b/components/esp_eth/src/esp_eth_netif_glue.c index f3bc360050..7fa38b29c4 100644 --- a/components/esp_eth/src/esp_eth_netif_glue.c +++ b/components/esp_eth/src/esp_eth_netif_glue.c @@ -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 = { diff --git a/components/esp_eth/src/mac/esp_eth_mac_esp.c b/components/esp_eth/src/mac/esp_eth_mac_esp.c index 14bfec2f63..f2ecfdedf4 100644 --- a/components/esp_eth/src/mac/esp_eth_mac_esp.c +++ b/components/esp_eth/src/mac/esp_eth_mac_esp.c @@ -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: diff --git a/components/esp_eth/src/mac/esp_eth_mac_esp_dma.c b/components/esp_eth/src/mac/esp_eth_mac_esp_dma.c index 73f5ee40c7..f8901d6a5a 100644 --- a/components/esp_eth/src/mac/esp_eth_mac_esp_dma.c +++ b/components/esp_eth/src/mac/esp_eth_mac_esp_dma.c @@ -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 */ diff --git a/components/esp_eth/test_apps/main/esp_eth_test_esp_emac.c b/components/esp_eth/test_apps/main/esp_eth_test_esp_emac.c index 5056c86482..518efe0602 100644 --- a/components/esp_eth/test_apps/main/esp_eth_test_esp_emac.c +++ b/components/esp_eth/test_apps/main/esp_eth_test_esp_emac.c @@ -5,6 +5,7 @@ */ #include #include +#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)); diff --git a/components/esp_netif/README.md b/components/esp_netif/README.md deleted file mode 100644 index c2000c8cb4..0000000000 --- a/components/esp_netif/README.md +++ /dev/null @@ -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 user’s 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. diff --git a/components/esp_netif/include/esp_vfs_l2tap.h b/components/esp_netif/include/esp_vfs_l2tap.h index 27c807beff..8c76c6fbca 100644 --- a/components/esp_netif/include/esp_vfs_l2tap.h +++ b/components/esp_netif/include/esp_vfs_l2tap.h @@ -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 #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 } diff --git a/components/esp_netif/test_apps/.build-test-rules.yml b/components/esp_netif/test_apps/.build-test-rules.yml index d6bcee11b8..7baad88301 100644 --- a/components/esp_netif/test_apps/.build-test-rules.yml +++ b/components/esp_netif/test_apps/.build-test-rules.yml @@ -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 diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/README.md b/components/esp_netif/test_apps/test_app_vfs_l2tap/README.md index f708a1985a..4873c15b15 100644 --- a/components/esp_netif/test_apps/test_app_vfs_l2tap/README.md +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/README.md @@ -1,2 +1,2 @@ -| Supported Targets | ESP32 | -| ----------------- | ----- | +| Supported Targets | ESP32 | ESP32-P4 | +| ----------------- | ----- | -------- | diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/main/test_vfs_l2tap.c b/components/esp_netif/test_apps/test_app_vfs_l2tap/main/test_vfs_l2tap.c index 074112bbe8..ff766f309b 100644 --- a/components/esp_netif/test_apps/test_app_vfs_l2tap/main/test_vfs_l2tap.c +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/main/test_vfs_l2tap.c @@ -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, ð_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(ð_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(ð_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, ð_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(ð_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 diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/pytest_esp_vfs_l2tap.py b/components/esp_netif/test_apps/test_app_vfs_l2tap/pytest_esp_vfs_l2tap.py index 966483b517..a93a9a9e34 100644 --- a/components/esp_netif/test_apps/test_app_vfs_l2tap/pytest_esp_vfs_l2tap.py +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/pytest_esp_vfs_l2tap.py @@ -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() diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.defaults b/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.ci.defaults similarity index 100% rename from components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.defaults rename to components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.ci.defaults diff --git a/components/esp_netif/vfs_l2tap/esp_vfs_l2tap.c b/components/esp_netif/vfs_l2tap/esp_vfs_l2tap.c index 69b1959f41..e6081e38ae 100644 --- a/components/esp_netif/vfs_l2tap/esp_vfs_l2tap.c +++ b/components/esp_netif/vfs_l2tap/esp_vfs_l2tap.c @@ -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 #include #include +#include #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, ð_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); diff --git a/components/hal/emac_hal.c b/components/hal/emac_hal.c index 427809f939..a8ee895ffc 100644 --- a/components/hal/emac_hal.c +++ b/components/hal/emac_hal.c @@ -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 */ diff --git a/components/hal/esp32/include/hal/emac_ll.h b/components/hal/esp32/include/hal/emac_ll.h index c858552fb2..b6d8b983f8 100644 --- a/components/hal/esp32/include/hal/emac_ll.h +++ b/components/hal/esp32/include/hal/emac_ll.h @@ -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) diff --git a/components/hal/esp32p4/include/hal/emac_ll.h b/components/hal/esp32p4/include/hal/emac_ll.h index 33a5ed8a5c..2dfca8d166 100644 --- a/components/hal/esp32p4/include/hal/emac_ll.h +++ b/components/hal/esp32p4/include/hal/emac_ll.h @@ -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; diff --git a/components/hal/include/hal/emac_hal.h b/components/hal/include/hal/emac_hal.h index 70b5e86478..953adb36d8 100644 --- a/components/hal/include/hal/emac_hal.h +++ b/components/hal/include/hal/emac_hal.h @@ -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 diff --git a/components/hal/include/hal/eth_types.h b/components/hal/include/hal/eth_types.h index 018d958f15..42ad5de57b 100644 --- a/components/hal/include/hal/eth_types.h +++ b/components/hal/include/hal/eth_types.h @@ -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 diff --git a/components/newlib/platform_include/esp_newlib.h b/components/newlib/platform_include/esp_newlib.h index fe5e4cf343..23da9f2d6b 100644 --- a/components/newlib/platform_include/esp_newlib.h +++ b/components/newlib/platform_include/esp_newlib.h @@ -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 */ diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index 3b6555c857..a7fee67524 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -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 diff --git a/components/soc/esp32p4/include/soc/clk_tree_defs.h b/components/soc/esp32p4/include/soc/clk_tree_defs.h index d621c6c899..9b3e05865f 100644 --- a/components/soc/esp32p4/include/soc/clk_tree_defs.h +++ b/components/soc/esp32p4/include/soc/clk_tree_defs.h @@ -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 */ diff --git a/components/soc/esp32p4/include/soc/emac_ptp_struct.h b/components/soc/esp32p4/include/soc/emac_ptp_struct.h new file mode 100644 index 0000000000..ef01da6f8a --- /dev/null +++ b/components/soc/esp32p4/include/soc/emac_ptp_struct.h @@ -0,0 +1,268 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#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 diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index 1aa1045d73..e6e4c51e5e 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -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 */ diff --git a/components/soc/esp32p4/ld/esp32p4.peripherals.ld b/components/soc/esp32p4/ld/esp32p4.peripherals.ld index fd880fc50a..e82a48159e 100644 --- a/components/soc/esp32p4/ld/esp32p4.peripherals.ld +++ b/components/soc/esp32p4/ld/esp32p4.peripherals.ld @@ -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); diff --git a/docs/en/api-reference/network/esp_eth.rst b/docs/en/api-reference/network/esp_eth.rst index 0b1ee22175..6f039d86d1 100644 --- a/docs/en/api-reference/network/esp_eth.rst +++ b/docs/en/api-reference/network/esp_eth.rst @@ -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 ` 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. diff --git a/docs/en/api-reference/network/esp_netif.rst b/docs/en/api-reference/network/esp_netif.rst index e9ffb448be..8e20c5df2c 100644 --- a/docs/en/api-reference/network/esp_netif.rst +++ b/docs/en/api-reference/network/esp_netif.rst @@ -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 diff --git a/docs/en/api-reference/network/esp_netif_programming.rst b/docs/en/api-reference/network/esp_netif_programming.rst index e07f2cdf2b..e2685e96bc 100644 --- a/docs/en/api-reference/network/esp_netif_programming.rst +++ b/docs/en/api-reference/network/esp_netif_programming.rst @@ -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 ` 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 ` 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 ` 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: diff --git a/examples/ethernet/.build-test-rules.yml b/examples/ethernet/.build-test-rules.yml index 965eb6b820..783ab909e1 100644 --- a/examples/ethernet/.build-test-rules.yml +++ b/examples/ethernet/.build-test-rules.yml @@ -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 diff --git a/examples/ethernet/README.md b/examples/ethernet/README.md index 1b059a1caf..abf7b2d176 100644 --- a/examples/ethernet/README.md +++ b/examples/ethernet/README.md @@ -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 diff --git a/examples/ethernet/ptp/CMakeLists.txt b/examples/ethernet/ptp/CMakeLists.txt new file mode 100644 index 0000000000..532e3753b9 --- /dev/null +++ b/examples/ethernet/ptp/CMakeLists.txt @@ -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) diff --git a/examples/ethernet/ptp/README.md b/examples/ethernet/ptp/README.md new file mode 100644 index 0000000000..52428895f2 --- /dev/null +++ b/examples/ethernet/ptp/README.md @@ -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.) diff --git a/examples/ethernet/ptp/components/esp_eth_time/CMakeLists.txt b/examples/ethernet/ptp/components/esp_eth_time/CMakeLists.txt new file mode 100644 index 0000000000..b80be4eafb --- /dev/null +++ b/examples/ethernet/ptp/components/esp_eth_time/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "esp_eth_time.c" + PRIV_REQUIRES esp_eth + INCLUDE_DIRS ".") diff --git a/examples/ethernet/ptp/components/esp_eth_time/README.md b/examples/ethernet/ptp/components/esp_eth_time/README.md new file mode 100644 index 0000000000..b369992b16 --- /dev/null +++ b/examples/ethernet/ptp/components/esp_eth_time/README.md @@ -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. diff --git a/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.c b/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.c new file mode 100644 index 0000000000..b9f9922375 --- /dev/null +++ b/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.c @@ -0,0 +1,142 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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; +} diff --git a/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.h b/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.h new file mode 100644 index 0000000000..0fa34f078d --- /dev/null +++ b/examples/ethernet/ptp/components/esp_eth_time/esp_eth_time.h @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "esp_eth_driver.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLOCK_PTP_SYSTEM ((clockid_t) 19) + +/** + * @brief Configuration of clock during initialization + * + */ +typedef struct { + esp_eth_handle_t eth_hndl; +} esp_eth_clock_cfg_t; + +/** + * @brief The mode of clock adjustment. + * + */ +typedef enum { + ETH_CLK_ADJ_FREQ_SCALE, +} esp_eth_clock_adj_mode_t; + +/** + * @brief Structure containing parameters for adjusting the Ethernet clock. + * + */ +typedef struct { + /** + * @brief The mode of clock adjustment. + * + */ + esp_eth_clock_adj_mode_t mode; + + /** + * @brief The frequency scale factor when in ETH_CLK_ADJ_FREQ_SCALE mode. + * + * This value represents the ratio of the desired frequency to the actual + * frequency. A value greater than 1 increases the frequency, while a value + * less than 1 decreases the frequency. + */ + double freq_scale; +} esp_eth_clock_adj_param_t; + +/** + * @brief Adjust the system clock frequency + * + * @param clk_id Identifier of the clock to adjust + * @param buf Pointer to the adjustment parameters + * + * @return + * - 0: Success + * - -1: Failure + */ +int esp_eth_clock_adjtime(clockid_t clk_id, esp_eth_clock_adj_param_t *adj); + +/** + * @brief Set the system clock time + * + * @param clk_id Identifier of the clock to set + * @param tp Pointer to the new time + * + * @return + * - 0: Success + * - -1: Failure + */ +int esp_eth_clock_settime(clockid_t clock_id, const struct timespec *tp); + +/** + * @brief Get the current system clock time + * + * @param clk_id Identifier of the clock to query + * @param tp Pointer to the buffer to store the current time + * + * @return + * - 0: Success + * - -1: Failure + */ +int esp_eth_clock_gettime(clockid_t clock_id, struct timespec *tp); + +/** + * @brief Set the target time for the system clock. + * + * @param clk_id Identifier of the clock to set the target time for + * @param tp Pointer to the target time + * + * @return + * - 0: Success + * - -1: Failure + */ +int esp_eth_clock_set_target_time(clockid_t clock_id, struct timespec *tp); + +/** + * @brief Register callback function invoked on Time Stamp target time exceeded interrupt + * + * @param clock_id Identifier of the clock + * @param ts_callback callback function to be registered + * @return + * - 0: Success + * - -1: Failure + */ +int esp_eth_clock_register_target_cb(clockid_t clock_id, + ts_target_exceed_cb_from_isr_t ts_callback); + +/** + * @brief Initialize the Ethernet clock subsystem + * + * @param clk_id Identifier of the clock to initialize + * @param cfg Pointer to the configuration structure + * + * @return + * - ESP_OK: Success + * - ESP_FAIL: Failure + */ +esp_err_t esp_eth_clock_init(clockid_t clock_id, esp_eth_clock_cfg_t *cfg); + +#ifdef __cplusplus +} +#endif diff --git a/examples/ethernet/ptp/components/ptpd/CMakeLists.txt b/examples/ethernet/ptp/components/ptpd/CMakeLists.txt new file mode 100644 index 0000000000..bfa611ce8c --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/CMakeLists.txt @@ -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) diff --git a/examples/ethernet/ptp/components/ptpd/Kconfig.projbuild b/examples/ethernet/ptp/components/ptpd/Kconfig.projbuild new file mode 100644 index 0000000000..6fbcfbd685 --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/Kconfig.projbuild @@ -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 diff --git a/examples/ethernet/ptp/components/ptpd/Make.defs b/examples/ethernet/ptp/components/ptpd/Make.defs new file mode 100644 index 0000000000..c3f5f60ffb --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/Make.defs @@ -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 diff --git a/examples/ethernet/ptp/components/ptpd/Makefile b/examples/ethernet/ptp/components/ptpd/Makefile new file mode 100644 index 0000000000..3f3e98a361 --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/Makefile @@ -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 diff --git a/examples/ethernet/ptp/components/ptpd/README.md b/examples/ethernet/ptp/components/ptpd/README.md new file mode 100644 index 0000000000..8f56b771ef --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/README.md @@ -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. diff --git a/examples/ethernet/ptp/components/ptpd/idf_component.yml b/examples/ethernet/ptp/components/ptpd/idf_component.yml new file mode 100644 index 0000000000..97451ea972 --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + esp_eth_time: + path: ${IDF_PATH}/examples/ethernet/ptp/components/esp_eth_time diff --git a/examples/ethernet/ptp/components/ptpd/include/ptpd.h b/examples/ethernet/ptp/components/ptpd/include/ptpd.h new file mode 100644 index 0000000000..0a65c4885b --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/include/ptpd.h @@ -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 +#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 */ diff --git a/examples/ethernet/ptp/components/ptpd/port/esp_ptpd.c b/examples/ethernet/ptp/components/ptpd/port/esp_ptpd.c new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/ethernet/ptp/components/ptpd/ptpd.c b/examples/ethernet/ptp/components/ptpd/ptpd.c new file mode 100644 index 0000000000..c20657338a --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/ptpd.c @@ -0,0 +1,2142 @@ +/* + * 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/ptpd.c + * + * 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. + * + ****************************************************************************/ + +#define ESP_PTP 1 + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#ifndef ESP_PTP +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifndef ESP_PTP +#include +#endif +#include +#include + +#include +#include +#ifndef ESP_PTP +#include +#endif + +#include +#include +#include +#ifndef ESP_PTP +#include +#include +#endif + +#include "ptpv2.h" + +#ifdef ESP_PTP +#include "ptpd.h" +#include "esp_eth_driver.h" +#include "esp_vfs_l2tap.h" +#include "semaphore.h" +#include "esp_log.h" +#include "esp_err.h" +#include "lwip/prot/ethernet.h" // Ethernet headers + +#include "esp_eth_time.h" + +#define ETH_TYPE_PTP 0x88F7 + +#define ERROR ESP_FAIL +#define OK ESP_OK + +#define UNUSED (void) + +#define MSEC_PER_SEC 1000 +#define NSEC_PER_USEC 1000 +#define NSEC_PER_MSEC 1000000ll +#define NSEC_PER_SEC 1000000000ll + +// To able to set either only server or only client +#ifndef CONFIG_NETUTILS_PTPD_TIMEOUT_MS +#define CONFIG_NETUTILS_PTPD_TIMEOUT_MS 0 +#endif +#ifndef CONFIG_NETUTILS_PTPD_SETTIME_THRESHOLD_MS +#define CONFIG_NETUTILS_PTPD_SETTIME_THRESHOLD_MS 0 +#endif +#ifndef CONFIG_NETUTILS_PTPD_MAX_PATH_DELAY_NS +#define CONFIG_NETUTILS_PTPD_MAX_PATH_DELAY_NS 0 +#endif +#ifndef CONFIG_NETUTILS_PTPD_DELAYREQ_AVGCOUNT +#define CONFIG_NETUTILS_PTPD_DELAYREQ_AVGCOUNT 0 +#endif +#ifndef CONFIG_NETUTILS_PTPD_DELAYRESP_INTERVAL +#define CONFIG_NETUTILS_PTPD_DELAYRESP_INTERVAL 0 +#endif +#ifndef CONFIG_NETUTILS_PTPD_PATH_DELAY_STABILITY_NS +#define CONFIG_NETUTILS_PTPD_PATH_DELAY_STABILITY_NS 0 +#endif + +#define clock_timespec_subtract(ts1, ts2, ts3) timespecsub(ts1, ts2, ts3) +#define clock_timespec_add(ts1, ts2, ts3) timespecadd(ts1, ts2, ts3) + +#endif // ESP_PTP + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef ESP_PTP +#define ADJ_FREQ_MAX 512000 // TODO tuneup +typedef struct +{ + int32_t kp; + int32_t ki; + int32_t drift_acc; +} pi_cntrl_t; +#endif // ESP_PTP + +/* Carrier structure for querying PTPD status */ + +struct ptpd_statusreq_s +{ + FAR sem_t *done; + FAR struct ptpd_status_s *dest; +}; + +/* Main PTPD state storage */ + +struct ptp_state_s +{ + /* Request for PTPD task to stop or report status */ + + bool stop; + struct ptpd_statusreq_s status_req; + +#ifdef ESP_PTP + uint8_t intf_hw_addr[ETH_ADDR_LEN]; + int ptp_socket; + + int64_t remote_time_ns_prev; + int64_t local_time_ns_prev; + + int64_t last_offset_ns; + + pi_cntrl_t offset_pi; +#else + /* Address of network interface we are operating on */ + + struct sockaddr_in interface_addr; + + /* Socket bound to interface for transmission */ + + int tx_socket; + + /* Sockets for PTP event and information ports */ + + int event_socket; + + int info_socket; +#endif // ESP_PTP + + /* Our own identity as a clock source */ + + struct ptp_announce_s own_identity; + + /* Sequence number counters per message type */ + + uint16_t announce_seq; + uint16_t sync_seq; + uint16_t delay_req_seq; + + /* Previous measurement and estimated clock drift rate */ + + struct timespec last_delta_timestamp; + int64_t last_delta_ns; + int64_t last_adjtime_ns; + long drift_avg_total_ms; + long drift_ppb; + + /* Identity of currently selected clock source, + * from the latest announcement message. + * + * The timestamps are used for timeout when a source disappears. + * They are from the local CLOCK_MONOTONIC. + */ + + bool selected_source_valid; /* True if operating as client */ + struct ptp_announce_s selected_source; /* Currently selected server */ + 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 */ + + /* Last transmitted packet timestamps (CLOCK_MONOTONIC) + * Used to set transmission interval. + */ + + struct timespec last_transmitted_sync; + struct timespec last_transmitted_announce; + struct timespec last_transmitted_delayresp; + struct timespec last_transmitted_delayreq; + + /* Timestamps related to path delay calculation (CLOCK_REALTIME) */ + + bool can_send_delayreq; + struct timespec delayreq_time; + int path_delay_avgcount; + long path_delay_ns; + long delayreq_interval; + + /* Latest received packet and its timestamp (CLOCK_REALTIME) */ + + struct timespec rxtime; + union + { + struct ptp_header_s header; + struct ptp_announce_s announce; + struct ptp_sync_s sync; + struct ptp_follow_up_s follow_up; + struct ptp_delay_req_s delay_req; + struct ptp_delay_resp_s delay_resp; + uint8_t raw[128]; + } rxbuf; + +#ifndef ESP_PTP + uint8_t rxcmsg[CMSG_LEN(sizeof(struct timeval))]; +#endif // ESP_PTP + + /* Buffered sync packet for two-step clock setting where server sends + * the accurate timestamp in a separate follow-up message. + */ + + struct ptp_sync_s twostep_packet; + struct timespec twostep_rxtime; +}; + +#ifdef CONFIG_NETUTILS_PTPD_SERVER +# define PTPD_POLL_INTERVAL CONFIG_NETUTILS_PTPD_SYNC_INTERVAL_MSEC +#else +# define PTPD_POLL_INTERVAL CONFIG_NETUTILS_PTPD_TIMEOUT_MS +#endif + +/* PTP debug messages are enabled by either CONFIG_DEBUG_NET_INFO + * or separately by CONFIG_NETUTILS_PTPD_DEBUG. This simplifies + * debugging without having excessive amount of logging from net. + */ + +#ifdef ESP_PTP +static const char *TAG = "ptpd"; +#define ptpinfo(format, ...) ESP_LOGI(TAG, format, ##__VA_ARGS__) +#define ptpwarn(format, ...) ESP_LOGW(TAG, format, ##__VA_ARGS__) +#define ptperr(format, ...) ESP_LOGE(TAG, format, ##__VA_ARGS__) +#else +#ifdef CONFIG_NETUTILS_PTPD_DEBUG +# define ptpinfo _info +# define ptpwarn _warn +# define ptperr _err +#else +# define ptpinfo ninfo +# define ptpwarn nwarn +# define ptperr nerr +#endif +#endif // ESP_PTP + +#ifdef ESP_PTP +static struct ptp_state_s *s_state; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#ifdef ESP_PTP +static void ptp_create_eth_frame(struct ptp_state_s *state, uint8_t *eth_frame, void *ptp_msg, uint16_t ptp_msg_len) +{ + struct eth_hdr eth_hdr = { + //.dest.addr = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E}, // TODO only for Pdelay_Req, Pdelay_Resp and Pdelay_Resp_Follow_Up + .dest.addr = {0x01, 0x1B, 0x19, 0x00, 0x00, 0x00}, // All except peer delay messages, ptp4l sends everything at this addr + .type = htons(ETH_TYPE_PTP) + }; + memcpy(ð_hdr.src.addr, state->intf_hw_addr, ETH_ADDR_LEN); + + memcpy(eth_frame, ð_hdr, sizeof(eth_hdr)); + memcpy(eth_frame + sizeof(eth_hdr), ptp_msg, ptp_msg_len); +} + +static int ptp_net_send(FAR struct ptp_state_s *state, void *ptp_msg, uint16_t ptp_msg_len, struct timespec *ts) +{ + uint8_t eth_frame[ptp_msg_len + ETH_HEADER_LEN]; + ptp_create_eth_frame(state, eth_frame, ptp_msg, ptp_msg_len); + + // 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, ts exists and ts_info is valid + if (ret > 0 && ts && ts_info->type == L2TAP_IREC_TIME_STAMP) + { + *ts = *(struct timespec *)ts_info->data; + } + + return ret; +} + +static int ptp_net_recv(FAR struct ptp_state_s *state, void *ptp_msg, uint16_t ptp_msg_len, struct timespec *ts) +{ + uint8_t eth_frame[ptp_msg_len + ETH_HEADER_LEN]; + + // 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 = read(state->ptp_socket, &ptp_msg_ext_buff, 0); + + // check if read was successful, ts exists and ts_info is valid + if (ret > 0 && ts && ts_info->type == L2TAP_IREC_TIME_STAMP) + { + *ts = *(struct timespec *)ts_info->data; + } + + memcpy(ptp_msg, ð_frame[ETH_HEADER_LEN], ret); + + return ret; +} + +static int64_t timespec_to_ns(FAR const struct timespec *ts) +{ + return ts->tv_sec * NSEC_PER_SEC + (ts->tv_nsec); +} +#endif // ESP_PTP + +/* Convert from timespec to PTP format */ + +static void timespec_to_ptp_format(FAR struct timespec *ts, + FAR uint8_t *timestamp) +{ + /* IEEE 1588 uses 48 bits for seconds and 32 bits for nanoseconds, + * both fields big-endian. + */ + +#ifdef CONFIG_SYSTEM_TIME64 + timestamp[0] = (uint8_t)(ts->tv_sec >> 40); + timestamp[1] = (uint8_t)(ts->tv_sec >> 32); +#else + timestamp[0] = 0; + timestamp[1] = 0; +#endif + timestamp[2] = (uint8_t)(ts->tv_sec >> 24); + timestamp[3] = (uint8_t)(ts->tv_sec >> 16); + timestamp[4] = (uint8_t)(ts->tv_sec >> 8); + timestamp[5] = (uint8_t)(ts->tv_sec >> 0); + + timestamp[6] = (uint8_t)(ts->tv_nsec >> 24); + timestamp[7] = (uint8_t)(ts->tv_nsec >> 16); + timestamp[8] = (uint8_t)(ts->tv_nsec >> 8); + timestamp[9] = (uint8_t)(ts->tv_nsec >> 0); +} + +/* Convert from PTP format to timespec */ + +static void ptp_format_to_timespec(FAR const uint8_t *timestamp, + FAR struct timespec *ts) +{ + ts->tv_sec = + (((int64_t)timestamp[0]) << 40) + | (((int64_t)timestamp[1]) << 32) + | (((int64_t)timestamp[2]) << 24) + | (((int64_t)timestamp[3]) << 16) + | (((int64_t)timestamp[4]) << 8) + | (((int64_t)timestamp[5]) << 0); + + ts->tv_nsec = + (((long)timestamp[6]) << 24) + | (((long)timestamp[7]) << 16) + | (((long)timestamp[8]) << 8) + | (((long)timestamp[9]) << 0); +} + +/* Returns true if A is a better clock source than B. + * Implements Best Master Clock algorithm from IEEE-1588. + */ + +static bool is_better_clock(FAR const struct ptp_announce_s *a, + FAR const struct ptp_announce_s *b) +{ + if (a->gm_priority1 < b->gm_priority1 /* Main priority field */ + || a->gm_quality[0] < b->gm_quality[0] /* Clock class */ + || a->gm_quality[1] < b->gm_quality[1] /* Clock accuracy */ + || a->gm_quality[2] < b->gm_quality[2] /* Clock variance high byte */ + || a->gm_quality[3] < b->gm_quality[3] /* Clock variance low byte */ + || a->gm_priority2 < b->gm_priority2 /* Sub priority field */ + || memcmp(a->gm_identity, b->gm_identity, sizeof(a->gm_identity)) < 0) + { + return true; + } + else + { + return false; + } +} + +static int64_t timespec_to_ms(FAR const struct timespec *ts) +{ + return ts->tv_sec * MSEC_PER_SEC + (ts->tv_nsec / NSEC_PER_MSEC); +} + +/* Get positive or negative delta between two timespec values. + * If value would exceed int64 limit (292 years), return INT64_MAX/MIN. + */ + +static int64_t timespec_delta_ns(FAR const struct timespec *ts1, + FAR const struct timespec *ts2) +{ + int64_t delta_s; + + delta_s = ts1->tv_sec - ts2->tv_sec; + +#ifdef CONFIG_SYSTEM_TIME64 + /* Conversion to nanoseconds could overflow if the system time is 64-bit */ + + if (delta_s >= INT64_MAX / NSEC_PER_SEC) + { + return INT64_MAX; + } + else if (delta_s <= INT64_MIN / NSEC_PER_SEC) + { + return INT64_MIN; + } +#endif + + return delta_s * NSEC_PER_SEC + (ts1->tv_nsec - ts2->tv_nsec); +} + +/* Check if the currently selected source is still valid */ + +static bool is_selected_source_valid(FAR struct ptp_state_s *state) +{ + struct timespec time_now; + struct timespec delta; + + if ((state->selected_source.header.messagetype & PTP_MSGTYPE_MASK) + != PTP_MSGTYPE_ANNOUNCE) + { + return false; /* Uninitialized value */ + } + + /* Note: this uses monotonic clock to track the timeout even when + * system clock is adjusted. + */ + + clock_gettime(CLOCK_MONOTONIC, &time_now); + clock_timespec_subtract(&time_now, &state->last_received_sync, &delta); + + if (timespec_to_ms(&delta) > CONFIG_NETUTILS_PTPD_TIMEOUT_MS) + { +#ifdef ESP_PTP + ESP_LOGD(TAG, "Too long time since received packet\n"); +#endif // ESP_PTP + return false; /* Too long time since received packet */ + } + + return true; +} + +/* Increment sequence number for packet type, and copy to header */ + +static void ptp_increment_sequence(FAR uint16_t *sequence_num, + FAR struct ptp_header_s *hdr) +{ + *sequence_num += 1; + hdr->sequenceid[0] = (uint8_t)(*sequence_num >> 8); + hdr->sequenceid[1] = (uint8_t)(*sequence_num); +} + +/* Get sequence number from received packet */ + +static uint16_t ptp_get_sequence(FAR const struct ptp_header_s *hdr) +{ + return ((uint16_t)hdr->sequenceid[0] << 8) | hdr->sequenceid[1]; +} + +/* Get current system timestamp as a timespec + * TODO: Possibly add support for selecting different clock or using + * architecture-specific interface for clock access. + */ + +static int ptp_gettime(FAR struct ptp_state_s *state, + FAR struct timespec *ts) +{ + UNUSED(state); +#ifdef ESP_PTP + return esp_eth_clock_gettime(CLOCK_PTP_SYSTEM, ts); +#else + return clock_gettime(CLOCK_REALTIME, ts); +#endif // ESP_PTP +} + +/* Change current system timestamp by jumping */ + +static int ptp_settime(FAR struct ptp_state_s *state, + FAR struct timespec *ts) +{ + UNUSED(state); +#ifdef ESP_PTP + return esp_eth_clock_settime(CLOCK_PTP_SYSTEM, ts); +#else + return clock_settime(CLOCK_REALTIME, ts); +#endif // ESP_PTP +} + +#ifndef ESP_PTP +/* Smoothly adjust timestamp. */ + +static int ptp_adjtime(FAR struct ptp_state_s *state, int64_t delta_ns) +{ + struct timeval delta; + + delta.tv_sec = delta_ns / NSEC_PER_SEC; + delta_ns -= (int64_t)delta.tv_sec * NSEC_PER_SEC; + delta.tv_usec = delta_ns / NSEC_PER_USEC; + return adjtime(&delta, NULL); +} +#endif // !ESP_PTP + +#ifndef ESP_PTP +/* Get timestamp of latest received packet */ + +static int ptp_getrxtime(FAR struct ptp_state_s *state, + FAR struct msghdr *rxhdr, + FAR struct timespec *ts) +{ + /* Get hardware or kernel timestamp if available */ + +#ifdef CONFIG_NET_TIMESTAMP + struct cmsghdr *cmsg; + + for_each_cmsghdr(cmsg, rxhdr) + { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SO_TIMESTAMP && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) + { + TIMEVAL_TO_TIMESPEC((FAR struct timeval *)CMSG_DATA(cmsg), ts); + + /* Sanity-check the value */ + + if (ts->tv_sec > 0 || ts->tv_nsec > 0) + { + return OK; + } + } + } + + ptpwarn("CONFIG_NET_TIMESTAMP enabled but did not get packet timestamp\n"); +#endif + + /* Fall back to current timestamp */ + + return ptp_gettime(state, ts); +} +#endif // !ESP_PTP + +/* Initialize PTP client/server state and create sockets */ +#ifdef ESP_PTP +static int ptp_initialize_state(FAR struct ptp_state_s *state, + FAR const char *interface) +{ + state->ptp_socket = open("/dev/net/tap", 0); + if (state->ptp_socket < 0) + { + ptperr("Failed to create tx socket: %d\n", errno); + return ERROR; + } + + // Set Ethernet interface on which to get raw frames + if (ioctl(state->ptp_socket, L2TAP_S_INTF_DEVICE, interface) < 0) + { + ptperr("failed to set network interface at socket: %d\n", errno); + return ERROR; + } + + // Set the Ethertype filter (frames with this type will be available through the state->tx_socket) + uint16_t eth_type_filter = ETH_TYPE_PTP; + if (ioctl(state->ptp_socket, L2TAP_S_RCV_FILTER, ð_type_filter) < 0) + { + ptperr("failed to set Ethertype filter: %d\n", errno); + return ERROR; + } + // Enable time stamping in driver + esp_eth_handle_t eth_handle; + if (ioctl(state->ptp_socket, L2TAP_G_DEVICE_DRV_HNDL, ð_handle) < 0) + { + ptperr("failed to get socket eth_handle %d\n", errno); + return ERROR; + } + esp_eth_clock_cfg_t clk_cfg = { + .eth_hndl = eth_handle, + }; + esp_eth_clock_init(CLOCK_PTP_SYSTEM, &clk_cfg); + + // Enable time stamping in L2TAP + if(ioctl(state->ptp_socket, L2TAP_S_TIMESTAMP_EN) < 0) + { + ptperr("failed to enable time stamping in l2 socket: %d\n", errno); + return ERROR; + } + + // get HW address + esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, &state->intf_hw_addr); + + state->remote_time_ns_prev = 0; + state->local_time_ns_prev = 0; + + state->offset_pi.kp = 1; + state->offset_pi.ki = 10; + state->offset_pi.drift_acc = 0; + + state->own_identity.header.version = 2; + state->own_identity.header.domain = CONFIG_NETUTILS_PTPD_DOMAIN; + state->own_identity.header.sourceidentity[0] = state->intf_hw_addr[0]; + state->own_identity.header.sourceidentity[1] = state->intf_hw_addr[1]; + state->own_identity.header.sourceidentity[2] = state->intf_hw_addr[2]; + state->own_identity.header.sourceidentity[3] = 0xff; + state->own_identity.header.sourceidentity[4] = 0xfe; + state->own_identity.header.sourceidentity[5] = state->intf_hw_addr[3]; + state->own_identity.header.sourceidentity[6] = state->intf_hw_addr[4]; + state->own_identity.header.sourceidentity[7] = state->intf_hw_addr[5]; + state->own_identity.header.sourceportindex[0] = 0; + state->own_identity.header.sourceportindex[1] = 1; +#ifdef CONFIG_NETUTILS_PTPD_SERVER + state->own_identity.gm_priority1 = CONFIG_NETUTILS_PTPD_PRIORITY1; + state->own_identity.gm_quality[0] = CONFIG_NETUTILS_PTPD_CLASS; + state->own_identity.gm_quality[1] = CONFIG_NETUTILS_PTPD_ACCURACY; + state->own_identity.gm_quality[2] = 0xff; /* No variance estimate */ + state->own_identity.gm_quality[3] = 0xff; + state->own_identity.gm_priority2 = CONFIG_NETUTILS_PTPD_PRIORITY2; + memcpy(state->own_identity.gm_identity, + state->own_identity.header.sourceidentity, + sizeof(state->own_identity.gm_identity)); + state->own_identity.timesource = CONFIG_NETUTILS_PTPD_CLOCKSOURCE; +#else + state->own_identity.gm_priority1 = 255; // When daemon is statically configured as slave, set the worst +#endif + + s_state = state; + return OK; +} +#else +static int ptp_initialize_state(FAR struct ptp_state_s *state, + FAR const char *interface) +{ + int ret; + struct ifreq req; + struct sockaddr_in bind_addr; + +#ifdef CONFIG_NET_TIMESTAMP + int arg; +#endif + + /* Create sockets */ + + state->tx_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (state->tx_socket < 0) + { + ptperr("Failed to create tx socket: %d\n", errno); + return ERROR; + } + + state->event_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (state->event_socket < 0) + { + ptperr("Failed to create event socket: %d\n", errno); + return ERROR; + } + + + state->info_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (state->info_socket < 0) + { + ptperr("Failed to create info socket: %d\n", errno); + return ERROR; + } + + /* Get address information of the specified interface for binding socket + * Only supports IPv4 currently. + */ + + memset(&req, 0, sizeof(req)); + strncpy(req.ifr_name, interface, sizeof(req.ifr_name)); + + if (ioctl(state->event_socket, SIOCGIFADDR, (unsigned long)&req) < 0) + { + ptperr("Failed to get IP address information for interface %s\n", + interface); + return ERROR; + } + + state->interface_addr = *(struct sockaddr_in *)&req.ifr_ifru.ifru_addr; + + /* Get hardware address to initialize the identity field in header. + * Clock identity is EUI-64, which we make from EUI-48. + */ + + if (ioctl(state->event_socket, SIOCGIFHWADDR, (unsigned long)&req) < 0) + { + ptperr("Failed to get HW address information for interface %s\n", + interface); + return ERROR; + } + + state->own_identity.header.version = 2; + state->own_identity.header.domain = CONFIG_NETUTILS_PTPD_DOMAIN; + state->own_identity.header.sourceidentity[0] = req.ifr_hwaddr.sa_data[0]; + state->own_identity.header.sourceidentity[1] = req.ifr_hwaddr.sa_data[1]; + state->own_identity.header.sourceidentity[2] = req.ifr_hwaddr.sa_data[2]; + state->own_identity.header.sourceidentity[3] = 0xff; + state->own_identity.header.sourceidentity[4] = 0xfe; + state->own_identity.header.sourceidentity[5] = req.ifr_hwaddr.sa_data[3]; + state->own_identity.header.sourceidentity[6] = req.ifr_hwaddr.sa_data[4]; + state->own_identity.header.sourceidentity[7] = req.ifr_hwaddr.sa_data[5]; + state->own_identity.header.sourceportindex[0] = 0; + state->own_identity.header.sourceportindex[1] = 1; + state->own_identity.gm_priority1 = CONFIG_NETUTILS_PTPD_PRIORITY1; + state->own_identity.gm_quality[0] = CONFIG_NETUTILS_PTPD_CLASS; + state->own_identity.gm_quality[1] = CONFIG_NETUTILS_PTPD_ACCURACY; + state->own_identity.gm_quality[2] = 0xff; /* No variance estimate */ + state->own_identity.gm_quality[3] = 0xff; + state->own_identity.gm_priority2 = CONFIG_NETUTILS_PTPD_PRIORITY2; + memcpy(state->own_identity.gm_identity, + state->own_identity.header.sourceidentity, + sizeof(state->own_identity.gm_identity)); + state->own_identity.timesource = CONFIG_NETUTILS_PTPD_CLOCKSOURCE; + + /* Subscribe to PTP multicast address */ + + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr.s_addr = HTONL(PTP_MULTICAST_ADDR); + + clock_gettime(CLOCK_MONOTONIC, &state->last_received_multicast); + + ret = ipmsfilter(&state->interface_addr.sin_addr, + &bind_addr.sin_addr, + MCAST_INCLUDE); + if (ret < 0) + { + ptperr("Failed to bind multicast address: %d\n", errno); + return ERROR; + } + + /* Bind socket for events */ + + bind_addr.sin_port = HTONS(PTP_UDP_PORT_EVENT); + ret = bind(state->event_socket, (struct sockaddr *)&bind_addr, + sizeof(bind_addr)); + if (ret < 0) + { + ptperr("Failed to bind to udp port %d\n", bind_addr.sin_port); + return ERROR; + } + +#ifdef CONFIG_NET_TIMESTAMP + arg = 1; + ret = setsockopt(state->event_socket, SOL_SOCKET, SO_TIMESTAMP, + &arg, sizeof(arg)); + + if (ret < 0) + { + ptperr("Failed to enable SO_TIMESTAMP: %s\n", strerror(errno)); + + /* PTPD can operate without, but with worse accuracy */ + } +#endif + + /* Bind socket for announcements */ + + bind_addr.sin_port = HTONS(PTP_UDP_PORT_INFO); + ret = bind(state->info_socket, (struct sockaddr *)&bind_addr, + sizeof(bind_addr)); + if (ret < 0) + { + ptperr("Failed to bind to udp port %d\n", bind_addr.sin_port); + return ERROR; + } + + /* Bind TX socket to interface address (local addr cannot be multicast) */ + + bind_addr.sin_addr = state->interface_addr.sin_addr; + ret = bind(state->tx_socket, (struct sockaddr *)&bind_addr, + sizeof(bind_addr)); + if (ret < 0) + { + ptperr("Failed to bind tx to port %d\n", bind_addr.sin_port); + return ERROR; + } + + return OK; +} +#endif // ESP_PTP + +/* Unsubscribe multicast and destroy sockets */ + +static int ptp_destroy_state(FAR struct ptp_state_s *state) +{ +#ifdef ESP_PTP + if (state->ptp_socket > 0) + { + close(state->ptp_socket); + state->ptp_socket = -1; + } +#else + struct in_addr mcast_addr; + + mcast_addr.s_addr = HTONL(PTP_MULTICAST_ADDR); + ipmsfilter(&state->interface_addr.sin_addr, + &mcast_addr, + MCAST_EXCLUDE); + + if (state->tx_socket > 0) + { + close(state->tx_socket); + state->tx_socket = -1; + } + + if (state->event_socket > 0) + { + close(state->event_socket); + state->event_socket = -1; + } + + if (state->info_socket > 0) + { + close(state->info_socket); + state->info_socket = -1; + } +#endif // ESP_PTP + return OK; +} + +#ifndef ESP_PTP +/* Re-subscribe multicast address. + * This can become necessary if Ethernet interface gets reset or if external + * IGMP-compliant Ethernet switch gets plugged in. + */ + +static int ptp_check_multicast_status(FAR struct ptp_state_s *state) +{ +#if CONFIG_NETUTILS_PTPD_MULTICAST_TIMEOUT_MS > 0 + struct in_addr mcast_addr; + struct timespec time_now; + struct timespec delta; + + clock_gettime(CLOCK_MONOTONIC, &time_now); + clock_timespec_subtract(&time_now, &state->last_received_multicast, + &delta); + + if (timespec_to_ms(&delta) > CONFIG_NETUTILS_PTPD_MULTICAST_TIMEOUT_MS) + { + /* Remove and re-add the multicast group */ + + state->last_received_multicast = time_now; + + mcast_addr.s_addr = HTONL(PTP_MULTICAST_ADDR); + ipmsfilter(&state->interface_addr.sin_addr, + &mcast_addr, + MCAST_EXCLUDE); + + return ipmsfilter(&state->interface_addr.sin_addr, + &mcast_addr, + MCAST_INCLUDE); + } + +#else + UNUSED(state); +#endif /* CONFIG_NETUTILS_PTPD_MULTICAST_TIMEOUT_MS */ + + return OK; +} +#endif // !ESP_PTP + +/* Send PTP server announcement packet */ + +static int ptp_send_announce(FAR struct ptp_state_s *state) +{ + struct ptp_announce_s msg; +#ifndef ESP_PTP + struct sockaddr_in addr; +#endif // !ESP_PTP + struct timespec ts; + int ret; + +#ifndef ESP_PTP + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = HTONL(PTP_MULTICAST_ADDR); + addr.sin_port = HTONS(PTP_UDP_PORT_INFO); +#endif // !ESP_PTP + + memset(&msg, 0, sizeof(msg)); + msg = state->own_identity; + msg.header.messagetype = PTP_MSGTYPE_ANNOUNCE; + msg.header.messagelength[1] = sizeof(msg); + + ptp_increment_sequence(&state->announce_seq, &msg.header); + ptp_gettime(state, &ts); + timespec_to_ptp_format(&ts, msg.origintimestamp); + +#ifdef ESP_PTP + ret = ptp_net_send(state, &msg, sizeof(msg), NULL); +#else + ret = sendto(state->tx_socket, &msg, sizeof(msg), 0, + (struct sockaddr *)&addr, sizeof(addr)); +#endif // ESP_PTP + + if (ret < 0) + { + ptperr("sendto failed: %d", errno); + } + else + { + ptpinfo("Sent announce, seq %ld\n", + (long)ptp_get_sequence(&msg.header)); + } + + return ret; +} + +/* Send PTP server synchronization packet */ + +static int ptp_send_sync(FAR struct ptp_state_s *state) +{ +#ifndef ESP_PTP + struct msghdr txhdr; + struct iovec txiov; +#endif // !ESP_PTP + struct ptp_sync_s msg; +#ifndef ESP_PTP + struct sockaddr_in addr; +#endif // !ESP_PTP + struct timespec ts; +#ifndef ESP_PTP + uint8_t controlbuf[64]; +#endif // !ESP_PTP + int ret; + +#ifndef ESP_PTP + memset(&txhdr, 0, sizeof(txhdr)); + memset(&txiov, 0, sizeof(txiov)); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = HTONL(PTP_MULTICAST_ADDR); + addr.sin_port = HTONS(PTP_UDP_PORT_EVENT); +#endif // !ESP_PTP + + memset(&msg, 0, sizeof(msg)); + msg.header = state->own_identity.header; + msg.header.messagetype = PTP_MSGTYPE_SYNC; + msg.header.messagelength[1] = sizeof(msg); + +#ifdef CONFIG_NETUTILS_PTPD_TWOSTEP_SYNC + msg.header.flags[0] = PTP_FLAGS0_TWOSTEP; +#endif + +#ifndef ESP_PTP + txhdr.msg_name = &addr; + txhdr.msg_namelen = sizeof(addr); + txhdr.msg_iov = &txiov; + txhdr.msg_iovlen = 1; + txhdr.msg_control = controlbuf; + txhdr.msg_controllen = sizeof(controlbuf); + txiov.iov_base = &msg; + txiov.iov_len = sizeof(msg); +#endif //!ESP_PTP + + /* Timestamp and send the sync message */ + + ptp_increment_sequence(&state->sync_seq, &msg.header); + ptp_gettime(state, &ts); + timespec_to_ptp_format(&ts, msg.origintimestamp); + +#ifdef ESP_PTP + ret = ptp_net_send(state, &msg, sizeof(msg), &ts); +#else + ret = sendmsg(state->tx_socket, &txhdr, 0); +#endif // ESP_PTP + if (ret < 0) + { + ptperr("sendmsg for sync message failed: %d\n", errno); + return ret; + } + +#ifdef CONFIG_NETUTILS_PTPD_TWOSTEP_SYNC +#ifndef ESP_PTP + /* Get timestamp after send completes and send follow-up message + * + * TODO: Implement SO_TIMESTAMPING and use the actual tx timestamp here. + */ + + ptp_gettime(state, &ts); +#endif // !ESP_PTP + timespec_to_ptp_format(&ts, msg.origintimestamp); + msg.header.messagetype = PTP_MSGTYPE_FOLLOW_UP; + msg.header.flags[0] = 0; +#ifndef ESP_PTP + addr.sin_port = HTONS(PTP_UDP_PORT_INFO); + + ret = sendto(state->tx_socket, &msg, sizeof(msg), 0, + (struct sockaddr *)&addr, sizeof(addr)); +#else + ret = ptp_net_send(state, &msg, sizeof(msg), NULL); +#endif // !ESP_PTP + if (ret < 0) + { + ptperr("sendto for follow-up message failed: %d\n", errno); + return ret; + } + + ptpinfo("Sent sync + follow-up, seq %ld\n", + (long)ptp_get_sequence(&msg.header)); +#else + ptpinfo("Sent sync, seq %ld\n", + (long)ptp_get_sequence(&msg.header)); +#endif /* CONFIG_NETUTILS_PTPD_TWOSTEP_SYNC */ + + return OK; +} + +/* Send delay request packet to selected source */ + +static int ptp_send_delay_req(FAR struct ptp_state_s *state) +{ + struct ptp_delay_req_s req; +#ifndef ESP_PTP + struct sockaddr_in addr; +#endif // !ESP_PTP + int ret; + +#ifndef ESP_PTP + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = HTONL(PTP_MULTICAST_ADDR); + addr.sin_port = HTONS(PTP_UDP_PORT_EVENT); +#endif // !ESP_PTP + + memset(&req, 0, sizeof(req)); + req.header = state->own_identity.header; + req.header.messagetype = PTP_MSGTYPE_DELAY_REQ; + req.header.messagelength[1] = sizeof(req); + ptp_increment_sequence(&state->delay_req_seq, &req.header); + + ptp_gettime(state, &state->delayreq_time); + timespec_to_ptp_format(&state->delayreq_time, req.origintimestamp); + +#ifdef ESP_PTP + ret = ptp_net_send(state, &req, sizeof(req), &state->delayreq_time); +#else + ret = sendto(state->tx_socket, &req, sizeof(req), 0, + (FAR struct sockaddr *)&addr, sizeof(addr)); +#endif // ESP_PTP + +#ifndef ESP_PTP + /* Get timestamp after send completes. + * TODO: Implement SO_TIMESTAMPING and use the actual tx timestamp here. + */ + + ptp_gettime(state, &state->delayreq_time); +#endif // !ESP_PTP + + if (ret < 0) + { + ptperr("sendto failed: %d", errno); + } + else + { + clock_gettime(CLOCK_MONOTONIC, &state->last_transmitted_delayreq); + ptpinfo("Sent delay req, seq %ld\n", + (long)ptp_get_sequence(&req.header)); + } + + return ret; +} + +/* Check if we need to send packets */ + +static int ptp_periodic_send(FAR struct ptp_state_s *state) +{ +#ifdef CONFIG_NETUTILS_PTPD_SERVER + /* If there is no better master clock on the network, + * act as the reference source and send server packets. + */ + + if (!state->selected_source_valid) + { + struct timespec time_now; + struct timespec delta; + + clock_gettime(CLOCK_MONOTONIC, &time_now); + clock_timespec_subtract(&time_now, + &state->last_transmitted_announce, &delta); + if (timespec_to_ms(&delta) + > CONFIG_NETUTILS_PTPD_ANNOUNCE_INTERVAL_MSEC) + { + state->last_transmitted_announce = time_now; + ptp_send_announce(state); + } + + clock_timespec_subtract(&time_now, + &state->last_transmitted_sync, &delta); + if (timespec_to_ms(&delta) > CONFIG_NETUTILS_PTPD_SYNC_INTERVAL_MSEC) + { + state->last_transmitted_sync = time_now; + ptp_send_sync(state); + } + } +#endif /* CONFIG_NETUTILS_PTPD_SERVER */ + +#ifdef CONFIG_NETUTILS_PTPD_SEND_DELAYREQ + if (state->selected_source_valid && state->can_send_delayreq) + { + struct timespec time_now; + struct timespec delta; + + clock_gettime(CLOCK_MONOTONIC, &time_now); + clock_timespec_subtract(&time_now, + &state->last_transmitted_delayreq, &delta); + + if (timespec_to_ms(&delta) > state->delayreq_interval * MSEC_PER_SEC) + { + ptp_send_delay_req(state); + } + } +#endif + + return OK; +} + +/* Process received PTP announcement */ + +static int ptp_process_announce(FAR struct ptp_state_s *state, + FAR struct ptp_announce_s *msg) +{ + clock_gettime(CLOCK_MONOTONIC, &state->last_received_announce); + + if (is_better_clock(msg, &state->own_identity)) + { + if (!state->selected_source_valid || + is_better_clock(msg, &state->selected_source)) + { + ptpinfo("Switching to better PTP time source\n"); + + state->selected_source = *msg; + state->last_received_sync = state->last_received_announce; + state->path_delay_avgcount = 0; + state->path_delay_ns = 0; + state->delayreq_time.tv_sec = 0; + } + } + + return OK; +} + +#ifdef ESP_PTP +static void ptp_lock_local_clock_freq(FAR struct ptp_state_s *state, + FAR struct timespec *remote_timestamp, + FAR struct timespec *local_timestamp) +{ + // Compute how off we are against master + int64_t offset_ns = timespec_delta_ns(remote_timestamp, local_timestamp); + offset_ns += state->path_delay_ns; + // TODO add offset filter + + // Execute PI controller to elimitate the offset + // compute I component + state->offset_pi.drift_acc += offset_ns / state->offset_pi.ki; + // clamp the accumulator to ADJ_FREQ_MAX for sanity + if (state->offset_pi.drift_acc > ADJ_FREQ_MAX){ + state->offset_pi.drift_acc = ADJ_FREQ_MAX; + } else if (state->offset_pi.drift_acc < -ADJ_FREQ_MAX) { + state->offset_pi.drift_acc = -ADJ_FREQ_MAX; + } + // compute P component and the whole controller + int32_t adj = offset_ns / state->offset_pi.kp + state->offset_pi.drift_acc; + + // Compute difference between number of ticks in slave and master over sync period. This is used to lock the frequency with the master. + // However, it never catch-up the offset by itself, hence also add `adj` at the end + int64_t remote_time_ns = timespec_to_ns(remote_timestamp); + int64_t local_time_ns = timespec_to_ns(local_timestamp); + int64_t remote_delta_ns = remote_time_ns - state->remote_time_ns_prev; + int64_t local_delta_ns = local_time_ns - state->local_time_ns_prev; + // clock tick difference between master and slave + int64_t tick_diff = remote_delta_ns - local_delta_ns; + + // compute how to scale the slave frequency to lock with master frequency and also try to catch-up the offset + double freq_scale = ((double)(remote_delta_ns /*+ tick_diff*/ + adj)) / (double)local_delta_ns; + esp_eth_clock_adj_param_t clk_adj_param = { + .mode = ETH_CLK_ADJ_FREQ_SCALE, + .freq_scale = freq_scale + }; + esp_eth_clock_adjtime(CLOCK_PTP_SYSTEM, &clk_adj_param); + + state->remote_time_ns_prev = remote_time_ns; + state->local_time_ns_prev = local_time_ns; + + ptpinfo("remote_delta_ns %lli, local_delta_ns %lli, tick_diff %lli\n", remote_delta_ns, local_delta_ns, tick_diff); + ptpinfo("offset_ns %lli, adj %li, drift_acc %li\n", offset_ns, adj, state->offset_pi.drift_acc); + + // Get the path delay only when clock is stable enough. If we were in process of adjustion (speeding/slowing slave), + // we would get incorrect delay + int64_t diff = llabs(offset_ns) - llabs(state->last_offset_ns); + static int cnt = 0; + if (llabs(diff) < CONFIG_NETUTILS_PTPD_PATH_DELAY_STABILITY_NS) { + if (cnt <= 3) + cnt++; + } else { + cnt = 0; + } + if (cnt > 3) + { + state->can_send_delayreq = true; + } + state->last_offset_ns = offset_ns; +} + +void ptp_clean_after_step(FAR struct ptp_state_s *state) +{ + state->remote_time_ns_prev = 0; + state->local_time_ns_prev = 0; + + state->offset_pi.drift_acc = 0; + state->last_offset_ns = 0; +} +#endif // ESP_PTP + +/* Update local clock either by smooth adjustment or by jumping. + * Remote time was remote_timestamp at local_timestamp. + */ + +static int ptp_update_local_clock(FAR struct ptp_state_s *state, + FAR struct timespec *remote_timestamp, + FAR struct timespec *local_timestamp) +{ + int ret = OK; + int64_t delta_ns; + int64_t absdelta_ns; + const int64_t adj_limit_ns = CONFIG_NETUTILS_PTPD_SETTIME_THRESHOLD_MS + * (int64_t)NSEC_PER_MSEC; + + ptpinfo("Local time: %lld.%09ld, remote time %lld.%09ld\n", + (long long)local_timestamp->tv_sec, + (long)local_timestamp->tv_nsec, + (long long)remote_timestamp->tv_sec, + (long)remote_timestamp->tv_nsec); + + delta_ns = timespec_delta_ns(remote_timestamp, local_timestamp); + delta_ns += state->path_delay_ns; + absdelta_ns = (delta_ns < 0) ? -delta_ns : delta_ns; + + if (absdelta_ns > adj_limit_ns) + { + /* Large difference, move by jumping. + * Account for delay since packet was received. + */ + + struct timespec new_time; + ptp_gettime(state, &new_time); + clock_timespec_subtract(&new_time, local_timestamp, &new_time); + clock_timespec_add(&new_time, remote_timestamp, &new_time); + ret = ptp_settime(state, &new_time); + + /* Reinitialize drift adjustment parameters */ + + state->last_delta_timestamp = new_time; + state->last_delta_ns = 0; + state->last_adjtime_ns = 0; + state->drift_avg_total_ms = 0; + state->drift_ppb = 0; + +#ifdef ESP_PTP + ptp_clean_after_step(state); +#endif // ESP_PTP + + if (ret == OK) + { + ptpinfo("Jumped to timestamp %lld.%09ld s\n", + (long long)new_time.tv_sec, (long)new_time.tv_nsec); + } + else + { + ptperr("ptp_settime() failed: %d\n", errno); + } + } + else + { +#ifdef ESP_PTP + ptp_lock_local_clock_freq(state, remote_timestamp, local_timestamp); +#else + /* Track drift rate based on two consecutive measurements and + * the adjustment that was made previously. + */ + + int64_t drift_ppb; + struct timespec interval; + int interval_ms; + int max_avg_period_ms; + int64_t adjustment_ns; + + clock_timespec_subtract(local_timestamp, + &state->last_delta_timestamp, + &interval); + interval_ms = timespec_to_ms(&interval); + + if (interval_ms > 0 && interval_ms < CONFIG_NETUTILS_PTPD_TIMEOUT_MS) + { + drift_ppb = (delta_ns - state->last_delta_ns) * MSEC_PER_SEC + / interval_ms; + } + else + { + ptpwarn("Measurement interval out of range: %d ms\n", interval_ms); + drift_ppb = 0; + interval_ms = 1; + } + + /* Account for the adjustment previously made */ + + drift_ppb += state->last_adjtime_ns * MSEC_PER_SEC + / CONFIG_CLOCK_ADJTIME_PERIOD_MS; + + if (drift_ppb > CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM * 1000 || + drift_ppb < -CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM * 1000) + { + ptpwarn("Drift estimate out of range: %lld\n", + (long long)drift_ppb); + drift_ppb = state->drift_ppb; + } + + /* Take direct average of drift estimate for first measurements, + * after that update the exponential sliding average. + * Measurements are weighted according to the interval, because + * drift estimate is more accurate over longer timespan. + */ + + state->drift_avg_total_ms += interval_ms; + max_avg_period_ms = CONFIG_NETUTILS_PTPD_DRIFT_AVERAGE_S + * MSEC_PER_SEC; + if (state->drift_avg_total_ms > max_avg_period_ms) + { + state->drift_avg_total_ms = max_avg_period_ms; + } + + state->drift_ppb += (drift_ppb - state->drift_ppb) * interval_ms + / state->drift_avg_total_ms; + + /* Compute the value we need to give to adjtime() to match the + * drift rate. + */ + + adjustment_ns = state->drift_ppb * CONFIG_CLOCK_ADJTIME_PERIOD_MS + / MSEC_PER_SEC; + + /* Drift estimation ensures local clock runs at same rate as remote. + * + * Adding the current clock offset to adjustment brings the clocks + * to match. To avoid individual outliers from causing jitter, we + * take the larger signed value of two previous deltas. This is based + * on the logic that packets can get delayed in transit, but do not + * travel backwards in time. + * + * Clock offset is applied over ADJTIME_PERIOD. If there is significant + * noise in measurements, increasing ADJTIME_PERIOD will reduce its + * effect on the local clock run rate. + */ + + if (state->last_delta_ns > delta_ns) + { + adjustment_ns += state->last_delta_ns; + } + else + { + adjustment_ns += delta_ns; + } + + /* Apply adjustment and store information for next time */ + + state->last_delta_ns = delta_ns; + state->last_delta_timestamp = *local_timestamp; + state->last_adjtime_ns = adjustment_ns; + + ptpinfo("Delta: %+lld ns, adjustment %+lld ns, drift rate %+lld ppb\n", + (long long)delta_ns, + (long long)state->last_adjtime_ns, + (long long)state->drift_ppb); + + ret = ptp_adjtime(state, adjustment_ns); + + if (ret != OK) + { + ptperr("ptp_adjtime() failed: %d\n", errno); + } + + /* Check if clock is stable enough for sending delay requests */ + + if (absdelta_ns < CONFIG_NETUTILS_PTPD_MAX_PATH_DELAY_NS) + { + state->can_send_delayreq = true; + } +#endif // ESP_PTP + } + + return ret; +} + +/* Process received PTP sync packet */ + +static int ptp_process_sync(FAR struct ptp_state_s *state, + FAR struct ptp_sync_s *msg) +{ + struct timespec remote_time; + + if (memcmp(msg->header.sourceidentity, + state->selected_source.header.sourceidentity, + sizeof(msg->header.sourceidentity)) != 0) + { + /* This packet wasn't from the currently selected source */ +#ifdef ESP_PTP + ESP_LOGD(TAG, "This packet wasn't from the currently selected source"); +#endif // ESP_PTP + return OK; + } + + /* Update timeout tracking */ + + clock_gettime(CLOCK_MONOTONIC, &state->last_received_sync); + + if (msg->header.flags[0] & PTP_FLAGS0_TWOSTEP) + { + /* We need to wait for a follow-up packet before setting the clock. */ + + state->twostep_rxtime = state->rxtime; + state->twostep_packet = *msg; + ptpinfo("Waiting for follow-up\n"); + return OK; + } + + /* Update local clock */ + + ptp_format_to_timespec(msg->origintimestamp, &remote_time); + return ptp_update_local_clock(state, &remote_time, &state->rxtime); +} + +static int ptp_process_followup(FAR struct ptp_state_s *state, + FAR struct ptp_follow_up_s *msg) +{ + struct timespec remote_time; + + if (memcmp(msg->header.sourceidentity, + state->twostep_packet.header.sourceidentity, + sizeof(msg->header.sourceidentity)) != 0) + { + return OK; /* This packet wasn't from the currently selected source */ + } + + if (ptp_get_sequence(&msg->header) + != ptp_get_sequence(&state->twostep_packet.header)) + { + ptpwarn("PTP follow-up packet sequence %ld does not match initial " + "sync packet sequence %ld, ignoring\n", + (long)ptp_get_sequence(&msg->header), + (long)ptp_get_sequence(&state->twostep_packet.header)); + return OK; + } + + /* Update local clock based on the remote timestamp we received now + * and the local timestamp of when the sync packet was received. + */ + + ptp_format_to_timespec(msg->origintimestamp, &remote_time); + return ptp_update_local_clock(state, &remote_time, &state->twostep_rxtime); +} + +static int ptp_process_delay_req(FAR struct ptp_state_s *state, + FAR struct ptp_delay_req_s *msg) +{ + struct ptp_delay_resp_s resp; +#ifndef ESP_PTP + struct sockaddr_in addr; +#endif // !ESP_PTP + int ret; + + if (state->selected_source_valid) + { + /* We are operating as a client, ignore delay requests */ + + return OK; + } + +#ifndef ESP_PTP + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = HTONL(PTP_MULTICAST_ADDR); + addr.sin_port = HTONS(PTP_UDP_PORT_INFO); +#endif // !ESP_PTP + + memset(&resp, 0, sizeof(resp)); + resp.header = state->own_identity.header; + resp.header.messagetype = PTP_MSGTYPE_DELAY_RESP; + resp.header.messagelength[1] = sizeof(resp); + timespec_to_ptp_format(&state->rxtime, resp.receivetimestamp); + memcpy(resp.reqidentity, msg->header.sourceidentity, + sizeof(resp.reqidentity)); + memcpy(resp.reqportindex, msg->header.sourceportindex, + sizeof(resp.reqportindex)); + memcpy(resp.header.sequenceid, msg->header.sequenceid, + sizeof(resp.header.sequenceid)); + resp.header.logmessageinterval = CONFIG_NETUTILS_PTPD_DELAYRESP_INTERVAL; + +#ifdef ESP_PTP + ret = ptp_net_send(state, &resp, sizeof(resp), NULL); +#else + ret = sendto(state->tx_socket, &resp, sizeof(resp), 0, + (FAR struct sockaddr *)&addr, sizeof(addr)); +#endif // ESP_PTP + + if (ret < 0) + { + ptperr("sendto failed: %d", errno); + } + else + { + clock_gettime(CLOCK_MONOTONIC, &state->last_transmitted_delayresp); + ptpinfo("Sent delay resp, seq %ld\n", + (long)ptp_get_sequence(&msg->header)); + } + + return ret; +} + +static int ptp_process_delay_resp(FAR struct ptp_state_s *state, + FAR struct ptp_delay_resp_s *msg) +{ + int64_t path_delay; + int64_t sync_delay; + struct timespec remote_rxtime; + uint16_t sequence; + int interval; + + if (!state->selected_source_valid || + memcmp(msg->header.sourceidentity, + state->selected_source.header.sourceidentity, + sizeof(msg->header.sourceidentity)) != 0 || + memcmp(msg->reqidentity, + state->own_identity.header.sourceidentity, + sizeof(msg->reqidentity)) != 0) + { + return OK; /* This packet wasn't for us */ + } + + sequence = ptp_get_sequence(&msg->header); + + if (sequence != state->delay_req_seq) + { + ptpwarn("Ignoring out-of-sequence delay resp (%d vs. expected %d)\n", + (int)sequence, (int)state->delay_req_seq); + return OK; + } + + /* Path delay is calculated as the average between delta for sync + * message and delta for delay req message. + * (IEEE-1588 section 11.3: Delay request-response mechanism) + */ + + ptp_format_to_timespec(msg->receivetimestamp, &remote_rxtime); + path_delay = timespec_delta_ns(&remote_rxtime, &state->delayreq_time); + sync_delay = state->path_delay_ns - state->last_delta_ns; + path_delay = (path_delay + sync_delay) / 2; + + if (path_delay >= 0 && path_delay < CONFIG_NETUTILS_PTPD_MAX_PATH_DELAY_NS) + { + if (state->path_delay_avgcount < + CONFIG_NETUTILS_PTPD_DELAYREQ_AVGCOUNT) + { + state->path_delay_avgcount++; + } + + state->path_delay_ns += (path_delay - state->path_delay_ns) + / state->path_delay_avgcount; + + ptpinfo("Path delay: %ld ns (avg: %ld ns)\n", + (long)path_delay, (long)state->path_delay_ns); + } + else + { + ptpwarn("Path delay out of range: %lld ns\n", + (long long)path_delay); + } + + /* Calculate interval until next packet */ + + if (msg->header.logmessageinterval <= 12) + { + interval = (1 << msg->header.logmessageinterval); + } + else + { + interval = 4096; /* Refuse to obey excessively long intervals */ + } + + /* Randomize up to 2x nominal delay) */ + + state->delayreq_interval = interval + (random() % interval); + + return OK; +} + +/* Determine received packet type and process it */ + +static int ptp_process_rx_packet(FAR struct ptp_state_s *state, + ssize_t length) +{ + if (length < sizeof(struct ptp_header_s)) + { + ptpwarn("Ignoring invalid PTP packet, length only %d bytes\n", + (int)length); + return OK; + } + + if (state->rxbuf.header.domain != CONFIG_NETUTILS_PTPD_DOMAIN) + { + /* Part of different clock domain, ignore */ + + return OK; + } + + clock_gettime(CLOCK_MONOTONIC, &state->last_received_multicast); + + switch (state->rxbuf.header.messagetype & PTP_MSGTYPE_MASK) + { +#ifdef CONFIG_NETUTILS_PTPD_CLIENT + case PTP_MSGTYPE_ANNOUNCE: + ptpinfo("Got announce packet, seq %ld\n", + (long)ptp_get_sequence(&state->rxbuf.header)); + return ptp_process_announce(state, &state->rxbuf.announce); + + case PTP_MSGTYPE_SYNC: + ptpinfo("Got sync packet, seq %ld\n", + (long)ptp_get_sequence(&state->rxbuf.header)); + return ptp_process_sync(state, &state->rxbuf.sync); + + case PTP_MSGTYPE_FOLLOW_UP: + ptpinfo("Got follow-up packet, seq %ld\n", + (long)ptp_get_sequence(&state->rxbuf.header)); + return ptp_process_followup(state, &state->rxbuf.follow_up); + + case PTP_MSGTYPE_DELAY_RESP: + ptpinfo("Got delay-resp, seq %ld\n", + (long)ptp_get_sequence(&state->rxbuf.header)); + return ptp_process_delay_resp(state, &state->rxbuf.delay_resp); +#endif + +#ifdef CONFIG_NETUTILS_PTPD_SERVER + case PTP_MSGTYPE_DELAY_REQ: + ptpinfo("Got delay req, seq %ld\n", + (long)ptp_get_sequence(&state->rxbuf.header)); + return ptp_process_delay_req(state, &state->rxbuf.delay_req); +#endif + + default: + ptpinfo("Ignoring unknown PTP packet type: 0x%02x\n", + state->rxbuf.header.messagetype); + return OK; + } +} + +/* Signal handler for status / stop requests */ +#ifndef ESP_PTP +static void ptp_signal_handler(int signo, FAR siginfo_t *siginfo, + FAR void *context) +{ + FAR struct ptp_state_s *state = (FAR struct ptp_state_s *)siginfo->si_user; + + if (signo == SIGHUP) + { + state->stop = true; + } + else if (signo == SIGUSR1 && siginfo->si_value.sival_ptr) + { + state->status_req = + *(FAR struct ptpd_statusreq_s *)siginfo->si_value.sival_ptr; + } +} + +static void ptp_setup_sighandlers(FAR struct ptp_state_s *state) +{ + struct sigaction act; + + act.sa_sigaction = ptp_signal_handler; + sigfillset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + act.sa_user = state; + + sigaction(SIGHUP, &act, NULL); + sigaction(SIGUSR1, &act, NULL); +} +#endif // !ESP_PTP + +/* Process status information request */ + +static void ptp_process_statusreq(FAR struct ptp_state_s *state) +{ + FAR struct ptpd_status_s *status; + + if (!state->status_req.dest) + { + return; /* No active request */ + } + + status = state->status_req.dest; + status->clock_source_valid = state->selected_source_valid; + + if (status->clock_source_valid) + { + /* Copy relevant parts of announce info to status struct */ + + FAR struct ptp_announce_s *s = &state->selected_source; + + memcpy(status->clock_source_info.id, + s->header.sourceidentity, + sizeof(status->clock_source_info.id)); + + status->clock_source_info.utcoffset = + (int16_t)(((uint16_t)s->utcoffset[0] << 8) | s->utcoffset[1]); + status->clock_source_info.priority1 = s->gm_priority1; + status->clock_source_info.clockclass = s->gm_quality[0]; + status->clock_source_info.accuracy = s->gm_quality[1]; + status->clock_source_info.priority2 = s->gm_priority2; + status->clock_source_info.variance = + ((uint16_t)s->gm_quality[2] << 8) | s->gm_quality[3]; + + memcpy(status->clock_source_info.gm_id, + s->gm_identity, + sizeof(status->clock_source_info.gm_id)); + + status->clock_source_info.stepsremoved = + ((uint16_t)s->stepsremoved[0] << 8) | s->stepsremoved[1]; + status->clock_source_info.timesource = s->timesource; + } + + /* Copy latest adjustment info */ + + status->last_clock_update = state->last_delta_timestamp; + status->last_delta_ns = state->last_delta_ns; + status->last_adjtime_ns = state->last_adjtime_ns; + status->drift_ppb = state->drift_ppb; + status->path_delay_ns = state->path_delay_ns; + + /* Copy timestamps */ + + status->last_received_multicast = state->last_received_multicast; + status->last_received_announce = state->last_received_announce; + status->last_received_sync = state->last_received_sync; + status->last_transmitted_sync = state->last_transmitted_sync; + status->last_transmitted_announce = state->last_transmitted_announce; + status->last_transmitted_delayresp = state->last_transmitted_delayresp; + status->last_transmitted_delayreq = state->last_transmitted_delayreq; + + /* Post semaphore to inform that we are done */ + + if (state->status_req.done) + { + sem_post(state->status_req.done); + } + + state->status_req.done = NULL; + state->status_req.dest = NULL; +} + +/* Main PTPD task */ +#ifdef ESP_PTP +static void ptp_daemon(void *task_param) +#else +static int ptp_daemon(int argc, FAR char** argv) +#endif // ESP_PTP +{ + FAR const char *interface = "eth0"; + FAR struct ptp_state_s *state; +#ifdef ESP_PTP + struct pollfd pollfds[1]; // everything is received over one socket at L2 +#else + struct pollfd pollfds[2]; + struct msghdr rxhdr; + struct iovec rxiov; +#endif // ESP_PTP + int ret; + +#ifndef ESP_PTP + memset(&rxhdr, 0, sizeof(rxhdr)); + memset(&rxiov, 0, sizeof(rxiov)); +#endif // !ESP_PTP + + state = calloc(1, sizeof(struct ptp_state_s)); + +#ifdef ESP_PTP + if (task_param != NULL) + { + interface = task_param; + } +#else + if (argc > 1) + { + interface = argv[1]; + } +#endif // ESP_PTP + + if (ptp_initialize_state(state, interface) != OK) + { + ptperr("Failed to initialize PTP state, exiting\n"); + + ptp_destroy_state(state); + free(state); + +#ifdef ESP_PTP + goto err; +#else + return ERROR; +#endif // ESP_PTP + } +#ifndef ESP_PTP + ptp_setup_sighandlers(state); +#endif // !ESP_PTP + + pollfds[0].events = POLLIN; +#ifdef ESP_PTP + pollfds[0].fd = state->ptp_socket; +#else + pollfds[0].fd = state->event_socket; + pollfds[1].events = POLLIN; + pollfds[1].fd = state->info_socket; +#endif // ESP_PTP + + while (!state->stop) + { + state->can_send_delayreq = false; + +#ifndef ESP_PTP + rxhdr.msg_name = NULL; + rxhdr.msg_namelen = 0; + rxhdr.msg_iov = &rxiov; + rxhdr.msg_iovlen = 1; + rxhdr.msg_control = &state->rxcmsg; + rxhdr.msg_controllen = sizeof(state->rxcmsg); + rxhdr.msg_flags = 0; + rxiov.iov_base = &state->rxbuf; + rxiov.iov_len = sizeof(state->rxbuf); +#endif // !ESP_PTP + + pollfds[0].revents = 0; +#ifndef ESP_PTP + pollfds[1].revents = 0; + ret = poll(pollfds, 2, PTPD_POLL_INTERVAL); +#else + ret = poll(pollfds, 1, PTPD_POLL_INTERVAL); +#endif // !ESP_PTP + + if (pollfds[0].revents) + { + /* Receive time-critical packet, potentially with cmsg + * indicating the timestamp. + */ + +#ifdef ESP_PTP + ret = ptp_net_recv(state, &state->rxbuf, sizeof(state->rxbuf), &state->rxtime); +#else + ret = recvmsg(state->event_socket, &rxhdr, MSG_DONTWAIT); +#endif // ESP_PTP + + if (ret > 0) + { +#ifndef ESP_PTP + ptp_getrxtime(state, &rxhdr, &state->rxtime); +#endif + ptp_process_rx_packet(state, ret); + } + } + +#ifndef ESP_PTP + if (pollfds[1].revents) + { + /* Receive non-time-critical packet. */ + + ret = recv(state->info_socket, &state->rxbuf, sizeof(state->rxbuf), + MSG_DONTWAIT); + if (ret > 0) + { + ptp_process_rx_packet(state, ret); + } + } + + if (pollfds[0].revents == 0 && pollfds[1].revents == 0) + { + /* No packets received, check for multicast timeout */ + + ptp_check_multicast_status(state); + } +#endif // !ESP_PTP + ptp_periodic_send(state); + + state->selected_source_valid = is_selected_source_valid(state); + ptp_process_statusreq(state); + } + ptp_destroy_state(state); + free(state); + +#ifdef ESP_PTP +err: + s_state = NULL; + vTaskDelete(NULL); +#else + return 0; +#endif // ESP_PTP +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * 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) +{ +#ifdef ESP_PTP + if (s_state == NULL) { + xTaskCreate(ptp_daemon, "PTPD", CONFIG_NETUTILS_PTPD_STACKSIZE, + (void *)interface, tskIDLE_PRIORITY + 2, NULL); + return 1; + } + ESP_LOGE(TAG, "Other instance of PTP is already running"); + return -1; +#else + int pid; + FAR char *task_argv[] = { + (FAR char *)interface, + NULL + }; + + pid = task_create("PTPD", CONFIG_NETUTILS_PTPD_SERVERPRIO, + CONFIG_NETUTILS_PTPD_STACKSIZE, ptp_daemon, task_argv); + + /* Use kill with signal 0 to check if the process is still alive + * after initialization. + */ + + usleep(USEC_PER_TICK); + if (kill(pid, 0) != OK) + { + return ERROR; + } + else + { + return pid; + } +#endif // ESP_PTP +} + +/**************************************************************************** + * 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) +{ +#ifdef ESP_PTP + int ret = 0; + sem_t donesem; + struct ptpd_statusreq_s req; + struct timespec timeout; + + /* Fill in the status request */ + + memset(status, 0, sizeof(struct ptpd_status_s)); + sem_init(&donesem, 0, 0); + req.done = &donesem; + req.dest = status; + + s_state->status_req = req; + + /* Wait for status request to be handled */ + clock_gettime(CLOCK_REALTIME, &timeout); // sem_timedwait uses CLOCK_REALTIME + timeout.tv_sec += 1; + + if (sem_timedwait(&donesem, &timeout) != 0) + { + req.done = NULL; + req.dest = NULL; + s_state->status_req = req; + ret = -errno; + } + sem_destroy(&donesem); + + return ret; +#endif +#ifndef CONFIG_BUILD_FLAT + + /* TODO: Use SHM memory to pass the status information if processes + * do not share the same memory space. + */ + + return -ENOTSUP; + +#else + + int ret = OK; + sem_t donesem; + struct ptpd_statusreq_s req; + union sigval val; + struct timespec timeout; + + /* Fill in the status request */ + + memset(status, 0, sizeof(struct ptpd_status_s)); + sem_init(&donesem, 0, 0); + req.done = &donesem; + req.dest = status; + val.sival_ptr = &req; + + if (sigqueue(pid, SIGUSR1, val) != OK) + { + return -errno; + } + + /* Wait for status request to be handled */ + + clock_gettime(CLOCK_MONOTONIC, &timeout); + timeout.tv_sec += 1; + if (sem_clockwait(&donesem, CLOCK_MONOTONIC, &timeout) != 0) + { + ret = -errno; + } + + return ret; + +#endif /* CONFIG_BUILD_FLAT */ +} + +/**************************************************************************** + * 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) +{ +#ifdef ESP_PTP + s_state->stop = true; + return OK; +#else + if (kill(pid, SIGHUP) == OK) + { + return OK; + } + else + { + return -errno; + } +#endif +} diff --git a/examples/ethernet/ptp/components/ptpd/ptpv2.h b/examples/ethernet/ptp/components/ptpd/ptpv2.h new file mode 100644 index 0000000000..30cf527808 --- /dev/null +++ b/examples/ethernet/ptp/components/ptpd/ptpv2.h @@ -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 + +/**************************************************************************** + * 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 */ diff --git a/examples/ethernet/ptp/docs/sync_osc.jpg b/examples/ethernet/ptp/docs/sync_osc.jpg new file mode 100644 index 0000000000..ee50715342 Binary files /dev/null and b/examples/ethernet/ptp/docs/sync_osc.jpg differ diff --git a/examples/ethernet/ptp/main/CMakeLists.txt b/examples/ethernet/ptp/main/CMakeLists.txt new file mode 100644 index 0000000000..12891e0717 --- /dev/null +++ b/examples/ethernet/ptp/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "ptp_main.c" + INCLUDE_DIRS ".") diff --git a/examples/ethernet/ptp/main/Kconfig.projbuild b/examples/ethernet/ptp/main/Kconfig.projbuild new file mode 100644 index 0000000000..1fd46022da --- /dev/null +++ b/examples/ethernet/ptp/main/Kconfig.projbuild @@ -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 diff --git a/examples/ethernet/ptp/main/idf_component.yml b/examples/ethernet/ptp/main/idf_component.yml new file mode 100644 index 0000000000..cbf926ba80 --- /dev/null +++ b/examples/ethernet/ptp/main/idf_component.yml @@ -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 diff --git a/examples/ethernet/ptp/main/ptp_main.c b/examples/ethernet/ptp/main/ptp_main.c new file mode 100644 index 0000000000..7954b17966 --- /dev/null +++ b/examples/ethernet/ptp/main/ptp_main.c @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#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(ð_handles, ð_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; + } +} diff --git a/examples/ethernet/ptp/sdkconfig.defaults b/examples/ethernet/ptp/sdkconfig.defaults new file mode 100644 index 0000000000..0f922b5824 --- /dev/null +++ b/examples/ethernet/ptp/sdkconfig.defaults @@ -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