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

169 lines
4.4 KiB
C

/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "slave_light_sleep.h"
#include "esp_log.h"
#include "esp_check.h"
#include "sdkconfig.h"
#ifdef CONFIG_ESP_HOSTED_LIGHT_SLEEP_ENABLE
#include "esp_pm.h"
static const char TAG[] = "slave_light_sleep";
/* PM lock handle for controlling light sleep */
static esp_pm_lock_handle_t pm_lock = NULL;
/* Track whether PM lock is currently acquired (light sleep disabled) */
static bool pm_lock_acquired = false;
/* Track initialization state */
static bool pm_configured = false;
esp_err_t slave_light_sleep_init(void)
{
if (pm_configured) {
ESP_LOGW(TAG, "Light sleep already initialized");
return ESP_OK;
}
ESP_LOGI(TAG, "Initializing light sleep power management");
/* Create PM lock to control when light sleep is allowed */
esp_err_t ret = esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "slave_pm_lock", &pm_lock);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to create PM lock: %s", esp_err_to_name(ret));
return ret;
}
/* Configure PM with automatic light sleep enabled */
esp_pm_config_t pm_config = {
.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ,
.min_freq_mhz = CONFIG_ESP_HOSTED_LIGHT_SLEEP_MIN_FREQ_MHZ,
.light_sleep_enable = true
};
ret = esp_pm_configure(&pm_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure PM: %s", esp_err_to_name(ret));
esp_pm_lock_delete(pm_lock);
pm_lock = NULL;
return ret;
}
ESP_LOGI(TAG, "PM configured: max=%d MHz, min=%d MHz, light_sleep=enabled",
pm_config.max_freq_mhz, pm_config.min_freq_mhz);
#if defined(CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP) && defined(CONFIG_ESP_HOSTED_LIGHT_SLEEP_PERIPHERAL_POWERDOWN)
ESP_LOGI(TAG, " Peripheral powerdown: ENABLED (UART console disabled in sleep)");
#else
ESP_LOGI(TAG, " Peripheral powerdown: DISABLED (UART console available)");
#endif
pm_configured = true;
/* Start with light sleep disabled (acquire lock) for safe initialization */
ret = slave_light_sleep_stop();
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to acquire initial PM lock, but continuing");
}
ESP_LOGI(TAG, "Light sleep initialized successfully");
return ESP_OK;
}
esp_err_t slave_light_sleep_start(void)
{
if (!pm_configured || !pm_lock) {
ESP_LOGE(TAG, "Light sleep not initialized, call slave_light_sleep_init() first");
return ESP_ERR_INVALID_STATE;
}
/* If lock already released, nothing to do */
if (!pm_lock_acquired) {
ESP_LOGD(TAG, "Light sleep already enabled (PM lock already released)");
return ESP_OK;
}
/* Release PM lock to allow light sleep */
esp_err_t ret = esp_pm_lock_release(pm_lock);
if (ret == ESP_OK) {
pm_lock_acquired = false;
ESP_EARLY_LOGI(TAG, "Light sleep ENABLED (PM lock released, CPU can scale down)");
ESP_EARLY_LOGI(TAG, "System will enter light sleep when idle");
} else {
ESP_EARLY_LOGE(TAG, "Failed to release PM lock: %s", esp_err_to_name(ret));
}
return ret;
}
esp_err_t slave_light_sleep_stop(void)
{
if (!pm_configured || !pm_lock) {
ESP_LOGE(TAG, "Light sleep not initialized");
return ESP_ERR_INVALID_STATE;
}
/* If lock already acquired, nothing to do */
if (pm_lock_acquired) {
ESP_LOGD(TAG, "Light sleep already disabled (PM lock already acquired)");
return ESP_OK;
}
/* Acquire PM lock to prevent light sleep */
esp_err_t ret = esp_pm_lock_acquire(pm_lock);
if (ret == ESP_OK) {
pm_lock_acquired = true;
ESP_LOGI(TAG, "Light sleep DISABLED (PM lock acquired, CPU at max freq)");
} else {
ESP_LOGE(TAG, "Failed to acquire PM lock: %s", esp_err_to_name(ret));
}
return ret;
}
esp_err_t slave_light_sleep_is_configured(void)
{
return pm_configured ? ESP_OK : ESP_FAIL;
}
esp_err_t slave_light_sleep_deinit(void)
{
if (!pm_configured) {
ESP_LOGD(TAG, "Light sleep not initialized, nothing to deinit");
return ESP_OK;
}
ESP_LOGI(TAG, "Deinitializing light sleep");
/* Ensure lock is acquired before deleting to avoid issues */
if (!pm_lock_acquired && pm_lock) {
esp_err_t ret = esp_pm_lock_acquire(pm_lock);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to acquire lock before delete: %s", esp_err_to_name(ret));
} else {
pm_lock_acquired = true;
}
}
/* Delete PM lock */
if (pm_lock) {
esp_pm_lock_delete(pm_lock);
pm_lock = NULL;
}
pm_lock_acquired = false;
pm_configured = false;
ESP_LOGI(TAG, "Light sleep deinitialized");
return ESP_OK;
}
#endif /* CONFIG_ESP_HOSTED_LIGHT_SLEEP_ENABLE */