mirror of
https://github.com/esphome/esphome.git
synced 2026-09-18 18:48:39 +00:00
libretiny: fix BK72xx Thumb asm; unify IRAM_ATTR on .sram.text
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.
This commit is contained in:
@@ -4,38 +4,69 @@ Import("env") # noqa
|
||||
import os
|
||||
import re
|
||||
|
||||
# BK72xx linker templates declare the SRAM region as "(rw!x)" — read/write but
|
||||
# not executable. That prevents the linker from emitting functions we mark
|
||||
# IRAM_ATTR (defined as section(".data.iram_text") on BK72xx in
|
||||
# esphome/core/hal.h; folded into .data by the linker's "*(.data.*)" glob)
|
||||
# into the .data output section, which is the only section the SDK startup
|
||||
# code copies from flash into SRAM before main() runs.
|
||||
# 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):
|
||||
#
|
||||
# LibreTiny's own Wi-Fi driver already runs code from SRAM at runtime, so the
|
||||
# BK72xx MMU permits execution from that region; the "!x" is a stale hint in
|
||||
# the linker template. Flipping it to "(rwx)" lets the linker place the
|
||||
# function body in .data, where it is copied alongside initialized globals and
|
||||
# becomes callable from an ISR even while flash is busy.
|
||||
#
|
||||
# LN882H already declares RAM0 as "(rwx)" and Realtek families use dedicated
|
||||
# .image2.ram.text / .sram.text output sections — nothing to patch there.
|
||||
# - 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).
|
||||
# - LN882H: stock linker has ".flash_copysection" which is flash-to-RAM0
|
||||
# copied at startup; inject "KEEP(*(.sram.text*))" there.
|
||||
|
||||
|
||||
def _is_bk72xx(defines):
|
||||
_MARKER = "/* esphome .sram.text */"
|
||||
_KEEP_LINE = " KEEP(*(.sram.text*)) " + _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*\))")
|
||||
_LN_COPY = re.compile(r"(\.flash_copysection\s*:\s*\{\s*\n)")
|
||||
_RTL8710B_IMAGE2 = re.compile(r"(\.image2\.ram\.text\s*:\s*\{\s*\n)")
|
||||
|
||||
|
||||
def _detect(defines):
|
||||
prefix = "USE_LIBRETINY_VARIANT_"
|
||||
bk = ("BK7231N", "BK7231T", "BK7231Q", "BK7251")
|
||||
for token in defines:
|
||||
if isinstance(token, tuple):
|
||||
token = token[0]
|
||||
if isinstance(token, str) and token.startswith(prefix):
|
||||
return token[len(prefix):] in bk
|
||||
return False
|
||||
return token[len(prefix):]
|
||||
return None
|
||||
|
||||
|
||||
_RW_NO_X = re.compile(r"(\bram\s*\()\s*rw\s*!\s*x(\s*\))")
|
||||
def _patch_bk72xx(content):
|
||||
new_content = _BK_RW_NO_X.sub(r"\1rwx\2", content)
|
||||
if _MARKER not in new_content:
|
||||
new_content = _BK_DATA.sub(r"\1" + _KEEP_LINE, new_content, count=1)
|
||||
return new_content
|
||||
|
||||
|
||||
def _patch_directory(build_dir):
|
||||
def _patch_ln882h(content):
|
||||
if _MARKER in content:
|
||||
return content
|
||||
return _LN_COPY.sub(r"\1" + _KEEP_LINE, content, count=1)
|
||||
|
||||
|
||||
def _patch_rtl8710b(content):
|
||||
if _MARKER in content:
|
||||
return content
|
||||
return _RTL8710B_IMAGE2.sub(r"\1" + _KEEP_LINE, content, count=1)
|
||||
|
||||
|
||||
def _patchers_for(variant):
|
||||
if variant in ("BK7231N", "BK7231T", "BK7231Q", "BK7251"):
|
||||
return (_patch_bk72xx,)
|
||||
if variant == "LN882H":
|
||||
return (_patch_ln882h,)
|
||||
if variant == "RTL8710B":
|
||||
return (_patch_rtl8710b,)
|
||||
return ()
|
||||
|
||||
|
||||
def _patch_build_dir(patchers, build_dir):
|
||||
if not os.path.isdir(build_dir):
|
||||
return
|
||||
for name in os.listdir(build_dir):
|
||||
@@ -44,22 +75,24 @@ def _patch_directory(build_dir):
|
||||
path = os.path.join(build_dir, name)
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
patched = _RW_NO_X.sub(r"\1rwx\2", content)
|
||||
patched = content
|
||||
for patch in patchers:
|
||||
patched = patch(patched)
|
||||
if patched != content:
|
||||
with open(path, "w", encoding="utf-8") as fh:
|
||||
fh.write(patched)
|
||||
print(
|
||||
"ESPHome: patched BK72xx linker script {} (rw!x -> rwx) so "
|
||||
"IRAM_ATTR functions can be placed in .data".format(name)
|
||||
)
|
||||
print("ESPHome: patched linker script {} for IRAM_ATTR placement".format(name))
|
||||
|
||||
|
||||
def _patch_ld_before_link(target, source, env):
|
||||
_patch_directory(env.subst("$BUILD_DIR"))
|
||||
def _pre_link(target, source, env):
|
||||
_patch_build_dir(_patchers, env.subst("$BUILD_DIR"))
|
||||
|
||||
|
||||
if _is_bk72xx(env.get("CPPDEFINES", [])):
|
||||
_variant = _detect(env.get("CPPDEFINES", []))
|
||||
_patchers = _patchers_for(_variant) if _variant else ()
|
||||
|
||||
if _patchers:
|
||||
# LibreTiny writes the processed .ld templates into $BUILD_DIR during its
|
||||
# own builder setup, which may run after this script. Register the patch
|
||||
# as a pre-link action so it executes once the linker scripts exist.
|
||||
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", _patch_ld_before_link)
|
||||
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", _pre_link)
|
||||
|
||||
+11
-23
@@ -24,23 +24,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). The section used
|
||||
// varies per family, based on what each linker script already supports:
|
||||
// - RTL8710B (AmebaZ): ".image2.ram.text" output section exists.
|
||||
// - RTL8720C (AmebaZ2): "*(.sram.text*)" is already consumed.
|
||||
// - BK72xx / LN882H: the stock linker script has no RAM text section, so we
|
||||
// use ".data.iram_text" which the linker's "*(.data.*)" glob folds into
|
||||
// the .data output section. The SDK startup code copies .data from flash
|
||||
// into SRAM before main() runs, so the function body ends up in executable
|
||||
// RAM. Using ".data.*" instead of plain ".data" keeps the assembler happy
|
||||
// (no section-attribute collision) while still landing in the right place.
|
||||
#if defined(USE_LIBRETINY_VARIANT_RTL8710B)
|
||||
#define IRAM_ATTR __attribute__((noinline, section(".image2.ram.text")))
|
||||
#elif defined(USE_LIBRETINY_VARIANT_RTL8720C)
|
||||
// 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.
|
||||
#define IRAM_ATTR __attribute__((noinline, section(".sram.text")))
|
||||
#else
|
||||
#define IRAM_ATTR __attribute__((noinline, section(".data.iram_text")))
|
||||
#endif
|
||||
#define PROGMEM
|
||||
|
||||
#else
|
||||
@@ -73,13 +63,11 @@ __attribute__((always_inline)) inline bool in_isr_context() {
|
||||
return ipsr != 0;
|
||||
#elif defined(USE_LIBRETINY_VARIANT_BK7231N) || defined(USE_LIBRETINY_VARIANT_BK7231T) || \
|
||||
defined(USE_LIBRETINY_VARIANT_BK7231Q) || defined(USE_LIBRETINY_VARIANT_BK7251)
|
||||
// BK72xx is ARM968E-S (ARM9). CPSR mode bits [4:0]:
|
||||
// 0x10 USR, 0x13 SVC, 0x1F SYS are normal; others (IRQ=0x12, FIQ=0x11,
|
||||
// ABT=0x17, UND=0x1B) are exception modes.
|
||||
uint32_t cpsr;
|
||||
__asm__ volatile("mrs %0, cpsr" : "=r"(cpsr));
|
||||
uint32_t mode = cpsr & 0x1Fu;
|
||||
return mode != 0x10u && mode != 0x13u && mode != 0x1Fu;
|
||||
// BK72xx is ARM968E-S (ARM9). The MRS CPSR instruction is ARM-only, and
|
||||
// user code here may be built in Thumb mode. Defer to the FreeRTOS port
|
||||
// helper (compiled in ARM mode by the SDK) which reads CPSR internally.
|
||||
extern "C" uint32_t platform_is_in_interrupt_context(void);
|
||||
return platform_is_in_interrupt_context() != 0;
|
||||
#elif defined(USE_LIBRETINY)
|
||||
// Cortex-M (AmebaZ, AmebaZ2, LN882H). IPSR is the active exception number;
|
||||
// non-zero means we're in a handler.
|
||||
|
||||
Reference in New Issue
Block a user