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>
266 lines
6.8 KiB
C
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);
|
|
}
|
|
}
|