The SDK's built-in ISR / flash / FreeRTOS critical-path code already fills
most of the stock 4.5 kB .itcm executable RAM region, leaving under 500
bytes for ESPHome's IRAM_ATTR functions. cb3s overflowed by 248 bytes on
first link. Physically .tcm (rw!x, data) and .itcm (rwx, code) are
contiguous SRAM blocks on ARM968E-S; the template's boundary is just a
linker carve-out. Shift it back 4 kB so .tcm shrinks 60k -> 56k and .itcm
grows 4.5k -> 8.5k. Confirmed fits every ESPHome IRAM_ATTR function on cb3s
with ample headroom.
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.
PROVIDE symbols without references get garbage-collected by the linker, so
the post-link summary was falling back to the symbol-scrape path on every
family. Use direct assignment so the markers always land in the ELF symbol
table, giving an exact byte count for the ESPHome IRAM_ATTR contribution.
After linking, walk the ELF symbol table and print one of:
ESPHome: IRAM_ATTR placement summary (BK7231N):
.sram.text: 312 bytes at 0x004001d0 - 0x00400308
ESPHome: IRAM_ATTR placement summary (RTL8720C):
IRAM symbols at 0x10002f6c - 0x10002fa8 (approx 60 bytes)
On the three families whose linker scripts we already patch (BK72xx, LN882H,
RTL8710B) the PROVIDE(__esphome_sram_text_start/end = .) markers injected
alongside KEEP(*(.sram.text*)) give an exact byte count for the ESPHome
IRAM_ATTR contribution. On RTL8720C we cannot patch the linker script (it
is loaded directly from the framework package) so the summary falls back to
reading the addresses of the three core IRAM_ATTR symbols. Either way the
address range is visible proof that the functions ended up in SRAM rather
than flash.
Two issues from the cb3s smoke test:
- The previous CPPDEFINES-only variant detection missed the
USE_LIBRETINY_VARIANT_* define on BK72xx in the pre-script environment,
so no patcher registered and the .sram.text section silently landed in
flash. Check BUILD_FLAGS as a fallback so the define is picked up
regardless of when PlatformIO finishes populating CPPDEFINES.
- When the variant could not be detected, or no .ld file was modified, the
script exited quietly and the firmware linked with IRAM_ATTR functions
in flash — the exact bug this PR is trying to fix. Raise a RuntimeError
in all three failure cases (variant missing, variant unknown, no .ld
patched) so the build fails loudly instead of producing broken firmware.
Two fixes on top of the initial landing:
- BK72xx builds Thumb-mode TUs alongside its ARM FreeRTOS port, and the
MRS CPSR instruction is ARM-only, so in_isr_context() failed to
assemble on cb3s. Delegate to the port's own platform_is_in_interrupt_context()
helper (declared extern "C" in portmacro.h and built in ARM mode) instead
of embedding inline CPSR reads in Thumb code.
- Section name unified to ".sram.text" for every LibreTiny family to avoid
the "setting incorrect section attributes for .data.*" GAS warning. The
pre-link patcher now knows how to route that section into each family's
RAM-resident output:
* BK72xx: flip SRAM region (rw!x) -> (rwx) and inject
KEEP(*(.sram.text*)) into .data : { ... }
* LN882H: inject KEEP(*(.sram.text*)) into .flash_copysection : { ... }
* RTL8710B: inject KEEP(*(.sram.text*)) into .image2.ram.text : { ... }
* RTL8720C: no-op (linker already consumes *(.sram.text*))
The injection uses a "/* esphome .sram.text */" marker so repeated runs
are idempotent.
Two issues surfaced building cb3s-test.yaml:
1. BK72xx's ARM9 FreeRTOS port does not define portYIELD_FROM_ISR; context
switches happen naturally at IRQ exit. Wrap the call in #ifdef so the
wake path compiles on ports that lack it.
2. Placing functions directly in section(".data") provoked assembler
"ignoring changed section attributes" warnings and a hard DWARF error
("leb128 operand is an undefined symbol: .LVU31") because the compiler
emits code-style attributes ("ax") that collide with .data's
data-style attributes ("aw"). Use section(".data.iram_text") instead;
the linker still folds it into the .data output via "*(.data.*)", so
the bytes land in SRAM via the SDK's .data copy, but the assembler no
longer sees conflicting attributes.
target_platform for LibreTiny devices is one of bk72xx, rtl87xx, ln882x
(not "libretiny" itself), so writer.py's platform-dispatched copy_files()
call was not reaching libretiny.copy_files(). That left the pre-link
patch_linker.py script absent from the build dir, so PlatformIO failed
with "missing SConscript file 'patch_linker.py'".
Add a delegating copy_files() to each generated sub-component and to
generate_components.py so regeneration keeps them in sync.
Previously, IRAM_ATTR was an empty no-op on every LibreTiny family, so any
ISR handler (gpio binary sensor, cc1101, sx126x/sx127x, mcp23xxx, pcf8574,
pca9554, pca6416a, pi4ioe5v6408, tca9555, ...) lived in flash. When the
ISR fired while flash was busy (XIP stall, OTA, logger flash write), the
device could deadlock or crash.
- hal.h: IRAM_ATTR now routes each family into a RAM-resident section
(RTL8710B .image2.ram.text, RTL8720C .sram.text, BK72xx / LN882H .data,
which the SDK startup code copies from flash into SRAM before main).
Also adds esphome::in_isr_context() as a portable always_inline ISR
detection helper: xPortInIsrContext on ESP32, PS.INTLEVEL on ESP8266,
IPSR on Cortex-M cores, CPSR mode on BK72xx ARM9.
- main_task.h: both notify helpers marked always_inline so IRAM callers
keep the wake path in IRAM; removes the ESP32-only notify_any_context
which moves into wake.h.
- wake.h / wake.cpp: LibreTiny now shares the ESP32 wake path via a new
wake_main_task_any_context() helper that picks between xTaskNotifyGive
and vTaskNotifyGiveFromISR using in_isr_context(). wake_loop_any_context
and wake_loop_isrsafe are now IRAM_ATTR entry points on LibreTiny too.
- libretiny/patch_linker.py.script: pre-link hook modelled on the ESP8266
testing_mode patcher. BK72xx linker templates declare the SRAM region
as (rw!x), blocking code placement in .data. The hook flips that to
(rwx) so IRAM_ATTR functions can be emitted there; the MMU already
permits RAM execution (the Wi-Fi driver runs from SRAM). No-op on the
other families.
Replace mutable tag references with immutable commit SHAs
to prevent supply-chain attacks via compromised tags.
Version comments are preserved for readability.