Files
desklock/firmware/components/esp_hosted/host/drivers/bt/vhci_drv.c
T
jpmschweitzerandClaude Fable 5 cb5826e02b
Test, Build and Push / test-gateway (push) Successful in 12s
Test, Build and Push / release (push) Skipped
Test, Build and Push / build-gateway (push) Skipped
Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
Root cause (verified against our exact IDF tree, not the community guess):
the "258" in "sdio_write_task: Failed to send data: 258" is NOT a timeout
(that is 263). 258 = 0x102 = ESP_ERR_INVALID_ARG. On the ESP32-P4, block-
mode CMD53 writes require the SOURCE buffer to be 64-byte (cache-line)
aligned; the IDF sdmmc driver rejects a misaligned source with INVALID_ARG
BEFORE any bus activity. esp_hosts write loop then declares "Unrecoverable
host sdio state" and reboots the whole P4. The audio TX payload is not
64-aligned, so streaming mic audio wedged on the very FIRST frame (which is
exactly what we saw: listening -> instant Failed to send -> reboot).

This also explains why buffer/queue/clock/retry tuning all did nothing: the
write never reached the bus. And why our symptom was instant, not after
~100 writes (the community block-mode-desync theory) — it is the first
misaligned buffer, every time.

Fix: vendored esp_hosted 2.12.11 as an editable local component (overrides
the registry copy) and bounce a misaligned TX payload through one aligned
DMA scratch buffer in hosted_sdio_write_block (port_esp_hosted_host_sdio.c).
TX is serialized by the bus lock so a single static bounce buffer is safe;
freed in hosted_sdio_deinit. Host-only change — no C6 reflash.

VERIFIED ON HARDWARE (autonomous self-test): 40s of continuous mic-audio
upstream streaming — the traffic that previously wedged on the first frame
— ran clean, zero timeouts, zero reboots. A guarded SDIO_TX_SELFTEST harness
is kept (compiled out) for future SDIO stress testing.

Credit: root cause + patch designed via multi-agent investigation; the
precise 258=INVALID_ARG decode (correcting the upstream community timeout
assumption) came from checking our actual esp_err.h.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:50:27 +02:00

284 lines
6.3 KiB
C

