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>
169 lines
4.4 KiB
C
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 */
|
|
|