From ecedd78302cb2236a2175d4a2c9a09b482c8db03 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 21 Jun 2023 17:13:09 +0200 Subject: [PATCH] drivers/esp-hosted: Add host driver 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: iabdalkader --- drivers/esp-hosted/README.md | 24 + drivers/esp-hosted/esp_hosted.proto | 439 ++++++++++++++ drivers/esp-hosted/esp_hosted_bthci.c | 149 +++++ drivers/esp-hosted/esp_hosted_hal.c | 209 +++++++ drivers/esp-hosted/esp_hosted_hal.h | 82 +++ drivers/esp-hosted/esp_hosted_internal.h | 117 ++++ drivers/esp-hosted/esp_hosted_netif.c | 167 ++++++ drivers/esp-hosted/esp_hosted_netif.h | 38 ++ drivers/esp-hosted/esp_hosted_stack.h | 63 ++ drivers/esp-hosted/esp_hosted_wifi.c | 697 +++++++++++++++++++++++ drivers/esp-hosted/esp_hosted_wifi.h | 110 ++++ extmod/extmod.mk | 43 ++ 12 files changed, 2138 insertions(+) create mode 100644 drivers/esp-hosted/README.md create mode 100644 drivers/esp-hosted/esp_hosted.proto create mode 100644 drivers/esp-hosted/esp_hosted_bthci.c create mode 100644 drivers/esp-hosted/esp_hosted_hal.c create mode 100644 drivers/esp-hosted/esp_hosted_hal.h create mode 100644 drivers/esp-hosted/esp_hosted_internal.h create mode 100644 drivers/esp-hosted/esp_hosted_netif.c create mode 100644 drivers/esp-hosted/esp_hosted_netif.h create mode 100644 drivers/esp-hosted/esp_hosted_stack.h create mode 100644 drivers/esp-hosted/esp_hosted_wifi.c create mode 100644 drivers/esp-hosted/esp_hosted_wifi.h diff --git a/drivers/esp-hosted/README.md b/drivers/esp-hosted/README.md new file mode 100644 index 000000000..f4ecfd23d --- /dev/null +++ b/drivers/esp-hosted/README.md @@ -0,0 +1,24 @@ +# esp-hosted driver + +This is a MicroPython driver for the Espressif +[esp_hosted](https://github.com/espressif/esp-hosted/#readme) communications +coprocessor, which allows creating a Wi-Fi and/or Bluetooth interface from +MicroPython to a separate connected ESP32 compatible device running the +`esp_hosted` firmware. + +## Building + +Enable this driver by setting `MICROPY_PY_NETWORK_ESP_HOSTED` to 1 in your +Makefile. If `MICROPY_PY_BLUETOOTH` is set then the Bluetooth host driver will +also be built. + +In addition to normal MicroPython build requirements, building this driver +requires the [protocol buffer +compiler](https://github.com/protocolbuffers/protobuf#protobuf-compiler-installation) +(protoc) to be installed. + +On Debian/Ubuntu, it can be installed by running: + +``` +sudo apt-get install protobuf-compiler +``` diff --git a/drivers/esp-hosted/esp_hosted.proto b/drivers/esp-hosted/esp_hosted.proto new file mode 100644 index 000000000..8a51e9502 --- /dev/null +++ b/drivers/esp-hosted/esp_hosted.proto @@ -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; + string 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/esp-hosted/esp_hosted_bthci.c b/drivers/esp-hosted/esp_hosted_bthci.c new file mode 100644 index 000000000..003054460 --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_bthci.c @@ -0,0 +1,149 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ESP-Hosted Bluetooth HCI driver. + */ + +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_PY_NETWORK_ESP_HOSTED + +#include +#include + +#include "py/runtime.h" +#include "extmod/mpbthci.h" +#include "esp_hosted_hal.h" + +#define HCI_COMMAND_PACKET (0x01) +#define HCI_ACLDATA_PACKET (0x02) +#define HCI_EVENT_PACKET (0x04) + +#define HCI_COMMAND_COMPLETE (0x0e) +#define HCI_COMMAND_TIMEOUT (3000) + +#define OGF_LINK_CTL (0x01) +#define OGF_HOST_CTL (0x03) + +#define OCF_SET_EVENT_MASK (0x0001) +#define OCF_RESET (0x0003) + +// Provided by the port, and also possibly shared with the stack. +extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + +int esp_hosted_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) { + uint8_t *buf = mp_bluetooth_hci_cmd_buf; + + buf[0] = HCI_COMMAND_PACKET; + buf[1] = ocf; + buf[2] = ogf << 2 | ocf >> 8; + buf[3] = param_len; + + if (param_len) { + memcpy(buf + 4, param_buf, param_len); + } + + debug_printf("HCI Command: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]); + + mp_bluetooth_hci_uart_write(buf, 4 + param_len); + + // Receive HCI event packet, initially reading 3 bytes (HCI Event, Event code, Plen). + for (mp_uint_t start = mp_hal_ticks_ms(), size = 3, i = 0; i < size;) { + while (!mp_bluetooth_hci_uart_any()) { + MICROPY_EVENT_POLL_HOOK + // Timeout. + if ((mp_hal_ticks_ms() - start) > HCI_COMMAND_TIMEOUT) { + error_printf("timeout waiting for HCI packet\n"); + return -1; + } + } + + buf[i] = mp_bluetooth_hci_uart_readchar(); + + // There seems to be a sync issue with this fw/module. + if (i == 0 && buf[0] == 0xFF) { + continue; + } + + // Check for packet type. + if (i == 0 && buf[0] != HCI_EVENT_PACKET) { + error_printf("unexpected HCI packet: %02x\n", buf[0]); + return -1; + } + + // Sanity check the packet parameters length. + if (i == 2 && ((size += buf[2]) > sizeof(mp_bluetooth_hci_cmd_buf))) { + error_printf("unexpected event packet length: %d\n", size); + return -1; + } + + i++; + } + + // We're only looking for command complete events. + if (buf[1] != HCI_COMMAND_COMPLETE || buf[4] != ocf || buf[5] != (ogf << 2 | ocf >> 8)) { + error_printf("response mismatch: %02x %02x\n", buf[4], buf[5]); + return -1; + } + + // Log event. + debug_printf("HCI Event packet: %02x %02x %02x %02x %02x %02x %02x\n", + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + + // Status code. + return buf[6]; +} + +int mp_bluetooth_hci_controller_init(void) { + // Low-level pins init, memory pool allocation etc... + esp_hosted_hal_init(ESP_HOSTED_MODE_BT); + + mp_uint_t start = mp_hal_ticks_ms(); + // Skip bootloader messages. + while ((mp_hal_ticks_ms() - start) < 2500) { + if (mp_bluetooth_hci_uart_any()) { + mp_bluetooth_hci_uart_readchar(); + } + MICROPY_EVENT_POLL_HOOK + } + + #ifdef MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY + mp_bluetooth_hci_uart_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY); + #endif + + mp_hal_pin_output(MICROPY_HW_BLE_UART_RTS); + mp_hal_pin_write(MICROPY_HW_BLE_UART_RTS, 0); + + // Send reset command + // It seems that nothing else is needed for now. + return esp_hosted_hci_cmd(OGF_HOST_CTL, OCF_RESET, 0, NULL); +} + +int mp_bluetooth_hci_controller_deinit(void) { + esp_hosted_hal_deinit(); + return 0; +} + +#endif diff --git a/drivers/esp-hosted/esp_hosted_hal.c b/drivers/esp-hosted/esp_hosted_hal.c new file mode 100644 index 000000000..d8e2e6ece --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_hal.c @@ -0,0 +1,209 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ESP-Hosted WiFi HAL. + */ + +#include "py/mphal.h" + +#if MICROPY_PY_NETWORK_ESP_HOSTED + +#include +#include + +#include "py/runtime.h" +#include "modmachine.h" +#include "extmod/machine_spi.h" +#include "mpconfigboard.h" + +#include "esp_hosted_hal.h" +#include "esp_hosted_wifi.h" + +#ifndef MICROPY_HW_WIFI_IRQ +#define MICROPY_HW_WIFI_IRQ MICROPY_HW_WIFI_HANDSHAKE +#endif + +STATIC mp_obj_t esp_hosted_pin_irq_callback(mp_obj_t self_in) { + extern void mod_network_poll_events(void); + mod_network_poll_events(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_hosted_pin_irq_callback_obj, esp_hosted_pin_irq_callback); + +MP_WEAK int esp_hosted_hal_init(uint32_t mode) { + // Perform a hard reset and set pins to their defaults. + esp_hosted_hal_deinit(); + + if (mode == ESP_HOSTED_MODE_BT) { + // For Bluetooth mode, init is done. + return 0; + } + + mp_hal_pin_input(MICROPY_HW_WIFI_HANDSHAKE); + mp_hal_pin_input(MICROPY_HW_WIFI_DATAREADY); + + // Enable Pin-IRQ for the handshake PIN to call esp_hosted_wifi_poll() + mp_obj_t irq_rising_attr[2]; + mp_load_method_maybe((mp_obj_t)MICROPY_HW_WIFI_IRQ, MP_QSTR_IRQ_RISING, irq_rising_attr); + + if (irq_rising_attr[0] != MP_OBJ_NULL && irq_rising_attr[1] == MP_OBJ_NULL) { // value for IRQ rising found + mp_obj_t pin_args[] = { + NULL, // Method pointer + (mp_obj_t)MICROPY_HW_WIFI_IRQ, // Pin object + (mp_obj_t)&esp_hosted_pin_irq_callback_obj, // Callback function object + NULL, // The Rising edge value is set below. + mp_const_true, // Hard IRQ, since the actual polling is scheduled. + }; + pin_args[3] = irq_rising_attr[0]; + mp_load_method_maybe((mp_obj_t)MICROPY_HW_WIFI_IRQ, MP_QSTR_irq, pin_args); + if (pin_args[0] != MP_OBJ_NULL && pin_args[1] != MP_OBJ_NULL) { + mp_call_method_n_kw(3, 0, pin_args); + } + } + + // Initialize SPI. + mp_obj_t args[] = { + MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_ID), + MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_BAUDRATE), + MP_OBJ_NEW_QSTR(MP_QSTR_polarity), MP_OBJ_NEW_SMALL_INT(1), + }; + + MP_STATE_PORT(mp_wifi_spi) = + MP_OBJ_TYPE_GET_SLOT(&machine_spi_type, make_new)((mp_obj_t)&machine_spi_type, 2, 1, args); + + // SPI might change the direction/mode of CS pin, + // set it to GPIO again just in case. + mp_hal_pin_output(MICROPY_HW_WIFI_SPI_CS); + mp_hal_pin_write(MICROPY_HW_WIFI_SPI_CS, 1); + return 0; +} + +MP_WEAK int esp_hosted_hal_deinit(void) { + // Disable Pin-IRQ for the handshake PIN + mp_obj_t pin_args[] = { + NULL, // Method pointer + (mp_obj_t)MICROPY_HW_WIFI_IRQ, // Pin object + mp_const_none // Set to None + }; + mp_load_method_maybe((mp_obj_t)MICROPY_HW_WIFI_IRQ, MP_QSTR_irq, pin_args); + if (pin_args[0] && pin_args[1]) { + mp_call_method_n_kw(1, 0, pin_args); + } + + // Remove all network interfaces and reset wifi state. + esp_hosted_wifi_deinit(); + + mp_hal_pin_output(MICROPY_HW_ESP_HOSTED_GPIO0); + mp_hal_pin_output(MICROPY_HW_ESP_HOSTED_RESET); + + #ifndef MICROPY_HW_ESP_HOSTED_SHARED_PINS + mp_hal_pin_output(MICROPY_HW_WIFI_SPI_CS); + mp_hal_pin_write(MICROPY_HW_WIFI_SPI_CS, 1); + #endif + + // Perform a hard reset + mp_hal_pin_write(MICROPY_HW_ESP_HOSTED_GPIO0, 1); + mp_hal_pin_write(MICROPY_HW_ESP_HOSTED_RESET, 0); + mp_hal_delay_ms(100); + mp_hal_pin_write(MICROPY_HW_ESP_HOSTED_RESET, 1); + mp_hal_delay_ms(500); + + MP_STATE_PORT(mp_wifi_spi) = MP_OBJ_NULL; + return 0; +} + +MP_WEAK int esp_hosted_hal_atomic_enter(void) { + #if MICROPY_ENABLE_SCHEDULER + mp_sched_lock(); + #endif + return 0; +} + +MP_WEAK int esp_hosted_hal_atomic_exit(void) { + #if MICROPY_ENABLE_SCHEDULER + mp_sched_unlock(); + #endif + return 0; +} + +MP_WEAK bool esp_hosted_hal_data_ready(void) { + return mp_hal_pin_read(MICROPY_HW_WIFI_DATAREADY) && mp_hal_pin_read(MICROPY_HW_WIFI_HANDSHAKE); +} + +MP_WEAK int esp_hosted_hal_spi_transfer(const uint8_t *tx_buf, uint8_t *rx_buf, uint32_t size) { + mp_obj_t mp_wifi_spi = MP_STATE_PORT(mp_wifi_spi); + const mp_machine_spi_p_t *spi_proto = MP_OBJ_TYPE_GET_SLOT(&machine_spi_type, protocol); + + // Wait for handshake pin to go high. + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(1)) { + if (mp_hal_pin_read(MICROPY_HW_WIFI_HANDSHAKE)) { + break; + } + if ((mp_hal_ticks_ms() - start) >= 1000) { + error_printf("timeout waiting for handshake\n"); + return -1; + } + } + + mp_hal_pin_write(MICROPY_HW_WIFI_SPI_CS, 0); + mp_hal_delay_us(10); + spi_proto->transfer(mp_wifi_spi, size, tx_buf, rx_buf); + mp_hal_pin_write(MICROPY_HW_WIFI_SPI_CS, 1); + return 0; +} + +MP_WEAK void *esp_hosted_hal_alloc(void *user, size_t size) { + (void)user; + void *mem = m_malloc0(size); + return mem; +} + +MP_WEAK void esp_hosted_hal_free(void *user, void *ptr) { + (void)user; + m_free(ptr); +} + +MP_WEAK void *esp_hosted_hal_calloc(size_t nmemb, size_t size) { + return NULL; +} + +MP_WEAK void *esp_hosted_hal_realloc(void *ptr, size_t size) { + return NULL; +} + +// Those are provided for protobuf-c's internally +// defined allocator, and are not actually used. +MP_WEAK void *malloc(size_t size) { + (void)size; + debug_printf("system malloc called\n"); + return NULL; +} + +MP_WEAK void free(void *ptr) { + (void)ptr; + debug_printf("system free called\n"); +} + +#endif // MICROPY_PY_NETWORK_NINAW10 diff --git a/drivers/esp-hosted/esp_hosted_hal.h b/drivers/esp-hosted/esp_hosted_hal.h new file mode 100644 index 000000000..abf060c89 --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_hal.h @@ -0,0 +1,82 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ESP-Hosted WiFi HAL. + */ + +#ifndef MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_HAL_H +#define MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_HAL_H + +#ifndef ESP_HOSTED_DEBUG +#define ESP_HOSTED_DEBUG (0) +#endif + +#if ESP_HOSTED_DEBUG +#define PROTOBUF_C_UNPACK_ERROR(...) error_printf(__VA_ARGS__); +#endif + +#define ANSI_C_RED "\x1B[31m" +#define ANSI_C_GREEN "\x1B[32m" +#define ANSI_C_YELLOW "\x1B[33m" +#define ANSI_C_BLUE "\x1B[34m" +#define ANSI_C_MAGENTA "\x1B[35m" +#define ANSI_C_CYAN "\x1B[36m" +#define ANSI_C_WHITE "\x1B[37m" +#define ANSI_C_DEFAULT "\x1B[0m" + +#if ESP_HOSTED_DEBUG +#define do_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__) +#else +#define do_printf(...) +#endif + +// Logging macros. +#define debug_printf(...) do_printf(ANSI_C_BLUE); do_printf(__VA_ARGS__); do_printf(ANSI_C_DEFAULT); +#define info_printf(...) do_printf(ANSI_C_GREEN); do_printf(__VA_ARGS__); do_printf(ANSI_C_DEFAULT); +#define warn_printf(...) do_printf(ANSI_C_YELLOW); do_printf(__VA_ARGS__); do_printf(ANSI_C_DEFAULT); +#define error_printf(...) do_printf(ANSI_C_RED); do_printf(__VA_ARGS__); do_printf(ANSI_C_DEFAULT); +#define crit_printf(...) do_printf(ANSI_C_MAGENTA); do_printf(__VA_ARGS__); do_printf(ANSI_C_DEFAULT); + +typedef enum { + ESP_HOSTED_MODE_BT, + ESP_HOSTED_MODE_WIFI, +} esp_hosted_mode_t; + +// HAL functions to be implemented by ports. +// Note A default machine-based implementation is provided in esp_hosted_hal.c. +int esp_hosted_hal_init(uint32_t mode); +int esp_hosted_hal_deinit(void); +bool esp_hosted_hal_data_ready(void); +int esp_hosted_hal_atomic_enter(void); +int esp_hosted_hal_atomic_exit(void); +int esp_hosted_hal_spi_transfer(const uint8_t *tx_buf, uint8_t *rx_buf, uint32_t size); + +// Memory management functions. +// Note alloc/free need to match the Protobuf allocator signature. +void *esp_hosted_hal_alloc(void *user, size_t size); +void esp_hosted_hal_free(void *user, void *ptr); +void *esp_hosted_hal_calloc(size_t nmemb, size_t size); +void *esp_hosted_hal_realloc(void *ptr, size_t size); +#endif // MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_HAL_H diff --git a/drivers/esp-hosted/esp_hosted_internal.h b/drivers/esp-hosted/esp_hosted_internal.h new file mode 100644 index 000000000..f57cd210a --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_internal.h @@ -0,0 +1,117 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ESP-Hosted internal. + */ + +#ifndef MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_INTERNAL_H +#define MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_INTERNAL_H + +#define ESP_HOSTED_DEBUG (0) + +#define ESP_FRAME_MAX_SIZE (1600) +#define ESP_FRAME_MAX_PAYLOAD (ESP_FRAME_MAX_SIZE - sizeof(esp_header_t)) +#define ESP_FRAME_FLAGS_FRAGMENT (1 << 0) + +#define ESP_STATE_NUM_ITFS (2) +#define ESP_STATE_BUF_SIZE (ESP_FRAME_MAX_SIZE * 2) +#define ESP_STACK_CAPACITY (32) +#define ESP_SYNC_REQ_TIMEOUT (5000) + +#define TLV_HEADER_TYPE_EP (1) +#define TLV_HEADER_TYPE_DATA (2) +#define TLV_HEADER_EP_RESP "ctrlResp" +#define TLV_HEADER_EP_EVENT "ctrlEvnt" + +typedef enum { + ESP_HOSTED_FLAGS_RESET = (0 << 0), + ESP_HOSTED_FLAGS_INIT = (1 << 0), + ESP_HOSTED_FLAGS_ACTIVE = (1 << 1), + ESP_HOSTED_FLAGS_STATIC_IP = (1 << 2), + ESP_HOSTED_FLAGS_AP_STARTED = (1 << 3), + ESP_HOSTED_FLAGS_STA_CONNECTED = (1 << 4), +} esp_hosted_flags_t; + +typedef enum { + ESP_PACKET_TYPE_EVENT, +} esp_hosted_priv_packet_t; + +typedef enum { + ESP_PRIV_EVENT_INIT, +} esp_hosted_priv_event_t; + +typedef struct esp_hosted_stack { + size_t capacity; + size_t top; + void *buff[ESP_STACK_CAPACITY]; +} esp_hosted_stack_t; + +typedef struct esp_hosted_state { + uint8_t chip_id; + uint8_t spi_clk; + uint8_t chip_flags; + uint8_t flags; + uint16_t seq_num; + uint32_t last_hb_ms; + struct netif netif[ESP_STATE_NUM_ITFS]; + struct dhcp dhcp_client; + dhcp_server_t dhcp_server; + esp_hosted_stack_t stack; + uint8_t buf[ESP_STATE_BUF_SIZE]; +} esp_hosted_state_t; + +typedef struct __attribute__((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[]; +} tlv_header_t; + +typedef struct __attribute__((packed)) { + uint8_t event_type; + uint8_t event_len; + uint8_t event_data[]; +} esp_event_t; + +typedef struct __attribute__((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; + }; + uint8_t payload[]; +} esp_header_t; + +uint16_t esp_hosted_checksum(esp_header_t *esp_header); +#endif // MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_INTERNAL_H diff --git a/drivers/esp-hosted/esp_hosted_netif.c b/drivers/esp-hosted/esp_hosted_netif.c new file mode 100644 index 000000000..b471c3ad4 --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_netif.c @@ -0,0 +1,167 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ESP-Hosted LWIP netif functions. + */ + +#include "py/mphal.h" +#include "py/mperrno.h" + +#if MICROPY_PY_NETWORK_ESP_HOSTED + +#include +#include +#include + +#include "lwip/err.h" +#include "lwip/dns.h" +#include "lwip/dhcp.h" +#include "netif/etharp.h" + +#include "shared/netutils/netutils.h" +#include "shared/netutils/dhcpserver.h" + +#include "esp_hosted_hal.h" +#include "esp_hosted_wifi.h" +#include "esp_hosted_netif.h" +#include "esp_hosted_internal.h" + +static err_t netif_struct_init(struct netif *netif) { + netif->linkoutput = esp_hosted_netif_output; + netif->output = etharp_output; + netif->mtu = 1500; + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP; + esp_hosted_wifi_get_mac(netif->name[1] - '0', netif->hwaddr); + netif->hwaddr_len = sizeof(netif->hwaddr); + info_printf("netif_init() netif initialized\n"); + return ERR_OK; +} + +int esp_hosted_netif_init(esp_hosted_state_t *state, uint32_t itf) { + struct netif *netif = &state->netif[itf]; + + ip_addr_t ipconfig[4]; + ipconfig[0].addr = 0; + ipconfig[1].addr = 0; + ipconfig[2].addr = 0; + ipconfig[3].addr = 0; + + if (itf == ESP_HOSTED_AP_IF) { + ipconfig[0].addr = PP_HTONL(ESP_HOSTED_AP_ADDRESS); + ipconfig[1].addr = PP_HTONL(ESP_HOSTED_AP_NETMASK); + ipconfig[2].addr = PP_HTONL(ESP_HOSTED_AP_GATEWAY); + } + + netif->name[0] = 'w'; + netif->name[1] = '0' + itf; + + netif_add(netif, ip_2_ip4(&ipconfig[0]), ip_2_ip4(&ipconfig[1]), + ip_2_ip4(&ipconfig[2]), state, netif_struct_init, ethernet_input); + + netif_set_hostname(netif, ESP_HOSTED_HOSTNAME); + netif_set_default(netif); + netif_set_up(netif); + netif_set_link_up(netif); + dns_setserver(0, &ipconfig[3]); + + if (itf == ESP_HOSTED_STA_IF) { + dhcp_set_struct(netif, &state->dhcp_client); + dhcp_start(netif); + } else { + dhcp_server_init(&state->dhcp_server, &ipconfig[0], &ipconfig[1]); + } + return 0; +} + +int esp_hosted_netif_deinit(esp_hosted_state_t *state, uint32_t itf) { + struct netif *netif = &state->netif[itf]; + + if (netif_is_link_up(netif)) { + if (itf == ESP_HOSTED_STA_IF) { + dhcp_stop(netif); + } else { + dhcp_server_deinit(&state->dhcp_server); + } + } + + for (struct netif *netifp = netif_list; netif != NULL; netif = netif->next) { + if (netif == netifp) { + netif_remove(netif); + netif->flags = 0; + return 0; + } + } + + return -1; +} + +int esp_hosted_netif_input(esp_hosted_state_t *state, uint32_t itf, const void *buf, size_t len) { + struct netif *netif = &state->netif[itf]; + + struct pbuf *p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); + if (p == NULL) { + error_printf("esp_hosted_netif_input() failed to alloc pbuf %d\n", len); + return -1; + } + // Copy buf to pbuf + pbuf_take(p, buf, len); + + if (netif->input(p, netif) != ERR_OK) { + error_printf("esp_hosted_netif_input() netif input failed\n"); + pbuf_free(p); + return -1; + } + + debug_printf("esp_hosted_netif_input() eth frame input %d\n", len); + return 0; +} + +err_t esp_hosted_netif_output(struct netif *netif, struct pbuf *p) { + esp_hosted_state_t *state = netif->state; + + if (p->tot_len > ESP_FRAME_MAX_PAYLOAD) { + error_printf("esp_hosted_netif_output() pbuf len > SPI buf len\n"); + return ERR_IF; + } + + esp_header_t *esp_header = (esp_header_t *)(state->buf); + esp_header->if_type = netif->name[1] - '0'; + esp_header->if_num = 0; + esp_header->flags = 0; + esp_header->len = p->tot_len; + esp_header->offset = sizeof(esp_header_t); + esp_header->seq_num = 0; + pbuf_copy_partial(p, esp_header->payload, p->tot_len, 0); + esp_header->checksum = esp_hosted_checksum(esp_header); + + size_t frame_size = (sizeof(esp_header_t) + esp_header->len + 3) & ~3U; + if (esp_hosted_hal_spi_transfer(state->buf, NULL, frame_size) != 0) { + error_printf("esp_hosted_netif_output() failed to send eth frame\n"); + return ERR_IF; + } + debug_printf("esp_hosted_netif_output() if %d pbuf len %d\n", esp_header->if_type, esp_header->len); + return ERR_OK; +} +#endif diff --git a/drivers/esp-hosted/esp_hosted_netif.h b/drivers/esp-hosted/esp_hosted_netif.h new file mode 100644 index 000000000..656f90d31 --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_netif.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ESP-Hosted LWIP netif functions. + */ + +#ifndef MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_NETIF_H +#define MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_NETIF_H + +typedef struct esp_hosted_state esp_hosted_state_t; +int esp_hosted_netif_init(esp_hosted_state_t *state, uint32_t itf); +int esp_hosted_netif_deinit(esp_hosted_state_t *state, uint32_t itf); +int esp_hosted_netif_input(esp_hosted_state_t *state, uint32_t itf, const void *buf, size_t len); +err_t esp_hosted_netif_output(struct netif *netif, struct pbuf *p); + +#endif // MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_NETIF_H diff --git a/drivers/esp-hosted/esp_hosted_stack.h b/drivers/esp-hosted/esp_hosted_stack.h new file mode 100644 index 000000000..8fa45cc43 --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_stack.h @@ -0,0 +1,63 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Simple stack for control messages. + */ + +#ifndef MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_STACK_H +#define MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_STACK_H + +#include +#include +#include "esp_hosted_internal.h" + +static inline void esp_hosted_stack_init(esp_hosted_stack_t *stack) { + stack->capacity = ESP_STACK_CAPACITY; + stack->top = 0; +} + +static inline bool esp_hosted_stack_empty(esp_hosted_stack_t *stack) { + return stack->top == 0; +} + +static inline bool esp_hosted_stack_push(esp_hosted_stack_t *stack, void *data) { + if (stack->top < stack->capacity) { + stack->buff[stack->top++] = data; + return true; + } + return false; +} + +static inline void *esp_hosted_stack_pop(esp_hosted_stack_t *stack, bool peek) { + void *data = NULL; + if (stack->top > 0) { + data = stack->buff[stack->top - 1]; + if (!peek) { + stack->top -= 1; + } + } + return data; +} +#endif // MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_STACK_H diff --git a/drivers/esp-hosted/esp_hosted_wifi.c b/drivers/esp-hosted/esp_hosted_wifi.c new file mode 100644 index 000000000..3cc153205 --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_wifi.c @@ -0,0 +1,697 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ESP-Hosted WiFi driver. + */ + +#include "py/mphal.h" +#include "py/mperrno.h" + +#if MICROPY_PY_NETWORK_ESP_HOSTED + +#include +#include +#include + +#include "lwip/err.h" +#include "lwip/dns.h" +#include "lwip/dhcp.h" +#include "netif/etharp.h" + +#include "shared/netutils/netutils.h" +#include "shared/netutils/dhcpserver.h" + +#include "esp_hosted.pb-c.h" + +#include "esp_hosted_hal.h" +#include "esp_hosted_stack.h" +#include "esp_hosted_netif.h" +#include "esp_hosted_wifi.h" +#include "esp_hosted_internal.h" + +static esp_hosted_state_t esp_state; + +static ProtobufCAllocator protobuf_alloc = { + .alloc = &esp_hosted_hal_alloc, + .free = &esp_hosted_hal_free, + .allocator_data = NULL, +}; + +static void esp_hosted_macstr_to_bytes(const uint8_t *mac_str, size_t mac_len, uint8_t *mac_out) { + uint8_t byte = 0; + for (int i = 0; i < mac_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) == mac_len) { + *mac_out++ = byte; + byte = 0; + } + } +} + +// to avoid bleeding the protocol buffer API into the public interface, convert esp_hosted_security_t +// to/from CtrlWifiSecProt here. + +static esp_hosted_security_t sec_prot_to_hosted_security(CtrlWifiSecProt sec_prot) +{ + switch (sec_prot) { + case CTRL__WIFI_SEC_PROT__Open: + return ESP_HOSTED_SEC_OPEN; + case CTRL__WIFI_SEC_PROT__WEP: + return ESP_HOSTED_SEC_WEP; + case CTRL__WIFI_SEC_PROT__WPA_PSK: + return ESP_HOSTED_SEC_WPA_PSK; + case CTRL__WIFI_SEC_PROT__WPA2_PSK: + return ESP_HOSTED_SEC_WPA2_PSK; + case CTRL__WIFI_SEC_PROT__WPA_WPA2_PSK: + return ESP_HOSTED_SEC_WPA_WPA2_PSK; + case CTRL__WIFI_SEC_PROT__WPA2_ENTERPRISE: + return ESP_HOSTED_SEC_WPA2_ENTERPRISE; + case CTRL__WIFI_SEC_PROT__WPA3_PSK: + return ESP_HOSTED_SEC_WPA3_PSK; + case CTRL__WIFI_SEC_PROT__WPA2_WPA3_PSK: + return ESP_HOSTED_SEC_WPA2_WPA3_PSK; + default: + return ESP_HOSTED_SEC_INVALID; + } +} + +static CtrlWifiSecProt hosted_security_to_sec_prot(esp_hosted_security_t hosted_security) +{ + switch (hosted_security) { + case ESP_HOSTED_SEC_OPEN: + return CTRL__WIFI_SEC_PROT__Open; + case ESP_HOSTED_SEC_WEP: + return CTRL__WIFI_SEC_PROT__WEP; + case ESP_HOSTED_SEC_WPA_PSK: + return CTRL__WIFI_SEC_PROT__WPA_PSK; + case ESP_HOSTED_SEC_WPA2_PSK: + return CTRL__WIFI_SEC_PROT__WPA2_PSK; + case ESP_HOSTED_SEC_WPA_WPA2_PSK: + return CTRL__WIFI_SEC_PROT__WPA_WPA2_PSK; + case ESP_HOSTED_SEC_WPA2_ENTERPRISE: + return CTRL__WIFI_SEC_PROT__WPA2_ENTERPRISE; + case ESP_HOSTED_SEC_WPA3_PSK: + return CTRL__WIFI_SEC_PROT__WPA3_PSK; + case ESP_HOSTED_SEC_WPA2_WPA3_PSK: + return CTRL__WIFI_SEC_PROT__WPA2_WPA3_PSK; + default: + abort(); // Range should be checked by the caller, making this unreachable + } +} + +uint16_t esp_hosted_checksum(esp_header_t *esp_header) { + uint16_t checksum = 0; + esp_header->checksum = 0; + uint8_t *buf = (uint8_t *)esp_header; + for (size_t i = 0; i < (esp_header->len + sizeof(esp_header_t)); i++) { + checksum += buf[i]; + } + return checksum; +} + +#if ESP_HOSTED_DEBUG +static void esp_hosted_dump_header(esp_header_t *esp_header) { + static const char *if_strs[] = { "STA", "AP", "SERIAL", "HCI", "PRIV", "TEST" }; + if (esp_header->if_type > ESP_HOSTED_MAX_IF) { + return; + } + debug_printf("esp header: if %s_IF length %d offset %d checksum %d seq %d flags %x\n", + if_strs[esp_header->if_type], esp_header->len, esp_header->offset, + esp_header->checksum, esp_header->seq_num, esp_header->flags); + + if (esp_header->if_type == ESP_HOSTED_SERIAL_IF) { + tlv_header_t *tlv_header = (tlv_header_t *)(esp_header->payload); + debug_printf("tlv header: ep_type %d ep_length %d ep_value %.8s data_type %d data_length %d\n", + tlv_header->ep_type, tlv_header->ep_length, + tlv_header->ep_value, tlv_header->data_type, tlv_header->data_length); + } +} +#endif + +static int32_t esp_hosted_resp_value(CtrlMsg *ctrl_msg) { + // Each response struct return value is located at a different offset, + // the following array maps response CtrlMsgs to return values (resp) + // offsets within each response struct. + const static size_t ctrl_msg_resp_offset[] = { + offsetof(CtrlMsgRespGetMacAddress, resp), + offsetof(CtrlMsgRespSetMacAddress, resp), + offsetof(CtrlMsgRespGetMode, resp), + offsetof(CtrlMsgRespSetMode, resp), + offsetof(CtrlMsgRespScanResult, resp), + offsetof(CtrlMsgRespGetAPConfig, resp), + offsetof(CtrlMsgRespConnectAP, resp), + offsetof(CtrlMsgRespGetStatus, resp), + offsetof(CtrlMsgRespGetSoftAPConfig, resp), + offsetof(CtrlMsgRespSetSoftAPVendorSpecificIE, resp), + offsetof(CtrlMsgRespStartSoftAP, resp), + offsetof(CtrlMsgRespSoftAPConnectedSTA, resp), + offsetof(CtrlMsgRespGetStatus, resp), + offsetof(CtrlMsgRespSetMode, resp), + offsetof(CtrlMsgRespGetMode, resp), + offsetof(CtrlMsgRespOTABegin, resp), + offsetof(CtrlMsgRespOTAWrite, resp), + offsetof(CtrlMsgRespOTAEnd, resp), + offsetof(CtrlMsgRespSetWifiMaxTxPower, resp), + offsetof(CtrlMsgRespGetWifiCurrTxPower, resp), + offsetof(CtrlMsgRespConfigHeartbeat, resp), + }; + + int32_t resp = -1; + size_t index = ctrl_msg->msg_id - CTRL_MSG_ID__Resp_Base; + + // All types of messages share the same payload base address. + if (ctrl_msg->resp_get_mac_address != NULL && + ctrl_msg->msg_type == CTRL_MSG_TYPE__Resp && + index > 0 && index <= MP_ARRAY_SIZE(ctrl_msg_resp_offset)) { + // Return the response struct's return value. + size_t offset = ctrl_msg_resp_offset[index - 1]; + resp = *((int32_t *)((char *)ctrl_msg->resp_get_mac_address + offset)); + } + return resp; +} + +static int esp_hosted_request(CtrlMsgId msg_id, void *ctrl_payload) { + CtrlMsg ctrl_msg = {0}; + ctrl_msg__init(&ctrl_msg); + ctrl_msg.msg_id = msg_id; + ctrl_msg.payload_case = msg_id; + + // All types of messages share the same payload base address. + ctrl_msg.req_get_mac_address = ctrl_payload; + + // Pack protobuf + size_t payload_size = ctrl_msg__get_packed_size(&ctrl_msg); + if ((payload_size + sizeof(tlv_header_t)) > ESP_FRAME_MAX_PAYLOAD) { + error_printf("esp_hosted_request() payload size > max payload %d\n", msg_id); + return -1; + } + + esp_header_t *esp_header = (esp_header_t *)(esp_state.buf); + tlv_header_t *tlv_header = (tlv_header_t *)(esp_header->payload); + + esp_header->if_type = ESP_HOSTED_SERIAL_IF; + esp_header->if_num = 0; + esp_header->flags = 0; + esp_header->len = payload_size + sizeof(tlv_header_t); + esp_header->offset = sizeof(esp_header_t); + esp_header->seq_num = esp_state.seq_num++; + + tlv_header->ep_type = TLV_HEADER_TYPE_EP; + tlv_header->ep_length = 8; + memcpy(tlv_header->ep_value, TLV_HEADER_EP_RESP, 8); + tlv_header->data_type = TLV_HEADER_TYPE_DATA; + tlv_header->data_length = payload_size; + ctrl_msg__pack(&ctrl_msg, tlv_header->data); + esp_header->checksum = esp_hosted_checksum(esp_header); + + size_t frame_size = (sizeof(esp_header_t) + esp_header->len + 3) & ~3U; + if (esp_hosted_hal_spi_transfer(esp_state.buf, NULL, frame_size) != 0) { + error_printf("esp_hosted_request() request %d failed\n", msg_id); + return -1; + } + return 0; +} + +static CtrlMsg *esp_hosted_response(CtrlMsgId msg_id, uint32_t timeout) { + CtrlMsg *ctrl_msg = NULL; + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(10)) { + if (!esp_hosted_stack_empty(&esp_state.stack)) { + ctrl_msg = esp_hosted_stack_pop(&esp_state.stack, true); + if (ctrl_msg->msg_id == msg_id) { + ctrl_msg = esp_hosted_stack_pop(&esp_state.stack, false); + break; + } + + debug_printf("esp_hosted_response() waiting for id %lu last id %lu\n", msg_id, ctrl_msg->msg_id); + ctrl_msg = NULL; + } + + if (timeout == 0) { + // Request expected a sync response. + return NULL; + } + + // Check timeout. + if ((mp_hal_ticks_ms() - start) >= timeout) { + return NULL; + } + + MICROPY_EVENT_POLL_HOOK + } + + // If message type is a response, check the response struct's return value. + if (ctrl_msg->msg_type == CTRL_MSG_TYPE__Resp && esp_hosted_resp_value(ctrl_msg) != 0) { + error_printf("esp_hosted_response() response %d failed %d\n", msg_id, esp_hosted_resp_value(ctrl_msg)); + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + return NULL; + } + + return ctrl_msg; +} + +static int esp_hosted_ctrl(CtrlMsgId req_id, void *req_payload, CtrlMsg **resp_msg) { + if (esp_hosted_request(req_id, req_payload) != 0) { + return -1; + } + uint32_t resp_id = (req_id - CTRL_MSG_ID__Req_Base) + CTRL_MSG_ID__Resp_Base; + if ((*resp_msg = esp_hosted_response(resp_id, ESP_SYNC_REQ_TIMEOUT)) == NULL) { + return -1; + } + return 0; +} + +int esp_hosted_wifi_poll(void) { + size_t offset = 0; + esp_header_t *esp_header = (esp_header_t *)(esp_state.buf); + tlv_header_t *tlv_header = (tlv_header_t *)(esp_header->payload); + + if (!(esp_state.flags & ESP_HOSTED_FLAGS_INIT) || !esp_hosted_hal_data_ready()) { + return 0; + } + + do { + esp_header_t *frag_header = (esp_header_t *)(esp_state.buf + offset); + if ((ESP_STATE_BUF_SIZE - offset) < ESP_FRAME_MAX_SIZE) { + // This shouldn't happen, but if it did stop polling. + error_printf("esp_hosted_poll() spi buffer overflow offs %d\n", offset); + return -1; + } + + if (esp_hosted_hal_spi_transfer(NULL, esp_state.buf + offset, ESP_FRAME_MAX_SIZE) != 0) { + error_printf("esp_hosted_poll() spi transfer failed\n"); + return 0; + } + + if (frag_header->len == 0 || + frag_header->len > ESP_FRAME_MAX_PAYLOAD || + frag_header->offset != sizeof(esp_header_t)) { + // Invalid or empty packet, just ignore it silently. + warn_printf("esp_hosted_poll() invalid frame size %d offset %d\n", + esp_header->len, esp_header->offset); + return 0; + } + + uint16_t checksum = frag_header->checksum; + frag_header->checksum = esp_hosted_checksum(frag_header); + if (frag_header->checksum != checksum) { + warn_printf("esp_hosted_poll() invalid checksum, expected %d\n", checksum); + return 0; + } + + if (offset) { + // Combine fragmented packet + if ((esp_header->seq_num + 1) != frag_header->seq_num) { + error_printf("esp_hosted_poll() fragmented frame sequence mismatch\n"); + return 0; + } + esp_header->len += frag_header->len; + esp_header->seq_num = frag_header->seq_num; + esp_header->flags = frag_header->flags; + info_printf("esp_hosted_poll() received fragmented packet %d\n", frag_header->len); + // Append the current fragment's payload to the previous one. + memcpy(esp_state.buf + offset, frag_header->payload, frag_header->len); + } + + offset = sizeof(esp_header_t) + esp_header->len; + } while ((esp_header->flags & ESP_FRAME_FLAGS_FRAGMENT)); + + #if ESP_HOSTED_DEBUG + esp_hosted_dump_header(esp_header); + #endif + + switch (esp_header->if_type) { + case ESP_HOSTED_STA_IF: + case ESP_HOSTED_AP_IF: { + // Networking traffic + uint32_t itf = esp_header->if_type; + if (netif_is_link_up(&esp_state.netif[itf])) { + if (esp_hosted_netif_input(&esp_state, itf, esp_header->payload, esp_header->len) != 0) { + error_printf("esp_hosted_poll() netif input failed\n"); + return -1; + } + debug_printf("esp_hosted_poll() eth frame input %d\n", esp_header->len); + } + return 0; + } + case ESP_HOSTED_PRIV_IF: { + esp_event_t *priv_event = (esp_event_t *)(esp_header->payload); + if (esp_header->priv_pkt_type == ESP_PACKET_TYPE_EVENT && + priv_event->event_type == ESP_PRIV_EVENT_INIT) { + esp_state.chip_id = priv_event->event_data[2]; + esp_state.spi_clk = priv_event->event_data[5]; + esp_state.chip_flags = priv_event->event_data[8]; + info_printf("esp_hosted_poll() chip id %d spi_mhz %d caps 0x%x\n", + esp_state.chip_id, esp_state.spi_clk, esp_state.chip_flags); + } + return 0; + } + case ESP_HOSTED_HCI_IF: + case ESP_HOSTED_TEST_IF: + case ESP_HOSTED_MAX_IF: + error_printf("esp_hosted_poll() unexpected interface type %d\n", esp_header->if_type); + return 0; + case ESP_HOSTED_SERIAL_IF: + // Requires further processing + break; + } + + CtrlMsg *ctrl_msg = ctrl_msg__unpack(&protobuf_alloc, tlv_header->data_length, tlv_header->data); + if (ctrl_msg == NULL) { + error_printf("esp_hosted_poll() failed to unpack protobuf\n"); + return 0; + } + + if (ctrl_msg->msg_type == CTRL_MSG_TYPE__Event) { + switch (ctrl_msg->msg_id) { + case CTRL_MSG_ID__Event_ESPInit: + esp_state.flags |= ESP_HOSTED_FLAGS_ACTIVE; + break; + case CTRL_MSG_ID__Event_Heartbeat: + esp_state.last_hb_ms = mp_hal_ticks_ms(); + info_printf("esp_hosted_poll() heartbeat %lu\n", esp_state.last_hb_ms); + return 0; + case CTRL_MSG_ID__Event_StationDisconnectFromAP: + esp_state.flags &= ~ESP_HOSTED_FLAGS_STA_CONNECTED; + return 0; + case CTRL_MSG_ID__Event_StationDisconnectFromESPSoftAP: + return 0; + default: + error_printf("esp_hosted_poll() unexpected event %d\n", ctrl_msg->msg_id); + return 0; + } + } + + // Responses that should be handled here. + if (ctrl_msg->msg_type == CTRL_MSG_TYPE__Resp) { + switch (ctrl_msg->msg_id) { + case CTRL_MSG_ID__Resp_ConnectAP: { + if (esp_hosted_resp_value(ctrl_msg) == 0) { + esp_state.flags |= ESP_HOSTED_FLAGS_STA_CONNECTED; + } + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + debug_printf("esp_hosted_poll() state %d\n", esp_state.flags); + return 0; + } + default: + break; + } + } + + // A control message resp/event will be pushed on the stack for further processing. + if (!esp_hosted_stack_push(&esp_state.stack, ctrl_msg)) { + error_printf("esp_hosted_poll() message stack full\n"); + return -1; + } + + debug_printf("esp_hosted_poll() pushed msg_type %lu msg_id %lu\n", ctrl_msg->msg_type, ctrl_msg->msg_id); + return 0; +} + +int esp_hosted_wifi_init(uint32_t itf) { + if (esp_state.flags == ESP_HOSTED_FLAGS_RESET) { + // Init state + memset(&esp_state, 0, sizeof(esp_hosted_state_t)); + esp_hosted_stack_init(&esp_state.stack); + + // Low-level pins and SPI init, memory pool allocation etc... + if (esp_hosted_hal_init(ESP_HOSTED_MODE_WIFI) != 0) { + return -1; + } + + // Allow polling the bus. + esp_state.flags |= ESP_HOSTED_FLAGS_INIT; + + CtrlMsg *ctrl_msg = NULL; + + // Wait for an ESPInit control event. + ctrl_msg = esp_hosted_response(CTRL_MSG_ID__Event_ESPInit, ESP_SYNC_REQ_TIMEOUT); + if (ctrl_msg == NULL) { + return -1; + } + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + + // Set WiFi mode to STA/AP. + CtrlMsgReqSetMode ctrl_payload; + ctrl_msg__req__set_mode__init(&ctrl_payload); + ctrl_payload.mode = CTRL__WIFI_MODE__APSTA; + if (esp_hosted_ctrl(CTRL_MSG_ID__Req_SetWifiMode, &ctrl_payload, &ctrl_msg) != 0) { + return -1; + } + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + + info_printf("esp_hosted_init() device initialized\n"); + } + + if (!netif_is_link_up(&esp_state.netif[itf])) { + // Init lwip netif, and start DHCP client/server. + esp_hosted_netif_init(&esp_state, itf); + info_printf("esp_hosted_init() initialized itf %lu\n", itf); + } + return 0; +} + +int esp_hosted_wifi_disable(uint32_t itf) { + // Remove netif + esp_hosted_netif_deinit(&esp_state, itf); + + if (itf == ESP_HOSTED_STA_IF) { + esp_state.flags &= ~ESP_HOSTED_FLAGS_STA_CONNECTED; + } else { + esp_state.flags &= ~ESP_HOSTED_FLAGS_AP_STARTED; + } + + info_printf("esp_hosted_deinit() deinitialized itf %lu\n", itf); + return 0; +} + +int esp_hosted_wifi_deinit(void) { + if (esp_state.flags & ESP_HOSTED_FLAGS_INIT) { + // Remove network interfaces + esp_hosted_wifi_disable(ESP_HOSTED_STA_IF); + esp_hosted_wifi_disable(ESP_HOSTED_AP_IF); + + // Reset state + memset(&esp_state, 0, sizeof(esp_hosted_state_t)); + esp_hosted_stack_init(&esp_state.stack); + + info_printf("esp_hosted_deinit() deinitialized\n"); + } + return 0; +} + +void *esp_hosted_wifi_get_netif(uint32_t itf) { + return &esp_state.netif[itf]; +} + +int esp_hosted_wifi_get_mac(int itf, uint8_t *mac) { + CtrlMsgReqGetMacAddress ctrl_payload; + ctrl_msg__req__get_mac_address__init(&ctrl_payload); + ctrl_payload.mode = (itf == ESP_HOSTED_STA_IF) ? CTRL__WIFI_MODE__STA : CTRL__WIFI_MODE__AP; + + CtrlMsg *ctrl_msg = NULL; + if (esp_hosted_ctrl(CTRL_MSG_ID__Req_GetMACAddress, &ctrl_payload, &ctrl_msg) != 0) { + error_printf("esp_hosted_get_mac() request failed\n"); + return -1; + } + + ProtobufCBinaryData macstr = ctrl_msg->resp_get_mac_address->mac; + if (macstr.data) { + esp_hosted_macstr_to_bytes(macstr.data, macstr.len, mac); + } + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + return 0; +} + +int esp_hosted_wifi_connect(const char *ssid, const char *bssid, esp_hosted_security_t security, const char *key, uint16_t channel) { + CtrlMsgReqConnectAP ctrl_payload; + ctrl_msg__req__connect_ap__init(&ctrl_payload); + + if (security >= ESP_HOSTED_SEC_MAX) { + // Note: this argument is otherwise unused(!) + return -1; + } + + ctrl_payload.ssid = (char *)ssid; + ctrl_payload.bssid = (char *)bssid; + ctrl_payload.pwd = (char *)key; + ctrl_payload.is_wpa3_supported = false; + ctrl_payload.listen_interval = 0; + + if (esp_hosted_request(CTRL_MSG_ID__Req_ConnectAP, &ctrl_payload) != 0) { + return -1; + } + return 0; +} + +int esp_hosted_wifi_start_ap(const char *ssid, esp_hosted_security_t security, const char *key, uint16_t channel) { + CtrlMsgReqStartSoftAP ctrl_payload; + ctrl_msg__req__start_soft_ap__init(&ctrl_payload); + + if (security >= ESP_HOSTED_SEC_MAX) { + return -1; + } + + ctrl_payload.ssid = (char *)ssid; + ctrl_payload.pwd = (char *)key; + ctrl_payload.chnl = channel; + ctrl_payload.sec_prot = hosted_security_to_sec_prot(security); + ctrl_payload.max_conn = ESP_HOSTED_MAX_AP_CLIENTS; + ctrl_payload.ssid_hidden = false; + ctrl_payload.bw = CTRL__WIFI_BW__HT40; + + CtrlMsg *ctrl_msg = NULL; + if (esp_hosted_ctrl(CTRL_MSG_ID__Req_StartSoftAP, &ctrl_payload, &ctrl_msg) != 0) { + return -1; + } + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + esp_state.flags |= ESP_HOSTED_FLAGS_AP_STARTED; + return 0; +} + +int esp_hosted_wifi_disconnect(uint32_t itf) { + CtrlMsg *ctrl_msg = NULL; + CtrlMsgReqGetStatus ctrl_payload; + ctrl_msg__req__get_status__init(&ctrl_payload); + + if (itf == ESP_HOSTED_STA_IF) { + esp_state.flags &= ~ESP_HOSTED_FLAGS_STA_CONNECTED; + if (esp_hosted_ctrl(CTRL_MSG_ID__Req_DisconnectAP, &ctrl_payload, &ctrl_msg) != 0) { + return -1; + } + } else { + esp_state.flags &= ~ESP_HOSTED_FLAGS_AP_STARTED; + if (esp_hosted_ctrl(CTRL_MSG_ID__Req_StopSoftAP, &ctrl_payload, &ctrl_msg) != 0) { + return -1; + } + } + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + return 0; +} + +int esp_hosted_wifi_link_status(uint32_t itf) { + return netif_is_link_up(&esp_state.netif[itf]); +} + +int esp_hosted_wifi_is_connected(uint32_t itf) { + if (!esp_hosted_wifi_link_status(itf)) { + return false; + } + if (itf == ESP_HOSTED_AP_IF) { + return esp_state.flags & ESP_HOSTED_FLAGS_AP_STARTED; + } + if ((esp_state.flags & ESP_HOSTED_FLAGS_STA_CONNECTED) && + ((esp_state.flags & ESP_HOSTED_FLAGS_STATIC_IP) || + dhcp_supplied_address(&esp_state.netif[itf]))) { + return true; + } + return false; +} + +int esp_hosted_wifi_get_stations(uint8_t *sta_list, size_t *sta_count) { + CtrlMsgReqSoftAPConnectedSTA ctrl_payload; + ctrl_msg__req__soft_apconnected_sta__init(&ctrl_payload); + + CtrlMsg *ctrl_msg = NULL; + if (esp_hosted_ctrl(CTRL_MSG_ID__Req_GetSoftAPConnectedSTAList, &ctrl_payload, &ctrl_msg) != 0) { + return -1; + } + + CtrlMsgRespSoftAPConnectedSTA *resp = ctrl_msg->resp_softap_connected_stas_list; + *sta_count = resp->n_stations; + for (size_t i = 0; i < resp->n_stations; i++) { + ProtobufCBinaryData mac = resp->stations[i]->mac; + esp_hosted_macstr_to_bytes(mac.data, mac.len, &sta_list[i * 6]); + } + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + return 0; +} + +int esp_hosted_wifi_netinfo(esp_hosted_netinfo_t *netinfo) { + CtrlMsgReqGetAPConfig ctrl_payload; + ctrl_msg__req__get_apconfig__init(&ctrl_payload); + + CtrlMsg *ctrl_msg = NULL; + if (esp_hosted_ctrl(CTRL_MSG_ID__Req_GetAPConfig, &ctrl_payload, &ctrl_msg) != 0) { + return -1; + } + + netinfo->rssi = ctrl_msg->resp_get_ap_config->rssi; + netinfo->security = sec_prot_to_hosted_security(ctrl_msg->resp_get_ap_config->sec_prot); + netinfo->channel = ctrl_msg->resp_get_ap_config->chnl; + + ProtobufCBinaryData ssid = ctrl_msg->resp_get_ap_config->ssid; + if (ssid.data) { + size_t ssid_len = MIN(ssid.len, (ESP_HOSTED_MAX_SSID_LEN - 1)); + memcpy(netinfo->ssid, ssid.data, ssid_len); + netinfo->ssid[ssid_len] = 0; + } + + ProtobufCBinaryData bssid = ctrl_msg->resp_get_ap_config->bssid; + if (bssid.data) { + esp_hosted_macstr_to_bytes(bssid.data, bssid.len, netinfo->bssid); + } + + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + return 0; +} + +int esp_hosted_wifi_scan(esp_hosted_scan_callback_t scan_callback, void *arg, uint32_t timeout) { + CtrlMsgReqScanResult ctrl_payload; + ctrl_msg__req__scan_result__init(&ctrl_payload); + + CtrlMsg *ctrl_msg = NULL; + if (esp_hosted_ctrl(CTRL_MSG_ID__Req_GetAPScanList, &ctrl_payload, &ctrl_msg) != 0) { + return -MP_ETIMEDOUT; + } + + CtrlMsgRespScanResult *rp = ctrl_msg->resp_scan_ap_list; + for (int i = 0; i < rp->count; i++) { + esp_hosted_scan_result_t result = {0}; + result.rssi = rp->entries[i]->rssi; + result.security = sec_prot_to_hosted_security(rp->entries[i]->sec_prot); + result.channel = rp->entries[i]->chnl; + if (rp->entries[i]->bssid.data) { + esp_hosted_macstr_to_bytes(rp->entries[i]->bssid.data, rp->entries[i]->bssid.len, result.bssid); + } + + if (rp->entries[i]->ssid.len) { + size_t ssid_len = MIN(rp->entries[i]->ssid.len, (ESP_HOSTED_MAX_SSID_LEN - 1)); + memcpy(result.ssid, rp->entries[i]->ssid.data, ssid_len); + result.ssid[ssid_len] = 0; + } + scan_callback(&result, arg); + } + + ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); + return 0; +} +#endif // MICROPY_PY_NETWORK_ESP_HOSTED diff --git a/drivers/esp-hosted/esp_hosted_wifi.h b/drivers/esp-hosted/esp_hosted_wifi.h new file mode 100644 index 000000000..03d225d88 --- /dev/null +++ b/drivers/esp-hosted/esp_hosted_wifi.h @@ -0,0 +1,110 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ESP-Hosted WiFi driver. + */ + +#ifndef MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_WIFI_H +#define MICROPY_INCLUDED_DRIVERS_ESP_HOSTED_WIFI_H + +#include +#include + +#define ESP_HOSTED_IPV4_ADDR_LEN (4) +#define ESP_HOSTED_MAC_ADDR_LEN (6) +#define ESP_HOSTED_MAC_STR_LEN (18) +#define ESP_HOSTED_MAX_SSID_LEN (32) +#define ESP_HOSTED_MAX_WEP_LEN (13) +#define ESP_HOSTED_MAX_WPA_LEN (63) +#define ESP_HOSTED_MAX_AP_CLIENTS (3) + +#define ESP_HOSTED_AP_GATEWAY LWIP_MAKEU32(192, 168, 1, 1) +#define ESP_HOSTED_AP_ADDRESS LWIP_MAKEU32(192, 168, 1, 1) +#define ESP_HOSTED_AP_NETMASK LWIP_MAKEU32(255, 255, 255, 0) +#define ESP_HOSTED_HOSTNAME "esphosted" + +typedef enum { + ESP_HOSTED_STA_IF = 0, + ESP_HOSTED_AP_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 enum { + ESP_HOSTED_SEC_INVALID = -1, + ESP_HOSTED_SEC_OPEN, + ESP_HOSTED_SEC_WEP, + ESP_HOSTED_SEC_WPA_PSK, + ESP_HOSTED_SEC_WPA2_PSK, + ESP_HOSTED_SEC_WPA_WPA2_PSK, + ESP_HOSTED_SEC_WPA2_ENTERPRISE, + ESP_HOSTED_SEC_WPA3_PSK, + ESP_HOSTED_SEC_WPA2_WPA3_PSK, + ESP_HOSTED_SEC_MAX, +} esp_hosted_security_t; + +typedef struct { + uint8_t ip_addr[ESP_HOSTED_IPV4_ADDR_LEN]; + uint8_t subnet_addr[ESP_HOSTED_IPV4_ADDR_LEN]; + uint8_t gateway_addr[ESP_HOSTED_IPV4_ADDR_LEN]; + uint8_t dns_addr[ESP_HOSTED_IPV4_ADDR_LEN]; +} esp_hosted_ifconfig_t; + +typedef struct { + int32_t rssi; + esp_hosted_security_t security; + uint8_t channel; + char ssid[ESP_HOSTED_MAX_SSID_LEN]; + uint8_t bssid[ESP_HOSTED_MAC_ADDR_LEN]; +} esp_hosted_scan_result_t; + +typedef struct { + int32_t rssi; + esp_hosted_security_t security; + uint8_t channel; + char ssid[ESP_HOSTED_MAX_SSID_LEN]; + uint8_t bssid[ESP_HOSTED_MAC_ADDR_LEN]; +} esp_hosted_netinfo_t; + +typedef int (*esp_hosted_scan_callback_t)(esp_hosted_scan_result_t *, void *); + +int esp_hosted_wifi_poll(void); +int esp_hosted_wifi_init(uint32_t itf); +int esp_hosted_wifi_deinit(void); +int esp_hosted_wifi_disable(uint32_t itf); +void *esp_hosted_wifi_get_netif(uint32_t itf); +int esp_hosted_wifi_get_mac(int itf, uint8_t *mac); +int esp_hosted_wifi_connect(const char *ssid, const char *bssid, esp_hosted_security_t security, const char *key, uint16_t channel); +int esp_hosted_wifi_start_ap(const char *ssid, esp_hosted_security_t security, const char *key, uint16_t channel); +int esp_hosted_wifi_disconnect(uint32_t itf); +int esp_hosted_wifi_link_status(uint32_t itf); +int esp_hosted_wifi_is_connected(uint32_t itf); +int esp_hosted_wifi_get_stations(uint8_t *sta_list, size_t *sta_count); +int esp_hosted_wifi_netinfo(esp_hosted_netinfo_t *netinfo); +int esp_hosted_wifi_scan(esp_hosted_scan_callback_t scan_callback, void *arg, uint32_t timeout); +#endif // MICROPY_INCLUDED_DRIVERS_ESPHOST_WIFI_H diff --git a/extmod/extmod.mk b/extmod/extmod.mk index a15b7e4a5..d5b223034 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -337,8 +337,51 @@ SRC_THIRDPARTY_C += $(addprefix $(WIZNET5K_DIR)/,\ Internet/DHCP/dhcp.c \ ) endif +endif # MICROPY_PY_NETWORK_WIZNET5K + +ifeq ($(MICROPY_PY_NETWORK_ESP_HOSTED),1) +ESP_HOSTED_DIR = drivers/esp-hosted +PROTOBUF_C_DIR = lib/protobuf-c +PROTOC ?= protoc-c +GIT_SUBMODULES += $(PROTOBUF_C_DIR) + +CFLAGS += -DMICROPY_PY_NETWORK_ESP_HOSTED=1 +CFLAGS_EXTMOD += -DMICROPY_PY_NETWORK_ESP_HOSTED=1 +INC += -I$(TOP)/$(ESP_HOSTED_DIR) + +ESP_HOSTED_SRC_C = $(addprefix $(ESP_HOSTED_DIR)/,\ + esp_hosted_wifi.c \ + esp_hosted_netif.c \ + esp_hosted_hal.c \ + ) + +ifeq ($(MICROPY_PY_BLUETOOTH),1) +ESP_HOSTED_SRC_C += $(ESP_HOSTED_DIR)/esp_hosted_bthci.c endif +# Include the protobuf-c support functions +ESP_HOSTED_SRC_C += $(addprefix $(PROTOBUF_C_DIR)/,\ + protobuf-c/protobuf-c.c \ + ) + +$(BUILD)/$(PROTOBUF_C_DIR)/%.o: CFLAGS += -Wno-unused-but-set-variable + +# Generate esp_hosted-pb-c.c|h from esp_hosted.proto +PROTO_GEN_SRC = $(BUILD)/extmod/esp_hosted.pb-c.c +ESP_HOSTED_SRC_C += $(PROTO_GEN_SRC) + +$(PROTO_GEN_SRC): $(TOP)/$(ESP_HOSTED_DIR)/esp_hosted.proto + $(PROTOC) --proto_path=$(dir $<) --c_out=$(dir $@) $< + +# Scope the protobuf include paths to the esp_hosted source files, only +ESP_HOSTED_OBJS = $(addprefix $(BUILD)/, $(ESP_HOSTED_SRC_C:.c=.o)) +$(ESP_HOSTED_OBJS): $(PROTO_GEN_SRC) +$(ESP_HOSTED_OBJS): CFLAGS += -I$(dir $(PROTO_GEN_SRC)) -I$(TOP)/$(PROTOBUF_C_DIR) + +DRIVERS_SRC_C += $(ESP_HOSTED_SRC_C) + +endif # MICROPY_PY_NETWORK_ESP_HOSTED + ################################################################################ # bluetooth