/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "esp_hosted_transport.h"
#include "esp_hosted_os_abstraction.h"
#include "transport_drv.h"
#include "port_esp_hosted_host_os.h"
#include "hci_drv.h"
#ifndef __WEAK__
# if defined(__GNUC__) || defined(__clang__)
# define __WEAK__ __attribute__((weak))
# elif defined(_MSC_VER)
# define __WEAK__ __declspec(selectany)
# else
# define __WEAK__
# endif
#endif
#if H_BT_HOST_ESP_NIMBLE
#include "host/ble_hs_mbuf.h"
#include "os/os_mbuf.h"
#include "nimble/transport.h"
#include "nimble/transport/hci_h4.h"
#include "nimble/hci_common.h"
#endif
#include "esp_hosted_bt.h"
#if H_BT_HOST_ESP_BLUEDROID
#include "esp_hosted_bluedroid.h"
#endif
#include "esp_hosted_log.h"
static const char TAG[] = "vhci_drv";
#if H_BT_HOST_ESP_NIMBLE
#define BLE_HCI_EVENT_HDR_LEN (2)
#define BLE_HCI_CMD_HDR_LEN (3)
#endif
void hci_drv_init(void)
{
// do nothing for VHCI: underlying transport should be ready
}
void hci_drv_show_configuration(void)
{
ESP_LOGI(TAG, "Host BT Support: Enabled");
ESP_LOGI(TAG, "\tBT Transport Type: VHCI");
}
#if H_BT_HOST_ESP_NIMBLE
/**
* HCI_H4_xxx is the first byte of the received data
*/
H_WEAK_REF int hci_rx_handler(uint8_t *buf, size_t buf_len)
{
uint8_t * data = buf;
uint32_t len_total_read = buf_len;
int rc;
if (data[0] == HCI_H4_EVT) {
uint8_t *evbuf;
int totlen;
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 [%d], drop",
totlen, UINT8_MAX + BLE_HCI_EVENT_HDR_LEN);
return ESP_FAIL;
}
if (totlen > MYNEWT_VAL(BLE_TRANSPORT_EVT_SIZE)) {
ESP_LOGE(TAG, "Rx: len[%d] > max BLE [%d], drop",
totlen, MYNEWT_VAL(BLE_TRANSPORT_EVT_SIZE));
return ESP_FAIL;
}
if (data[1] == BLE_HCI_EVCODE_HW_ERROR) {
ESP_LOGE(TAG, "Rx: HW_ERROR");
return ESP_FAIL;
}
/* Allocate LE Advertising Report Event from lo pool only */
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);
/* Skip advertising report if we're out of memory */
if (!evbuf) {
ESP_LOGW(TAG, "Rx: Drop ADV Report Event: NimBLE OOM (not fatal)");
return ESP_FAIL;
}
} else {
evbuf = ble_transport_alloc_evt(0);
if (!evbuf) {
ESP_LOGE(TAG, "Rx: failed transport_alloc_evt(0)");
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");
return ESP_FAIL;
}
} else if (data[0] == HCI_H4_ACL) {
struct os_mbuf *m = NULL;
m = ble_transport_alloc_acl_from_ll();
if (!m) {
ESP_LOGE(TAG, "Rx: alloc_acl_from_ll failed");
return ESP_FAIL;
}
if ((rc = os_mbuf_append(m, &data[1], len_total_read - 1)) != 0) {
ESP_LOGE(TAG, "Rx: failed os_mbuf_append; rc = %d", rc);
os_mbuf_free_chain(m);
return ESP_FAIL;
}
ble_transport_to_hs_acl(m);
}
return ESP_OK;
}
/**
* ESP NimBLE expects these interfaces for Tx
*
* For doing non-zero copy:
* - transport expects the HCI_H4_xxx type to be the first byte of the
* data stream
*
* For doing zero copy:
* - fill in esp_paylod_header and payload data
* - HCI_H4_xxx type should be set in esp_payload_header.hci_pkt_type
*/
#if H_BT_ENABLE_LL_INIT
void ble_transport_ll_init(void)
{
ESP_ERROR_CHECK(transport_drv_reconfigure());
}
void ble_transport_ll_deinit(void)
{
// transport may still be in used for other data (serial, Wi-Fi, ...)
}
#endif
int ble_transport_to_ll_acl_impl(struct os_mbuf *om)
{
// TODO: zerocopy version
// calculate data length from the incoming data
int data_len = OS_MBUF_PKTLEN(om) + 1;
uint8_t * data = NULL;
int res;
data = g_h.funcs->_h_malloc_align(data_len, HOSTED_MEM_ALIGNMENT_64);
if (!data) {
ESP_LOGE(TAG, "Tx %s: malloc failed", __func__);
res = ESP_FAIL;
goto exit;
}
data[0] = HCI_H4_ACL;
res = ble_hs_mbuf_to_flat(om, &data[1], OS_MBUF_PKTLEN(om), NULL);
if (res) {
ESP_LOGE(TAG, "Tx: Error copying HCI_H4_ACL data %d", res);
os_mbuf_free_chain(om);
g_h.funcs->_h_free_align(data);
res = ESP_FAIL;
goto exit;
}
res = esp_hosted_tx(ESP_HCI_IF, 0, data, data_len, H_BUFF_NO_ZEROCOPY, data, H_DEFLT_FREE_FUNC, 0);
exit:
os_mbuf_free_chain(om);
return res;
}
int ble_transport_to_ll_cmd_impl(void *buf)
{
// TODO: zerocopy version
// calculate data length from the incoming data
int buf_len = 3 + ((uint8_t *)buf)[2] + 1;
uint8_t * data = NULL;
int res;
data = g_h.funcs->_h_malloc_align(buf_len, HOSTED_MEM_ALIGNMENT_64);
if (!data) {
ESP_LOGE(TAG, "Tx %s: malloc failed", __func__);
res = ESP_FAIL;
goto exit;
}
data[0] = HCI_H4_CMD;
memcpy(&data[1], buf, buf_len - 1);
res = esp_hosted_tx(ESP_HCI_IF, 0, data, buf_len, H_BUFF_NO_ZEROCOPY, data, H_DEFLT_FREE_FUNC, 0);
exit:
ble_transport_free(buf);
return res;
}
#endif // H_BT_HOST_ESP_NIMBLE
#if H_BT_HOST_ESP_BLUEDROID
static esp_bluedroid_hci_driver_callbacks_t s_callback = { 0 };
H_WEAK_REF int hci_rx_handler(uint8_t *buf, size_t buf_len)
{
uint8_t * data = buf;
uint32_t len_total_read = buf_len;
if (s_callback.notify_host_recv) {
s_callback.notify_host_recv(data, len_total_read);
}
return ESP_OK;
}
void hosted_hci_bluedroid_open(void)
{
ESP_ERROR_CHECK(transport_drv_reconfigure());
}
void hosted_hci_bluedroid_close(void)
{
}
esp_err_t hosted_hci_bluedroid_register_host_callback(const esp_bluedroid_hci_driver_callbacks_t *callback)
{
s_callback.notify_host_send_available = callback->notify_host_send_available;
s_callback.notify_host_recv = callback->notify_host_recv;
return ESP_OK;
}
void hosted_hci_bluedroid_send(uint8_t *data, uint16_t len)
{
int res;
uint8_t * ptr = NULL;
ptr = g_h.funcs->_h_malloc_align(len, HOSTED_MEM_ALIGNMENT_64);
if (!ptr) {
ESP_LOGE(TAG, "%s: malloc failed", __func__);
return;
}
memcpy(ptr, data, len);
res = esp_hosted_tx(ESP_HCI_IF, 0, ptr, len, H_BUFF_NO_ZEROCOPY, ptr, H_DEFLT_FREE_FUNC, 0);
if (res) {
ESP_LOGE(TAG, "%s: Tx failed", __func__);
}
}
bool hosted_hci_bluedroid_check_send_available(void)
{
return true;
}
#endif // H_BT_HOST_ESP_BLUEDROID