diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 10dc0fe984..6b24f4d52c 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -468,16 +468,14 @@ 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") 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. + # where the ISR-during-flash race is real (see esphome/core/hal.h). This + # pre-link hook routes that section into each family's executable RAM: # - 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. + # BK72xx (all variants) is no-op: the Beken SDK wraps every flash write + # in GLOBAL_INT_DISABLE() so no ISR can fire during a flash stall; the + # race IRAM_ATTR guards against cannot occur. 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)")) diff --git a/esphome/components/libretiny/patch_linker.py.script b/esphome/components/libretiny/patch_linker.py.script index ab11257be0..f7f536df4e 100644 --- a/esphome/components/libretiny/patch_linker.py.script +++ b/esphome/components/libretiny/patch_linker.py.script @@ -3,6 +3,7 @@ Import("env") # noqa import os import re +import subprocess # ESPHome marks ISR code IRAM_ATTR, which on LibreTiny expands to # section(".sram.text") (see esphome/core/hal.h). Each family's linker script @@ -122,46 +123,50 @@ def _pre_link(target, source, env): _patch_build_dir(_patchers, env.subst("$BUILD_DIR")) -def _post_link(target, source, env): - """Print where IRAM_ATTR ended up so users can confirm at a glance.""" - nm = env.subst("$NM") or "arm-none-eabi-nm" - elf = env.subst("$BUILD_DIR/${PROGNAME}.elf") - if not os.path.isfile(elf): - return +# Well-known ESPHome IRAM_ATTR symbols used as a fallback on RTL8720C, where +# we cannot inject the __esphome_sram_text_start/end markers. +_FALLBACK_IRAM_SYMBOLS = frozenset({ + "_ZN7esphome21wake_loop_any_contextEv", + "_ZN7esphome17wake_loop_isrsafeEPl", + "_ZN7esphome9Component28enable_loop_soon_any_contextEv", +}) + + +def _collect_iram_symbols(nm, elf): + """Return (start, end, fallback_addresses) for the IRAM_ATTR payload.""" try: - import subprocess out = subprocess.check_output([nm, "--defined-only", elf], text=True) except (OSError, subprocess.CalledProcessError): - return + return None, None, [] start = end = None - sample = [] + fallback = [] for line in out.splitlines(): parts = line.split(maxsplit=2) if len(parts) != 3: continue - addr, _kind, name = parts + addr_str, _kind, name = parts if name == "__esphome_sram_text_start": - start = int(addr, 16) + start = int(addr_str, 16) elif name == "__esphome_sram_text_end": - end = int(addr, 16) - elif name in ( - "_ZN7esphome21wake_loop_any_contextEv", - "_ZN7esphome17wake_loop_isrsafeEPl", - "_ZN7esphome9Component28enable_loop_soon_any_contextEv", - ): - sample.append((int(addr, 16), name)) - header = "ESPHome: IRAM_ATTR placement summary ({}):".format(_variant) + end = int(addr_str, 16) + elif name in _FALLBACK_IRAM_SYMBOLS: + fallback.append(int(addr_str, 16)) + return start, end, fallback + + +def _post_link(target, source, env): + """Print where IRAM_ATTR ended up so users can confirm at a glance.""" + elf = env.subst("$BUILD_DIR/${PROGNAME}.elf") + if not os.path.isfile(elf): + return + start, end, fallback = _collect_iram_symbols(env.subst("$NM"), elf) + print("ESPHome: IRAM_ATTR placement summary ({}):".format(_variant)) if start is not None and end is not None: - print(header) print(" .sram.text: {} bytes at 0x{:08x} - 0x{:08x}".format(end - start, start, end)) - elif sample: - print(header) - sample.sort() - lo = sample[0][0] - hi = sample[-1][0] + elif fallback: + lo, hi = min(fallback), max(fallback) print(" IRAM symbols at 0x{:08x} - 0x{:08x} (approx {} bytes)".format(lo, hi, hi - lo)) else: - print(header) print(" no IRAM_ATTR symbols found in the ELF") diff --git a/esphome/core/hal.h b/esphome/core/hal.h index 01ce4189d1..79eca8d5c3 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -56,10 +56,11 @@ #include #endif -#if defined(USE_LIBRETINY_VARIANT_BK7231N) || defined(USE_LIBRETINY_VARIANT_BK7231T) || \ - defined(USE_LIBRETINY_VARIANT_BK7231Q) || defined(USE_LIBRETINY_VARIANT_BK7251) +#ifdef USE_BK72XX // Declared in the Beken FreeRTOS port (portmacro.h) and built in ARM mode so -// it is callable from Thumb code via interworking. +// it is callable from Thumb code via interworking. The MRS CPSR instruction +// is ARM-only and user code here may be built in Thumb, so in_isr_context() +// defers to this port helper on BK72xx instead of reading CPSR inline. extern "C" uint32_t platform_is_in_interrupt_context(void); #endif @@ -79,11 +80,8 @@ __attribute__((always_inline)) inline bool in_isr_context() { uint32_t ipsr; __asm__ volatile("mrs %0, ipsr" : "=r"(ipsr)); 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). The MRS CPSR instruction is ARM-only, and - // user code here may be built in Thumb mode. Defer to the FreeRTOS port - // helper declared above (compiled in ARM mode by the SDK). +#elif defined(USE_BK72XX) + // BK72xx is ARM968E-S (ARM9); see extern declaration above. return platform_is_in_interrupt_context() != 0; #elif defined(USE_LIBRETINY) // Cortex-M (AmebaZ, AmebaZ2, LN882H). IPSR is the active exception number;