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>
224 lines
7.2 KiB
Python
224 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
"""
|
|
Check that functions are properly marked as weak symbols.
|
|
|
|
Usage:
|
|
# Single file, all esp_ functions:
|
|
./check_weak_functions.py --file path/to/file.c
|
|
|
|
# Single file, specific functions:
|
|
./check_weak_functions.py --file path/to/file.c --functions func1,func2,func3
|
|
|
|
# Multiple files (--functions applies to the preceding --file):
|
|
./check_weak_functions.py --file file1.c --functions a,b --file file2.c --file file3.c --functions c
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
# All possible weak symbol markers
|
|
WEAK_MARKERS = [
|
|
'H_WEAK_REF',
|
|
'WEAK_REF',
|
|
'WEAK',
|
|
'__attribute__((weak))',
|
|
]
|
|
|
|
def check_weak_functions(filepath, target_functions=None, debug=False, verbose=False):
|
|
"""
|
|
Check that functions have weak symbol attributes.
|
|
|
|
Args:
|
|
filepath: Path to the file to check
|
|
target_functions: List of specific function names to check, or None for all
|
|
debug: Enable debug output
|
|
verbose: Print success messages
|
|
|
|
Returns:
|
|
bool: True if all checks pass, False otherwise
|
|
"""
|
|
|
|
if not Path(filepath).exists():
|
|
print(f"❌ ERROR: {filepath} not found.", file=sys.stderr)
|
|
return False
|
|
|
|
with open(filepath, 'r') as f:
|
|
content = f.read()
|
|
|
|
violations = []
|
|
found_functions = {}
|
|
|
|
# Pattern to match function definitions (not calls)
|
|
# A function definition has the opening brace { on the same or next line
|
|
func_def_pattern = r'([^\n;]*?)\b(\w+)\s*\([^)]*\)\s*(?:\n\s*)?\{'
|
|
|
|
for match in re.finditer(func_def_pattern, content, re.MULTILINE):
|
|
function_name = match.group(2)
|
|
|
|
# Only check functions starting with esp_
|
|
if not function_name.startswith('esp_'):
|
|
continue
|
|
|
|
# Find the line number
|
|
start_pos = match.start()
|
|
line_num = content[:start_pos].count('\n') + 1
|
|
|
|
# Get the full line(s) for this function definition
|
|
declaration = match.group(0)
|
|
|
|
# Check if any weak marker is present in the declaration
|
|
has_weak_marker = any(marker in declaration for marker in WEAK_MARKERS)
|
|
found_marker = None
|
|
if has_weak_marker:
|
|
for marker in WEAK_MARKERS:
|
|
if marker in declaration:
|
|
found_marker = marker
|
|
break
|
|
|
|
if debug:
|
|
marker_str = found_marker if has_weak_marker else "NONE"
|
|
print(f"DEBUG: Line {line_num}: {function_name}()")
|
|
print(f" Marker: {marker_str}")
|
|
print(f" Declaration: {declaration[:100]}...")
|
|
print()
|
|
|
|
found_functions[function_name] = {
|
|
'line': line_num,
|
|
'has_weak': has_weak_marker,
|
|
'marker': found_marker
|
|
}
|
|
|
|
# If checking specific functions, skip others
|
|
if target_functions and function_name not in target_functions:
|
|
continue
|
|
|
|
if not has_weak_marker:
|
|
violations.append(f" Line {line_num}: {function_name}() - Missing weak marker")
|
|
|
|
# Check if all target functions were found
|
|
if target_functions:
|
|
missing = set(target_functions) - set(found_functions.keys())
|
|
if missing:
|
|
print(f"❌ ERROR: The following functions were not found in {filepath}:", file=sys.stderr)
|
|
for func in sorted(missing):
|
|
print(f" - {func}()", file=sys.stderr)
|
|
print(file=sys.stderr)
|
|
return False
|
|
|
|
# Report results
|
|
if violations:
|
|
print(f"❌ ERROR: Found function definitions without weak markers in {filepath}:", file=sys.stderr)
|
|
print('\n'.join(violations), file=sys.stderr)
|
|
print(f"\nAccepted weak markers: {', '.join(WEAK_MARKERS)}", file=sys.stderr)
|
|
return False
|
|
|
|
# Success message (only if verbose or debug)
|
|
if verbose or debug:
|
|
if target_functions:
|
|
checked_count = len([f for f in target_functions if f in found_functions])
|
|
if checked_count > 0:
|
|
print(f"✅ Checked {checked_count} function(s) in {filepath}")
|
|
for func in target_functions:
|
|
if func in found_functions:
|
|
info = found_functions[func]
|
|
marker_display = info['marker'] if info['marker'] else "NO MARKER"
|
|
print(f" Line {info['line']}: {func}() - {marker_display}")
|
|
else:
|
|
total = len(found_functions)
|
|
print(f"✅ All {total} function(s) in {filepath} are properly marked as weak")
|
|
|
|
return True
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Check that functions are properly marked as weak symbols',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--file', '-f',
|
|
dest='files',
|
|
metavar='FILE',
|
|
help='File to check. Can be repeated. Each --file may be followed by --functions.'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--functions', '-F',
|
|
help='Comma-separated list of specific function names to check'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--verbose', '-v',
|
|
action='store_true',
|
|
help='Print success messages'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--debug', '-d',
|
|
action='store_true',
|
|
help='Debug output'
|
|
)
|
|
|
|
# Don't use parse_args() — walk sys.argv manually so we can pair
|
|
# each --file with the --functions that immediately follows it (if any).
|
|
checks = []
|
|
current_file = None
|
|
verbose = False
|
|
debug = False
|
|
|
|
i = 1
|
|
while i < len(sys.argv):
|
|
arg = sys.argv[i]
|
|
if arg in ('--file', '-f'):
|
|
if current_file is not None:
|
|
# previous --file had no --functions, check all
|
|
checks.append((current_file, None))
|
|
current_file = sys.argv[i + 1]
|
|
i += 2
|
|
elif arg in ('--functions', '-F'):
|
|
if current_file is None:
|
|
print("❌ ERROR: --functions must follow --file", file=sys.stderr)
|
|
sys.exit(1)
|
|
funcs = [f.strip() for f in sys.argv[i + 1].split(',') if f.strip()]
|
|
checks.append((current_file, funcs))
|
|
current_file = None
|
|
i += 2
|
|
elif arg in ('--verbose', '-v'):
|
|
verbose = True
|
|
i += 1
|
|
elif arg in ('--debug', '-d'):
|
|
debug = True
|
|
i += 1
|
|
else:
|
|
i += 1
|
|
|
|
# flush last --file if it had no --functions
|
|
if current_file is not None:
|
|
checks.append((current_file, None))
|
|
|
|
if not checks:
|
|
print("❌ ERROR: At least one --file is required.", file=sys.stderr)
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
if debug:
|
|
for filepath, funcs in checks:
|
|
func_str = ', '.join(funcs) if funcs else 'all esp_ functions'
|
|
print(f"Checking: {filepath} ({func_str})")
|
|
print()
|
|
|
|
all_passed = True
|
|
for filepath, target_functions in checks:
|
|
if not check_weak_functions(filepath, target_functions, debug=debug, verbose=verbose):
|
|
all_passed = False
|
|
|
|
if not all_passed:
|
|
sys.exit(1)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|