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

183 lines
5.2 KiB
C

/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_vfs_eventfd.h"
#include "esp_openthread.h"
#include "esp_openthread_types.h"
#include "sdkconfig.h"
#include "esp_hosted_transport_init.h"
#include "slave_util.h"
#include "slave_openthread.h"
#if CONFIG_OPENTHREAD_ENABLED
#if !CONFIG_SOC_IEEE802154_SUPPORTED
#error "Thread RCP is only supported for the SoCs which have IEEE 802.15.4"
#endif
static const char TAG[] = "h_ot";
static bool eventfd_registered = false;
static slave_openthread_state_t slave_ot_state = SLAVE_OT_STATE_NOT_INITED;
static bool slave_ot_started = false;
#define ESP_OPENTHREAD_HOSTED_RADIO_CONFIG() \
{ \
.radio_mode = RADIO_MODE_NATIVE, \
}
#define ESP_OPENTHREAD_HOSTED_PORT_CONFIG() \
{ \
.storage_partition_name = "nvs", \
.netif_queue_size = CONFIG_ESP_HOSTED_OT_NETIF_QUEUE_SIZE, \
.task_queue_size = CONFIG_ESP_HOSTED_OT_TASK_QUEUE_SIZE, \
}
#if CONFIG_ESP_HOSTED_OT_TRANSPORT_UART
// init values for data_bits, parity, stop_bits not done here
#define ESP_OPENTHREAD_HOSTED_HOST_CONFIG() \
{ \
.host_connection_mode = HOST_CONNECTION_MODE_RCP_UART, \
.host_uart_config = { \
.port = CONFIG_ESP_HOSTED_OT_UART_PORT, \
.uart_config = \
{ \
.baud_rate = CONFIG_ESP_HOSTED_OT_UART_BAUDRATE, \
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \
.rx_flow_ctrl_thresh = 0, \
.source_clk = UART_SCLK_DEFAULT, \
}, \
.rx_pin = CONFIG_ESP_HOSTED_OT_UART_PIN_RX, \
.tx_pin = CONFIG_ESP_HOSTED_OT_UART_PIN_TX, \
}, \
}
#endif // ESP_HOSTED_OT_TRANSPORT_UART
esp_err_t slave_openthread_init(void)
{
if (!eventfd_registered) {
// register only once
// eventfds used for:
// * openthread task queue
// * radio driver
esp_vfs_eventfd_config_t eventfd_config = {
.max_fds = 2,
};
if (esp_vfs_eventfd_register(&eventfd_config) == ESP_OK) {
eventfd_registered = true;
} else {
ESP_LOGE(TAG, "esp_vfs_eventfd_register failed");
return ESP_FAIL;
}
}
slave_ot_state = SLAVE_OT_STATE_INITED;
return ESP_OK;
}
esp_err_t slave_openthread_deinit(void)
{
// nothing to do
slave_ot_state = SLAVE_OT_STATE_NOT_INITED;
return ESP_OK;
}
esp_err_t slave_openthread_start(void)
{
if (slave_ot_started) // already started
return ESP_OK;
esp_err_t ret;
static esp_openthread_config_t config = {
.netif_config = {0},
.platform_config = {
.radio_config = ESP_OPENTHREAD_HOSTED_RADIO_CONFIG(),
.host_config = ESP_OPENTHREAD_HOSTED_HOST_CONFIG(),
.port_config = ESP_OPENTHREAD_HOSTED_PORT_CONFIG(),
},
};
#if CONFIG_ESP_HOSTED_OT_TRANSPORT_UART
config.platform_config.host_config.host_uart_config.uart_config.data_bits = CONFIG_ESP_HOSTED_OT_UART_NUM_DATA_BITS;
config.platform_config.host_config.host_uart_config.uart_config.parity = CONFIG_ESP_HOSTED_OT_UART_PARITY;
config.platform_config.host_config.host_uart_config.uart_config.stop_bits = CONFIG_ESP_HOSTED_OT_UART_STOP_BITS;
#endif
#if CONFIG_ESP_HOSTED_OT_TRANSPORT_HOSTED
#error OpenThread over ESP-Hosted transport not yet supported
#endif
ret = esp_openthread_start(&config);
if (ret == ESP_OK) {
slave_ot_started = true;
slave_ot_state = SLAVE_OT_STATE_READY; // enabled and ready
}
return ret;
}
esp_err_t slave_openthread_stop(void)
{
if (!slave_ot_started) // already stopped
return ESP_OK;
#if 0
// todo: enabled after IDF build error is fixed
esp_err_t res = esp_openthread_stop();
if (res != ESP_OK) {
ESP_LOGE(TAG, "esp_openthread_stop failed");
}
slave_ot_started = false;
slave_ot_state = SLAVE_OT_STATE_ENABLED; // enabled but not ready
return ret;
#else
ESP_LOGW(TAG, "esp_openthread_stop() not supported for now due to build break");
return ESP_ERR_NOT_SUPPORTED;
#endif
}
uint32_t get_ot_ext_capabilities(void)
{
uint32_t ext_cap = 0;
ESP_LOGI(TAG, "- OpenThread");
ext_cap |= ESP_OT_SUPPORT;
#if CONFIG_ESP_HOSTED_OT_TRANSPORT_UART
ESP_LOGI(TAG, " - OT over UART");
#endif
return ext_cap;
}
#else
uint32_t get_ot_ext_capabilities(void)
{
// no OT capabilities
return 0;
}
#endif // CONFIG_OPENTHREAD_ENABLED
slave_openthread_state_t slave_openthread_get_state(void)
{
return slave_ot_state;
}
// return ESP_OK if current slave feature state is bigger or equal to expected state
// else return ESP_FAIL
esp_err_t slave_openthread_state_check(slave_openthread_state_t current_state,
slave_openthread_state_t expected_state)
{
if (current_state >= expected_state)
return ESP_OK;
else
return ESP_FAIL;
}