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>
This commit is contained in:
@@ -0,0 +1,501 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "host_power_save.h"
|
||||
#include "esp_hosted_transport_init.h"
|
||||
#include "esp_hosted_interface.h"
|
||||
#include "host_power_save.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "esp_log.h"
|
||||
#include <string.h>
|
||||
#include "esp_timer.h"
|
||||
#include "interface.h"
|
||||
static char *TAG = "host_ps";
|
||||
|
||||
#if H_HOST_PS_ALLOWED
|
||||
|
||||
static host_power_save_config_t hps_config = HOST_POWER_SAVE_DEFAULT_CONFIG_DISABLED();
|
||||
uint8_t power_save_on;
|
||||
|
||||
#if H_HOST_PS_DEEP_SLEEP_ALLOWED
|
||||
SemaphoreHandle_t wakeup_sem;
|
||||
|
||||
/* Wakeup GPIO control macros */
|
||||
#define set_host_wakeup_gpio() gpio_set_level(hps_config.host_wakeup_gpio, hps_config.host_wakeup_level)
|
||||
#define reset_host_wakeup_gpio() gpio_set_level(hps_config.host_wakeup_gpio, !hps_config.host_wakeup_level)
|
||||
#endif
|
||||
|
||||
static esp_err_t configure_host_wakeup_gpio(uint32_t gpio_num, uint8_t level)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
/* Make sure the GPIO is configured */
|
||||
gpio_config_t io_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = (1ULL << gpio_num)
|
||||
};
|
||||
|
||||
ret = gpio_config(&io_conf);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure host wakeup GPIO: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Configure pull based on wakeup level */
|
||||
if (level) {
|
||||
ret = gpio_pulldown_en(gpio_num);
|
||||
} else {
|
||||
ret = gpio_pullup_en(gpio_num);
|
||||
}
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure pull based on wakeup level: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
extern interface_context_t *if_context;
|
||||
extern interface_handle_t *if_handle;
|
||||
|
||||
int is_host_wakeup_needed(interface_buffer_handle_t *buf_handle)
|
||||
{
|
||||
int wakup_needed = 0;
|
||||
char reason[100] = "";
|
||||
#if H_HOST_PS_ALLOWED
|
||||
uint8_t *buf_start;
|
||||
|
||||
buf_start = buf_handle->payload;
|
||||
|
||||
#if CONFIG_ESP_SPI_HD_HOST_INTERFACE || CONFIG_ESP_UART_HOST_INTERFACE || CONFIG_ESP_SPI_HOST_INTERFACE
|
||||
/* Flow control packet cannot miss */
|
||||
if (buf_handle->wifi_flow_ctrl_en) {
|
||||
strlcpy(reason, "flow_ctl_pkt", sizeof(reason));
|
||||
wakup_needed = 1;
|
||||
goto end;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!buf_start) {
|
||||
/* Do not wake up */
|
||||
strlcpy(reason, "NULL_TxBuff", sizeof(reason));
|
||||
wakup_needed = 0;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Wake up for serial msg */
|
||||
switch (buf_handle->if_type) {
|
||||
|
||||
case ESP_SERIAL_IF:
|
||||
strlcpy(reason, "serial tx msg", sizeof(reason));
|
||||
wakup_needed = 1;
|
||||
goto end;
|
||||
break;
|
||||
|
||||
case ESP_HCI_IF:
|
||||
strlcpy(reason, "bt tx msg", sizeof(reason));
|
||||
wakup_needed = 1;
|
||||
goto end;
|
||||
break;
|
||||
|
||||
case ESP_PRIV_IF:
|
||||
strlcpy(reason, "priv tx msg", sizeof(reason));
|
||||
wakup_needed = 1;
|
||||
goto end;
|
||||
break;
|
||||
|
||||
case ESP_TEST_IF:
|
||||
strlcpy(reason, "test tx msg", sizeof(reason));
|
||||
wakup_needed = 1;
|
||||
goto end;
|
||||
break;
|
||||
|
||||
case ESP_STA_IF:
|
||||
|
||||
/* User can parse the incoming Wi-Fi frame here, for any selective wake up, or drop.
|
||||
* if network split configured, you can also amend function,
|
||||
* nw_split_filter_and_route_packet() to process at slave or selective forward to host,
|
||||
* by inspecting frame/packet
|
||||
**/
|
||||
strlcpy(reason, "sta tx msg", sizeof(reason));
|
||||
wakup_needed = 1;
|
||||
goto end;
|
||||
break;
|
||||
|
||||
case ESP_AP_IF:
|
||||
strlcpy(reason, "ap tx msg", sizeof(reason));
|
||||
wakup_needed = 1;
|
||||
goto end;
|
||||
break;
|
||||
}
|
||||
|
||||
end:
|
||||
#else
|
||||
strlcpy(reason, "host_ps_disabled", sizeof(reason));
|
||||
wakup_needed = 0;
|
||||
#endif
|
||||
|
||||
if (wakup_needed) {
|
||||
ESP_LOGI(TAG, "Wakeup needed, reason %s", reason);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Wakeup not needed");
|
||||
}
|
||||
return wakup_needed;
|
||||
}
|
||||
|
||||
int host_power_save_init(host_power_save_config_t *config)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
#if H_HOST_PS_ALLOWED
|
||||
if (config) {
|
||||
memcpy(&hps_config, config, sizeof(host_power_save_config_t));
|
||||
} else {
|
||||
hps_config = (host_power_save_config_t){
|
||||
.enable = 1,
|
||||
.host_wakeup_gpio = H_HOST_WAKE_UP_GPIO,
|
||||
.host_wakeup_level = H_HOST_WAKEUP_GPIO_LEVEL,
|
||||
.callbacks = {0}
|
||||
};
|
||||
}
|
||||
|
||||
if (!hps_config.enable) {
|
||||
ESP_LOGI(TAG, "Host power save disabled via config");
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if H_HOST_PS_DEEP_SLEEP_ALLOWED
|
||||
/* Configure GPIO from config or use Kconfig default */
|
||||
assert(hps_config.host_wakeup_gpio != -1);
|
||||
|
||||
/* Configure the host wakeup GPIO */
|
||||
ret = configure_host_wakeup_gpio(hps_config.host_wakeup_gpio, hps_config.host_wakeup_level);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure host wakeup GPIO: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGI(TAG, "Host wakeup: IO%u, level:%u (configured)",
|
||||
hps_config.host_wakeup_gpio, gpio_get_level(hps_config.host_wakeup_gpio));
|
||||
reset_host_wakeup_gpio();
|
||||
|
||||
ESP_LOGI(TAG, "Host wakeup: IO%u, level:%u (active %s)",
|
||||
hps_config.host_wakeup_gpio, gpio_get_level(hps_config.host_wakeup_gpio),
|
||||
hps_config.host_wakeup_level ? "HIGH" : "LOW");
|
||||
|
||||
if (!wakeup_sem) {
|
||||
assert(wakeup_sem = xSemaphoreCreateBinary());
|
||||
xSemaphoreGive(wakeup_sem);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Store callbacks from config */
|
||||
if (hps_config.callbacks.host_power_save_on_prepare_cb ||
|
||||
hps_config.callbacks.host_power_save_on_ready_cb ||
|
||||
hps_config.callbacks.host_power_save_off_prepare_cb ||
|
||||
hps_config.callbacks.host_power_save_off_ready_cb) {
|
||||
ESP_LOGI(TAG, "Host power save callbacks registered");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Host power save init without callbacks (manual control)");
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
int host_power_save_deinit(void)
|
||||
{
|
||||
#if H_HOST_PS_ALLOWED
|
||||
#if H_HOST_PS_DEEP_SLEEP_ALLOWED
|
||||
if (wakeup_sem) {
|
||||
xSemaphoreTake(wakeup_sem, portMAX_DELAY);
|
||||
xSemaphoreGive(wakeup_sem);
|
||||
vSemaphoreDelete(wakeup_sem);
|
||||
wakeup_sem = NULL;
|
||||
}
|
||||
#endif
|
||||
/* Clear callbacks */
|
||||
memset(&hps_config.callbacks, 0, sizeof(hps_config.callbacks));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int host_power_save_set_callbacks(host_power_save_callbacks_t *new_callbacks)
|
||||
{
|
||||
#if H_HOST_PS_ALLOWED
|
||||
if (!new_callbacks) {
|
||||
ESP_LOGE(TAG, "NULL callbacks provided");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Warn if replacing existing callbacks */
|
||||
if (hps_config.callbacks.host_power_save_on_prepare_cb ||
|
||||
hps_config.callbacks.host_power_save_on_ready_cb ||
|
||||
hps_config.callbacks.host_power_save_off_prepare_cb ||
|
||||
hps_config.callbacks.host_power_save_off_ready_cb) {
|
||||
ESP_LOGW(TAG, "Replacing existing host power save callbacks");
|
||||
}
|
||||
|
||||
/* Replace callbacks */
|
||||
memcpy(&hps_config.callbacks, new_callbacks, sizeof(hps_config.callbacks));
|
||||
ESP_LOGI(TAG, "Host power save callbacks updated");
|
||||
|
||||
return 0;
|
||||
#else
|
||||
ESP_LOGE(TAG, "Host power save not enabled");
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#define GET_CURR_TIME_IN_MS() esp_timer_get_time()/100
|
||||
|
||||
/* Add new callback function for ESP Timer */
|
||||
#if H_HOST_PS_ALLOWED && H_HOST_PS_DEEP_SLEEP_ALLOWED
|
||||
static void clean_wakeup_gpio_timer_cb(void* arg)
|
||||
{
|
||||
reset_host_wakeup_gpio();
|
||||
ESP_EARLY_LOGI(TAG, "Cleared wakeup gpio, IO%u", hps_config.host_wakeup_gpio);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if H_HOST_PS_ALLOWED && H_HOST_PS_DEEP_SLEEP_ALLOWED
|
||||
static int trigger_host_wakeup(uint32_t timeout_ms)
|
||||
{
|
||||
esp_timer_handle_t timer = NULL;
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint64_t start_time = GET_CURR_TIME_IN_MS();
|
||||
uint8_t wakeup_success = 0;
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = &clean_wakeup_gpio_timer_cb,
|
||||
.name = "host_wakeup_timer",
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "WAKE UP Host!!!!!\n");
|
||||
|
||||
do {
|
||||
set_host_wakeup_gpio();
|
||||
|
||||
/* Create ESP Timer instead of FreeRTOS timer */
|
||||
if (!timer) {
|
||||
ret = esp_timer_create(&timer_args, &timer);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to create timer for host wakeup");
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(timer);
|
||||
|
||||
/* Start one-shot timer (10ms) */
|
||||
ret = esp_timer_start_once(timer, 10000); /* 10ms in microseconds */
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start timer for host wakeup");
|
||||
esp_timer_delete(timer);
|
||||
timer = NULL;
|
||||
break;
|
||||
}
|
||||
vTaskDelay(100);
|
||||
|
||||
if (wakeup_sem) {
|
||||
/* wait for host resume */
|
||||
ret = xSemaphoreTake(wakeup_sem, pdMS_TO_TICKS(100));
|
||||
if (ret == pdPASS) {
|
||||
ESP_LOGI(TAG, "Wakeup semaphore acquired - host responded");
|
||||
xSemaphoreGive(wakeup_sem);
|
||||
wakeup_success = 1;
|
||||
break;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Wakeup semaphore wait timeout, retrying...");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_CURR_TIME_IN_MS() - start_time > timeout_ms) {
|
||||
/* timeout */
|
||||
ESP_LOGI(TAG, "%s:%u timeout Curr:%llu start:%llu timeout:%lu",
|
||||
__func__,__LINE__, GET_CURR_TIME_IN_MS(), start_time, timeout_ms);
|
||||
break;
|
||||
}
|
||||
|
||||
} while (1);
|
||||
|
||||
/* Clean up timer if it's still active */
|
||||
if (timer) {
|
||||
esp_timer_stop(timer);
|
||||
esp_timer_delete(timer);
|
||||
timer = NULL;
|
||||
}
|
||||
|
||||
return wakeup_success;
|
||||
}
|
||||
#endif
|
||||
|
||||
int wakeup_host_mandate(uint32_t timeout_ms)
|
||||
{
|
||||
#if H_HOST_PS_ALLOWED && H_HOST_PS_DEEP_SLEEP_ALLOWED
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_LOGI(TAG, "Mandate host wakeup");
|
||||
|
||||
/* Configure the host wakeup GPIO */
|
||||
ret = configure_host_wakeup_gpio(hps_config.host_wakeup_gpio, hps_config.host_wakeup_level);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure host wakeup GPIO: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Trigger host wakeup */
|
||||
return trigger_host_wakeup(timeout_ms);
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int wakeup_host(uint32_t timeout_ms)
|
||||
{
|
||||
|
||||
#if H_HOST_PS_ALLOWED
|
||||
int wakeup_success = 0;
|
||||
|
||||
if(!is_host_power_saving()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!hps_config.enable) {
|
||||
ESP_LOGW(TAG, "%s: host_power_save_init never called, ignore", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!if_handle || !if_context) {
|
||||
ESP_LOGE(TAG, "Failed to wakeup, if_handle or if_context is NULL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "if_handle->state: %u", if_handle->state);
|
||||
if (if_handle->state < DEACTIVE) {
|
||||
ESP_LOGI(TAG, "%s:%u Re-Initializing driver\n", __func__, __LINE__);
|
||||
|
||||
/* host wakeup mandated in sdio init */
|
||||
wakeup_success = 1;
|
||||
if_handle = if_context->if_ops->init();
|
||||
if (!if_handle) {
|
||||
ESP_LOGE(TAG, "%s:%u Failed to initialize driver\n", __func__, __LINE__);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_host_power_saving()) {
|
||||
wakeup_success = trigger_host_wakeup(timeout_ms);
|
||||
ESP_LOGI(TAG, "host %s woke up", is_host_power_saving() ? "not" : "");
|
||||
}
|
||||
|
||||
return wakeup_success;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int host_power_save_alert(uint32_t ps_evt)
|
||||
{
|
||||
|
||||
#if H_HOST_PS_ALLOWED
|
||||
/* Running in interrupt context - Keep it short and simple */
|
||||
BaseType_t do_yeild = pdFALSE;
|
||||
|
||||
if (!hps_config.enable) {
|
||||
ESP_EARLY_LOGW(TAG, "%s: host_power_save_init never called, ignore", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ESP_POWER_SAVE_ON == ps_evt) {
|
||||
ESP_EARLY_LOGI(TAG, "Host Sleep");
|
||||
|
||||
/* USER CALLBACK: Prepare to enter power save */
|
||||
if (hps_config.callbacks.host_power_save_on_prepare_cb) {
|
||||
hps_config.callbacks.host_power_save_on_prepare_cb();
|
||||
}
|
||||
|
||||
#if H_HOST_PS_DEEP_SLEEP_ALLOWED
|
||||
if (wakeup_sem) {
|
||||
/* Host sleeping */
|
||||
/* Check if we're in ISR context */
|
||||
if (xPortInIsrContext()) {
|
||||
xSemaphoreTakeFromISR(wakeup_sem, &do_yeild);
|
||||
} else {
|
||||
/* Task context - use regular take with no timeout (should succeed immediately) */
|
||||
xSemaphoreTake(wakeup_sem, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
power_save_on = 1;
|
||||
|
||||
if (!if_handle || !if_context || if_handle->state < DEACTIVE) {
|
||||
ESP_EARLY_LOGE(TAG, "%s:%u Failed to bring down transport", __func__, __LINE__);
|
||||
}
|
||||
|
||||
if (if_handle->state >= DEACTIVE) {
|
||||
if (!if_context->if_ops || !if_context->if_ops->deinit) {
|
||||
ESP_EARLY_LOGI(TAG, "%s:%u if_context->if_ops->deinit not available", __func__, __LINE__);
|
||||
} else {
|
||||
ESP_EARLY_LOGI(TAG, "%s:%u Deinitializing driver", __func__, __LINE__);
|
||||
if_context->if_ops->deinit(if_handle);
|
||||
/* if_handle->state would be changed to DEINIT */
|
||||
}
|
||||
}
|
||||
|
||||
/* USER CALLBACK: Power save active, device ready */
|
||||
if (hps_config.callbacks.host_power_save_on_ready_cb) {
|
||||
hps_config.callbacks.host_power_save_on_ready_cb();
|
||||
}
|
||||
|
||||
} else if ((ESP_POWER_SAVE_OFF == ps_evt) || (ESP_OPEN_DATA_PATH == ps_evt)) {
|
||||
ESP_EARLY_LOGI(TAG, "Host Awake, transport state: %u", if_handle->state);
|
||||
|
||||
/* USER CALLBACK: Prepare to exit power save */
|
||||
if (hps_config.callbacks.host_power_save_off_prepare_cb) {
|
||||
hps_config.callbacks.host_power_save_off_prepare_cb();
|
||||
}
|
||||
|
||||
power_save_on = 0;
|
||||
|
||||
#if H_HOST_PS_DEEP_SLEEP_ALLOWED
|
||||
if (wakeup_sem) {
|
||||
ESP_EARLY_LOGI(TAG, "Giving wakeup semaphore");
|
||||
/* Check if we're in ISR context */
|
||||
if (xPortInIsrContext()) {
|
||||
xSemaphoreGiveFromISR(wakeup_sem, &do_yeild);
|
||||
} else {
|
||||
/* Task context - use regular give */
|
||||
xSemaphoreGive(wakeup_sem);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* USER CALLBACK: Power save off, device ready */
|
||||
if (hps_config.callbacks.host_power_save_off_ready_cb) {
|
||||
hps_config.callbacks.host_power_save_off_ready_cb();
|
||||
}
|
||||
|
||||
} else {
|
||||
ESP_EARLY_LOGI(TAG, "Ignore event[%u]", ps_evt);
|
||||
}
|
||||
#if 0
|
||||
/* Only yield from ISR if we're actually in ISR context */
|
||||
if (do_yeild == pdTRUE && xPortInIsrContext()) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int is_host_power_saving(void)
|
||||
{
|
||||
#if H_HOST_PS_ALLOWED
|
||||
return power_save_on;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user