mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'feat/eth_netif_sub' into 'master'
Feat/eth netif sub Closes IDF-16068 and IDFCI-8665 See merge request espressif/esp-idf!48823
This commit is contained in:
1
Kconfig
1
Kconfig
@@ -1031,3 +1031,4 @@ mainmenu "Espressif IoT Development Framework Configuration"
|
||||
- CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS
|
||||
- CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION
|
||||
- CONFIG_COMPILER_KASAN
|
||||
- CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
|
||||
@@ -6,6 +6,7 @@ endif()
|
||||
|
||||
set(srcs)
|
||||
set(include)
|
||||
set(priv_include_dirs)
|
||||
set(ld_fragments linker.lf)
|
||||
# As CONFIG_ETH_ENABLED comes from Kconfig, it is not evaluated yet
|
||||
# when components are being registered.
|
||||
@@ -22,6 +23,14 @@ if(CONFIG_ETH_ENABLED)
|
||||
idf_build_get_property(components_to_build BUILD_COMPONENTS)
|
||||
if(esp_netif IN_LIST components_to_build)
|
||||
list(APPEND srcs "src/esp_eth_netif_glue.c")
|
||||
if(CONFIG_ETH_SUBLAYER_SUPPORT)
|
||||
list(APPEND srcs "src/sublayer/esp_eth_sublayer.c"
|
||||
"src/sublayer/esp_eth_sublayer_vlan_child.c")
|
||||
if(CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT)
|
||||
list(APPEND srcs "src/sublayer/esp_eth_sublayer_switch.c")
|
||||
endif()
|
||||
list(APPEND priv_include_dirs "src/sublayer")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -30,6 +39,7 @@ if(CONFIG_ETH_ENABLED)
|
||||
"src/mac/esp_eth_mac_esp_dma.c"
|
||||
"src/mac/esp_eth_mac_esp_gpio.c"
|
||||
"src/phy/esp_eth_phy_generic.c")
|
||||
list(APPEND priv_include_dirs "src/mac")
|
||||
if(CONFIG_SOC_PAU_SUPPORTED)
|
||||
list(APPEND srcs "src/mac/${target}/emac_retention.c")
|
||||
endif()
|
||||
@@ -47,7 +57,7 @@ endif()
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ${include}
|
||||
PRIV_INCLUDE_DIRS "src/mac"
|
||||
PRIV_INCLUDE_DIRS ${priv_include_dirs}
|
||||
LDFRAGMENTS ${ld_fragments}
|
||||
REQUIRES esp_event # For using "ESP_EVENT_DECLARE_BASE" in header file
|
||||
esp_hal_emac
|
||||
@@ -55,7 +65,18 @@ idf_component_register(SRCS "${srcs}"
|
||||
|
||||
if(CONFIG_ETH_ENABLED)
|
||||
if(IDF_BUILD_V2)
|
||||
target_sources(${COMPONENT_TARGET} PRIVATE "$<$<TARGET_EXISTS:idf::esp_netif>:src/esp_eth_netif_glue.c>")
|
||||
target_sources(${COMPONENT_TARGET} PRIVATE
|
||||
"$<$<TARGET_EXISTS:idf::esp_netif>:src/esp_eth_netif_glue.c>")
|
||||
if(CONFIG_ETH_SUBLAYER_SUPPORT)
|
||||
target_sources(${COMPONENT_TARGET} PRIVATE
|
||||
"$<$<TARGET_EXISTS:idf::esp_netif>:src/sublayer/esp_eth_sublayer.c>"
|
||||
"$<$<TARGET_EXISTS:idf::esp_netif>:src/sublayer/esp_eth_sublayer_vlan_child.c>")
|
||||
if(CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT)
|
||||
target_sources(${COMPONENT_TARGET} PRIVATE
|
||||
"$<$<TARGET_EXISTS:idf::esp_netif>:src/sublayer/esp_eth_sublayer_switch.c>")
|
||||
endif()
|
||||
target_include_directories(${COMPONENT_TARGET} PRIVATE "src/sublayer")
|
||||
endif()
|
||||
endif()
|
||||
if(CONFIG_ETH_USE_SPI_ETHERNET)
|
||||
idf_component_optional_requires(PUBLIC esp_driver_spi)
|
||||
|
||||
@@ -174,8 +174,103 @@ menu "Ethernet"
|
||||
config ETH_TRANSMIT_MUTEX
|
||||
depends on ETH_ENABLED
|
||||
bool "Enable Transmit Mutex"
|
||||
default y if ESP_NETIF_L2_TAP
|
||||
default n
|
||||
help
|
||||
Prevents multiple accesses when Ethernet interface is used as shared resource and multiple
|
||||
functionalities might try to access it at a time.
|
||||
|
||||
config ETH_SUBLAYER_SUPPORT
|
||||
bool "Support Ethernet netif sublayer (EXPERIMENTAL)"
|
||||
depends on ETH_ENABLED && IDF_EXPERIMENTAL_FEATURES
|
||||
default n
|
||||
help
|
||||
Enable the experimental Ethernet sublayer API (esp_eth_sublayer.h).
|
||||
|
||||
The sublayer is the coupling point between one physical Ethernet driver and one or more
|
||||
esp_netif instances, with optional 802.1Q VLAN demux/mux, integrated switch, and L2TAP integration.
|
||||
|
||||
This API is experimental and may change in future ESP-IDF releases.
|
||||
|
||||
if ETH_SUBLAYER_SUPPORT
|
||||
|
||||
config ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
bool "Expose the sublayer as an IO driver provider"
|
||||
default y if ESP_NETIF_L2_TAP
|
||||
default n
|
||||
help
|
||||
Build the sublayer's IO driver provider implementation : a generic virtual table
|
||||
(transmit / transmit_wrap / free_rx_buffer / get_ll_driver) that resolves a
|
||||
base, VLAN child, or integrated switch port into its transmit/free functions.
|
||||
|
||||
This is required for L2 TAP integration and is enabled automatically when
|
||||
ESP_NETIF_L2_TAP is selected. It can also be enabled independently so that other upper
|
||||
layers can obtain IO functions for a specific sublayer endpoint.
|
||||
|
||||
config ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
bool "Enable Ethernet sublayer Transmit Mutex"
|
||||
default y if ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
default n
|
||||
help
|
||||
Serializes the whole Ethernet sublayer TX path (TX hook, integrated switch mux, and the
|
||||
actual driver transmit call) per sublayer instance with a dedicated mutex.
|
||||
|
||||
This is needed whenever more than one task can end up transmitting through the same
|
||||
sublayer concurrently (e.g. multiple L2TAP sockets bound to different switch/VLAN ports,
|
||||
each potentially owned by a different task), since TX hooks and integrated switch drivers
|
||||
may keep small amounts of mutable state (e.g. tag/padding scratch buffers) that are only
|
||||
safe to access from one task at a time.
|
||||
|
||||
This option is independent of ETH_TRANSMIT_MUTEX: enabling one does not disable the other.
|
||||
If every esp_eth_handle_t used by the application is exclusively driven through a
|
||||
sublayer, ETH_TRANSMIT_MUTEX can usually be left disabled to avoid the extra locking, since
|
||||
this option already serializes the complete TX path leading up to the driver transmit
|
||||
call. However, if any Ethernet handle is also transmitted to directly (bypassing the
|
||||
sublayer), ETH_TRANSMIT_MUTEX is still required for that handle - this option only
|
||||
protects traffic that goes through eth_sublayer_transmit().
|
||||
|
||||
config ETH_SUBLAYER_VLAN_SUPPORT
|
||||
bool "Enable 802.1Q VLAN support in the sublayer"
|
||||
default n
|
||||
help
|
||||
Allow tagged 802.1Q VLAN netifs to be created on top of the sublayer, in addition
|
||||
to the plain untagged one.
|
||||
|
||||
Disabling this option restricts the sublayer to a single, untagged netif per
|
||||
Ethernet driver (or per integrated switch port), reducing code size and RAM usage.
|
||||
|
||||
config ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
bool "Enable integrated Ethernet switch support in the sublayer"
|
||||
default n
|
||||
help
|
||||
Enable support for attaching an integrated Ethernet switch to the sublayer,
|
||||
including switch-specific frame mux/demux and per-port IO driver handling.
|
||||
|
||||
This is intended for Ethernet switch drivers that require host-interface frame
|
||||
tagging. Leave this option disabled to reduce code size and RAM usage when the
|
||||
sublayer is used without a switch.
|
||||
|
||||
config ETH_SUBLAYER_TX_BUF_DESC_CAP_RANGE_MIN
|
||||
int
|
||||
default 3 if ETH_SUBLAYER_VLAN_SUPPORT && ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
default 2 if ETH_SUBLAYER_VLAN_SUPPORT || ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
default 1
|
||||
|
||||
config ETH_SUBLAYER_TX_BUF_DESC_CAPACITY
|
||||
int "Sublayer TX buffer descriptor capacity"
|
||||
range ETH_SUBLAYER_TX_BUF_DESC_CAP_RANGE_MIN 16
|
||||
default ETH_SUBLAYER_TX_BUF_DESC_CAP_RANGE_MIN
|
||||
help
|
||||
Maximum number of esp_eth_buf_desc_t entries available to the
|
||||
Ethernet sublayer TX path (including TX hook edits).
|
||||
|
||||
If TX hook needs to add more frame segments without memcpying the entire frame,
|
||||
it can do so by adding more entries to the descriptor array.
|
||||
|
||||
The descriptor array is stack-allocated on hot TX paths, so larger
|
||||
values increase stack usage but allow hooks to add more frame
|
||||
segments without allocating a new descriptor array.
|
||||
|
||||
endif # ETH_SUBLAYER_SUPPORT
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/**
|
||||
* Purpose of this file is to create a common header for the typical ethernet usecase,
|
||||
* so using the ethernet driver and the default glue layer to its network interface.
|
||||
* so using the ethernet driver and the default glue or sublayer layer to its network interface.
|
||||
*
|
||||
* If you prefer to create a custom network interface or use the Ethernet as a driver only,
|
||||
* then it is recommended to include the "esp_eth_driver.h" only.
|
||||
*/
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_eth_netif_glue.h"
|
||||
#if CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
#include "esp_eth_sublayer.h"
|
||||
#endif
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_event_base.h"
|
||||
#include "hal/eth_types.h"
|
||||
@@ -14,6 +16,14 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Scatter-gather buffer descriptor for extended Ethernet transmit.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t *buf; /*!< Pointer to the buffer segment */
|
||||
size_t len; /*!< Length of the buffer segment in bytes */
|
||||
} esp_eth_buf_desc_t;
|
||||
|
||||
/**
|
||||
* @brief Offset for start of MAC custom ioctl commands
|
||||
*
|
||||
|
||||
@@ -324,6 +324,8 @@ esp_err_t esp_eth_transmit(esp_eth_handle_t hdl, void *buf, size_t length);
|
||||
/**
|
||||
* @brief Extended Transmit with variable number of arguments
|
||||
*
|
||||
* @deprecated Use esp_eth_transmit_ctrl_bufs() instead.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
@@ -337,12 +339,13 @@ esp_err_t esp_eth_transmit(esp_eth_handle_t hdl, void *buf, size_t length);
|
||||
* - 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, ...);
|
||||
esp_err_t esp_eth_transmit_ctrl_vargs(esp_eth_handle_t hdl, void *ctrl, uint32_t argc, ...)
|
||||
__attribute__((deprecated("Use esp_eth_transmit_ctrl_bufs() instead")));
|
||||
|
||||
/**
|
||||
* @brief Wrapper over Extended Transmit function to ensure backward compatibility.
|
||||
*
|
||||
* @note For new implementations, it is recommended to use `esp_eth_transmit_ctrl_vargs()` directly.
|
||||
* @note For new implementations, it is recommended to use `esp_eth_transmit_ctrl_bufs()` directly.
|
||||
*
|
||||
* @param[in] eth_hdl handle of Ethernet driver
|
||||
* @param[in] argc number variable arguments
|
||||
@@ -355,6 +358,25 @@ esp_err_t esp_eth_transmit_ctrl_vargs(esp_eth_handle_t hdl, void *ctrl, uint32_t
|
||||
*/
|
||||
#define esp_eth_transmit_vargs(eth_hdl, argc, ...) esp_eth_transmit_ctrl_vargs(eth_hdl, NULL, (argc) * 2, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @brief Extended transmit from scatter-gather buffer descriptors.
|
||||
*
|
||||
* @note Typical intended use case is to assemble an Ethernet frame from multiple
|
||||
* input buffers at the 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 bufs array of buffer descriptors comprising the frame
|
||||
* @param buf_count number of valid entries in @a bufs
|
||||
* @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_bufs(esp_eth_handle_t hdl, void *ctrl, const esp_eth_buf_desc_t *bufs, size_t buf_count);
|
||||
|
||||
/**
|
||||
* @brief Misc IO function of Ethernet driver
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -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 scatter-gather buffer descriptors.
|
||||
*
|
||||
* @param[in] mac: Ethernet MAC instance
|
||||
* @param[in] ctrl: optional transmit control structure (chip specific), set to NULL when not required
|
||||
* @param[in] bufs: array of buffer descriptors comprising the frame
|
||||
* @param[in] buf_count: number of valid entries in @a bufs
|
||||
*
|
||||
* @note Typical intended use case is to make possible to construct a frame from multiple higher layer
|
||||
* buffers without a need of buffer reallocations.
|
||||
*
|
||||
* @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_bufs)(esp_eth_mac_t *mac, void *ctrl, const esp_eth_buf_desc_t *bufs, size_t buf_count);
|
||||
|
||||
/**
|
||||
* @brief Transmit packet with extended control from Ethernet MAC and constructed with special parameters at Layer2.
|
||||
*
|
||||
* @warning Deprecated, use `transmit_ctrl_bufs()` instead.
|
||||
*
|
||||
* @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
|
||||
@@ -123,7 +146,7 @@ struct esp_eth_mac_s {
|
||||
* @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);
|
||||
esp_err_t (*transmit_ctrl_vargs)(esp_eth_mac_t *mac, void *ctrl, uint32_t argc, va_list args) __attribute__((deprecated("Use transmit_ctrl_bufs instead")));
|
||||
|
||||
/**
|
||||
* @brief Transmit packet from Ethernet MAC constructed with special parameters at Layer2.
|
||||
@@ -145,7 +168,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) __attribute__((deprecated("Use transmit_ctrl_vargs instead")));
|
||||
esp_err_t (*transmit_vargs)(esp_eth_mac_t *mac, uint32_t argc, va_list args) __attribute__((deprecated("Use transmit_ctrl_bufs instead")));
|
||||
|
||||
/**
|
||||
* @brief Receive packet from Ethernet MAC
|
||||
|
||||
360
components/esp_eth/include/esp_eth_sublayer.h
Normal file
360
components/esp_eth/include/esp_eth_sublayer.h
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct esp_eth_sublayer_s *esp_eth_sublayer_handle_t;
|
||||
typedef struct esp_eth_sublayer_vlan_s *esp_eth_sublayer_vlan_handle_t;
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
typedef struct esp_eth_sublayer_switch_s *esp_eth_sublayer_switch_handle_t;
|
||||
#endif
|
||||
|
||||
#define ESP_ETH_SUBLAYER_UNTAGGED_VID UINT16_MAX
|
||||
#define ESP_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY CONFIG_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY
|
||||
|
||||
/**
|
||||
* @brief Mutable TX buffer descriptor set passed through the sublayer TX path.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_eth_buf_desc_t *bufs; /*!< Descriptor array */
|
||||
size_t *buf_count; /*!< In/out: number of valid entries in @c bufs */
|
||||
const size_t buf_capacity; /*!< Maximum number of entries in @c bufs */
|
||||
} esp_eth_sublayer_tx_bufs_t;
|
||||
|
||||
/**
|
||||
* @brief Event info structure for sublayer optional events.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
esp_event_base_t base;
|
||||
int32_t event_id;
|
||||
void *event_data;
|
||||
} esp_eth_sublayer_event_t;
|
||||
|
||||
/**
|
||||
* @brief Extra information for RX hook callbacks.
|
||||
*/
|
||||
typedef struct {
|
||||
void *l2_buffer; /*!< Starting address of the original Ethernet driverRX allocation */
|
||||
void *driver_info; /*!< Opaque per-frame metadata from MAC / stack_input_info, may be NULL */
|
||||
} esp_eth_sublayer_rx_info_t;
|
||||
|
||||
/**
|
||||
* @brief Optional TX hook on frames passed to the Ethernet driver (after VLAN tag insert for VLAN netifs).
|
||||
*
|
||||
* When a VLAN tag is inserted the sublayer splits the frame into two
|
||||
* esp_eth_buf_desc_t entries (tagged header + payload) so that no payload
|
||||
* memcpy is needed. For untagged frames a single descriptor is passed.
|
||||
*
|
||||
* The hook may modify the descriptor array in place:
|
||||
* - Change @c buf pointers, @c len values, and descriptor order (e.g. insert a
|
||||
* prefix in @c bufs[0] and move the original frame to @c bufs[1], encrypt into
|
||||
* a new buffer, or merge scattered segments into one contiguous @c bufs[0]).
|
||||
* Use @c post_tx_hook to free any buffers allocated for these edits after the
|
||||
* driver finishes transmitting.
|
||||
* - Adjust *@a tx_bufs->buf_count up or down, but never exceed @a tx_bufs->buf_capacity.
|
||||
* - Must NOT free the input buffers.
|
||||
* - May set *@a tx_bufs->buf_count to 0 to skip driver transmit and @c post_tx_hook
|
||||
* (e.g. the hook handled the frame elsewhere or chose to drop it). The @a tx_bufs
|
||||
* descriptor set remains caller-owned; only transmission is suppressed.
|
||||
*
|
||||
* @param eth Underlying Ethernet driver handle
|
||||
* @param tx_bufs Mutable buffer descriptor set comprising the frame
|
||||
* @param ctx User context from esp_eth_sublayer_config_t::hook_ctx
|
||||
* @return ESP_OK to transmit; other values abort transmit for this packet
|
||||
*/
|
||||
typedef esp_err_t (*esp_eth_sublayer_tx_hook_t)(esp_eth_handle_t eth,
|
||||
esp_eth_sublayer_tx_bufs_t *tx_bufs,
|
||||
void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Optional post-TX hook called after a successful TX hook, to release resources it allocated.
|
||||
*
|
||||
* @warning The Tx buffer descriptors may have different order than on the output of the TX hook.
|
||||
*
|
||||
* Intended for freeing buffers that were allocated by the TX hook.
|
||||
* Called whenever the TX hook completed with @a *tx_bufs->buf_count > 0, regardless of the transmit result
|
||||
* (or of any failure that occurs after the TX hook and before the actual transmit). This guarantees that
|
||||
* buffers allocated by the TX hook are always released.
|
||||
*
|
||||
* Not called when the TX hook sets @a *tx_bufs->buf_count to 0 (transmit suppressed) or returns non-ESP_OK.
|
||||
*
|
||||
* @param eth Underlying Ethernet driver handle
|
||||
* @param tx_bufs Buffer descriptor set that was passed to (or prepared for) the driver
|
||||
* @param ctx User context from esp_eth_sublayer_config_t::hook_ctx
|
||||
*/
|
||||
typedef void (*esp_eth_sublayer_post_tx_hook_t)(esp_eth_handle_t eth,
|
||||
const esp_eth_sublayer_tx_bufs_t *tx_bufs,
|
||||
void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Optional RX hook on frames from the Ethernet driver (before VLAN demux / input path).
|
||||
*
|
||||
* The hook may strip a leading protocol header (e.g. MACsec SecTAG) by advancing @p buf forward and
|
||||
* decrementing @p len, or remove a trailing trailer by decrementing @p len only.
|
||||
* @p buf must remain within the original allocation.
|
||||
*
|
||||
* Set @c *len to 0 to take ownership of the original allocation and stop further sublayer processing.
|
||||
* The hook must then release or forward @c info->alloc_base itself.
|
||||
*
|
||||
* @param eth Underlying Ethernet driver handle (when integrated switch is used, this is per port handle)
|
||||
* @param[in,out] buf in: frame start; out: frame start with hook-specific header/trailer removed
|
||||
* @param[in,out] len frame length; set to 0 to stop further processing
|
||||
* @param info Per-frame RX metadata; never NULL
|
||||
* @param ctx User context from esp_eth_sublayer_config_t::hook_ctx
|
||||
* @return ESP_OK to continue processing; other values cause the sublayer to free the allocation and stop
|
||||
*/
|
||||
typedef esp_err_t (*esp_eth_sublayer_rx_hook_t)(esp_eth_handle_t eth, uint8_t **buf, uint32_t *len, esp_eth_sublayer_rx_info_t *info, void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Sublayer configuration structure.
|
||||
*
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
esp_eth_handle_t eth_handle; /*!< Underlying Ethernet driver handle the sublayer is bound to (required) */
|
||||
|
||||
/**
|
||||
* Optional event that must be observed (in addition to the physical link being up) before the sublayer
|
||||
* reports child netifs as connected. Leave @c base NULL (the default) to bring netifs up as soon as
|
||||
* ETHERNET_EVENT_CONNECTED fires, with no extra confirmation needed.
|
||||
*
|
||||
* The order in which this event and ETHERNET_EVENT_CONNECTED are received is not significant, the child
|
||||
* netifs are brought up once both conditions are met. Any subsequent disconnect (ETHERNET_EVENT_DISCONNECTED,
|
||||
* ETHERNET_EVENT_STOP or @c disconnect_trigger_event) invalidates the confirmation, i.e. the event needs to
|
||||
* be posted again to bring the child netifs up.
|
||||
*/
|
||||
esp_eth_sublayer_event_t connect_confirm_event;
|
||||
|
||||
/**
|
||||
* Optional event that, in addition to ETHERNET_EVENT_DISCONNECTED, forces all child netifs down
|
||||
* (e.g. an upper-layer authentication/link-partner event). Leave @c base NULL (the default) to only
|
||||
* react to ETHERNET_EVENT_DISCONNECTED.
|
||||
*/
|
||||
esp_eth_sublayer_event_t disconnect_trigger_event;
|
||||
|
||||
esp_eth_sublayer_tx_hook_t tx_hook; /*!< Optional TX hook, see esp_eth_sublayer_tx_hook_t (NULL to disable) */
|
||||
esp_eth_sublayer_post_tx_hook_t post_tx_hook; /*!< Optional post-TX hook, see esp_eth_sublayer_post_tx_hook_t (NULL to disable) */
|
||||
esp_eth_sublayer_rx_hook_t rx_hook; /*!< Optional RX hook, see esp_eth_sublayer_rx_hook_t (NULL to disable) */
|
||||
void *hook_ctx; /*!< User context passed to tx_hook, post_tx_hook, and rx_hook */
|
||||
|
||||
/**
|
||||
* Timeout (ms) to acquire the CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX before failing with ESP_ERR_TIMEOUT.
|
||||
* 0 uses the built-in default. Ignored when CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX is disabled.
|
||||
*/
|
||||
uint32_t transmit_mutex_timeout_ms;
|
||||
} esp_eth_sublayer_config_t;
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
/**
|
||||
* @brief Integrated switch "tag process init" callback.
|
||||
*
|
||||
* Invoked once by the sublayer when it is created. The driver allocates and returns (through @p ctx) all the
|
||||
* state it needs to mux/demux Tail Tagged traffic between the single Host Ethernet interface and the per-port
|
||||
* Ethernet interfaces. The sublayer owns the returned @p ctx and passes it back to the demux and deinit callbacks.
|
||||
*
|
||||
* @param[in] host_eth_handle Host Ethernet driver handle (the one the sublayer is bound to)
|
||||
* @param[in] port_eth_handles array of per-port Ethernet driver handles
|
||||
* @param[in] ports_count number of entries in @p port_eth_handles
|
||||
* @param[out] ctx location to store the driver's integrated switch context
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
typedef esp_err_t (*esp_eth_sublayer_switch_tag_init_t)(esp_eth_handle_t host_eth_handle,
|
||||
esp_eth_handle_t *port_eth_handles,
|
||||
uint32_t ports_count,
|
||||
void **ctx);
|
||||
|
||||
/**
|
||||
* @brief Integrated switch "tag process deinit" callback. Releases the context allocated by the init callback.
|
||||
*
|
||||
* @param[in] ctx driver integrated switch context returned by the init callback
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
typedef esp_err_t (*esp_eth_sublayer_switch_tag_deinit_t)(void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Integrated switch RX demux callback.
|
||||
*
|
||||
* Called by the sublayer at the very beginning of the RX path (before RX hook / L2 TAP / VLAN demux). The driver
|
||||
* strips its switch tag and reports the ingress port number through @p src_port. The frame is NOT re-routed by
|
||||
* this callback; the sublayer continues its normal RX pipeline on the now tag-free frame.
|
||||
*
|
||||
* Tag stripping strategies:
|
||||
* - Trailing tag (e.g. KSZ8863 Tail Tag): decrement @p length.
|
||||
* - Leading tag after the MAC header (e.g. DSA, 802.1Q port-VLAN): advance @p buffer forward and decrement
|
||||
* @p length (typically after memmove of the 12-byte MAC header over the tag).
|
||||
*
|
||||
* The callback must NOT free or realloc the buffer; on error the sublayer frees the original allocation.
|
||||
* @p buffer must remain within the original allocation.
|
||||
*
|
||||
* @param[in] ctx driver integrated switch context returned by the init callback
|
||||
* @param[in,out] buffer in: frame start including the switch tag; out: frame start with tag removed
|
||||
* @param[in,out] length frame length including the tag on input, with the tag removed on output
|
||||
* @param[out] src_port ingress port number (callback may ignore if NULL)
|
||||
* @return ESP_OK on success, error code otherwise (frame will be dropped by the sublayer)
|
||||
*/
|
||||
typedef esp_err_t (*esp_eth_sublayer_switch_demux_t)(void *ctx, uint8_t **buffer, uint32_t *length, int32_t *src_port);
|
||||
|
||||
/**
|
||||
* @brief Integrated switch TX mux callback.
|
||||
*
|
||||
* Called by the sublayer during transmit to append switch-specific tagging (e.g. KSZ8863 Tail Tag) to
|
||||
* @a tx_bufs before the frame is sent on the Host Ethernet interface.
|
||||
*
|
||||
* @param[in] ctx driver integrated switch context returned by the init callback
|
||||
* @param[in,out] tx_bufs buffer descriptor set comprising the frame
|
||||
* @param port destination port number, or negative for default switch lookup behaviour
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
typedef esp_err_t (*esp_eth_sublayer_switch_mux_t)(void *ctx, esp_eth_sublayer_tx_bufs_t *tx_bufs, int32_t port);
|
||||
|
||||
/**
|
||||
* @brief Integrated switch configuration.
|
||||
*
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
esp_eth_sublayer_switch_tag_init_t tag_process_init; /*!< Called when the switch is added; returns @c ctx */
|
||||
esp_eth_sublayer_switch_tag_deinit_t tag_process_deinit; /*!< Called when the switch is deleted */
|
||||
esp_eth_sublayer_switch_demux_t demux; /*!< Called per RX frame to strip the tag / resolve the ingress port */
|
||||
esp_eth_sublayer_switch_mux_t mux; /*!< Called per TX frame to append the switch tag */
|
||||
esp_eth_handle_t host_eth_handle;
|
||||
esp_eth_handle_t *port_eth_handles;
|
||||
uint32_t ports_count;
|
||||
} esp_eth_sublayer_switch_config_t;
|
||||
#endif // CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
|
||||
/**
|
||||
* @brief Default sublayer configuration.
|
||||
*
|
||||
*/
|
||||
#define ESP_ETH_SUBLAYER_CONFIG_DEFAULT() \
|
||||
{ \
|
||||
.eth_handle = NULL, \
|
||||
.connect_confirm_event = { NULL, 0, NULL }, \
|
||||
.disconnect_trigger_event = { NULL, 0, NULL }, \
|
||||
.tx_hook = NULL, \
|
||||
.post_tx_hook = NULL, \
|
||||
.rx_hook = NULL, \
|
||||
.hook_ctx = NULL, \
|
||||
.transmit_mutex_timeout_ms = 0, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a Ethernet sublayer for an Ethernet driver.
|
||||
*
|
||||
* @note The interface base sublayer is required to be created. Derived child
|
||||
* interfaces can be used and attached to netifs (e.g. VLAN interfaces).
|
||||
*
|
||||
* @warning Management of the Ethernet-Netif sublayer is not thread-safe for performance reasons.
|
||||
* Configure it only from a single task/context (for example during initialization).
|
||||
*
|
||||
* @param config Sublayer configuration
|
||||
* @param[out] sublayer Location to store the created sublayer handle
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t esp_eth_sublayer_new(const esp_eth_sublayer_config_t *config, esp_eth_sublayer_handle_t *sublayer);
|
||||
|
||||
/**
|
||||
* @brief Delete Ethernet sublayer and all child entries (VLANs).
|
||||
*
|
||||
* Caller must detach and destroy esp_netif instances before calling this.
|
||||
*
|
||||
* @param sublayer Sublayer handle
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t esp_eth_sublayer_del(esp_eth_sublayer_handle_t sublayer);
|
||||
|
||||
/**
|
||||
* @brief Add a VLAN entry to the sublayer (802.1Q VID 0..4095 excluding duplicates).
|
||||
*
|
||||
* Attach the VLAN netif with esp_netif_attach(esp_netif, returned handle).
|
||||
*
|
||||
* @warning Management of the Ethernet-Netif sublayer is not thread-safe for performance reasons.
|
||||
* Configure it only from a single task/context (for example during initialization).
|
||||
*
|
||||
* @param sublayer Sublayer handle
|
||||
* @param vlan_id VLAN ID (12-bit), or ESP_ETH_SUBLAYER_UNTAGGED_VID for the untagged child
|
||||
* @param[out] vlan Location to store the created VLAN handle
|
||||
* @return ESP_OK on success; ESP_ERR_INVALID_STATE if the VLAN ID is already present;
|
||||
* ESP_ERR_NOT_SUPPORTED if @p vlan_id is not ESP_ETH_SUBLAYER_UNTAGGED_VID and
|
||||
* CONFIG_ETH_SUBLAYER_VLAN_SUPPORT is disabled
|
||||
*/
|
||||
esp_err_t esp_eth_sublayer_vlan_add(esp_eth_sublayer_handle_t sublayer, uint16_t vlan_id,
|
||||
esp_eth_sublayer_vlan_handle_t *vlan);
|
||||
|
||||
/**
|
||||
* @brief Remove a VLAN entry from the sublayer.
|
||||
*
|
||||
* @param sublayer Sublayer handle
|
||||
* @param vlan VLAN handle returned by esp_eth_sublayer_vlan_add
|
||||
* @return ESP_ERR_NOT_FOUND if not on this sublayer; ESP_OK otherwise
|
||||
*/
|
||||
esp_err_t esp_eth_sublayer_vlan_del(esp_eth_sublayer_handle_t sublayer, esp_eth_sublayer_vlan_handle_t vlan);
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
/**
|
||||
* @brief Add the integrated switch (Tail Tag) processing to the sublayer.
|
||||
*
|
||||
* A sublayer can hold at most one switch instance. The switch is attached to the sublayer similarly to a VLAN
|
||||
* child but it is a single instance rather than a list. The provided @p config callbacks (`tag_process_init`,
|
||||
* `demux`, `tag_process_deinit`) are implemented by the integrated switch driver (e.g. KSZ8863). `tag_process_init`
|
||||
* is invoked here to create the driver's switch context.
|
||||
*
|
||||
* @warning Management of the Ethernet-Netif sublayer is not thread-safe for performance reasons.
|
||||
* Configure it only from a single task/context (for example during initialization).
|
||||
*
|
||||
* @param sublayer Sublayer handle
|
||||
* @param config Integrated switch configuration (its own configuration, not part of the sublayer config)
|
||||
* @param[out] sw Location to store the created switch handle
|
||||
* @return ESP_OK on success; ESP_ERR_INVALID_STATE if a switch is already added
|
||||
*/
|
||||
esp_err_t esp_eth_sublayer_switch_add(esp_eth_sublayer_handle_t sublayer,
|
||||
const esp_eth_sublayer_switch_config_t *config,
|
||||
esp_eth_sublayer_switch_handle_t *sw);
|
||||
|
||||
/**
|
||||
* @brief Remove the integrated switch from the sublayer.
|
||||
*
|
||||
* @param sw Switch handle returned by esp_eth_sublayer_switch_add
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t esp_eth_sublayer_switch_del(esp_eth_sublayer_switch_handle_t sw);
|
||||
#endif // CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
|
||||
/**
|
||||
* @brief Get the underlying Ethernet driver handle from the sublayer.
|
||||
*
|
||||
* @param sublayer Sublayer handle
|
||||
* @return Ethernet driver handle
|
||||
*/
|
||||
esp_eth_handle_t esp_eth_sublayer_get_eth_handle(esp_eth_sublayer_handle_t sublayer);
|
||||
|
||||
/**
|
||||
* @brief Get the underlying Ethernet driver handle from the VLAN child of the sublayer.
|
||||
*
|
||||
* @param vlan VLAN handle
|
||||
* @return Ethernet driver handle
|
||||
*/
|
||||
esp_eth_handle_t esp_eth_sublayer_vlan_get_eth_handle(esp_eth_sublayer_vlan_handle_t vlan);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* @file esp_private/esp_eth_sublayer_iodriver.h
|
||||
*
|
||||
* @brief Internal (inter-component) IO driver access contract.
|
||||
*
|
||||
* This header is NOT part of the stable public application API. It defines a generic virtual table used
|
||||
* between an IO driver provider (e.g. the Ethernet sublayer) and a consumer (e.g. L2 TAP, or any other
|
||||
* upper layer).
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle identifying a single logical IO driver endpoint.
|
||||
*
|
||||
* An IO driver endpoint is a point at which L2 frames can be transmitted and received (e.g. an Ethernet
|
||||
* driver, a VLAN child, or an integrated switch port of the Ethernet sublayer). The concrete meaning of the
|
||||
* handle is defined by the provider that resolves it (see @ref esp_eth_iodriver_provider_base_t).
|
||||
*/
|
||||
typedef void *esp_eth_iodriver_handle;
|
||||
|
||||
/**
|
||||
* @brief Opaque handle identifying an IO driver provider.
|
||||
*
|
||||
* A provider knows how to resolve one or more @ref esp_eth_iodriver_handle values into a set of IO functions
|
||||
* (@ref esp_eth_iodriver_io_fns_t). The Ethernet sublayer is one such provider.
|
||||
*/
|
||||
typedef void *esp_eth_iodriver_provider_handle;
|
||||
|
||||
/**
|
||||
* @brief Set of IO functions for a single IO driver endpoint.
|
||||
*
|
||||
* This is a lightweight virtual table that decouples a consumer (e.g. L2 TAP, or any other upper layer) from
|
||||
* the concrete implementation of an IO driver endpoint. A provider fills this structure for a given
|
||||
* @ref esp_eth_iodriver_handle; the consumer then calls the function pointers passing back @c io_handle.
|
||||
*
|
||||
* @note This concept is intentionally independent of any particular consumer so it can be reused by layers
|
||||
* other than L2 TAP.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_eth_iodriver_handle io_handle; /*!< Handle passed back to the function pointers below */
|
||||
esp_err_t (*iodriver_transmit)(esp_eth_iodriver_handle io_handle, void *buf, size_t len); /*!< Transmit a single buffer */
|
||||
esp_err_t (*iodriver_transmit_wrap)(esp_eth_iodriver_handle io_handle, void *buf, size_t len, void *eb); /*!< Transmit with a driver control/extended buffer (e.g. metadata) */
|
||||
void (*iodriver_free_rx_buffer)(esp_eth_iodriver_handle io_handle, void *buffer); /*!< Free an RX buffer previously received on this endpoint */
|
||||
esp_err_t (*iodriver_get_ll_driver)(esp_eth_iodriver_handle io_handle, void **ll_driver); /*!< Get the underlying low-level driver handle */
|
||||
} esp_eth_iodriver_io_fns_t;
|
||||
|
||||
/**
|
||||
* @brief Base interface implemented by an IO driver provider.
|
||||
*
|
||||
* A provider is registered with a consumer (e.g. L2 TAP) by embedding this structure as the first member of
|
||||
* the provider object, so the provider handle can be cast to @c esp_eth_iodriver_provider_base_t *.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* @brief Resolve an IO driver handle into its set of IO functions.
|
||||
*
|
||||
* @warning The consumer may call this callback with its internal lock held. The implementation
|
||||
* must therefore be a simple lookup only: it must not log, allocate, block, or take any lock.
|
||||
*
|
||||
* @param[in] provider_handle Provider handle (the object embedding this base).
|
||||
* @param[in] io_handle IO driver handle to resolve.
|
||||
* @param[out] io_fns Filled with the IO functions for @p io_handle on success.
|
||||
* @return
|
||||
* - ESP_OK if @p io_handle is served by this provider and @p io_fns was filled
|
||||
* - ESP_ERR_NOT_FOUND if this provider does not serve @p io_handle
|
||||
* - ESP_ERR_INVALID_ARG on invalid arguments
|
||||
*/
|
||||
esp_err_t (*get_io_fns)(esp_eth_iodriver_provider_handle provider_handle, void *io_handle, esp_eth_iodriver_io_fns_t *io_fns);
|
||||
} esp_eth_iodriver_provider_base_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -3,8 +3,10 @@ archive: libesp_eth.a
|
||||
entries:
|
||||
if ETH_IRAM_OPTIMIZATION = y:
|
||||
esp_eth:esp_eth_transmit (noflash_text)
|
||||
esp_eth:esp_eth_transmit_ctrl_bufs (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_ctrl_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)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_eth_driver.h"
|
||||
@@ -403,7 +405,30 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_transmit_ctrl_vargs(esp_eth_handle_t hdl, void *ctrl, uint32_t argc, ...)
|
||||
static esp_err_t esp_eth_transmit_ctrl_bufs_fallback(esp_eth_mac_t *mac, const esp_eth_buf_desc_t *bufs, size_t buf_count)
|
||||
{
|
||||
size_t total_len = 0;
|
||||
for (size_t i = 0; i < buf_count; i++) {
|
||||
total_len += bufs[i].len;
|
||||
}
|
||||
|
||||
uint8_t *single_buff = (uint8_t *)malloc(total_len);
|
||||
if (single_buff == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
for (size_t i = 0; i < buf_count; i++) {
|
||||
memcpy(single_buff + offset, bufs[i].buf, bufs[i].len);
|
||||
offset += bufs[i].len;
|
||||
}
|
||||
|
||||
esp_err_t ret = mac->transmit(mac, single_buff, (uint32_t)total_len);
|
||||
free(single_buff);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_transmit_ctrl_bufs(esp_eth_handle_t hdl, void *ctrl, const esp_eth_buf_desc_t *bufs, size_t buf_count)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_eth_driver_t *eth_driver = (esp_eth_driver_t *)hdl;
|
||||
@@ -415,22 +440,46 @@ esp_err_t esp_eth_transmit_ctrl_vargs(esp_eth_handle_t hdl, void *ctrl, uint32_t
|
||||
}
|
||||
|
||||
esp_eth_mac_t *mac = eth_driver->mac;
|
||||
|
||||
#if CONFIG_ETH_TRANSMIT_MUTEX
|
||||
if (xSemaphoreTake(eth_driver->transmit_mutex, pdMS_TO_TICKS(ESP_ETH_TX_TIMEOUT_MS)) == pdFALSE) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
#endif // CONFIG_ETH_TRANSMIT_MUTEX
|
||||
va_list args = {0};
|
||||
va_start(args, argc);
|
||||
ret = mac->transmit_ctrl_vargs(mac, ctrl, argc, args);
|
||||
|
||||
// if the underlying MAC does not support transmit_ctrl_bufs (multiple buffer zero-copy transmit), use the fallback
|
||||
if (mac->transmit_ctrl_bufs) {
|
||||
ret = mac->transmit_ctrl_bufs(mac, ctrl, bufs, buf_count);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Using fallback transmit function (transmit control and multiple buffer zero-copy not supported)");
|
||||
ret = esp_eth_transmit_ctrl_bufs_fallback(mac, bufs, buf_count);
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_TRANSMIT_MUTEX
|
||||
xSemaphoreGive(eth_driver->transmit_mutex);
|
||||
#endif // CONFIG_ETH_TRANSMIT_MUTEX
|
||||
va_end(args);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_transmit_ctrl_vargs(esp_eth_handle_t hdl, void *ctrl, uint32_t argc, ...)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t buf_num = argc / 2;
|
||||
esp_eth_buf_desc_t stack_bufs[buf_num];
|
||||
va_list args = {0};
|
||||
|
||||
va_start(args, argc);
|
||||
for (uint32_t i = 0; i < buf_num; i++) {
|
||||
stack_bufs[i].buf = va_arg(args, uint8_t *);
|
||||
stack_bufs[i].len = va_arg(args, uint32_t);
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
ret = esp_eth_transmit_ctrl_bufs(hdl, ctrl, stack_bufs, buf_num);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_ioctl(esp_eth_handle_t hdl, esp_eth_io_cmd_t cmd, void *data)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -33,7 +33,11 @@ static esp_err_t eth_input_to_netif(esp_eth_handle_t eth_handle, uint8_t *buffer
|
||||
{
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
esp_err_t ret = ESP_OK;
|
||||
ret = esp_vfs_l2tap_eth_filter_frame(eth_handle, buffer, (size_t *)&length, info);
|
||||
l2tap_eth_filter_info_t l2tap_info = {
|
||||
.l2_buffer = NULL,
|
||||
.hw_ts = (l2tap_timestamp_t *)info, // Memory layout matches the Ethernet MAC driver type (eth_mac_time_t)
|
||||
};
|
||||
ret = esp_vfs_l2tap_eth_filter_frame(eth_handle, buffer, (size_t *)&length, &l2tap_info);
|
||||
if (length == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -556,30 +556,41 @@ 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_ctrl_vargs(esp_eth_mac_t *mac, void *ctrl, uint32_t argc, va_list args)
|
||||
static esp_err_t emac_esp32_transmit_ctrl_bufs(esp_eth_mac_t *mac, void *ctrl, const esp_eth_buf_desc_t *bufs, size_t buf_count)
|
||||
{
|
||||
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
|
||||
uint32_t buf_num = argc / 2;
|
||||
|
||||
emac_esp_dma_transmit_buff_t buff_array[buf_num];
|
||||
emac_esp_dma_transmit_buff_t buff_array[buf_count];
|
||||
|
||||
uint32_t exp_len = 0;
|
||||
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);
|
||||
for (size_t i = 0; i < buf_count; i++) {
|
||||
buff_array[i].buf = bufs[i].buf;
|
||||
buff_array[i].size = bufs[i].len;
|
||||
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);
|
||||
uint32_t sent_len = emac_esp_dma_transmit_frame_ext(emac->emac_dma_hndl, buff_array, (uint32_t)buf_count, ts);
|
||||
|
||||
if(sent_len != exp_len) {
|
||||
if (sent_len != exp_len) {
|
||||
ESP_LOGD(TAG, "insufficient TX buffer size");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t emac_esp32_transmit_ctrl_vargs(esp_eth_mac_t *mac, void *ctrl, uint32_t argc, va_list args)
|
||||
{
|
||||
uint32_t buf_num = argc / 2;
|
||||
esp_eth_buf_desc_t bufs[buf_num];
|
||||
|
||||
for (uint32_t i = 0; i < buf_num; i++) {
|
||||
bufs[i].buf = va_arg(args, uint8_t *);
|
||||
bufs[i].len = va_arg(args, uint32_t);
|
||||
}
|
||||
|
||||
return emac_esp32_transmit_ctrl_bufs(mac, ctrl, bufs, buf_num);
|
||||
}
|
||||
|
||||
static esp_err_t emac_esp32_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
@@ -1239,7 +1250,11 @@ 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_ctrl_bufs = emac_esp32_transmit_ctrl_bufs;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
emac->parent.transmit_ctrl_vargs = emac_esp32_transmit_ctrl_vargs;
|
||||
#pragma GCC diagnostic pop
|
||||
emac->parent.receive = emac_esp32_receive;
|
||||
emac->parent.custom_ioctl = emac_esp_custom_ioctl;
|
||||
#ifdef SOC_EMAC_IEEE1588V2_SUPPORTED
|
||||
|
||||
327
components/esp_eth/src/sublayer/README.md
Normal file
327
components/esp_eth/src/sublayer/README.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# ESP-ETH Netif Sublayer
|
||||
|
||||
> **Experimental feature** — enable `CONFIG_ETH_SUBLAYER_SUPPORT` in menuconfig
|
||||
> (requires `CONFIG_IDF_EXPERIMENTAL_FEATURES`). The sublayer API is under active
|
||||
> development and **may change** in future ESP-IDF releases without a deprecation
|
||||
> period.
|
||||
>
|
||||
> Integrated Ethernet switch support is a separate, disabled-by-default feature.
|
||||
> Enable `CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT` to build its API and frame-processing paths.
|
||||
|
||||
The **sublayer** (`esp_eth_sublayer_t`) is the single coupling point between one physical Ethernet driver (`esp_eth_handle_t`) and one or more `esp_netif` instances.
|
||||
It owns the RX/TX paths registered with the driver, distributes ETH events to every attached `esp_netif`, and exposes optional hooks for mid-path frame processing (e.g. MACsec).
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Driver["`**ETH Driver**
|
||||
esp_eth_handle_t`"]
|
||||
|
||||
subgraph SUB["Sublayer — esp_eth_sublayer_t"]
|
||||
ICswitch["`**Integrated Switch (opt)**
|
||||
MUX / DEMUX
|
||||
(e.g. tail tagging)`"]
|
||||
Hook["`**RX / TX Hook** (optional)
|
||||
e.g. MACsec`"]
|
||||
TapLL["`**ETH TAP LL**
|
||||
L2TAP at driver level
|
||||
sees raw 802.1Q frames`"]
|
||||
Demux["`**VLAN DEMUX / MUX**
|
||||
802.1Q inspect, route
|
||||
strip RX, insert TX`"]
|
||||
end
|
||||
|
||||
subgraph CU["VLAN Child — untagged (VID = 0xFFFF)"]
|
||||
TapU["`**L2TAP**
|
||||
per-VLAN filter`"]
|
||||
NetifU["esp_netif (untagged)"]
|
||||
end
|
||||
|
||||
subgraph CN["VLAN Child — tagged (VID = N)"]
|
||||
TapN["`**L2TAP**
|
||||
per-VLAN filter`"]
|
||||
NetifN["esp_netif (VLAN N)"]
|
||||
end
|
||||
|
||||
Driver <-->|"RX / TX"| ICswitch
|
||||
ICswitch <--> Hook
|
||||
Hook <--> TapLL
|
||||
TapLL <--> Demux
|
||||
Demux <-->|"untagged"| TapU <--> NetifU
|
||||
Demux <-->|"VID=N"| TapN <--> NetifN
|
||||
```
|
||||
|
||||
### Structural rules
|
||||
|
||||
* The **sublayer must not be attached to a netif directly** — only VLAN children are attached via `esp_netif_attach()`. The `post_attach` callback lives in the VLAN child, not in the sublayer.
|
||||
* The sublayer is the **sole owner** of the driver input path (`esp_eth_update_input_path_info`) and the ETH event handlers; children derive their events from it.
|
||||
* VLAN children are stored in a singly-linked list (`SLIST`) managed exclusively by the sublayer.
|
||||
|
||||
---
|
||||
|
||||
## RX Data Path
|
||||
|
||||
```
|
||||
ETH Driver
|
||||
│ raw frame (may carry 802.1Q VLAN tag) + optional hardware timestamp
|
||||
▼
|
||||
eth_sublayer_input() [esp_eth_sublayer.c]
|
||||
│
|
||||
│ alloc_base = buffer (original malloc pointer, used for all frees)
|
||||
│ frame = buffer (movable frame start, may advance as tags are stripped)
|
||||
│
|
||||
├─ ⓪ Integrated switch demux (optional; CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT)
|
||||
│ eth_switch_demux(sw, &frame, &length, &src_port)
|
||||
│ Strips switch-specific tagging before any further processing.
|
||||
│ • Trailing tag (e.g. KSZ8863 Tail Tag): decrement length.
|
||||
│ • Leading tag (e.g. DSA, 802.1Q port-VLAN): advance frame forward
|
||||
│ (typically memmove MAC header over the tag) and decrement length.
|
||||
│ eth_switch_resolve_ingress_port() then maps src_port to the per-port
|
||||
│ Ethernet handle (passed to the RX hook) and L2TAP iodriver handle.
|
||||
│
|
||||
├─ ① RX Hook (optional)
|
||||
│ rx_info = { .l2_buffer = alloc_base, .driver_info = info }
|
||||
│ rx_hook(eth, &frame, &length, &rx_info, hook_ctx)
|
||||
│ eth is the host driver, or the ingress port handle when a switch is attached.
|
||||
│ Set via esp_eth_sublayer_config_t at creation (immutable thereafter).
|
||||
│ May advance frame or shrink length to strip a leading/trailing header
|
||||
│ (e.g. MACsec SecTAG). Must NOT realloc the buffer.
|
||||
│ Set length=0 to take ownership and stop sublayer RX processing;
|
||||
│ the hook must release/forward rx_info.l2_buffer itself.
|
||||
│ Example use-case: MACsec decryption, filtering.
|
||||
│
|
||||
├─ ② ETH TAP LL ← closest to driver
|
||||
│ esp_vfs_l2tap_eth_filter_frame(io_handle, frame, &length, &l2tap_info)
|
||||
│ l2tap_info.l2_buffer = alloc_base (original allocation for L2TAP free path).
|
||||
│ io_handle is the sublayer (sub) when no switch is attached, or the
|
||||
│ resolved ingress-port iodriver when a switch is attached.
|
||||
│ Frames captured here still carry the original 802.1Q tag.
|
||||
│ length=0 after the call means the frame was consumed by L2TAP.
|
||||
│
|
||||
├─ ③ VLAN DEMUX
|
||||
│ eth_vlan_get_ether_type(frame) reads EtherType at bytes [12:13].
|
||||
│ • 0x8100 (802.1Q): extracts VID from TCI bytes [14:15].
|
||||
│ • anything else: VID = ESP_ETH_SUBLAYER_UNTAGGED_VID (0xFFFF).
|
||||
│ eth_sublayer_find_vlan_by_vid() looks up the matching VLAN child.
|
||||
│ Frame is dropped (freed via alloc_base) when no child is registered.
|
||||
│
|
||||
└─ ④ Per-VLAN input eth_vlan_input() [esp_eth_sublayer_vlan_child.c]
|
||||
│
|
||||
├─ L2TAP (per-VLAN)
|
||||
│ esp_vfs_l2tap_eth_filter_frame(vlan_netif_driver, ...)
|
||||
│ The vlan_netif_driver pointer is the iodriver handle here.
|
||||
│ For tagged frames: the VLAN tag is stripped first (memmove of
|
||||
│ the Ethernet header over the 4-byte TCI/TPID fields) so the
|
||||
│ netif and L2TAP at this level see an untagged frame.
|
||||
│ l2tap_info.l2_buffer = alloc_base so L2TAP can free correctly.
|
||||
│
|
||||
└─ esp_netif_receive(netif, data, len, alloc_base)
|
||||
Delivers the untagged payload to the network stack (lwIP).
|
||||
alloc_base is passed as the L2 buffer base for zero-copy pbuf free.
|
||||
```
|
||||
|
||||
### RX frame pointer contract
|
||||
|
||||
Integrated switch demux and RX hook callbacks receive the frame start as an in/out
|
||||
`uint8_t **` pointer. They may advance `*buffer` forward (leading tag strip) and/or
|
||||
decrement `*length` (trailing tag strip), but must stay within the original allocation
|
||||
and must not realloc. RX hook also receives `esp_eth_sublayer_rx_info_t`, which includes
|
||||
`l2_buffer`. The sublayer uses `alloc_base` for every
|
||||
`eth_sublayer_buf_free()` call and as the `eb` / `l2_buffer` argument to
|
||||
`esp_netif_receive()` / L2TAP unless the hook takes ownership by setting length to 0.
|
||||
|
||||
---
|
||||
|
||||
## TX Data Path
|
||||
|
||||
```
|
||||
esp_netif
|
||||
│ standard Ethernet frame (no VLAN tag)
|
||||
▼
|
||||
eth_vlan_transmit_wrap() [esp_eth_sublayer_vlan_child.c]
|
||||
│
|
||||
├─ Tagged VLAN child (VID ≠ 0xFFFF):
|
||||
│ Allocates a new Ethernet header buffer (ETH_HEADER_LEN + 4 bytes).
|
||||
│ Copies DA/SA, inserts TPID=0x8100, TCI (pre-computed tci_be), and
|
||||
│ original EtherType. Frame is split into two scatter-gather descriptors:
|
||||
│ bufs[0] = new tagged header (14 + 4 = 18 bytes)
|
||||
│ bufs[1] = original payload (frame − 14 bytes)
|
||||
│ No memcpy of payload.
|
||||
│
|
||||
└─ Untagged child (VID = 0xFFFF):
|
||||
Single descriptor, frame passed through as-is.
|
||||
|
||||
▼
|
||||
eth_sublayer_transmit() [esp_eth_sublayer.c]
|
||||
│
|
||||
├─ TX Hook (optional)
|
||||
│ tx_hook(eth_driver, tx_bufs, hook_ctx)
|
||||
│ May modify buf pointers / lengths in place, merge bufs, or set
|
||||
│ *tx_bufs->buf_count = 0 to skip driver transmit (descriptor set
|
||||
│ stays caller-owned).
|
||||
│ Example use-case: MACsec encryption.
|
||||
│
|
||||
├─ Integrated switch mux (optional; CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT)
|
||||
│ eth_switch_mux(sw, tx_bufs, port)
|
||||
│ Appends switch-specific tagging (e.g. Tail Tag) after the TX hook.
|
||||
│
|
||||
└─ esp_eth_transmit_ctrl_bufs()
|
||||
Passes the descriptor array to the MAC driver.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## L2TAP Integration
|
||||
|
||||
L2TAP provides POSIX-like file-descriptor access to raw Ethernet frames
|
||||
(`/dev/net/tap`). The sublayer integrates these L2TAP access points:
|
||||
|
||||
| Access point | Bind / iodriver handle | Frame content | Typical use |
|
||||
|---|---|---|---|
|
||||
| **ETH TAP LL** (no switch) | host `esp_eth_handle_t` / `esp_eth_sublayer_t *sub` | Raw, still 802.1Q-tagged | Capture/inject frames before VLAN demux; TSN / PTP tooling |
|
||||
| **Switch port** | per-port `esp_eth_handle_t` | Switch tag already stripped; 802.1Q still present | Per-port capture/inject |
|
||||
| **Per-VLAN L2TAP** | `esp_eth_sublayer_vlan_t *vlan_netif_driver` | VLAN tag already stripped | Per-VLAN capture; tagged-VLAN sockets |
|
||||
|
||||
The sublayer registers itself with L2TAP on creation (`esp_vfs_l2tap_iodriver_provider_register`) and unregisters on deletion. The `get_io_fns` callback (`eth_sublayer_get_io_fns`) is called by L2TAP when an fd binds to a specific iodriver handle:
|
||||
|
||||
* if `io_handle` matches a registered VLAN child → returns VLAN child TX functions (includes VLAN tag insertion on transmit).
|
||||
* if a switch is attached and `io_handle` matches a port Ethernet handle → returns that port's TX functions (switch mux tags the frame for the port). Direct bind to the host driver (`sub->eth_driver`) is not served in switch mode.
|
||||
* if no switch is attached and `io_handle` matches the host driver (`sub->eth_driver`) → returns the ETH TAP LL TX functions that bypass VLAN tag insertion.
|
||||
|
||||
The IO driver function table (`esp_eth_iodriver_io_fns_t`) and the provider interface
|
||||
(`esp_eth_iodriver_provider_base_t`) are a **generic esp_eth concept** defined in
|
||||
`esp_private/esp_eth_sublayer_iodriver.h` (an internal, inter-component header — not stable application API),
|
||||
independent of L2TAP. L2TAP is just the first consumer; any upper layer can
|
||||
obtain the transmit/free/get-ll-driver functions for a base, VLAN child, or switch port through the
|
||||
same `get_io_fns` resolver. The provider implementation is built when
|
||||
`CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER` is enabled (selected automatically by `CONFIG_ESP_NETIF_L2_TAP`),
|
||||
while the L2TAP-specific registration is additionally guarded by `CONFIG_ESP_NETIF_L2_TAP`.
|
||||
|
||||
---
|
||||
|
||||
## VLAN Support (802.1Q)
|
||||
|
||||
```
|
||||
Registered child VIDs Frame arriving at sublayer input
|
||||
──────────────────────── ──────────────────────────────────────────────
|
||||
ESP_ETH_SUBLAYER_UNTAGGED_VID ← EtherType ≠ 0x8100 (untagged)
|
||||
VID = 10 ← EtherType == 0x8100, TCI & 0x0FFF == 10
|
||||
VID = 20 ← EtherType == 0x8100, TCI & 0x0FFF == 20
|
||||
(no match) ← frame is freed / dropped
|
||||
```
|
||||
|
||||
* **RX**: the 4-byte VLAN tag (TPID + TCI) is removed from tagged frames using `memmove` before delivery to `esp_netif_receive`. The `l2_buffer` pointer in `l2tap_eth_filter_info_t` is set to the original allocation start so that the L2TAP layer can free it.
|
||||
* **TX**: a new 18-byte Ethernet header is allocated and the payload is referenced via a second scatter-gather descriptor. The original frame buffer is never copied.
|
||||
* **ETH events**: `ETHERNET_EVENT_START/STOP/CONNECTED/DISCONNECTED` are applied to every VLAN child whose `base.netif` is non-NULL. `IP_EVENT_ETH_GOT_IP` is matched against `ip_event->esp_netif` and forwarded to exactly one child.
|
||||
|
||||
Optional `esp_eth_sublayer_config_t` events (leave event `base` NULL to disable):
|
||||
|
||||
* `connect_confirm_event` — child netifs go up only after both `ETHERNET_EVENT_CONNECTED` and this event. Order does not matter. A later disconnect (`ETHERNET_EVENT_DISCONNECTED`, `ETHERNET_EVENT_STOP`, or `disconnect_trigger_event`) invalidates the confirmation.
|
||||
* `disconnect_trigger_event` — additional event that forces all child netifs down, besides `ETHERNET_EVENT_DISCONNECTED`.
|
||||
|
||||
`CONFIG_ETH_SUBLAYER_VLAN_SUPPORT` (disabled by default) gates 802.1Q tagging support. When disabled:
|
||||
|
||||
* `esp_eth_sublayer_vlan_add()` returns `ESP_ERR_NOT_SUPPORTED` for any `vlan_id` other than `ESP_ETH_SUBLAYER_UNTAGGED_VID` — only a single, untagged netif per sublayer is possible. Switch ports are L2TAP / iodriver endpoints, not extra untagged netifs.
|
||||
* The TX tag-insertion and RX tag-removal code paths are compiled out.
|
||||
|
||||
---
|
||||
|
||||
## Optional TX / RX Hooks
|
||||
|
||||
```c
|
||||
esp_eth_sublayer_config_t sub_cfg = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
sub_cfg.eth_handle = eth_handle;
|
||||
sub_cfg.tx_hook = tx_hook;
|
||||
sub_cfg.post_tx_hook = post_tx_hook;
|
||||
sub_cfg.rx_hook = rx_hook;
|
||||
sub_cfg.hook_ctx = ctx;
|
||||
esp_eth_sublayer_handle_t sub = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_new(&sub_cfg, &sub));
|
||||
```
|
||||
|
||||
Hooks are called on every frame passing through the sublayer before VLAN demux (RX) or after VLAN tag insertion (TX). Pass `NULL` for a hook to leave that callback unhooked. Hooks are fixed at sublayer creation and cannot be changed at runtime.
|
||||
|
||||
`post_tx_hook` is an optional companion to `tx_hook`: it is invoked after the TX hook returns `ESP_OK` with `*tx_bufs->buf_count > 0`, so the hook can free any buffers it allocated. It is not called when the TX hook sets `*tx_bufs->buf_count` to 0 or returns an error.
|
||||
|
||||
**RX hook signature**:
|
||||
```c
|
||||
esp_err_t rx_hook(esp_eth_handle_t eth, uint8_t **buf, uint32_t *len,
|
||||
esp_eth_sublayer_rx_info_t *info, void *ctx);
|
||||
// Set *len = 0 to stop sublayer processing for the frame.
|
||||
// This takes ownership by the hook function; release/forward info->l2_buffer.
|
||||
// Return non-ESP_OK to abort processing (buffer is freed).
|
||||
```
|
||||
|
||||
**TX hook signature**:
|
||||
```c
|
||||
esp_err_t tx_hook(esp_eth_handle_t eth, esp_eth_sublayer_tx_bufs_t *tx_bufs, void *ctx);
|
||||
// May reorder descriptors (e.g. move original frame from bufs[0] to bufs[1]
|
||||
// and place prefix/header data into bufs[0]).
|
||||
// May increase/decrease *tx_bufs->buf_count as needed, but never exceed tx_bufs->buf_capacity.
|
||||
// Set *tx_bufs->buf_count = 0 to skip driver transmit (and post_tx_hook).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Minimal Usage
|
||||
|
||||
```c
|
||||
// 1. Create sublayer for an existing Ethernet driver handle
|
||||
esp_eth_sublayer_config_t sub_cfg = { .eth_handle = eth_handle };
|
||||
esp_eth_sublayer_handle_t sub = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_new(&sub_cfg, &sub));
|
||||
|
||||
// 2. Add VLAN children (VID or UNTAGGED sentinel)
|
||||
esp_eth_sublayer_vlan_handle_t untagged = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_vlan_add(sub, ESP_ETH_SUBLAYER_UNTAGGED_VID, &untagged));
|
||||
esp_eth_sublayer_vlan_handle_t vlan10 = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_vlan_add(sub, 10, &vlan10));
|
||||
|
||||
// 3. Create esp_netif instances and attach VLAN child handles
|
||||
esp_netif_t *netif_plain = esp_netif_new(&ESP_NETIF_DEFAULT_ETH());
|
||||
esp_netif_attach(netif_plain, untagged);
|
||||
|
||||
esp_netif_t *netif_vlan10 = esp_netif_new(&vlan_cfg);
|
||||
esp_netif_attach(netif_vlan10, vlan10);
|
||||
|
||||
// 4. Start driver — sublayer handles ETH events from here
|
||||
esp_eth_start(eth_handle);
|
||||
```
|
||||
|
||||
See `examples/ethernet/sublayer` for a complete working example including static IP configuration.
|
||||
|
||||
---
|
||||
|
||||
## Integrated Switch
|
||||
|
||||
Requires `CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT`. A sublayer holds at most one switch. The mux/demux/init/deinit callbacks are implemented by the switch driver (e.g. KSZ8863 Tail Tag); the sublayer only invokes them.
|
||||
|
||||
```c
|
||||
esp_eth_sublayer_switch_config_t sw_cfg = {
|
||||
.tag_process_init = driver_tag_init, // once, when the switch is added; returns ctx
|
||||
.tag_process_deinit = driver_tag_deinit, // when the switch is deleted
|
||||
.demux = driver_demux, // RX: strip switch tag, report ingress port
|
||||
.mux = driver_mux, // TX: append switch tag (port < 0 = default lookup)
|
||||
.host_eth_handle = host_eth,
|
||||
.port_eth_handles = port_eths,
|
||||
.ports_count = port_count,
|
||||
};
|
||||
esp_eth_sublayer_switch_handle_t sw = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_switch_add(sub, &sw_cfg, &sw));
|
||||
// ...
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_switch_del(sw));
|
||||
```
|
||||
|
||||
`esp_eth_sublayer_del()` also deletes an attached switch if one is still present.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
* The sublayer's `vlan_children` list is not protected by a mutex; configure it only from a single task context (during initialization), as documented in the header.
|
||||
* The sublayer increases the driver's reference count on creation and decreases it on deletion to prevent the driver from being destroyed while still in use.
|
||||
* `ESP_ETH_SUBLAYER_UNTAGGED_VID` is `UINT16_MAX` (0xFFFF); it is never a valid 802.1Q VID (VIDs are 12-bit, 0..4094, with 4095 reserved).
|
||||
|
||||
651
components/esp_eth/src/sublayer/esp_eth_sublayer.c
Normal file
651
components/esp_eth/src/sublayer/esp_eth_sublayer.c
Normal file
@@ -0,0 +1,651 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/queue.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_net_stack.h"
|
||||
#include "esp_eth_sublayer_core.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_eth_spec.h"
|
||||
#include "hal/eth_types.h"
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
#include "esp_private/esp_eth_sublayer_iodriver.h"
|
||||
#endif
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
#include "esp_vfs_l2tap.h"
|
||||
#endif
|
||||
#if CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
/**
|
||||
* @brief Transmit timeout when multiple tasks may transmit through the same sublayer concurrently
|
||||
*/
|
||||
#define ESP_ETH_SUBLAYER_TX_TIMEOUT_MS 250
|
||||
#endif // CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
|
||||
typedef struct esp_eth_sublayer_s esp_eth_sublayer_t;
|
||||
|
||||
static const char *TAG = "esp_eth.sublayer";
|
||||
|
||||
struct esp_eth_sublayer_s {
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
esp_eth_iodriver_provider_base_t base; /*!< IO driver provider base; must stay first for provider handle downcast */
|
||||
#endif
|
||||
SLIST_HEAD(, esp_eth_sublayer_vlan_s) vlan_child;
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
esp_eth_sublayer_switch_t *switch_child;
|
||||
#endif
|
||||
|
||||
esp_eth_handle_t eth_driver;
|
||||
eth_speed_t link_speed;
|
||||
bool connect_confirmed;
|
||||
esp_event_handler_instance_t start_ctx_handler;
|
||||
esp_event_handler_instance_t stop_ctx_handler;
|
||||
esp_event_handler_instance_t connect_ctx_handler;
|
||||
esp_event_handler_instance_t connect_confirm_ctx_handler;
|
||||
esp_event_handler_instance_t disconnect_ctx_handler;
|
||||
esp_event_handler_instance_t disconnect_trigger_ctx_handler;
|
||||
esp_event_handler_instance_t get_ip_ctx_handler;
|
||||
|
||||
esp_eth_sublayer_event_t connect_confirm;
|
||||
esp_eth_sublayer_event_t disconnect_trigger;
|
||||
|
||||
esp_eth_sublayer_tx_hook_t tx_hook;
|
||||
esp_eth_sublayer_post_tx_hook_t post_tx_hook;
|
||||
esp_eth_sublayer_rx_hook_t rx_hook;
|
||||
void *hook_ctx;
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
SemaphoreHandle_t transmit_mutex;
|
||||
TickType_t transmit_mutex_timeout_ticks;
|
||||
#endif // CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
};
|
||||
|
||||
void eth_sublayer_buf_free(esp_eth_sublayer_handle_t sub, void *buffer)
|
||||
{
|
||||
(void)sub;
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
esp_err_t eth_sublayer_set_mac_filter(esp_eth_sublayer_handle_t sub, const uint8_t *eth_mac, size_t mac_len, bool add)
|
||||
{
|
||||
esp_eth_sublayer_t *sub_impl = (esp_eth_sublayer_t *)sub;
|
||||
|
||||
ESP_RETURN_ON_FALSE(mac_len == ETH_ADDR_LEN, ESP_ERR_INVALID_ARG, TAG, "invalid MAC length");
|
||||
ESP_LOGD(TAG, "%s filter MAC: %02x:%02x:%02x:%02x:%02x:%02x", add ? "Add" : "Del", eth_mac[0], eth_mac[1],
|
||||
eth_mac[2], eth_mac[3], eth_mac[4], eth_mac[5]);
|
||||
if (add) {
|
||||
ESP_RETURN_ON_ERROR(esp_eth_ioctl(sub_impl->eth_driver, ETH_CMD_ADD_MAC_FILTER, (void *)eth_mac), TAG,
|
||||
"failed to add mac filter");
|
||||
} else {
|
||||
ESP_RETURN_ON_ERROR(esp_eth_ioctl(sub_impl->eth_driver, ETH_CMD_DEL_MAC_FILTER, (void *)eth_mac), TAG,
|
||||
"failed to delete mac filter");
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t eth_sublayer_transmit(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_tx_bufs_t *tx_bufs, void *eb, int32_t port)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
#if !CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
(void)port;
|
||||
#endif
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
// Serializes the complete TX path below (TX hook, integrated switch mux, driver transmit and post-TX
|
||||
// hook) since different tasks may call this concurrently (e.g. multiple L2TAP sockets on different
|
||||
// switch/VLAN ports), and TX hooks / integrated switch drivers may keep mutable state that is only
|
||||
// safe to touch from one task at a time.
|
||||
if (xSemaphoreTake(sub->transmit_mutex, sub->transmit_mutex_timeout_ticks) == pdFALSE) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
#endif // CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
|
||||
if (sub->tx_hook) {
|
||||
ret = sub->tx_hook(sub->eth_driver, tx_bufs, sub->hook_ctx);
|
||||
if (ret != ESP_OK) {
|
||||
goto out_no_post_tx;
|
||||
}
|
||||
}
|
||||
// No buffers to transmit, skip the rest of the function
|
||||
if (*tx_bufs->buf_count == 0) {
|
||||
goto out_no_post_tx;
|
||||
}
|
||||
assert(*tx_bufs->buf_count <= tx_bufs->buf_capacity && "buffer count exceeds capacity");
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
if (sub->switch_child) {
|
||||
ret = eth_switch_mux(sub->switch_child, tx_bufs, port);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_NOT_SUPPORTED) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = esp_eth_transmit_ctrl_bufs(sub->eth_driver, eb, tx_bufs->bufs, *tx_bufs->buf_count);
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
out:
|
||||
#endif
|
||||
if (sub->post_tx_hook) {
|
||||
sub->post_tx_hook(sub->eth_driver, tx_bufs, sub->hook_ctx);
|
||||
}
|
||||
out_no_post_tx:
|
||||
#if CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
xSemaphoreGive(sub->transmit_mutex);
|
||||
#endif // CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t eth_sublayer_input(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t length, void *priv, void *info)
|
||||
{
|
||||
esp_eth_sublayer_t *sub = (esp_eth_sublayer_t *)priv;
|
||||
esp_err_t ret = ESP_OK;
|
||||
(void)eth_handle;
|
||||
|
||||
uint8_t *alloc_base = buffer;
|
||||
uint8_t *frame = buffer;
|
||||
|
||||
esp_eth_handle_t src_eth_handle = sub->eth_driver;
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
void *l2tap_io_handle = (void *)sub;
|
||||
#endif
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
// Integrated switch demux: strip the switch tag and resolve the ingress port before
|
||||
// any further processing, so RX hook / L2 TAP / VLAN demux all operate on a clean frame.
|
||||
if (sub->switch_child) {
|
||||
int32_t src_port = -1;
|
||||
ret = eth_switch_demux(sub->switch_child, &frame, &length, &src_port);
|
||||
if (ret == ESP_OK) {
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
ret = eth_switch_resolve_ingress_port(sub->switch_child, src_port, &src_eth_handle, &l2tap_io_handle);
|
||||
#else
|
||||
ret = eth_switch_resolve_ingress_port(sub->switch_child, src_port, &src_eth_handle, NULL);
|
||||
#endif
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "eth_switch_resolve_ingress_port failed: %d", ret);
|
||||
eth_sublayer_buf_free(sub, alloc_base);
|
||||
return ret;
|
||||
}
|
||||
} else if (ret != ESP_ERR_NOT_SUPPORTED) {
|
||||
ESP_LOGE(TAG, "eth_switch_demux failed: %d", ret);
|
||||
eth_sublayer_buf_free(sub, alloc_base);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_eth_sublayer_rx_info_t rx_info = {
|
||||
.l2_buffer = alloc_base,
|
||||
.driver_info = info,
|
||||
};
|
||||
if (sub->rx_hook) {
|
||||
ret = sub->rx_hook(src_eth_handle, &frame, &length, &rx_info, sub->hook_ctx);
|
||||
if (ret == ESP_OK) {
|
||||
if (length == 0) {
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
eth_sublayer_buf_free(sub, alloc_base);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// low level L2TAP
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
l2tap_eth_filter_info_t l2tap_info = {
|
||||
.l2_buffer = alloc_base,
|
||||
.hw_ts = (l2tap_timestamp_t *)info, // Memory layout matches the Ethernet MAC driver type (eth_mac_time_t)
|
||||
};
|
||||
ret = esp_vfs_l2tap_eth_filter_frame(l2tap_io_handle, frame, (size_t *)&length, &l2tap_info);
|
||||
if (ret == ESP_OK) {
|
||||
if (length == 0) {
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
eth_sublayer_buf_free(sub, alloc_base);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t vlan_id = eth_vlan_get_ether_type(frame);
|
||||
esp_eth_sublayer_vlan_t *vlan_netif_driver = eth_sublayer_find_vlan_by_vid(sub, vlan_id);
|
||||
if (vlan_netif_driver == NULL) {
|
||||
ESP_LOGD(TAG, "vlan child not found for VLAN ID %" PRIu16, vlan_id);
|
||||
eth_sublayer_buf_free(sub, alloc_base);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
return eth_vlan_input(vlan_netif_driver, frame, length, alloc_base, info);
|
||||
}
|
||||
|
||||
esp_eth_sublayer_vlan_t *eth_sublayer_find_vlan_by_vid(esp_eth_sublayer_handle_t sub, uint16_t vlan_id)
|
||||
{
|
||||
esp_eth_sublayer_vlan_t *netif_iodriver;
|
||||
esp_eth_sublayer_t *sub_impl = (esp_eth_sublayer_t *)sub;
|
||||
SLIST_FOREACH(netif_iodriver, &sub_impl->vlan_child, next) {
|
||||
if (netif_iodriver->vlan_id == vlan_id) {
|
||||
return netif_iodriver;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t eth_sublayer_insert_vlan(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_vlan_t *vlan)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sub && vlan, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
|
||||
esp_eth_sublayer_t *sub_impl = (esp_eth_sublayer_t *)sub;
|
||||
SLIST_INSERT_HEAD(&sub_impl->vlan_child, vlan, next);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t eth_sublayer_remove_vlan(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_vlan_t *vlan)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sub && vlan, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
|
||||
esp_eth_sublayer_t *sub_impl = (esp_eth_sublayer_t *)sub;
|
||||
esp_eth_sublayer_vlan_t *v;
|
||||
SLIST_FOREACH(v, &sub_impl->vlan_child, next) {
|
||||
if (v == vlan) {
|
||||
SLIST_REMOVE(&sub_impl->vlan_child, vlan, esp_eth_sublayer_vlan_s, next);
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
esp_err_t eth_sublayer_set_switch(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_switch_t *sw)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sub && sw, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
ESP_RETURN_ON_FALSE(sub->switch_child == NULL, ESP_ERR_INVALID_STATE, TAG, "switch already attached");
|
||||
sub->switch_child = sw;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_eth_sublayer_switch_t *eth_sublayer_get_switch(esp_eth_sublayer_handle_t sub)
|
||||
{
|
||||
if (sub == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return sub->switch_child;
|
||||
}
|
||||
|
||||
esp_err_t eth_sublayer_remove_switch(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_switch_t *sw)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sub && sw, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
ESP_RETURN_ON_FALSE(sub->switch_child == sw, ESP_ERR_NOT_FOUND, TAG, "switch is not attached to this sublayer");
|
||||
sub->switch_child = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif // CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
|
||||
/** One handler per ETH_EVENT: dispatch to base netif and every VLAN netif bound to this sublayer. */
|
||||
static void eth_action_start(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
|
||||
esp_eth_sublayer_t *sub = handler_args;
|
||||
if (sub->eth_driver != eth_handle) {
|
||||
return;
|
||||
}
|
||||
esp_eth_sublayer_vlan_t *netif_iodriver;
|
||||
SLIST_FOREACH(netif_iodriver, &sub->vlan_child, next) {
|
||||
if (netif_iodriver->base.netif) {
|
||||
esp_netif_action_start(netif_iodriver->base.netif, base, event_id, event_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void eth_action_stop(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
|
||||
esp_eth_sublayer_t *sub = handler_args;
|
||||
if (sub->eth_driver != eth_handle) {
|
||||
return;
|
||||
}
|
||||
sub->link_speed = ETH_SPEED_MAX; // set link speed to invalid value to indicate that the physical link is not up
|
||||
sub->connect_confirmed = false; // a new confirmation is required once the interface is started again
|
||||
esp_eth_sublayer_vlan_t *netif_iodriver;
|
||||
SLIST_FOREACH(netif_iodriver, &sub->vlan_child, next) {
|
||||
if (netif_iodriver->base.netif) {
|
||||
esp_netif_action_stop(netif_iodriver->base.netif, base, event_id, event_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void eth_action_connected(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_eth_sublayer_t *sub = handler_args;
|
||||
|
||||
if (base == ETH_EVENT) {
|
||||
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
|
||||
if (sub->eth_driver != eth_handle) {
|
||||
return;
|
||||
}
|
||||
if (event_id == ETHERNET_EVENT_CONNECTED) {
|
||||
eth_speed_t speed;
|
||||
esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &speed);
|
||||
sub->link_speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
bool set_connected = false;
|
||||
// if no confirm connect handler is registered, immediately set the link, otherwise wait for confirmation event
|
||||
if (!sub->connect_confirm_ctx_handler) {
|
||||
set_connected = true;
|
||||
} else if (base == sub->connect_confirm.base && event_id == sub->connect_confirm.event_id) {
|
||||
// the confirmation is remembered so the order in which the confirmation and the physical link up
|
||||
// are reported does not matter (the confirmation source does not need to be driven by ETH_EVENT)
|
||||
sub->connect_confirmed = true;
|
||||
if (sub->link_speed < ETH_SPEED_MAX) {
|
||||
set_connected = true;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "physical link is not up, skipping confirm connected event");
|
||||
}
|
||||
} else if (sub->connect_confirmed && sub->link_speed < ETH_SPEED_MAX) {
|
||||
// the confirmation was already received before the physical link came up
|
||||
set_connected = true;
|
||||
}
|
||||
|
||||
if (set_connected) {
|
||||
uint32_t link_speed = sub->link_speed == ETH_SPEED_1000M ? 1000'000'000 : sub->link_speed == ETH_SPEED_100M ? 100'000'000 : 10'000'000;
|
||||
|
||||
esp_eth_sublayer_vlan_t *netif_iodriver;
|
||||
SLIST_FOREACH(netif_iodriver, &sub->vlan_child, next) {
|
||||
if (netif_iodriver->base.netif) {
|
||||
esp_netif_set_link_speed(netif_iodriver->base.netif, link_speed);
|
||||
esp_netif_action_connected(netif_iodriver->base.netif, base, event_id, event_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void eth_action_disconnected(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_eth_sublayer_t *sub = handler_args;
|
||||
if (base == ETH_EVENT) {
|
||||
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
|
||||
if (sub->eth_driver != eth_handle) {
|
||||
return;
|
||||
}
|
||||
sub->link_speed = ETH_SPEED_MAX; // set link speed to invalid value to indicate that the physical link is not up
|
||||
}
|
||||
sub->connect_confirmed = false; // a new confirmation is required to bring the child netifs up again
|
||||
|
||||
// note: disconnect event always results in all child netifs being disconnected (no matter of event id)
|
||||
esp_eth_sublayer_vlan_t *netif_iodriver;
|
||||
SLIST_FOREACH(netif_iodriver, &sub->vlan_child, next) {
|
||||
if (netif_iodriver->base.netif) {
|
||||
esp_netif_action_disconnected(netif_iodriver->base.netif, base, event_id, event_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void eth_action_got_ip(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
ip_event_got_ip_t *ip_event = (ip_event_got_ip_t *)event_data;
|
||||
esp_eth_sublayer_t *sub = handler_args;
|
||||
|
||||
esp_eth_sublayer_vlan_t *netif_iodriver;
|
||||
SLIST_FOREACH(netif_iodriver, &sub->vlan_child, next) {
|
||||
if (netif_iodriver->base.netif == ip_event->esp_netif) {
|
||||
esp_netif_action_got_ip(ip_event->esp_netif, base, event_id, event_data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t sub_clear_handlers(esp_eth_sublayer_handle_t sub)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sub, ESP_ERR_INVALID_ARG, TAG, "sublayer handle can't be null");
|
||||
|
||||
if (sub->start_ctx_handler) {
|
||||
esp_event_handler_instance_unregister(ETH_EVENT, ETHERNET_EVENT_START, sub->start_ctx_handler);
|
||||
sub->start_ctx_handler = NULL;
|
||||
}
|
||||
if (sub->stop_ctx_handler) {
|
||||
esp_event_handler_instance_unregister(ETH_EVENT, ETHERNET_EVENT_STOP, sub->stop_ctx_handler);
|
||||
sub->stop_ctx_handler = NULL;
|
||||
}
|
||||
if (sub->connect_ctx_handler) {
|
||||
esp_event_handler_instance_unregister(ETH_EVENT, ETHERNET_EVENT_CONNECTED, sub->connect_ctx_handler);
|
||||
sub->connect_ctx_handler = NULL;
|
||||
}
|
||||
if (sub->connect_confirm_ctx_handler) {
|
||||
esp_event_handler_instance_unregister(sub->connect_confirm.base, sub->connect_confirm.event_id,
|
||||
sub->connect_confirm_ctx_handler);
|
||||
sub->connect_confirm_ctx_handler = NULL;
|
||||
}
|
||||
if (sub->disconnect_ctx_handler) {
|
||||
esp_event_handler_instance_unregister(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, sub->disconnect_ctx_handler);
|
||||
sub->disconnect_ctx_handler = NULL;
|
||||
}
|
||||
if (sub->disconnect_trigger_ctx_handler) {
|
||||
esp_event_handler_instance_unregister(sub->disconnect_trigger.base, sub->disconnect_trigger.event_id,
|
||||
sub->disconnect_trigger_ctx_handler);
|
||||
sub->disconnect_trigger_ctx_handler = NULL;
|
||||
}
|
||||
if (sub->get_ip_ctx_handler) {
|
||||
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, sub->get_ip_ctx_handler);
|
||||
sub->get_ip_ctx_handler = NULL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t sub_set_handlers(esp_eth_sublayer_handle_t sub)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sub, ESP_ERR_INVALID_ARG, TAG, "sublayer handle can't be null");
|
||||
|
||||
esp_err_t ret = esp_event_handler_instance_register(ETH_EVENT, ETHERNET_EVENT_START, eth_action_start, sub,
|
||||
&sub->start_ctx_handler);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
ret = esp_event_handler_instance_register(ETH_EVENT, ETHERNET_EVENT_STOP, eth_action_stop, sub, &sub->stop_ctx_handler);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
ret = esp_event_handler_instance_register(ETH_EVENT, ETHERNET_EVENT_CONNECTED, eth_action_connected, sub,
|
||||
&sub->connect_ctx_handler);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
ret = esp_event_handler_instance_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, eth_action_disconnected, sub,
|
||||
&sub->disconnect_ctx_handler);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
ret = esp_event_handler_instance_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, eth_action_got_ip, sub, &sub->get_ip_ctx_handler);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (sub->connect_confirm.base) {
|
||||
ret = esp_event_handler_instance_register(sub->connect_confirm.base, sub->connect_confirm.event_id,
|
||||
eth_action_connected, sub,
|
||||
&sub->connect_confirm_ctx_handler);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if (sub->disconnect_trigger.base) {
|
||||
ret = esp_event_handler_instance_register(sub->disconnect_trigger.base, sub->disconnect_trigger.event_id,
|
||||
eth_action_disconnected, sub,
|
||||
&sub->disconnect_trigger_ctx_handler);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
fail:
|
||||
sub_clear_handlers(sub);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
static esp_err_t eth_sublayer_ll_transmit_wrap(void *h, void *buf, size_t len, void *eb)
|
||||
{
|
||||
esp_eth_sublayer_t *sub = (esp_eth_sublayer_t *)h;
|
||||
esp_eth_buf_desc_t bufs[ESP_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY];
|
||||
bufs[0].buf = buf;
|
||||
bufs[0].len = len;
|
||||
size_t buf_count = 1;
|
||||
esp_eth_sublayer_tx_bufs_t tx_bufs = {
|
||||
.bufs = bufs,
|
||||
.buf_count = &buf_count,
|
||||
.buf_capacity = ESP_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY,
|
||||
};
|
||||
// port is not defined for direct lower layer transmit (integrated switch internal lookup is used when needed)
|
||||
return eth_sublayer_transmit(sub, &tx_bufs, eb, -1);
|
||||
}
|
||||
|
||||
static esp_err_t eth_sublayer_ll_transmit(void *h, void *buf, size_t len)
|
||||
{
|
||||
return eth_sublayer_ll_transmit_wrap(h, buf, len, NULL);
|
||||
}
|
||||
|
||||
static esp_err_t eth_sublayer_get_ll_driver(void *h, void **ll_driver)
|
||||
{
|
||||
esp_eth_sublayer_t *sub = (esp_eth_sublayer_t *)h;
|
||||
*ll_driver = sub->eth_driver;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void eth_sublayer_ll_free(void *h, void *buffer)
|
||||
{
|
||||
esp_eth_sublayer_t *sub = (esp_eth_sublayer_t *)h;
|
||||
eth_sublayer_buf_free(sub, buffer);
|
||||
}
|
||||
|
||||
// Note: The consumer may call this from a critical section, hence no logging, allocation nor blocking is
|
||||
// allowed here. See esp_eth_iodriver_provider_base_t::get_io_fns.
|
||||
static esp_err_t eth_sublayer_get_io_fns(esp_eth_iodriver_provider_handle sublayer, void *io_handle, esp_eth_iodriver_io_fns_t *io_fns)
|
||||
{
|
||||
if (sublayer == NULL || io_handle == NULL || io_fns == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_eth_sublayer_t *sub = (esp_eth_sublayer_t *)sublayer;
|
||||
|
||||
// Based on the io_handle, get the io_fns - it can be either higher layer transmit functions with additional
|
||||
// processing like VLAN tagged, or lower layer sublayer transmit functions (closer to the underlying Ethernet driver).
|
||||
// At first check if the io_handle is a VLAN tagged netif iodriver
|
||||
esp_eth_sublayer_vlan_t *netif_iodriver;
|
||||
SLIST_FOREACH(netif_iodriver, &sub->vlan_child, next) {
|
||||
if (netif_iodriver == io_handle) {
|
||||
return eth_vlan_get_iodriver_io_fns(netif_iodriver, io_fns);
|
||||
}
|
||||
}
|
||||
// If no VLAN tagged netif iodriver is found, check if switch port is requested
|
||||
// Note: It's not allowed to request direct host Ethernet driver access when in switch mode.
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
if (sub->switch_child) {
|
||||
return eth_switch_get_iodriver_io_fns(sub->switch_child, io_handle, io_fns);
|
||||
}
|
||||
#endif
|
||||
// If no switch, check if direct lower layer is requested
|
||||
if (io_handle == sub->eth_driver) {
|
||||
io_fns->io_handle = sub;
|
||||
io_fns->iodriver_transmit = eth_sublayer_ll_transmit;
|
||||
io_fns->iodriver_transmit_wrap = eth_sublayer_ll_transmit_wrap;
|
||||
io_fns->iodriver_free_rx_buffer = eth_sublayer_ll_free;
|
||||
io_fns->iodriver_get_ll_driver = eth_sublayer_get_ll_driver;
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t esp_eth_sublayer_del(esp_eth_sublayer_handle_t sublayer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sublayer, ESP_ERR_INVALID_ARG, TAG, "sublayer can't be null");
|
||||
|
||||
while (!SLIST_EMPTY(&sublayer->vlan_child)) {
|
||||
esp_eth_sublayer_vlan_del(sublayer, SLIST_FIRST(&sublayer->vlan_child));
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER && CONFIG_ESP_NETIF_L2_TAP
|
||||
esp_vfs_l2tap_iodriver_provider_unregister(sublayer);
|
||||
#endif // CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER && CONFIG_ESP_NETIF_L2_TAP
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
if (sublayer->switch_child) {
|
||||
esp_eth_sublayer_switch_del(sublayer->switch_child);
|
||||
}
|
||||
#endif
|
||||
|
||||
sub_clear_handlers(sublayer);
|
||||
esp_eth_decrease_reference(sublayer->eth_driver);
|
||||
#if CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
if (sublayer->transmit_mutex) {
|
||||
vSemaphoreDelete(sublayer->transmit_mutex);
|
||||
}
|
||||
#endif // CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
free(sublayer);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_sublayer_new(const esp_eth_sublayer_config_t *config, esp_eth_sublayer_handle_t *sublayer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(config && sublayer, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
ESP_RETURN_ON_ERROR(esp_eth_increase_reference(config->eth_handle), TAG, "failed to increase reference");
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_eth_sublayer_t *sub = calloc(1, sizeof(esp_eth_sublayer_t));
|
||||
ESP_RETURN_ON_FALSE(sub, ESP_ERR_NO_MEM, TAG, "create netif sublayer failed");
|
||||
sub->eth_driver = config->eth_handle;
|
||||
sub->link_speed = ETH_SPEED_MAX; // invalid speed indicates that the speed is not yet known (link is not up)
|
||||
|
||||
sub->connect_confirm = config->connect_confirm_event;
|
||||
sub->disconnect_trigger = config->disconnect_trigger_event;
|
||||
|
||||
sub->tx_hook = config->tx_hook;
|
||||
sub->post_tx_hook = config->post_tx_hook;
|
||||
sub->rx_hook = config->rx_hook;
|
||||
sub->hook_ctx = config->hook_ctx;
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
sub->transmit_mutex = xSemaphoreCreateMutex();
|
||||
ESP_GOTO_ON_FALSE(sub->transmit_mutex, ESP_ERR_NO_MEM, err, TAG, "failed to create sublayer transmit mutex");
|
||||
uint32_t transmit_mutex_timeout_ms = config->transmit_mutex_timeout_ms ? config->transmit_mutex_timeout_ms
|
||||
: ESP_ETH_SUBLAYER_TX_TIMEOUT_MS;
|
||||
sub->transmit_mutex_timeout_ticks = pdMS_TO_TICKS(transmit_mutex_timeout_ms);
|
||||
#endif // CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX
|
||||
|
||||
SLIST_INIT(&sub->vlan_child);
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
sub->base.get_io_fns = eth_sublayer_get_io_fns;
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
ESP_GOTO_ON_ERROR(esp_vfs_l2tap_iodriver_provider_register(sub), err, TAG, "failed to register sublayer with L2 TAP");
|
||||
#endif // CONFIG_ESP_NETIF_L2_TAP
|
||||
#endif // CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
ESP_GOTO_ON_ERROR(esp_eth_update_input_path_info(config->eth_handle, eth_sublayer_input, sub), err, TAG, "failed to update input path info");
|
||||
ESP_GOTO_ON_ERROR(sub_set_handlers(sub), err, TAG, "failed to set handlers");
|
||||
|
||||
*sublayer = sub;
|
||||
return ESP_OK;
|
||||
err:
|
||||
esp_eth_sublayer_del(sub);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t eth_sublayer_get_mac_addr(esp_eth_sublayer_handle_t sublayer, uint8_t *mac_addr)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sublayer && mac_addr, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
return esp_eth_ioctl(sublayer->eth_driver, ETH_CMD_G_MAC_ADDR, mac_addr);
|
||||
}
|
||||
|
||||
esp_eth_handle_t esp_eth_sublayer_get_eth_handle(esp_eth_sublayer_handle_t sublayer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sublayer, NULL, TAG, "sublayer can't be null");
|
||||
return sublayer->eth_driver;
|
||||
}
|
||||
110
components/esp_eth/src/sublayer/esp_eth_sublayer_core.h
Normal file
110
components/esp_eth/src/sublayer/esp_eth_sublayer_core.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_eth_sublayer.h"
|
||||
#include "esp_eth_sublayer_vlan.h"
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
#include "esp_eth_sublayer_switch.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Find a VLAN entry by VID in the sublayer's child list.
|
||||
*
|
||||
* @param sub The sublayer handle.
|
||||
* @param vlan_id The VLAN ID to find.
|
||||
* @return The VLAN child if found, NULL otherwise.
|
||||
*/
|
||||
esp_eth_sublayer_vlan_t *eth_sublayer_find_vlan_by_vid(esp_eth_sublayer_handle_t sub, uint16_t vlan_id);
|
||||
|
||||
/**
|
||||
* @brief Insert a VLAN child into the sublayer's child list.
|
||||
*/
|
||||
esp_err_t eth_sublayer_insert_vlan(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_vlan_t *vlan);
|
||||
|
||||
/**
|
||||
* @brief Remove a VLAN child from the sublayer's child list.
|
||||
*
|
||||
* @param sub The sublayer handle.
|
||||
* @param vlan The VLAN child to remove.
|
||||
* @return ESP_OK if found and removed, ESP_ERR_NOT_FOUND otherwise.
|
||||
*/
|
||||
esp_err_t eth_sublayer_remove_vlan(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_vlan_t *vlan);
|
||||
|
||||
/**
|
||||
* @brief Get the MAC address from the underlying Ethernet driver.
|
||||
*
|
||||
* @param sublayer The sublayer handle.
|
||||
* @param mac_addr The MAC address to get.
|
||||
* @return ESP_OK if the MAC address is got, ESP_ERR_INVALID_ARG if the sublayer is NULL, ESP_ERR_INVALID_ARG if the MAC address is NULL.
|
||||
*/
|
||||
esp_err_t eth_sublayer_get_mac_addr(esp_eth_sublayer_handle_t sublayer, uint8_t *mac_addr);
|
||||
|
||||
/**
|
||||
* @brief Set the MAC filter through the sublayer.
|
||||
*
|
||||
* @param sub The sublayer handle.
|
||||
* @param eth_mac The MAC address.
|
||||
* @param mac_len The length of the MAC address (must be 6).
|
||||
* @param add Whether to add or remove the MAC address.
|
||||
* @return ESP_OK if the MAC filter is set, ESP_ERR_INVALID_ARG if the sublayer is NULL, ESP_ERR_INVALID_ARG if the MAC address is NULL, ESP_ERR_INVALID_ARG if the MAC address length is not 6, ESP_ERR_INVALID_ARG if the add flag is not true or false.
|
||||
*/
|
||||
esp_err_t eth_sublayer_set_mac_filter(esp_eth_sublayer_handle_t sub, const uint8_t *eth_mac, size_t mac_len, bool add);
|
||||
|
||||
/**
|
||||
* @brief Transmit a frame through the sublayer.
|
||||
*
|
||||
* @param sub The sublayer handle.
|
||||
* @param tx_bufs The buffer descriptor set.
|
||||
* @param eb The extended buffer (may hold additional metadata like time stamp).
|
||||
* @param port The port number.
|
||||
* @return ESP_OK if the frame is transmitted, ESP_ERR_INVALID_ARG if the sublayer is NULL, ESP_ERR_INVALID_ARG if the buffer descriptors are NULL, ESP_ERR_INVALID_ARG if the event base is NULL.
|
||||
*/
|
||||
esp_err_t eth_sublayer_transmit(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_tx_bufs_t *tx_bufs, void *eb, int32_t port);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Free a buffer from the sublayer.
|
||||
*
|
||||
* @param sub The sublayer handle.
|
||||
* @param buffer The buffer to free.
|
||||
* @return void
|
||||
*/
|
||||
void eth_sublayer_buf_free(esp_eth_sublayer_handle_t sub, void *buffer);
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
/**
|
||||
* @brief Attach the single integrated switch instance to the sublayer.
|
||||
*
|
||||
* @param sub The sublayer handle.
|
||||
* @param sw The switch instance.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_STATE if a switch is already attached, ESP_ERR_INVALID_ARG on bad args.
|
||||
*/
|
||||
esp_err_t eth_sublayer_set_switch(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_switch_t *sw);
|
||||
|
||||
/**
|
||||
* @brief Get the single integrated switch instance attached to the sublayer.
|
||||
*
|
||||
* @param sub The sublayer handle.
|
||||
* @return The switch instance if found, NULL otherwise.
|
||||
*/
|
||||
esp_eth_sublayer_switch_t *eth_sublayer_get_switch(esp_eth_sublayer_handle_t sub);
|
||||
|
||||
/**
|
||||
* @brief Detach the integrated switch instance from the sublayer.
|
||||
*
|
||||
* @param sub The sublayer handle.
|
||||
* @param sw The switch instance.
|
||||
* @return ESP_OK if detached, ESP_ERR_NOT_FOUND if not attached to this sublayer, ESP_ERR_INVALID_ARG on bad args.
|
||||
*/
|
||||
esp_err_t eth_sublayer_remove_switch(esp_eth_sublayer_handle_t sub, esp_eth_sublayer_switch_t *sw);
|
||||
#endif // CONFIG_ETH_SUBLAYER_SWITCH_SUPPORT
|
||||
28
components/esp_eth/src/sublayer/esp_eth_sublayer_frame.h
Normal file
28
components/esp_eth/src/sublayer/esp_eth_sublayer_frame.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_eth_spec.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t da[ETH_ADDR_LEN];
|
||||
uint8_t sa[ETH_ADDR_LEN];
|
||||
uint16_t ether_type;
|
||||
} __attribute__((packed)) eth_hdr_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t tpid;
|
||||
uint16_t tci;
|
||||
} __attribute__((packed)) eth_vlan_tag_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t da[ETH_ADDR_LEN];
|
||||
uint8_t sa[ETH_ADDR_LEN];
|
||||
eth_vlan_tag_t vlan_tag;
|
||||
uint16_t ether_type;
|
||||
} __attribute__((packed)) eth_vlan_hdr_t;
|
||||
186
components/esp_eth/src/sublayer/esp_eth_sublayer_switch.c
Normal file
186
components/esp_eth/src/sublayer/esp_eth_sublayer_switch.c
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include "esp_eth_sublayer_switch.h"
|
||||
#include "esp_eth_sublayer_core.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
#include "esp_private/esp_eth_sublayer_iodriver.h"
|
||||
#endif
|
||||
|
||||
typedef struct esp_eth_sublayer_ic_switch_port_s {
|
||||
esp_eth_sublayer_switch_t *sw_parent;
|
||||
int32_t port_num;
|
||||
esp_eth_handle_t eth_handle;
|
||||
} esp_eth_sublayer_ic_switch_port_t;
|
||||
|
||||
/* Integrated switch (Tail Tag) child. */
|
||||
struct esp_eth_sublayer_switch_s {
|
||||
esp_eth_sublayer_handle_t sub_parent;
|
||||
esp_eth_sublayer_switch_demux_t demux; /*!< Per RX frame tag strip / ingress port resolve */
|
||||
esp_eth_sublayer_switch_mux_t mux; /*!< Per TX frame tag append */
|
||||
esp_eth_sublayer_switch_tag_deinit_t tag_process_deinit; /*!< Releases @c ctx on delete */
|
||||
void *ctx; /*!< Driver switch context from tag_process_init */
|
||||
uint32_t ports_count; /*!< Number of per-port Ethernet driver handles */
|
||||
esp_eth_sublayer_ic_switch_port_t port_io_handles[]; /*!< Per-port driver handles */
|
||||
};
|
||||
|
||||
static const char *TAG = "esp_eth.sublayer.ic_switch";
|
||||
|
||||
esp_err_t eth_switch_demux(esp_eth_sublayer_switch_t *sw, uint8_t **buffer, uint32_t *length, int32_t *src_port)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sw, ESP_ERR_INVALID_ARG, TAG, "switch can't be null");
|
||||
|
||||
// No demux callback means nothing to strip and no ingress port to report, so the frame is left
|
||||
// unchanged and the caller must keep using the sublayer level handles
|
||||
if (sw->demux == NULL) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
return sw->demux(sw->ctx, buffer, length, src_port);
|
||||
}
|
||||
|
||||
esp_err_t eth_switch_resolve_ingress_port(esp_eth_sublayer_switch_t *sw, int32_t port_num,
|
||||
esp_eth_handle_t *eth_handle, void **io_handle)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sw, ESP_ERR_INVALID_ARG, TAG, "switch can't be null");
|
||||
ESP_RETURN_ON_FALSE(port_num >= 1 && port_num <= (int32_t)sw->ports_count, ESP_ERR_INVALID_ARG, TAG, "invalid port");
|
||||
|
||||
if (eth_handle) {
|
||||
*eth_handle = sw->port_io_handles[port_num - 1].eth_handle;
|
||||
}
|
||||
if (io_handle) {
|
||||
*io_handle = &sw->port_io_handles[port_num - 1];
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t eth_switch_mux(esp_eth_sublayer_switch_t *sw, esp_eth_sublayer_tx_bufs_t *tx_bufs, int32_t port)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sw, ESP_ERR_INVALID_ARG, TAG, "switch can't be null");
|
||||
ESP_RETURN_ON_FALSE(tx_bufs && tx_bufs->bufs && tx_bufs->buf_count, ESP_ERR_INVALID_ARG, TAG, "invalid buffer");
|
||||
ESP_RETURN_ON_FALSE(port >= -1 && port <= (int32_t)sw->ports_count, ESP_ERR_INVALID_ARG, TAG, "invalid port");
|
||||
|
||||
if (sw->mux == NULL) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
return sw->mux(sw->ctx, tx_bufs, port);
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_sublayer_switch_add(esp_eth_sublayer_handle_t sublayer,
|
||||
const esp_eth_sublayer_switch_config_t *config,
|
||||
esp_eth_sublayer_switch_handle_t *sw)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sublayer && config && sw, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
ESP_RETURN_ON_FALSE(config->port_eth_handles && config->ports_count > 0, ESP_ERR_INVALID_ARG, TAG, "invalid port config");
|
||||
|
||||
// A sublayer can hold at most one switch instance
|
||||
if (eth_sublayer_get_switch(sublayer) != NULL) {
|
||||
ESP_LOGE(TAG, "switch already added to sublayer");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_eth_sublayer_switch_t *sw_entry = calloc(1, sizeof(esp_eth_sublayer_switch_t)
|
||||
+ config->ports_count * sizeof(esp_eth_sublayer_ic_switch_port_t));
|
||||
ESP_RETURN_ON_FALSE(sw_entry, ESP_ERR_NO_MEM, TAG, "alloc switch entry failed");
|
||||
for (uint32_t i = 0; i < config->ports_count; i++) {
|
||||
sw_entry->port_io_handles[i].eth_handle = config->port_eth_handles[i];
|
||||
sw_entry->port_io_handles[i].port_num = i + 1;
|
||||
sw_entry->port_io_handles[i].sw_parent = sw_entry;
|
||||
}
|
||||
sw_entry->ports_count = config->ports_count;
|
||||
sw_entry->sub_parent = sublayer;
|
||||
sw_entry->demux = config->demux;
|
||||
sw_entry->mux = config->mux;
|
||||
sw_entry->tag_process_deinit = config->tag_process_deinit;
|
||||
|
||||
// Let the driver allocate its switch context (e.g. enable Tail Tagging, build the port table)
|
||||
if (config->tag_process_init) {
|
||||
esp_err_t ret = config->tag_process_init(config->host_eth_handle, config->port_eth_handles,
|
||||
config->ports_count, &sw_entry->ctx);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "switch tag process init failed (0x%x)", ret);
|
||||
free(sw_entry);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t ret = eth_sublayer_set_switch(sublayer, sw_entry);
|
||||
if (ret != ESP_OK) {
|
||||
if (sw_entry->tag_process_deinit) {
|
||||
sw_entry->tag_process_deinit(sw_entry->ctx);
|
||||
}
|
||||
free(sw_entry);
|
||||
return ret;
|
||||
}
|
||||
*sw = sw_entry;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_sublayer_switch_del(esp_eth_sublayer_switch_handle_t sw)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sw, ESP_ERR_INVALID_ARG, TAG, "switch handle can't be null");
|
||||
|
||||
esp_err_t ret = eth_sublayer_remove_switch(sw->sub_parent, sw);
|
||||
if (ret == ESP_OK) {
|
||||
if (sw->tag_process_deinit) {
|
||||
sw->tag_process_deinit(sw->ctx);
|
||||
}
|
||||
free(sw);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
static esp_err_t eth_switch_transmit_wrap(void *h, void *buf, size_t len, void *eb)
|
||||
{
|
||||
esp_eth_sublayer_ic_switch_port_t *port = (esp_eth_sublayer_ic_switch_port_t *)h;
|
||||
esp_eth_sublayer_handle_t sub = port->sw_parent->sub_parent;
|
||||
esp_eth_buf_desc_t bufs[ESP_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY];
|
||||
size_t buf_count = 1;
|
||||
|
||||
bufs[0] = (esp_eth_buf_desc_t){ .buf = (uint8_t *)buf, .len = len };
|
||||
esp_eth_sublayer_tx_bufs_t tx_bufs = {
|
||||
.bufs = bufs,
|
||||
.buf_count = &buf_count,
|
||||
.buf_capacity = ESP_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY,
|
||||
};
|
||||
return eth_sublayer_transmit(sub, &tx_bufs, eb, port->port_num);
|
||||
}
|
||||
|
||||
static esp_err_t eth_switch_transmit(void *h, void *buf, size_t len)
|
||||
{
|
||||
return eth_switch_transmit_wrap(h, buf, len, NULL);
|
||||
}
|
||||
|
||||
static void eth_switch_free_rx_buffer(void *h, void *buffer)
|
||||
{
|
||||
esp_eth_sublayer_ic_switch_port_t *port = (esp_eth_sublayer_ic_switch_port_t *)h;
|
||||
eth_sublayer_buf_free(port->sw_parent->sub_parent, buffer);
|
||||
}
|
||||
|
||||
static esp_err_t eth_switch_get_ll_driver(void *h, void **ll_driver)
|
||||
{
|
||||
esp_eth_sublayer_ic_switch_port_t *port = (esp_eth_sublayer_ic_switch_port_t *)h;
|
||||
*ll_driver = port->eth_handle;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t eth_switch_get_iodriver_io_fns(esp_eth_sublayer_switch_t *sw, void *io_handle, esp_eth_iodriver_io_fns_t *io_fns)
|
||||
{
|
||||
for(int i = 0; i < sw->ports_count; i++) {
|
||||
if (sw->port_io_handles[i].eth_handle == io_handle) {
|
||||
io_fns->io_handle = &sw->port_io_handles[i];
|
||||
io_fns->iodriver_transmit = eth_switch_transmit;
|
||||
io_fns->iodriver_transmit_wrap = eth_switch_transmit_wrap;
|
||||
io_fns->iodriver_free_rx_buffer = eth_switch_free_rx_buffer;
|
||||
io_fns->iodriver_get_ll_driver = eth_switch_get_ll_driver;
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
#endif
|
||||
72
components/esp_eth/src/sublayer/esp_eth_sublayer_switch.h
Normal file
72
components/esp_eth/src/sublayer/esp_eth_sublayer_switch.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_eth_sublayer.h"
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
#include "esp_private/esp_eth_sublayer_iodriver.h"
|
||||
#endif
|
||||
|
||||
typedef struct esp_eth_sublayer_switch_s esp_eth_sublayer_switch_t;
|
||||
|
||||
/**
|
||||
* @brief RX demultiplexing of an integrated switch frame.
|
||||
*
|
||||
* Strips the switch tag (e.g. Tail Tag) and resolves the ingress port by invoking the driver demux
|
||||
* callback. The frame is not re-routed; the sublayer continues its normal RX pipeline afterwards.
|
||||
*
|
||||
* @param[in] sw The switch instance.
|
||||
* @param[in,out] buffer In: frame start including the switch tag; out: frame start with tag removed.
|
||||
* @param[in,out] length Frame length including the tag on input, tag removed on output.
|
||||
* @param[out] src_port Ingress port number (may be NULL). Only written when ESP_OK is returned.
|
||||
* @return ESP_OK on success, ESP_ERR_NOT_SUPPORTED when the switch has no demux callback (frame left
|
||||
* unchanged, @p src_port not resolved), error code otherwise (the sublayer drops the frame).
|
||||
*/
|
||||
esp_err_t eth_switch_demux(esp_eth_sublayer_switch_t *sw, uint8_t **buffer, uint32_t *length, int32_t *src_port);
|
||||
|
||||
/**
|
||||
* @brief Resolves a switch port number to its Ethernet driver handle and L2 TAP iodriver handle.
|
||||
*
|
||||
* @param[in] sw The switch instance.
|
||||
* @param[in] port_num Ingress port number reported by the demux callback.
|
||||
* @param[out] eth_handle Port Ethernet driver handle (may be NULL).
|
||||
* @param[out] io_handle Port L2 TAP iodriver handle (may be NULL).
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if @p port_num is not registered.
|
||||
*/
|
||||
esp_err_t eth_switch_resolve_ingress_port(esp_eth_sublayer_switch_t *sw, int32_t port_num,
|
||||
esp_eth_handle_t *eth_handle, void **io_handle);
|
||||
|
||||
/**
|
||||
* @brief TX multiplexing of an integrated switch frame.
|
||||
*
|
||||
* Appends the switch tag (e.g. Tail Tag) by invoking the driver mux callback.
|
||||
*
|
||||
* @param[in] sw The switch instance.
|
||||
* @param[in,out] tx_bufs Buffer descriptor set comprising the frame.
|
||||
* @param port Destination port number.
|
||||
* @return ESP_OK on success, ESP_ERR_NOT_SUPPORTED when the switch has no mux callback (frame left
|
||||
* unchanged), error code otherwise.
|
||||
*/
|
||||
esp_err_t eth_switch_mux(esp_eth_sublayer_switch_t *sw, esp_eth_sublayer_tx_bufs_t *tx_bufs, int32_t port);
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
/**
|
||||
* @brief Fill IO driver function pointers for a switch.
|
||||
*
|
||||
* Allows transmit via switch specific port.
|
||||
*
|
||||
* @param[in] sw The switch instance.
|
||||
* @param[in] io_handle The IO handle.
|
||||
* @param[out] io_fns Output; switch specific port iodriver handle.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t eth_switch_get_iodriver_io_fns(esp_eth_sublayer_switch_t *sw, void *io_handle, esp_eth_iodriver_io_fns_t *io_fns);
|
||||
#endif
|
||||
54
components/esp_eth/src/sublayer/esp_eth_sublayer_vlan.h
Normal file
54
components/esp_eth/src/sublayer/esp_eth_sublayer_vlan.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/queue.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_netif_types.h"
|
||||
#include "esp_eth_sublayer.h"
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
#include "esp_private/esp_eth_sublayer_iodriver.h"
|
||||
#endif
|
||||
|
||||
typedef struct esp_eth_sublayer_vlan_s {
|
||||
esp_netif_driver_base_t base;
|
||||
uint16_t vlan_id;
|
||||
uint16_t tci_be; /*!< Pre-computed big-endian TCI for TX path */
|
||||
esp_eth_sublayer_handle_t parent;
|
||||
SLIST_ENTRY(esp_eth_sublayer_vlan_s) next; /*!< Managed exclusively by sublayer.c */
|
||||
} esp_eth_sublayer_vlan_t;
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
/**
|
||||
* @brief Fill IO driver function pointers for a VLAN
|
||||
*
|
||||
* @param vlan_netif_driver VLAN netif driver handle (same pointer as esp_netif_attach(..., handle) / esp_netif_get_io_driver()).
|
||||
* @param io_fns Output; iodriver handle transmit functions.
|
||||
*/
|
||||
esp_err_t eth_vlan_get_iodriver_io_fns(esp_eth_sublayer_vlan_t *vlan_netif_driver, esp_eth_iodriver_io_fns_t *io_fns);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Input a frame through the VLAN netif.
|
||||
*
|
||||
* @param vlan_netif_driver VLAN netif driver handle.
|
||||
* @param frame Input; Ethernet frame start (may be offset from the allocation base).
|
||||
* @param length Input; Ethernet frame length.
|
||||
* @param alloc_base Original RX buffer allocation base (passed to esp_netif_receive for freeing).
|
||||
* @param info Input; Additional information.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
|
||||
*/
|
||||
esp_err_t eth_vlan_input(esp_eth_sublayer_vlan_t *vlan_netif_driver, uint8_t *frame, uint32_t length,
|
||||
void *alloc_base, void *info);
|
||||
|
||||
/**
|
||||
* @brief Extract VLAN ID from an Ethernet frame (or ESP_ETH_SUBLAYER_UNTAGGED_VID if untagged).
|
||||
*/
|
||||
uint16_t eth_vlan_get_ether_type(uint8_t *buffer);
|
||||
269
components/esp_eth/src/sublayer/esp_eth_sublayer_vlan_child.c
Normal file
269
components/esp_eth/src/sublayer/esp_eth_sublayer_vlan_child.c
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include "esp_eth_sublayer_frame.h"
|
||||
#include "esp_eth_sublayer_vlan.h"
|
||||
#include "esp_eth_sublayer_core.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_eth_spec.h"
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
#include "esp_private/esp_eth_sublayer_iodriver.h"
|
||||
#endif
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
#include "esp_vfs_l2tap.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "esp_eth.sublayer.vlan_child";
|
||||
|
||||
|
||||
uint16_t eth_vlan_get_ether_type(uint8_t *buffer)
|
||||
{
|
||||
const eth_hdr_t *hdr = (const eth_hdr_t *)buffer;
|
||||
if (__builtin_bswap16(hdr->ether_type) != ETH_T_8021Q) {
|
||||
return ESP_ETH_SUBLAYER_UNTAGGED_VID;
|
||||
}
|
||||
const eth_vlan_hdr_t *vlan_hdr = (const eth_vlan_hdr_t *)buffer;
|
||||
uint16_t vid = __builtin_bswap16(vlan_hdr->vlan_tag.tci) & 0x0FFF;
|
||||
|
||||
return vid;
|
||||
}
|
||||
|
||||
esp_err_t eth_vlan_input(esp_eth_sublayer_vlan_t *vlan_netif_driver, uint8_t *frame, uint32_t length,
|
||||
void *alloc_base, void *info)
|
||||
{
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
esp_err_t ret = ESP_OK;
|
||||
#endif
|
||||
|
||||
// frame is not VLAN tagged
|
||||
if (vlan_netif_driver->vlan_id == ESP_ETH_SUBLAYER_UNTAGGED_VID) {
|
||||
if (unlikely(vlan_netif_driver->base.netif == NULL)) {
|
||||
ESP_LOGD(TAG, "untagged netif not found");
|
||||
eth_sublayer_buf_free(vlan_netif_driver->parent, alloc_base);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
l2tap_eth_filter_info_t l2tap_info = {
|
||||
.l2_buffer = alloc_base,
|
||||
.hw_ts = (l2tap_timestamp_t *)info, // Memory layout matches the Ethernet MAC driver type (eth_mac_time_t)
|
||||
};
|
||||
ret = esp_vfs_l2tap_eth_filter_frame(vlan_netif_driver, frame, (size_t *)&length, &l2tap_info);
|
||||
if (ret == ESP_OK) {
|
||||
if (length == 0) {
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
eth_sublayer_buf_free(vlan_netif_driver->parent, alloc_base);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
return esp_netif_receive(vlan_netif_driver->base.netif, frame, length, alloc_base);
|
||||
}
|
||||
|
||||
#if !CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
// esp_eth_sublayer_vlan_add() only allows the untagged VID when VLAN support is disabled, so no
|
||||
// tagged VLAN child can exist here - this path is unreachable, kept only to satisfy the compiler.
|
||||
eth_sublayer_buf_free(vlan_netif_driver->parent, alloc_base);
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
// check if VLAN netif is attached
|
||||
if (vlan_netif_driver->base.netif == NULL) {
|
||||
ESP_LOGD(TAG, "netif not found for VLAN ID %" PRIu16, vlan_netif_driver->vlan_id);
|
||||
eth_sublayer_buf_free(vlan_netif_driver->parent, alloc_base);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// remove VLAN tag - just shift start of the frame over VLAN tag to avoid long memcpy of payload
|
||||
memmove(frame + ETH_VLAN_TAG_LEN, frame, ETH_HEADER_LEN - 2);
|
||||
length = length - ETH_VLAN_TAG_LEN;
|
||||
#if CONFIG_ESP_NETIF_L2_TAP
|
||||
// Process VLAN tagged frames through L2 TAP
|
||||
l2tap_eth_filter_info_t l2tap_info = {
|
||||
.l2_buffer = alloc_base,
|
||||
.hw_ts = (l2tap_timestamp_t *)info, // Memory layout matches the Ethernet MAC driver type (eth_mac_time_t)
|
||||
};
|
||||
ret = esp_vfs_l2tap_eth_filter_frame(vlan_netif_driver, frame + ETH_VLAN_TAG_LEN, (size_t *)&length, &l2tap_info);
|
||||
if (ret == ESP_OK) {
|
||||
if (length == 0) {
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
eth_sublayer_buf_free(vlan_netif_driver->parent, alloc_base);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
return esp_netif_receive(vlan_netif_driver->base.netif, frame + ETH_VLAN_TAG_LEN, length, alloc_base);
|
||||
#endif // !CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
}
|
||||
|
||||
static esp_err_t eth_vlan_transmit_wrap(void *h, void *buf, size_t len, void *eb)
|
||||
{
|
||||
esp_eth_sublayer_vlan_t *vlan_netif_driver = (esp_eth_sublayer_vlan_t *)h;
|
||||
esp_eth_sublayer_handle_t sub = vlan_netif_driver->parent;
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_eth_buf_desc_t bufs[ESP_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY];
|
||||
size_t buf_count;
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
eth_vlan_hdr_t *eth_hdr_buf = NULL;
|
||||
if (vlan_netif_driver->vlan_id != ESP_ETH_SUBLAYER_UNTAGGED_VID) {
|
||||
eth_hdr_buf = (eth_vlan_hdr_t *)malloc(sizeof(eth_vlan_hdr_t));
|
||||
if (eth_hdr_buf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Copy Ethernet header to special buffer and add VLAN tag, manipulate only with header to avoid memcpy of payload
|
||||
memcpy(eth_hdr_buf->da, ((eth_hdr_t *)buf)->da, sizeof(eth_hdr_buf->da) + sizeof(eth_hdr_buf->sa));
|
||||
eth_hdr_buf->vlan_tag.tpid = __builtin_bswap16(ETH_T_8021Q);
|
||||
eth_hdr_buf->vlan_tag.tci = vlan_netif_driver->tci_be;
|
||||
eth_hdr_buf->ether_type = ((eth_hdr_t *)buf)->ether_type;
|
||||
bufs[0] = (esp_eth_buf_desc_t){ .buf = (uint8_t *)eth_hdr_buf, .len = ETH_HEADER_LEN + ETH_VLAN_TAG_LEN };
|
||||
|
||||
// Set offset of payload to skip Ethernet header in the second buffer
|
||||
bufs[1] = (esp_eth_buf_desc_t){ .buf = (uint8_t *)buf + ETH_HEADER_LEN, .len = len - ETH_HEADER_LEN };
|
||||
buf_count = 2;
|
||||
} else
|
||||
#endif // CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
{
|
||||
// vlan_id is always ESP_ETH_SUBLAYER_UNTAGGED_VID when CONFIG_ETH_SUBLAYER_VLAN_SUPPORT is disabled,
|
||||
// esp_eth_sublayer_vlan_add() rejects any other VID in that case.
|
||||
bufs[0] = (esp_eth_buf_desc_t){ .buf = (uint8_t *)buf, .len = len };
|
||||
buf_count = 1;
|
||||
}
|
||||
esp_eth_sublayer_tx_bufs_t tx_bufs = {
|
||||
.bufs = bufs,
|
||||
.buf_count = &buf_count,
|
||||
.buf_capacity = ESP_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY,
|
||||
};
|
||||
// eb holds additional metadata like time stamp,
|
||||
// specific port is not defined for VLAN (use integrated switch internal lookup)
|
||||
// Note, this keeps door open to force port netif instance if needed in the future
|
||||
ret = eth_sublayer_transmit(sub, &tx_bufs, eb, -1);
|
||||
#if CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
free(eth_hdr_buf);
|
||||
#endif // CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t eth_vlan_transmit(void *h, void *buf, size_t len)
|
||||
{
|
||||
return eth_vlan_transmit_wrap(h, buf, len, NULL);
|
||||
}
|
||||
|
||||
static esp_err_t eth_vlan_set_mac_filter(void *h, const uint8_t *eth_mac, size_t mac_len, bool add)
|
||||
{
|
||||
esp_eth_sublayer_vlan_t *vlan_netif_driver = (esp_eth_sublayer_vlan_t *)h;
|
||||
return eth_sublayer_set_mac_filter(vlan_netif_driver->parent, eth_mac, mac_len, add);
|
||||
}
|
||||
|
||||
static void eth_vlan_free_rx_buffer(void *h, void *buffer)
|
||||
{
|
||||
esp_eth_sublayer_vlan_t *vlan_netif_driver = (esp_eth_sublayer_vlan_t *)h;
|
||||
eth_sublayer_buf_free(vlan_netif_driver->parent, buffer);
|
||||
}
|
||||
|
||||
esp_err_t netif_post_attach(esp_netif_t *esp_netif, void *args)
|
||||
{
|
||||
uint8_t eth_mac[ETH_ADDR_LEN];
|
||||
esp_eth_sublayer_vlan_t *vlan_netif_driver = (esp_eth_sublayer_vlan_t *)args;
|
||||
esp_eth_sublayer_handle_t parent_sub = vlan_netif_driver->parent;
|
||||
|
||||
esp_netif_driver_ifconfig_t driver_ifconfig = {
|
||||
.handle = vlan_netif_driver,
|
||||
.transmit = eth_vlan_transmit,
|
||||
.transmit_wrap = eth_vlan_transmit_wrap,
|
||||
.driver_free_rx_buffer = eth_vlan_free_rx_buffer,
|
||||
.driver_set_mac_filter = eth_vlan_set_mac_filter,
|
||||
};
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_netif_set_driver_config(esp_netif, &driver_ifconfig), TAG, "failed to set driver config");
|
||||
ESP_RETURN_ON_ERROR(eth_sublayer_get_mac_addr(parent_sub, eth_mac), TAG, "failed to get MAC addr");
|
||||
ESP_RETURN_ON_ERROR(esp_netif_set_mac(esp_netif, eth_mac), TAG, "failed to set MAC");
|
||||
// assigned only once the netif is fully configured, the Rx path uses it to decide whether the frame
|
||||
// can be passed to the netif
|
||||
vlan_netif_driver->base.netif = esp_netif;
|
||||
|
||||
if (vlan_netif_driver->vlan_id == ESP_ETH_SUBLAYER_UNTAGGED_VID) {
|
||||
ESP_LOGI(TAG, "%02x:%02x:%02x:%02x:%02x:%02x", eth_mac[0], eth_mac[1],
|
||||
eth_mac[2], eth_mac[3], eth_mac[4], eth_mac[5]);
|
||||
ESP_LOGI(TAG, "untagged Ethernet attached to netif");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "VLAN %" PRIu16 " attached to netif", vlan_netif_driver->vlan_id);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_eth_handle_t esp_eth_sublayer_vlan_get_eth_handle(esp_eth_sublayer_vlan_handle_t vlan)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(vlan, NULL, TAG, "vlan can't be null");
|
||||
return esp_eth_sublayer_get_eth_handle(vlan->parent);
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_sublayer_vlan_add(esp_eth_sublayer_handle_t sublayer, uint16_t vlan_id,
|
||||
esp_eth_sublayer_vlan_handle_t *vlan)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sublayer && vlan, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
|
||||
#if !CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
ESP_RETURN_ON_FALSE(vlan_id == ESP_ETH_SUBLAYER_UNTAGGED_VID, ESP_ERR_NOT_SUPPORTED, TAG,
|
||||
"tagged VLAN children require CONFIG_ETH_SUBLAYER_VLAN_SUPPORT");
|
||||
#endif // !CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
|
||||
if (eth_sublayer_find_vlan_by_vid(sublayer, vlan_id) != NULL) {
|
||||
ESP_LOGE(TAG, "duplicate VLAN id %" PRIu16, vlan_id);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_eth_sublayer_vlan_t *vlan_entry = calloc(1, sizeof(esp_eth_sublayer_vlan_t));
|
||||
ESP_RETURN_ON_FALSE(vlan_entry, ESP_ERR_NO_MEM, TAG, "alloc vlan entry failed");
|
||||
vlan_entry->vlan_id = vlan_id;
|
||||
vlan_entry->tci_be = __builtin_bswap16((uint16_t)(vlan_id & 0x0FFF));
|
||||
vlan_entry->parent = sublayer;
|
||||
vlan_entry->base.post_attach = netif_post_attach;
|
||||
|
||||
eth_sublayer_insert_vlan(sublayer, vlan_entry);
|
||||
*vlan = vlan_entry;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_eth_sublayer_vlan_del(esp_eth_sublayer_handle_t sublayer, esp_eth_sublayer_vlan_handle_t vlan)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sublayer && vlan, ESP_ERR_INVALID_ARG, TAG, "invalid arg");
|
||||
|
||||
esp_err_t ret = eth_sublayer_remove_vlan(sublayer, vlan);
|
||||
if (ret == ESP_OK) {
|
||||
free(vlan);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_IODRIVER_PROVIDER
|
||||
static esp_err_t eth_vlan_get_ll_driver(void *h, void **ll_driver)
|
||||
{
|
||||
esp_eth_sublayer_vlan_t *vlan_netif_driver = (esp_eth_sublayer_vlan_t *)h;
|
||||
*ll_driver = esp_eth_sublayer_get_eth_handle(vlan_netif_driver->parent);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t eth_vlan_get_iodriver_io_fns(esp_eth_sublayer_vlan_t *vlan_netif_driver, esp_eth_iodriver_io_fns_t *io_fns)
|
||||
{
|
||||
// Note: May be called from a critical section, hence no logging, allocation nor blocking is allowed here.
|
||||
if (vlan_netif_driver == NULL || io_fns == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
io_fns->io_handle = vlan_netif_driver;
|
||||
io_fns->iodriver_transmit = eth_vlan_transmit;
|
||||
io_fns->iodriver_transmit_wrap = eth_vlan_transmit_wrap;
|
||||
io_fns->iodriver_free_rx_buffer = eth_vlan_free_rx_buffer;
|
||||
io_fns->iodriver_get_ll_driver = eth_vlan_get_ll_driver;
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
components/esp_eth/test_apps:
|
||||
components/esp_eth/test_apps/test_app_driver:
|
||||
enable:
|
||||
- if: IDF_TARGET in ["esp32", "esp32p4", "esp32s31"]
|
||||
reason: ESP32, ESP32P4 and ESP32S31 have internal EMAC.
|
||||
@@ -13,3 +13,12 @@ components/esp_eth/test_apps:
|
||||
- hal
|
||||
- lwip
|
||||
- soc
|
||||
|
||||
components/esp_eth/test_apps/test_app_sublayer:
|
||||
enable:
|
||||
- if: IDF_TARGET in ["esp32", "esp32p4"]
|
||||
reason: ESP32 and ESP32P4 have internal EMAC.
|
||||
depends_components:
|
||||
- esp_eth
|
||||
- esp_netif
|
||||
- lwip
|
||||
|
||||
@@ -191,48 +191,58 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
|
||||
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
|
||||
|
||||
ESP_LOGI(TAG, "-- Verify transmission using extended Tx fnc using one buffer--");
|
||||
esp_eth_buf_desc_t tx_bufs[3] = {
|
||||
{ .buf = (uint8_t *)test_pkt },
|
||||
};
|
||||
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));
|
||||
tx_bufs[0].len = transmit_size;
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, &ts, tx_bufs, 1));
|
||||
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));
|
||||
tx_bufs[0].len = transmit_size;
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 1));
|
||||
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));
|
||||
tx_bufs[0].len = transmit_size;
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 1));
|
||||
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));
|
||||
tx_bufs[0].len = transmit_size;
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 1));
|
||||
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));
|
||||
tx_bufs[0].len = transmit_size;
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 1));
|
||||
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));
|
||||
tx_bufs[0].len = transmit_size;
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 1));
|
||||
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 = (uint8_t *)eth_test_alloc(ETH_MAX_PAYLOAD_LEN);
|
||||
tx_bufs[1].buf = pkt_data_2;
|
||||
// fill with data (reverse order to differentiate the buffers)
|
||||
int j = ETH_MAX_PAYLOAD_LEN;
|
||||
for (int i = 0; i < ETH_MAX_PAYLOAD_LEN; i++) {
|
||||
@@ -247,9 +257,11 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
|
||||
transmit_size_2 = CONFIG_ETH_DMA_BUFFER_SIZE;
|
||||
recv_info.expected_size = transmit_size;
|
||||
recv_info.expected_size_2 = transmit_size_2;
|
||||
tx_bufs[0].len = transmit_size;
|
||||
tx_bufs[1].len = 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_ctrl_vargs(eth_handle, NULL, 4, test_pkt, transmit_size, pkt_data_2, transmit_size_2));
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 2));
|
||||
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
|
||||
}
|
||||
|
||||
@@ -258,8 +270,10 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
|
||||
transmit_size_2 = CONFIG_ETH_DMA_BUFFER_SIZE;
|
||||
recv_info.expected_size = transmit_size;
|
||||
recv_info.expected_size_2 = transmit_size_2;
|
||||
tx_bufs[0].len = transmit_size;
|
||||
tx_bufs[1].len = 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_bufs(eth_handle, NULL, tx_bufs, 2));
|
||||
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
|
||||
|
||||
ESP_LOGI(TAG, "Verify boundary conditions");
|
||||
@@ -267,29 +281,36 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
|
||||
transmit_size_2 = CONFIG_ETH_DMA_BUFFER_SIZE;
|
||||
recv_info.expected_size = transmit_size;
|
||||
recv_info.expected_size_2 = transmit_size_2;
|
||||
tx_bufs[0].len = transmit_size;
|
||||
tx_bufs[1].len = 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_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 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;
|
||||
tx_bufs[0].len = transmit_size;
|
||||
tx_bufs[1].len = 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_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 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;
|
||||
tx_bufs[0].len = transmit_size;
|
||||
tx_bufs[1].len = 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_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 2));
|
||||
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
|
||||
|
||||
uint16_t transmit_size_3 = 256;
|
||||
// allocated the third buffer
|
||||
uint8_t *pkt_data_3 = (uint8_t *)eth_test_alloc(256);
|
||||
tx_bufs[2].buf = pkt_data_3;
|
||||
// fill with data
|
||||
for (int i = 0; i < 256; i++) {
|
||||
pkt_data_3[i] = i & 0xFF;
|
||||
@@ -302,8 +323,11 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
|
||||
recv_info.expected_size = transmit_size;
|
||||
recv_info.expected_size_2 = transmit_size_2;
|
||||
recv_info.expected_size_3 = transmit_size_3;
|
||||
tx_bufs[0].len = transmit_size;
|
||||
tx_bufs[1].len = transmit_size_2;
|
||||
tx_bufs[2].len = 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_ctrl_vargs(eth_handle, NULL, 6, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 3));
|
||||
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
|
||||
|
||||
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE - 1;
|
||||
@@ -312,8 +336,11 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
|
||||
recv_info.expected_size = transmit_size;
|
||||
recv_info.expected_size_2 = transmit_size_2;
|
||||
recv_info.expected_size_3 = transmit_size_3;
|
||||
tx_bufs[0].len = transmit_size;
|
||||
tx_bufs[1].len = transmit_size_2;
|
||||
tx_bufs[2].len = 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_ctrl_vargs(eth_handle, NULL, 6, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 3));
|
||||
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
|
||||
|
||||
transmit_size = CONFIG_ETH_DMA_BUFFER_SIZE + 1;
|
||||
@@ -322,8 +349,11 @@ TEST_CASE("internal emac receive/transmit", "[esp_emac]")
|
||||
recv_info.expected_size = transmit_size;
|
||||
recv_info.expected_size_2 = transmit_size_2;
|
||||
recv_info.expected_size_3 = transmit_size_3;
|
||||
tx_bufs[0].len = transmit_size;
|
||||
tx_bufs[1].len = transmit_size_2;
|
||||
tx_bufs[2].len = 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_ctrl_vargs(eth_handle, NULL, 6, test_pkt, transmit_size, pkt_data_2, transmit_size_2, pkt_data_3, transmit_size_3));
|
||||
TEST_ESP_OK(esp_eth_transmit_ctrl_bufs(eth_handle, NULL, tx_bufs, 3));
|
||||
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(500)));
|
||||
|
||||
// stop Ethernet driver
|
||||
@@ -0,0 +1,5 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp_eth_sublayer_test)
|
||||
2
components/esp_eth/test_apps/test_app_sublayer/README.md
Normal file
2
components/esp_eth/test_apps/test_app_sublayer/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-P4 |
|
||||
| ----------------- | ----- | -------- |
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(SRCS "test_main.c"
|
||||
"test_sublayer_common.c"
|
||||
"test_sublayer_events.c"
|
||||
"test_sublayer_hooks.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity esp_eth esp_netif esp_event lwip
|
||||
WHOLE_ARCHIVE)
|
||||
@@ -0,0 +1,4 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/ethernet_init:
|
||||
version: "^1.3.0"
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity.h"
|
||||
#include "unity_test_utils_memory.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "test_sublayer_common.h"
|
||||
|
||||
#ifndef CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
#error "This test app requires CONFIG_ETH_SUBLAYER_SUPPORT"
|
||||
#endif
|
||||
|
||||
#define TEST_TASK_STACK_SIZE 8192
|
||||
#define TEST_TASK_PRIORITY 5
|
||||
/* Some resources are lazy allocated in lwIP which we don't have under control */
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD 400
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
unity_utils_record_free_mem();
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
sublayer_test_force_teardown();
|
||||
unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD);
|
||||
}
|
||||
|
||||
static void test_task(void *pvParameters)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
xTaskCreatePinnedToCore(test_task, "testTask", TEST_TASK_STACK_SIZE,
|
||||
NULL, TEST_TASK_PRIORITY, NULL, tskNO_AFFINITY);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "test_sublayer_common.h"
|
||||
#include "esp_vfs_l2tap.h"
|
||||
#include "esp_log.h"
|
||||
#include "unity.h"
|
||||
#include "ethernet_init.h"
|
||||
|
||||
static const char *TAG = "sublayer_test_common";
|
||||
|
||||
static sublayer_test_ctx_t *s_active_ctx;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* ETH event handler */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void eth_event_handler(void *arg, esp_event_base_t base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
EventGroupHandle_t eg = (EventGroupHandle_t)arg;
|
||||
(void)base;
|
||||
(void)event_data;
|
||||
switch (event_id) {
|
||||
case ETHERNET_EVENT_START:
|
||||
xEventGroupSetBits(eg, SUBLAYER_TEST_ETH_START_BIT);
|
||||
break;
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
xEventGroupSetBits(eg, SUBLAYER_TEST_ETH_CONNECT_BIT);
|
||||
break;
|
||||
case ETHERNET_EVENT_STOP:
|
||||
xEventGroupSetBits(eg, SUBLAYER_TEST_ETH_STOP_BIT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Granular init helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void sublayer_test_create_event_loop(sublayer_test_ctx_t *ctx)
|
||||
{
|
||||
TEST_ESP_OK(esp_netif_init());
|
||||
|
||||
if (esp_event_loop_create_default() == ESP_ERR_INVALID_STATE) {
|
||||
esp_event_loop_delete_default();
|
||||
TEST_ESP_OK(esp_event_loop_create_default());
|
||||
}
|
||||
|
||||
ctx->eg = xEventGroupCreate();
|
||||
TEST_ASSERT_NOT_NULL(ctx->eg);
|
||||
|
||||
TEST_ESP_OK(esp_event_handler_instance_register(ETH_EVENT, ESP_EVENT_ANY_ID,
|
||||
eth_event_handler, ctx->eg,
|
||||
&ctx->eth_evt_inst));
|
||||
}
|
||||
|
||||
void sublayer_test_init_ethernet(sublayer_test_ctx_t *ctx)
|
||||
{
|
||||
TEST_ESP_OK(ethernet_init_all(&ctx->eth_handles, &ctx->eth_cnt));
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx->eth_cnt);
|
||||
}
|
||||
|
||||
void sublayer_test_register_l2tap(sublayer_test_ctx_t *ctx)
|
||||
{
|
||||
esp_err_t ret = esp_vfs_l2tap_intf_register(NULL);
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
esp_vfs_l2tap_intf_unregister(NULL);
|
||||
TEST_ESP_OK(esp_vfs_l2tap_intf_register(NULL));
|
||||
} else {
|
||||
TEST_ESP_OK(ret);
|
||||
}
|
||||
ctx->l2tap_registered = true;
|
||||
}
|
||||
|
||||
esp_netif_t *sublayer_test_create_eth_netif(sublayer_test_ctx_t *ctx,
|
||||
const char *if_key,
|
||||
const char *if_desc)
|
||||
{
|
||||
esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_ETH();
|
||||
base_cfg.if_key = if_key;
|
||||
base_cfg.if_desc = if_desc;
|
||||
esp_netif_config_t netif_cfg = {
|
||||
.base = &base_cfg,
|
||||
.driver = NULL,
|
||||
.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH,
|
||||
};
|
||||
esp_netif_t *netif = esp_netif_new(&netif_cfg);
|
||||
TEST_ASSERT_NOT_NULL(netif);
|
||||
|
||||
TEST_ASSERT_LESS_THAN(SUBLAYER_TEST_MAX_NETIFS, ctx->netif_count);
|
||||
ctx->netifs[ctx->netif_count++] = netif;
|
||||
return netif;
|
||||
}
|
||||
|
||||
void sublayer_test_start_and_wait_connect(sublayer_test_ctx_t *ctx)
|
||||
{
|
||||
bool loopback_en = true;
|
||||
esp_eth_ioctl(ctx->eth_handles[0], ETH_CMD_S_PHY_LOOPBACK, &loopback_en);
|
||||
|
||||
TEST_ESP_OK(esp_eth_start(ctx->eth_handles[0]));
|
||||
|
||||
EventBits_t bits;
|
||||
bits = xEventGroupWaitBits(ctx->eg, SUBLAYER_TEST_ETH_START_BIT, pdTRUE, pdTRUE,
|
||||
pdMS_TO_TICKS(SUBLAYER_TEST_ETH_START_TIMEOUT_MS));
|
||||
TEST_ASSERT_BITS(SUBLAYER_TEST_ETH_START_BIT, SUBLAYER_TEST_ETH_START_BIT, bits);
|
||||
|
||||
bits = xEventGroupWaitBits(ctx->eg, SUBLAYER_TEST_ETH_CONNECT_BIT, pdTRUE, pdTRUE,
|
||||
pdMS_TO_TICKS(SUBLAYER_TEST_ETH_CONNECT_TIMEOUT_MS));
|
||||
TEST_ASSERT_BITS(SUBLAYER_TEST_ETH_CONNECT_BIT, SUBLAYER_TEST_ETH_CONNECT_BIT, bits);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(SUBLAYER_TEST_EVENT_PROPAGATION_MS));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Teardown */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void sublayer_test_teardown(sublayer_test_ctx_t *ctx)
|
||||
{
|
||||
if (ctx->eth_handles != NULL && ctx->eth_cnt > 0) {
|
||||
esp_eth_stop(ctx->eth_handles[0]);
|
||||
if (ctx->eg != NULL) {
|
||||
xEventGroupWaitBits(ctx->eg, SUBLAYER_TEST_ETH_STOP_BIT, pdTRUE, pdTRUE,
|
||||
pdMS_TO_TICKS(SUBLAYER_TEST_ETH_STOP_TIMEOUT_MS));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
for (int i = 0; i < ctx->netif_count; i++) {
|
||||
if (ctx->netifs[i] != NULL) {
|
||||
esp_netif_destroy(ctx->netifs[i]);
|
||||
ctx->netifs[i] = NULL;
|
||||
}
|
||||
}
|
||||
ctx->netif_count = 0;
|
||||
|
||||
if (ctx->sub != NULL) {
|
||||
esp_eth_sublayer_del(ctx->sub);
|
||||
ctx->sub = NULL;
|
||||
}
|
||||
|
||||
if (ctx->eth_evt_inst != NULL) {
|
||||
esp_event_handler_instance_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, ctx->eth_evt_inst);
|
||||
ctx->eth_evt_inst = NULL;
|
||||
}
|
||||
|
||||
if (ctx->eth_handles != NULL) {
|
||||
ethernet_deinit_all(ctx->eth_handles);
|
||||
ctx->eth_handles = NULL;
|
||||
ctx->eth_cnt = 0;
|
||||
}
|
||||
|
||||
if (ctx->l2tap_registered) {
|
||||
esp_vfs_l2tap_intf_unregister(NULL);
|
||||
ctx->l2tap_registered = false;
|
||||
}
|
||||
|
||||
if (ctx->eg != NULL) {
|
||||
vEventGroupDelete(ctx->eg);
|
||||
ctx->eg = NULL;
|
||||
}
|
||||
|
||||
esp_event_loop_delete_default();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Force-teardown safety net */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void sublayer_test_register_ctx(sublayer_test_ctx_t *ctx)
|
||||
{
|
||||
s_active_ctx = ctx;
|
||||
}
|
||||
|
||||
void sublayer_test_unregister_ctx(void)
|
||||
{
|
||||
s_active_ctx = NULL;
|
||||
}
|
||||
|
||||
void sublayer_test_force_teardown(void)
|
||||
{
|
||||
if (s_active_ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Force teardown triggered — cleaning up after test failure");
|
||||
sublayer_test_teardown(s_active_ctx);
|
||||
s_active_ctx = NULL;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifndef CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
#error "This test app requires CONFIG_ETH_SUBLAYER_SUPPORT"
|
||||
#endif
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SUBLAYER_TEST_ETH_START_BIT BIT0
|
||||
#define SUBLAYER_TEST_ETH_CONNECT_BIT BIT1
|
||||
#define SUBLAYER_TEST_ETH_STOP_BIT BIT2
|
||||
|
||||
#define SUBLAYER_TEST_ETH_START_TIMEOUT_MS 10000
|
||||
#define SUBLAYER_TEST_ETH_CONNECT_TIMEOUT_MS 30000
|
||||
#define SUBLAYER_TEST_ETH_STOP_TIMEOUT_MS 10000
|
||||
#define SUBLAYER_TEST_EVENT_PROPAGATION_MS 500
|
||||
|
||||
#define SUBLAYER_TEST_MAX_NETIFS 4
|
||||
|
||||
/**
|
||||
* Base test context shared by all sublayer test modules.
|
||||
* Embed as the FIRST member of test-specific context structs.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_eth_handle_t *eth_handles;
|
||||
uint8_t eth_cnt;
|
||||
EventGroupHandle_t eg;
|
||||
esp_eth_sublayer_handle_t sub;
|
||||
esp_event_handler_instance_t eth_evt_inst;
|
||||
esp_netif_t *netifs[SUBLAYER_TEST_MAX_NETIFS];
|
||||
uint8_t netif_count;
|
||||
bool l2tap_registered;
|
||||
} sublayer_test_ctx_t;
|
||||
|
||||
/**
|
||||
* Create the default event loop and register an ETH event handler
|
||||
* that signals START/CONNECT/STOP bits on ctx->eg.
|
||||
*/
|
||||
void sublayer_test_create_event_loop(sublayer_test_ctx_t *ctx);
|
||||
|
||||
/**
|
||||
* Initialize all available Ethernet drivers (PHY + MAC).
|
||||
* Populates ctx->eth_handles and ctx->eth_cnt.
|
||||
*/
|
||||
void sublayer_test_init_ethernet(sublayer_test_ctx_t *ctx);
|
||||
|
||||
/**
|
||||
* Register L2TAP VFS interface (/dev/net/tap).
|
||||
* Sets ctx->l2tap_registered so teardown knows to unregister.
|
||||
*/
|
||||
void sublayer_test_register_l2tap(sublayer_test_ctx_t *ctx);
|
||||
|
||||
/**
|
||||
* Create an esp_netif with custom if_key/if_desc and track it in
|
||||
* ctx->netifs[] for automatic cleanup during teardown.
|
||||
*/
|
||||
esp_netif_t *sublayer_test_create_eth_netif(sublayer_test_ctx_t *ctx,
|
||||
const char *if_key,
|
||||
const char *if_desc);
|
||||
|
||||
/**
|
||||
* Enable PHY loopback, start ETH driver, and wait for START + CONNECT events.
|
||||
*/
|
||||
void sublayer_test_start_and_wait_connect(sublayer_test_ctx_t *ctx);
|
||||
|
||||
/**
|
||||
* Graceful teardown: stop ETH, destroy tracked netifs, delete sublayer,
|
||||
* unregister events, deinit ETH drivers, unregister L2TAP, delete event loop.
|
||||
*/
|
||||
void sublayer_test_teardown(sublayer_test_ctx_t *ctx);
|
||||
|
||||
/**
|
||||
* Register the active test context for the force-teardown safety net.
|
||||
*/
|
||||
void sublayer_test_register_ctx(sublayer_test_ctx_t *ctx);
|
||||
|
||||
/**
|
||||
* Clear the registered active context (call at end of normal teardown).
|
||||
*/
|
||||
void sublayer_test_unregister_ctx(void);
|
||||
|
||||
/**
|
||||
* Force-teardown safety net called from Unity tearDown() — cleans up
|
||||
* after a test failure regardless of which test module was active.
|
||||
*/
|
||||
void sublayer_test_force_teardown(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Target tests for the Ethernet sublayer event forwarding logic.
|
||||
*
|
||||
* Tested behaviour:
|
||||
* - Without a connect-confirm event the sublayer forwards
|
||||
* ETHERNET_EVENT_CONNECTED straight to esp_netif_action_connected(),
|
||||
* bringing the netif up immediately.
|
||||
* - With a connect-confirm event the netif stays down after
|
||||
* ETHERNET_EVENT_CONNECTED and only comes up when the custom
|
||||
* confirm event is posted.
|
||||
* - ETHERNET_EVENT_DISCONNECTED brings the netif down.
|
||||
* - A custom disconnect-trigger event also brings the netif down.
|
||||
*
|
||||
* PHY loopback mode is enabled so the tests are self-contained and do
|
||||
* not require a physical cable.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "esp_log.h"
|
||||
#include "unity.h"
|
||||
#include "ethernet_init.h"
|
||||
#include "test_sublayer_common.h"
|
||||
|
||||
static const char *TAG = "sublayer_events_test";
|
||||
|
||||
#define EVENT_PROPAGATION_MS 200
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(TEST_SUBLAYER_EVENT);
|
||||
enum {
|
||||
TEST_CONFIRM_EVENT_ID = 0,
|
||||
TEST_DISCONNECT_EVENT_ID = 1,
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test context and helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef struct {
|
||||
sublayer_test_ctx_t base;
|
||||
esp_netif_t *netif;
|
||||
esp_eth_sublayer_vlan_handle_t vlan_hdl;
|
||||
} events_test_ctx_t;
|
||||
|
||||
static void test_setup(events_test_ctx_t *ctx, const esp_eth_sublayer_config_t *sub_cfg_template)
|
||||
{
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
sublayer_test_create_event_loop(&ctx->base);
|
||||
sublayer_test_init_ethernet(&ctx->base);
|
||||
sublayer_test_register_ctx(&ctx->base);
|
||||
|
||||
esp_eth_sublayer_config_t cfg = *sub_cfg_template;
|
||||
cfg.eth_handle = ctx->base.eth_handles[0];
|
||||
TEST_ESP_OK(esp_eth_sublayer_new(&cfg, &ctx->base.sub));
|
||||
|
||||
TEST_ESP_OK(esp_eth_sublayer_vlan_add(ctx->base.sub, ESP_ETH_SUBLAYER_UNTAGGED_VID, &ctx->vlan_hdl));
|
||||
|
||||
ctx->netif = sublayer_test_create_eth_netif(&ctx->base, "ETH_DEF", "eth");
|
||||
TEST_ESP_OK(esp_netif_attach(ctx->netif, ctx->vlan_hdl));
|
||||
}
|
||||
|
||||
static void test_start_and_wait_connect(events_test_ctx_t *ctx)
|
||||
{
|
||||
sublayer_test_start_and_wait_connect(&ctx->base);
|
||||
}
|
||||
|
||||
static void test_teardown(events_test_ctx_t *ctx)
|
||||
{
|
||||
sublayer_test_teardown(&ctx->base);
|
||||
sublayer_test_unregister_ctx();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test cases */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
TEST_CASE("connect without confirm event sets link up", "[sublayer_events]")
|
||||
{
|
||||
ESP_LOGI(TAG, "--- no confirm event: CONNECTED should bring netif up immediately ---");
|
||||
|
||||
esp_eth_sublayer_config_t sub_cfg = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
|
||||
events_test_ctx_t ctx;
|
||||
test_setup(&ctx, &sub_cfg);
|
||||
test_start_and_wait_connect(&ctx);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"netif must be up after CONNECTED (no confirm event)");
|
||||
|
||||
test_teardown(&ctx);
|
||||
}
|
||||
|
||||
TEST_CASE("connect with confirm event defers link up", "[sublayer_events]")
|
||||
{
|
||||
ESP_LOGI(TAG, "--- with confirm event: CONNECTED alone must NOT bring netif up ---");
|
||||
|
||||
esp_eth_sublayer_config_t sub_cfg = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
sub_cfg.connect_confirm_event = (esp_eth_sublayer_event_t){
|
||||
.base = TEST_SUBLAYER_EVENT,
|
||||
.event_id = TEST_CONFIRM_EVENT_ID,
|
||||
};
|
||||
|
||||
events_test_ctx_t ctx;
|
||||
test_setup(&ctx, &sub_cfg);
|
||||
test_start_and_wait_connect(&ctx);
|
||||
|
||||
TEST_ASSERT_FALSE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"netif must stay down after CONNECTED when confirm event is configured");
|
||||
|
||||
ESP_LOGI(TAG, "--- posting confirm event ---");
|
||||
TEST_ESP_OK(esp_event_post(TEST_SUBLAYER_EVENT, TEST_CONFIRM_EVENT_ID,
|
||||
NULL, 0, portMAX_DELAY));
|
||||
vTaskDelay(pdMS_TO_TICKS(EVENT_PROPAGATION_MS));
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"netif must be up after confirm event");
|
||||
|
||||
test_teardown(&ctx);
|
||||
}
|
||||
|
||||
TEST_CASE("confirm event prior connect sets link up", "[sublayer_events]")
|
||||
{
|
||||
ESP_LOGI(TAG, "--- confirm event posted before CONNECTED: netif must come up on CONNECTED ---");
|
||||
|
||||
esp_eth_sublayer_config_t sub_cfg = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
sub_cfg.connect_confirm_event = (esp_eth_sublayer_event_t){
|
||||
.base = TEST_SUBLAYER_EVENT,
|
||||
.event_id = TEST_CONFIRM_EVENT_ID,
|
||||
};
|
||||
|
||||
events_test_ctx_t ctx;
|
||||
test_setup(&ctx, &sub_cfg);
|
||||
|
||||
ESP_LOGI(TAG, "--- posting confirm event while the physical link is down ---");
|
||||
TEST_ESP_OK(esp_event_post(TEST_SUBLAYER_EVENT, TEST_CONFIRM_EVENT_ID,
|
||||
NULL, 0, portMAX_DELAY));
|
||||
vTaskDelay(pdMS_TO_TICKS(EVENT_PROPAGATION_MS));
|
||||
|
||||
TEST_ASSERT_FALSE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"netif must stay down when only the confirm event was received");
|
||||
|
||||
test_start_and_wait_connect(&ctx);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"netif must be up after CONNECTED when the confirm event was received before");
|
||||
|
||||
test_teardown(&ctx);
|
||||
}
|
||||
|
||||
TEST_CASE("eth disconnect event sets link down", "[sublayer_events]")
|
||||
{
|
||||
ESP_LOGI(TAG, "--- ETHERNET_EVENT_DISCONNECTED should bring netif down ---");
|
||||
|
||||
esp_eth_sublayer_config_t sub_cfg = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
|
||||
events_test_ctx_t ctx;
|
||||
test_setup(&ctx, &sub_cfg);
|
||||
test_start_and_wait_connect(&ctx);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"precondition: netif must be up before disconnect test");
|
||||
|
||||
esp_eth_handle_t hdl = ctx.base.eth_handles[0];
|
||||
TEST_ESP_OK(esp_event_post(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED,
|
||||
&hdl, sizeof(hdl), portMAX_DELAY));
|
||||
vTaskDelay(pdMS_TO_TICKS(EVENT_PROPAGATION_MS));
|
||||
|
||||
TEST_ASSERT_FALSE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"netif must be down after ETHERNET_EVENT_DISCONNECTED");
|
||||
|
||||
test_teardown(&ctx);
|
||||
}
|
||||
|
||||
TEST_CASE("custom disconnect trigger sets link down", "[sublayer_events]")
|
||||
{
|
||||
ESP_LOGI(TAG, "--- custom disconnect trigger should bring netif down ---");
|
||||
|
||||
esp_eth_sublayer_config_t sub_cfg = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
sub_cfg.disconnect_trigger_event = (esp_eth_sublayer_event_t){
|
||||
.base = TEST_SUBLAYER_EVENT,
|
||||
.event_id = TEST_DISCONNECT_EVENT_ID,
|
||||
};
|
||||
|
||||
events_test_ctx_t ctx;
|
||||
test_setup(&ctx, &sub_cfg);
|
||||
test_start_and_wait_connect(&ctx);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"precondition: netif must be up before disconnect trigger test");
|
||||
|
||||
TEST_ESP_OK(esp_event_post(TEST_SUBLAYER_EVENT, TEST_DISCONNECT_EVENT_ID,
|
||||
NULL, 0, portMAX_DELAY));
|
||||
vTaskDelay(pdMS_TO_TICKS(EVENT_PROPAGATION_MS));
|
||||
|
||||
TEST_ASSERT_FALSE_MESSAGE(esp_netif_is_netif_up(ctx.netif),
|
||||
"netif must be down after custom disconnect trigger");
|
||||
|
||||
test_teardown(&ctx);
|
||||
}
|
||||
@@ -0,0 +1,757 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Target tests for Ethernet sublayer TX / RX / post-TX hooks.
|
||||
*
|
||||
* PHY loopback mode and L2TAP read/write are used so the tests are
|
||||
* self-contained. Both untagged and tagged (802.1Q) netifs are
|
||||
* exercised in the same cases where applicable.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "esp_eth_spec.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs_l2tap.h"
|
||||
#include "unity.h"
|
||||
#include "ethernet_init.h"
|
||||
#include "lwip/prot/ethernet.h"
|
||||
#include "arpa/inet.h"
|
||||
#include "test_sublayer_common.h"
|
||||
|
||||
static const char *TAG = "sublayer_hooks_test";
|
||||
|
||||
#define L2TAP_SEND_DELAY_MS 1000
|
||||
#define L2TAP_IO_TIMEOUT_MS 5000
|
||||
|
||||
#define IFKEY_UNTAGGED "ETH_DEF"
|
||||
#define IFKEY_VLAN "ETH_VLAN"
|
||||
#define TEST_VLAN_ID 100
|
||||
|
||||
#define ETH_TYPE_HOOK_UNTAGGED 0x88B5
|
||||
#define ETH_TYPE_HOOK_VLAN 0x88B6
|
||||
#define ETH_TYPE_HOOK_HEAD 0x88B7 /* Ethertype of the TX hook inserted leading segment */
|
||||
|
||||
#define TX_PREFIX_HEAD_SEG_LEN (ETH_HEADER_LEN + TX_PREFIX_LEN)
|
||||
|
||||
#define TX_PREFIX_MAGIC_0 0xDE
|
||||
#define TX_PREFIX_MAGIC_1 0xAD
|
||||
#define TX_PREFIX_MAGIC_2 0xBE
|
||||
#define TX_PREFIX_MAGIC_3 0xEF
|
||||
#define TX_PREFIX_LEN 4
|
||||
|
||||
#define FRAME_MARKER_TX 0xA5
|
||||
#define FRAME_MARKER_RX 0x5A
|
||||
|
||||
/*
|
||||
* Use large frames so a missed free in a sublayer/hook path exceeds the
|
||||
* non-zero leak threshold (see TEST_MEMORY_LEAK_THRESHOLD in test_main.c;
|
||||
* some lwIP resources are lazy-allocated and not under our control).
|
||||
*/
|
||||
#define HOOK_TEST_FRAME_LEN 1000
|
||||
#define HOOK_TEST_RX_BUF_LEN (HOOK_TEST_FRAME_LEN + TX_PREFIX_LEN)
|
||||
|
||||
typedef enum {
|
||||
HOOK_TX_OP_NONE = 0,
|
||||
HOOK_TX_OP_PREFIX,
|
||||
HOOK_TX_OP_PREFIX_HEAD,
|
||||
HOOK_TX_OP_MERGE,
|
||||
HOOK_TX_OP_SKIP,
|
||||
HOOK_TX_OP_ABORT,
|
||||
HOOK_TX_OP_POST_TX_FREE,
|
||||
} hook_tx_op_t;
|
||||
|
||||
typedef enum {
|
||||
HOOK_RX_OP_NONE = 0,
|
||||
HOOK_RX_OP_MODIFY,
|
||||
HOOK_RX_OP_DROP,
|
||||
HOOK_RX_OP_ABORT,
|
||||
} hook_rx_op_t;
|
||||
|
||||
typedef enum {
|
||||
HOOK_DIR_NONE = 0,
|
||||
HOOK_DIR_TX,
|
||||
HOOK_DIR_RX,
|
||||
} hook_dir_t;
|
||||
|
||||
typedef struct {
|
||||
hook_tx_op_t op;
|
||||
uint32_t hook_calls;
|
||||
uint32_t post_hook_calls;
|
||||
uint32_t rx_strip_calls; /*!< PREFIX_HEAD companion strips on loopback */
|
||||
void *freed_ptr;
|
||||
} hooks_tx_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
hook_rx_op_t op;
|
||||
uint32_t hook_calls;
|
||||
} hooks_rx_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
hook_dir_t dir;
|
||||
union {
|
||||
hooks_tx_ctx_t tx;
|
||||
hooks_rx_ctx_t rx;
|
||||
};
|
||||
} hooks_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
struct eth_hdr header;
|
||||
uint8_t marker;
|
||||
uint8_t pad[HOOK_TEST_FRAME_LEN - ETH_HEADER_LEN - 1];
|
||||
} __attribute__((packed)) hook_test_frame_t;
|
||||
|
||||
_Static_assert(sizeof(hook_test_frame_t) == HOOK_TEST_FRAME_LEN,
|
||||
"hook_test_frame_t must be HOOK_TEST_FRAME_LEN bytes");
|
||||
|
||||
typedef struct {
|
||||
sublayer_test_ctx_t base;
|
||||
esp_eth_sublayer_vlan_handle_t vlan_untagged;
|
||||
esp_eth_sublayer_vlan_handle_t vlan_tagged;
|
||||
hooks_ctx_t *hook_ctx;
|
||||
} hooks_test_ctx_t;
|
||||
|
||||
static hooks_ctx_t s_hooks_ctx;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Hook implementations */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* TX hook used by the sublayer hook tests. The active scenario is selected
|
||||
* via hooks_ctx_t::tx.op when dir == HOOK_DIR_TX.
|
||||
*
|
||||
* Scenarios exercised (see esp_eth_sublayer.h TX hook contract):
|
||||
* - HOOK_TX_OP_PREFIX: Modify buf pointers/len and increase buf_count —
|
||||
* insert a 4-byte prefix before payload; on untagged
|
||||
* frames split one descriptor into header + prefixed
|
||||
* payload (bufs[0]/bufs[1]); on VLAN-tagged frames
|
||||
* the sublayer already provides two descriptors.
|
||||
* - HOOK_TX_OP_PREFIX_HEAD: Insert a new custom leading bufs[0] (broadcast DA,
|
||||
* device SA, ETH_TYPE_HOOK_HEAD, magic payload), shift
|
||||
* original descriptors down and increase buf_count.
|
||||
* The RX hook strips that segment on loopback by
|
||||
* recognizing ETH_TYPE_HOOK_HEAD.
|
||||
* - HOOK_TX_OP_MERGE: Merge scattered segments into one contiguous bufs[0]
|
||||
* (synthetic split for untagged, native VLAN split
|
||||
* for tagged); buf_count reduced to 1.
|
||||
* - HOOK_TX_OP_SKIP: Set buf_count to 0 — driver transmit and post_tx_hook
|
||||
* are skipped; tx_bufs remains caller-owned.
|
||||
* - HOOK_TX_OP_ABORT: Return non-ESP_OK — transmit aborted, post_tx_hook
|
||||
* not called.
|
||||
* - HOOK_TX_OP_POST_TX_FREE: Replace bufs[0] with a malloc'd copy so that
|
||||
* post_tx_hook can free hook-owned memory after TX.
|
||||
*/
|
||||
static void test_fill_prefix_magics(uint8_t *dst)
|
||||
{
|
||||
dst[0] = TX_PREFIX_MAGIC_0;
|
||||
dst[1] = TX_PREFIX_MAGIC_1;
|
||||
dst[2] = TX_PREFIX_MAGIC_2;
|
||||
dst[3] = TX_PREFIX_MAGIC_3;
|
||||
}
|
||||
|
||||
static bool test_prefix_magics_match(const uint8_t *src)
|
||||
{
|
||||
uint8_t expected[TX_PREFIX_LEN];
|
||||
|
||||
test_fill_prefix_magics(expected);
|
||||
return memcmp(src, expected, TX_PREFIX_LEN) == 0;
|
||||
}
|
||||
|
||||
static esp_err_t test_tx_hook(esp_eth_handle_t eth, esp_eth_sublayer_tx_bufs_t *tx_bufs, void *ctx)
|
||||
{
|
||||
hooks_ctx_t *hctx = (hooks_ctx_t *)ctx;
|
||||
|
||||
if (hctx->dir != HOOK_DIR_TX) {
|
||||
return ESP_OK;
|
||||
}
|
||||
hctx->tx.hook_calls++;
|
||||
|
||||
esp_eth_buf_desc_t *bufs = tx_bufs->bufs;
|
||||
size_t *buf_count = tx_bufs->buf_count;
|
||||
size_t buf_capacity = tx_bufs->buf_capacity;
|
||||
|
||||
switch (hctx->tx.op) {
|
||||
case HOOK_TX_OP_PREFIX: {
|
||||
/* Change buf pointers/len and bump buf_count (untagged) or edit bufs[1] (tagged). */
|
||||
if (*buf_count < 1 || buf_capacity < 2) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
if (*buf_count >= 2 && bufs[0].len == ETH_HEADER_LEN + ETH_VLAN_TAG_LEN) {
|
||||
/* Tagged VLAN path: header already in bufs[0], prefix payload in bufs[1]. */
|
||||
size_t payload_len = bufs[1].len;
|
||||
uint8_t *prefixed = malloc(TX_PREFIX_LEN + payload_len);
|
||||
if (prefixed == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
test_fill_prefix_magics(prefixed);
|
||||
memcpy(prefixed + TX_PREFIX_LEN, bufs[1].buf, payload_len);
|
||||
bufs[1] = (esp_eth_buf_desc_t){ .buf = prefixed, .len = TX_PREFIX_LEN + payload_len };
|
||||
return ESP_OK;
|
||||
}
|
||||
/* Untagged path: single descriptor — shrink bufs[0] to header, add prefixed bufs[1]. */
|
||||
size_t payload_len = bufs[0].len - ETH_HEADER_LEN;
|
||||
uint8_t *prefixed = malloc(TX_PREFIX_LEN + payload_len);
|
||||
if (prefixed == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
test_fill_prefix_magics(prefixed);
|
||||
memcpy(prefixed + TX_PREFIX_LEN, bufs[0].buf + ETH_HEADER_LEN, payload_len);
|
||||
bufs[0].len = ETH_HEADER_LEN;
|
||||
bufs[1] = (esp_eth_buf_desc_t){ .buf = prefixed, .len = TX_PREFIX_LEN + payload_len };
|
||||
*buf_count = 2;
|
||||
return ESP_OK;
|
||||
}
|
||||
case HOOK_TX_OP_PREFIX_HEAD: {
|
||||
/*
|
||||
* Insert a standalone leading Ethernet frame at bufs[0] (any ethertype
|
||||
* is fine on TX). Shift the original frame descriptors down and bump
|
||||
* buf_count. RX hook strips this segment before VLAN demux / L2TAP.
|
||||
*/
|
||||
if (*buf_count < 1 || buf_capacity < *buf_count + 1) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
uint8_t *head = malloc(TX_PREFIX_HEAD_SEG_LEN);
|
||||
if (head == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memset(head, 0xFF, ETH_ADDR_LEN);
|
||||
esp_eth_ioctl(eth, ETH_CMD_G_MAC_ADDR, head + ETH_ADDR_LEN);
|
||||
head[ETH_HEADER_LEN - sizeof(uint16_t)] = (uint8_t)(ETH_TYPE_HOOK_HEAD >> 8);
|
||||
head[ETH_HEADER_LEN - sizeof(uint16_t) + 1] = (uint8_t)ETH_TYPE_HOOK_HEAD;
|
||||
test_fill_prefix_magics(head + ETH_HEADER_LEN);
|
||||
for (size_t i = *buf_count; i > 0; i--) {
|
||||
bufs[i] = bufs[i - 1];
|
||||
}
|
||||
bufs[0] = (esp_eth_buf_desc_t){ .buf = head, .len = TX_PREFIX_HEAD_SEG_LEN };
|
||||
*buf_count += 1;
|
||||
return ESP_OK;
|
||||
}
|
||||
case HOOK_TX_OP_MERGE: {
|
||||
/* Merge scattered segments into one malloc'd bufs[0]; reduce buf_count to 1. */
|
||||
if (*buf_count == 1 && bufs[0].len > ETH_HEADER_LEN) {
|
||||
bufs[1] = (esp_eth_buf_desc_t){
|
||||
.buf = bufs[0].buf + ETH_HEADER_LEN,
|
||||
.len = bufs[0].len - ETH_HEADER_LEN,
|
||||
};
|
||||
bufs[0].len = ETH_HEADER_LEN;
|
||||
*buf_count = 2;
|
||||
}
|
||||
if (*buf_count < 2) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
size_t total = 0;
|
||||
for (size_t i = 0; i < *buf_count; i++) {
|
||||
total += bufs[i].len;
|
||||
}
|
||||
uint8_t *merged = malloc(total);
|
||||
if (merged == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
size_t off = 0;
|
||||
for (size_t i = 0; i < *buf_count; i++) {
|
||||
memcpy(merged + off, bufs[i].buf, bufs[i].len);
|
||||
off += bufs[i].len;
|
||||
}
|
||||
bufs[0] = (esp_eth_buf_desc_t){ .buf = merged, .len = total };
|
||||
*buf_count = 1;
|
||||
return ESP_OK;
|
||||
}
|
||||
case HOOK_TX_OP_SKIP:
|
||||
/* buf_count = 0: skip driver TX and post_tx_hook; tx_bufs stays caller-owned. */
|
||||
*buf_count = 0;
|
||||
return ESP_OK;
|
||||
case HOOK_TX_OP_ABORT:
|
||||
/* Non-ESP_OK: abort transmit for this packet; post_tx_hook not called. */
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
case HOOK_TX_OP_POST_TX_FREE:
|
||||
/* Allocate replacement buffer; post_tx_hook must free bufs[0] after TX. */
|
||||
if (*buf_count >= 1) {
|
||||
uint8_t *copy = malloc(bufs[0].len);
|
||||
if (copy == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memcpy(copy, bufs[0].buf, bufs[0].len);
|
||||
copy[ETH_HEADER_LEN + 1] ^= 0x01;
|
||||
bufs[0].buf = copy;
|
||||
}
|
||||
return ESP_OK;
|
||||
default:
|
||||
return ESP_OK;
|
||||
}
|
||||
(void)eth;
|
||||
}
|
||||
|
||||
static void test_post_tx_hook(esp_eth_handle_t eth, const esp_eth_sublayer_tx_bufs_t *tx_bufs, void *ctx)
|
||||
{
|
||||
hooks_ctx_t *hctx = (hooks_ctx_t *)ctx;
|
||||
|
||||
if (hctx->dir != HOOK_DIR_TX) {
|
||||
return;
|
||||
}
|
||||
hctx->tx.post_hook_calls++;
|
||||
|
||||
esp_eth_buf_desc_t *bufs = tx_bufs->bufs;
|
||||
size_t buf_count = *tx_bufs->buf_count;
|
||||
|
||||
switch (hctx->tx.op) {
|
||||
case HOOK_TX_OP_PREFIX:
|
||||
if (buf_count >= 2) {
|
||||
hctx->tx.freed_ptr = bufs[1].buf;
|
||||
free(bufs[1].buf);
|
||||
}
|
||||
break;
|
||||
case HOOK_TX_OP_PREFIX_HEAD:
|
||||
if (buf_count >= 1) {
|
||||
hctx->tx.freed_ptr = bufs[0].buf;
|
||||
free(bufs[0].buf);
|
||||
}
|
||||
break;
|
||||
case HOOK_TX_OP_MERGE:
|
||||
case HOOK_TX_OP_POST_TX_FREE:
|
||||
if (buf_count >= 1) {
|
||||
hctx->tx.freed_ptr = bufs[0].buf;
|
||||
free(bufs[0].buf);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
(void)eth;
|
||||
}
|
||||
|
||||
static esp_err_t test_rx_hook(esp_eth_handle_t eth, uint8_t **buf, uint32_t *len,
|
||||
esp_eth_sublayer_rx_info_t *info, void *ctx)
|
||||
{
|
||||
hooks_ctx_t *hctx = (hooks_ctx_t *)ctx;
|
||||
|
||||
if (hctx->dir == HOOK_DIR_TX) {
|
||||
/*
|
||||
* TX PREFIX_HEAD companion: strip the custom leading segment on loopback
|
||||
* before VLAN demux / L2TAP. Counted on the TX context because the op
|
||||
* union holds tx while dir == HOOK_DIR_TX.
|
||||
*/
|
||||
if (*len >= TX_PREFIX_HEAD_SEG_LEN) {
|
||||
uint16_t etype = (uint16_t)(((*buf)[12] << 8) | (*buf)[13]);
|
||||
if (etype == ETH_TYPE_HOOK_HEAD) {
|
||||
if (!test_prefix_magics_match(*buf + ETH_HEADER_LEN)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
hctx->tx.rx_strip_calls++;
|
||||
*buf += TX_PREFIX_HEAD_SEG_LEN;
|
||||
*len -= TX_PREFIX_HEAD_SEG_LEN;
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (hctx->dir != HOOK_DIR_RX) {
|
||||
return ESP_OK;
|
||||
}
|
||||
hctx->rx.hook_calls++;
|
||||
|
||||
switch (hctx->rx.op) {
|
||||
case HOOK_RX_OP_MODIFY:
|
||||
if (*len > ETH_HEADER_LEN + 1) {
|
||||
size_t marker_off = ETH_HEADER_LEN;
|
||||
if (*len > ETH_HEADER_LEN + ETH_VLAN_TAG_LEN + 1 &&
|
||||
((uint16_t)(((*buf)[12] << 8) | (*buf)[13]) == ETH_T_8021Q)) {
|
||||
marker_off = ETH_HEADER_LEN + ETH_VLAN_TAG_LEN;
|
||||
}
|
||||
if (marker_off + 1 < *len && (*buf)[marker_off] == FRAME_MARKER_TX) {
|
||||
(*buf)[marker_off] = FRAME_MARKER_RX;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
case HOOK_RX_OP_DROP:
|
||||
*len = 0;
|
||||
// rx hook took the buffer, so we need to free it
|
||||
free(info->l2_buffer);
|
||||
info->l2_buffer = NULL;
|
||||
return ESP_OK;
|
||||
case HOOK_RX_OP_ABORT:
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
default:
|
||||
return ESP_OK;
|
||||
}
|
||||
(void)eth;
|
||||
(void)info;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int l2tap_open_filtered(const char *if_key, uint16_t ethertype_filter)
|
||||
{
|
||||
int fd = open(L2TAP_VFS_DEFAULT_PATH, O_NONBLOCK);
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(-1, fd, "failed to open L2TAP device");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(-1, ioctl(fd, L2TAP_S_INTF_DEVICE, if_key),
|
||||
"failed to bind L2TAP fd to interface");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(-1, ioctl(fd, L2TAP_S_RCV_FILTER, ðertype_filter),
|
||||
"failed to set L2TAP ethertype filter");
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int l2tap_open_tx(const char *if_key)
|
||||
{
|
||||
int fd = open(L2TAP_VFS_DEFAULT_PATH, O_NONBLOCK);
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(-1, fd, "failed to open L2TAP device");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(-1, ioctl(fd, L2TAP_S_INTF_DEVICE, if_key),
|
||||
"failed to bind L2TAP fd to interface");
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void test_build_frame(hook_test_frame_t *frame, esp_eth_handle_t eth,
|
||||
uint16_t ethertype, uint8_t marker)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
esp_eth_ioctl(eth, ETH_CMD_G_MAC_ADDR, frame->header.src.addr);
|
||||
memcpy(frame->header.dest.addr, frame->header.src.addr, ETH_ADDR_LEN);
|
||||
frame->header.type = htons(ethertype);
|
||||
frame->marker = marker;
|
||||
}
|
||||
|
||||
static void test_setup(hooks_test_ctx_t *ctx)
|
||||
{
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
memset(&s_hooks_ctx, 0, sizeof(s_hooks_ctx));
|
||||
ctx->hook_ctx = &s_hooks_ctx;
|
||||
|
||||
sublayer_test_create_event_loop(&ctx->base);
|
||||
sublayer_test_init_ethernet(&ctx->base);
|
||||
sublayer_test_register_l2tap(&ctx->base);
|
||||
sublayer_test_register_ctx(&ctx->base);
|
||||
|
||||
esp_eth_sublayer_config_t sub_cfg = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
sub_cfg.eth_handle = ctx->base.eth_handles[0];
|
||||
sub_cfg.tx_hook = test_tx_hook;
|
||||
sub_cfg.post_tx_hook = test_post_tx_hook;
|
||||
sub_cfg.rx_hook = test_rx_hook;
|
||||
sub_cfg.hook_ctx = ctx->hook_ctx;
|
||||
TEST_ESP_OK(esp_eth_sublayer_new(&sub_cfg, &ctx->base.sub));
|
||||
|
||||
TEST_ESP_OK(esp_eth_sublayer_vlan_add(ctx->base.sub, ESP_ETH_SUBLAYER_UNTAGGED_VID, &ctx->vlan_untagged));
|
||||
TEST_ESP_OK(esp_eth_sublayer_vlan_add(ctx->base.sub, TEST_VLAN_ID, &ctx->vlan_tagged));
|
||||
|
||||
esp_netif_t *netif_untagged = sublayer_test_create_eth_netif(&ctx->base, IFKEY_UNTAGGED, "eth");
|
||||
esp_netif_t *netif_vlan = sublayer_test_create_eth_netif(&ctx->base, IFKEY_VLAN, "eth_vlan");
|
||||
|
||||
TEST_ESP_OK(esp_netif_attach(netif_untagged, ctx->vlan_untagged));
|
||||
TEST_ESP_OK(esp_netif_attach(netif_vlan, ctx->vlan_tagged));
|
||||
|
||||
sublayer_test_start_and_wait_connect(&ctx->base);
|
||||
}
|
||||
|
||||
static void test_teardown(hooks_test_ctx_t *ctx)
|
||||
{
|
||||
sublayer_test_teardown(&ctx->base);
|
||||
sublayer_test_unregister_ctx();
|
||||
}
|
||||
|
||||
static bool verify_loopback_frame(const uint8_t *rx_buf, size_t rx_len,
|
||||
const hook_test_frame_t *tx_frame)
|
||||
{
|
||||
return rx_len >= sizeof(*tx_frame) &&
|
||||
memcmp(rx_buf, tx_frame, sizeof(*tx_frame)) == 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *if_key;
|
||||
const hook_test_frame_t *frame;
|
||||
} l2tap_send_arg_t;
|
||||
|
||||
static void l2tap_send_task(void *arg)
|
||||
{
|
||||
l2tap_send_arg_t *send_arg = (l2tap_send_arg_t *)arg;
|
||||
vTaskDelay(pdMS_TO_TICKS(L2TAP_SEND_DELAY_MS));
|
||||
int tx_fd = l2tap_open_tx(send_arg->if_key);
|
||||
ssize_t n = write(tx_fd, send_arg->frame, sizeof(*send_arg->frame));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, n);
|
||||
close(tx_fd);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static bool l2tap_wait_for_frame_timeout(int fd, uint8_t *rx_buf, size_t rx_buf_size,
|
||||
size_t *rx_len, int timeout_ms)
|
||||
{
|
||||
struct timeval tv = {
|
||||
.tv_sec = timeout_ms / 1000,
|
||||
.tv_usec = (timeout_ms % 1000) * 1000,
|
||||
};
|
||||
fd_set rfds;
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(fd, &rfds);
|
||||
|
||||
int ret = select(fd + 1, &rfds, NULL, NULL, &tv);
|
||||
if (ret <= 0 || !FD_ISSET(fd, &rfds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t n = read(fd, rx_buf, rx_buf_size);
|
||||
if (n <= 0) {
|
||||
return false;
|
||||
}
|
||||
*rx_len = (size_t)n;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool l2tap_wait_for_frame(int fd, uint8_t *rx_buf, size_t rx_buf_size, size_t *rx_len)
|
||||
{
|
||||
return l2tap_wait_for_frame_timeout(fd, rx_buf, rx_buf_size, rx_len, L2TAP_IO_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
static bool verify_frame_prefix(const uint8_t *rx_buf, size_t rx_len,
|
||||
const hook_test_frame_t *tx_frame)
|
||||
{
|
||||
size_t expected_len = sizeof(*tx_frame) + TX_PREFIX_LEN;
|
||||
if (rx_len < expected_len) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(rx_buf, tx_frame, ETH_HEADER_LEN) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (!test_prefix_magics_match(rx_buf + ETH_HEADER_LEN)) {
|
||||
return false;
|
||||
}
|
||||
return memcmp(rx_buf + ETH_HEADER_LEN + TX_PREFIX_LEN,
|
||||
((const uint8_t *)tx_frame) + ETH_HEADER_LEN,
|
||||
sizeof(*tx_frame) - ETH_HEADER_LEN) == 0;
|
||||
}
|
||||
|
||||
/* Send a frame and verify the TX PREFIX_HEAD rewrite (new leading buf) is
|
||||
* delivered intact to L2TAP via EMAC loopback. */
|
||||
static void test_tx_prefix_head_on_interface(hooks_test_ctx_t *ctx, const char *if_key,
|
||||
uint16_t ethertype)
|
||||
{
|
||||
hook_test_frame_t tx_frame;
|
||||
test_build_frame(&tx_frame, ctx->base.eth_handles[0], ethertype, FRAME_MARKER_TX);
|
||||
|
||||
int rx_fd = l2tap_open_filtered(if_key, ethertype);
|
||||
l2tap_send_arg_t send_arg = { .if_key = if_key, .frame = &tx_frame };
|
||||
xTaskCreate(l2tap_send_task, "l2tap_send", 2048, &send_arg, tskIDLE_PRIORITY + 2, NULL);
|
||||
|
||||
uint8_t rx_buf[HOOK_TEST_RX_BUF_LEN];
|
||||
size_t rx_len = 0;
|
||||
bool got_frame = l2tap_wait_for_frame(rx_fd, rx_buf, sizeof(rx_buf), &rx_len);
|
||||
bool frame_ok = got_frame && verify_loopback_frame(rx_buf, rx_len, &tx_frame);
|
||||
close(rx_fd);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
TEST_ASSERT_TRUE(got_frame);
|
||||
TEST_ASSERT_TRUE(frame_ok);
|
||||
}
|
||||
|
||||
/* Send a frame and verify the TX PREFIX rewrite (extra segment + ethertype
|
||||
* tweak) is delivered to L2TAP via EMAC loopback. */
|
||||
static void test_tx_prefix_on_interface(hooks_test_ctx_t *ctx, const char *if_key,
|
||||
uint16_t ethertype)
|
||||
{
|
||||
hook_test_frame_t tx_frame;
|
||||
test_build_frame(&tx_frame, ctx->base.eth_handles[0], ethertype, FRAME_MARKER_TX);
|
||||
|
||||
int rx_fd = l2tap_open_filtered(if_key, ethertype);
|
||||
l2tap_send_arg_t send_arg = { .if_key = if_key, .frame = &tx_frame };
|
||||
xTaskCreate(l2tap_send_task, "l2tap_send", 2048, &send_arg, tskIDLE_PRIORITY + 2, NULL);
|
||||
|
||||
uint8_t rx_buf[HOOK_TEST_RX_BUF_LEN];
|
||||
size_t rx_len = 0;
|
||||
bool got_frame = l2tap_wait_for_frame(rx_fd, rx_buf, sizeof(rx_buf), &rx_len);
|
||||
bool frame_ok = got_frame && verify_frame_prefix(rx_buf, rx_len, &tx_frame);
|
||||
close(rx_fd);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
TEST_ASSERT_TRUE(got_frame);
|
||||
TEST_ASSERT_TRUE(frame_ok);
|
||||
}
|
||||
|
||||
/* Send a VLAN frame and verify the TX MERGE rewrite (contiguous buffer) is
|
||||
* delivered to L2TAP via EMAC loopback. */
|
||||
static void test_tx_merge_on_vlan(hooks_test_ctx_t *ctx)
|
||||
{
|
||||
hook_test_frame_t tx_frame;
|
||||
test_build_frame(&tx_frame, ctx->base.eth_handles[0], ETH_TYPE_HOOK_VLAN, FRAME_MARKER_TX);
|
||||
|
||||
int rx_fd = l2tap_open_filtered(IFKEY_VLAN, ETH_TYPE_HOOK_VLAN);
|
||||
l2tap_send_arg_t send_arg = { .if_key = IFKEY_VLAN, .frame = &tx_frame };
|
||||
xTaskCreate(l2tap_send_task, "l2tap_send", 2048, &send_arg, tskIDLE_PRIORITY + 2, NULL);
|
||||
|
||||
uint8_t rx_buf[HOOK_TEST_RX_BUF_LEN];
|
||||
size_t rx_len = 0;
|
||||
bool got_frame = l2tap_wait_for_frame(rx_fd, rx_buf, sizeof(rx_buf), &rx_len);
|
||||
bool frame_ok = got_frame && verify_loopback_frame(rx_buf, rx_len, &tx_frame);
|
||||
close(rx_fd);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
TEST_ASSERT_TRUE(got_frame);
|
||||
TEST_ASSERT_TRUE(frame_ok);
|
||||
}
|
||||
|
||||
/* Send a frame on if_key and assert L2TAP never receives it.
|
||||
* Used when a TX hook skips/aborts transmit, or an RX hook drops/aborts the
|
||||
* frame before delivery. expect_write_error selects whether write() itself
|
||||
* must fail (TX abort) or succeed (skip / RX drop-or-abort). */
|
||||
static void test_expect_no_frame_on_interface(hooks_test_ctx_t *ctx, const char *if_key,
|
||||
uint16_t ethertype, bool expect_write_error)
|
||||
{
|
||||
hook_test_frame_t tx_frame;
|
||||
test_build_frame(&tx_frame, ctx->base.eth_handles[0], ethertype, FRAME_MARKER_TX);
|
||||
|
||||
int rx_fd = l2tap_open_filtered(if_key, ethertype);
|
||||
int tx_fd = l2tap_open_tx(if_key);
|
||||
ssize_t wr = write(tx_fd, &tx_frame, sizeof(tx_frame));
|
||||
close(tx_fd);
|
||||
if (expect_write_error) {
|
||||
TEST_ASSERT_EQUAL(-1, wr);
|
||||
} else {
|
||||
TEST_ASSERT_NOT_EQUAL(-1, wr);
|
||||
}
|
||||
|
||||
uint8_t rx_buf[HOOK_TEST_RX_BUF_LEN];
|
||||
size_t rx_len = 0;
|
||||
bool got_frame = l2tap_wait_for_frame_timeout(rx_fd, rx_buf, sizeof(rx_buf), &rx_len, 1500);
|
||||
close(rx_fd);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
TEST_ASSERT_FALSE(got_frame);
|
||||
(void)ctx;
|
||||
}
|
||||
|
||||
/* Send a frame and verify the RX hook rewrote the payload marker
|
||||
* (FRAME_MARKER_TX -> FRAME_MARKER_RX) before L2TAP delivery. */
|
||||
static void test_rx_modify_on_interface(hooks_test_ctx_t *ctx, const char *if_key,
|
||||
uint16_t ethertype)
|
||||
{
|
||||
hook_test_frame_t tx_frame;
|
||||
test_build_frame(&tx_frame, ctx->base.eth_handles[0], ethertype, FRAME_MARKER_TX);
|
||||
|
||||
int rx_fd = l2tap_open_filtered(if_key, ethertype);
|
||||
l2tap_send_arg_t send_arg = { .if_key = if_key, .frame = &tx_frame };
|
||||
xTaskCreate(l2tap_send_task, "l2tap_send", 2048, &send_arg, tskIDLE_PRIORITY + 2, NULL);
|
||||
|
||||
uint8_t rx_buf[HOOK_TEST_RX_BUF_LEN];
|
||||
size_t rx_len = 0;
|
||||
bool got_frame = l2tap_wait_for_frame(rx_fd, rx_buf, sizeof(rx_buf), &rx_len);
|
||||
bool marker_ok = got_frame && rx_len >= ETH_HEADER_LEN + 1 &&
|
||||
rx_buf[ETH_HEADER_LEN] == FRAME_MARKER_RX;
|
||||
close(rx_fd);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
TEST_ASSERT_TRUE(got_frame);
|
||||
TEST_ASSERT_TRUE(marker_ok);
|
||||
}
|
||||
|
||||
static void test_set_tx_op(hooks_test_ctx_t *ctx, hook_tx_op_t tx_op)
|
||||
{
|
||||
memset(&s_hooks_ctx, 0, sizeof(s_hooks_ctx));
|
||||
s_hooks_ctx.dir = HOOK_DIR_TX;
|
||||
s_hooks_ctx.tx.op = tx_op;
|
||||
ctx->hook_ctx = &s_hooks_ctx;
|
||||
}
|
||||
|
||||
static void test_set_rx_op(hooks_test_ctx_t *ctx, hook_rx_op_t rx_op)
|
||||
{
|
||||
memset(&s_hooks_ctx, 0, sizeof(s_hooks_ctx));
|
||||
s_hooks_ctx.dir = HOOK_DIR_RX;
|
||||
s_hooks_ctx.rx.op = rx_op;
|
||||
ctx->hook_ctx = &s_hooks_ctx;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test cases */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
TEST_CASE("sublayer tx and post tx hooks", "[sublayer_hooks]")
|
||||
{
|
||||
hooks_test_ctx_t ctx;
|
||||
test_setup(&ctx);
|
||||
test_set_tx_op(&ctx, HOOK_TX_OP_PREFIX);
|
||||
|
||||
ESP_LOGI(TAG, "TX hook: insert prefix, move frame to bufs[1]");
|
||||
test_tx_prefix_on_interface(&ctx, IFKEY_UNTAGGED, ETH_TYPE_HOOK_UNTAGGED);
|
||||
test_tx_prefix_on_interface(&ctx, IFKEY_VLAN, ETH_TYPE_HOOK_VLAN);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.hook_calls);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.post_hook_calls);
|
||||
|
||||
ESP_LOGI(TAG, "TX hook: insert custom leading frame + reorder; RX hook strips on loopback");
|
||||
test_set_tx_op(&ctx, HOOK_TX_OP_PREFIX_HEAD);
|
||||
test_tx_prefix_head_on_interface(&ctx, IFKEY_UNTAGGED, ETH_TYPE_HOOK_UNTAGGED);
|
||||
test_tx_prefix_head_on_interface(&ctx, IFKEY_VLAN, ETH_TYPE_HOOK_VLAN);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.hook_calls);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.rx_strip_calls);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.post_hook_calls);
|
||||
|
||||
ESP_LOGI(TAG, "TX hook: merge tagged header and payload into one buffer");
|
||||
test_set_tx_op(&ctx, HOOK_TX_OP_MERGE);
|
||||
test_tx_merge_on_vlan(&ctx);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.hook_calls);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.post_hook_calls);
|
||||
|
||||
ESP_LOGI(TAG, "TX hook: buf_count=0 takes ownership, no loopback frame");
|
||||
test_set_tx_op(&ctx, HOOK_TX_OP_SKIP);
|
||||
test_expect_no_frame_on_interface(&ctx, IFKEY_UNTAGGED, ETH_TYPE_HOOK_UNTAGGED, false);
|
||||
test_expect_no_frame_on_interface(&ctx, IFKEY_VLAN, ETH_TYPE_HOOK_VLAN, false);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.hook_calls);
|
||||
TEST_ASSERT_EQUAL(0, ctx.hook_ctx->tx.post_hook_calls);
|
||||
|
||||
ESP_LOGI(TAG, "TX hook: non-ESP_OK aborts transmit");
|
||||
test_set_tx_op(&ctx, HOOK_TX_OP_ABORT);
|
||||
test_expect_no_frame_on_interface(&ctx, IFKEY_UNTAGGED, ETH_TYPE_HOOK_UNTAGGED, true);
|
||||
test_expect_no_frame_on_interface(&ctx, IFKEY_VLAN, ETH_TYPE_HOOK_VLAN, true);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.hook_calls);
|
||||
TEST_ASSERT_EQUAL(0, ctx.hook_ctx->tx.post_hook_calls);
|
||||
|
||||
ESP_LOGI(TAG, "post_tx_hook frees buffer allocated in tx hook");
|
||||
test_set_tx_op(&ctx, HOOK_TX_OP_POST_TX_FREE);
|
||||
hook_test_frame_t tx_frame;
|
||||
test_build_frame(&tx_frame, ctx.base.eth_handles[0], ETH_TYPE_HOOK_UNTAGGED, FRAME_MARKER_TX);
|
||||
int rx_fd = l2tap_open_filtered(IFKEY_UNTAGGED, ETH_TYPE_HOOK_UNTAGGED);
|
||||
l2tap_send_arg_t send_arg = { .if_key = IFKEY_UNTAGGED, .frame = &tx_frame };
|
||||
xTaskCreate(l2tap_send_task, "l2tap_send", 2048, &send_arg, tskIDLE_PRIORITY + 2, NULL);
|
||||
uint8_t rx_buf[HOOK_TEST_RX_BUF_LEN];
|
||||
size_t rx_len = 0;
|
||||
bool got_frame = l2tap_wait_for_frame(rx_fd, rx_buf, sizeof(rx_buf), &rx_len);
|
||||
close(rx_fd);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
TEST_ASSERT_TRUE(got_frame);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->tx.post_hook_calls);
|
||||
TEST_ASSERT_NOT_NULL(ctx.hook_ctx->tx.freed_ptr);
|
||||
|
||||
test_teardown(&ctx);
|
||||
}
|
||||
|
||||
TEST_CASE("sublayer rx hooks", "[sublayer_hooks]")
|
||||
{
|
||||
hooks_test_ctx_t ctx;
|
||||
test_setup(&ctx);
|
||||
test_set_rx_op(&ctx, HOOK_RX_OP_MODIFY);
|
||||
|
||||
ESP_LOGI(TAG, "RX hook: modify payload before VLAN demux / L2TAP delivery");
|
||||
test_rx_modify_on_interface(&ctx, IFKEY_UNTAGGED, ETH_TYPE_HOOK_UNTAGGED);
|
||||
test_rx_modify_on_interface(&ctx, IFKEY_VLAN, ETH_TYPE_HOOK_VLAN);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->rx.hook_calls);
|
||||
|
||||
ESP_LOGI(TAG, "RX hook: len=0 drops frame on untagged and tagged");
|
||||
test_set_rx_op(&ctx, HOOK_RX_OP_DROP);
|
||||
test_expect_no_frame_on_interface(&ctx, IFKEY_UNTAGGED, ETH_TYPE_HOOK_UNTAGGED, false);
|
||||
test_expect_no_frame_on_interface(&ctx, IFKEY_VLAN, ETH_TYPE_HOOK_VLAN, false);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->rx.hook_calls);
|
||||
|
||||
ESP_LOGI(TAG, "RX hook: non-ESP_OK aborts processing");
|
||||
test_set_rx_op(&ctx, HOOK_RX_OP_ABORT);
|
||||
test_expect_no_frame_on_interface(&ctx, IFKEY_UNTAGGED, ETH_TYPE_HOOK_UNTAGGED, false);
|
||||
test_expect_no_frame_on_interface(&ctx, IFKEY_VLAN, ETH_TYPE_HOOK_VLAN, false);
|
||||
TEST_ASSERT_GREATER_THAN(0, ctx.hook_ctx->rx.hook_calls);
|
||||
|
||||
test_teardown(&ctx);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.eth_ip101
|
||||
@pytest.mark.parametrize('config', ['default_generic'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_sublayer_events(dut: IdfDut) -> None:
|
||||
dut.run_all_single_board_cases(group='sublayer_events', timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.eth_ip101
|
||||
@pytest.mark.parametrize('config', ['default_generic'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_sublayer_hooks(dut: IdfDut) -> None:
|
||||
dut.run_all_single_board_cases(group='sublayer_hooks', timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.eth_ip101
|
||||
@pytest.mark.parametrize('config', ['default_generic_esp32p4'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
|
||||
def test_sublayer_events_esp32p4(dut: IdfDut) -> None:
|
||||
dut.run_all_single_board_cases(group='sublayer_events', timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.eth_ip101
|
||||
@pytest.mark.parametrize('config', ['default_generic_esp32p4'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
|
||||
def test_sublayer_hooks_esp32p4(dut: IdfDut) -> None:
|
||||
dut.run_all_single_board_cases(group='sublayer_hooks', timeout=120)
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
@@ -0,0 +1,20 @@
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096
|
||||
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y
|
||||
CONFIG_ETH_USE_ESP32_EMAC=y
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
|
||||
# interface configuration
|
||||
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
|
||||
CONFIG_ETHERNET_PHY_GENERIC=y
|
||||
CONFIG_ETHERNET_DEFAULT_EVENT_HANDLER=n
|
||||
|
||||
CONFIG_ESP_NETIF_L2_TAP=y
|
||||
CONFIG_ETH_TRANSMIT_MUTEX=n
|
||||
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_ETH_SUBLAYER_SUPPORT=y
|
||||
CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX=y
|
||||
CONFIG_ETH_SUBLAYER_TX_BUF_DESC_CAPACITY=3
|
||||
CONFIG_ETH_SUBLAYER_VLAN_SUPPORT=y
|
||||
@@ -66,7 +66,6 @@ menu "ESP NETIF Adapter"
|
||||
|
||||
config ESP_NETIF_L2_TAP
|
||||
bool "Enable netif L2 TAP support"
|
||||
select ETH_TRANSMIT_MUTEX
|
||||
help
|
||||
A user program can read/write link layer (L2) frames from/to ESP TAP device.
|
||||
The ESP TAP device can be currently associated only with Ethernet physical interfaces.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdalign.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
|
||||
@@ -20,7 +21,12 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The IO driver / IO driver provider vtable types are a generic concept owned by esp_eth for example
|
||||
* (see esp_private/esp_eth_sublayer_iodriver.h).
|
||||
*/
|
||||
typedef void *l2tap_iodriver_handle;
|
||||
typedef void *l2tap_iodriver_provider_handle;
|
||||
|
||||
/**
|
||||
* @brief L2Tap VFS config parameters
|
||||
@@ -30,6 +36,10 @@ typedef struct {
|
||||
const char* base_path; /*!< vfs base path */
|
||||
} l2tap_vfs_config_t;
|
||||
|
||||
/**
|
||||
* @brief L2 TAP ioctl options
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
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. */
|
||||
@@ -40,6 +50,29 @@ typedef enum {
|
||||
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 Hardware timestamp for L2 TAP (seconds + nanoseconds).
|
||||
*
|
||||
* Used for RX metadata passed to esp_vfs_l2tap_eth_filter_frame() and for TX completion when
|
||||
* ``L2TAP_S_TIMESTAMP_EN`` is enabled.
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t sec; /*!< Seconds */
|
||||
uint32_t nsec; /*!< Nanoseconds (typically 0..999999999 for PTP-style HW time) */
|
||||
} l2tap_timestamp_t;
|
||||
|
||||
/**
|
||||
* @brief Extra information about received Ethernet frame
|
||||
*
|
||||
* Used for RX metadata passed to esp_vfs_l2tap_eth_filter_frame().
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
void *l2_buffer; /*!< Pointer to the starting address of the L2 buffer (optional) */
|
||||
l2tap_timestamp_t *hw_ts; /*!< Pointer to the hardware timestamp (optional) */
|
||||
} l2tap_eth_filter_info_t;
|
||||
|
||||
/**
|
||||
* @brief Information Record (IREC) Header Type indicates expected type of Header Data
|
||||
*
|
||||
@@ -121,11 +154,11 @@ 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
|
||||
* @param info optional per-frame metadata from the IO driver; may be NULL.
|
||||
* @return esp_err_t
|
||||
* - ESP_OK is always returned
|
||||
*/
|
||||
esp_err_t esp_vfs_l2tap_eth_filter_frame(l2tap_iodriver_handle driver_handle, void *buff, size_t *size, void *info);
|
||||
esp_err_t esp_vfs_l2tap_eth_filter_frame(l2tap_iodriver_handle driver_handle, void *buff, size_t *size, l2tap_eth_filter_info_t *info);
|
||||
|
||||
/**
|
||||
* @brief Wrapper over L2 TAP filter function to ensure backward compatibility.
|
||||
@@ -140,6 +173,33 @@ esp_err_t esp_vfs_l2tap_eth_filter_frame(l2tap_iodriver_handle driver_handle, vo
|
||||
*/
|
||||
#define esp_vfs_l2tap_eth_filter(drv_hndl, buf, size) esp_vfs_l2tap_eth_filter_frame(drv_hndl, buf, size, NULL)
|
||||
|
||||
/**
|
||||
* @brief Register an iodriver provider handle with L2 TAP.
|
||||
*
|
||||
* The registered handle becomes visible to all open L2 TAP file descriptors.
|
||||
* Safe to call from any task context. Must not be called from an ISR.
|
||||
*
|
||||
* @param provider Opaque iodriver provider handle (e.g. from esp_eth_sublayer_new()).
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if provider is NULL
|
||||
* - ESP_ERR_INVALID_STATE if provider is already registered
|
||||
* - ESP_ERR_NO_MEM if memory allocation failed
|
||||
*/
|
||||
esp_err_t esp_vfs_l2tap_iodriver_provider_register(l2tap_iodriver_provider_handle provider);
|
||||
|
||||
/**
|
||||
* @brief Unregister a previously registered iodriver provider handle from L2 TAP.
|
||||
*
|
||||
* Safe to call from any task context. Must not be called from an ISR.
|
||||
*
|
||||
* @param provider Handle to remove.
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if provider is NULL
|
||||
* - ESP_ERR_NOT_FOUND if provider was not registered
|
||||
*/
|
||||
esp_err_t esp_vfs_l2tap_iodriver_provider_unregister(l2tap_iodriver_provider_handle provider);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* SPDX-FileContributor: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileContributor: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*/
|
||||
/**
|
||||
* @file
|
||||
@@ -114,7 +114,7 @@ static err_t ethernet_low_level_output(struct netif *netif, struct pbuf *p)
|
||||
* @param h lwip network interface structure (struct netif) for this ethernetif
|
||||
* @param buffer ethernet buffer
|
||||
* @param len length of buffer
|
||||
* @param l2_buff Placeholder for a separate L2 buffer. Unused for ethernet interface
|
||||
* @param l2_buff Optional original allocation for RX path (when @a buffer is offset into it); if NULL, @a buffer is freed
|
||||
*/
|
||||
esp_err_t ethernetif_input(void *h, void *buffer, size_t len, void *l2_buff)
|
||||
{
|
||||
@@ -124,15 +124,15 @@ esp_err_t ethernetif_input(void *h, void *buffer, size_t len, void *l2_buff)
|
||||
|
||||
if (unlikely(buffer == NULL || !netif_is_up(netif))) {
|
||||
if (buffer) {
|
||||
esp_netif_free_rx_buffer(esp_netif, buffer);
|
||||
esp_netif_free_rx_buffer(esp_netif, l2_buff ? l2_buff : buffer);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* allocate custom pbuf to hold */
|
||||
p = esp_pbuf_allocate(esp_netif, buffer, len, buffer);
|
||||
p = esp_pbuf_allocate(esp_netif, buffer, len, l2_buff ? l2_buff : buffer);
|
||||
if (p == NULL) {
|
||||
esp_netif_free_rx_buffer(esp_netif, buffer);
|
||||
esp_netif_free_rx_buffer(esp_netif, l2_buff ? l2_buff : buffer);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
/* full packet send to tcpip_thread to process */
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
menu "L2 TAP VFS test app"
|
||||
|
||||
config L2TAP_TEST_USE_ETH_SUBLAYER
|
||||
bool "Use Ethernet netif sublayer instead of classic glue"
|
||||
default n
|
||||
depends on ETH_SUBLAYER_SUPPORT
|
||||
help
|
||||
When set, the test attaches esp-netif with esp_eth_sublayer_new() /
|
||||
esp_eth_sublayer_del() (802.1Q demux path). When unset, the classic
|
||||
esp_eth_new_netif_glue() / esp_eth_del_netif_glue() layer is used.
|
||||
|
||||
endmenu
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -52,8 +52,14 @@ typedef struct {
|
||||
esp_netif_t *eth_netif;
|
||||
esp_eth_mac_t *mac;
|
||||
esp_eth_phy_t *phy;
|
||||
void *glue;
|
||||
#ifdef CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER
|
||||
esp_eth_sublayer_handle_t eth_sub;
|
||||
esp_eth_sublayer_vlan_handle_t vlan_untagged;
|
||||
#else
|
||||
void *netif_io_driver;
|
||||
#endif
|
||||
esp_eth_handle_t eth_handle;
|
||||
EventGroupHandle_t eth_event_group;
|
||||
} test_vfs_eth_network_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -175,18 +181,24 @@ static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
|
||||
/**
|
||||
* @brief Initializes Ethernet interface and starts driver (internal EMAC & IP101 PHY)
|
||||
*
|
||||
* @param init_netif true to create and attach an esp_netif; false to skip netif (L2TAP via driver handle)
|
||||
*/
|
||||
static void ethernet_init(test_vfs_eth_network_t *network_hndls)
|
||||
static void ethernet_init(test_vfs_eth_network_t *network_hndls, bool init_netif)
|
||||
{
|
||||
EventBits_t bits = 0;
|
||||
EventGroupHandle_t eth_event_group = xEventGroupCreate();
|
||||
TEST_ASSERT(eth_event_group != NULL);
|
||||
|
||||
test_case_uses_tcpip();
|
||||
memset(network_hndls, 0, sizeof(*network_hndls));
|
||||
network_hndls->eth_event_group = eth_event_group;
|
||||
|
||||
TEST_ESP_OK(esp_event_loop_create_default());
|
||||
// create TCP/IP netif
|
||||
esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
network_hndls->eth_netif = esp_netif_new(&netif_cfg);
|
||||
if (init_netif) {
|
||||
test_case_uses_tcpip();
|
||||
// create TCP/IP netif
|
||||
esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
network_hndls->eth_netif = esp_netif_new(&netif_cfg);
|
||||
}
|
||||
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
|
||||
@@ -201,11 +213,30 @@ static void ethernet_init(test_vfs_eth_network_t *network_hndls)
|
||||
// install Ethernet driver
|
||||
TEST_ESP_OK(esp_eth_driver_install(ð_config, &network_hndls->eth_handle));
|
||||
// combine driver with netif
|
||||
network_hndls->glue = esp_eth_new_netif_glue(network_hndls->eth_handle);
|
||||
TEST_ESP_OK(esp_netif_attach(network_hndls->eth_netif, network_hndls->glue));
|
||||
#ifdef CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER
|
||||
ESP_LOGI(TAG, "Using Ethernet Sublayer%s", init_netif ? "" : " without esp_netif");
|
||||
esp_eth_sublayer_config_t sub_config = {
|
||||
.eth_handle = network_hndls->eth_handle,
|
||||
};
|
||||
TEST_ESP_OK(esp_eth_sublayer_new(&sub_config, &network_hndls->eth_sub));
|
||||
// Create untagged Ethernet interface
|
||||
TEST_ESP_OK(esp_eth_sublayer_vlan_add(network_hndls->eth_sub, ESP_ETH_SUBLAYER_UNTAGGED_VID,
|
||||
&network_hndls->vlan_untagged));
|
||||
if (init_netif) {
|
||||
TEST_ESP_OK(esp_netif_attach(network_hndls->eth_netif, network_hndls->vlan_untagged));
|
||||
}
|
||||
#else
|
||||
if (init_netif) {
|
||||
ESP_LOGI(TAG, "Using Ethernet Netif Glue");
|
||||
network_hndls->netif_io_driver = esp_eth_new_netif_glue(network_hndls->eth_handle);
|
||||
TEST_ESP_OK(esp_netif_attach(network_hndls->eth_netif, network_hndls->netif_io_driver));
|
||||
}
|
||||
#endif // CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER
|
||||
// register user defined event handlers
|
||||
TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, eth_event_group));
|
||||
TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, eth_event_group));
|
||||
if (init_netif) {
|
||||
TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, eth_event_group));
|
||||
}
|
||||
|
||||
// set PHY loopback mode
|
||||
bool loopback_en = true;
|
||||
@@ -227,13 +258,27 @@ static void ethernet_init(test_vfs_eth_network_t *network_hndls)
|
||||
static void ethernet_deinit(test_vfs_eth_network_t *network_hndls)
|
||||
{
|
||||
TEST_ESP_OK(esp_eth_stop(network_hndls->eth_handle));
|
||||
TEST_ESP_OK(esp_eth_del_netif_glue(network_hndls->glue));
|
||||
EventBits_t stop_bits = xEventGroupWaitBits(network_hndls->eth_event_group, ETH_STOP_BIT, pdTRUE, pdTRUE,
|
||||
pdMS_TO_TICKS(1000));
|
||||
TEST_ASSERT((stop_bits & ETH_STOP_BIT) == ETH_STOP_BIT);
|
||||
#ifdef CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER
|
||||
// delete sublayer, it also removes all VLANs
|
||||
TEST_ESP_OK(esp_eth_sublayer_del(network_hndls->eth_sub));
|
||||
#else
|
||||
if (network_hndls->netif_io_driver) {
|
||||
TEST_ESP_OK(esp_eth_del_netif_glue(network_hndls->netif_io_driver));
|
||||
}
|
||||
#endif // CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER
|
||||
esp_eth_driver_uninstall(network_hndls->eth_handle);
|
||||
TEST_ESP_OK(network_hndls->phy->del(network_hndls->phy));
|
||||
TEST_ESP_OK(network_hndls->mac->del(network_hndls->mac));
|
||||
TEST_ESP_OK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, got_ip_event_handler));
|
||||
if (network_hndls->eth_netif) {
|
||||
TEST_ESP_OK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, got_ip_event_handler));
|
||||
esp_netif_destroy(network_hndls->eth_netif);
|
||||
}
|
||||
TEST_ESP_OK(esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler));
|
||||
esp_netif_destroy(network_hndls->eth_netif);
|
||||
vEventGroupDelete(network_hndls->eth_event_group);
|
||||
network_hndls->eth_event_group = NULL;
|
||||
TEST_ESP_OK(esp_event_loop_delete_default());
|
||||
}
|
||||
|
||||
@@ -297,8 +342,6 @@ TEST_CASE("esp32 l2tap - vfs register", "[ethernet]")
|
||||
ESP_LOGI(TAG, "Verify that L2 TAP VFS can be registered only once...");
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_vfs_l2tap_intf_register(NULL));
|
||||
|
||||
//ethernet_init(ð_network_hndls);
|
||||
|
||||
eth_tap_fd = open("/dev/net/tap", O_NONBLOCK);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
|
||||
@@ -408,7 +451,7 @@ TEST_CASE("esp32 l2tap - open/close", "[ethernet]")
|
||||
test_vfs_eth_network_t eth_network_hndls;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
open_close_task_ctrl_t task_control;
|
||||
task_control.sem = xSemaphoreCreateBinary();
|
||||
@@ -498,7 +541,7 @@ TEST_CASE("esp32 l2tap - non blocking read", "[ethernet]")
|
||||
int loop_cnt = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
eth_tap_fd = open("/dev/net/tap", O_NONBLOCK);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
@@ -527,6 +570,7 @@ TEST_CASE("esp32 l2tap - non blocking read", "[ethernet]")
|
||||
|
||||
// Verify the read does not block
|
||||
while (loop_cnt < 100) {
|
||||
errno = 0;
|
||||
if ((n = read(eth_tap_fd, in_buffer, IN_BUFFER_SIZE)) > 0) {
|
||||
ESP_LOG_BUFFER_HEX(TAG, in_buffer, n);
|
||||
ESP_LOGI(TAG, "recv test string: %s", ((test_vfs_eth_tap_msg_t *)in_buffer)->str);
|
||||
@@ -594,8 +638,8 @@ TEST_CASE("esp32 l2tap - non blocking read", "[ethernet]")
|
||||
FD_SET(eth_tap_fd, &rfds);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, select(eth_tap_fd + 1, &rfds, NULL, NULL, &tv));
|
||||
TEST_ASSERT_EQUAL(EAGAIN, errno);
|
||||
|
||||
errno = 0;
|
||||
n = read(eth_tap_fd, in_buffer, IN_BUFFER_SIZE);
|
||||
TEST_ASSERT_EQUAL(EAGAIN, errno);
|
||||
TEST_ASSERT_EQUAL(-1, n);
|
||||
@@ -618,7 +662,7 @@ TEST_CASE("esp32 l2tap - blocking read", "[ethernet]")
|
||||
int loop_cnt = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
eth_tap_fd = open("/dev/net/tap", 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
@@ -678,7 +722,7 @@ TEST_CASE("esp32 l2tap - write", "[ethernet]")
|
||||
int eth_tap_fd;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
eth_tap_fd = open("/dev/net/tap", 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
@@ -703,6 +747,7 @@ TEST_CASE("esp32 l2tap - write", "[ethernet]")
|
||||
esp_eth_ioctl(eth_network_hndls.eth_handle, ETH_CMD_G_MAC_ADDR, &test_msg.header.dest.addr);
|
||||
// set different Ethernet type than the fd is configured to
|
||||
test_msg.header.type = htons(ETH_FILTER_LE + 10);
|
||||
errno = 0;
|
||||
TEST_ASSERT_EQUAL(-1, write(eth_tap_fd, &test_msg, sizeof(test_msg)));
|
||||
TEST_ASSERT_EQUAL(EBADMSG, errno);
|
||||
|
||||
@@ -797,7 +842,7 @@ TEST_CASE("esp32 l2tap - read/write multiple fd's used by multiple tasks", "[eth
|
||||
test_vfs_eth_network_t eth_network_hndls;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
task_info_t task_info[NUM_OF_TASKS];
|
||||
for (int i = 0; i < NUM_OF_TASKS; i++) {
|
||||
@@ -828,7 +873,7 @@ 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);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
int eth_tap_fd = open("/dev/net/tap", 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
@@ -904,9 +949,11 @@ TEST_CASE("esp32 l2tap - time stamping", "[ethernet]")
|
||||
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++;
|
||||
errno = 0;
|
||||
n = write(eth_tap_fd, &test_ptp_msg, sizeof(test_ptp_msg));
|
||||
TEST_ASSERT_EQUAL(-1, n);
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
errno = 0;
|
||||
n = read(eth_tap_fd, &in_buffer, sizeof(test_ptp_msg));
|
||||
TEST_ASSERT_EQUAL(-1, n);
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
@@ -985,6 +1032,7 @@ TEST_CASE("esp32 l2tap - time stamping", "[ethernet]")
|
||||
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);
|
||||
errno = 0;
|
||||
n = write(eth_tap_fd, &ptp_msg_ext_buff, 0);
|
||||
TEST_ASSERT_EQUAL(-1, n);
|
||||
TEST_ASSERT_EQUAL(EFAULT, errno);
|
||||
@@ -993,6 +1041,7 @@ TEST_CASE("esp32 l2tap - time stamping", "[ethernet]")
|
||||
ts_info->len = L2TAP_IREC_LEN(1);
|
||||
ptp_msg_ext_buff.buff = NULL;
|
||||
ptp_msg_ext_buff.buff_len = IN_BUFFER_SIZE;
|
||||
errno = 0;
|
||||
n = read(eth_tap_fd, &ptp_msg_ext_buff, 0);
|
||||
TEST_ASSERT_EQUAL(-1, n);
|
||||
TEST_ASSERT_EQUAL(EFAULT, errno);
|
||||
@@ -1050,12 +1099,13 @@ TEST_CASE("esp32 l2tap - ioctl - RCV_FILTER", "[ethernet]")
|
||||
int eth_tap_fd;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
eth_tap_fd = open("/dev/net/tap", 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
ESP_LOGI(TAG, "Verify that RCV_FILTER is allowed to be configured only after interface is set...");
|
||||
uint16_t eth_type_filter = ETH_FILTER_LE;
|
||||
errno = 0;
|
||||
TEST_ASSERT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter));
|
||||
TEST_ASSERT_EQUAL(EACCES, errno);
|
||||
TEST_ASSERT_EQUAL(0, close(eth_tap_fd));
|
||||
@@ -1122,6 +1172,7 @@ TEST_CASE("esp32 l2tap - ioctl - RCV_FILTER", "[ethernet]")
|
||||
TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str);
|
||||
|
||||
ESP_LOGI(TAG, "Verify that the setting the same Ethernet type to other fd at the same interface was unsuccessful...");
|
||||
errno = 0;
|
||||
TEST_ASSERT_EQUAL(-1, ioctl(eth_tap_fd_2, L2TAP_S_RCV_FILTER, ð_type_filter));
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd_2, L2TAP_G_RCV_FILTER, ð_type_filter_get));
|
||||
@@ -1146,7 +1197,7 @@ TEST_CASE("esp32 l2tap - ioctl - INTF_DEVICE/DEVICE_DRV_HNDL", "[ethernet]")
|
||||
int eth_tap_fd;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
eth_tap_fd = open("/dev/net/tap", 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
@@ -1161,6 +1212,7 @@ TEST_CASE("esp32 l2tap - ioctl - INTF_DEVICE/DEVICE_DRV_HNDL", "[ethernet]")
|
||||
// Check getter of direct Ethernet interface handle
|
||||
esp_eth_handle_t l2tap_eth_handle;
|
||||
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_DEVICE_DRV_HNDL, &l2tap_eth_handle));
|
||||
|
||||
TEST_ASSERT_EQUAL(eth_network_hndls.eth_handle, l2tap_eth_handle);
|
||||
|
||||
// Set the Ethertype filter (frames with this type will be available through the eth_tap_fd)
|
||||
@@ -1182,6 +1234,7 @@ TEST_CASE("esp32 l2tap - ioctl - INTF_DEVICE/DEVICE_DRV_HNDL", "[ethernet]")
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n);
|
||||
|
||||
ESP_LOGI(TAG, "Try to set non-existing Ethernet interface...");
|
||||
errno = 0;
|
||||
TEST_ASSERT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_NOT_DEF"));
|
||||
TEST_ASSERT_EQUAL(ENODEV, errno);
|
||||
ESP_LOGI(TAG, "Verify that previous setting is kept...");
|
||||
@@ -1235,7 +1288,7 @@ TEST_CASE("esp32 l2tap - ioctl - unknown", "[ethernet]")
|
||||
int eth_tap_fd;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
eth_tap_fd = open("/dev/net/tap", 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
@@ -1260,7 +1313,7 @@ TEST_CASE("esp32 l2tap - fcntl", "[ethernet]")
|
||||
int eth_tap_fd;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
ethernet_init(ð_network_hndls);
|
||||
ethernet_init(ð_network_hndls, true);
|
||||
|
||||
eth_tap_fd = open("/dev/net/tap", 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
@@ -1302,6 +1355,7 @@ TEST_CASE("esp32 l2tap - fcntl", "[ethernet]")
|
||||
int loop_cnt = 0;
|
||||
xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL);
|
||||
while (loop_cnt < 100) {
|
||||
errno = 0;
|
||||
if ((n = read(eth_tap_fd, in_buffer, IN_BUFFER_SIZE)) > 0) {
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n);
|
||||
break;
|
||||
@@ -1329,6 +1383,7 @@ TEST_CASE("esp32 l2tap - fcntl", "[ethernet]")
|
||||
loop_cnt = 0;
|
||||
xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL);
|
||||
while (loop_cnt < 100) {
|
||||
errno = 0;
|
||||
if ((n = read(eth_tap_fd, in_buffer, IN_BUFFER_SIZE)) > 0) {
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n);
|
||||
break;
|
||||
@@ -1343,14 +1398,17 @@ TEST_CASE("esp32 l2tap - fcntl", "[ethernet]")
|
||||
TEST_ASSERT_EQUAL(0, loop_cnt);
|
||||
|
||||
// Try to use unsupported operation
|
||||
errno = 0;
|
||||
flags = fcntl(eth_tap_fd, F_DUPFD, 0);
|
||||
TEST_ASSERT_EQUAL(-1, flags);
|
||||
TEST_ASSERT_EQUAL(ENOSYS, errno);
|
||||
|
||||
// Try to set unsupported flag
|
||||
errno = 0;
|
||||
flags = fcntl(eth_tap_fd, F_SETFL, O_TRUNC);
|
||||
TEST_ASSERT_EQUAL(-1, flags);
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
errno = 0;
|
||||
flags = fcntl(eth_tap_fd, F_SETFL, O_TRUNC | O_NONBLOCK);
|
||||
TEST_ASSERT_EQUAL(-1, flags);
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
@@ -1362,6 +1420,52 @@ TEST_CASE("esp32 l2tap - fcntl", "[ethernet]")
|
||||
ethernet_deinit(ð_network_hndls);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER
|
||||
TEST_CASE("esp32 l2tap - no netif attached", "[ethernet]")
|
||||
{
|
||||
test_vfs_eth_network_t eth_network_hndls;
|
||||
int eth_tap_fd;
|
||||
int n;
|
||||
uint16_t eth_type_filter;
|
||||
esp_eth_handle_t l2tap_eth_handle;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL));
|
||||
// No netif attached
|
||||
ethernet_init(ð_network_hndls, false);
|
||||
|
||||
// ==========================================================
|
||||
// Untagged frames, no VLAN child, Ethernet driver handle
|
||||
// ==========================================================
|
||||
ESP_LOGI(TAG, "Verify L2TAP untagged frames with no netif (Ethernet driver handle)...");
|
||||
eth_tap_fd = open("/dev/net/tap", 0);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_DEVICE_DRV_HNDL, eth_network_hndls.eth_handle));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_DEVICE_DRV_HNDL, &l2tap_eth_handle));
|
||||
TEST_ASSERT_EQUAL(eth_network_hndls.eth_handle, l2tap_eth_handle);
|
||||
|
||||
eth_type_filter = ETH_FILTER_LE;
|
||||
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter));
|
||||
|
||||
TEST_ESP_OK(esp_eth_ioctl(eth_network_hndls.eth_handle, ETH_CMD_G_MAC_ADDR, &s_test_msg.header.src.addr));
|
||||
TEST_ESP_OK(esp_eth_ioctl(eth_network_hndls.eth_handle, ETH_CMD_G_MAC_ADDR, &s_test_msg.header.dest.addr));
|
||||
s_test_msg.header.type = ETH_FILTER_BE;
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, write(eth_tap_fd, &s_test_msg, sizeof(s_test_msg)));
|
||||
n = read(eth_tap_fd, in_buffer, IN_BUFFER_SIZE);
|
||||
TEST_ASSERT_GREATER_THAN(0, n);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n);
|
||||
ESP_LOGI(TAG, "untagged recv test string: %s", ((test_vfs_eth_tap_msg_t *)in_buffer)->str);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(eth_tap_fd));
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL));
|
||||
ethernet_deinit(ð_network_hndls);
|
||||
}
|
||||
#if CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
// TODO add vlan tests IDF-16041
|
||||
#endif // CONFIG_ETH_SUBLAYER_VLAN_SUPPORT
|
||||
#endif // CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
|
||||
@@ -8,7 +8,9 @@ from pytest_embedded import Dut
|
||||
'config, target',
|
||||
[
|
||||
pytest.param('default_esp32', 'esp32', marks=[pytest.mark.eth_ip101]),
|
||||
pytest.param('sub_esp32', 'esp32', marks=[pytest.mark.eth_ip101]),
|
||||
pytest.param('default_esp32p4', 'esp32p4', marks=[pytest.mark.eth_ip101]),
|
||||
pytest.param('sub_esp32p4', 'esp32p4', marks=[pytest.mark.eth_ip101]),
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_ETH_SUBLAYER_SUPPORT=y
|
||||
CONFIG_ETH_TRANSMIT_MUTEX=n
|
||||
CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX=y
|
||||
CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER=y
|
||||
@@ -0,0 +1,6 @@
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_ETH_SUBLAYER_SUPPORT=y
|
||||
CONFIG_ETH_TRANSMIT_MUTEX=n
|
||||
CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX=y
|
||||
CONFIG_L2TAP_TEST_USE_ETH_SUBLAYER=y
|
||||
@@ -2,6 +2,7 @@ CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y
|
||||
|
||||
CONFIG_ETH_USE_ESP32_EMAC=y
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
|
||||
CONFIG_ESP_NETIF_L2_TAP=y
|
||||
CONFIG_LWIP_CHECK_THREAD_SAFETY=y
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/param.h>
|
||||
@@ -21,6 +20,7 @@
|
||||
#include "esp_check.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_private/esp_eth_sublayer_iodriver.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
@@ -51,9 +51,18 @@ typedef struct {
|
||||
QueueHandle_t rx_queue;
|
||||
|
||||
SemaphoreHandle_t close_done_sem;
|
||||
|
||||
// Sublayer transmit functions
|
||||
esp_err_t (*iodriver_transmit)(l2tap_iodriver_handle io_handle, void *buf, size_t len);
|
||||
esp_err_t (*iodriver_transmit_wrap)(l2tap_iodriver_handle io_handle, void *buf, size_t len, void *eb);
|
||||
void (*iodriver_free_rx_buffer)(l2tap_iodriver_handle io_handle, void *buffer);
|
||||
esp_err_t (*iodriver_get_ll_driver)(l2tap_iodriver_handle io_handle, void **ll_driver);
|
||||
|
||||
// TODO create JIRA to remove this along with Ethernet glue or update glue to have `get_io_fns`
|
||||
// Direct Ethernet Driver transmit functions (legacy mode)
|
||||
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, ...);
|
||||
esp_err_t (*eth_transmit)(l2tap_iodriver_handle io_handle, void *buffer, size_t len);
|
||||
esp_err_t (*eth_transmit_ctrl_bufs)(l2tap_iodriver_handle io_handle, void *ctrl, const esp_eth_buf_desc_t *bufs, size_t buf_count);
|
||||
};
|
||||
void (*driver_free_rx_buffer)(l2tap_iodriver_handle io_handle, void* buffer);
|
||||
} l2tap_context_t;
|
||||
@@ -61,7 +70,8 @@ typedef struct {
|
||||
typedef struct {
|
||||
void *buff;
|
||||
size_t len;
|
||||
eth_mac_time_t ts;
|
||||
void *l2_buff;
|
||||
l2tap_timestamp_t ts;
|
||||
} frame_queue_entry_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -89,11 +99,33 @@ static portMUX_TYPE s_critical_section_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static l2tap_select_args_t **s_registered_selects = NULL;
|
||||
static int32_t s_registered_select_cnt = 0;
|
||||
|
||||
typedef struct l2tap_provider_node {
|
||||
l2tap_iodriver_provider_handle handle;
|
||||
SLIST_ENTRY(l2tap_provider_node) next;
|
||||
} l2tap_provider_node_t;
|
||||
|
||||
static SLIST_HEAD(l2tap_provider_list, l2tap_provider_node) s_provider_list =
|
||||
SLIST_HEAD_INITIALIZER(s_provider_list);
|
||||
|
||||
static const char *TAG = "vfs_l2tap";
|
||||
|
||||
static void l2tap_select_notify(int fd, l2tap_select_notif_e select_notif);
|
||||
|
||||
/* ================== Utils ====================== */
|
||||
static inline void default_free_rx_buffer(l2tap_iodriver_handle io_handle, void* buffer)
|
||||
{
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
static void l2tap_free_rx_buffer(l2tap_context_t *l2tap_socket, void *buffer)
|
||||
{
|
||||
if (l2tap_socket->iodriver_free_rx_buffer != NULL) {
|
||||
l2tap_socket->iodriver_free_rx_buffer(l2tap_socket->driver_handle, buffer);
|
||||
return;
|
||||
}
|
||||
l2tap_socket->driver_free_rx_buffer(l2tap_socket->driver_handle, buffer);
|
||||
}
|
||||
|
||||
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));
|
||||
@@ -101,12 +133,13 @@ static esp_err_t init_rx_queue(l2tap_context_t *l2tap_socket)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t push_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t len, eth_mac_time_t *ts)
|
||||
static esp_err_t push_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t len, void *l2_buff, l2tap_timestamp_t *ts)
|
||||
{
|
||||
frame_queue_entry_t rx_frame_info;
|
||||
frame_queue_entry_t rx_frame_info = {0};
|
||||
|
||||
rx_frame_info.buff = buff;
|
||||
rx_frame_info.len = len;
|
||||
rx_frame_info.l2_buff = l2_buff;
|
||||
if (ts) {
|
||||
rx_frame_info.ts = *ts;
|
||||
}
|
||||
@@ -131,7 +164,7 @@ static esp_err_t pop_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t
|
||||
// empty queue was issued indicating the fd is going to be closed
|
||||
if (rx_frame_info.len == 0) {
|
||||
// indicate to "clean_task" that task waiting for queue was unblocked
|
||||
push_rx_queue(l2tap_socket, NULL, 0, NULL);
|
||||
push_rx_queue(l2tap_socket, NULL, 0, NULL, NULL);
|
||||
*copy_len = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -160,8 +193,8 @@ static esp_err_t pop_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t
|
||||
// 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;
|
||||
ts->tv_sec = rx_frame_info.ts.sec;
|
||||
ts->tv_nsec = rx_frame_info.ts.nsec;
|
||||
} else {
|
||||
info_rec->type = L2TAP_IREC_INVALID;
|
||||
}
|
||||
@@ -176,7 +209,7 @@ static esp_err_t pop_rx_queue(l2tap_context_t *l2tap_socket, void *buff, size_t
|
||||
}
|
||||
}
|
||||
memcpy(copy_buff, rx_frame_info.buff, *copy_len);
|
||||
l2tap_socket->driver_free_rx_buffer(l2tap_socket->driver_handle, rx_frame_info.buff);
|
||||
l2tap_free_rx_buffer(l2tap_socket, rx_frame_info.l2_buff != NULL ? rx_frame_info.l2_buff : rx_frame_info.buff);
|
||||
} else {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -193,7 +226,7 @@ static void flush_rx_queue(l2tap_context_t *l2tap_socket)
|
||||
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);
|
||||
l2tap_free_rx_buffer(l2tap_socket, rx_frame_info.l2_buff != NULL ? rx_frame_info.l2_buff : rx_frame_info.buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,13 +247,34 @@ static inline void l2tap_exit_critical(void)
|
||||
portEXIT_CRITICAL(&s_critical_section_lock);
|
||||
}
|
||||
|
||||
static inline void default_free_rx_buffer(l2tap_iodriver_handle io_handle, void* buffer)
|
||||
static esp_err_t l2tap_transmit(l2tap_context_t *l2tap_socket, void *data, size_t size)
|
||||
{
|
||||
free(buffer);
|
||||
if (l2tap_socket->iodriver_transmit != NULL) {
|
||||
return l2tap_socket->iodriver_transmit(l2tap_socket->driver_handle, data, size);
|
||||
}
|
||||
if (l2tap_socket->eth_transmit != NULL) {
|
||||
return l2tap_socket->eth_transmit(l2tap_socket->driver_handle, data, size);
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
static esp_err_t l2tap_transmit_ts(l2tap_context_t *l2tap_socket, void *data, size_t size, l2tap_timestamp_t *hw_ts)
|
||||
{
|
||||
if (l2tap_socket->iodriver_transmit_wrap != NULL) {
|
||||
return l2tap_socket->iodriver_transmit_wrap(l2tap_socket->driver_handle, data, size, hw_ts);
|
||||
}
|
||||
if (l2tap_socket->eth_transmit_ctrl_bufs != NULL) {
|
||||
const esp_eth_buf_desc_t bufs[] = {
|
||||
{ .buf = (uint8_t *)data, .len = size },
|
||||
};
|
||||
return l2tap_socket->eth_transmit_ctrl_bufs(l2tap_socket->driver_handle, hw_ts, bufs, 1);
|
||||
}
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
|
||||
/* ================== ESP NETIF L2 TAP intf ====================== */
|
||||
esp_err_t esp_vfs_l2tap_eth_filter_frame(l2tap_iodriver_handle driver_handle, void *buff, size_t *size, void *info)
|
||||
esp_err_t esp_vfs_l2tap_eth_filter_frame(l2tap_iodriver_handle driver_handle, void *buff, size_t *size, l2tap_eth_filter_info_t *info)
|
||||
{
|
||||
struct eth_hdr *eth_header = buff;
|
||||
uint16_t eth_type = ntohs(eth_header->type);
|
||||
@@ -233,15 +287,17 @@ esp_err_t esp_vfs_l2tap_eth_filter_frame(l2tap_iodriver_handle driver_handle, vo
|
||||
// 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_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;
|
||||
l2tap_timestamp_t *ts = NULL;
|
||||
void *l2_buffer = NULL;
|
||||
if (info != NULL) {
|
||||
if (s_l2tap_sockets[i].flags & L2TAP_FLAG_TS) {
|
||||
ts = info->hw_ts;
|
||||
}
|
||||
l2_buffer = info->l2_buffer;
|
||||
}
|
||||
if (push_rx_queue(&s_l2tap_sockets[i], buff, *size, ts) != ESP_OK) {
|
||||
if (push_rx_queue(&s_l2tap_sockets[i], buff, *size, l2_buffer, 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);
|
||||
l2tap_free_rx_buffer(&s_l2tap_sockets[i], l2_buffer != NULL ? l2_buffer : buff);
|
||||
ESP_LOGD(TAG, "fd %d rx queue is full", i);
|
||||
}
|
||||
l2tap_enter_critical();
|
||||
@@ -274,8 +330,12 @@ static int l2tap_open(__attribute__((unused)) void *ctx, const char *path, int f
|
||||
s_l2tap_sockets[fd].ethtype_filter = 0x0;
|
||||
s_l2tap_sockets[fd].flags = 0;
|
||||
s_l2tap_sockets[fd].driver_handle = NULL;
|
||||
s_l2tap_sockets[fd].iodriver_transmit = NULL;
|
||||
s_l2tap_sockets[fd].iodriver_transmit_wrap = NULL;
|
||||
s_l2tap_sockets[fd].iodriver_free_rx_buffer = NULL;
|
||||
s_l2tap_sockets[fd].iodriver_get_ll_driver = NULL;
|
||||
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].eth_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;
|
||||
@@ -349,8 +409,8 @@ static ssize_t l2tap_write(__attribute__((unused)) void *ctx, int fd, const void
|
||||
}
|
||||
|
||||
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){
|
||||
l2tap_timestamp_t hw_ts;
|
||||
if ((esp_ret = l2tap_transmit_ts(&s_l2tap_sockets[fd], eth_buff, size, &hw_ts)) == 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) {
|
||||
@@ -363,18 +423,19 @@ static ssize_t l2tap_write(__attribute__((unused)) void *ctx, int fd, const void
|
||||
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;
|
||||
ts->tv_sec = hw_ts.sec;
|
||||
ts->tv_nsec = hw_ts.nsec;
|
||||
} else {
|
||||
info_rec->type = L2TAP_IREC_INVALID;
|
||||
}
|
||||
}
|
||||
ret = size;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "l2tap_transmit_ts failed: %d", esp_ret);
|
||||
errno = l2tap_tx_esp_err_to_errno(esp_ret);
|
||||
}
|
||||
} else {
|
||||
if ((esp_ret = s_l2tap_sockets[fd].driver_transmit(s_l2tap_sockets[fd].driver_handle, eth_buff, size)) == ESP_OK) {
|
||||
if ((esp_ret = l2tap_transmit(&s_l2tap_sockets[fd], eth_buff, size)) == ESP_OK) {
|
||||
ret = size;
|
||||
} else {
|
||||
errno = l2tap_tx_esp_err_to_errno(esp_ret);
|
||||
@@ -449,7 +510,7 @@ static void l2tap_clean_task(void *task_param)
|
||||
flush_rx_queue(l2tap_socket);
|
||||
|
||||
// push empty queue to unblock possibly blocking task
|
||||
push_rx_queue(l2tap_socket, NULL, 0, NULL);
|
||||
push_rx_queue(l2tap_socket, NULL, 0, NULL, NULL);
|
||||
// wait for the indication that blocking task was executed (unblocked)
|
||||
ssize_t actual_size;
|
||||
pop_rx_queue(l2tap_socket, NULL, 0, &actual_size);
|
||||
@@ -500,7 +561,7 @@ static int l2tap_close(__attribute__((unused)) void *ctx, int fd)
|
||||
|
||||
close_failed:
|
||||
flush_rx_queue(&s_l2tap_sockets[fd]);
|
||||
(void)push_rx_queue(&s_l2tap_sockets[fd], NULL, 0, NULL);
|
||||
(void)push_rx_queue(&s_l2tap_sockets[fd], NULL, 0, NULL, NULL);
|
||||
delete_rx_queue(&s_l2tap_sockets[fd]);
|
||||
atomic_store(&s_l2tap_sockets[fd].state, L2TAP_SOCK_STATE_READY);
|
||||
errno = ENOMEM;
|
||||
@@ -513,6 +574,43 @@ static bool netif_driver_matches(esp_netif_t *netif, void* driver)
|
||||
return esp_netif_get_io_driver(netif) == driver;
|
||||
}
|
||||
|
||||
static void l2tap_socket_apply_io_fns(l2tap_context_t *l2tap_socket, const esp_eth_iodriver_io_fns_t *io_fns)
|
||||
{
|
||||
l2tap_socket->driver_handle = io_fns->io_handle;
|
||||
l2tap_socket->iodriver_transmit = io_fns->iodriver_transmit;
|
||||
l2tap_socket->iodriver_transmit_wrap = io_fns->iodriver_transmit_wrap;
|
||||
l2tap_socket->iodriver_free_rx_buffer = io_fns->iodriver_free_rx_buffer;
|
||||
l2tap_socket->iodriver_get_ll_driver = io_fns->iodriver_get_ll_driver;
|
||||
}
|
||||
|
||||
static void l2tap_bind_io_handle(l2tap_context_t *l2tap_socket, l2tap_iodriver_handle io_handle)
|
||||
{
|
||||
l2tap_provider_node_t *it;
|
||||
esp_eth_iodriver_provider_base_t *provider;
|
||||
esp_eth_iodriver_io_fns_t io_fns;
|
||||
|
||||
l2tap_enter_critical();
|
||||
// NULL handle can't be served by any provider, don't even try and bind it directly (legacy mode)
|
||||
if (io_handle != NULL) {
|
||||
SLIST_FOREACH(it, &s_provider_list, next) {
|
||||
provider = (esp_eth_iodriver_provider_base_t *)it->handle;
|
||||
if (provider->get_io_fns != NULL &&
|
||||
provider->get_io_fns(provider, io_handle, &io_fns) == ESP_OK) {
|
||||
l2tap_socket_apply_io_fns(l2tap_socket, &io_fns);
|
||||
l2tap_exit_critical();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No iodriver provider matched: bind the handle directly (legacy mode).
|
||||
l2tap_socket->driver_handle = io_handle;
|
||||
l2tap_socket->iodriver_transmit = NULL;
|
||||
l2tap_socket->iodriver_transmit_wrap = NULL;
|
||||
l2tap_socket->iodriver_free_rx_buffer = NULL;
|
||||
l2tap_socket->iodriver_get_ll_driver = NULL;
|
||||
l2tap_exit_critical();
|
||||
}
|
||||
|
||||
static int l2tap_ioctl(__attribute__((unused)) void *ctx, int fd, int cmd, va_list args)
|
||||
{
|
||||
esp_netif_t *esp_netif;
|
||||
@@ -552,15 +650,21 @@ static int l2tap_ioctl(__attribute__((unused)) void *ctx, int fd, int cmd, va_li
|
||||
}
|
||||
case L2TAP_S_INTF_DEVICE:{
|
||||
const char *str = va_arg(args, const char *);
|
||||
// get netif handle from if key (the highest layer)
|
||||
esp_netif = esp_netif_get_handle_from_ifkey(str);
|
||||
if (esp_netif == NULL) {
|
||||
// No such device
|
||||
errno = ENODEV;
|
||||
goto err;
|
||||
}
|
||||
l2tap_enter_critical();
|
||||
s_l2tap_sockets[fd].driver_handle = esp_netif_get_io_driver(esp_netif);
|
||||
l2tap_exit_critical();
|
||||
// find the iodriver provider based on the netif iodriver
|
||||
esp_netif_iodriver_handle netif_iodriver = esp_netif_get_io_driver(esp_netif);
|
||||
if (netif_iodriver == NULL) {
|
||||
// No such device (netif does not have any IO driver attached)
|
||||
errno = ENODEV;
|
||||
goto err;
|
||||
}
|
||||
l2tap_bind_io_handle(&s_l2tap_sockets[fd], netif_iodriver);
|
||||
break;
|
||||
}
|
||||
case L2TAP_G_INTF_DEVICE:{
|
||||
@@ -572,26 +676,34 @@ static int l2tap_ioctl(__attribute__((unused)) void *ctx, int fd, int cmd, va_li
|
||||
break;
|
||||
}
|
||||
case L2TAP_S_DEVICE_DRV_HNDL:{
|
||||
l2tap_iodriver_handle set_driver_hdl = va_arg(args, l2tap_iodriver_handle);
|
||||
if (set_driver_hdl == NULL) {
|
||||
l2tap_iodriver_handle device_driver_hdl = va_arg(args, l2tap_iodriver_handle);
|
||||
if (device_driver_hdl == NULL) {
|
||||
// No such device (not valid driver handle)
|
||||
errno = ENODEV;
|
||||
errno = EINVAL;
|
||||
goto err;
|
||||
}
|
||||
l2tap_enter_critical();
|
||||
s_l2tap_sockets[fd].driver_handle = set_driver_hdl;
|
||||
l2tap_exit_critical();
|
||||
l2tap_bind_io_handle(&s_l2tap_sockets[fd], device_driver_hdl);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
// the driver handle and the associated getter must be read consistently since they can be
|
||||
// reassigned from other task
|
||||
l2tap_enter_critical();
|
||||
// no iodriver provider registered, get the driver handle directly (legacy mode)
|
||||
if (s_l2tap_sockets[fd].iodriver_get_ll_driver == NULL) {
|
||||
*get_driver_hdl = s_l2tap_sockets[fd].driver_handle;
|
||||
} else {
|
||||
s_l2tap_sockets[fd].iodriver_get_ll_driver(s_l2tap_sockets[fd].driver_handle, get_driver_hdl);
|
||||
}
|
||||
l2tap_exit_critical();
|
||||
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;
|
||||
// TODO add JIRA ticket - maybe not needed tests with sublayer where L2TAP_S_TIMESTAMP_EN may not be needed
|
||||
s_l2tap_sockets[fd].eth_transmit_ctrl_bufs = esp_eth_transmit_ctrl_bufs;
|
||||
l2tap_exit_critical();
|
||||
break;
|
||||
default:
|
||||
@@ -822,6 +934,61 @@ static const esp_vfs_fs_ops_t s_vfs_l2tap = {
|
||||
#endif // CONFIG_VFS_SUPPORT_SELECT
|
||||
};
|
||||
|
||||
/* ================== IO Driver Provider Registry ====================== */
|
||||
|
||||
esp_err_t esp_vfs_l2tap_iodriver_provider_register(l2tap_iodriver_provider_handle provider)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(provider, ESP_ERR_INVALID_ARG, TAG, "iodriver provider handle is NULL");
|
||||
|
||||
l2tap_provider_node_t *node = malloc(sizeof(*node));
|
||||
if (!node) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
node->handle = provider;
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
l2tap_enter_critical();
|
||||
l2tap_provider_node_t *it;
|
||||
SLIST_FOREACH(it, &s_provider_list, next) {
|
||||
if (it->handle == provider) {
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret == ESP_OK) {
|
||||
SLIST_INSERT_HEAD(&s_provider_list, node, next);
|
||||
}
|
||||
l2tap_exit_critical();
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
free(node);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_l2tap_iodriver_provider_unregister(l2tap_iodriver_provider_handle provider)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(provider, ESP_ERR_INVALID_ARG, TAG, "iodriver provider handle is NULL");
|
||||
|
||||
l2tap_provider_node_t *found = NULL;
|
||||
l2tap_enter_critical();
|
||||
l2tap_provider_node_t *it;
|
||||
SLIST_FOREACH(it, &s_provider_list, next) {
|
||||
if (it->handle == provider) {
|
||||
found = it;
|
||||
SLIST_REMOVE(&s_provider_list, it, l2tap_provider_node, next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
l2tap_exit_critical();
|
||||
|
||||
if (found) {
|
||||
free(found);
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_l2tap_intf_register(l2tap_vfs_config_t *config)
|
||||
{
|
||||
l2tap_vfs_config_t def_config = L2TAP_VFS_CONFIG_DEFAULT();
|
||||
|
||||
@@ -577,7 +577,7 @@ The following functions should only be invoked after the Ethernet driver has bee
|
||||
|
||||
* :cpp:member:`eth_mac_ptp_config_t::roll_type`: Rollover mode (digital or binary) for subseconds register. The binary rollover mode is recommended as it provides a more precise time synchronization.
|
||||
|
||||
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.
|
||||
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_bufs` 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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -683,6 +683,20 @@ The majority of PHY management functionality required by the ESP-IDF Ethernet dr
|
||||
|
||||
Once you finish the new custom PHY driver implementation, consider sharing it among other users via `ESP Component Registry <https://components.espressif.com/>`_.
|
||||
|
||||
.. _ethernet-sublayer:
|
||||
|
||||
Ethernet Sublayer
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Ethernet sublayer is an optional layer between one physical Ethernet driver (``esp_eth_handle_t``) and one or more ``esp_netif`` instances. It owns the driver RX input path, distributes Ethernet events to attached interfaces, and can perform mid-path frame processing such as 802.1Q VLAN multiplexing and demultiplexing.
|
||||
|
||||
.. note::
|
||||
The Ethernet sublayer is an **experimental** feature. Enable :menuitem:`CONFIG_IDF_EXPERIMENTAL_FEATURES` and :menuitem:`CONFIG_ETH_SUBLAYER_SUPPORT` to use it. The API is under active development and may change in future ESP-IDF releases without a deprecation period.
|
||||
|
||||
For architecture details, configuration, and usage guidance, see :component_file:`esp_eth/src/sublayer/README.md`.
|
||||
|
||||
A working demonstration is available in :example:`ethernet/sublayer`, which shares one Ethernet driver between an untagged ``esp_netif`` and a tagged VLAN ``esp_netif``.
|
||||
|
||||
.. ---------------------------- API Reference ----------------------------------
|
||||
|
||||
API Reference
|
||||
|
||||
@@ -715,7 +715,7 @@ Mark Flaky Tests
|
||||
|
||||
Certain test cases are based on Ethernet or Wi-Fi. However, the test may be flaky due to networking issues. Thus, it is possible to mark a particular test case as flaky.
|
||||
|
||||
This code example is taken from :idf_file:`pytest_esp_eth.py <components/esp_eth/test_apps/pytest_esp_eth.py>`.
|
||||
This code example is taken from :idf_file:`pytest_esp_eth.py <components/esp_eth/test_apps/test_app_driver/pytest_esp_eth.py>`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
@@ -577,7 +577,7 @@ ESP-IDF 在宏 :c:macro:`ETH_DEFAULT_CONFIG` 中为安装驱动程序提供了
|
||||
|
||||
* :cpp:member:`eth_mac_ptp_config_t::roll_type`:亚秒寄存器的翻转模式(数字或二进制)。推荐使用二进制翻转模式,因为它能提供更精确的时间同步。
|
||||
|
||||
接收帧的时间戳可以通过注册的 :cpp:member:`esp_eth_config_t::stack_input_info` 函数的最后一个参数进行访问,传输帧的时间戳可以通过注册的 :cpp:func:`esp_eth_transmit_ctrl_vargs` 函数的 ``ctrl`` 参数进行访问。然而,对于用户获取时间戳信息,更简便的方式是利用 L2 TAP :ref:`扩展缓冲区 <esp_netif_l2tap_ext_buff>` 机制。
|
||||
接收帧的时间戳可以通过注册的 :cpp:member:`esp_eth_config_t::stack_input_info` 函数的最后一个参数进行访问,传输帧的时间戳可以通过 :cpp:func:`esp_eth_transmit_ctrl_bufs` 函数的 ``ctrl`` 参数进行访问。然而,对于用户获取时间戳信息,更简便的方式是利用 L2 TAP :ref:`扩展缓冲区 <esp_netif_l2tap_ext_buff>` 机制。
|
||||
|
||||
您可以通过注册回调函数和设置事件触发的目标时间,在精确的时间点调度事件。请注意,回调函数将在中断服务程序 (ISR) 上下文中调用,因此应尽量简洁。
|
||||
|
||||
@@ -683,6 +683,20 @@ ESP-IDF 以太网驱动程序所需的大部分 PHY 管理功能都已涵盖在
|
||||
|
||||
实现新的自定义 PHY 驱动程序后,你可以通过 `乐鑫组件注册表 <https://components.espressif.com/>`_ 将驱动分享给其他用户。
|
||||
|
||||
.. _ethernet-sublayer:
|
||||
|
||||
以太网子层
|
||||
^^^^^^^^^^
|
||||
|
||||
以太网子层是位于一个物理以太网驱动程序(``esp_eth_handle_t``)与一个或多个 ``esp_netif`` 实例之间的可选层。它负责拥有驱动程序的 RX 输入路径,将以太网事件分发给所连接的接口,并可执行路径中的帧处理,例如 802.1Q VLAN 复用与解复用。
|
||||
|
||||
.. note::
|
||||
以太网子层是一项**实验性**功能。需启用 :menuitem:`CONFIG_IDF_EXPERIMENTAL_FEATURES` 和 :menuitem:`CONFIG_ETH_SUBLAYER_SUPPORT` 后方可使用。该 API 仍在积极开发中,未来 ESP-IDF 版本中可能会在不经过弃用周期的情况下发生变更。
|
||||
|
||||
有关架构细节、配置和使用指南,请参阅 :component_file:`esp_eth/src/sublayer/README.md`。
|
||||
|
||||
可参考 :example:`ethernet/sublayer` 中的示例,该示例演示了如何在一个未打标签的 ``esp_netif`` 和一个带标签的 VLAN ``esp_netif`` 之间共享同一个以太网驱动程序。
|
||||
|
||||
.. ---------------------------- API Reference ----------------------------------
|
||||
|
||||
API 参考
|
||||
|
||||
@@ -715,7 +715,7 @@ Pytest 使用技巧
|
||||
|
||||
某些测试用例基于以太网或 Wi-Fi。然而由于网络问题,测试可能会不稳定。此时,可以将某个测试用例标记为不稳定的测试用例。
|
||||
|
||||
以下代码示例来自 :idf_file:`pytest_esp_eth.py <components/esp_eth/test_apps/pytest_esp_eth.py>`。
|
||||
以下代码示例来自 :idf_file:`pytest_esp_eth.py <components/esp_eth/test_apps/test_app_driver/pytest_esp_eth.py>`。
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
@@ -36,3 +36,14 @@ examples/ethernet/ptp:
|
||||
depends_components:
|
||||
- esp_eth
|
||||
- esp_netif
|
||||
|
||||
examples/ethernet/sublayer:
|
||||
enable:
|
||||
- if: INCLUDE_DEFAULT == 1
|
||||
disable:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32p4", "esp32s31"]
|
||||
depends_components:
|
||||
- esp_eth
|
||||
- esp_netif
|
||||
- lwip
|
||||
- esp_driver_gpio
|
||||
|
||||
@@ -9,6 +9,8 @@ Ethernet examples demonstrate basic features related to Ethernet layer, such as:
|
||||
|
||||
* basic Ethernet initialization and binding to IP stack via ESP-NETIF
|
||||
|
||||
* experimental Ethernet netif sublayer (untagged + VLAN interfaces on one driver)
|
||||
|
||||
* tools for performance measurement, i.e. `iperf`
|
||||
|
||||
* hardware time synchronization
|
||||
|
||||
@@ -59,8 +59,21 @@ void init_ethernet_and_netif(void)
|
||||
esp_netif_base_config.route_prio -= i*5;
|
||||
esp_netif_t *eth_netif = esp_netif_new(&esp_netif_config);
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
ESP_LOGI(TAG, "Using Ethernet sublayer");
|
||||
esp_eth_sublayer_config_t sub_config = {
|
||||
.eth_handle = eth_handles[i],
|
||||
};
|
||||
esp_eth_sublayer_handle_t eth_sublayer = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_new(&sub_config, ð_sublayer));
|
||||
esp_eth_sublayer_vlan_handle_t eth_netif_untag = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_vlan_add(eth_sublayer, ESP_ETH_SUBLAYER_UNTAGGED_VID, ð_netif_untag));
|
||||
// attach Ethernet driver to TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, eth_netif_untag));
|
||||
#else
|
||||
// attach Ethernet driver to TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handles[i])));
|
||||
#endif
|
||||
}
|
||||
|
||||
for (int i = 0; i < eth_port_cnt; i++) {
|
||||
@@ -127,7 +140,12 @@ void app_main(void)
|
||||
|
||||
ptp_deamon_start();
|
||||
#if CONFIG_EXAMPLE_PTP_PULSE_EMAC_PPS
|
||||
esp_eth_handle_t eth_handle = esp_netif_get_io_driver(esp_netif_get_handle_from_ifkey(ETH_IF_KEY));
|
||||
esp_netif_iodriver_handle netif_iodriver = esp_netif_get_io_driver(esp_netif_get_handle_from_ifkey(ETH_IF_KEY));
|
||||
#if CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
esp_eth_handle_t eth_handle = esp_eth_sublayer_vlan_get_eth_handle(netif_iodriver);
|
||||
#else
|
||||
esp_eth_handle_t eth_handle = (esp_eth_handle_t)netif_iodriver;
|
||||
#endif
|
||||
esp_eth_mac_t *mac;
|
||||
ESP_ERROR_CHECK(esp_eth_get_mac_instance(eth_handle, &mac));
|
||||
ESP_ERROR_CHECK(esp_eth_mac_set_pps_out_gpio(mac, CONFIG_EXAMPLE_PTP_PULSE_GPIO));
|
||||
|
||||
8
examples/ethernet/sublayer/CMakeLists.txt
Normal file
8
examples/ethernet/sublayer/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ethernet_sublayer)
|
||||
55
examples/ethernet/sublayer/README.md
Normal file
55
examples/ethernet/sublayer/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
| Supported Targets | ESP32 | ESP32-P4 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | --------- |
|
||||
|
||||
# Ethernet Sublayer Example
|
||||
|
||||
Demonstrates the experimental Ethernet netif sublayer (`esp_eth_sublayer`): one physical Ethernet driver shared by an untagged `esp_netif` (DHCP) and a tagged 802.1Q VLAN `esp_netif` (static IP).
|
||||
|
||||
Uses the [ethernet_init](https://components.espressif.com/components/espressif/ethernet_init/) component for driver setup.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
See common configurations for Ethernet examples from [upper level](../README.md#common-configurations).
|
||||
|
||||
This example requires enabling `CONFIG_IDF_EXPERIMENTAL_FEATURES` and `CONFIG_ETH_SUBLAYER_SUPPORT`.
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
Build the project and flash it to the board, 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-]``.)
|
||||
|
||||
### Setup VLAN interface on Linux
|
||||
|
||||
Create a tagged 802.1Q interface on the host so you can reach the ESP VLAN netif (defaults: VID `20`, IP `192.168.20.10`):
|
||||
|
||||
```bash
|
||||
# Replace eth0 with your host Ethernet interface (e.g. enp3s0)
|
||||
sudo ip link add link eth0 name eth0.20 type vlan id 20
|
||||
sudo ip addr add 192.168.20.1/24 dev eth0.20
|
||||
sudo ip link set eth0.20 up
|
||||
|
||||
ping 192.168.20.10
|
||||
```
|
||||
|
||||
To remove it later:
|
||||
|
||||
```bash
|
||||
sudo ip link delete eth0.20
|
||||
```
|
||||
|
||||
Match `id` / address to **Example Configuration** if you changed the defaults. The physical link between the host and the board must carry tagged frames (direct cable, or a switch port in trunk/hybrid mode).
|
||||
|
||||
See [upper-level Ethernet README](../README.md) for hardware notes and common troubleshooting.
|
||||
3
examples/ethernet/sublayer/main/CMakeLists.txt
Normal file
3
examples/ethernet/sublayer/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "ethernet_sublayer_example_main.c"
|
||||
PRIV_REQUIRES esp_netif esp_eth
|
||||
INCLUDE_DIRS ".")
|
||||
22
examples/ethernet/sublayer/main/Kconfig.projbuild
Normal file
22
examples/ethernet/sublayer/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,22 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_ETHERNET_VLAN_ID
|
||||
int "VLAN identifier"
|
||||
range 1 4094
|
||||
default 20
|
||||
help
|
||||
802.1Q VLAN ID for the tagged virtual interface created via the Ethernet sublayer.
|
||||
|
||||
config EXAMPLE_VLAN_STATIC_IPV4_ADDR
|
||||
string "VLAN IPV4 Address"
|
||||
default "192.168.20.10"
|
||||
|
||||
config EXAMPLE_VLAN_STATIC_ADDR_MASK
|
||||
string "VLAN Subnet Mask"
|
||||
default "255.255.255.0"
|
||||
|
||||
config EXAMPLE_VLAN_STATIC_ADDR_DEF_GW
|
||||
string "VLAN IPV4 Default Gateway"
|
||||
default "192.168.20.1"
|
||||
|
||||
endmenu
|
||||
143
examples/ethernet/sublayer/main/ethernet_sublayer_example_main.c
Normal file
143
examples/ethernet/sublayer/main/ethernet_sublayer_example_main.c
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "ethernet_init.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifndef CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
#error "This example requires CONFIG_ETH_SUBLAYER_SUPPORT (enable IDF experimental features)"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "eth_sublayer_example";
|
||||
|
||||
/** Event handler for Ethernet events */
|
||||
static void eth_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
uint8_t mac_addr[6] = {0};
|
||||
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
|
||||
|
||||
switch (event_id) {
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
|
||||
ESP_LOGI(TAG, "Ethernet Link Up");
|
||||
ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet Link Down");
|
||||
break;
|
||||
case ETHERNET_EVENT_START:
|
||||
ESP_LOGI(TAG, "Ethernet Started");
|
||||
break;
|
||||
case ETHERNET_EVENT_STOP:
|
||||
ESP_LOGI(TAG, "Ethernet Stopped");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Event handler for IP_EVENT_ETH_GOT_IP */
|
||||
static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
|
||||
const esp_netif_ip_info_t *ip_info = &event->ip_info;
|
||||
|
||||
ESP_LOGI(TAG, "Ethernet Got IP Address (%s)", esp_netif_get_ifkey(event->esp_netif));
|
||||
ESP_LOGI(TAG, "~~~~~~~~~~~");
|
||||
ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
|
||||
ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
|
||||
ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
|
||||
ESP_LOGI(TAG, "~~~~~~~~~~~");
|
||||
}
|
||||
|
||||
static esp_err_t create_vlan_netif_config(uint16_t vlan_id, esp_netif_config_t *cfg)
|
||||
{
|
||||
char *if_key;
|
||||
if (asprintf(&if_key, "ETH_VLAN%d", vlan_id) < 0) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_netif_inherent_config_t *base = malloc(sizeof(*base));
|
||||
if (base == NULL) {
|
||||
free(if_key);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
*base = (esp_netif_inherent_config_t)ESP_NETIF_INHERENT_DEFAULT_ETH();
|
||||
base->if_key = if_key;
|
||||
base->if_desc = "eth_vlan";
|
||||
base->route_prio = 30;
|
||||
|
||||
cfg->base = base;
|
||||
cfg->driver = NULL;
|
||||
cfg->stack = ESP_NETIF_NETSTACK_DEFAULT_ETH;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void free_vlan_netif_config(esp_netif_config_t *cfg)
|
||||
{
|
||||
if (cfg && cfg->base) {
|
||||
free((void *)cfg->base->if_key);
|
||||
free((void *)cfg->base);
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
uint8_t eth_port_cnt = 0;
|
||||
esp_eth_handle_t *eth_handles = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(ethernet_init_all(ð_handles, ð_port_cnt));
|
||||
if (eth_port_cnt > 1) {
|
||||
ESP_LOGW(TAG, "Multiple Ethernet interfaces detected, using the first one");
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
|
||||
|
||||
// Create Ethernet sublayer bound to the physical driver
|
||||
esp_eth_sublayer_config_t sub_config = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
sub_config.eth_handle = eth_handles[0];
|
||||
esp_eth_sublayer_handle_t sublayer = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_new(&sub_config, &sublayer));
|
||||
|
||||
// Untagged interface (DHCP)
|
||||
esp_eth_sublayer_vlan_handle_t untagged = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_vlan_add(sublayer, ESP_ETH_SUBLAYER_UNTAGGED_VID, &untagged));
|
||||
esp_netif_config_t eth_cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
esp_netif_t *eth_netif = esp_netif_new(ð_cfg);
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, untagged));
|
||||
|
||||
// Tagged VLAN interface (static IP)
|
||||
esp_eth_sublayer_vlan_handle_t vlan = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_vlan_add(sublayer, CONFIG_EXAMPLE_ETHERNET_VLAN_ID, &vlan));
|
||||
esp_netif_config_t vlan_cfg;
|
||||
ESP_ERROR_CHECK(create_vlan_netif_config(CONFIG_EXAMPLE_ETHERNET_VLAN_ID, &vlan_cfg));
|
||||
esp_netif_t *vlan_netif = esp_netif_new(&vlan_cfg);
|
||||
free_vlan_netif_config(&vlan_cfg);
|
||||
ESP_ERROR_CHECK(esp_netif_attach(vlan_netif, vlan));
|
||||
|
||||
esp_netif_dhcpc_stop(vlan_netif);
|
||||
esp_netif_ip_info_t vlan_ip = {0};
|
||||
inet_aton(CONFIG_EXAMPLE_VLAN_STATIC_IPV4_ADDR, &vlan_ip.ip.addr);
|
||||
inet_aton(CONFIG_EXAMPLE_VLAN_STATIC_ADDR_DEF_GW, &vlan_ip.gw.addr);
|
||||
inet_aton(CONFIG_EXAMPLE_VLAN_STATIC_ADDR_MASK, &vlan_ip.netmask.addr);
|
||||
ESP_ERROR_CHECK(esp_netif_set_ip_info(vlan_netif, &vlan_ip));
|
||||
|
||||
ESP_ERROR_CHECK(esp_eth_start(eth_handles[0]));
|
||||
}
|
||||
3
examples/ethernet/sublayer/main/idf_component.yml
Normal file
3
examples/ethernet/sublayer/main/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
espressif/ethernet_init:
|
||||
version: "~1.4.1"
|
||||
26
examples/ethernet/sublayer/pytest_eth_sublayer.py
Normal file
26
examples/ethernet/sublayer/pytest_eth_sublayer.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'config, target',
|
||||
[
|
||||
pytest.param('defaults', 'esp32', marks=[pytest.mark.eth_ip101]),
|
||||
pytest.param('defaults_esp32p4', 'esp32p4', marks=[pytest.mark.eth_ip101]),
|
||||
pytest.param('defaults_esp32s31', 'esp32s31', marks=[pytest.mark.eth_yt8531]),
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_esp_eth_sublayer(dut: Dut) -> None:
|
||||
# wait for IP on the untagged interface
|
||||
dut_ip = dut.expect(r'esp_netif_handlers: .+ ip: (\d+\.\d+\.\d+\.\d+),').group(1)
|
||||
param = '-n' if platform.system().lower() == 'windows' else '-c'
|
||||
command = ['ping', param, '1', dut_ip]
|
||||
output = subprocess.run(command, capture_output=True)
|
||||
if 'unreachable' in str(output.stdout):
|
||||
raise RuntimeError('Host unreachable')
|
||||
4
examples/ethernet/sublayer/sdkconfig.ci.defaults
Normal file
4
examples/ethernet/sublayer/sdkconfig.ci.defaults
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
|
||||
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
|
||||
CONFIG_ETHERNET_PHY_IP101=y
|
||||
4
examples/ethernet/sublayer/sdkconfig.ci.defaults_esp32p4
Normal file
4
examples/ethernet/sublayer/sdkconfig.ci.defaults_esp32p4
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
|
||||
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
|
||||
CONFIG_ETHERNET_PHY_IP101=y
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32s31"
|
||||
|
||||
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
|
||||
CONFIG_ETHERNET_PHY_YT8531=y
|
||||
5
examples/ethernet/sublayer/sdkconfig.defaults
Normal file
5
examples/ethernet/sublayer/sdkconfig.defaults
Normal file
@@ -0,0 +1,5 @@
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_ETH_SUBLAYER_SUPPORT=y
|
||||
CONFIG_ETH_SUBLAYER_VLAN_SUPPORT=y
|
||||
CONFIG_ETH_TRANSMIT_MUTEX=n
|
||||
CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX=y
|
||||
Reference in New Issue
Block a user