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

182 lines
4.7 KiB
C

/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "slave_gpio_expander.h"
#include "slave_transport_gpio_pin_guard.h"
#include "slave_control.h"
#include "esp_log.h"
#if H_GPIO_EXPANDER_SUPPORT
static const char* TAG = "slave_gpio_expander";
esp_err_t req_gpio_config(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespGpioConfig, resp_gpio_config,
RpcReqGpioConfig, req_gpio_config,
rpc__resp__gpio_config__init);
gpio_config_t config = {0};
config.mode = req_payload->config->mode;
config.pull_up_en = req_payload->config->pull_up_en;
config.pull_down_en = req_payload->config->pull_down_en;
config.intr_type = req_payload->config->intr_type;
config.pin_bit_mask = req_payload->config->pin_bit_mask;
if (config.intr_type != GPIO_INTR_DISABLE) {
ESP_LOGE(TAG, "ISR is not supported from slave yet. please contact through github issues, if needed.");
resp_payload->resp = ESP_ERR_NOT_SUPPORTED;
return ESP_OK;
}
for (int pin = 0; pin < GPIO_NUM_MAX; ++pin) {
if (config.pin_bit_mask & (1ULL << pin)) {
if (!transport_gpio_pin_guard_is_eligible((gpio_num_t)pin)) {
ESP_LOGE(TAG, "GPIO pin %d is not allowed to be configured", pin);
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
}
}
RPC_RET_FAIL_IF(gpio_config(&config));
return ESP_OK;
}
esp_err_t req_gpio_reset(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespGpioResetPin, resp_gpio_reset,
RpcReqGpioResetPin, req_gpio_reset_pin,
rpc__resp__gpio_reset_pin__init);
gpio_num_t gpio_num;
gpio_num = req_payload->gpio_num;
if (!transport_gpio_pin_guard_is_eligible(gpio_num)) {
ESP_LOGE(TAG, "GPIO pin %d is not allowed to be configured", gpio_num);
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
RPC_RET_FAIL_IF(gpio_reset_pin(gpio_num));
return ESP_OK;
}
esp_err_t req_gpio_set_level(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespGpioSetLevel, resp_gpio_set_level,
RpcReqGpioSetLevel, req_gpio_set_level,
rpc__resp__gpio_set_level__init);
gpio_num_t gpio_num;
gpio_num = req_payload->gpio_num;
uint32_t level;
level = req_payload->level;
if (!transport_gpio_pin_guard_is_eligible(gpio_num)) {
ESP_LOGE(TAG, "GPIO pin %d is not allowed to be configured", gpio_num);
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
RPC_RET_FAIL_IF(gpio_set_level(gpio_num, level));
return ESP_OK;
}
esp_err_t req_gpio_get_level(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespGpioGetLevel, resp_gpio_get_level,
RpcReqGpioGetLevel, req_gpio_get_level,
rpc__resp__gpio_get_level__init);
gpio_num_t gpio_num;
gpio_num = req_payload->gpio_num;
if (!transport_gpio_pin_guard_is_eligible(gpio_num)) {
ESP_LOGE(TAG, "GPIO pin %d is not allowed to be configured", gpio_num);
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
int level = gpio_get_level(gpio_num);
resp_payload->level = level;
return ESP_OK;
}
esp_err_t req_gpio_set_direction(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespGpioSetDirection, resp_gpio_set_direction,
RpcReqGpioSetDirection, req_gpio_set_direction,
rpc__resp__gpio_set_direction__init);
gpio_num_t gpio_num;
gpio_num = req_payload->gpio_num;
gpio_mode_t mode;
mode = req_payload->mode;
if (!transport_gpio_pin_guard_is_eligible(gpio_num)) {
ESP_LOGE(TAG, "GPIO pin %d is not allowed to be configured", gpio_num);
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
RPC_RET_FAIL_IF(gpio_set_direction(gpio_num, mode));
return ESP_OK;
}
esp_err_t req_gpio_input_enable(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespGpioInputEnable, resp_gpio_input_enable,
RpcReqGpioInputEnable, req_gpio_input_enable,
rpc__resp__gpio_input_enable__init);
gpio_num_t gpio_num;
gpio_num = req_payload->gpio_num;
if (!transport_gpio_pin_guard_is_eligible(gpio_num)) {
ESP_LOGE(TAG, "GPIO pin %d is not allowed to be configured", gpio_num);
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
RPC_RET_FAIL_IF(gpio_input_enable(gpio_num));
return ESP_OK;
}
esp_err_t req_gpio_set_pull_mode(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespGpioSetPullMode, resp_gpio_set_pull_mode,
RpcReqGpioSetPullMode, req_gpio_set_pull_mode,
rpc__resp__gpio_set_pull_mode__init);
gpio_num_t gpio_num;
gpio_num = req_payload->gpio_num;
gpio_pull_mode_t pull_mode;
pull_mode = req_payload->pull;
if (!transport_gpio_pin_guard_is_eligible(gpio_num)) {
ESP_LOGE(TAG, "GPIO pin %d is not allowed to be configured", gpio_num);
resp_payload->resp = ESP_ERR_INVALID_ARG;
return ESP_OK;
}
RPC_RET_FAIL_IF(gpio_set_pull_mode(gpio_num, pull_mode));
return ESP_OK;
}
#endif