libretiny: scope BK72xx IRAM_ATTR to BK7231N; T/Q/7251 stay no-op

BK7231T / BK7231Q / BK7251 share a LibreTiny linker template that only
declares flash + ram, no executable RAM region. Their SDK wraps flash
writes in GLOBAL_INT_DISABLE() which masks both FIQ and IRQ, so no ISR
fires while flash is stalled and the "code in flash while flash is busy"
scenario IRAM_ATTR is guarding against does not occur on those variants.
Map IRAM_ATTR to nothing on them rather than orphan-linking .sram.text
into flash and drop the T/Q/7251 entries from _PATCHERS_BY_VARIANT so
the pre-link hook is not registered for them.

BK7231N still gets the full fix (grown .itcm.code) because its SDK takes
the defense-in-depth route of also placing flash/ISR/FreeRTOS critical
code in ITCM, so our IRAM_ATTR functions need to live there too.

Also make _grow_bk72xx_itcm fail loudly if the tcm/itcm declarations in
the BK7231N template ever change shape, matching the fail-hard stance of
the rest of the script.
This commit is contained in:
J. Nick Koston
2026-04-15 14:34:16 -10:00
parent 85d1ab7303
commit bc672d358b
3 changed files with 41 additions and 19 deletions
+8 -6
View File
@@ -467,15 +467,17 @@ async def component_to_code(config):
# it for project source files only. GCC uses the last -O flag.
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 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).
# IRAM_ATTR on LibreTiny expands to section(".sram.text") on families
# where executable RAM is available (see esphome/core/hal.h). This pre-
# link hook rewrites the processed .ld files in the build dir so that
# section lands in each family's executable RAM output section:
# - BK7231N: grow .itcm from 4.5 kB to 8.5 kB (steal from .tcm) and
# inject KEEP(*(.sram.text*)) into .itcm.code.
# - 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*)).
# BK7231T/Q/7251 have no executable RAM region; their SDK disables IRQ
# + FIQ around flash writes, so IRAM_ATTR is left a no-op on them.
cg.add_platformio_option("extra_scripts", ["pre:patch_linker.py"])
# dummy version code
cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)"))
@@ -52,9 +52,20 @@ _BK_ITCM_REGION = re.compile(
def _grow_bk72xx_itcm(content):
# Shrink tcm by 4 kB: 60k - 512 -> 56k - 512.
new_content = _BK_TCM_LEN.sub(r"\g<1>56k\g<2>", content)
new_content, tcm_count = _BK_TCM_LEN.subn(r"\g<1>56k\g<2>", content)
# Shift itcm origin back 4 kB and grow length: 4k + 512 -> 8k + 512.
new_content = _BK_ITCM_REGION.sub(r"\g<1>0x003FDE00\g<2>8k\g<3>", new_content)
new_content, itcm_count = _BK_ITCM_REGION.subn(
r"\g<1>0x003FDE00\g<2>8k\g<3>", new_content
)
if tcm_count != 1 or itcm_count != 1:
raise RuntimeError(
"ESPHome: BK72xx linker script did not match the expected "
"tcm/itcm declarations (tcm matches: {}, itcm matches: {}); "
"refusing to link because IRAM_ATTR placement cannot be verified. "
"LibreTiny probably changed the bk7231*_bsp.template.ld layout, "
"update _BK_TCM_LEN / _BK_ITCM_REGION in patch_linker.py.script "
"before shipping firmware.".format(tcm_count, itcm_count)
)
return new_content
@@ -96,13 +107,13 @@ def _inject_keep(host_section):
return patch
# RTL8720C is absent: its stock linker already consumes *(.sram.text*), so
# no .ld patch is needed; the summary falls back to reading symbol addresses.
# Variants not listed here intentionally have no .ld patcher:
# - RTL8720C: stock linker already consumes *(.sram.text*).
# - BK7231T / BK7231Q / BK7251: SDK wraps flash ops in GLOBAL_INT_DISABLE()
# (FIQ + IRQ masked), so no ISR fires during a flash stall and IRAM_ATTR
# is a no-op on those variants (see esphome/core/hal.h).
_PATCHERS_BY_VARIANT = {
"BK7231N": (_grow_bk72xx_itcm, _inject_keep(_BK_ITCM)),
"BK7231T": (_grow_bk72xx_itcm, _inject_keep(_BK_ITCM)),
"BK7231Q": (_grow_bk72xx_itcm, _inject_keep(_BK_ITCM)),
"BK7251": (_grow_bk72xx_itcm, _inject_keep(_BK_ITCM)),
"LN882H": (_inject_keep(_LN_COPY),),
"RTL8710B": (_inject_keep(_RTL8710B_IMAGE2),),
}
+15 -6
View File
@@ -24,13 +24,22 @@
#elif defined(USE_LIBRETINY)
// 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.
// ISR even while flash is busy (XIP stall, OTA, logger flash write).
// patch_linker.py.script routes ".sram.text" into each family's RAM-
// executable output section: .itcm.code on BK7231N, .image2.ram.text on
// RTL8710B, .flash_copysection on LN882H, stock *(.sram.text*) glob on
// RTL8720C.
//
// BK7231T/Q/7251 are left as a no-op: their SDK wraps flash operations in
// GLOBAL_INT_DISABLE() which masks FIQ + IRQ for the duration of the
// write, so no ISR fires while flash is stalled and the scenario
// IRAM_ATTR guards against does not occur there.
#if defined(USE_LIBRETINY_VARIANT_BK7231T) || defined(USE_LIBRETINY_VARIANT_BK7231Q) || \
defined(USE_LIBRETINY_VARIANT_BK7251)
#define IRAM_ATTR
#else
#define IRAM_ATTR __attribute__((noinline, section(".sram.text")))
#endif
#define PROGMEM
#else