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

Added mechanism to L2 TAP to retreive time stamp

Added PTP time synchronization example
This commit is contained in:
Ondrej Kosta
2024-06-26 15:40:32 +02:00
parent 269322191a
commit d2b1202d5a
57 changed files with 5653 additions and 403 deletions
+54 -1
View File
@@ -382,7 +382,7 @@ To install the Ethernet driver, we need to combine the instance of MAC and PHY a
* :cpp:member:`esp_eth_config_t::check_link_period_ms`: Ethernet driver starts an OS timer to check the link status periodically, this field is used to set the interval, in milliseconds.
* :cpp:member:`esp_eth_config_t::stack_input`: In most Ethernet IoT applications, any Ethernet frame received by a driver should be passed to the upper layer (e.g., TCP/IP stack). This field is set to a function that is responsible to deal with the incoming frames. You can even update this field at runtime via function :cpp:func:`esp_eth_update_input_path` after driver installation.
* :cpp:member:`esp_eth_config_t::stack_input` or :cpp:member:`esp_eth_config_t::stack_input_info`: In most Ethernet IoT applications, any Ethernet frame received by a driver should be passed to the upper layer (e.g., TCP/IP stack). This field is set to a function that is responsible to deal with the incoming frames. You can even update this field at runtime via function :cpp:func:`esp_eth_update_input_path` after driver installation.
* :cpp:member:`esp_eth_config_t::on_lowlevel_init_done` and :cpp:member:`esp_eth_config_t::on_lowlevel_deinit_done`: These two fields are used to specify the hooks which get invoked when low-level hardware has been initialized or de-initialized.
@@ -517,6 +517,57 @@ The following functions should only be invoked after the Ethernet driver has bee
esp_eth_ioctl(eth_handle, ETH_CMD_G_PHY_ADDR, &phy_addr);
ESP_LOGI(TAG, "Ethernet PHY Address: %d", phy_addr);
.. _time-stamping:
.. only:: SOC_EMAC_IEEE1588V2_SUPPORTED
EMAC Hardware Time Stamping
---------------------------
Time stamping in EMAC allows precise tracking of when Ethernet frames are transmitted or received. Hardware time stamping is crucial for applications like Precision Time Protocol (PTP) because it minimizes jitter and inaccuracies that can occur when relying on software-based time stamps. By embedding time stamps directly in hardware, delays introduced by software layers or processing overhead are avoided, ensuring nanosecond-level precision.
.. warning::
Time stamp associated API is currently in **"Experimental Feature"** state so be aware it may change with future releases.
The basic way how to enable time stamping, get and set time in the EMAC is demonstrated below.
.. highlight:: c
::
// Enable hardware time stamping
bool ptp_enable = true;
esp_eth_ioctl(eth_hndl, ETH_MAC_ESP_CMD_PTP_ENABLE, &ptp_enable);
// Get current EMAC time
eth_mac_time_t ptp_time;
esp_eth_ioctl(eth_hndl, ETH_MAC_ESP_CMD_G_PTP_TIME, &ptp_time);
// Set EMAC time
ptp_time = {
.seconds = 42,
.nanoseconds = 0
};
esp_eth_ioctl(eth_hndl, ETH_MAC_ESP_CMD_S_PTP_TIME, &ptp_time);
You have an option to schedule event at precise point in time by registering callback function and configuring a target time when the event is supposed to be fired. Note that the callback function is then called from ISR context so it should be as brief as possible.
.. highlight:: c
::
// Register the callback function
esp_eth_ioctl(eth_hndl, ETH_MAC_ESP_CMD_S_TARGET_CB, ts_callback);
// Set time when event is triggered
eth_mac_time_t mac_target_time = {
.seconds = 42,
.nanoseconds = 0
};
esp_eth_ioctl(s_eth_hndl, ETH_MAC_ESP_CMD_S_TARGET_TIME, &mac_target_time);
Time stamps for transmitted and received frames can be accessed via the last argument of the registered :cpp:member:`esp_eth_config_t::stack_input_info` function for the receive path, and via the ``ctrl`` argument of the :cpp:func:`esp_eth_transmit_ctrl_vargs` function for the transmit path. However, a more user-friendly approach to retrieve time stamp information in user space is by utilizing the L2 TAP :ref:`Extended Buffer <esp_netif_l2tap_ext_buff>` mechanism.
.. _flow-control:
Flow Control
@@ -546,6 +597,8 @@ Application Examples
* :example:`ethernet/iperf` demonstrates how to use the Ethernet capabilities to measure the throughput/bandwidth using iPerf.
* :example:`ethernet/ptp` demonstrates the use of Precision Time Protocol (PTP) for time synchronization over Ethernet.
* :example:`network/vlan_support` demonstrates how to create virtual network interfaces over Ethernet, including VLAN and non-VLAN interfaces.
* :example:`network/sta2eth` demonstrates how to create a 1-to-1 bridge using a Wi-Fi station and a wired interface such as Ethernet or USB.
+33 -33
View File
@@ -148,42 +148,42 @@ ESP-NETIF Architecture
| (A) USER CODE |
| Apps |
.................| init settings events |
| Apps |
.................| init settings events |
. +----------------------------------------+
. . | *
. . | *
--------+ +===========================+ * +-----------------------+
| | new/config get/set/apps | * | init |
| | |...*.....| Apps (DHCP, SNTP) |
| |---------------------------| * | |
init | | |**** | |
start |************| event handler |*********| DHCP |
stop | | | | |
| |---------------------------| | |
| | | | NETIF |
+-----| | | +-----------------+ |
| glue|---<----|---| esp_netif_transmit |--<------| netif_output | |
| | | | | | | |
| |--->----|---| esp_netif_receive |-->------| netif_input | |
| | | | | + ----------------+ |
| |...<....|...| esp_netif_free_rx_buffer |...<.....| packet buffer |
+-----| | | | | | |
| | | | | | (D) |
(B) | | | | (C) | +-----------------------+
--------+ | | +===========================+ NETWORK STACK
. . | *
. . | *
--------+ +================================+ * +-----------------------+
| | new/config get/set/apps | * | init |
| | |...*.....| Apps (DHCP, SNTP) |
| |--------------------------------| * | |
init | | |**** | |
start |************| event handler |*********| DHCP |
stop | | | | |
| |--------------------------------| | |
| | | | NETIF |
+-----| | | +-----------------+ |
| glue|---<----|---| esp_netif_transmit |--<------| netif_output | |
| | | | | | | |
| |--->----|---| esp_netif_receive |-->------| netif_input | |
| | | | | + ----------------+ |
| |...<....|...| esp_netif_free_rx_buffer |...<.....| packet buffer |
+-----| | | | | | |
| | | | | | (D) |
(B) | | | | (C) | +-----------------------+
--------+ | | +================================+ NETWORK STACK
NETWORK | | ESP-NETIF
INTERFACE | |
DRIVER | | +---------------------------+ +------------------+
| | | |.........| open/close |
| | | | | |
| -<--| l2tap_write |-----<---| write |
| | | | |
---->--| esp_vfs_l2tap_eth_filter |----->---| read |
| | | (A) |
| (E) | +------------------+
+---------------------------+ USER CODE
ESP-NETIF L2 TAP
DRIVER | | +--------------------------------+ +------------------+
| | | |.........| open/close |
| | | | | |
| -<--| l2tap_write |-----<---| write |
| | | | |
---->--| esp_vfs_l2tap_eth_filter_frame |----->---| read |
| | | (A) |
| (E) | +------------------+
+--------------------------------+ USER CODE
ESP-NETIF L2 TAP
Data and Event Flow in the Diagram
@@ -132,8 +132,9 @@ The newly opened ESP-NETIF L2 TAP file descriptor needs to be configured prior t
* ``L2TAP_S_INTF_DEVICE`` - bounds the file descriptor to a specific Network Interface that is identified by its ``if_key``. ESP-NETIF Network Interface ``if_key`` is passed to ``ioctl()`` as the third parameter. Note that default Network Interfaces ``if_key``'s used in ESP-IDF can be found in :component_file:`esp_netif/include/esp_netif_defaults.h`.
* ``L2TAP_S_DEVICE_DRV_HNDL`` - is another way to bound the file descriptor to a specific Network Interface. In this case, the Network interface is identified directly by IO Driver handle (e.g., :cpp:type:`esp_eth_handle_t` in case of Ethernet). The IO Driver handle is passed to ``ioctl()`` as the third parameter.
* ``L2TAP_S_RCV_FILTER`` - sets the filter to frames with the type to be passed to the file descriptor. In the case of Ethernet frames, the frames are to be filtered based on the Length and Ethernet type field. In case the filter value is set less than or equal to 0x05DC, the Ethernet type field is considered to represent IEEE802.3 Length Field, and all frames with values in interval <0, 0x05DC> at that field are passed to the file descriptor. The IEEE802.2 logical link control (LLC) resolution is then expected to be performed by the user's application. In case the filter value is set greater than 0x05DC, the Ethernet type field is considered to represent protocol identification and only frames that are equal to the set value are to be passed to the file descriptor.
* ``L2TAP_S_TIMESTAMP_EN`` - enables the hardware Time Stamping processing inside the file descriptor. The Time Stamps are retrieved to user space by using :ref:`Extended Buffer <esp_netif_l2tap_ext_buff>` mechanism when accessing the file descriptor by ``read()`` and ``write()`` functions. Hardware time stamping needs to be supported by target and needs to be enabled in IO Driver to this option work as expected.
All above-set configuration options have a getter counterpart option to read the current settings.
All above-set configuration options have a getter counterpart option to read the current settings except for ``L2TAP_S_TIMESTAMP_EN``.
.. warning::
The file descriptor needs to be firstly bounded to a specific Network Interface by ``L2TAP_S_INTF_DEVICE`` or ``L2TAP_S_DEVICE_DRV_HNDL`` to make ``L2TAP_S_RCV_FILTER`` option available.
@@ -153,14 +154,14 @@ All above-set configuration options have a getter counterpart option to read the
``fcntl()``
^^^^^^^^^^^
``fcntl()`` is used to manipulate with properties of opened ESP-NETIF L2 TAP file descriptor.
The ``fcntl()`` is used to manipulate with properties of opened ESP-NETIF L2 TAP file descriptor.
The following commands manipulate the status flags associated with the file descriptor:
* ``F_GETFD`` - the function returns the file descriptor flags, and the third argument is ignored.
* ``F_SETFD`` - sets the file descriptor flags to the value specified by the third argument. Zero is returned.
| On success, ``ioctl()`` returns 0. On error, -1 is returned, and ``errno`` is set to indicate the error.
| On success, ``fcntl()`` returns 0. On error, -1 is returned, and ``errno`` is set to indicate the error.
| * EBADF - not a valid file descriptor.
| * ENOSYS - unsupported command.
@@ -172,23 +173,24 @@ Opened and configured ESP-NETIF L2 TAP file descriptor can be accessed by ``read
| * EBADF - not a valid file descriptor.
| * EAGAIN - the file descriptor has been marked non-blocking (``O_NONBLOCK``), and the read would block.
.. note::
ESP-NETIF L2 TAP ``read()`` implementation extends the standard and offers Extended Buffer mechanism to retrieve additional information about received frame. See :ref:`Extended Buffer <esp_netif_l2tap_ext_buff>` section for more information.
``write()``
^^^^^^^^^^^
A raw Data Link Layer frame can be sent to Network Interface via opened and configured ESP-NETIF L2 TAP file descriptor. The user's application is responsible to construct the whole frame except for fields which are added automatically by the physical interface device. The following fields need to be constructed by the user's application in case of an Ethernet link: source/destination MAC addresses, Ethernet type, actual protocol header, and user data. The length of these fields is as follows:
.. list-table::
:header-rows: 1
:widths: 20 20 20 30
:align: center
.. packetdiag::
* - Destination MAC
- Source MAC
- Type/Length
- Payload (protocol header/data)
* - 6 B
- 6 B
- 2 B
- 0-1486 B
packetdiag {
colwidth = 16;
node_width = 38;
0-5: Destination MAC (6B) [color = "#ffcccc"];
6-11: Source MAC Port (6B) [color = "#ffcccc"];
12-13: Type/Length (2B) [color = "#ccccff"];
14-15: [color = "#ffffcc"];
16-31: Payload (protocol header/data - 1486B) [color = "#ffffcc", colheight = 3];
}
In other words, there is no additional frame processing performed by the ESP-NETIF L2 TAP interface. It only checks the Ethernet type of the frame is the same as the filter configured in the file descriptor. If the Ethernet type is different, an error is returned and the frame is not sent. Note that the ``write()`` may block in the current implementation when accessing a Network interface since it is a shared resource among multiple ESP-NETIF L2 TAP file descriptors and IP stack, and there is currently no queuing mechanism deployed.
@@ -197,9 +199,12 @@ In other words, there is no additional frame processing performed by the ESP-NET
| * EBADMSG - The Ethernet type of the frame is different from the file descriptor configured filter.
| * EIO - Network interface not available or busy.
.. note::
ESP-NETIF L2 TAP ``write()`` implementation extends the standard and offers Extended Buffer mechanism to retrieve additional information about transmitted frame. See :ref:`Extended Buffer <esp_netif_l2tap_ext_buff>` section for more information.
``close()``
^^^^^^^^^^^
Opened ESP-NETIF L2 TAP file descriptor can be closed by the ``close()`` to free its allocated resources. The ESP-NETIF L2 TAP implementation of ``close()`` may block. On the other hand, it is thread-safe and can be called from a different task than the file descriptor is actually used. If such a situation occurs and one task is blocked in the I/O operation and another task tries to close the file descriptor, the first task is unblocked. The first's task read operation then ends with an error.
Opened ESP-NETIF L2 TAP file descriptor can be closed by the ``close()`` to free its allocated resources. The ESP-NETIF L2 TAP implementation of ``close()`` may block. On the other hand, it is thread-safe and can be called from a different task than the file descriptor is actually used. If such a situation occurs and one task is blocked in the I/O operation and another task tries to close the file descriptor, the first task is unblocked. The first's task ``read`` operation then ends with returning `0` bytes was read.
| On success, ``close()`` returns zero. On error, -1 is returned, and ``errno`` is set to indicate the error.
| * EBADF - not a valid file descriptor.
@@ -208,6 +213,77 @@ Opened ESP-NETIF L2 TAP file descriptor can be closed by the ``close()`` to free
^^^^^^^^^^^^
Select is used in a standard way, just :ref:`CONFIG_VFS_SUPPORT_SELECT` needs to be enabled to make the ``select()`` function available.
.. _esp_netif_l2tap_ext_buff:
Extended Buffer
^^^^^^^^^^^^^^^
The Extended Buffer is ESP-NETIF L2 TAP's mechanism of how to retrieve additional information about transmitted or received IO frame via ``write()`` or ``read()`` functions. The Extended Buffer must be only used when specific functionality is enabled in the file descriptor (such as ``L2TAP_S_TIMESTAMP_EN``) and you want to access the additional data (such as Time Stamp) or control the frame processing.
The **Extended Buffer** is a structure with fields which serve as arguments to drive underlying functionality in the ESP-NETIF L2 TAP file descriptor. The structure is defined as follows:
.. code-block:: c
typedef struct {
size_t info_recs_len; /*!< Length of Information Records buffer */
void *info_recs_buff; /*!< Buffer holding extended information (IRECs) related to IO frames */
size_t buff_len; /*!< Length of the actual IO Frame buffer */
void *buff; /*!< Pointer to the IO Frame buffer */
} l2tap_extended_buff_t;
One Extended buffer may hold multiple **Information Records** (IRECs). These are variable data typed (and sized) records which may hold any datatype of additional information associated with the IO frame. The IREC structure is defined as follows:
.. code-block:: c
typedef struct
{
size_t len; /*!< Length of the record including header and data*/
l2tap_irec_type_t type; /*!< Type of the record */
alignas(long long) uint8_t data[]; /*!< Records Data aligned to double word */
} l2tap_irec_hdr_t;
Currently implement and used IREC data types are defined in :cpp:type:`l2tap_irec_type_t`.
Since the flexible array to hold data is used, proper memory alignment of multiple IRECs in the records buffer is required to correctly access memory. Improper alignment can result in slower memory access due to misaligned read/write operations, or in the worst case, cause undefined behavior on certain architectures. Therefore it is strictly recommended to use the below macros when manipulating with IRECs:
* ``L2TAP_IREC_SPACE()`` - determines the space required for an IREC, ensuring that it is properly aligned.
* ``L2TAP_IREC_LEN()`` - calculates the total length of one IREC, including the header and the data section of the record.
* ``L2TAP_IREC_FIRST()`` - retrieves the first IREC from the :cpp:member:`l2tap_extended_buff_t::info_recs_buff` pool of Extended Buffer. If the :cpp:member:`l2tap_extended_buff_t::info_recs_len` is smaller than the size of a record header, it returns NULL.
* ``L2TAP_IREC_NEXT()`` - retrieves the next IREC in the Extended Buffer after the current record. If the current record is NULL, it returns the first record.
Extended Buffer Usage
"""""""""""""""""""""
Prior any Extended Buffer IO operation (either ``write()`` or ``read()``), you first need to fully populate the Extended Buffer and its IREC fields. For example, when you want to retrieve Time Stamp, you need to set type of the IREC to :cpp:enumerator:`L2TAP_IREC_TIME_STAMP` and configure appropriate length. If you don't set the type correctly, the frame is still received or transmitted but information to be retrieved is lost. Similarly, when the IREC length is less than expected length, the frame is still received or transmitted but the type of affected IREC is marked to :cpp:enumerator:`L2TAP_IREC_INVALID` by the ESP-NETIF L2 TAP and information to be retrieved is lost.
When accessing the file descriptor using Extended Buffer, ``size`` parameter of ``write()`` or ``read()`` function must be set equal to ``0``. Failing to do so (i.e. accessing such file descriptor in a standard way with ``size`` parameter set to data length) will result in an -1 error and ``errno`` set to EINVAL.
.. code-block:: c
// wrap "Info Records Buffer" into union to ensure proper alignment of data (this is typically needed when
// accessing double word variables or structs containing double word variables)
union {
uint8_t info_recs_buff[L2TAP_IREC_SPACE(sizeof(struct timespec))];
l2tap_irec_hdr_t align;
} u;
l2tap_extended_buff_t ptp_msg_ext_buff;
ptp_msg_ext_buff.info_recs_len = sizeof(u.info_recs_buff);
ptp_msg_ext_buff.info_recs_buff = u.info_recs_buff;
ptp_msg_ext_buff.buff = eth_frame;
ptp_msg_ext_buff.buff_len = sizeof(eth_frame);
l2tap_irec_hdr_t *ts_info = L2TAP_IREC_FIRST(&ptp_msg_ext_buff);
ts_info->len = L2TAP_IREC_LEN(sizeof(struct timespec));
ts_info->type = L2TAP_IREC_TIME_STAMP;
int ret = write(state->ptp_socket, &ptp_msg_ext_buff, 0);
// check if write was successful and ts_info is valid
if (ret > 0 && ts_info->type == L2TAP_IREC_TIME_STAMP) {
*ts = *(struct timespec *)ts_info->data;
}
.. _esp_netif_other_events: