Files
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

266 lines
6.8 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "serial_if.h"
#include "serial_ll_if.h"
#include "esp_hosted_log.h"
#include "port_esp_hosted_host_log.h"
DEFINE_LOG_TAG(serial);
struct serial_drv_handle_t {
int handle; /* dummy variable */
};
static serial_ll_handle_t * serial_ll_if_g;
static void * readSemaphore;
static void rpc_rx_indication(void);
/* Global serial handle - shared by RPC RX and TX threads */
static struct serial_drv_handle_t* g_serial_drv_handle = NULL;
/* -------- Serial Drv ---------- */
struct serial_drv_handle_t* serial_drv_open(const char *transport)
{
if (!transport) {
ESP_LOGE(TAG, "Invalid parameter in open");
return NULL;
}
/* Return existing handle if already opened */
if(g_serial_drv_handle) {
ESP_LOGD(TAG, "Serial already open, returning existing handle");
return g_serial_drv_handle;
}
/* Allocate new handle */
g_serial_drv_handle = (struct serial_drv_handle_t*) g_h.funcs->_h_calloc
(1,sizeof(struct serial_drv_handle_t));
if (!g_serial_drv_handle) {
ESP_LOGE(TAG, "Failed to allocate memory \n");
return NULL;
}
ESP_LOGD(TAG, "Serial handle allocated");
return g_serial_drv_handle;
}
int serial_drv_write (struct serial_drv_handle_t* serial_drv_handle,
uint8_t* buf, int in_count, int* out_count)
{
int ret = 0;
if (!serial_drv_handle || !buf || !in_count || !out_count) {
ESP_LOGE(TAG,"Invalid parameters in write\n\r");
return RET_INVALID;
}
if( (!serial_ll_if_g) ||
(!serial_ll_if_g->fops) ||
(!serial_ll_if_g->fops->write)) {
ESP_LOGE(TAG,"serial interface not valid\n\r");
return RET_INVALID;
}
ESP_HEXLOGV("serial_write", buf, in_count, 32);
ret = serial_ll_if_g->fops->write(serial_ll_if_g, buf, in_count);
if (ret != RET_OK) {
*out_count = 0;
ESP_LOGE(TAG,"Failed to write data\n\r");
return RET_FAIL;
}
*out_count = in_count;
return RET_OK;
}
uint8_t * serial_drv_read(struct serial_drv_handle_t *serial_drv_handle,
uint32_t *out_nbyte)
{
uint16_t init_read_len = 0;
uint16_t rx_buf_len = 0;
uint8_t* read_buf = NULL;
int ret = 0;
/* Any of `RPC_EP_NAME_EVT` and `RPC_EP_NAME_RSP` could be used,
* as both have same strlen in esp_hosted_transport.h */
const char* ep_name = RPC_EP_NAME_RSP;
uint8_t *buf = NULL;
uint32_t buf_len = 0;
if (!serial_drv_handle || !out_nbyte) {
ESP_LOGE(TAG,"Invalid parameters in read\n\r");
return NULL;
}
*out_nbyte = 0;
if(!readSemaphore) {
ESP_LOGE(TAG,"Semaphore not initialized\n\r");
return NULL;
}
ESP_LOGV(TAG, "Wait for serial_ll_semaphore");
g_h.funcs->_h_get_semaphore(readSemaphore, HOSTED_BLOCK_MAX);
if( (!serial_ll_if_g) ||
(!serial_ll_if_g->fops) ||
(!serial_ll_if_g->fops->read)) {
ESP_LOGE(TAG,"serial interface refusing to read\n\r");
return NULL;
}
ESP_LOGV(TAG, "Starting serial_ll read");
/* Get buffer from serial interface */
read_buf = serial_ll_if_g->fops->read(serial_ll_if_g, &rx_buf_len);
if ((!read_buf) || (!rx_buf_len)) {
ESP_LOGE(TAG,"serial read failed\n\r");
return NULL;
}
ESP_HEXLOGV("serial_read", read_buf, rx_buf_len, 32);
/*
* Read Operation happens in two steps because total read length is unknown
* at first read.
* 1) Read fixed length of RX data
* 2) Read variable length of RX data
*
* (1) Read fixed length of RX data :
* Read fixed length of received data in below format:
* ----------------------------------------------------------------------------
* Endpoint Type | Endpoint Length | Endpoint Value | Data Type | Data Length
* ----------------------------------------------------------------------------
*
* Bytes used per field as follows:
* ---------------------------------------------------------------------------
* 1 | 2 | Endpoint Length | 1 | 2 |
* ---------------------------------------------------------------------------
*
* int_read_len = 1 + 2 + Endpoint length + 1 + 2
*/
init_read_len = SIZE_OF_TYPE + SIZE_OF_LENGTH + strlen(ep_name) +
SIZE_OF_TYPE + SIZE_OF_LENGTH;
if(rx_buf_len < init_read_len) {
HOSTED_FREE(read_buf);
ESP_LOGE(TAG,"Incomplete serial buff, return\n");
return NULL;
}
HOSTED_CALLOC(uint8_t,buf,init_read_len,free_bufs);
g_h.funcs->_h_memcpy(buf, read_buf, init_read_len);
/* parse_tlv function returns variable payload length
* of received data in buf_len
**/
ret = parse_tlv(buf, &buf_len);
if (ret || !buf_len) {
HOSTED_FREE(buf);
ESP_LOGE(TAG,"Failed to parse RX data \n\r");
goto free_bufs;
}
ESP_LOGV(TAG, "TLV parsed");
if (rx_buf_len < (init_read_len + buf_len)) {
ESP_LOGE(TAG,"Buf read on serial iface is smaller than expected len\n");
HOSTED_FREE(buf);
goto free_bufs;
}
if (rx_buf_len > (init_read_len + buf_len)) {
ESP_LOGE(TAG,"Buf read on serial iface is smaller than expected len\n");
}
HOSTED_FREE(buf);
/*
* (2) Read variable length of RX data:
*/
HOSTED_CALLOC(uint8_t,buf,buf_len,free_bufs);
g_h.funcs->_h_memcpy((buf), read_buf+init_read_len, buf_len);
HOSTED_FREE(read_buf);
*out_nbyte = buf_len;
ESP_LOGV(TAG, "Serial payload size(after removing TLV): %" PRIu32, *out_nbyte);
return buf;
free_bufs:
HOSTED_FREE(read_buf);
HOSTED_FREE(buf);
return NULL;
}
int serial_drv_close(struct serial_drv_handle_t** serial_drv_handle)
{
if (!serial_drv_handle || !(*serial_drv_handle)) {
ESP_LOGE(TAG,"Invalid parameter in close \n\r");
return RET_INVALID;
}
ESP_LOGD(TAG, "Freeing serial handle");
HOSTED_FREE(*serial_drv_handle);
*serial_drv_handle = NULL;
g_serial_drv_handle = NULL; /* Clear global so next open allocates fresh */
return RET_OK;
}
int rpc_platform_init(void)
{
/* rpc semaphore */
readSemaphore = g_h.funcs->_h_create_semaphore(H_MAX_SYNC_RPC_REQUESTS +
H_MAX_ASYNC_RPC_REQUESTS);
assert(readSemaphore);
/* grab the semaphore, so that task will be mandated to wait on semaphore */
g_h.funcs->_h_get_semaphore(readSemaphore, 0);
serial_ll_if_g = serial_ll_init(rpc_rx_indication);
if (!serial_ll_if_g) {
ESP_LOGE(TAG,"Serial interface creation failed\n\r");
assert(serial_ll_if_g);
return RET_FAIL;
}
if (RET_OK != serial_ll_if_g->fops->open(serial_ll_if_g)) {
ESP_LOGE(TAG,"Serial interface open failed\n\r");
return RET_FAIL;
}
return RET_OK;
}
/* TODO: Why this is not called in transport_pserial_close() */
int rpc_platform_deinit(void)
{
if (serial_ll_if_g) {
if (RET_OK != serial_ll_if_g->fops->close(serial_ll_if_g)) {
ESP_LOGE(TAG,"Serial interface close failed\n\r");
return RET_FAIL;
}
/* serial_ll_close frees the handle, NULL our pointer */
serial_ll_if_g = NULL;
}
if (readSemaphore) {
g_h.funcs->_h_destroy_semaphore(readSemaphore);
readSemaphore = NULL;
}
return RET_OK;
}
static void rpc_rx_indication(void)
{
/* heads up to rpc for read */
if(readSemaphore) {
g_h.funcs->_h_post_semaphore(readSemaphore);
}
}