From 66939c5aa868dcea6d32aca7ccad2eef3efbed60 Mon Sep 17 00:00:00 2001 From: Ibrahim Abdalkader Date: Fri, 7 Mar 2025 14:39:48 +0100 Subject: [PATCH] drivers: wifi: Add WiFi drivers for ESP hosted firmware. This is a host driver for ESP32 chips running the esp-hosted firmware, which turns ESP32s into a WLAN/BT co-processor. Signed-off-by: Ibrahim Abdalkader --- drivers/wifi/CMakeLists.txt | 1 + drivers/wifi/Kconfig | 1 + drivers/wifi/esp_hosted/CMakeLists.txt | 27 + drivers/wifi/esp_hosted/Kconfig.esp_hosted | 44 ++ drivers/wifi/esp_hosted/esp_hosted_hal.c | 85 +++ drivers/wifi/esp_hosted/esp_hosted_hal.h | 12 + .../wifi/esp_hosted/esp_hosted_proto.options | 8 + drivers/wifi/esp_hosted/esp_hosted_proto.pb | 439 ++++++++++++ drivers/wifi/esp_hosted/esp_hosted_util.h | 174 +++++ drivers/wifi/esp_hosted/esp_hosted_wifi.c | 644 ++++++++++++++++++ drivers/wifi/esp_hosted/esp_hosted_wifi.h | 120 ++++ dts/bindings/wifi/espressif,esp-hosted.yaml | 21 + 12 files changed, 1576 insertions(+) create mode 100644 drivers/wifi/esp_hosted/CMakeLists.txt create mode 100644 drivers/wifi/esp_hosted/Kconfig.esp_hosted create mode 100644 drivers/wifi/esp_hosted/esp_hosted_hal.c create mode 100644 drivers/wifi/esp_hosted/esp_hosted_hal.h create mode 100644 drivers/wifi/esp_hosted/esp_hosted_proto.options create mode 100644 drivers/wifi/esp_hosted/esp_hosted_proto.pb create mode 100644 drivers/wifi/esp_hosted/esp_hosted_util.h create mode 100644 drivers/wifi/esp_hosted/esp_hosted_wifi.c create mode 100644 drivers/wifi/esp_hosted/esp_hosted_wifi.h create mode 100644 dts/bindings/wifi/espressif,esp-hosted.yaml diff --git a/drivers/wifi/CMakeLists.txt b/drivers/wifi/CMakeLists.txt index 96c24d245b68..2a410cad8a8a 100644 --- a/drivers/wifi/CMakeLists.txt +++ b/drivers/wifi/CMakeLists.txt @@ -20,6 +20,7 @@ endif() # CONFIG_BUILD_ONLY_NO_BLOBS add_subdirectory_ifdef(CONFIG_WIFI_ESP_AT esp_at) add_subdirectory_ifdef(CONFIG_WIFI_ESP32 esp32) +add_subdirectory_ifdef(CONFIG_WIFI_ESP_HOSTED esp_hosted) add_subdirectory_ifdef(CONFIG_WIFI_ESWIFI eswifi) add_subdirectory_ifdef(CONFIG_WIFI_SIMPLELINK simplelink) add_subdirectory_ifdef(CONFIG_WIFI_WINC1500 winc1500) diff --git a/drivers/wifi/Kconfig b/drivers/wifi/Kconfig index b6f7cfcb84c6..77db02fa9c82 100644 --- a/drivers/wifi/Kconfig +++ b/drivers/wifi/Kconfig @@ -40,6 +40,7 @@ source "drivers/wifi/simplelink/Kconfig.simplelink" source "drivers/wifi/eswifi/Kconfig.eswifi" source "drivers/wifi/esp_at/Kconfig.esp_at" source "drivers/wifi/esp32/Kconfig.esp32" +source "drivers/wifi/esp_hosted/Kconfig.esp_hosted" source "drivers/wifi/nxp/Kconfig.nxp" source "drivers/wifi/infineon/Kconfig.airoc" source "drivers/wifi/nrf_wifi/Kconfig.nrfwifi" diff --git a/drivers/wifi/esp_hosted/CMakeLists.txt b/drivers/wifi/esp_hosted/CMakeLists.txt new file mode 100644 index 000000000000..15bbb990a4b1 --- /dev/null +++ b/drivers/wifi/esp_hosted/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2025 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +if (CONFIG_WIFI_ESP_HOSTED) + +zephyr_library_named(esp_hosted) + +list(APPEND CMAKE_MODULE_PATH ${ZEPHYR_BASE}/modules/nanopb) +include(nanopb) + +zephyr_library_sources( + esp_hosted_hal.c + esp_hosted_wifi.c +) + +zephyr_nanopb_sources(esp_hosted + esp_hosted_proto.pb +) + +zephyr_include_directories( + ./ +) + +zephyr_compile_definitions( + PB_ENABLE_MALLOC=1 +) +endif() diff --git a/drivers/wifi/esp_hosted/Kconfig.esp_hosted b/drivers/wifi/esp_hosted/Kconfig.esp_hosted new file mode 100644 index 000000000000..3136d9631519 --- /dev/null +++ b/drivers/wifi/esp_hosted/Kconfig.esp_hosted @@ -0,0 +1,44 @@ +# ESP Hosted WiFi driver options + +# Copyright (c) 2025 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +menuconfig WIFI_ESP_HOSTED + bool "ESP_HOSTED driver support" + depends on DT_HAS_ESPRESSIF_ESP_HOSTED_ENABLED + default y + select GPIO + select SPI + select WIFI_OFFLOAD + select WIFI_NM + select NANOPB + select NET_L2_ETHERNET + select NET_L2_WIFI_MGMT + select WIFI_USE_NATIVE_NETWORKING + +if WIFI_ESP_HOSTED + +config WIFI_ESP_HOSTED_DEBUG + bool "Extra debugging messages" + +config WIFI_ESP_HOSTED_EVENT_TASK_PRIORITY + int "Event task priority" + default 4 + +config WIFI_ESP_HOSTED_EVENT_TASK_STACK_SIZE + int "Event task stack size" + default 16384 + +config WIFI_ESP_HOSTED_EVENT_TASK_POLL_MS + int "Event task poll rate in ms" + default 20 + +config WIFI_ESP_HOSTED_AP_CLIENTS_MAX + int "Max number of AP clients" + default 5 + +config WIFI_ESP_HOSTED_AP_CHANNEL_DEF + int "Default AP channel" + default 9 + +endif diff --git a/drivers/wifi/esp_hosted/esp_hosted_hal.c b/drivers/wifi/esp_hosted/esp_hosted_hal.c new file mode 100644 index 000000000000..2c97006bf8d4 --- /dev/null +++ b/drivers/wifi/esp_hosted/esp_hosted_hal.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include "esp_hosted_wifi.h" + +#include +LOG_MODULE_REGISTER(esp_hosted_hal, CONFIG_WIFI_LOG_LEVEL); + +int esp_hosted_hal_init(const struct device *dev) +{ + const esp_hosted_config_t *config = dev->config; + + if (!spi_is_ready_dt(&config->spi_bus)) { + LOG_ERR("SPI device is not ready"); + return -ENODEV; + } + + /* Configure pins. */ + gpio_pin_configure_dt(&config->dataready_gpio, GPIO_OUTPUT); + gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT); + + /* Perform a hard-reset */ + gpio_pin_set_dt(&config->dataready_gpio, 1); + gpio_pin_set_dt(&config->reset_gpio, 0); + k_msleep(100); + gpio_pin_set_dt(&config->reset_gpio, 1); + k_msleep(500); + + /* Configure handshake/dataready pins. */ + gpio_pin_configure_dt(&config->dataready_gpio, GPIO_INPUT); + gpio_pin_configure_dt(&config->handshake_gpio, GPIO_INPUT); + + return 0; +} + +bool esp_hosted_hal_data_ready(const struct device *dev) +{ + const esp_hosted_config_t *config = dev->config; + + return gpio_pin_get_dt(&config->dataready_gpio); +} + +int esp_hosted_hal_spi_transfer(const struct device *dev, void *tx, void *rx, uint32_t size) +{ + int ret = 0; + esp_hosted_data_t *data = dev->data; + const esp_hosted_config_t *config = dev->config; + + const struct spi_buf tx_buf = {.buf = tx ? tx : rx, .len = size}; + const struct spi_buf_set tx_set = {.buffers = &tx_buf, .count = 1}; + + const struct spi_buf rx_buf = {.buf = rx ? rx : tx, .len = size}; + const struct spi_buf_set rx_set = {.buffers = &rx_buf, .count = 1}; + + /* Wait for handshake pin to go high. */ + for (uint64_t start = k_uptime_get();; k_msleep(1)) { + if (gpio_pin_get_dt(&config->handshake_gpio) && + (rx == NULL || gpio_pin_get_dt(&config->dataready_gpio))) { + break; + } + if ((k_uptime_get() - start) >= 100) { + return -ETIMEDOUT; + } + } + + if (k_sem_take(&data->bus_sem, K_FOREVER) != 0) { + return -1; + } + + /* Transfer SPI buffers. */ + if (spi_transceive_dt(&config->spi_bus, &tx_set, &rx_set)) { + LOG_ERR("spi_transceive failed"); + ret = -EIO; + } + + k_sem_give(&data->bus_sem); + return ret; +} diff --git a/drivers/wifi/esp_hosted/esp_hosted_hal.h b/drivers/wifi/esp_hosted/esp_hosted_hal.h new file mode 100644 index 000000000000..37affb99d56b --- /dev/null +++ b/drivers/wifi/esp_hosted/esp_hosted_hal.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDED_DRIVERS_ESP_HOSTED_HAL_H +#define ZEPHYR_INCLUDED_DRIVERS_ESP_HOSTED_HAL_H +int esp_hosted_hal_init(const struct device *dev); +bool esp_hosted_hal_data_ready(const struct device *dev); +int esp_hosted_hal_spi_transfer(const struct device *dev, void *tx, void *rx, uint32_t size); +#endif /* ZEPHYR_INCLUDED_DRIVERS_ESP_HOSTED_HAL_H */ diff --git a/drivers/wifi/esp_hosted/esp_hosted_proto.options b/drivers/wifi/esp_hosted/esp_hosted_proto.options new file mode 100644 index 000000000000..c2f71c1061aa --- /dev/null +++ b/drivers/wifi/esp_hosted/esp_hosted_proto.options @@ -0,0 +1,8 @@ +*.ssid max_size:32 max_length:31 +*.bssid max_size:17 +*.mac max_size:17 +*.pwd max_size:64 max_length:63 +*.entries type:FT_POINTER +*.stations type:FT_POINTER +*.init_data max_size:0 +esp_hosted_proto.pb max_size:256 max_count:64 anonymous_oneof:true type:FT_STATIC diff --git a/drivers/wifi/esp_hosted/esp_hosted_proto.pb b/drivers/wifi/esp_hosted/esp_hosted_proto.pb new file mode 100644 index 000000000000..a8de528b9f9b --- /dev/null +++ b/drivers/wifi/esp_hosted/esp_hosted_proto.pb @@ -0,0 +1,439 @@ +/* Copyright (C) 2015-2023 Espressif Systems (Shanghai) PTE LTD */ +/* SPDX-License-Identifier: Apache-2.0 */ + +/* This file is sourced from + https://github.com/espressif/esp-hosted/blob/master/esp_hosted_fg/common/proto/esp_hosted_config.proto +*/ + +syntax = "proto3"; + +/* Enums similar to ESP IDF */ +enum Ctrl_VendorIEType { + Beacon = 0; + Probe_req = 1; + Probe_resp = 2; + Assoc_req = 3; + Assoc_resp = 4; +} + +enum Ctrl_VendorIEID { + ID_0 = 0; + ID_1 = 1; +} + +enum Ctrl_WifiMode { + NONE = 0; + STA = 1; + AP = 2; + APSTA = 3; +} + +enum Ctrl_WifiBw { + BW_Invalid = 0; + HT20 = 1; + HT40 = 2; +} + +enum Ctrl_WifiPowerSave { + PS_Invalid = 0; + MIN_MODEM = 1; + MAX_MODEM = 2; +} + +enum Ctrl_WifiSecProt { + Open = 0; + WEP = 1; + WPA_PSK = 2; + WPA2_PSK = 3; + WPA_WPA2_PSK = 4; + WPA2_ENTERPRISE = 5; + WPA3_PSK = 6; + WPA2_WPA3_PSK = 7; +} + +/* enums for Control path */ +enum Ctrl_Status { + Connected = 0; + Not_Connected = 1; + No_AP_Found = 2; + Connection_Fail = 3; + Invalid_Argument = 4; + Out_Of_Range = 5; +} + + +enum CtrlMsgType { + MsgType_Invalid = 0; + Req = 1; + Resp = 2; + Event = 3; + MsgType_Max = 4; +} + +enum CtrlMsgId { + MsgId_Invalid = 0; + + /** Request Msgs **/ + Req_Base = 100; + + Req_GetMacAddress = 101; + Req_SetMacAddress = 102; + Req_GetWifiMode = 103; + Req_SetWifiMode = 104; + + Req_GetAPScanList = 105; + Req_GetAPConfig = 106; + Req_ConnectAP = 107; + Req_DisconnectAP = 108; + + Req_GetSoftAPConfig = 109; + Req_SetSoftAPVendorSpecificIE = 110; + Req_StartSoftAP = 111; + Req_GetSoftAPConnectedSTAList = 112; + Req_StopSoftAP = 113; + + Req_SetPowerSaveMode = 114; + Req_GetPowerSaveMode = 115; + + Req_OTABegin = 116; + Req_OTAWrite = 117; + Req_OTAEnd = 118; + + Req_SetWifiMaxTxPower = 119; + Req_GetWifiCurrTxPower = 120; + + Req_ConfigHeartbeat = 121; + /* Add new control path command response before Req_Max + * and update Req_Max */ + Req_Max = 122; + + /** Response Msgs **/ + Resp_Base = 200; + + Resp_GetMacAddress = 201; + Resp_SetMacAddress = 202; + Resp_GetWifiMode = 203; + Resp_SetWifiMode = 204; + + Resp_GetAPScanList = 205; + Resp_GetAPConfig = 206; + Resp_ConnectAP = 207; + Resp_DisconnectAP = 208; + + Resp_GetSoftAPConfig = 209; + Resp_SetSoftAPVendorSpecificIE = 210; + Resp_StartSoftAP = 211; + Resp_GetSoftAPConnectedSTAList = 212; + Resp_StopSoftAP = 213; + + Resp_SetPowerSaveMode = 214; + Resp_GetPowerSaveMode = 215; + + Resp_OTABegin = 216; + Resp_OTAWrite = 217; + Resp_OTAEnd = 218; + + Resp_SetWifiMaxTxPower = 219; + Resp_GetWifiCurrTxPower = 220; + + Resp_ConfigHeartbeat = 221; + /* Add new control path command response before Resp_Max + * and update Resp_Max */ + Resp_Max = 222; + + /** Event Msgs **/ + Event_Base = 300; + Event_ESPInit = 301; + Event_Heartbeat = 302; + Event_StationDisconnectFromAP = 303; + Event_StationDisconnectFromESPSoftAP = 304; + /* Add new control path command notification before Event_Max + * and update Event_Max */ + Event_Max = 305; +} + +/* internal supporting structures for CtrlMsg */ +message ScanResult { + bytes ssid = 1; + uint32 chnl = 2; + int32 rssi = 3; + bytes bssid = 4; + Ctrl_WifiSecProt sec_prot = 5; +} + +message ConnectedSTAList { + bytes mac = 1; + int32 rssi = 2; +} + + +/* Control path structures */ +/** Req/Resp structure **/ +message CtrlMsg_Req_GetMacAddress { + int32 mode = 1; +} + +message CtrlMsg_Resp_GetMacAddress { + bytes mac = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_GetMode { +} + +message CtrlMsg_Resp_GetMode { + int32 mode = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_SetMode { + int32 mode = 1; +} + +message CtrlMsg_Resp_SetMode { + int32 resp = 1; +} + +message CtrlMsg_Req_GetStatus { +} + +message CtrlMsg_Resp_GetStatus { + int32 resp = 1; +} + +message CtrlMsg_Req_SetMacAddress { + bytes mac = 1; + int32 mode = 2; +} + +message CtrlMsg_Resp_SetMacAddress { + int32 resp = 1; +} + +message CtrlMsg_Req_GetAPConfig { +} + +message CtrlMsg_Resp_GetAPConfig { + bytes ssid = 1; + bytes bssid = 2; + int32 rssi = 3; + int32 chnl = 4; + Ctrl_WifiSecProt sec_prot = 5; + int32 resp = 6; +} + +message CtrlMsg_Req_ConnectAP { + string ssid = 1; + string pwd = 2; + bytes bssid = 3; + bool is_wpa3_supported = 4; + int32 listen_interval = 5; +} + +message CtrlMsg_Resp_ConnectAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg_Req_GetSoftAPConfig { +} + +message CtrlMsg_Resp_GetSoftAPConfig { + bytes ssid = 1; + bytes pwd = 2; + int32 chnl = 3; + Ctrl_WifiSecProt sec_prot = 4; + int32 max_conn = 5; + bool ssid_hidden = 6; + int32 bw = 7; + int32 resp = 8; +} + +message CtrlMsg_Req_StartSoftAP { + string ssid = 1; + string pwd = 2; + int32 chnl = 3; + Ctrl_WifiSecProt sec_prot = 4; + int32 max_conn = 5; + bool ssid_hidden = 6; + int32 bw = 7; +} + +message CtrlMsg_Resp_StartSoftAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg_Req_ScanResult { +} + +message CtrlMsg_Resp_ScanResult { + uint32 count = 1; + repeated ScanResult entries = 2; + int32 resp = 3; +} + +message CtrlMsg_Req_SoftAPConnectedSTA { +} + +message CtrlMsg_Resp_SoftAPConnectedSTA { + uint32 num = 1; + repeated ConnectedSTAList stations = 2; + int32 resp = 3; +} + +message CtrlMsg_Req_OTABegin { +} + +message CtrlMsg_Resp_OTABegin { + int32 resp = 1; +} + +message CtrlMsg_Req_OTAWrite { + bytes ota_data = 1; +} + +message CtrlMsg_Resp_OTAWrite { + int32 resp = 1; +} + +message CtrlMsg_Req_OTAEnd { +} + +message CtrlMsg_Resp_OTAEnd { + int32 resp = 1; +} + +message CtrlMsg_Req_VendorIEData { + int32 element_id = 1; + int32 length = 2; + bytes vendor_oui = 3; + int32 vendor_oui_type = 4; + bytes payload = 5; +} + +message CtrlMsg_Req_SetSoftAPVendorSpecificIE { + bool enable = 1; + Ctrl_VendorIEType type = 2; + Ctrl_VendorIEID idx = 3; + CtrlMsg_Req_VendorIEData vendor_ie_data = 4; +} + +message CtrlMsg_Resp_SetSoftAPVendorSpecificIE { + int32 resp = 1; +} + +message CtrlMsg_Req_SetWifiMaxTxPower { + int32 wifi_max_tx_power = 1; +} + +message CtrlMsg_Resp_SetWifiMaxTxPower { + int32 resp = 1; +} + +message CtrlMsg_Req_GetWifiCurrTxPower { +} + +message CtrlMsg_Resp_GetWifiCurrTxPower { + int32 wifi_curr_tx_power = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_ConfigHeartbeat { + bool enable = 1; + int32 duration = 2; +} + +message CtrlMsg_Resp_ConfigHeartbeat { + int32 resp = 1; +} + +/** Event structure **/ +message CtrlMsg_Event_ESPInit { + bytes init_data = 1; +} + +message CtrlMsg_Event_Heartbeat { + int32 hb_num = 1; +} + +message CtrlMsg_Event_StationDisconnectFromAP { + int32 resp = 1; +} + +message CtrlMsg_Event_StationDisconnectFromESPSoftAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg { + /* msg_type could be req, resp or Event */ + CtrlMsgType msg_type = 1; + + /* msg id */ + CtrlMsgId msg_id = 2; + + /* union of all msg ids */ + oneof payload { + /** Requests **/ + CtrlMsg_Req_GetMacAddress req_get_mac_address = 101; + CtrlMsg_Req_SetMacAddress req_set_mac_address = 102; + CtrlMsg_Req_GetMode req_get_wifi_mode = 103; + CtrlMsg_Req_SetMode req_set_wifi_mode = 104; + + CtrlMsg_Req_ScanResult req_scan_ap_list = 105; + CtrlMsg_Req_GetAPConfig req_get_ap_config = 106; + CtrlMsg_Req_ConnectAP req_connect_ap = 107; + CtrlMsg_Req_GetStatus req_disconnect_ap = 108; + + CtrlMsg_Req_GetSoftAPConfig req_get_softap_config = 109; + CtrlMsg_Req_SetSoftAPVendorSpecificIE req_set_softap_vendor_specific_ie = 110; + CtrlMsg_Req_StartSoftAP req_start_softap = 111; + CtrlMsg_Req_SoftAPConnectedSTA req_softap_connected_stas_list = 112; + CtrlMsg_Req_GetStatus req_stop_softap = 113; + + CtrlMsg_Req_SetMode req_set_power_save_mode = 114; + CtrlMsg_Req_GetMode req_get_power_save_mode = 115; + + CtrlMsg_Req_OTABegin req_ota_begin = 116; + CtrlMsg_Req_OTAWrite req_ota_write = 117; + CtrlMsg_Req_OTAEnd req_ota_end = 118; + + CtrlMsg_Req_SetWifiMaxTxPower req_set_wifi_max_tx_power = 119; + CtrlMsg_Req_GetWifiCurrTxPower req_get_wifi_curr_tx_power = 120; + CtrlMsg_Req_ConfigHeartbeat req_config_heartbeat = 121; + + /** Responses **/ + CtrlMsg_Resp_GetMacAddress resp_get_mac_address = 201; + CtrlMsg_Resp_SetMacAddress resp_set_mac_address = 202; + CtrlMsg_Resp_GetMode resp_get_wifi_mode = 203; + CtrlMsg_Resp_SetMode resp_set_wifi_mode = 204; + + CtrlMsg_Resp_ScanResult resp_scan_ap_list = 205; + CtrlMsg_Resp_GetAPConfig resp_get_ap_config = 206; + CtrlMsg_Resp_ConnectAP resp_connect_ap = 207; + CtrlMsg_Resp_GetStatus resp_disconnect_ap = 208; + + CtrlMsg_Resp_GetSoftAPConfig resp_get_softap_config = 209; + CtrlMsg_Resp_SetSoftAPVendorSpecificIE resp_set_softap_vendor_specific_ie = 210; + CtrlMsg_Resp_StartSoftAP resp_start_softap = 211; + CtrlMsg_Resp_SoftAPConnectedSTA resp_softap_connected_stas_list = 212; + CtrlMsg_Resp_GetStatus resp_stop_softap = 213; + + CtrlMsg_Resp_SetMode resp_set_power_save_mode = 214; + CtrlMsg_Resp_GetMode resp_get_power_save_mode = 215; + + CtrlMsg_Resp_OTABegin resp_ota_begin = 216; + CtrlMsg_Resp_OTAWrite resp_ota_write = 217; + CtrlMsg_Resp_OTAEnd resp_ota_end = 218; + CtrlMsg_Resp_SetWifiMaxTxPower resp_set_wifi_max_tx_power = 219; + CtrlMsg_Resp_GetWifiCurrTxPower resp_get_wifi_curr_tx_power = 220; + CtrlMsg_Resp_ConfigHeartbeat resp_config_heartbeat = 221; + + /** Notifications **/ + CtrlMsg_Event_ESPInit event_esp_init = 301; + CtrlMsg_Event_Heartbeat event_heartbeat = 302; + CtrlMsg_Event_StationDisconnectFromAP event_station_disconnect_from_AP = 303; + CtrlMsg_Event_StationDisconnectFromESPSoftAP event_station_disconnect_from_ESP_SoftAP = 304; + } +} diff --git a/drivers/wifi/esp_hosted/esp_hosted_util.h b/drivers/wifi/esp_hosted/esp_hosted_util.h new file mode 100644 index 000000000000..786233e26539 --- /dev/null +++ b/drivers/wifi/esp_hosted/esp_hosted_util.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2025 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_WIFI_ESP_HOSTED_UTIL_H_ +#define ZEPHYR_DRIVERS_WIFI_ESP_HOSTED_UTIL_H_ + +static inline void esp_hosted_mac_to_str(const uint8_t *mac_bytes, uint8_t *mac_str) +{ + static const char *hex = "0123456789ABCDEF"; + + for (int i = 0; i < 6; i++) { + *mac_str++ = hex[(mac_bytes[i] >> 4) & 0x0F]; + *mac_str++ = hex[mac_bytes[i] & 0x0F]; + if (i < 5) { + *mac_str++ = ':'; + } + } +} + +static inline void esp_hosted_str_to_mac(const uint8_t *mac_str, uint8_t *mac_bytes) +{ + uint8_t byte = 0; + + for (int i = 0; i < ESP_HOSTED_MAC_STR_LEN; i++) { + char c = mac_str[i]; + + if (c >= '0' && c <= '9') { + byte = (byte << 4) | (c - '0'); + } else if (c >= 'a' && c <= 'f') { + byte = (byte << 4) | (c - 'a' + 10); + } else if (c >= 'A' && c <= 'F') { + byte = (byte << 4) | (c - 'A' + 10); + } + if (c == ':' || (i + 1) == ESP_HOSTED_MAC_STR_LEN) { + *mac_bytes++ = byte; + byte = 0; + } + } +} + +static inline enum wifi_security_type esp_hosted_prot_to_sec(Ctrl_WifiSecProt sec_prot) +{ + switch (sec_prot) { + case Ctrl_WifiSecProt_Open: + return WIFI_SECURITY_TYPE_NONE; + case Ctrl_WifiSecProt_WEP: + return WIFI_SECURITY_TYPE_WEP; + case Ctrl_WifiSecProt_WPA_PSK: + return WIFI_SECURITY_TYPE_WPA_PSK; + case Ctrl_WifiSecProt_WPA2_PSK: + return WIFI_SECURITY_TYPE_PSK; + case Ctrl_WifiSecProt_WPA2_ENTERPRISE: + return WIFI_SECURITY_TYPE_EAP; + case Ctrl_WifiSecProt_WPA3_PSK: + return WIFI_SECURITY_TYPE_SAE; + case Ctrl_WifiSecProt_WPA2_WPA3_PSK: + case Ctrl_WifiSecProt_WPA_WPA2_PSK: + return WIFI_SECURITY_TYPE_WPA_AUTO_PERSONAL; + default: + return WIFI_SECURITY_TYPE_UNKNOWN; + } +} + +static inline Ctrl_WifiSecProt esp_hosted_sec_to_prot(enum wifi_security_type sec_type) +{ + switch (sec_type) { + case WIFI_SECURITY_TYPE_NONE: + return Ctrl_WifiSecProt_Open; + case WIFI_SECURITY_TYPE_WEP: + return Ctrl_WifiSecProt_WEP; + case WIFI_SECURITY_TYPE_WPA_PSK: + return Ctrl_WifiSecProt_WPA_PSK; + case WIFI_SECURITY_TYPE_PSK: + return Ctrl_WifiSecProt_WPA2_PSK; + case WIFI_SECURITY_TYPE_EAP: + return Ctrl_WifiSecProt_WPA2_ENTERPRISE; + case WIFI_SECURITY_TYPE_SAE: + return Ctrl_WifiSecProt_WPA3_PSK; + case WIFI_SECURITY_TYPE_WPA_AUTO_PERSONAL: + return Ctrl_WifiSecProt_WPA2_WPA3_PSK; + default: + return -1; + } +} + +static inline int esp_hosted_ctrl_response(CtrlMsg *ctrl_msg) +{ + /* Response values are located at a different offsets. */ + switch (ctrl_msg->msg_id) { + case CtrlMsgId_Resp_GetMacAddress: + return ctrl_msg->resp_get_mac_address.resp; + case CtrlMsgId_Resp_SetMacAddress: + return ctrl_msg->resp_set_mac_address.resp; + case CtrlMsgId_Resp_GetWifiMode: + return ctrl_msg->resp_get_wifi_mode.resp; + case CtrlMsgId_Resp_SetWifiMode: + return ctrl_msg->resp_set_wifi_mode.resp; + case CtrlMsgId_Resp_GetAPScanList: + return ctrl_msg->resp_scan_ap_list.resp; + case CtrlMsgId_Resp_GetAPConfig: + return ctrl_msg->resp_get_ap_config.resp; + case CtrlMsgId_Resp_ConnectAP: + return ctrl_msg->resp_connect_ap.resp; + case CtrlMsgId_Resp_DisconnectAP: + return ctrl_msg->resp_disconnect_ap.resp; + case CtrlMsgId_Resp_GetSoftAPConfig: + return ctrl_msg->resp_get_softap_config.resp; + case CtrlMsgId_Resp_SetSoftAPVendorSpecificIE: + return ctrl_msg->resp_set_softap_vendor_specific_ie.resp; + case CtrlMsgId_Resp_StartSoftAP: + return ctrl_msg->resp_start_softap.resp; + case CtrlMsgId_Resp_GetSoftAPConnectedSTAList: + return ctrl_msg->resp_softap_connected_stas_list.resp; + case CtrlMsgId_Resp_StopSoftAP: + return ctrl_msg->resp_stop_softap.resp; + case CtrlMsgId_Resp_SetPowerSaveMode: + return ctrl_msg->resp_set_power_save_mode.resp; + case CtrlMsgId_Resp_GetPowerSaveMode: + return ctrl_msg->resp_get_power_save_mode.resp; + case CtrlMsgId_Resp_OTABegin: + return ctrl_msg->resp_ota_begin.resp; + case CtrlMsgId_Resp_OTAWrite: + return ctrl_msg->resp_ota_write.resp; + case CtrlMsgId_Resp_OTAEnd: + return ctrl_msg->resp_ota_end.resp; + case CtrlMsgId_Resp_SetWifiMaxTxPower: + return ctrl_msg->resp_set_wifi_max_tx_power.resp; + case CtrlMsgId_Resp_GetWifiCurrTxPower: + return ctrl_msg->resp_get_wifi_curr_tx_power.resp; + case CtrlMsgId_Resp_ConfigHeartbeat: + return ctrl_msg->resp_config_heartbeat.resp; + default: + return -1; + } +} + +#if CONFIG_WIFI_ESP_HOSTED_DEBUG +static void esp_hosted_frame_dump(esp_frame_t *frame) +{ + static const char *const if_strs[] = {"STA", "AP", "SERIAL", "HCI", "PRIV", "TEST"}; + + if (frame->if_type > ESP_HOSTED_MAX_IF) { + return; + } + LOG_DBG("esp header: if %s_IF length %d offset %d checksum %d seq %d flags %x", + if_strs[frame->if_type], frame->len, frame->offset, frame->checksum, frame->seq_num, + frame->flags); + + if (frame->if_type == ESP_HOSTED_SERIAL_IF) { + LOG_DBG("tlv header: ep_type %d ep_length %d ep_value %.8s data_type %d " + "data_length %d", + frame->ep_type, frame->ep_length, frame->ep_value, frame->data_type, + frame->data_length); + } +} +#endif + +static inline uint16_t esp_hosted_frame_checksum(esp_frame_t *frame) +{ + uint16_t checksum = 0; + uint8_t *buf = (uint8_t *)frame; + + frame->checksum = 0; + for (size_t i = 0; i < (frame->len + ESP_FRAME_HEADER_SIZE); i++) { + checksum += buf[i]; + } + + return checksum; +} + +#endif /* ZEPHYR_DRIVERS_WIFI_ESP_HOSTED_UTIL_H_ */ diff --git a/drivers/wifi/esp_hosted/esp_hosted_wifi.c b/drivers/wifi/esp_hosted/esp_hosted_wifi.c new file mode 100644 index 000000000000..471dfa69967d --- /dev/null +++ b/drivers/wifi/esp_hosted/esp_hosted_wifi.c @@ -0,0 +1,644 @@ +/* + * Copyright (c) 2025 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(esp_hosted, CONFIG_WIFI_LOG_LEVEL); + +static struct k_thread esp_hosted_event_thread; +K_THREAD_STACK_DEFINE(esp_hosted_event_stack, CONFIG_WIFI_ESP_HOSTED_EVENT_TASK_STACK_SIZE); + +K_MSGQ_DEFINE(esp_hosted_msgq, sizeof(CtrlMsg), 8, 4); + +static esp_hosted_config_t esp_hosted_config = { + .reset_gpio = GPIO_DT_SPEC_INST_GET(0, reset_gpios), + .dataready_gpio = GPIO_DT_SPEC_INST_GET(0, dataready_gpios), + .handshake_gpio = GPIO_DT_SPEC_INST_GET(0, handshake_gpios), + .spi_bus = SPI_DT_SPEC_INST_GET(0, ESP_HOSTED_SPI_CONFIG, 10U)}; + +static esp_hosted_data_t esp_hosted_data = {0}; + +static int esp_hosted_recv(struct net_if *, void *, size_t); + +static size_t esp_hosted_get_iface(const struct device *dev) +{ + return (strcmp(dev->name, "sta_if") == 0) ? ESP_HOSTED_STA_IF : ESP_HOSTED_SAP_IF; +} + +static int esp_hosted_request(const struct device *dev, CtrlMsgId msg_id, CtrlMsg *ctrl_msg, + uint32_t timeout) +{ + size_t payload_size = 0; + esp_frame_t frame = {0}; + esp_hosted_data_t *data = (esp_hosted_data_t *)dev->data; + + /* Init control message */ + ctrl_msg->msg_type = CtrlMsgType_Req; + ctrl_msg->msg_id = msg_id; + ctrl_msg->which_payload = msg_id; + + /* Get packed protobuf size */ + pb_get_encoded_size(&payload_size, CtrlMsg_fields, ctrl_msg); + + if ((payload_size + TLV_HEADER_SIZE) > ESP_FRAME_MAX_PAYLOAD) { + LOG_ERR("payload size > max payload %d", msg_id); + return -1; + } + + /* Create frame. */ + frame.if_type = ESP_HOSTED_SERIAL_IF; + frame.len = payload_size + TLV_HEADER_SIZE; + frame.offset = ESP_FRAME_HEADER_SIZE; + frame.seq_num = data->seq_num++; + + frame.ep_type = TLV_HEADER_TYPE_EP; + frame.ep_length = 8; + memcpy(frame.ep_value, TLV_HEADER_EP_RESP, 8); + frame.data_type = TLV_HEADER_TYPE_DATA; + frame.data_length = payload_size; + + pb_ostream_t stream = pb_ostream_from_buffer(frame.data_value, frame.data_length); + + if (!pb_encode(&stream, CtrlMsg_fields, ctrl_msg)) { + LOG_ERR("failed to encode protobuf"); + return -1; + } + + /* Update frame checksum and send the frame. */ + frame.checksum = esp_hosted_frame_checksum(&frame); + if (esp_hosted_hal_spi_transfer(dev, &frame, NULL, ESP_FRAME_SIZE_ROUND(frame))) { + LOG_ERR("request %d failed", msg_id); + return -1; + } + return 0; +} + +static int esp_hosted_response(const struct device *dev, CtrlMsgId msg_id, CtrlMsg *ctrl_msg, + uint32_t timeout) +{ + if (k_msgq_get(&esp_hosted_msgq, ctrl_msg, K_MSEC(timeout))) { + if (timeout == 0) { + return 0; + } + LOG_ERR("failed to receive response for %d", msg_id); + return -1; + } + + if (ctrl_msg->msg_id != msg_id) { + LOG_ERR("expected id %u got id %u", msg_id, ctrl_msg->msg_id); + return -1; + } + + /* If message type is a response, check the response struct's return value. */ + if (ctrl_msg->msg_type == CtrlMsgType_Resp && esp_hosted_ctrl_response(ctrl_msg)) { + LOG_ERR("response %d failed %d", msg_id, esp_hosted_ctrl_response(ctrl_msg)); + return -1; + } + + return 0; +} + +static int esp_hosted_ctrl(const struct device *dev, CtrlMsgId req_id, CtrlMsg *ctrl_msg, + uint32_t timeout) +{ + uint32_t res_id = (req_id - CtrlMsgId_Req_Base) + CtrlMsgId_Resp_Base; + + /* Wait for event message. */ + if (req_id > CtrlMsgId_Event_Base && req_id < CtrlMsgId_Event_Max && + esp_hosted_response(dev, req_id, ctrl_msg, timeout)) { + return -1; + } + + /* Regular control message. */ + if (req_id > CtrlMsgId_Req_Base && req_id < CtrlMsgId_Req_Max && + esp_hosted_request(dev, req_id, ctrl_msg, timeout)) { + return -1; + } + + if (res_id > CtrlMsgId_Resp_Base && res_id < CtrlMsgId_Resp_Max && + esp_hosted_response(dev, res_id, ctrl_msg, timeout)) { + return -1; + } + + return 0; +} + +static void esp_hosted_event_task(const struct device *dev, void *p2, void *p3) +{ + esp_hosted_data_t *data = dev->data; + + for (;; k_msleep(CONFIG_WIFI_ESP_HOSTED_EVENT_TASK_POLL_MS)) { + esp_frame_t frame = {0}; + + do { + esp_frame_t ffrag = {0}; + + if (((ESP_FRAME_SIZE * 2) - frame.len) < ESP_FRAME_SIZE) { + /* This shouldn't happen, but if it did stop the thread. */ + LOG_ERR("spi buffer overflow offset: %d", frame.len); + return; + } + + if (esp_hosted_hal_spi_transfer(dev, NULL, &ffrag, ESP_FRAME_SIZE)) { + goto restart; + } + + if (!ESP_FRAME_CHECK_VALID(ffrag)) { + goto restart; + } + + if (ffrag.checksum != esp_hosted_frame_checksum(&ffrag)) { + LOG_ERR("invalid checksum"); + goto restart; + } + + if (frame.len == 0) { + /* First frame */ + memcpy(&frame, &ffrag, sizeof(esp_frame_t)); + } else { + /* Fragmented frame */ + if ((frame.seq_num + 1) != ffrag.seq_num) { + LOG_ERR("unexpected fragmented frame sequence"); + goto restart; + } + + /* Append the current fragment's payload. */ + memcpy(frame.payload + frame.len, ffrag.payload, ffrag.len); + + /* Update frame */ + frame.len += ffrag.len; + frame.seq_num++; + frame.flags = ffrag.flags; + LOG_DBG("received fragmented frame, length: %d", frame.len); + } + } while (frame.flags & ESP_FRAME_FLAGS_FRAGMENT); + + switch (frame.if_type) { + case ESP_HOSTED_SAP_IF: + case ESP_HOSTED_STA_IF: { + esp_hosted_recv(data->iface[frame.if_type], frame.payload, frame.len); + continue; + } + case ESP_HOSTED_PRIV_IF: { + if (frame.priv_pkt_type == ESP_PACKET_TYPE_EVENT && + frame.event_type == ESP_PRIV_EVENT_INIT) { + LOG_INF("chip id %d spi_mhz %d caps 0x%x", frame.event_data[2], + frame.event_data[5], frame.event_data[8]); + } + continue; + } + case ESP_HOSTED_SERIAL_IF: /* Requires further processing */ + break; + default: + LOG_ERR("unexpected interface type %d", frame.if_type); + continue; + } + +#if CONFIG_WIFI_ESP_HOSTED_DEBUG + esp_hosted_frame_dump(&frame); +#endif + + CtrlMsg ctrl_msg = CtrlMsg_init_zero; + pb_istream_t stream = pb_istream_from_buffer(frame.data_value, frame.data_length); + + if (!pb_decode(&stream, CtrlMsg_fields, &ctrl_msg)) { + LOG_ERR("failed to decode protobuf"); + continue; + } + + switch (ctrl_msg.msg_id) { + case CtrlMsgId_Event_Heartbeat: + data->last_hb_ms = k_uptime_get(); + continue; + case CtrlMsgId_Event_StationDisconnectFromAP: + data->state[ESP_HOSTED_STA_IF] = WIFI_STATE_DISCONNECTED; + net_if_dormant_on(data->iface[ESP_HOSTED_STA_IF]); + wifi_mgmt_raise_disconnect_result_event(data->iface[ESP_HOSTED_STA_IF], 0); + continue; + case CtrlMsgId_Event_StationDisconnectFromESPSoftAP: { + struct wifi_ap_sta_info sta_info = {0}; + + sta_info.link_mode = WIFI_LINK_MODE_UNKNOWN; + sta_info.twt_capable = false; + sta_info.mac_length = WIFI_MAC_ADDR_LEN; + esp_hosted_str_to_mac( + ctrl_msg.event_station_disconnect_from_ESP_SoftAP.mac.bytes, + sta_info.mac); + wifi_mgmt_raise_ap_sta_disconnected_event(data->iface[ESP_HOSTED_SAP_IF], + &sta_info); + continue; + } + case CtrlMsgId_Resp_ConnectAP: { + int ret = esp_hosted_ctrl_response(&ctrl_msg); + + if (!ret) { + data->state[ESP_HOSTED_STA_IF] = WIFI_STATE_COMPLETED; + net_if_dormant_off(data->iface[ESP_HOSTED_STA_IF]); + } + wifi_mgmt_raise_connect_result_event(data->iface[ESP_HOSTED_STA_IF], ret); + continue; + } + default: /* Unhandled events/responses will be queued. */ + break; + } + + /* Queue control message resp/event for further processing. */ + if (k_msgq_put(&esp_hosted_msgq, &ctrl_msg, K_FOREVER)) { + LOG_ERR("Failed to enqueue message"); + return; + } + + LOG_DBG("pushed msg_type %u msg_id %u", ctrl_msg.msg_type, ctrl_msg.msg_id); +restart:; + } +} + +static void esp_hosted_init(struct net_if *iface) +{ + const struct device *dev = net_if_get_device(iface); + esp_hosted_data_t *data = dev->data; + struct ethernet_context *eth_ctx = net_if_l2_data(iface); + struct wifi_nm_instance *nm = wifi_nm_get_instance("nm"); + size_t itf = esp_hosted_get_iface(dev); + + data->iface[itf] = iface; + eth_ctx->eth_if_type = L2_ETH_IF_TYPE_WIFI; + + /* Set mac address. */ + net_if_set_link_addr(iface, data->mac_addr[itf], 6, NET_LINK_ETHERNET); + + /* Configure interface */ + ethernet_init(iface); + net_if_dormant_on(iface); + net_if_carrier_on(iface); + + /* Register a managed interface. */ + wifi_nm_register_mgd_type_iface( + nm, itf == ESP_HOSTED_STA_IF ? WIFI_TYPE_STA : WIFI_TYPE_SAP, iface); +} + +static int esp_hosted_connect(const struct device *dev, struct wifi_connect_req_params *params) +{ + CtrlMsg ctrl_msg = CtrlMsg_init_zero; + + ctrl_msg.req_connect_ap.listen_interval = 0; + ctrl_msg.req_connect_ap.is_wpa3_supported = (bool)params->wpa3_ent_mode; + + strncpy(ctrl_msg.req_connect_ap.pwd, params->psk, ESP_HOSTED_MAX_PASS_LEN); + strncpy(ctrl_msg.req_connect_ap.ssid, params->ssid, ESP_HOSTED_MAX_SSID_LEN); + esp_hosted_mac_to_str(params->bssid, ctrl_msg.req_connect_ap.bssid.bytes); + + if (esp_hosted_ctrl(dev, CtrlMsgId_Req_ConnectAP, &ctrl_msg, 0)) { + return -EAGAIN; + } + return 0; +} + +static int esp_hosted_disconnect(const struct device *dev) +{ + int ret = 0; + esp_hosted_data_t *data = dev->data; + CtrlMsg ctrl_msg = CtrlMsg_init_zero; + struct net_if *iface = net_if_lookup_by_dev(dev); + + if (esp_hosted_ctrl(dev, CtrlMsgId_Req_DisconnectAP, &ctrl_msg, ESP_HOSTED_SYNC_TIMEOUT)) { + ret = -EIO; + } + + wifi_mgmt_raise_disconnect_result_event(iface, ret); + data->state[ESP_HOSTED_STA_IF] = WIFI_STATE_DISCONNECTED; + return ret; +} + +static int esp_hosted_ap_enable(const struct device *dev, struct wifi_connect_req_params *params) +{ + int ret = 0; + esp_hosted_data_t *data = dev->data; + CtrlMsg ctrl_msg = CtrlMsg_init_zero; + + uint32_t channel = params->channel; + Ctrl_WifiSecProt security = esp_hosted_sec_to_prot(params->security); + + if (security == -1) { + ret = -ENOTSUP; + goto exit; + } + + if (params->channel == WIFI_CHANNEL_ANY) { + channel = CONFIG_WIFI_ESP_HOSTED_AP_CHANNEL_DEF; + } + + ctrl_msg.req_start_softap.chnl = channel; + ctrl_msg.req_start_softap.bw = Ctrl_WifiBw_HT40; + ctrl_msg.req_start_softap.ssid_hidden = false; + ctrl_msg.req_start_softap.sec_prot = security; + ctrl_msg.req_start_softap.max_conn = CONFIG_WIFI_ESP_HOSTED_AP_CLIENTS_MAX; + strncpy(ctrl_msg.req_start_softap.pwd, params->psk, ESP_HOSTED_MAX_PASS_LEN); + strncpy(ctrl_msg.req_start_softap.ssid, params->ssid, ESP_HOSTED_MAX_SSID_LEN); + + /* Start Soft-AP mode. */ + if (esp_hosted_ctrl(dev, CtrlMsgId_Req_StartSoftAP, &ctrl_msg, ESP_HOSTED_SYNC_TIMEOUT)) { + ret = -EIO; + } + +exit: + if (!ret) { + data->state[ESP_HOSTED_SAP_IF] = WIFI_STATE_COMPLETED; + net_if_dormant_off(data->iface[ESP_HOSTED_SAP_IF]); + } + + wifi_mgmt_raise_ap_enable_result_event(data->iface[ESP_HOSTED_SAP_IF], ret); + return ret; +} + +static int esp_hosted_ap_disable(const struct device *dev) +{ + int ret = 0; + esp_hosted_data_t *data = dev->data; + CtrlMsg ctrl_msg = CtrlMsg_init_zero; + + if (esp_hosted_ctrl(dev, CtrlMsgId_Req_StopSoftAP, &ctrl_msg, ESP_HOSTED_SYNC_TIMEOUT)) { + ret = -EIO; + } + + data->state[ESP_HOSTED_SAP_IF] = WIFI_STATE_DISCONNECTED; + wifi_mgmt_raise_ap_disable_result_event(data->iface[ESP_HOSTED_SAP_IF], ret); + return ret; +} + +static int esp_hosted_send(const struct device *dev, struct net_pkt *pkt) +{ + esp_frame_t frame = {0}; + size_t pkt_len = net_pkt_get_len(pkt); + size_t itf = esp_hosted_get_iface(dev); +#if defined(CONFIG_NET_STATISTICS_WIFI) + esp_hosted_data_t *data = dev->data; +#endif + + if (pkt_len > ESP_FRAME_MAX_PAYLOAD) { + LOG_ERR("packet length > SPI buf length"); + return -ENOMEM; + } + + /* Create frame. */ + frame.if_type = itf; + frame.len = pkt_len; + frame.offset = ESP_FRAME_HEADER_SIZE; + + /* Copy frame payload. */ + if (net_pkt_read(pkt, frame.payload, pkt_len) < 0) { + LOG_ERR("net_pkt_read failed"); + return -EIO; + } + + /* Update frame checksum and send the frame. */ + frame.checksum = esp_hosted_frame_checksum(&frame); + if (esp_hosted_hal_spi_transfer(dev, &frame, NULL, ESP_FRAME_SIZE_ROUND(frame))) { + LOG_ERR("spi_transfer failed"); + return -EIO; + } + +#if defined(CONFIG_NET_STATISTICS_WIFI) + data->stats.pkts.tx++; + data->stats.bytes.sent += pkt_len; +#endif + + LOG_DBG("send eth frame size: %d", pkt_len); + return 0; +} + +static int esp_hosted_recv(struct net_if *iface, void *buf, size_t len) +{ + int ret = 0; + struct net_pkt *pkt = NULL; +#if defined(CONFIG_NET_STATISTICS_WIFI) + const struct device *dev = net_if_get_device(iface); + esp_hosted_data_t *data = dev->data; +#endif + + if (!iface || !net_if_flag_is_set(iface, NET_IF_UP)) { + return 0; + } + + pkt = net_pkt_rx_alloc_with_buffer(iface, len, AF_UNSPEC, 0, K_MSEC(1000)); + if (pkt == NULL) { + LOG_ERR("Failed to allocate net buffer"); + return -ENOMEM; + } + + if (net_pkt_write(pkt, buf, len) < 0) { + ret = -EIO; + LOG_ERR("Failed to write to net buffer"); + goto error; + } + + if (net_recv_data(iface, pkt) < 0) { + ret = -EIO; + LOG_ERR("Failed to push received data"); + goto error; + } + +#if defined(CONFIG_NET_STATISTICS_WIFI) + data->stats.pkts.rx++; + data->stats.bytes.received += len; +#endif + + LOG_DBG("recv eth frame size: %d", len); + return 0; + +error: + net_pkt_unref(pkt); +#if defined(CONFIG_NET_STATISTICS_WIFI) + data->stats.errors.rx++; +#endif + return ret; +} + +#if defined(CONFIG_NET_STATISTICS_WIFI) +static int esp_hosted_stats(const struct device *dev, struct net_stats_wifi *stats) +{ + esp_hosted_data_t *data = dev->data; + + stats->bytes.received = data->stats.bytes.received; + stats->bytes.sent = data->stats.bytes.sent; + stats->pkts.rx = data->stats.pkts.rx; + stats->pkts.tx = data->stats.pkts.tx; + stats->errors.rx = data->stats.errors.rx; + stats->errors.tx = data->stats.errors.tx; + stats->broadcast.rx = data->stats.broadcast.rx; + stats->broadcast.tx = data->stats.broadcast.tx; + stats->multicast.rx = data->stats.multicast.rx; + stats->multicast.tx = data->stats.multicast.tx; + stats->sta_mgmt.beacons_rx = data->stats.sta_mgmt.beacons_rx; + stats->sta_mgmt.beacons_miss = data->stats.sta_mgmt.beacons_miss; + return 0; +} +#endif + +static int esp_hosted_status(const struct device *dev, struct wifi_iface_status *status) +{ + esp_hosted_data_t *data = dev->data; + CtrlMsg ctrl_msg = CtrlMsg_init_zero; + size_t itf = esp_hosted_get_iface(dev); + + status->state = data->state[itf]; + status->band = WIFI_FREQ_BAND_2_4_GHZ; + status->link_mode = WIFI_LINK_MODE_UNKNOWN; + status->wpa3_ent_type = WIFI_WPA3_ENTERPRISE_NA; + status->mfp = WIFI_MFP_DISABLE; + status->iface_mode = (itf == ESP_HOSTED_STA_IF) ? WIFI_MODE_INFRA : WIFI_MODE_AP; + + if (status->state == WIFI_STATE_DISCONNECTED) { + return 0; + } + + if (esp_hosted_ctrl(dev, + (itf == ESP_HOSTED_STA_IF) ? CtrlMsgId_Req_GetAPConfig + : CtrlMsgId_Req_GetSoftAPConfig, + &ctrl_msg, ESP_HOSTED_SYNC_TIMEOUT)) { + return -EIO; + } + + if (itf == ESP_HOSTED_STA_IF) { + status->security = esp_hosted_prot_to_sec(ctrl_msg.resp_get_ap_config.sec_prot); + status->channel = ctrl_msg.resp_get_ap_config.chnl; + status->rssi = ctrl_msg.resp_get_ap_config.rssi; + status->ssid_len = MIN(ctrl_msg.resp_get_ap_config.ssid.size, WIFI_SSID_MAX_LEN); + memcpy(status->ssid, ctrl_msg.resp_get_ap_config.ssid.bytes, status->ssid_len); + esp_hosted_str_to_mac(ctrl_msg.resp_get_ap_config.bssid.bytes, status->bssid); + } else { + status->security = esp_hosted_prot_to_sec(ctrl_msg.resp_get_softap_config.sec_prot); + status->channel = ctrl_msg.resp_get_softap_config.chnl; + status->ssid_len = + MIN(ctrl_msg.resp_get_softap_config.ssid.size, WIFI_SSID_MAX_LEN); + memcpy(status->ssid, ctrl_msg.resp_get_softap_config.ssid.bytes, status->ssid_len); + memcpy(status->bssid, data->mac_addr[ESP_HOSTED_SAP_IF], ESP_HOSTED_MAC_ADDR_LEN); + } + return 0; +} + +static int esp_hosted_scan(const struct device *dev, struct wifi_scan_params *params, + scan_result_cb_t cb) +{ + CtrlMsg ctrl_msg = CtrlMsg_init_zero; + struct net_if *iface = net_if_lookup_by_dev(dev); + + if (esp_hosted_ctrl(dev, CtrlMsgId_Req_GetAPScanList, &ctrl_msg, ESP_HOSTED_SCAN_TIMEOUT)) { + return -ETIMEDOUT; + } + + size_t ap_count = ctrl_msg.resp_scan_ap_list.count; + ScanResult *ap_list = ctrl_msg.resp_scan_ap_list.entries; + + for (size_t i = 0; i < ap_count; i++) { + struct wifi_scan_result result = {0}; + + result.rssi = ap_list[i].rssi; + result.channel = ap_list[i].chnl; + result.security = esp_hosted_prot_to_sec(ap_list[i].sec_prot); + + if (ap_list[i].ssid.size) { + result.ssid_length = MIN(ap_list[i].ssid.size, ESP_HOSTED_MAX_SSID_LEN); + memcpy(result.ssid, ap_list[i].ssid.bytes, result.ssid_length); + } + + if (ap_list[i].bssid.size) { + result.mac_length = MIN(ap_list[i].bssid.size, ESP_HOSTED_MAC_ADDR_LEN); + esp_hosted_str_to_mac(ap_list[i].bssid.bytes, result.mac); + } + + cb(iface, 0, &result); + k_yield(); + } + + /* End of scan */ + cb(iface, 0, NULL); + + /* Entries are dynamically allocated. */ + pb_release(CtrlMsg_fields, &ctrl_msg); + return 0; +} + +static int esp_hosted_dev_init(const struct device *dev) +{ + esp_hosted_data_t *data = dev->data; + + /* Pins config and SPI init. */ + if (esp_hosted_hal_init(dev)) { + return -EAGAIN; + } + + /* Initialize semaphores. */ + if (k_sem_init(&data->bus_sem, 1, 1)) { + LOG_ERR("k_sem_init() failed"); + return -EINVAL; + } + + data->tid = k_thread_create(&esp_hosted_event_thread, esp_hosted_event_stack, + K_THREAD_STACK_SIZEOF(esp_hosted_event_stack), + (k_thread_entry_t)esp_hosted_event_task, (void *)dev, NULL, + NULL, CONFIG_WIFI_ESP_HOSTED_EVENT_TASK_PRIORITY, + K_INHERIT_PERMS, K_NO_WAIT); + + if (!data->tid) { + LOG_ERR("ERROR spawning event processing thread"); + return -EAGAIN; + } + + /* Wait for an ESPInit control event. */ + CtrlMsg ctrl_msg = CtrlMsg_init_zero; + + if (esp_hosted_ctrl(dev, CtrlMsgId_Event_ESPInit, &ctrl_msg, ESP_HOSTED_SYNC_TIMEOUT)) { + return -EIO; + } + + /* Set MAC addresses. */ + for (size_t i = 0; i < 2; i++) { + ctrl_msg.req_get_mac_address.mode = i + 1; + if (esp_hosted_ctrl(dev, CtrlMsgId_Req_GetMacAddress, &ctrl_msg, + ESP_HOSTED_SYNC_TIMEOUT)) { + return -EIO; + } + esp_hosted_str_to_mac(ctrl_msg.resp_get_mac_address.mac.bytes, data->mac_addr[i]); + } + return 0; +} + +static const struct wifi_mgmt_ops esp_hosted_mgmt = { + .scan = esp_hosted_scan, + .connect = esp_hosted_connect, + .disconnect = esp_hosted_disconnect, + .ap_enable = esp_hosted_ap_enable, + .ap_disable = esp_hosted_ap_disable, +#if defined(CONFIG_NET_STATISTICS_WIFI) + .get_stats = esp_hosted_stats, +#endif + .iface_status = esp_hosted_status, +}; + +static const struct net_wifi_mgmt_offload esp_hosted_api = { + .wifi_iface.iface_api.init = esp_hosted_init, + .wifi_iface.send = esp_hosted_send, + .wifi_mgmt_api = &esp_hosted_mgmt, +}; + +NET_DEVICE_INIT_INSTANCE(0, "sta_if", 0, esp_hosted_dev_init, NULL, &esp_hosted_data, + &esp_hosted_config, CONFIG_WIFI_INIT_PRIORITY, &esp_hosted_api, + ETHERNET_L2, NET_L2_GET_CTX_TYPE(ETHERNET_L2), NET_ETH_MTU); + +NET_DEVICE_INIT_INSTANCE(1, "sap_if", 1, NULL, NULL, &esp_hosted_data, &esp_hosted_config, + CONFIG_WIFI_INIT_PRIORITY, &esp_hosted_api, ETHERNET_L2, + NET_L2_GET_CTX_TYPE(ETHERNET_L2), NET_ETH_MTU); + +DEFINE_WIFI_NM_INSTANCE(nm, &esp_hosted_mgmt); +CONNECTIVITY_WIFI_MGMT_BIND(Z_DEVICE_DT_DEV_ID(DT_DRV_INST(0))); diff --git a/drivers/wifi/esp_hosted/esp_hosted_wifi.h b/drivers/wifi/esp_hosted/esp_hosted_wifi.h new file mode 100644 index 000000000000..0b2c0c1ca4c8 --- /dev/null +++ b/drivers/wifi/esp_hosted/esp_hosted_wifi.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2025 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_WIFI_ESP_HOSTED_WIFI_H_ +#define ZEPHYR_DRIVERS_WIFI_ESP_HOSTED_WIFI_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#define DT_DRV_COMPAT espressif_esp_hosted + +#define ESP_HOSTED_MAC_ADDR_LEN (6) +#define ESP_HOSTED_MAC_STR_LEN (17) +#define ESP_HOSTED_MAX_SSID_LEN (32) +#define ESP_HOSTED_MAX_PASS_LEN (64) +#define ESP_HOSTED_SYNC_TIMEOUT (5000) +#define ESP_HOSTED_SCAN_TIMEOUT (10000) +#define ESP_HOSTED_SPI_CONFIG \ + (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8) | SPI_MODE_CPOL) + +typedef enum { + ESP_HOSTED_STA_IF = 0, + ESP_HOSTED_SAP_IF, + ESP_HOSTED_SERIAL_IF, + ESP_HOSTED_HCI_IF, + ESP_HOSTED_PRIV_IF, + ESP_HOSTED_TEST_IF, + ESP_HOSTED_MAX_IF, +} esp_hosted_interface_t; + +typedef struct { + struct gpio_dt_spec cs_gpio; + struct gpio_dt_spec reset_gpio; + struct gpio_dt_spec dataready_gpio; + struct gpio_dt_spec handshake_gpio; + struct spi_dt_spec spi_bus; +} esp_hosted_config_t; + +typedef struct { + uint16_t seq_num; + uint64_t last_hb_ms; + struct net_if *iface[2]; + uint8_t mac_addr[2][6]; + k_tid_t tid; + struct k_sem bus_sem; +#if defined(CONFIG_NET_STATISTICS_WIFI) + struct net_stats_wifi stats; +#endif + enum wifi_iface_state state[2]; +} esp_hosted_data_t; + +#define TLV_HEADER_SIZE (14) +#define TLV_HEADER_TYPE_EP (1) +#define TLV_HEADER_TYPE_DATA (2) +#define TLV_HEADER_EP_RESP "ctrlResp" +#define TLV_HEADER_EP_EVENT "ctrlEvnt" + +#define ESP_FRAME_SIZE (1600) +#define ESP_FRAME_HEADER_SIZE (12) +#define ESP_FRAME_MAX_PAYLOAD (ESP_FRAME_SIZE - ESP_FRAME_HEADER_SIZE) +#define ESP_FRAME_FLAGS_FRAGMENT (1 << 0) + +typedef enum { + ESP_PACKET_TYPE_EVENT, +} esp_hosted_priv_packet_t; + +typedef enum { + ESP_PRIV_EVENT_INIT, +} esp_hosted_priv_event_t; + +typedef struct __packed { + uint8_t if_type: 4; + uint8_t if_num: 4; + uint8_t flags; + uint16_t len; + uint16_t offset; + uint16_t checksum; + uint16_t seq_num; + uint8_t reserved2; + union { + uint8_t hci_pkt_type; + uint8_t priv_pkt_type; + }; + union { + struct __packed { + uint8_t event_type; + uint8_t event_len; + uint8_t event_data[]; + }; + struct __packed { + uint8_t ep_type; + uint16_t ep_length; + uint8_t ep_value[8]; + uint8_t data_type; + uint16_t data_length; + uint8_t data_value[]; + }; + struct { + /* To support fragmented frames, reserve 2x payload size. */ + uint8_t payload[ESP_FRAME_MAX_PAYLOAD * 2]; + }; + }; +} esp_frame_t; + +#define ESP_FRAME_CHECK_VALID(frame) \ + (frame.len > 0 && frame.len <= ESP_FRAME_MAX_PAYLOAD && \ + frame.offset == ESP_FRAME_HEADER_SIZE) + +#define ESP_FRAME_SIZE_ROUND(frame) ((ESP_FRAME_HEADER_SIZE + frame.len + 3) & ~3U) + +#endif /* ZEPHYR_DRIVERS_WIFI_ESP_HOSTED_WIFI_H_ */ diff --git a/dts/bindings/wifi/espressif,esp-hosted.yaml b/dts/bindings/wifi/espressif,esp-hosted.yaml new file mode 100644 index 000000000000..395c480e194c --- /dev/null +++ b/dts/bindings/wifi/espressif,esp-hosted.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2025 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +description: Espressif ESP-Hosted WiFi + +compatible: "espressif,esp-hosted" + +include: spi-device.yaml + +properties: + reset-gpios: + type: phandle-array + required: true + + dataready-gpios: + type: phandle-array + required: true + + handshake-gpios: + type: phandle-array + required: true