Files
Linus Torvalds 968cd8845a Only put the audio core in RAM, and check that it stays clean
Drop copy_to_ram.  It was always the lazy option - I didn't want to work
out which functions actually needed to be in SRAM, so I put all of them
there.  Now that the audio core is marked, the linker script already
does the right thing: it pulls '.time_critical*' out of flash, and
everything else is cold enough to run from XIP.

	RAM before:  73132 text + 386972 bss = 460104
	RAM after:   20504 data + 384652 bss = 405156

which is 54948 bytes back, and takes the headroom from 70K to 124K out
of 520K.  That matters more than it sounds: bss is already 384K, mostly
the 64K analyzer ring and the 64K FFT buffer, so headroom is the thing
that was going to run out first.

Also add scripts/check-audio.py, run after every link.  Everything in a
'.time_critical.audio_*' section may only call into another one, and
anything else fails the build.  Right now there is nothing on the
allowed list at all - the audio path doesn't call a single library
function - which is the whole reason to add the check now rather than
after it has rotted.  The last one to go was the newlib 'lrintf()' a
couple of commits ago.

The one thing this cannot check is that the code still *works* from
flash.  cpu1 is unaffected by construction since all of it is in RAM,
but cpu0 now takes XIP cache misses, and the tuner does an 8192-point
FFT at 25Hz.  I think the cache holds the inner loop fine.  I have not
proven it on hardware.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-07-27 12:41:32 -07:00

79 lines
2.7 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Check that nothing running on the audio core calls out of it.
#
# Everything marked with __audio_func() is compiled into its own
# '.time_critical.audio_<name>' section. Anything such a function calls
# has to be marked too - if it isn't, either it wants marking, or we have
# accidentally pulled a newlib or soft-float routine into the
# hard-realtime path.
#
# The linker merges all of those input sections into .data, so the
# section names only survive in the map file. That is fine: the macro
# builds the section name out of the function name, so the map is enough
# to recover the list.
#
# Called as: check-audio.py <map> <elf> [objdump]
#
import re
import subprocess
import sys
# Calls allowed out of the audio sections. Keep this short, and say why.
ALLOWED = {
# (nothing yet - the audio path is entirely self-contained)
}
def main():
mapfile, elf = sys.argv[1], sys.argv[2]
objdump = (sys.argv[3] if len(sys.argv) > 3 and sys.argv[3]
else "arm-none-eabi-objdump")
with open(mapfile) as f:
audio = set(re.findall(r"^ \.time_critical\.audio_(\S+)$",
f.read(), re.M))
if not audio:
sys.exit("check-audio: no __audio_func() symbols in the map - "
"stale build?")
res = subprocess.run([objdump, "-d", "--no-show-raw-insn", elf],
capture_output=True, text=True)
if res.returncode:
sys.exit(f"check-audio: {objdump} failed:\n{res.stderr}")
func = re.compile(r"^[0-9a-f]+ <([^>]+)>:")
call = re.compile(r"\s(?:bl|blx)\s+[0-9a-f]+ <([^>+]+)")
cur, bad, seen = None, set(), set()
for line in res.stdout.splitlines():
m = func.match(line)
if m:
cur = m.group(1)
if cur in audio:
seen.add(cur)
continue
m = call.search(line)
if m and cur in audio:
callee = m.group(1)
if callee not in audio and callee not in ALLOWED:
bad.add((cur, callee))
# A marked function that vanished was fully inlined into its callers,
# which is fine - they are marked too.
if bad:
print(f"check-audio: {len(bad)} call(s) leaving the audio core:",
file=sys.stderr)
for caller, callee in sorted(bad):
print(f" {caller} -> {callee}", file=sys.stderr)
print("\nMark the callee with __audio_func(), or if it really has to "
"live\nout of line, add it to ALLOWED in this script with a "
"reason.", file=sys.stderr)
return 1
print(f"check-audio: {len(seen)} functions on the audio core, no calls out")
return 0
if __name__ == "__main__":
sys.exit(main())