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>
105 lines
3.7 KiB
C
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
|