feat(esp_netif): integrate L2TAP with Ethernet sublayer

Route L2TAP TX/RX through the iodriver provider, support offset RX
buffers in ethernetif, and extend L2TAP tests for sublayer configs.
This commit is contained in:
Ondrej Kosta
2026-08-12 12:39:10 +02:00
parent 2e79c3fd69
commit ddaf9c2b79
10 changed files with 432 additions and 75 deletions

View File

@@ -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.

View File

@@ -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
}

View File

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

View File

@@ -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

View File

@@ -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(&eth_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, &eth_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(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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, &eth_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, &eth_type_filter));
TEST_ASSERT_EQUAL(EINVAL, errno);
TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd_2, L2TAP_G_RCV_FILTER, &eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_network_hndls);
ethernet_init(&eth_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(&eth_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(&eth_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, &eth_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(&eth_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();

View File

@@ -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'],
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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, &eth_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();