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>
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*prevent recursive inclusion */
|
||||
#ifndef __SERIAL_DRV_H
|
||||
#define __SERIAL_DRV_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** includes **/
|
||||
#include "serial_ll_if.h"
|
||||
|
||||
/** Exported Functions **/
|
||||
/*
|
||||
* rpc_platform_init function initializes the rpc
|
||||
* path data structures
|
||||
* Input parameter
|
||||
* None
|
||||
* Returns
|
||||
* SUCCESS(0) or FAILURE(-1) of above operation
|
||||
*/
|
||||
int rpc_platform_init(void);
|
||||
|
||||
/*
|
||||
* rpc_platform_deinit function cleans up the rpc
|
||||
* path library data structure
|
||||
* Input parameter
|
||||
* None
|
||||
* Returns
|
||||
* SUCCESS(0) or FAILURE(-1) of above operation
|
||||
*/
|
||||
int rpc_platform_deinit(void);
|
||||
|
||||
/*
|
||||
* serial_drv_open function opens driver interface.
|
||||
*
|
||||
* Input parameter
|
||||
* transport : Pointer to transport driver
|
||||
* Returns
|
||||
* serial_drv_handle : Driver Handle
|
||||
*/
|
||||
struct serial_drv_handle_t* serial_drv_open (const char* transport);
|
||||
|
||||
/*
|
||||
* serial_drv_write function writes in_count bytes
|
||||
* from buffer to driver interface
|
||||
*
|
||||
* Input parameter
|
||||
* serial_drv_handle : Driver Handler
|
||||
* buf : Data Buffer (Data written from buf to
|
||||
* driver interface)
|
||||
* in_count : Number of Bytes to be written
|
||||
* Output parameter
|
||||
* out_count : Number of Bytes written
|
||||
*
|
||||
* Returns
|
||||
* SUCCESS(0) or FAILURE(-1) of above operation
|
||||
*/
|
||||
int serial_drv_write (struct serial_drv_handle_t* serial_drv_handle,
|
||||
uint8_t* buf, int in_count, int* out_count);
|
||||
|
||||
/*
|
||||
* serial_drv_read function gets buffer from serial driver
|
||||
* after TLV parsing. output buffer is protobuf encoded
|
||||
*
|
||||
* Input parameter
|
||||
* serial_drv_handle : Driver Handle
|
||||
* Output parameter
|
||||
* out_nbyte : Size of TLV parsed buffer
|
||||
* Returns
|
||||
* buf : Protocol encoded data Buffer
|
||||
* caller will decode the protobuf
|
||||
*/
|
||||
|
||||
uint8_t * serial_drv_read(struct serial_drv_handle_t *serial_drv_handle,
|
||||
uint32_t *out_nbyte);
|
||||
|
||||
/*
|
||||
* serial_drv_close function closes driver interface.
|
||||
*
|
||||
* Input parameter
|
||||
* serial_drv_handle : Driver Handle
|
||||
* Returns
|
||||
* SUCCESS(0) or FAILURE(-1) of above operation
|
||||
*/
|
||||
|
||||
int serial_drv_close (struct serial_drv_handle_t** serial_drv_handle);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** Includes **/
|
||||
#include "string.h"
|
||||
#include "serial_ll_if.h"
|
||||
#include "transport_drv.h"
|
||||
#include "esp_hosted_transport.h"
|
||||
#include "esp_hosted_header.h"
|
||||
#include "port_esp_hosted_host_log.h"
|
||||
#include "esp_hosted_log.h"
|
||||
|
||||
DEFINE_LOG_TAG(serial_ll);
|
||||
|
||||
/** Macros / Constants **/
|
||||
#define MAX_SERIAL_INTF 2
|
||||
#define TO_SERIAL_INFT_QUEUE_SIZE 10
|
||||
|
||||
typedef enum {
|
||||
INIT,
|
||||
ACTIVE,
|
||||
DESTROY
|
||||
} serial_ll_state_e;
|
||||
|
||||
static struct rx_data {
|
||||
int len;
|
||||
uint8_t *data;
|
||||
} r;
|
||||
|
||||
/* data structures needed for serial driver */
|
||||
static queue_handle_t to_serial_ll_intf_queue[MAX_SERIAL_INTF];
|
||||
static serial_ll_handle_t * interface_handle_g[MAX_SERIAL_INTF] = {NULL};
|
||||
static uint8_t conn_num = 0;
|
||||
|
||||
/** Function Declarations **/
|
||||
static int serial_ll_open (serial_ll_handle_t *serial_ll_hdl);
|
||||
static uint8_t * serial_ll_read (const serial_ll_handle_t * serial_ll_hdl,
|
||||
uint16_t * rlen);
|
||||
static int serial_ll_write (const serial_ll_handle_t * serial_ll_hdl,
|
||||
uint8_t * wbuffer, const uint16_t wlen);
|
||||
static int serial_ll_close (serial_ll_handle_t * serial_ll_hdl);
|
||||
|
||||
|
||||
/* define serial interface */
|
||||
static struct serial_ll_operations serial_ll_fops = {
|
||||
.open = serial_ll_open,
|
||||
.read = serial_ll_read,
|
||||
.write = serial_ll_write,
|
||||
.close = serial_ll_close,
|
||||
};
|
||||
|
||||
/** function definition **/
|
||||
|
||||
/** Local Functions **/
|
||||
|
||||
/**
|
||||
* @brief Open new Serial interface
|
||||
* @param serial_ll_hdl - handle of serial interface
|
||||
* @retval 0 if success, -1 on failure
|
||||
*/
|
||||
static int serial_ll_open(serial_ll_handle_t *serial_ll_hdl)
|
||||
{
|
||||
if (! serial_ll_hdl) {
|
||||
ESP_LOGE(TAG, "serial invalid hdr");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (serial_ll_hdl->queue) {
|
||||
/* clean up earlier queue */
|
||||
ESP_LOGW(TAG, "Flush existing serial queue");
|
||||
g_h.funcs->_h_destroy_queue(serial_ll_hdl->queue);
|
||||
}
|
||||
|
||||
/* Queue - serial rx */
|
||||
serial_ll_hdl->queue = g_h.funcs->_h_create_queue(TO_SERIAL_INFT_QUEUE_SIZE,
|
||||
sizeof(interface_buffer_handle_t));
|
||||
|
||||
if (! serial_ll_hdl->queue) {
|
||||
serial_ll_close(serial_ll_hdl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
serial_ll_hdl->state = ACTIVE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get serial handle for iface_num
|
||||
* @param iface_num - serial connection number
|
||||
* @retval serial_ll_hdl - output handle of serial interface
|
||||
*/
|
||||
static serial_ll_handle_t * get_serial_ll_handle(const uint8_t iface_num)
|
||||
{
|
||||
if ((iface_num < MAX_SERIAL_INTF) &&
|
||||
(interface_handle_g[iface_num]) &&
|
||||
(interface_handle_g[iface_num]->state == ACTIVE)) {
|
||||
|
||||
return interface_handle_g[iface_num];
|
||||
}
|
||||
ESP_LOGD(TAG, "%s Failed to get interface handle", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close serial interface
|
||||
* @param serial_ll_hdl - handle
|
||||
* @retval rbuffer - ready buffer read on serial interface
|
||||
*/
|
||||
static int serial_ll_close(serial_ll_handle_t * serial_ll_hdl)
|
||||
{
|
||||
serial_ll_hdl->state = DESTROY;
|
||||
|
||||
if (serial_ll_hdl->queue) {
|
||||
ESP_LOGI(TAG, "Clean-up serial queue");
|
||||
|
||||
interface_buffer_handle_t buf_handle;
|
||||
while (g_h.funcs->_h_dequeue_item(serial_ll_hdl->queue, &buf_handle, 0) == 0) {
|
||||
/* Free buffer using the provided free function */
|
||||
if (buf_handle.priv_buffer_handle && buf_handle.free_buf_handle) {
|
||||
buf_handle.free_buf_handle(buf_handle.priv_buffer_handle);
|
||||
}
|
||||
}
|
||||
|
||||
g_h.funcs->_h_destroy_queue(serial_ll_hdl->queue);
|
||||
serial_ll_hdl->queue = NULL;
|
||||
}
|
||||
|
||||
/* reset connection */
|
||||
if (conn_num > 0) {
|
||||
interface_handle_g[--conn_num] = NULL;
|
||||
}
|
||||
|
||||
if (serial_ll_hdl) {
|
||||
g_h.funcs->_h_free(serial_ll_hdl);
|
||||
serial_ll_hdl = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Serial interface read non blocking
|
||||
* @param serial_ll_hdl - handle
|
||||
* rlen - output param, number of bytes read
|
||||
* @retval rbuffer - ready buffer read on serial interface
|
||||
*/
|
||||
static uint8_t * serial_ll_read(const serial_ll_handle_t * serial_ll_hdl,
|
||||
uint16_t * rlen)
|
||||
{
|
||||
/* This is a non-blocking call */
|
||||
interface_buffer_handle_t buf_handle = {0};
|
||||
|
||||
/* Initial value */
|
||||
*rlen = 0 ;
|
||||
|
||||
/* check if serial interface valid */
|
||||
if ((! serial_ll_hdl) || (serial_ll_hdl->state != ACTIVE)) {
|
||||
ESP_LOGE(TAG, "serial invalid interface");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* This is **blocking** receive.
|
||||
*
|
||||
* Although not needed in normal circumstances,
|
||||
* User can convert it to non blocking using below steps:
|
||||
*
|
||||
* To make it non blocking:
|
||||
* As an another design option, serial_rx_callback can also be
|
||||
* thought of incoming data indication, i.e. asynchronous rx
|
||||
* indication, which can be used by higher layer in separate
|
||||
* dedicated rx task to receive and process rx data.
|
||||
*
|
||||
* In our example, first approach of blocking read is used.
|
||||
*/
|
||||
ESP_LOGV(TAG, "before deQ for ll_read");
|
||||
if (g_h.funcs->_h_dequeue_item(serial_ll_hdl->queue, &buf_handle, HOSTED_BLOCK_MAX)) {
|
||||
ESP_LOGE(TAG, "serial queue recv failed ");
|
||||
return NULL;
|
||||
}
|
||||
ESP_LOGV(TAG, "after deQ for ll_read");
|
||||
|
||||
/* proceed only if payload and length are sane */
|
||||
if (!buf_handle.payload || !buf_handle.payload_len) {
|
||||
ESP_LOGE(TAG, "%s: Dequeue result in empty buffer",__func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*rlen = buf_handle.payload_len;
|
||||
|
||||
ESP_HEXLOGV("ll_read", buf_handle.payload, buf_handle.payload_len, 32);
|
||||
|
||||
return buf_handle.payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serial interface write
|
||||
* @param serial_ll_hdl - handle
|
||||
* wlen - number of bytes to write
|
||||
* wbuffer - buffer to send
|
||||
* @retval 0 on success, -1 on failure
|
||||
*/
|
||||
static int serial_ll_write(const serial_ll_handle_t * serial_ll_hdl,
|
||||
uint8_t * wbuffer, const uint16_t wlen)
|
||||
{
|
||||
|
||||
if ((! serial_ll_hdl) || (serial_ll_hdl->state != ACTIVE)) {
|
||||
ESP_LOGE(TAG, "serial invalid interface for write");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!wbuffer || !wlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (wlen > MAX_FRAGMENTABLE_PAYLOAD_SIZE) {
|
||||
ESP_LOGE(TAG, "Payload too large: %u bytes (max allowed: %u)", wlen, MAX_FRAGMENTABLE_PAYLOAD_SIZE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t offset = 0;
|
||||
uint16_t remaining_len = wlen;
|
||||
void (*free_func)(void *) = NULL;
|
||||
uint8_t *buf_to_free = NULL;
|
||||
|
||||
while (remaining_len > 0) {
|
||||
uint16_t frag_len = (remaining_len > MAX_PAYLOAD_SIZE) ? MAX_PAYLOAD_SIZE : remaining_len;
|
||||
uint8_t *frag_ptr = wbuffer + offset;
|
||||
|
||||
uint8_t flags = 0;
|
||||
if (remaining_len > MAX_PAYLOAD_SIZE) {
|
||||
flags |= MORE_FRAGMENT;
|
||||
}
|
||||
else {
|
||||
// FRAGMENTATION COMPLETED
|
||||
buf_to_free = wbuffer;
|
||||
free_func = H_DEFLT_FREE_FUNC;
|
||||
}
|
||||
|
||||
int ret = esp_hosted_tx(serial_ll_hdl->if_type,
|
||||
serial_ll_hdl->if_num,
|
||||
frag_ptr,
|
||||
frag_len,
|
||||
H_BUFF_NO_ZEROCOPY,
|
||||
buf_to_free, free_func,
|
||||
flags);
|
||||
if (ret != ESP_OK) {
|
||||
if (flags & MORE_FRAGMENT) {
|
||||
H_FREE_PTR_WITH_FUNC(H_DEFLT_FREE_FUNC, wbuffer);
|
||||
}
|
||||
ESP_LOGE(TAG, "esp_hosted_tx failed at offset=%u len=%u", offset, frag_len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
offset += frag_len;
|
||||
remaining_len -= frag_len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serial rx handler is called by spi driver when there
|
||||
* is incoming data with interface type is Serial.
|
||||
* @param if_num - interface instance
|
||||
* rxbuff - buffer from spi driver
|
||||
* rx_len - size of rxbuff
|
||||
* seq_num - serial sequence number
|
||||
* flag_more_frags - Flags for fragmentation
|
||||
* @retval 0 on success, else failure
|
||||
*/
|
||||
int serial_ll_rx_handler(interface_buffer_handle_t * buf_handle)
|
||||
{
|
||||
|
||||
#define SERIAL_ALLOC_REALLOC_RDATA() \
|
||||
do { \
|
||||
if(!r.data) { \
|
||||
r.data = (uint8_t *)g_h.funcs->_h_malloc(buf_handle->payload_len); \
|
||||
} else { \
|
||||
r.data = (uint8_t *)g_h.funcs->_h_realloc(r.data, r.len + buf_handle->payload_len); \
|
||||
} \
|
||||
if (!r.data) { \
|
||||
ESP_LOGE(TAG, "Failed to allocate serial data"); \
|
||||
goto serial_buff_cleanup; \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
serial_ll_handle_t * serial_ll_hdl = NULL;
|
||||
uint8_t *serial_buf = NULL;
|
||||
interface_buffer_handle_t new_buf_handle = {0};
|
||||
|
||||
/* Check valid handle and length */
|
||||
if (!buf_handle || !buf_handle->payload_len) {
|
||||
ESP_LOGE(TAG, "%s:%u Invalid parameters", __func__, __LINE__);
|
||||
goto serial_buff_cleanup;
|
||||
}
|
||||
|
||||
serial_ll_hdl = get_serial_ll_handle(buf_handle->if_num);
|
||||
|
||||
/* Is serial interface up */
|
||||
if ((! serial_ll_hdl) || (serial_ll_hdl->state != ACTIVE)) {
|
||||
ESP_LOGD(TAG, "Serial interface not active (possibly shutting down), discarding packet");
|
||||
goto serial_buff_cleanup_silent;
|
||||
}
|
||||
|
||||
|
||||
/* Accumulate fragments */
|
||||
if (buf_handle->flag & MORE_FRAGMENT) {
|
||||
|
||||
ESP_LOGD(TAG, "Fragment!!!");
|
||||
SERIAL_ALLOC_REALLOC_RDATA();
|
||||
|
||||
g_h.funcs->_h_memcpy((r.data + r.len), buf_handle->payload, buf_handle->payload_len);
|
||||
r.len += buf_handle->payload_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SERIAL_ALLOC_REALLOC_RDATA();
|
||||
|
||||
/* No or last fragment */
|
||||
g_h.funcs->_h_memcpy((r.data + r.len), buf_handle->payload, buf_handle->payload_len);
|
||||
r.len += buf_handle->payload_len;
|
||||
|
||||
serial_buf = (uint8_t *)g_h.funcs->_h_malloc(r.len);
|
||||
if(!serial_buf) {
|
||||
ESP_LOGE(TAG, "Malloc failed, drop pkt");
|
||||
goto serial_buff_cleanup;
|
||||
}
|
||||
g_h.funcs->_h_memcpy(serial_buf, r.data, r.len);
|
||||
|
||||
/* form new buf handle for processing of serial msg */
|
||||
new_buf_handle.if_type = ESP_SERIAL_IF;
|
||||
new_buf_handle.if_num = buf_handle->if_num;
|
||||
new_buf_handle.payload_len = r.len;
|
||||
new_buf_handle.payload = serial_buf;
|
||||
new_buf_handle.priv_buffer_handle = serial_buf;
|
||||
new_buf_handle.free_buf_handle = g_h.funcs->_h_free;
|
||||
|
||||
/* clear old buf handle */
|
||||
//H_FREE_PTR_WITH_FUNC(buf_handle->free_buf_handle, buf_handle->priv_buffer_handle);
|
||||
|
||||
|
||||
r.len = 0;
|
||||
g_h.funcs->_h_free(r.data);
|
||||
r.data = NULL;
|
||||
|
||||
ESP_LOGV(TAG, "before ENQ for ll_read");
|
||||
/* send to serial queue */
|
||||
if (g_h.funcs->_h_queue_item(serial_ll_hdl->queue,
|
||||
&new_buf_handle, HOSTED_BLOCK_MAX)) {
|
||||
ESP_LOGE(TAG, "Failed send serialif queue[%u]", new_buf_handle.if_num);
|
||||
goto serial_buff_cleanup;
|
||||
}
|
||||
ESP_LOGV(TAG, "after ENQ for ll_read");
|
||||
|
||||
/* Indicate higher layer about data ready for consumption */
|
||||
if (serial_ll_hdl->serial_rx_callback) {
|
||||
(*serial_ll_hdl->serial_rx_callback) ();
|
||||
} else {
|
||||
goto serial_buff_cleanup;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
serial_buff_cleanup:
|
||||
ESP_LOGE(TAG, "Err occurred, discard current buffer");
|
||||
|
||||
serial_buff_cleanup_silent:
|
||||
/* Common cleanup path - used for both errors and expected shutdown */
|
||||
H_FREE_PTR_WITH_FUNC(buf_handle->free_buf_handle, buf_handle->priv_buffer_handle);
|
||||
|
||||
r.len = 0;
|
||||
|
||||
H_FREE_PTR_WITH_FUNC(new_buf_handle.free_buf_handle, new_buf_handle.priv_buffer_handle);
|
||||
|
||||
g_h.funcs->_h_free(r.data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Exported Functions **/
|
||||
|
||||
/**
|
||||
* @brief create and return new serial interface
|
||||
* @param serial_rx_callback - callback to be invoked on rx data
|
||||
* @retval serial_ll_hdl - output handle of serial interface
|
||||
*/
|
||||
serial_ll_handle_t * serial_ll_init(void(*serial_rx_callback)(void))
|
||||
{
|
||||
serial_ll_handle_t * serial_ll_hdl = NULL;
|
||||
|
||||
/* Check if more serial interfaces be created */
|
||||
if ((conn_num+1) < MAX_SERIAL_INTF) {
|
||||
|
||||
serial_ll_hdl = (serial_ll_handle_t *)g_h.funcs->_h_malloc(sizeof(serial_ll_handle_t));
|
||||
if (! serial_ll_hdl) {
|
||||
ESP_LOGE(TAG, "Serial interface - malloc failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
serial_ll_hdl->if_type = ESP_SERIAL_IF;
|
||||
serial_ll_hdl->if_num = conn_num;
|
||||
serial_ll_hdl->queue = to_serial_ll_intf_queue[conn_num];
|
||||
serial_ll_hdl->state = INIT;
|
||||
serial_ll_hdl->fops = &serial_ll_fops;
|
||||
serial_ll_hdl->serial_rx_callback = serial_rx_callback;
|
||||
interface_handle_g[conn_num] = serial_ll_hdl;
|
||||
conn_num++;
|
||||
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Number of serial interface connections overflow");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return serial_ll_hdl;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*prevent recursive inclusion */
|
||||
#ifndef __SERIAL_LL_IF_H
|
||||
#define __SERIAL_LL_IF_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** includes **/
|
||||
#include "transport_drv.h"
|
||||
#include "port_esp_hosted_host_os.h"
|
||||
|
||||
struct serial_ll_operations;
|
||||
|
||||
/* serial interface handle */
|
||||
typedef struct serial_handle_s {
|
||||
queue_handle_t queue;
|
||||
uint8_t if_type;
|
||||
uint8_t if_num;
|
||||
struct serial_ll_operations *fops;
|
||||
uint8_t state;
|
||||
void (*serial_rx_callback) (void);
|
||||
} serial_ll_handle_t;
|
||||
|
||||
/* serial interface */
|
||||
struct serial_ll_operations {
|
||||
/**
|
||||
* @brief Open new Serial interface
|
||||
* @param serial_ll_hdl - handle of serial interface
|
||||
* @retval 0 if success, -1 on failure
|
||||
*/
|
||||
int (*open) (serial_ll_handle_t *serial_ll_hdl);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Serial interface read non blocking
|
||||
* This is non blocking receive
|
||||
* In case higher layer using serial interface needs to make
|
||||
* blocking read, it should register serial_rx_callback through
|
||||
* serial_ll_init.
|
||||
*
|
||||
* serial_rx_callback is notification mechanism to implementer of
|
||||
* serial interface. Higher layer would understand there is data
|
||||
* is ready through this notification. Then higher layer should do
|
||||
* serial_read API to receive actual data.
|
||||
*
|
||||
* As an another design option, serial_rx_callback can also be
|
||||
* thought of incoming data indication, i.e. asynchronous rx
|
||||
* indication, which can be used by higher layer in separate
|
||||
* dedicated rx task to receive and process rx data.
|
||||
*
|
||||
* @param serial_ll_hdl - handle
|
||||
* rlen - output param, number of bytes read
|
||||
*
|
||||
* @retval rbuffer - ready buffer read on serial interface
|
||||
*/
|
||||
uint8_t * (*read) (const serial_ll_handle_t * serial_ll_hdl,
|
||||
uint16_t * rlen);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Serial interface write
|
||||
* @param serial_ll_hdl - handle
|
||||
* wlen - number of bytes to write
|
||||
* wbuffer - buffer to send
|
||||
* @retval 0 on success, -1 on failure
|
||||
*/
|
||||
int (*write) (const serial_ll_handle_t * serial_ll_hdl,
|
||||
uint8_t * wbuffer, const uint16_t wlen);
|
||||
|
||||
|
||||
/**
|
||||
* @brief close - Close serial interface
|
||||
* @param serial_ll_hdl - handle
|
||||
* @retval rbuffer - ready buffer read on serial interface
|
||||
*/
|
||||
int (*close) (serial_ll_handle_t * serial_ll_hdl);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief serial_ll_init - create and return new serial interface
|
||||
* @param serial_rx_callback - callback to be invoked on rx data
|
||||
* @retval serial_ll_hdl - output handle of serial interface
|
||||
*/
|
||||
serial_ll_handle_t * serial_ll_init(void(*rx_data_ind)(void));
|
||||
|
||||
int serial_ll_rx_handler(interface_buffer_handle_t * buf_handle);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __SERIAL_LL_IF_H */
|
||||
Reference in New Issue
Block a user