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

105 lines
3.7 KiB
C

/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "slave_ext_coex.h"
#include "slave_transport_gpio_pin_guard.h"
#include "slave_control.h"
#include "esp_log.h"
#if H_EXT_COEX_SUPPORT
#include "esp_coexist.h"
static const char* TAG = "slave_coex";
esp_err_t req_ext_coex(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespExtCoex, resp_ext_coex,
RpcReqExtCoex, req_ext_coex,
rpc__resp__ext_coex__init);
#if defined(CONFIG_EXTERNAL_COEX_ENABLE)
switch (req_payload->cmd) {
case RPC__EXT_COEX_CMD__SetGpioPin: {
external_coex_wire_t wire_type = (external_coex_wire_t)req_payload->set_gpio_wire_type;
esp_external_coex_gpio_set_t gpio_pins = {
.request = (gpio_num_t)req_payload->set_gpio_request_pin,
.priority = (gpio_num_t)req_payload->set_gpio_priority_pin,
.grant = (gpio_num_t)req_payload->set_gpio_grant_pin,
.tx_line = (gpio_num_t)req_payload->set_gpio_tx_line_pin
};
if (req_payload->set_gpio_wire_type > EXTERNAL_COEXIST_WIRE_4) {
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
/* ----- Guard: reject pins reserved by active transports ---------- */
gpio_num_t coex_pins_to_check[4] = {
gpio_pins.request,
gpio_pins.priority,
gpio_pins.grant,
gpio_pins.tx_line,
};
/* Only check as many pins as the wire type actually uses:
* WIRE_1 -> request only
* WIRE_2 -> request, grant
* WIRE_3 -> request, priority, grant
* WIRE_4 -> request, priority, grant, tx_line
* Pins unused by the wire type are typically set to -1/GPIO_NUM_NC.
*/
for (int i = 0; i < 4; i++) {
gpio_num_t p = coex_pins_to_check[i];
if (p < 0 || p == GPIO_NUM_NC) continue; /* not used */
if (!transport_gpio_pin_guard_is_eligible(p)) {
ESP_LOGE(TAG, "Ext Co-Ex: GPIO %d is reserved by an active transport/console, rejecting", (int)p);
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
}
ESP_LOGW(TAG, "Ext Co-Ex: set GPIO pin %d, priority pin %d, grant pin %d, tx line pin %d with wire type %d",
(int)gpio_pins.request, (int)gpio_pins.priority, (int)gpio_pins.grant, (int)gpio_pins.tx_line, (int)wire_type);
RPC_RET_FAIL_IF(esp_enable_extern_coex_gpio_pin(wire_type, gpio_pins));
break;
}
case RPC__EXT_COEX_CMD__Disable:
ESP_LOGW(TAG, "Ext Co-Ex: disable");
RPC_RET_FAIL_IF(esp_disable_extern_coex_gpio_pin());
break;
case RPC__EXT_COEX_CMD__SetWorkMode: {
ESP_LOGW(TAG, "Ext Co-Ex: set work mode %d", (int)req_payload->set_work_mode);
esp_extern_coex_work_mode_t work_mode = (esp_extern_coex_work_mode_t)req_payload->set_work_mode;
RPC_RET_FAIL_IF(esp_external_coex_set_work_mode(work_mode));
break;
}
#if defined(SOC_EXTERNAL_COEX_ADVANCE) && SOC_EXTERNAL_COEX_ADVANCE
case RPC__EXT_COEX_CMD__SetGrantDelay:
ESP_LOGW(TAG, "Ext Co-Ex: set grant delay %d us", (int)req_payload->set_grant_delay_us);
RPC_RET_FAIL_IF(esp_external_coex_set_grant_delay((uint8_t)req_payload->set_grant_delay_us));
break;
case RPC__EXT_COEX_CMD__SetValidateHigh:
ESP_LOGW(TAG, "Ext Co-Ex: set validate high %d", (int)req_payload->set_validate_high);
RPC_RET_FAIL_IF(esp_external_coex_set_validate_high(req_payload->set_validate_high));
break;
#else
case RPC__EXT_COEX_CMD__SetGrantDelay:
case RPC__EXT_COEX_CMD__SetValidateHigh:
resp_payload->resp = ESP_ERR_NOT_SUPPORTED;
ESP_LOGE(TAG, "Ext Co-Ex: SOC_EXTERNAL_COEX_ADVANCE not defined, ignoring RPC Req [0x%x]", RPC_ID__Req_ExtCoex);
break;
#endif
default:
ESP_LOGE(TAG, "Ext Co-Ex: invalid command %d", (int)req_payload->cmd);
resp_payload->resp = ESP_ERR_INVALID_ARG;
break;
}
#else
resp_payload->resp = ESP_ERR_NOT_SUPPORTED;
#endif
return ESP_OK;
}
#endif