Files
esp-idf/components/esp_eth/include/esp_eth_sublayer.h
Ondrej Kosta 2e79c3fd69 feat(esp_eth): add Ethernet sublayer with optional VLAN and switch
Introduce the experimental Ethernet sublayer (parent/child VLAN, optional
switch path, iodriver provider), restructure eth test apps, and document
the new APIs.
2026-08-13 13:56:57 +02:00

361 lines
16 KiB
C

/*
* 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