Files
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

116 lines
2.8 KiB
Python

#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# check that RPC calls in protobuf and documentation files match
# exit with 0 if ok
# exit with 1 if fail
import os
import re
import sys
# method:
# 1. get the set of RPC calls from both the protobuf and documentation files
# 2. compare the sets
# 3. if there are differences, print the differences
protobuf_file = "common/proto/esp_hosted_rpc.proto"
doc_file = "docs/implemented_rpcs.md"
def get_set_from_document() -> set:
set_rpcs = set()
# read the protobuf file
try:
file_info = open(doc_file, "r")
except:
print("Document file open error")
return set_of_rpcs
# from this pattern in the doc_file:
# | 1 | 257 | GetMacAddress | 0.0.6 |
# it will extract GetMacAddress
# this pattern works for the list of RCP Requests and Events
pattern = re.compile(r"\|\s*\d+\s*\|\s*\d+\s*\|\s*(\S+)")
for line in file_info:
# get the list of rpc calls
rpc_call = pattern.search(line)
if rpc_call:
# print(rpc_call.group(1))
set_rpcs.add(rpc_call.group(1))
file_info.close()
# print(set_rpcs)
return set_rpcs
def get_set_from_protobuf() -> set:
set_rpcs = set()
# read the protobuf file
try:
file_info = open(protobuf_file, "r")
except:
print("Protobuf file open error")
return set_of_rpcs
# extract requests
# from this pattern in the protobuf_file
# Rpc_Req_GetMacAddress req_get_mac_address = 257;
# it will extract GetMacAddress
pattern_req = re.compile(r"\s+(Rpc_Req_)(\S+)\s+req_")
# extract events
# from this pattern in the protobuf_file
# Rpc_Event_ESPInit event_esp_init = 769;
# it will extract ESPInit
pattern_event = re.compile(r"\s+(Rpc_Event_)(\S+)\s+event_")
for line in file_info:
# get the list of rpc calls
rpc_call = pattern_req.search(line)
if rpc_call:
set_rpcs.add(rpc_call.group(2))
rpc_call = pattern_event.search(line)
if rpc_call:
set_rpcs.add(rpc_call.group(2))
file_info.close()
# print(set_rpcs)
return set_rpcs
def check() -> int:
ret = 1
# get the set of RPC calls in the protobuf file
protobuf_set = get_set_from_protobuf()
# get the set of RPC calls in the documentation file
document_set = get_set_from_document()
# compare the two
# if not equal
if (protobuf_set == document_set):
ret = 0
else:
# get the differences
only_in_docs = document_set.difference(protobuf_set)
only_in_protobuf = protobuf_set.difference(document_set)
if only_in_docs:
print("Error: RPCs only found in document:", only_in_docs)
if only_in_protobuf:
print("Error: RPCs only found in protobuf:", only_in_protobuf)
return ret
if __name__ == '__main__':
sys.exit(check())