Files
desklock/firmware/components/esp_hosted/common/utils/esp_hosted_cli.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

508 lines
13 KiB
C

/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_hosted_cli.h"
#include "sdkconfig.h"
#include "esp_idf_version.h"
#include "esp_console.h"
#ifdef CONFIG_ESP_HOSTED_ENABLED
#include "port_esp_hosted_host_config.h"
#include "esp_hosted_power_save.h"
#endif
#ifdef H_ESP_HOSTED_CLI_ENABLED
#include <stdio.h>
#include <string.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_heap_caps.h>
#ifdef CONFIG_HEAP_TRACING
#include <esp_heap_trace.h>
#endif
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_wifi.h>
#include "lwip/sockets.h"
/* This should be agnostic, later */
#ifdef CONFIG_ESP_HOSTED_COPROCESSOR
#include "iperf.h"
#include "wifi_cmd.h"
#include "iperf_cmd.h"
#include "ping_cmd.h"
#include "esp_attr.h"
#define H_IRAM_ATTR IRAM_ATTR
#else
#define H_IRAM_ATTR
#endif
static const char *TAG = "esp_cli";
static int task_dump_cli_handler(int argc, char *argv[])
{
/* Just to go to the next line */
printf("\n");
#ifndef CONFIG_FREERTOS_USE_TRACE_FACILITY
printf("%s: To use this utility enable: Component config > FreeRTOS > Kernel > configUSE_TRACEFACILITY\n", TAG);
#else
int num_of_tasks = uxTaskGetNumberOfTasks();
TaskStatus_t *task_array = calloc(1, num_of_tasks * sizeof(TaskStatus_t));
if (!task_array) {
ESP_LOGE(TAG, "Memory not allocated for task list.");
return 0;
}
num_of_tasks = uxTaskGetSystemState(task_array, num_of_tasks, NULL);
printf("\tName\tNumber\tPriority\tStackWaterMark\n");
for (int i = 0; i < num_of_tasks; i++) {
printf("%16s\t%u\t%u\t%u\n",
task_array[i].pcTaskName,
(unsigned) task_array[i].xTaskNumber,
(unsigned) task_array[i].uxCurrentPriority,
(unsigned) task_array[i].usStackHighWaterMark);
}
free(task_array);
#endif
return 0;
}
static int cpu_dump_cli_handler(int argc, char *argv[])
{
/* Just to go to the next line */
printf("\n");
#ifndef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
printf("%s: To use this utility enable: Component config > FreeRTOS > Kernel > configGENERATE_RUN_TIME_STATS\n", TAG);
#else
char *buf = calloc(1, 2 * 1024);
vTaskGetRunTimeStats(buf);
printf("%s: Run Time Stats:\n%s\n", TAG, buf);
free(buf);
#endif
return 0;
}
static void print_heap_stats(void)
{
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 int mem_dump_cli_handler(int argc, char *argv[])
{
/* Just to go to the next line */
printf("\n");
print_heap_stats();
return 0;
}
#ifdef CONFIG_HEAP_TRACING
static int heap_trace_records;
static heap_trace_record_t *heap_trace_records_buf;
static void cli_heap_trace_start(void)
{
/* Just to go to the next line */
printf("\n");
if (!heap_trace_records_buf) {
heap_trace_records_buf = malloc(heap_trace_records * sizeof(heap_trace_record_t));
if (!heap_trace_records_buf) {
printf("%s: Failed to allocate records buffer\n", TAG);
return;
}
if (heap_trace_init_standalone(heap_trace_records_buf, heap_trace_records) != ESP_OK) {
printf("%s: Failed to initialise tracing\n", TAG);
goto error1;
}
}
if (heap_trace_start(HEAP_TRACE_LEAKS) != ESP_OK) {
printf("%s: Failed to start heap trace\n", TAG);
goto error2;
}
return;
error2:
heap_trace_init_standalone(NULL, 0);
error1:
free(heap_trace_records_buf);
heap_trace_records_buf = NULL;
return;
}
static void cli_heap_trace_stop(void)
{
/* Just to go to the next line */
printf("\n");
if (!heap_trace_records_buf) {
printf("%s: Tracing not started?\n", TAG);
return;
}
heap_trace_stop();
heap_trace_dump();
heap_trace_init_standalone(NULL, 0);
free(heap_trace_records_buf);
heap_trace_records_buf = NULL;
}
#endif
static int heap_trace_cli_handler(int argc, char *argv[])
{
/* Just to go to the next line */
printf("\n");
#ifndef CONFIG_HEAP_TRACING
printf("%s: To use this utility enable: Component config --> Heap memory debugging --> Enable heap tracing\n", TAG);
#else
if (argc < 2) {
printf("%s: Incorrect arguments\n", TAG);
return 0;
}
if (strcmp(argv[1], "start") == 0) {
#define DEFAULT_HEAP_TRACE_RECORDS 200
if (argc != 3) {
heap_trace_records = DEFAULT_HEAP_TRACE_RECORDS;
} else {
heap_trace_records = atoi(argv[2]);
}
printf("%s: Using a buffer to trace %d records\n", TAG, heap_trace_records);
cli_heap_trace_start();
} else if (strcmp(argv[1], "stop") == 0) {
cli_heap_trace_stop();
} else {
printf("%s: Invalid argument:%s:\n", TAG, argv[1]);
}
#endif
return 0;
}
static int reboot_cli_handler(int argc, char *argv[])
{
/* Just to go to the next line */
printf("\n");
esp_unregister_shutdown_handler((shutdown_handler_t)esp_wifi_stop);
esp_restart();
return 0;
}
static int crash_device_handler(int argc, char** argv)
{
printf("Crashing the device now...\n");
// Writing at invalid address
*(int *) (0x0) = 0;
return ESP_OK;
}
static int sock_dump_cli_handler(int argc, char *argv[])
{
printf("\n");
int i, ret, used_sockets = 0;
struct sockaddr_in local_sock, peer_sock;
socklen_t local_sock_len = sizeof(struct sockaddr_in), peer_sock_len = sizeof(struct sockaddr_in);
char local_ip_addr[16], peer_ip_addr[16];
unsigned int local_port, peer_port;
int sock_type;
socklen_t sock_type_len;
#define TOTAL_NUM_SOCKETS MEMP_NUM_NETCONN
printf("sock_fd\tprotocol\tlocal_addr\t\tpeer_addr\n");
for (i = LWIP_SOCKET_OFFSET; i < LWIP_SOCKET_OFFSET + TOTAL_NUM_SOCKETS; i++) {
memset(&local_sock, 0, sizeof(struct sockaddr_in));
memset(&peer_sock, 0, sizeof(struct sockaddr_in));
local_sock_len = sizeof(struct sockaddr);
peer_sock_len = sizeof(struct sockaddr);
memset(local_ip_addr, 0, sizeof(local_ip_addr));
memset(peer_ip_addr, 0, sizeof(peer_ip_addr));
local_port = 0;
peer_port = 0;
sock_type = 0;
sock_type_len = sizeof(int);
ret = getsockname(i, (struct sockaddr *)&local_sock, &local_sock_len);
if (ret >= 0) {
used_sockets++;
inet_ntop(AF_INET, &local_sock.sin_addr, local_ip_addr, sizeof(local_ip_addr));
local_port = ntohs(local_sock.sin_port);
getsockopt(i, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len);
printf("%d\t%d:%s\t%16s:%d", i, sock_type, sock_type == SOCK_STREAM ? "tcp" : sock_type == SOCK_DGRAM ? "udp" : "raw", local_ip_addr, local_port);
ret = getpeername(i, (struct sockaddr *)&peer_sock, &peer_sock_len);
if (ret >= 0) {
inet_ntop(AF_INET, &peer_sock.sin_addr, peer_ip_addr, sizeof(peer_ip_addr));
peer_port = ntohs(peer_sock.sin_port);
printf("\t%16s:%d", peer_ip_addr, peer_port);
}
printf("\n");
}
}
printf("Remaining sockets: %d\n", TOTAL_NUM_SOCKETS - used_sockets);
return 0;
}
#if defined(H_HOST_PS_ALLOWED)
#ifdef H_ESP_HOSTED_HOST
static int power_save_cli_handler(int argc, char *argv[])
{
ESP_LOGI(TAG, "Putting ESP32-P4 into power save...");
esp_hosted_power_save_start(HOSTED_POWER_SAVE_TYPE_DEEP_SLEEP);
return 0;
}
#endif
#ifdef CONFIG_ESP_HOSTED_COPROCESSOR
static int wakeup_cli_handler(int argc, char *argv[])
{
ESP_LOGI(TAG, "wake-up host... (ignored if host is NOT sleeping)");
wakeup_host(portMAX_DELAY);
return 0;
}
static int reset_host_cli_handler(int argc, char *argv[])
{
ESP_LOGI(TAG, "Resetting host...");
wakeup_host_mandate(portMAX_DELAY);
return 0;
}
#endif
#endif
static esp_console_cmd_t diag_cmds[] = {
{
.command = "crash",
.help = "Crash the device writing at invalid address",
.func = &crash_device_handler,
},
{
.command = "reboot",
.help = "Reboot the device",
.func = reboot_cli_handler,
},
{
.command = "mem-dump",
.help = "Prints memory stats",
.func = mem_dump_cli_handler,
},
{
.command = "task-dump",
.help = "Print task snapshots",
.func = task_dump_cli_handler,
},
{
.command = "cpu-dump",
.help = "Print CPU consumption data at the moment",
.func = cpu_dump_cli_handler,
},
{
.command = "heap-trace",
.help = "<start|stop> [trace-buf-size]",
.func = heap_trace_cli_handler,
},
{
.command = "sock-dump",
.help = "",
.func = sock_dump_cli_handler,
},
#if defined(H_HOST_PS_ALLOWED)
#ifdef H_ESP_HOSTED_HOST
{
.command = "host-power-save",
.help = "Put Host into power save",
.func = power_save_cli_handler,
},
#endif
#ifdef CONFIG_ESP_HOSTED_COPROCESSOR
{
.command = "wake-up-host",
.help = "Wake-up host from power save",
.func = wakeup_cli_handler,
},
{
.command = "reset-host",
.help = "Reset host",
.func = reset_host_cli_handler,
},
#endif
#endif
};
#ifdef CONFIG_ESP_HOSTED_COPROCESSOR
#if !defined(H_HOST_PS_ALLOWED)
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS || CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
#include "esp_wifi_he.h"
#endif
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS
extern int wifi_cmd_get_tx_statistics(int argc, char **argv);
extern int wifi_cmd_clr_tx_statistics(int argc, char **argv);
#endif
#if CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
extern int wifi_cmd_get_rx_statistics(int argc, char **argv);
extern int wifi_cmd_clr_rx_statistics(int argc, char **argv);
#endif
static void hosted_iperf_hook_show_wifi_stats(iperf_traffic_type_t type, iperf_status_t status)
{
if (status == IPERF_STARTED) {
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS
if (type != IPERF_UDP_SERVER) {
wifi_cmd_clr_tx_statistics(0, NULL);
}
#endif
#if CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
if (type != IPERF_UDP_CLIENT) {
wifi_cmd_clr_rx_statistics(0, NULL);
}
#endif
}
if (status == IPERF_STOPPED) {
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS
if (type != IPERF_UDP_SERVER) {
wifi_cmd_get_tx_statistics(0, NULL);
}
#endif
#if CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
if (type != IPERF_UDP_CLIENT) {
wifi_cmd_get_rx_statistics(0, NULL);
}
#endif
}
}
#endif
#endif
#if 0
/* auto deregistered as part of esp_console_stop_repl(repl); */
static esp_err_t esp_hosted_cli_deregister_cmds(void)
{
int ret = 0;
int cmds_num = sizeof(diag_cmds) / sizeof(esp_console_cmd_t);
int i;
for (i = 0; i < cmds_num; i++) {
ESP_LOGI(TAG, "Deregistering command: %s", diag_cmds[i].command);
ret = esp_console_cmd_deregister(diag_cmds[i].command);
if (ret != ESP_OK && ret != ESP_ERR_INVALID_ARG) {
ESP_LOGW(TAG, "Failed to deregister command %s: %s",
diag_cmds[i].command, esp_err_to_name(ret));
}
}
return ret;
}
#endif
static int esp_hosted_cli_register_cmds(void)
{
int cmds_num = sizeof(diag_cmds) / sizeof(esp_console_cmd_t);
int i;
for (i = 0; i < cmds_num; i++) {
ESP_LOGI(TAG, "Registering command: %s", diag_cmds[i].command);
esp_console_cmd_register(&diag_cmds[i]);
}
#if !defined(H_HOST_PS_ALLOWED)
/* NOTE: As we do not have corresponding deregister cmds
* we will not register these for host wake ups, as doing so,
* memory leak would happen for these commands
*/
app_register_all_wifi_commands();
app_register_iperf_commands();
ping_cmd_register_ping();
app_register_iperf_hook_func(hosted_iperf_hook_show_wifi_stats);
#endif
return 0;
}
#ifdef CONFIG_ESP_HOSTED_CLI_NEW_INSTANCE
static esp_console_repl_t *repl = NULL;
int esp_hosted_cli_start(void)
{
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
#ifdef CONFIG_ESP_HOSTED_COPROCESSOR
repl_config.prompt = "coprocessor> ";
#elif defined(H_ESP_HOSTED_HOST)
repl_config.prompt = "host> ";
#endif
esp_hosted_cli_register_cmds();
if (!repl) {
#if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM)
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
#elif defined(CONFIG_ESP_CONSOLE_USB_CDC)
esp_console_dev_usb_cdc_config_t hw_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&hw_config, &repl_config, &repl));
#elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG)
esp_console_dev_usb_serial_jtag_config_t hw_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl));
#else
#error Unsupported console type
#endif
}
ESP_ERROR_CHECK(esp_console_start_repl(repl));
return 0;
}
void H_IRAM_ATTR esp_hosted_cli_stop(void)
{
if (repl) {
/* Stop the REPL first */
esp_console_stop_repl(repl);
/* Clear the handle */
repl = NULL;
print_heap_stats();
}
}
#else
int esp_hosted_cli_start(void)
{
return esp_hosted_cli_register_cmds();
}
void esp_hosted_cli_stop(void)
{
/* Old CLI implementation - no stop needed */
}
#endif
#endif /*ESP_HOSTED_CLI_ENABLED*/