diff --git a/Devices/m5stack-tab5/device.properties b/Devices/m5stack-tab5/device.properties index c55287cea..40a1c7549 100644 --- a/Devices/m5stack-tab5/device.properties +++ b/Devices/m5stack-tab5/device.properties @@ -33,8 +33,15 @@ sdkconfig.CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 sdkconfig.CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y sdkconfig.CONFIG_SLAVE_IDF_TARGET_ESP32C6=y sdkconfig.CONFIG_ESP_HOSTED_HOST_TRANSPORT_BUS_SDIO=y -# esp_hosted 3.x ships a native pin/reset-GPIO preset for this exact board. -sdkconfig.CONFIG_ESP32P4_TAB5_C6_BOARD=y +sdkconfig.CONFIG_ESP_HOSTED_MEMPOOL_PREFER_SPIRAM=y +sdkconfig.CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=12 +sdkconfig.CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=13 +sdkconfig.CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=11 +sdkconfig.CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=10 +sdkconfig.CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=9 +sdkconfig.CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8 +sdkconfig.CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15 +sdkconfig.CONFIG_ESP_SDIO_GPIO_RESET_SLAVE=15 # ESP-NOW bridge (EspNowBackendHosted.cpp) registers 6 custom-RPC handlers (4 resp + 2 event); default cap is 3 sdkconfig.CONFIG_ESP_HOSTED_HOST_FEAT_PEER_DATA_MAX_CUSTOM_MSG_HANDLERS=8 # Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers diff --git a/Platforms/platform-esp32/CMakeLists.txt b/Platforms/platform-esp32/CMakeLists.txt index 44964cc54..8ff671567 100644 --- a/Platforms/platform-esp32/CMakeLists.txt +++ b/Platforms/platform-esp32/CMakeLists.txt @@ -22,7 +22,7 @@ idf_component_register( if (DEFINED ENV{ESP_IDF_VERSION}) GET_PROPERTY_FILE_CONTENT("${CMAKE_CURRENT_LIST_DIR}/../../sdkconfig" sdkconfig_text) - GET_PROPERTY_VALUE(sdkconfig_text "CONFIG_ESP_HOSTED_HOST" esp_host_enabled "n") + GET_PROPERTY_VALUE(sdkconfig_text "CONFIG_ESP_HOSTED_ENABLED" esp_host_enabled "n") if (esp_host_enabled STREQUAL "y") idf_component_optional_requires(PRIVATE espressif__esp_hosted) endif () diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_esp_hosted_ota.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_esp_hosted_ota.h index d4f16ac87..1908d4dd4 100644 --- a/Platforms/platform-esp32/include/tactility/drivers/esp32_esp_hosted_ota.h +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_esp_hosted_ota.h @@ -3,5 +3,5 @@ struct FirmwareOps; /** @return the esp_hosted co-processor FirmwareOps implementation. Only declared/linked when - * CONFIG_ESP_HOSTED_HOST - callers (esp32_wifi.cpp) must guard with the same #if. */ + * CONFIG_SLAVE_SOC_WIFI_SUPPORTED - callers (esp32_wifi.cpp) must guard with the same #if. */ const FirmwareOps* esp32_esp_hosted_ota_get_ops(); diff --git a/Platforms/platform-esp32/private/bluetooth/esp32_ble_internal.h b/Platforms/platform-esp32/private/bluetooth/esp32_ble_internal.h index bd0c3b324..09520de68 100644 --- a/Platforms/platform-esp32/private/bluetooth/esp32_ble_internal.h +++ b/Platforms/platform-esp32/private/bluetooth/esp32_ble_internal.h @@ -172,5 +172,18 @@ extern size_t active_hid_rpt_map_len; // esp32_ble_hid.cpp extern uint16_t hid_appearance; // esp32_ble_hid.cpp extern BleHidProfile current_hid_profile; // esp32_ble_hid.cpp +// ---- HCI gate (ble_hci_gate.c) — P4/esp-hosted only ---- +// Controls whether hci_rx_handler forwards packets to NimBLE. +// Set true after nimble_port_init(), false before nimble_port_stop(). +#if defined(CONFIG_ESP_HOSTED_ENABLED) +#ifdef __cplusplus +extern "C" { +#endif +void ble_hci_gate_set_active(bool active); +bool ble_hci_gate_wait_idle(int max_ms); +#ifdef __cplusplus +} +#endif +#endif // CONFIG_ESP_HOSTED_ENABLED #endif // CONFIG_BT_NIMBLE_ENABLED diff --git a/Platforms/platform-esp32/source/drivers/bluetooth/ble_hci_gate.c b/Platforms/platform-esp32/source/drivers/bluetooth/ble_hci_gate.c new file mode 100644 index 000000000..960340f2d --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/bluetooth/ble_hci_gate.c @@ -0,0 +1,142 @@ +// ble_hci_gate.c — Strong override of the weak hci_rx_handler from vhci_drv.c. +// Gates HCI packet delivery so packets are silently dropped when NimBLE is not +// initialised, preventing null-npl_funcs crashes during radio teardown (P4 only). +// +// On ESP32-P4, sdio_process_rx_task runs independently of the NimBLE host and can +// deliver an HCI packet after nimble_port_deinit() has zeroed npl_funcs. +// dispatch_disable() calls ble_hci_gate_set_active(false) + ble_hci_gate_wait_idle() +// before touching NimBLE, ensuring no in-flight or future hci_rx_handler call can +// dereference NimBLE internals. + +#include +#if defined(CONFIG_BT_NIMBLE_ENABLED) && defined(CONFIG_ESP_HOSTED_ENABLED) + +#include +#include +#include +#include +#include +#include +#include + +// NimBLE transport + HCI +#include +#include +#include + +// H4 packet type indicators (same values used in vhci_drv.c) +#define HCI_H4_EVT 0x04 +#define HCI_H4_ACL 0x02 + +// Local defines from vhci_drv.c (not exported by any header) +#define BLE_HCI_EVENT_HDR_LEN (2) + +static const char* TAG = "ble_hci_gate"; + +// ---- Gate state (zero-init = gate closed until dispatch_enable opens it) ---- +static atomic_bool s_hci_active = ATOMIC_VAR_INIT(false); +static atomic_int s_hci_refcount = ATOMIC_VAR_INIT(0); + +void ble_hci_gate_set_active(bool active) { + atomic_store_explicit(&s_hci_active, active, memory_order_seq_cst); +} + +// Spin-wait until all in-flight hci_rx_handler calls complete (max_ms timeout). +// Returns true if drained within the timeout. +bool ble_hci_gate_wait_idle(int max_ms) { + for (int elapsed = 0; elapsed < max_ms; elapsed++) { + if (atomic_load_explicit(&s_hci_refcount, memory_order_acquire) == 0) + return true; + vTaskDelay(pdMS_TO_TICKS(1)); + } + return (atomic_load_explicit(&s_hci_refcount, memory_order_acquire) == 0); +} + +// Strong override — replaces the H_WEAK_REF hci_rx_handler in vhci_drv.c. +// All four esp-hosted transport drivers (SDIO, SPI, SPI-HD, UART) call this symbol. +int hci_rx_handler(uint8_t *buf, size_t buf_len) { + // Fast path: gate closed — NimBLE not ready, drop silently + if (!atomic_load_explicit(&s_hci_active, memory_order_acquire)) + return ESP_OK; + + // Hold a reference so dispatch_disable() waits for us to finish + atomic_fetch_add_explicit(&s_hci_refcount, 1, memory_order_acquire); + + // Double-check: gate may have closed between the first check and the increment + if (!atomic_load_explicit(&s_hci_active, memory_order_acquire)) { + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_OK; + } + + // ---- Original vhci_drv.c (NimBLE branch) logic ---- + uint8_t *data = buf; + uint32_t len_total_read = buf_len; + int rc; + + if (data[0] == HCI_H4_EVT) { + uint8_t *evbuf; + int totlen = BLE_HCI_EVENT_HDR_LEN + data[2]; + + if (totlen > UINT8_MAX + BLE_HCI_EVENT_HDR_LEN) { + ESP_LOGE(TAG, "Rx: len[%d] > max INT, drop", totlen); + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_FAIL; + } + if (totlen > MYNEWT_VAL(BLE_TRANSPORT_EVT_SIZE)) { + ESP_LOGE(TAG, "Rx: len[%d] > max BLE, drop", totlen); + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_FAIL; + } + if (data[1] == BLE_HCI_EVCODE_HW_ERROR) { + ESP_LOGE(TAG, "Rx: HW_ERROR"); + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_FAIL; + } + + if ((data[1] == BLE_HCI_EVCODE_LE_META) && + (data[3] == BLE_HCI_LE_SUBEV_ADV_RPT || + data[3] == BLE_HCI_LE_SUBEV_EXT_ADV_RPT)) { + evbuf = ble_transport_alloc_evt(1); + if (!evbuf) { + ESP_LOGW(TAG, "Rx: Drop ADV Report: OOM (not fatal)"); + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_FAIL; + } + } else { + evbuf = ble_transport_alloc_evt(0); + if (!evbuf) { + ESP_LOGE(TAG, "Rx: transport_alloc_evt(0) failed"); + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_FAIL; + } + } + + memset(evbuf, 0, sizeof *evbuf); + memcpy(evbuf, &data[1], totlen); + rc = ble_transport_to_hs_evt(evbuf); + if (rc) { + ESP_LOGE(TAG, "Rx: transport_to_hs_evt failed"); + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_FAIL; + } + } else if (data[0] == HCI_H4_ACL) { + struct os_mbuf *m = ble_transport_alloc_acl_from_ll(); + if (!m) { + ESP_LOGE(TAG, "Rx: alloc_acl_from_ll failed"); + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_FAIL; + } + if ((rc = os_mbuf_append(m, &data[1], len_total_read - 1)) != 0) { + ESP_LOGE(TAG, "Rx: os_mbuf_append failed; rc=%d", rc); + os_mbuf_free_chain(m); + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_FAIL; + } + ble_transport_to_hs_acl(m); + } + + atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release); + return ESP_OK; +} + +#endif // CONFIG_BT_NIMBLE_ENABLED && CONFIG_ESP_HOSTED_ENABLED diff --git a/Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp b/Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp index 6768c0ec1..dfb934096 100644 --- a/Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp +++ b/Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp @@ -29,9 +29,11 @@ constexpr auto* TAG = "esp32_ble"; #include #include -#if defined(CONFIG_ESP_HOSTED_HOST) +#if defined(CONFIG_ESP_HOSTED_ENABLED) #include -#include +extern "C" { +#include +} #endif // ble_store_config_init() is not declared in the public header in some IDF versions. @@ -679,31 +681,21 @@ static void dispatch_enable(BleCtx* ctx) { ble_publish_event(ctx->device, e); } -#if defined(CONFIG_ESP_HOSTED_HOST) - // Over esp_hosted, the co-processor's BT controller only comes up via - // esp_hosted_bt_host_stack_setup() below, which needs the SDIO/SPI transport already up - - // BT can auto-enable before Wi-Fi has driven that bring-up, so (re)connect here first. +#if defined(CONFIG_ESP_HOSTED_ENABLED) if (esp_hosted_connect_to_slave() != ESP_OK) { LOG_W(TAG, "esp_hosted_connect_to_slave failed"); } - esp_hosted_bt_host_stack_cfg_t hosted_bt_cfg = ESP_HOSTED_BT_HOST_STACK_CONFIG_DEFAULT(); - if (esp_hosted_bt_host_stack_setup(&hosted_bt_cfg) != ESP_OK) { - LOG_E(TAG, "esp_hosted_bt_host_stack_setup failed"); - ctx->radio_state.store(BT_RADIO_STATE_OFF); - struct BtEvent e = {}; - e.type = BT_EVENT_RADIO_STATE_CHANGED; - e.radio_state = BT_RADIO_STATE_OFF; - ble_publish_event(ctx->device, e); - return; + if (esp_hosted_bt_controller_init() != ESP_OK) { + LOG_W(TAG, "esp_hosted_bt_controller_init failed"); + } + if (esp_hosted_bt_controller_enable() != ESP_OK) { + LOG_W(TAG, "esp_hosted_bt_controller_enable failed"); } #endif int rc = nimble_port_init(); if (rc != 0) { LOG_E(TAG, "nimble_port_init failed (rc=%d)", rc); -#if defined(CONFIG_ESP_HOSTED_HOST) - esp_hosted_bt_host_stack_teardown(); -#endif ctx->radio_state.store(BT_RADIO_STATE_OFF); struct BtEvent e = {}; e.type = BT_EVENT_RADIO_STATE_CHANGED; @@ -768,6 +760,9 @@ static void dispatch_enable(BleCtx* ctx) { ble_svc_gap_device_name_set(ctx->device_name); ble_att_set_preferred_mtu(BLE_ATT_MTU_MAX); +#if defined(CONFIG_ESP_HOSTED_ENABLED) + ble_hci_gate_set_active(true); // Open gate: NimBLE transport pool is ready +#endif // Drain any stale "done" signal left over from a previous cycle (e.g. if the // last dispatch_disable() timed out waiting on it — see xSemaphoreTake below // with pdMS_TO_TICKS(2000)) so dispatch_disable() only ever consumes the @@ -798,9 +793,9 @@ static void dispatch_disable(BleCtx* ctx) { // Blocking: waits for nimble_port_run() to exit. // Do NOT call ble_gap_adv_stop()/disc_cancel() before — if controller is // unresponsive they generate more HCI timeouts before the stop takes effect. - // The HCI link must stay bound here: nimble_port_stop() → ble_hs_stop() sends + // The HCI gate must stay OPEN here: nimble_port_stop() → ble_hs_stop() sends // HCI commands and needs to receive the command-complete events back from the - // controller. Unbinding before this point starves NimBLE of those events, + // controller. Closing the gate before this point starves NimBLE of those events, // causing HCI timeouts and a cascade of resets that crash in ble_hs_timer_sched. nimble_port_stop(); // nimble_port_stop()'s internal semaphore only confirms the stop sentinel event was @@ -815,17 +810,27 @@ static void dispatch_disable(BleCtx* ctx) { LOG_W(TAG, "host task did not signal completion in time"); } } -#if defined(CONFIG_ESP_HOSTED_HOST) - // Unbind before nimble_port_deinit() zeros npl_funcs. Unlike the old ble_hci_gate.c this - // repo carried for esp_hosted 2.x, esp_hosted 3.x exposes no drain/wait-for-in-flight - // primitive here, so a packet already inside the RX callback at this exact moment is a - // small theoretical race this migration cannot close without patching esp_hosted itself. - if (esp_hosted_bt_host_stack_teardown() != ESP_OK) { - LOG_W(TAG, "esp_hosted_bt_host_stack_teardown failed"); +#if defined(CONFIG_ESP_HOSTED_ENABLED) + // Close the gate NOW — after nimble_port_stop() returns the NimBLE host task has + // exited and nimble_port_deinit() is about to zero npl_funcs. Any HCI packet + // arriving after this point must not reach ble_transport_alloc_evt(). + ble_hci_gate_set_active(false); + if (!ble_hci_gate_wait_idle(20)) { + LOG_W(TAG, "HCI gate drain timed out"); } #endif nimble_port_deinit(); +#if defined(CONFIG_ESP_HOSTED_ENABLED) + // Symmetric with the enable-side esp_hosted_bt_controller_init/enable() calls. + if (esp_hosted_bt_controller_disable() != ESP_OK) { + LOG_W(TAG, "esp_hosted_bt_controller_disable failed"); + } + if (esp_hosted_bt_controller_deinit(true) != ESP_OK) { + LOG_W(TAG, "esp_hosted_bt_controller_deinit failed"); + } +#endif + ctx->spp_conn_handle.store(BLE_HS_CONN_HANDLE_NONE); ctx->spp_active.store(false); ctx->midi_conn_handle.store(BLE_HS_CONN_HANDLE_NONE); diff --git a/Platforms/platform-esp32/source/drivers/esp32_esp_hosted_ota.cpp b/Platforms/platform-esp32/source/drivers/esp32_esp_hosted_ota.cpp index e1bc79d15..69fd6eceb 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_esp_hosted_ota.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_esp_hosted_ota.cpp @@ -2,7 +2,7 @@ #include #endif -#if defined(CONFIG_ESP_HOSTED_HOST) +#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) #include #include @@ -251,4 +251,4 @@ const FirmwareOps* esp32_esp_hosted_ota_get_ops() { return &firmwareOps; } -#endif // CONFIG_ESP_HOSTED_HOST +#endif // CONFIG_SLAVE_SOC_WIFI_SUPPORTED diff --git a/Platforms/platform-esp32/source/drivers/esp32_wifi.cpp b/Platforms/platform-esp32/source/drivers/esp32_wifi.cpp index 7b6c387c2..79d5c1b31 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_wifi.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_wifi.cpp @@ -20,7 +20,7 @@ #include -#if defined(CONFIG_ESP_HOSTED_HOST) +#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) #include #endif @@ -583,7 +583,7 @@ error_t api_get_firmware_ops(Device* /*device*/, const FirmwareOps** ops, void** if (ops == nullptr || ctx == nullptr) { return ERROR_INVALID_ARGUMENT; } -#if defined(CONFIG_ESP_HOSTED_HOST) +#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) // Only meaningful on a hosted board (P4+C6/C5 etc.) - this wifi device is backed by a real // co-processor with its own updatable firmware there. On a native (non-hosted) chip, this // device's "radio" is the chip's own built-in WiFi, nothing to update via this interface. diff --git a/Tactility/Source/service/espnow/EspNowBackendHosted.cpp b/Tactility/Source/service/espnow/EspNowBackendHosted.cpp index f6d3d6ba9..b6966795e 100644 --- a/Tactility/Source/service/espnow/EspNowBackendHosted.cpp +++ b/Tactility/Source/service/espnow/EspNowBackendHosted.cpp @@ -2,7 +2,7 @@ #include #endif -#if defined(CONFIG_ESP_HOSTED_HOST) +#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) #include #include @@ -310,4 +310,4 @@ uint32_t getVersion() { } -#endif // CONFIG_ESP_HOSTED_HOST +#endif // CONFIG_SLAVE_SOC_WIFI_SUPPORTED diff --git a/Tactility/Source/service/espnow/EspNowBackendNative.cpp b/Tactility/Source/service/espnow/EspNowBackendNative.cpp index 887f25357..93b8bc65f 100644 --- a/Tactility/Source/service/espnow/EspNowBackendNative.cpp +++ b/Tactility/Source/service/espnow/EspNowBackendNative.cpp @@ -2,7 +2,7 @@ #include #endif -#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_ESP_HOSTED_HOST) +#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) #include #include @@ -205,4 +205,4 @@ uint32_t getVersion() { } -#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_ESP_HOSTED_HOST +#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED diff --git a/Tactility/idf_component.yml b/Tactility/idf_component.yml index 3f24865ee..9e27b5a6a 100644 --- a/Tactility/idf_component.yml +++ b/Tactility/idf_component.yml @@ -6,7 +6,7 @@ dependencies: rules: - if: "target in [esp32s3, esp32c6, esp32p4]" espressif/esp_hosted: - version: "3.0.7" + version: "2.12.11" rules: - if: "target == esp32p4" espressif/esp_wifi_remote: diff --git a/device.py b/device.py index 2f7b4ef61..8bd34c47b 100644 --- a/device.py +++ b/device.py @@ -425,10 +425,7 @@ def write_bluetooth_variables(output_file, device_properties: dict): output_file.write("CONFIG_BT_NIMBLE_ENABLED=y\n") if idf_target == "esp32p4": output_file.write("CONFIG_BT_NIMBLE_TRANSPORT_UART=n\n") - # esp_hosted 3.0 removed the legacy ESP_HOSTED_ENABLE_BT_NIMBLE knob in favor - # of a stack-agnostic feature flag; the app binds NimBLE to it explicitly via - # esp_hosted_bt_host_stack_setup() (see esp32_ble.cpp). - output_file.write("CONFIG_ESP_HOSTED_HOST_FEAT_BT=y\n") + output_file.write("CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y\n") # Move NimBLE host buffers to SPIRAM when available, regardless of target. # The default (INTERNAL) mode causes heap fragmentation after a disable+deinit # cycle, preventing a subsequent nimble_port_init() from allocating its buffers