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>
116 lines
2.8 KiB
Python
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())
|