Compare commits

..

2 Commits

Author SHA1 Message Date
Ken Van Hoeylandt
2cf8beae7a esp_hosted downgrade 2026-09-11 19:49:30 +02:00
Ken Van Hoeylandt
4022f206e4 Fixes 2026-09-11 15:26:16 +02:00
16 changed files with 227 additions and 45 deletions

View File

@ -40,6 +40,13 @@ runs:
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.31.x'
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install mbedtls codegen Python dependencies
shell: bash
run: pip install -r Libraries/mbedtls/scripts/driver.requirements.txt
- name: "Prepare Project"
shell: bash
run: cmake -S ./ -B buildsim

View File

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

View File

@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "source"
REQUIRES TactilityKernel TactilityFreeRtos bq24295-module driver
REQUIRES TactilityKernel TactilityFreeRtos bq24295-module driver esp_driver_gpio
)

View File

@ -156,19 +156,17 @@ static void getKey(uint8_t key[32]) {
// psa_crypto_init() is safe to call more than once (subsequent calls are a cheap no-op); ESP-IDF
// calls it during startup already, but the simulator has no equivalent, so call it here too
// rather than depend on init order across callers.
static void ensure_psa_crypto_init() {
static bool initialized = false;
if (!initialized) {
psa_crypto_init();
initialized = true;
}
static psa_status_t ensure_psa_crypto_init() {
return psa_crypto_init();
}
void crypt_get_iv(const void* data, size_t dataLength, uint8_t iv[16]) {
ensure_psa_crypto_init();
check(ensure_psa_crypto_init() == PSA_SUCCESS);
uint8_t hash[32];
size_t hash_length;
psa_hash_compute(PSA_ALG_SHA_256, static_cast<const uint8_t*>(data), dataLength, hash, sizeof(hash), &hash_length);
check(psa_hash_compute(PSA_ALG_SHA_256, static_cast<const uint8_t*>(data),
dataLength, hash, sizeof(hash), &hash_length) == PSA_SUCCESS);
check(hash_length == sizeof(hash));
memcpy(iv, hash, 16);
mbedtls_platform_zeroize(hash, sizeof(hash));
}

View File

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

View File

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

View File

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

View File

@ -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 <sdkconfig.h>
#if defined(CONFIG_BT_NIMBLE_ENABLED) && defined(CONFIG_ESP_HOSTED_ENABLED)
#include <stdbool.h>
#include <stdatomic.h>
#include <string.h>
#include <esp_log.h>
#include <esp_err.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
// NimBLE transport + HCI
#include <nimble/transport.h>
#include <nimble/hci_common.h>
#include <os/os_mbuf.h>
// 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

View File

@ -29,9 +29,11 @@ constexpr auto* TAG = "esp32_ble";
#include <tactility/log.h>
#include <esp_timer.h>
#if defined(CONFIG_ESP_HOSTED_HOST)
#if defined(CONFIG_ESP_HOSTED_ENABLED)
#include <esp_hosted.h>
#include <esp_hosted_bt_host_stack.h>
extern "C" {
#include <esp_hosted_misc.h>
}
#endif
// ble_store_config_init() is not declared in the public header in some IDF versions.
@ -679,16 +681,15 @@ 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_W(TAG, "esp_hosted_bt_host_stack_setup failed");
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
@ -759,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
@ -789,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
@ -806,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);

View File

@ -2,7 +2,7 @@
#include <sdkconfig.h>
#endif
#if defined(CONFIG_ESP_HOSTED_HOST)
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <tactility/drivers/esp32_esp_hosted_ota.h>
#include <tactility/drivers/wifi.h>
@ -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

View File

@ -20,7 +20,7 @@
#include <TactilityCpp/Allocator.h>
#if defined(CONFIG_ESP_HOSTED_HOST)
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <tactility/drivers/esp32_esp_hosted_ota.h>
#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.

View File

@ -1,5 +1,6 @@
#include <Tactility/file/File.h>
#include <cerrno>
#include <cstring>
#include <fstream>
#include <unistd.h>
@ -195,7 +196,10 @@ static bool findOrCreateDirectoryInternal(const std::string& path, mode_t mode)
return S_ISDIR(dir_stat.st_mode);
}
return mkdir(path.c_str(), mode) == 0;
if (mkdir(path.c_str(), mode) == 0) {
return true;
}
return errno == EEXIST && stat(path.c_str(), &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode);
}
std::string getLastPathSegment(const std::string& path) {

View File

@ -2,7 +2,7 @@
#include <sdkconfig.h>
#endif
#if defined(CONFIG_ESP_HOSTED_HOST)
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <Tactility/service/espnow/EspNowBackend.h>
#include <Tactility/service/espnow/EspNowHostedTransport.h>
@ -310,4 +310,4 @@ uint32_t getVersion() {
}
#endif // CONFIG_ESP_HOSTED_HOST
#endif // CONFIG_SLAVE_SOC_WIFI_SUPPORTED

View File

@ -2,7 +2,7 @@
#include <sdkconfig.h>
#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 <Tactility/service/espnow/EspNowBackend.h>
#include <Tactility/service/wifi/Wifi.h>
@ -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

View File

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

View File

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