Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
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

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>
This commit is contained in:
2026-07-15 09:50:27 +02:00
co-authored by Claude Fable 5
parent ac9d24e0f9
commit cb5826e02b
666 changed files with 216925 additions and 0 deletions
@@ -0,0 +1,8 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(gpio_expander_example)
@@ -0,0 +1,38 @@
# ESP-Hosted GPIO Control Example
This example demonstrates how to use the ESP-Hosted solution to control the GPIOs of the slave (co-processor) from the host MCU.
## How to Use
### Feature Switch
Feature can be controlled at Host and Slave using `CONFIG_ESP_HOSTED_ENABLE_GPIO_EXPANDER` option.
It is by default disabled at slave.
**Slave Configuration:**
* The firmware flashed on the slave board must have the GPIO RPC feature enabled. In the slave's `idf.py menuconfig`, set the following:
```
(Top) →
Example Configuration --->
[*] Enable GPIO Expander support (host can control slave GPIOs)
```
This corresponds to the `CONFIG_ESP_HOSTED_ENABLE_GPIO_EXPANDER=y` option.
### Build and Flash
1. **Slave**
* Slave build & flashed with above config
2. **Host:**
* Set your target board (e.g., `idf.py set-target esp32p4`).
* Build and flash the project: `idf.py -p <host_port> build flash monitor`.
The host application will:
1. Initialize the ESP-Hosted transport.
2. Configure a specific GPIO pin on the slave as an output.
3. Toggle the GPIO level (HIGH and LOW) every second.
4. After a few toggles, it will reconfigure the same GPIO as an input.
5. Read and print the GPIO level.
You can connect an LED to the specified GPIO on the slave board to visually verify the output functionality.
@@ -0,0 +1,3 @@
idf_component_register(SRCS "host_gpio_expander_example.c"
PRIV_REQUIRES nvs_flash
INCLUDE_DIRS ".")
@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_hosted.h"
static const char* TAG = "gpio_expander_example";
#define SLAVE_GPIO_PIN 2
static esp_err_t configure_and_test_gpio(esp_hosted_cp_gpio_config_t *config, const char *demo_name)
{
esp_err_t ret;
int level;
ESP_LOGW(TAG, "---- %s ----", demo_name);
ret = esp_hosted_cp_gpio_config(config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "GPIO config failed: 0x%x", ret);
return ret;
}
// For output modes, toggle the pin
if (config->mode == H_CP_GPIO_MODE_OUTPUT ||
config->mode == H_CP_GPIO_MODE_OUTPUT_OD) {
ESP_LOGI(TAG, "Setting GPIO %d LOW", SLAVE_GPIO_PIN);
esp_hosted_cp_gpio_set_level(SLAVE_GPIO_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(500));
ESP_LOGI(TAG, "Setting GPIO %d HIGH", SLAVE_GPIO_PIN);
esp_hosted_cp_gpio_set_level(SLAVE_GPIO_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(500));
ESP_LOGI(TAG, "GPIO toggled successfully");
} else {
// For input modes, read the level
ret = esp_hosted_cp_gpio_get_level(SLAVE_GPIO_PIN, &level);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "GPIO %d level: %d", SLAVE_GPIO_PIN, level);
}
}
return ESP_OK;
}
void app_main(void)
{
esp_err_t ret;
esp_hosted_cp_gpio_config_t gpio_config = {
.pin_bit_mask = (1ULL << SLAVE_GPIO_PIN),
};
// Initialize ESP-Hosted
ret = esp_hosted_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "ESP-Hosted init failed: 0x%x", ret);
return;
}
ret = esp_hosted_connect_to_slave();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Connect to slave failed: %s", esp_err_to_name(ret));
goto cleanup;
}
ESP_LOGI(TAG, "ESP-Hosted initialized successfully");
// Demo 1: Standard output
gpio_config.mode = H_CP_GPIO_MODE_OUTPUT;
gpio_config.pull_up_en = 0;
gpio_config.pull_down_en = 0;
if (configure_and_test_gpio(&gpio_config, "Demo 1: Standard Output") != ESP_OK) {
goto cleanup;
}
// Demo 2: Open-drain output with pull-up
gpio_config.mode = H_CP_GPIO_MODE_OUTPUT_OD;
gpio_config.pull_up_en = 1;
gpio_config.pull_down_en = 0;
if (configure_and_test_gpio(&gpio_config, "Demo 2: Open-Drain Output") != ESP_OK) {
goto cleanup;
}
// Demo 3: Input with pull-up
gpio_config.mode = H_CP_GPIO_MODE_INPUT;
gpio_config.pull_up_en = 1;
gpio_config.pull_down_en = 0;
if (configure_and_test_gpio(&gpio_config, "Demo 3: Input with Pull-Up") != ESP_OK) {
goto cleanup;
}
// Demo 4: Input with pull-down
gpio_config.mode = H_CP_GPIO_MODE_INPUT;
gpio_config.pull_up_en = 0;
gpio_config.pull_down_en = 1;
if (configure_and_test_gpio(&gpio_config, "Demo 4: Input with Pull-Down") != ESP_OK) {
goto cleanup;
}
ESP_LOGI(TAG, "All demos completed successfully!");
cleanup:
esp_hosted_deinit();
}
@@ -0,0 +1,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '>=2.9.5'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -0,0 +1,4 @@
# ESP-Hosted GPIO Expander Example Configuration
# Enable GPIO expander support
CONFIG_ESP_HOSTED_ENABLE_GPIO_EXPANDER=y