libretiny: target BK72xx .itcm.code instead of .data for IRAM_ATTR

cb3s flashed firmware crashed after logger init. Main SRAM on ARM968E-S
is not instruction-fetchable: the stock linker marks it (rw!x) because
the bus does not allow instruction fetches from that region, so putting
IRAM_ATTR code in .data landed the bytes in RAM but triggered a prefetch
abort when the first IRAM function was called.

The ARM968 design has a separate tightly-coupled instruction memory
("itcm", 4.5 kB, rwx) where the SDK already routes its own ISR, flash,
and FreeRTOS critical-path code via the .itcm.code output section.
Inject KEEP(*(.sram.text*)) into .itcm.code so our IRAM_ATTR functions
share that executable RAM region. No (rw!x) flip is needed since we no
longer touch the main SRAM layout.
This commit is contained in:
J. Nick Koston
2026-04-15 14:18:39 -10:00
parent a1970737c6
commit 365ff86bac
3 changed files with 31 additions and 32 deletions
+5 -4
View File
@@ -468,10 +468,11 @@ async def component_to_code(config):
build_src_flags += " -Os"
cg.add_platformio_option("build_src_flags", build_src_flags)
# IRAM_ATTR on LibreTiny expands to section(".sram.text") (see
# esphome/core/hal.h). This pre-link hook rewrites the processed .ld files
# in the build dir so that section lands in RAM on each family:
# - BK72xx: flip SRAM region (rw!x) -> (rwx) and inject
# KEEP(*(.sram.text*)) into the .data output section.
# esphome/core/hal.h). This pre-link hook rewrites the processed .ld
# files in the build dir so that section lands in an executable RAM
# output section on each family:
# - BK72xx: inject KEEP(*(.sram.text*)) into .itcm.code (the only
# rwx RAM region; main SRAM is rw!x on ARM968E-S).
# - LN882H: inject KEEP(*(.sram.text*)) into .flash_copysection.
# - RTL8710B: inject KEEP(*(.sram.text*)) into .image2.ram.text.
# - RTL8720C: no-op (stock linker already consumes *(.sram.text*)).
@@ -6,17 +6,23 @@ import re
# ESPHome marks ISR code IRAM_ATTR, which on LibreTiny expands to
# section(".sram.text") (see esphome/core/hal.h). Each family's linker script
# needs that section routed into RAM-resident code so the function is callable
# while flash is busy (XIP stall, OTA, logger flash write):
# needs that section routed into RAM-resident *executable* memory so the
# function is callable while flash is busy (XIP stall, OTA, logger flash
# write):
#
# - RTL8720C (AmebaZ2): stock linker already consumes "*(.sram.text*)", no-op.
# - RTL8710B (AmebaZ): stock linker has ".image2.ram.text" — inject
# "*(.sram.text*)" into it.
# - BK72xx: stock linker has a (rw!x) SRAM region that blocks executable
# placement; flip to (rwx) and inject "KEEP(*(.sram.text*))" into the ".data"
# output (which is already flash-to-SRAM copied at startup by the SDK).
# - BK72xx: ARM968E-S has tightly-coupled instruction RAM ("itcm", 4.5 kB,
# rwx) that the SDK uses for its own ISR / flash / FreeRTOS critical
# routines; main SRAM ("ram", 192 kB) is rw!x (bus does not allow
# instruction fetches). Inject "KEEP(*(.sram.text*))" into ".itcm.code"
# so our IRAM_ATTR functions share the only executable RAM region.
# - LN882H: stock linker has ".flash_copysection" which is flash-to-RAM0
# copied at startup; inject "KEEP(*(.sram.text*))" there.
# - RTL8710B (AmebaZ): stock linker has ".image2.ram.text" — inject
# "KEEP(*(.sram.text*))" into it.
# - RTL8720C (AmebaZ2): stock linker already consumes "*(.sram.text*)",
# no-op. Loaded directly from the framework package so we cannot inject
# our __esphome_sram_text_start/end markers either; the post-link summary
# falls back to reading known IRAM_ATTR symbol addresses instead.
_MARKER = "/* esphome .sram.text */"
@@ -28,13 +34,9 @@ _KEEP_LINE = (
"__esphome_sram_text_end = .; "
+ _MARKER + "\n"
)
_BK_DATA = re.compile(r"(\.data\s*:\s*\{\s*\n)")
_BK_RW_NO_X = re.compile(r"(\bram\s*\()\s*rw\s*!\s*x(\s*\))")
_BK_ITCM = re.compile(r"(\.itcm\.code\s*ALIGN\s*\(\s*\d+\s*\)\s*:\s*\{\s*\n)")
_LN_COPY = re.compile(r"(\.flash_copysection\s*:\s*\{\s*\n)")
_RTL8710B_IMAGE2 = re.compile(r"(\.image2\.ram\.text\s*:\s*\{\s*\n)")
# RTL8720C loads its linker script directly from the framework package so we
# cannot inject PROVIDE markers for it; the summary falls back to reading
# addresses of known IRAM_ATTR symbols instead of measuring a bracketed span.
def _detect(env):
@@ -75,17 +77,13 @@ def _inject_keep(host_section):
return patch
def _flip_bk72xx_rwx(content):
return _BK_RW_NO_X.sub(r"\1rwx\2", content)
# RTL8720C is absent: its stock linker already consumes *(.sram.text*), so
# no .ld patch is needed; the summary falls back to reading symbol addresses.
_PATCHERS_BY_VARIANT = {
"BK7231N": (_flip_bk72xx_rwx, _inject_keep(_BK_DATA)),
"BK7231T": (_flip_bk72xx_rwx, _inject_keep(_BK_DATA)),
"BK7231Q": (_flip_bk72xx_rwx, _inject_keep(_BK_DATA)),
"BK7251": (_flip_bk72xx_rwx, _inject_keep(_BK_DATA)),
"BK7231N": (_inject_keep(_BK_ITCM),),
"BK7231T": (_inject_keep(_BK_ITCM),),
"BK7231Q": (_inject_keep(_BK_ITCM),),
"BK7251": (_inject_keep(_BK_ITCM),),
"LN882H": (_inject_keep(_LN_COPY),),
"RTL8710B": (_inject_keep(_RTL8710B_IMAGE2),),
}
+7 -7
View File
@@ -23,13 +23,13 @@
#elif defined(USE_LIBRETINY)
// IRAM_ATTR places a function in SRAM so it is callable from an ISR even
// while flash is busy (XIP stall, OTA, logger flash write). All LibreTiny
// families use ".sram.text". RTL8720C (AmebaZ2) already consumes that
// section; RTL8710B (AmebaZ), BK72xx, and LN882H get it routed into their
// RAM-resident output section via patch_linker.py.script. Using a custom
// name avoids the assembler "setting incorrect section attributes" warning
// that ".data.*" triggers when we place executable code there.
// IRAM_ATTR places a function in executable RAM so it is callable from an
// ISR even while flash is busy (XIP stall, OTA, logger flash write). All
// LibreTiny families use ".sram.text"; patch_linker.py.script routes it
// into each family's RAM-executable output section (.itcm.code on BK72xx,
// .image2.ram.text on RTL8710B, .flash_copysection on LN882H). RTL8720C's
// stock linker already consumes *(.sram.text*) via its .ram.code_text
// output.
#define IRAM_ATTR __attribute__((noinline, section(".sram.text")))
#define PROGMEM