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>
390 lines
11 KiB
C
390 lines
11 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "stats.h"
|
|
#include <unistd.h>
|
|
#include "esp_idf_version.h"
|
|
#include "esp_log.h"
|
|
#include "esp_hosted_transport_init.h"
|
|
#include "esp_hosted_header.h"
|
|
|
|
#if TEST_RAW_TP || ESP_PKT_STATS || defined(CONFIG_ESP_HOSTED_LOG_RUNTIME_FREERTOS_STATS)
|
|
static const char TAG[] = "stats";
|
|
#endif /* TEST_RAW_TP || ESP_PKT_STATS || defined(CONFIG_ESP_HOSTED_LOG_RUNTIME_FREERTOS_STATS) || ESP_PKT_NUM_DEBUG */
|
|
|
|
#if ESP_PKT_NUM_DEBUG
|
|
struct dbg_stats_t dbg_stats;
|
|
#endif /* ESP_PKT_NUM_DEBUG */
|
|
|
|
#if TEST_RAW_TP
|
|
#define ESP_PKT_STATS_REPORT_INTERVAL CONFIG_ESP_RAW_TP_REPORT_INTERVAL
|
|
#elif ESP_PKT_STATS
|
|
#define ESP_PKT_STATS_REPORT_INTERVAL CONFIG_ESP_PKT_STATS_INTERVAL_SEC
|
|
#endif
|
|
|
|
#if ESP_PKT_STATS
|
|
struct pkt_stats_t pkt_stats;
|
|
#endif /* ESP_PKT_STATS */
|
|
|
|
#ifdef ESP_FUNCTION_PROFILING
|
|
/* Define the global variables */
|
|
struct timing_stats_entry timing_entries[CONFIG_ESP_HOSTED_FUNCTION_PROFILING_MAX_ENTRIES] = {0};
|
|
int num_timing_entries = 0;
|
|
|
|
/* Function to register new timing stats */
|
|
struct timing_stats* register_prof_stats(const char *func_name)
|
|
{
|
|
if (num_timing_entries >= CONFIG_ESP_HOSTED_FUNCTION_PROFILING_MAX_ENTRIES) {
|
|
ESP_LOGE(TAG, "Max timing stats reached");
|
|
return NULL;
|
|
}
|
|
|
|
/* Check if already registered */
|
|
for (int i = 0; i < num_timing_entries; i++) {
|
|
if (strcmp(timing_entries[i].name, func_name) == 0) {
|
|
return &timing_entries[i].stats;
|
|
}
|
|
}
|
|
|
|
/* Add new entry */
|
|
timing_entries[num_timing_entries].name = func_name;
|
|
timing_entries[num_timing_entries].active = true;
|
|
num_timing_entries++;
|
|
|
|
return &timing_entries[num_timing_entries-1].stats;
|
|
}
|
|
|
|
/* Function to get timing measure for a stats entry */
|
|
struct timing_measure* get_prof_data(struct timing_stats *s)
|
|
{
|
|
for (int i = 0; i < num_timing_entries; i++) {
|
|
if (&timing_entries[i].stats == s) {
|
|
return &timing_entries[i].measure;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Print timing stats function */
|
|
static void print_timing_stats(struct timing_measure *t, struct timing_stats *s, const char *name)
|
|
{
|
|
if (!t || !s || !name) {
|
|
ESP_LOGE(TAG, "Invalid arguments");
|
|
return;
|
|
}
|
|
|
|
s->avg_time = t->total_time / t->count;
|
|
|
|
ESP_LOGI(TAG, "[%s] Timing Stats - Count: %" PRIu32 ", Min: %" PRIu32 " us, Max: %" PRIu32 " us, Avg: %" PRIu32 " us",
|
|
name,
|
|
t->count,
|
|
s->min_time,
|
|
s->max_time,
|
|
s->avg_time);
|
|
}
|
|
#endif /* ESP_FUNCTION_PROFILING */
|
|
|
|
#if defined(CONFIG_ESP_HOSTED_LOG_RUNTIME_FREERTOS_STATS)
|
|
/* These functions are only for debugging purpose
|
|
* Please do not enable in production environments
|
|
*/
|
|
static esp_err_t log_real_time_stats(TickType_t xTicksToWait)
|
|
{
|
|
TaskStatus_t *start_array = NULL, *end_array = NULL;
|
|
UBaseType_t start_array_size, end_array_size;
|
|
uint32_t start_run_time, end_run_time;
|
|
esp_err_t ret;
|
|
|
|
/* Allocate array to store current task states */
|
|
start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
|
|
start_array = malloc(sizeof(TaskStatus_t) * start_array_size);
|
|
if (start_array == NULL) {
|
|
ret = ESP_ERR_NO_MEM;
|
|
goto exit;
|
|
}
|
|
|
|
start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time);
|
|
if (start_array_size == 0) {
|
|
ret = ESP_ERR_INVALID_SIZE;
|
|
goto exit;
|
|
}
|
|
|
|
vTaskDelay(xTicksToWait);
|
|
|
|
/* Allocate array to store tasks states post delay */
|
|
end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
|
|
end_array = malloc(sizeof(TaskStatus_t) * end_array_size);
|
|
if (end_array == NULL) {
|
|
ret = ESP_ERR_NO_MEM;
|
|
goto exit;
|
|
}
|
|
|
|
end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time);
|
|
if (end_array_size == 0) {
|
|
ret = ESP_ERR_INVALID_SIZE;
|
|
goto exit;
|
|
}
|
|
|
|
uint32_t total_elapsed_time = (end_run_time - start_run_time);
|
|
if (total_elapsed_time == 0) {
|
|
ret = ESP_ERR_INVALID_STATE;
|
|
goto exit;
|
|
}
|
|
|
|
// Updated Header with Priority and Stack High Water Mark
|
|
ESP_LOGI(TAG, "%-16s | %-10s | %-4s | %-4s | %-10s", "Task", "Run Time", "CPU%", "Prio", "Stack HWM");
|
|
ESP_LOGI(TAG, "-----------------------------------------------------------------------");
|
|
|
|
for (int i = 0; i < start_array_size; i++) {
|
|
int k = -1;
|
|
for (int j = 0; j < end_array_size; j++) {
|
|
if (start_array[i].xHandle == end_array[j].xHandle) {
|
|
k = j;
|
|
start_array[i].xHandle = NULL;
|
|
end_array[j].xHandle = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (k >= 0) {
|
|
uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter;
|
|
uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * portNUM_PROCESSORS);
|
|
|
|
// Log with new metrics: Priority and Stack High Water Mark (HWM)
|
|
ESP_LOGI(TAG, "%-16s | %-10" PRIu32 " | %3" PRIu32 "%% | %4u | %10u",
|
|
end_array[k].pcTaskName,
|
|
task_elapsed_time,
|
|
percentage_time,
|
|
(unsigned int)end_array[k].uxCurrentPriority,
|
|
(unsigned int)end_array[k].usStackHighWaterMark);
|
|
}
|
|
}
|
|
|
|
/* Print unmatched tasks */
|
|
for (int i = 0; i < start_array_size; i++) {
|
|
if (start_array[i].xHandle != NULL) {
|
|
ESP_LOGI(TAG, "| %-16s | Deleted", start_array[i].pcTaskName);
|
|
}
|
|
}
|
|
for (int i = 0; i < end_array_size; i++) {
|
|
if (end_array[i].xHandle != NULL) {
|
|
ESP_LOGI(TAG, "| %-16s | Created", end_array[i].pcTaskName);
|
|
}
|
|
}
|
|
ret = ESP_OK;
|
|
|
|
exit:
|
|
|
|
if (start_array)
|
|
free(start_array);
|
|
|
|
if (end_array)
|
|
free(end_array);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void print_mem_stats()
|
|
{
|
|
uint32_t freeSize = esp_get_free_heap_size();
|
|
printf("The available total size of heap:%" PRIu32 "\n", freeSize);
|
|
|
|
printf("\tDescription\tInternal\tSPIRAM\n");
|
|
printf("Current Free Memory\t%d\t\t%d\n",
|
|
heap_caps_get_free_size(MALLOC_CAP_8BIT) - heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
|
|
heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
|
printf("Largest Free Block\t%d\t\t%d\n",
|
|
heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL),
|
|
heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM));
|
|
printf("Min. Ever Free Size\t%d\t\t%d\n",
|
|
heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL),
|
|
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
|
|
}
|
|
|
|
static void log_runtime_stats_task(void* pvParameters)
|
|
{
|
|
while (1) {
|
|
ESP_LOGI(TAG, "\n\nGetting real time stats over %d ticks\n", (int)STATS_TICKS);
|
|
if (log_real_time_stats(STATS_TICKS) == ESP_OK) {
|
|
ESP_LOGI(TAG, "Real time stats obtained\n");
|
|
} else {
|
|
ESP_LOGE(TAG, "Error getting real time stats\n");
|
|
}
|
|
print_mem_stats();
|
|
vTaskDelay(STATS_TICKS);
|
|
}
|
|
}
|
|
#endif /* CONFIG_ESP_HOSTED_LOG_RUNTIME_FREERTOS_STATS */
|
|
|
|
#if TEST_RAW_TP
|
|
uint64_t test_raw_tp_rx_len;
|
|
uint64_t test_raw_tp_tx_len;
|
|
|
|
void debug_update_raw_tp_rx_count(uint16_t len)
|
|
{
|
|
test_raw_tp_rx_len += len;
|
|
}
|
|
|
|
/* static buffer to hold tx data during test */
|
|
DMA_ATTR static uint8_t tx_buf[TEST_RAW_TP__BUF_SIZE];
|
|
|
|
extern volatile uint8_t datapath;
|
|
static void raw_tp_tx_task(void* pvParameters)
|
|
{
|
|
int ret;
|
|
interface_buffer_handle_t buf_handle = {0};
|
|
uint8_t *raw_tp_tx_buf = NULL;
|
|
uint32_t *ptr = NULL;
|
|
uint16_t i = 0;
|
|
|
|
sleep(5);
|
|
|
|
/* initialise the static buffer */
|
|
raw_tp_tx_buf = tx_buf;
|
|
ptr = (uint32_t*)raw_tp_tx_buf;
|
|
/* initialise the tx buffer */
|
|
for (i=0; i<(TEST_RAW_TP__BUF_SIZE/4-1); i++, ptr++)
|
|
*ptr = 0xdeadbeef;
|
|
|
|
for (;;) {
|
|
|
|
if (!datapath) {
|
|
sleep(1);
|
|
continue;
|
|
}
|
|
|
|
buf_handle.if_type = ESP_TEST_IF;
|
|
buf_handle.if_num = 0;
|
|
|
|
buf_handle.payload = raw_tp_tx_buf;
|
|
buf_handle.payload_len = TEST_RAW_TP__BUF_SIZE;
|
|
/* free the buffer after it has been sent */
|
|
buf_handle.free_buf_handle = NULL;
|
|
buf_handle.priv_buffer_handle = buf_handle.payload;
|
|
|
|
ret = send_to_host_queue(&buf_handle, PRIO_Q_OTHERS);
|
|
|
|
if (ret) {
|
|
ESP_LOGE(TAG,"Failed to send to queue\n");
|
|
continue;
|
|
}
|
|
test_raw_tp_tx_len += (TEST_RAW_TP__BUF_SIZE);
|
|
}
|
|
}
|
|
#endif /* TEST_RAW_TP */
|
|
|
|
#if TEST_RAW_TP || ESP_PKT_STATS
|
|
|
|
static void stats_timer_func(void* arg)
|
|
{
|
|
/* Rest of existing stats_timer_func code */
|
|
#if TEST_RAW_TP
|
|
static int32_t cur = 0;
|
|
double actual_bandwidth_rx = 0;
|
|
double actual_bandwidth_tx = 0;
|
|
int32_t div = 1024;
|
|
|
|
actual_bandwidth_tx = (test_raw_tp_tx_len*8)/TEST_RAW_TP__TIMEOUT;
|
|
actual_bandwidth_rx = (test_raw_tp_rx_len*8)/TEST_RAW_TP__TIMEOUT;
|
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
ESP_LOGI(TAG,"%lu-%lu sec Rx: %.2f Tx: %.2f kbps", cur, cur + TEST_RAW_TP__TIMEOUT, actual_bandwidth_rx/div, actual_bandwidth_tx/div);
|
|
#else
|
|
ESP_LOGI(TAG,"%u-%u sec Rx: %.2f Tx: %.2f kbps", cur, cur + TEST_RAW_TP__TIMEOUT, actual_bandwidth_rx/div, actual_bandwidth_tx/div);
|
|
#endif
|
|
cur += TEST_RAW_TP__TIMEOUT;
|
|
test_raw_tp_rx_len = test_raw_tp_tx_len = 0;
|
|
#endif
|
|
#if ESP_PKT_STATS
|
|
ESP_LOGI(TAG, "STA: flw_ctrl(on[%lu] off[%lu]) H2S(in[%lu] out[%lu] fail[%lu]) S2H(in[%lu] out[%lu]) Ctrl: (in[%lu] rsp[%lu] evt[%lu])",
|
|
pkt_stats.sta_flowctrl_on, pkt_stats.sta_flowctrl_off,
|
|
pkt_stats.hs_bus_sta_in,pkt_stats.hs_bus_sta_out, pkt_stats.hs_bus_sta_fail,
|
|
pkt_stats.sta_sh_in,pkt_stats.sta_sh_out,
|
|
pkt_stats.serial_rx, pkt_stats.serial_tx_total, pkt_stats.serial_tx_evt);
|
|
ESP_LOGI(TAG, "Lwip: in[%lu] slave_out[%lu] host_out[%lu] both_out[%lu]",
|
|
pkt_stats.sta_lwip_in, pkt_stats.sta_slave_lwip_out,
|
|
pkt_stats.sta_host_lwip_out, pkt_stats.sta_both_lwip_out);
|
|
|
|
#ifdef ESP_FUNCTION_PROFILING
|
|
/* Print timing stats for all active entries */
|
|
for (int i = 0; i < num_timing_entries; i++) {
|
|
if (!timing_entries[i].active || !timing_entries[i].measure.count) {
|
|
continue;
|
|
}
|
|
|
|
struct timing_measure *t = &timing_entries[i].measure;
|
|
struct timing_stats *s = &timing_entries[i].stats;
|
|
|
|
/* Calculate rate and print stats in one pass */
|
|
uint32_t rate = (uint32_t)(t->count / ESP_PKT_STATS_REPORT_INTERVAL);
|
|
s->avg_time = t->total_time / t->count;
|
|
|
|
ESP_LOGI(TAG, "[%s] Stats - Count: %" PRIu32 ", Min: %" PRIu32 ", Max: %" PRIu32 ", Avg: %" PRIu32 " us, Rate: %" PRIu32 "/s",
|
|
timing_entries[i].name,
|
|
t->count,
|
|
s->min_time,
|
|
s->max_time,
|
|
s->avg_time,
|
|
rate);
|
|
}
|
|
#endif /* ESP_FUNCTION_PROFILING */
|
|
#endif /* ESP_PKT_STATS */
|
|
|
|
}
|
|
|
|
static void start_timer_to_display_stats(int periodic_time_sec)
|
|
{
|
|
test_args_t args = {0};
|
|
esp_timer_handle_t raw_tp_timer = {0};
|
|
esp_timer_create_args_t create_args = {
|
|
.callback = &stats_timer_func,
|
|
.arg = &args,
|
|
.name = "raw_tp_timer",
|
|
};
|
|
|
|
ESP_ERROR_CHECK(esp_timer_create(&create_args, &raw_tp_timer));
|
|
|
|
args.timer = raw_tp_timer;
|
|
|
|
ESP_ERROR_CHECK(esp_timer_start_periodic(raw_tp_timer, SEC_TO_USEC(periodic_time_sec)));
|
|
}
|
|
#endif /* TEST_RAW_TP || ESP_PKT_STATS */
|
|
|
|
|
|
#if TEST_RAW_TP
|
|
void process_test_capabilities(uint8_t capabilities)
|
|
{
|
|
ESP_LOGD(TAG, "capabilities: %d", capabilities);
|
|
if ((capabilities & ESP_TEST_RAW_TP__ESP_TO_HOST) ||
|
|
(capabilities & ESP_TEST_RAW_TP__BIDIRECTIONAL)) {
|
|
assert(xTaskCreate(raw_tp_tx_task , "raw_tp_tx_task",
|
|
CONFIG_ESP_HOSTED_DEFAULT_TASK_STACK_SIZE, NULL ,
|
|
CONFIG_ESP_HOSTED_DEFAULT_TASK_PRIORITY, NULL) == pdTRUE);
|
|
}
|
|
}
|
|
#endif /* TEST_RAW_TP */
|
|
|
|
void create_debugging_tasks(void)
|
|
{
|
|
#if defined(CONFIG_ESP_HOSTED_LOG_RUNTIME_FREERTOS_STATS)
|
|
assert(xTaskCreate(log_runtime_stats_task, "log_runtime_stats_task",
|
|
CONFIG_ESP_HOSTED_DEFAULT_TASK_STACK_SIZE, NULL,
|
|
/*CONFIG_ESP_HOSTED_DEFAULT_TASK_PRIORITY*/ 1, NULL) == pdTRUE);
|
|
#endif /* CONFIG_ESP_HOSTED_LOG_RUNTIME_FREERTOS_STATS */
|
|
|
|
#if TEST_RAW_TP || ESP_PKT_STATS
|
|
start_timer_to_display_stats(ESP_PKT_STATS_REPORT_INTERVAL);
|
|
#endif
|
|
}
|
|
|
|
uint8_t debug_get_raw_tp_conf(void) {
|
|
uint8_t raw_tp_cap = 0;
|
|
#if TEST_RAW_TP
|
|
raw_tp_cap |= ESP_TEST_RAW_TP;
|
|
ESP_LOGI(TAG, "\n\n***** Slave: Raw Throughput testing (Report per %u sec)*****\n", CONFIG_ESP_RAW_TP_REPORT_INTERVAL);
|
|
#endif
|
|
return raw_tp_cap;
|
|
}
|