Files
desklock/firmware/components/esp_hosted/slave/main/sdio_slave_api.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

935 lines
25 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "esp_log.h"
#include "esp_hosted_log.h"
#include "interface.h"
#include "sdio_slave_api.h"
#include "driver/sdio_slave.h"
#include "endian.h"
#include "stats.h"
#include "esp_hosted_interface.h"
#include "esp_hosted_header.h"
#include "esp_hosted_transport.h"
#include "esp_hosted_transport_init.h"
#include "host_power_save.h"
#include "esp_hosted_coprocessor_fw_ver.h"
#include "slave_util.h"
#include "slave_config.h"
#include "mempool.h"
#if H_USE_MEMPOOL
// memory should be 4 byte aligned for DMA access
#define MEM_ALIGNMENT_BYTES 4
#endif
//#define SIMPLIFIED_SDIO_SLAVE 1
#define SDIO_DRIVER_TX_QUEUE_SIZE CONFIG_ESP_SDIO_TX_Q_SIZE
#define SDIO_RX_BUFFER_SIZE MAX_TRANSPORT_BUF_SIZE
#define SDIO_NUM_RX_BUFFERS CONFIG_ESP_SDIO_RX_Q_SIZE
static uint8_t sdio_slave_rx_buffer[SDIO_NUM_RX_BUFFERS][SDIO_RX_BUFFER_SIZE];
static sdio_slave_buf_handle_t sdio_rx_buf_handles[SDIO_NUM_RX_BUFFERS];
#define SDIO_MEMPOOL_NUM_BLOCKS SDIO_DRIVER_TX_QUEUE_SIZE+2
#if H_USE_MEMPOOL
static hosted_mempool_t * buf_mp_tx_g;
#endif
interface_context_t context;
interface_handle_t if_handle_g;
static const char *TAG = "SDIO_SLAVE";
static uint8_t hosted_constructs_created = 0;
#if !SIMPLIFIED_SDIO_SLAVE
static SemaphoreHandle_t sdio_rx_sem;
static QueueHandle_t sdio_rx_queue[MAX_PRIORITY_QUEUES];
static SemaphoreHandle_t sdio_send_queue_sem = NULL; // to count number of Tx bufs in IDF SDIO driver
static TaskHandle_t sdio_rx_task_handle = NULL;
static TaskHandle_t sdio_tx_done_task_handle = NULL;
#endif
#define SDIO_SLAVE_TO_HOST_INT_BIT7 7
#define SDIO_SLAVE_TO_HOST_INT_BIT6 6
#define HOST_INT_START_THROTTLE SDIO_SLAVE_TO_HOST_INT_BIT7
#define HOST_INT_STOP_THROTTLE SDIO_SLAVE_TO_HOST_INT_BIT6
/* Note: Sometimes the SDIO card is detected but gets problem in
* Read/Write or handling ISR because of SDIO timing issues.
* In these cases, Please tune timing below via Menuconfig
* */
#if CONFIG_ESP_SDIO_PSEND_PSAMPLE
#define SDIO_SLAVE_TIMING SDIO_SLAVE_TIMING_PSEND_PSAMPLE
#elif CONFIG_ESP_SDIO_NSEND_PSAMPLE
#define SDIO_SLAVE_TIMING SDIO_SLAVE_TIMING_NSEND_PSAMPLE
#elif CONFIG_ESP_SDIO_PSEND_NSAMPLE
#define SDIO_SLAVE_TIMING SDIO_SLAVE_TIMING_PSEND_NSAMPLE
#elif CONFIG_ESP_SDIO_NSEND_NSAMPLE
#define SDIO_SLAVE_TIMING SDIO_SLAVE_TIMING_NSEND_NSAMPLE
#else
#error No SDIO Slave Timing configured
#endif
static interface_handle_t * sdio_init(void);
static int32_t sdio_write(interface_handle_t *handle, interface_buffer_handle_t *buf_handle);
static int sdio_read(interface_handle_t *if_handle, interface_buffer_handle_t *buf_handle);
static esp_err_t sdio_reset(interface_handle_t *handle);
static void sdio_deinit(interface_handle_t *handle);
#if !SIMPLIFIED_SDIO_SLAVE
static void sdio_rx_task(void* pvParameters);
static void sdio_tx_done_task(void* pvParameters);
#endif
if_ops_t if_ops = {
.init = sdio_init,
.write = sdio_write,
.read = sdio_read,
.reset = sdio_reset,
.deinit = sdio_deinit,
};
static inline void sdio_mempool_create(void)
{
#if H_USE_MEMPOOL
hosted_mempool_config_t config = {
.pre_allocated_mem = NULL,
.pre_allocated_mem_size = 0,
.num_blocks = SDIO_MEMPOOL_NUM_BLOCKS,
.block_size = SDIO_RX_BUFFER_SIZE,
.alignment_in_bytes = MEM_ALIGNMENT_BYTES,
.malloc = slave_util_malloc,
.calloc = slave_util_calloc,
.memset = memset,
.free = free,
};
buf_mp_tx_g = hosted_mempool_create(&config);
assert(buf_mp_tx_g);
#endif // H_USE_MEMPOOL
}
static inline void sdio_mempool_destroy(void)
{
#if H_USE_MEMPOOL
hosted_mempool_destroy(buf_mp_tx_g);
#endif // #if H_USE_MEMPOOL
}
static inline void *sdio_buffer_tx_alloc(size_t nbytes, uint need_memset)
{
MEMPOOL_ALLOC(buf_mp_tx_g, nbytes, need_memset);
}
static inline void sdio_buffer_tx_free(void *buf)
{
MEMPOOL_FREE(buf_mp_tx_g, buf);
}
#if !SIMPLIFIED_SDIO_SLAVE
static void start_rx_data_throttling_if_needed(void)
{
uint32_t queue_load;
uint8_t load_percent;
if (slv_cfg_g.throttle_high_threshold > 0) {
/* Already throttling, nothing to be done */
if (slv_state_g.current_throttling)
return;
queue_load = uxQueueMessagesWaiting(sdio_rx_queue[PRIO_Q_OTHERS]);
load_percent = (queue_load*100/SDIO_NUM_RX_BUFFERS);
if (load_percent > slv_cfg_g.throttle_high_threshold) {
slv_state_g.current_throttling = 1;
ESP_LOGV(TAG, "start data throttling at host");
#if ESP_PKT_STATS
pkt_stats.sta_flowctrl_on++;
#endif
sdio_slave_send_host_int(HOST_INT_START_THROTTLE);
}
}
}
static void stop_rx_data_throttling_if_needed(void)
{
uint32_t queue_load;
uint8_t load_percent;
if (slv_state_g.current_throttling) {
queue_load = uxQueueMessagesWaiting(sdio_rx_queue[PRIO_Q_OTHERS]);
load_percent = (queue_load*100/SDIO_NUM_RX_BUFFERS);
if (load_percent < slv_cfg_g.throttle_low_threshold) {
slv_state_g.current_throttling = 0;
ESP_LOGV(TAG, "stop data throttling at host");
#if ESP_PKT_STATS
pkt_stats.sta_flowctrl_off++;
#endif
sdio_slave_send_host_int(HOST_INT_STOP_THROTTLE);
}
}
}
#endif
interface_context_t *interface_insert_driver(int (*event_handler)(uint8_t val))
{
ESP_LOGI(TAG, "Using SDIO interface");
memset(&context, 0, sizeof(context));
context.type = SDIO;
context.if_ops = &if_ops;
context.event_handler = event_handler;
return &context;
}
int interface_remove_driver()
{
memset(&context, 0, sizeof(context));
return 0;
}
IRAM_ATTR static void event_cb(uint8_t val)
{
if (context.event_handler) {
context.event_handler(val);
}
}
void generate_startup_event(uint8_t cap, uint32_t ext_cap)
{
struct esp_payload_header *header = NULL;
interface_buffer_handle_t buf_handle = {0};
struct esp_priv_event *event = NULL;
uint8_t *pos = NULL;
uint16_t len = 0;
uint8_t raw_tp_cap = 0;
esp_err_t ret = ESP_OK;
raw_tp_cap = debug_get_raw_tp_conf();
memset(&buf_handle, 0, sizeof(buf_handle));
buf_handle.payload = sdio_buffer_tx_alloc(512, MEMSET_REQUIRED);
assert(buf_handle.payload);
header = (struct esp_payload_header *) buf_handle.payload;
header->if_type = ESP_PRIV_IF;
header->if_num = 0;
header->offset = htole16(sizeof(struct esp_payload_header));
header->priv_pkt_type = ESP_PACKET_TYPE_EVENT;
UPDATE_HEADER_TX_PKT_NO(header);
/* Populate event data */
event = (struct esp_priv_event *) (buf_handle.payload + sizeof(struct esp_payload_header));
event->event_type = ESP_PRIV_EVENT_INIT;
/* Populate TLVs for event */
pos = event->event_data;
/* TLVs start */
/* TLV - Board type */
*pos = ESP_PRIV_FIRMWARE_CHIP_ID; pos++;len++;
*pos = LENGTH_1_BYTE; pos++;len++;
*pos = CONFIG_IDF_FIRMWARE_CHIP_ID; pos++;len++;
#if CONFIG_ESP_SDIO_STREAMING_MODE
uint8_t sdio_mode = 1;
#else
uint8_t sdio_mode = 0;
#endif
/* TLV - SDIO mode */
*pos = ESP_PRIV_TRANS_SDIO_MODE; pos++;len++;
*pos = LENGTH_1_BYTE; pos++;len++;
*pos = sdio_mode; pos++;len++;
/* TLV - Capability */
*pos = ESP_PRIV_CAPABILITY; pos++;len++;
*pos = LENGTH_1_BYTE; pos++;len++;
*pos = cap; pos++;len++;
/* TLV - Extended Capability */
*pos = ESP_PRIV_CAP_EXT; pos++;len++;
*pos = LENGTH_4_BYTE; pos++;len++;
*pos = (ext_cap) & 0xFF; pos++;len++;
*pos = (ext_cap >> 8) & 0xFF; pos++;len++;
*pos = (ext_cap >> 16) & 0xFF; pos++;len++;
*pos = (ext_cap >> 24) & 0xFF; pos++;len++;
*pos = ESP_PRIV_TEST_RAW_TP; pos++;len++;
*pos = LENGTH_1_BYTE; pos++;len++;
*pos = raw_tp_cap; pos++;len++;
*pos = ESP_PRIV_TX_Q_SIZE; pos++;len++;
*pos = LENGTH_1_BYTE; pos++;len++;
*pos = SDIO_DRIVER_TX_QUEUE_SIZE; pos++;len++;
*pos = ESP_PRIV_RX_Q_SIZE; pos++;len++;
*pos = LENGTH_1_BYTE; pos++;len++;
#if !SIMPLIFIED_SDIO_SLAVE
*pos = SDIO_NUM_RX_BUFFERS; pos++;len++;
#else
*pos = 0; pos++;len++;
#endif
// convert fw version into a uint32_t
uint32_t fw_version = ESP_HOSTED_VERSION_VAL(PROJECT_VERSION_MAJOR_1,
PROJECT_VERSION_MINOR_1,
PROJECT_VERSION_PATCH_1);
// send fw version as a little-endian uint32_t
*pos = ESP_PRIV_FIRMWARE_VERSION; pos++;len++;
*pos = LENGTH_4_BYTE; pos++;len++;
// send fw_version as a little endian 32bit value
*pos = (fw_version & 0xff); pos++;len++;
*pos = (fw_version >> 8) & 0xff; pos++;len++;
*pos = (fw_version >> 16) & 0xff; pos++;len++;
*pos = (fw_version >> 24) & 0xff; pos++;len++;
/* TLVs end */
event->event_len = len;
/* payload len = Event len + sizeof(event type) + sizeof(event len) */
len += 2;
header->len = htole16(len);
buf_handle.payload_len = len + sizeof(struct esp_payload_header);
#if CONFIG_ESP_SDIO_CHECKSUM
header->checksum = htole16(compute_checksum(buf_handle.payload, buf_handle.payload_len));
#endif
ESP_HEXLOGV("bus_tx_init", buf_handle.payload, buf_handle.payload_len, 32);
#if !SIMPLIFIED_SDIO_SLAVE
xSemaphoreTake(sdio_send_queue_sem, portMAX_DELAY);
ret = sdio_slave_send_queue(buf_handle.payload, buf_handle.payload_len,
buf_handle.payload, portMAX_DELAY);
#else
ret = sdio_slave_transmit(buf_handle.payload, buf_handle.payload_len);
#endif
if (ret != ESP_OK) {
ESP_LOGE(TAG , "sdio slave tx error, ret : 0x%x\r\n", ret);
sdio_buffer_tx_free(buf_handle.payload);
return;
}
#if SIMPLIFIED_SDIO_SLAVE
sdio_buffer_tx_free(buf_handle.payload);
#endif
}
static void sdio_read_done(void *handle)
{
ESP_LOGV(TAG, "sdio_read_done, reloading buf");
sdio_slave_recv_load_buf((sdio_slave_buf_handle_t) handle);
}
static interface_handle_t * sdio_init(void)
{
if (if_handle_g.state >= DEACTIVE) {
return &if_handle_g;
}
esp_err_t ret = ESP_OK;
sdio_slave_config_t config = {
#if CONFIG_ESP_SDIO_STREAMING_MODE
.sending_mode = SDIO_SLAVE_SEND_STREAM,
#else
.sending_mode = SDIO_SLAVE_SEND_PACKET,
#endif
.send_queue_size = SDIO_DRIVER_TX_QUEUE_SIZE,
.recv_buffer_size = SDIO_RX_BUFFER_SIZE,
.event_cb = event_cb,
/* Note: For small devkits there may be no pullups on the board.
This enables the internal pullups to help evaluate the driver
quickly. However the internal pullups are not sufficient and not
reliable, please make sure external pullups are connected to the
bus in your real design.
*/
//.flags = SDIO_SLAVE_FLAG_INTERNAL_PULLUP,
#if CONFIG_ESP_SDIO_DEFAULT_SPEED
.flags = SDIO_SLAVE_FLAG_DEFAULT_SPEED,
#elif CONFIG_ESP_SDIO_HIGH_SPEED
.flags = SDIO_SLAVE_FLAG_HIGH_SPEED,
#else
#error Invalid SDIO bus speed selection
#endif
.timing = SDIO_SLAVE_TIMING,
};
#if !SIMPLIFIED_SDIO_SLAVE
#if CONFIG_ESP_SDIO_STREAMING_MODE
ESP_LOGI(TAG, "%s: sending mode: SDIO_SLAVE_SEND_STREAM", __func__);
#else
ESP_LOGI(TAG, "%s: sending mode: SDIO_SLAVE_SEND_PACKET", __func__);
#endif
#endif
ESP_LOGI(TAG, "%s: ESP32 SDIO DriverTxQ[%d] timing[%u]\n", __func__, SDIO_DRIVER_TX_QUEUE_SIZE, config.timing);
#if !SIMPLIFIED_SDIO_SLAVE
if (hosted_constructs_created == 0) {
sdio_send_queue_sem = xSemaphoreCreateCounting(SDIO_DRIVER_TX_QUEUE_SIZE, SDIO_DRIVER_TX_QUEUE_SIZE);
assert(sdio_send_queue_sem);
sdio_rx_sem = xSemaphoreCreateCounting(SDIO_NUM_RX_BUFFERS* MAX_PRIORITY_QUEUES, 0);
assert(sdio_rx_sem != NULL);
for (int prio_q_idx=0; prio_q_idx<MAX_PRIORITY_QUEUES;prio_q_idx++) {
sdio_rx_queue[prio_q_idx] = xQueueCreate(SDIO_NUM_RX_BUFFERS, sizeof(interface_buffer_handle_t));
assert(sdio_rx_queue[prio_q_idx] != NULL);
}
}
#endif
ret = sdio_slave_initialize(&config);
if (ret != ESP_OK) {
return NULL;
}
for (int i = 0; i < SDIO_NUM_RX_BUFFERS; i++) {
sdio_rx_buf_handles[i] = sdio_slave_recv_register_buf(sdio_slave_rx_buffer[i]);
assert(sdio_rx_buf_handles[i] != NULL);
ret = sdio_slave_recv_load_buf(sdio_rx_buf_handles[i]);
if (ret != ESP_OK) {
sdio_slave_deinit();
return NULL;
}
}
/* ESP-Hosted uses bit6 and bit 7 internal use. Rest free for Users */
sdio_slave_set_host_intena(SDIO_SLAVE_HOSTINT_SEND_NEW_PACKET |
SDIO_SLAVE_HOSTINT_BIT0 |
SDIO_SLAVE_HOSTINT_BIT1 |
SDIO_SLAVE_HOSTINT_BIT2 |
SDIO_SLAVE_HOSTINT_BIT3 |
SDIO_SLAVE_HOSTINT_BIT4 |
SDIO_SLAVE_HOSTINT_BIT5 |
SDIO_SLAVE_HOSTINT_BIT6 |
SDIO_SLAVE_HOSTINT_BIT7);
ret = sdio_slave_start();
if (ret != ESP_OK) {
sdio_slave_deinit();
return NULL;
}
memset(&if_handle_g, 0, sizeof(if_handle_g));
if (hosted_constructs_created == 0) {
sdio_mempool_create();
hosted_constructs_created = 1;
}
if_handle_g.state = ACTIVE;
#if !SIMPLIFIED_SDIO_SLAVE
/* Create tasks only if they don't exist (preserve across init/deinit cycles) */
if (sdio_rx_task_handle == NULL) {
assert(xTaskCreate(sdio_rx_task, "sdio_rx_task" ,
CONFIG_ESP_HOSTED_DEFAULT_TASK_STACK_SIZE, NULL,
CONFIG_ESP_HOSTED_DEFAULT_TASK_PRIORITY, &sdio_rx_task_handle) == pdTRUE);
}
/* task to clean up after doing sdio tx */
if (sdio_tx_done_task_handle == NULL) {
assert(xTaskCreate(sdio_tx_done_task, "sdio_tx_done_task" ,
CONFIG_ESP_HOSTED_DEFAULT_TASK_STACK_SIZE, NULL,
CONFIG_ESP_HOSTED_DEFAULT_TASK_PRIORITY+1, &sdio_tx_done_task_handle) == pdTRUE);
}
#endif
return &if_handle_g;
}
/* wait for sdio to finish tx, then free the buffer */
#if !SIMPLIFIED_SDIO_SLAVE
static void sdio_tx_done_task(void* pvParameters)
{
esp_err_t res;
uint8_t sendbuf = 0;
uint8_t *sendbuf_p = &sendbuf;
while (true) {
/* Check if SDIO is deinitialized */
if (if_handle_g.state < DEACTIVE) {
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
res = sdio_slave_send_get_finished((void**)&sendbuf_p, portMAX_DELAY);
if (res) {
ESP_LOGE(TAG, "sdio_slave_send_get_finished() error");
continue;
}
xSemaphoreGive(sdio_send_queue_sem);
sdio_buffer_tx_free(sendbuf_p);
}
}
#endif
static inline struct esp_payload_header * update_tx_header(uint8_t* sendbuf,
interface_buffer_handle_t *buf_handle)
{
struct esp_payload_header *header = (struct esp_payload_header *)sendbuf;
uint16_t offset = sizeof(struct esp_payload_header);
if (unlikely(!header))
return NULL;
memset (header, 0, sizeof(struct esp_payload_header));
/* Initialize header */
header->if_type = buf_handle->if_type;
header->if_num = buf_handle->if_num;
header->len = htole16(buf_handle->payload_len);
header->offset = htole16(offset);
header->seq_num = htole16(buf_handle->seq_num);
header->flags = buf_handle->flag;
//header->throttle_cmd = buf_handle->flow_ctl_en;
UPDATE_HEADER_TX_PKT_NO(header);
#if CONFIG_ESP_SDIO_CHECKSUM
header->checksum = htole16(compute_checksum(sendbuf,
offset+buf_handle->payload_len));
#endif
return header;
}
static inline esp_err_t copy_tx_payload(uint8_t *sendbuf, uint8_t* payload, uint16_t len)
{
memcpy(sendbuf + sizeof(struct esp_payload_header), payload, len);
return ESP_OK;
}
static int32_t sdio_write(interface_handle_t *handle, interface_buffer_handle_t *buf_handle)
{
uint32_t total_len = 0;
uint8_t* sendbuf = NULL;
uint16_t offset = sizeof(struct esp_payload_header);
int ret = 0;
if (!handle || !buf_handle) {
ESP_LOGE(TAG , "Invalid arguments");
return ESP_FAIL;
}
if (handle->state < ACTIVE) {
ESP_LOGI(TAG, "Driver state not active, drop");
return ESP_FAIL;
}
if (is_host_power_saving()) {
ESP_LOGI(TAG, "Host sleeping, drop");
return ESP_FAIL;
}
if (!buf_handle->payload_len && !buf_handle->payload && !buf_handle->flag) {
ESP_LOGW(TAG , "Invalid arguments, len:%d, payload:%p, flag:%d, drop", buf_handle->payload_len, buf_handle->payload, buf_handle->flag);
return ESP_FAIL;
}
total_len = buf_handle->payload_len + offset;
sendbuf = sdio_buffer_tx_alloc(total_len, MEMSET_REQUIRED);
if (sendbuf == NULL) {
ESP_LOGE(TAG, "send buffer[%"PRIu32"] malloc fail", total_len);
return ESP_FAIL;
}
copy_tx_payload(sendbuf, buf_handle->payload, buf_handle->payload_len);
update_tx_header(sendbuf, buf_handle);
ESP_HEXLOGV("bus_tx", sendbuf, total_len, 32);
#if !SIMPLIFIED_SDIO_SLAVE
if (xSemaphoreTake(sdio_send_queue_sem, portMAX_DELAY) != pdTRUE) {
sdio_buffer_tx_free(sendbuf);
return ESP_FAIL;
}
ret = sdio_slave_send_queue(sendbuf, total_len, sendbuf, portMAX_DELAY);
#else
ret = sdio_slave_transmit(sendbuf, total_len);
#endif
if (ret != ESP_OK) {
ESP_LOGE(TAG , "sdio slave transmit error, ret : 0x%x\r\n", ret);
#if !SIMPLIFIED_SDIO_SLAVE
xSemaphoreGive(sdio_send_queue_sem);
#endif
sdio_buffer_tx_free(sendbuf);
return ESP_FAIL;
}
#if SIMPLIFIED_SDIO_SLAVE
sdio_buffer_tx_free(sendbuf);
#endif
#if ESP_PKT_STATS
if (buf_handle->if_type == ESP_STA_IF)
pkt_stats.sta_sh_out++;
else if (buf_handle->if_type == ESP_SERIAL_IF)
pkt_stats.serial_tx_total++;
#endif
return buf_handle->payload_len;
}
#if !SIMPLIFIED_SDIO_SLAVE
static int sdio_read(interface_handle_t *if_handle, interface_buffer_handle_t *buf_handle)
{
if (!if_handle || (if_handle->state < DEACTIVE) || !buf_handle) {
ESP_LOGE(TAG, "%s: Invalid state/args", __func__);
return ESP_FAIL;
}
xSemaphoreTake(sdio_rx_sem, portMAX_DELAY);
if (pdFALSE == xQueueReceive(sdio_rx_queue[PRIO_Q_SERIAL], buf_handle, 0))
if (pdFALSE == xQueueReceive(sdio_rx_queue[PRIO_Q_BT], buf_handle, 0))
if (pdFALSE == xQueueReceive(sdio_rx_queue[PRIO_Q_OTHERS], buf_handle, 0)) {
ESP_LOGE(TAG, "%s No element in rx queue", __func__);
return ESP_FAIL;
}
stop_rx_data_throttling_if_needed();
return buf_handle->payload_len;
}
static void sdio_rx_task(void* pvParameters)
{
esp_err_t ret = ESP_OK;
struct esp_payload_header *header = NULL;
#if CONFIG_ESP_SDIO_CHECKSUM
uint16_t rx_checksum = 0, checksum = 0;
#endif
uint16_t len = 0, offset = 0;
size_t sdio_read_len = 0;
interface_buffer_handle_t buf_handle = {0};
uint8_t flags = 0;
uint32_t recv_timeout = portMAX_DELAY;
for(;;) {
/* Check if SDIO is deinitialized */
if (if_handle_g.state < DEACTIVE) {
ESP_LOGV(TAG, "SDIO is deinitialized, cannot read");
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
#if H_PS_UNLOAD_BUS_WHILE_PS
recv_timeout = pdMS_TO_TICKS(10);
#endif
ret = sdio_slave_recv(&(buf_handle.sdio_buf_handle), &(buf_handle.payload),
&(sdio_read_len), recv_timeout);
if (ret) {
/* Not an error if timed out, just return and let caller try again */
if (ret == ESP_ERR_TIMEOUT) {
continue;
}
ESP_LOGE(TAG, "sdio_slave_recv failed. ret [0x%x]", ret);
continue;
}
buf_handle.payload_len = sdio_read_len & 0xFFFF;
header = (struct esp_payload_header *) buf_handle.payload;
UPDATE_HEADER_RX_PKT_NO(header);
flags = header->flags;
if (flags & FLAG_POWER_SAVE_STARTED) {
if (context.event_handler) {
context.event_handler(ESP_POWER_SAVE_ON);
}
} else if (flags & FLAG_POWER_SAVE_STOPPED) {
if (context.event_handler) {
context.event_handler(ESP_POWER_SAVE_OFF);
}
}
len = le16toh(header->len);
if (!len) {
ESP_LOGE(TAG, "sdio_slave_recv returned 0 len");
sdio_read_done(buf_handle.sdio_buf_handle);
continue;
}
offset = le16toh(header->offset);
if (buf_handle.payload_len < len+offset) {
ESP_LOGE(TAG, "%s: err: read_len[%u] < len[%u]+offset[%u]", __func__,
buf_handle.payload_len, len, offset);
sdio_read_done(buf_handle.sdio_buf_handle);
continue;
}
#if CONFIG_ESP_SDIO_CHECKSUM
rx_checksum = le16toh(header->checksum);
header->checksum = 0;
checksum = compute_checksum(buf_handle.payload, len+offset);
if (checksum != rx_checksum) {
ESP_LOGE(TAG, "sdio rx calc_chksum[%u] != exp_chksum[%u], drop pkt", checksum, rx_checksum);
sdio_read_done(buf_handle.sdio_buf_handle);
continue;
}
#endif
buf_handle.if_type = header->if_type;
buf_handle.if_num = header->if_num;
buf_handle.free_buf_handle = sdio_read_done;
#if ESP_PKT_STATS
if (header->if_type == ESP_STA_IF)
pkt_stats.hs_bus_sta_in++;
#endif
start_rx_data_throttling_if_needed();
if (header->if_type == ESP_SERIAL_IF) {
xQueueSend(sdio_rx_queue[PRIO_Q_SERIAL], &buf_handle, portMAX_DELAY);
} else if (header->if_type == ESP_HCI_IF) {
xQueueSend(sdio_rx_queue[PRIO_Q_BT], &buf_handle, portMAX_DELAY);
} else {
xQueueSend(sdio_rx_queue[PRIO_Q_OTHERS], &buf_handle, portMAX_DELAY);
}
xSemaphoreGive(sdio_rx_sem);
}
}
#else /* !SIMPLIFIED_SDIO_SLAVE */
static int sdio_read(interface_handle_t *if_handle, interface_buffer_handle_t *buf_handle)
{
esp_err_t ret = ESP_OK;
struct esp_payload_header *header = NULL;
#if CONFIG_ESP_SDIO_CHECKSUM
uint16_t rx_checksum = 0, checksum = 0;
#endif
uint16_t len = 0;
size_t sdio_read_len = 0;
uint32_t recv_timeout = portMAX_DELAY;
if (!if_handle || !buf_handle) {
ESP_LOGE(TAG, "Invalid arguments to sdio_read");
return ESP_FAIL;
}
if (if_handle->state < DEACTIVE)
return ESP_FAIL;
#if H_PS_UNLOAD_BUS_WHILE_PS
recv_timeout = pdMS_TO_TICKS(10);
#endif
/* Use a timeout instead of indefinite blocking to allow processing reset events */
ret = sdio_slave_recv(&(buf_handle->sdio_buf_handle), &(buf_handle->payload),
&(sdio_read_len), recv_timeout);
if (ret) {
/* Not an error if timed out, just return and let caller try again */
if (ret == ESP_ERR_TIMEOUT) {
return 0;
}
ESP_LOGD(TAG, "sdio_slave_recv returned failure");
return ESP_FAIL;
}
buf_handle->payload_len = sdio_read_len & 0xFFFF;
header = (struct esp_payload_header *) buf_handle->payload;
UPDATE_HEADER_RX_PKT_NO(header);
#if 0
if (header->flags & FLAG_POWER_SAVE_STARTED) {
ESP_LOGI(TAG, "Host informed starting to power sleep");
event_cb(ESP_POWER_SAVE_ON);
}
if (header->flags & FLAG_POWER_SAVE_STOPPED) {
ESP_LOGI(TAG, "Host informed that it waken up");
event_cb(ESP_OPEN_DATA_PATH);
}
#endif
len = le16toh(header->len) + le16toh(header->offset);
#if CONFIG_ESP_SDIO_CHECKSUM
rx_checksum = le16toh(header->checksum);
header->checksum = 0;
checksum = compute_checksum(buf_handle->payload, len);
if (checksum != rx_checksum) {
ESP_LOGE(TAG, "sdio rx calc_chksum[%u] != exp_chksum[%u], drop pkt", checksum, rx_checksum);
sdio_read_done(buf_handle->sdio_buf_handle);
return ESP_FAIL;
}
#endif
#if ESP_PKT_STATS
if (header->if_type == ESP_STA_IF)
pkt_stats.hs_bus_sta_in++;
#endif
buf_handle->if_type = header->if_type;
buf_handle->if_num = header->if_num;
buf_handle->free_buf_handle = sdio_read_done;
return len;
}
#endif /* !SIMPLIFIED_SDIO_SLAVE */
static esp_err_t sdio_reset(interface_handle_t *handle)
{
esp_err_t ret = ESP_OK;
if (handle->state >= DEACTIVE) {
handle->state = DEACTIVE;
return ESP_OK;
}
sdio_slave_stop();
ret = sdio_slave_reset();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to reset SDIO slave: %d", ret);
return ret;
}
/* ESP-Hosted uses bit6 and bit 7 internal use, rest bits free */
sdio_slave_set_host_intena(SDIO_SLAVE_HOSTINT_SEND_NEW_PACKET |
SDIO_SLAVE_HOSTINT_BIT0 |
SDIO_SLAVE_HOSTINT_BIT1 |
SDIO_SLAVE_HOSTINT_BIT2 |
SDIO_SLAVE_HOSTINT_BIT3 |
SDIO_SLAVE_HOSTINT_BIT4 |
SDIO_SLAVE_HOSTINT_BIT5 |
SDIO_SLAVE_HOSTINT_BIT6 |
SDIO_SLAVE_HOSTINT_BIT7);
ret = sdio_slave_start();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to start SDIO slave: %d", ret);
return ret;
}
handle->state = ACTIVE;
return ESP_OK;
}
#if H_PS_UNLOAD_BUS_WHILE_PS
static void sdio_deinit(interface_handle_t *handle)
{
esp_err_t ret = ESP_OK;
if (if_handle_g.state == DEINIT) {
ESP_LOGW(TAG, "SDIO already deinitialized");
return;
}
ESP_LOGI(TAG, "Deinitializing SDIO interface");
#if !SIMPLIFIED_SDIO_SLAVE
ESP_LOGI(TAG, "Waiting for sdio_rx_task to exit");
if (sdio_rx_task_handle) {
vTaskDelete(sdio_rx_task_handle);
sdio_rx_task_handle = NULL;
}
ESP_LOGI(TAG, "sdio_rx_task: Exiting");
ESP_LOGI(TAG, "Waiting for sdio_tx_done_task to exit");
if (sdio_tx_done_task_handle) {
vTaskDelete(sdio_tx_done_task_handle);
sdio_tx_done_task_handle = NULL;
}
/* Give tasks time to exit */
vTaskDelay(pdMS_TO_TICKS(50));
#endif
/* Unregister all RX buffers BEFORE stopping SDIO (stop may invalidate handles) */
for (int i = 0; i < SDIO_NUM_RX_BUFFERS; i++) {
if (sdio_rx_buf_handles[i]) {
ret = sdio_slave_recv_unregister_buf(sdio_rx_buf_handles[i]);
/* ESP_ERR_INVALID_ARG means buffer already unregistered/invalid - ignore */
if (ret != ESP_OK && ret != ESP_ERR_INVALID_ARG) {
ESP_LOGW(TAG, "Failed to unregister RX buffer %d: 0x%x", i, ret);
}
sdio_rx_buf_handles[i] = NULL;
}
}
/* First stop SDIO to prevent new operations */
sdio_slave_stop();
/* Give some time for ongoing operations to complete */
vTaskDelay(pdMS_TO_TICKS(20));
/* Clear all pending interrupts */
sdio_slave_clear_host_int(SDIO_SLAVE_HOSTINT_SEND_NEW_PACKET |
SDIO_SLAVE_HOSTINT_BIT0 |
SDIO_SLAVE_HOSTINT_BIT1 |
SDIO_SLAVE_HOSTINT_BIT2 |
SDIO_SLAVE_HOSTINT_BIT3 |
SDIO_SLAVE_HOSTINT_BIT4 |
SDIO_SLAVE_HOSTINT_BIT5 |
SDIO_SLAVE_HOSTINT_BIT6 |
SDIO_SLAVE_HOSTINT_BIT7);
/* Disable all interrupts */
sdio_slave_set_host_intena(0);
/* Reset the SDIO slave peripheral */
sdio_slave_reset();
/* Now try to clean up TX buffers with timeout */
int retry = 3;
while (retry--) {
sdio_slave_buf_handle_t buf_handle = NULL;
ret = sdio_slave_send_get_finished((void**)&buf_handle, 10); // 10ms timeout
if (ret == ESP_ERR_TIMEOUT) {
ESP_LOGW(TAG, "Buffer cleanup timed out, retrying...");
continue;
}
if (ret != ESP_OK || !buf_handle) {
break;
}
#if !SIMPLIFIED_SDIO_SLAVE
if (sdio_send_queue_sem) {
xSemaphoreGive(sdio_send_queue_sem);
}
#endif
if (buf_handle) {
sdio_buffer_tx_free(buf_handle);
}
}
sdio_slave_deinit();
if_handle_g.state = DEINIT;
ESP_LOGI(TAG, "SDIO interface deinitialized");
}
#else
static void sdio_deinit(interface_handle_t *handle)
{
}
#endif