diff --git a/components/esp_netif/Kconfig b/components/esp_netif/Kconfig index 660134a2dda..68a32041853 100644 --- a/components/esp_netif/Kconfig +++ b/components/esp_netif/Kconfig @@ -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. diff --git a/components/esp_netif/include/esp_vfs_l2tap.h b/components/esp_netif/include/esp_vfs_l2tap.h index 8c76c6fbcae..64603eda3b4 100644 --- a/components/esp_netif/include/esp_vfs_l2tap.h +++ b/components/esp_netif/include/esp_vfs_l2tap.h @@ -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 +#include #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 } diff --git a/components/esp_netif/lwip/netif/ethernetif.c b/components/esp_netif/lwip/netif/ethernetif.c index 021fb2bd916..96c904463f5 100644 --- a/components/esp_netif/lwip/netif/ethernetif.c +++ b/components/esp_netif/lwip/netif/ethernetif.c @@ -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 */ diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/main/Kconfig.projbuild b/components/esp_netif/test_apps/test_app_vfs_l2tap/main/Kconfig.projbuild new file mode 100644 index 00000000000..df85ee58b60 --- /dev/null +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/main/Kconfig.projbuild @@ -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 diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/main/test_vfs_l2tap.c b/components/esp_netif/test_apps/test_app_vfs_l2tap/main/test_vfs_l2tap.c index 62bc53ad29d..3b4513255a5 100644 --- a/components/esp_netif/test_apps/test_app_vfs_l2tap/main/test_vfs_l2tap.c +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/main/test_vfs_l2tap.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-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(); diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/pytest_esp_vfs_l2tap.py b/components/esp_netif/test_apps/test_app_vfs_l2tap/pytest_esp_vfs_l2tap.py index 237510d992e..f32b7bd00f1 100644 --- a/components/esp_netif/test_apps/test_app_vfs_l2tap/pytest_esp_vfs_l2tap.py +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/pytest_esp_vfs_l2tap.py @@ -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'], ) diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.ci.sub_esp32 b/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.ci.sub_esp32 new file mode 100644 index 00000000000..fa571e7d5f3 --- /dev/null +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.ci.sub_esp32 @@ -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 diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.ci.sub_esp32p4 b/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.ci.sub_esp32p4 new file mode 100644 index 00000000000..6dab2b3ae7a --- /dev/null +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.ci.sub_esp32p4 @@ -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 diff --git a/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.defaults b/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.defaults index d5365ebde97..f4a08701a8b 100644 --- a/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.defaults +++ b/components/esp_netif/test_apps/test_app_vfs_l2tap/sdkconfig.defaults @@ -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 diff --git a/components/esp_netif/vfs_l2tap/esp_vfs_l2tap.c b/components/esp_netif/vfs_l2tap/esp_vfs_l2tap.c index ee459577ac3..003fb36d499 100644 --- a/components/esp_netif/vfs_l2tap/esp_vfs_l2tap.c +++ b/components/esp_netif/vfs_l2tap/esp_vfs_l2tap.c @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include #include #include #include @@ -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();