From 33118e0e57f6c1c5468e050c11c2a9921fed3b36 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 Apr 2026 13:59:42 -1000 Subject: [PATCH] =?UTF-8?q?libretiny:=20patch=5Flinker.py.script=20?= =?UTF-8?q?=E2=80=94=20fail=20hard=20if=20IRAM=5FATTR=20cannot=20land=20in?= =?UTF-8?q?=20SRAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../libretiny/patch_linker.py.script | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/esphome/components/libretiny/patch_linker.py.script b/esphome/components/libretiny/patch_linker.py.script index 08b95462f0..ce6db29294 100644 --- a/esphome/components/libretiny/patch_linker.py.script +++ b/esphome/components/libretiny/patch_linker.py.script @@ -27,16 +27,35 @@ _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): +def _detect(env): prefix = "USE_LIBRETINY_VARIANT_" - for token in defines: - if isinstance(token, tuple): + # CPPDEFINES may hold strings or (name, value) tuples; BUILD_FLAGS holds + # the raw "-DNAME" strings. PlatformIO populates both, but the exact order + # vs. extra_scripts varies, so check both to be robust. + for token in env.get("CPPDEFINES", []): + if isinstance(token, (list, tuple)): token = token[0] if isinstance(token, str) and token.startswith(prefix): return token[len(prefix):] + for flag in env.get("BUILD_FLAGS", []): + if isinstance(flag, str) and "-D" + prefix in flag: + name = flag.split("-D", 1)[1].split("=", 1)[0].strip() + if name.startswith(prefix): + return name[len(prefix):] return None +KNOWN_VARIANTS = ( + "BK7231N", + "BK7231T", + "BK7231Q", + "BK7251", + "LN882H", + "RTL8710B", + "RTL8720C", +) + + def _patch_bk72xx(content): new_content = _BK_RW_NO_X.sub(r"\1rwx\2", content) if _MARKER not in new_content: @@ -68,8 +87,12 @@ def _patchers_for(variant): def _patch_build_dir(patchers, build_dir): if not os.path.isdir(build_dir): - return - for name in os.listdir(build_dir): + raise RuntimeError( + "ESPHome: LibreTiny build dir {} does not exist at link time; " + "IRAM_ATTR placement cannot be verified".format(build_dir) + ) + patched_any = False + for name in sorted(os.listdir(build_dir)): if not name.endswith(".ld"): continue path = os.path.join(build_dir, name) @@ -82,15 +105,35 @@ def _patch_build_dir(patchers, build_dir): with open(path, "w", encoding="utf-8") as fh: fh.write(patched) print("ESPHome: patched linker script {} for IRAM_ATTR placement".format(name)) + patched_any = True + if not patched_any: + raise RuntimeError( + "ESPHome: no linker script in {} was patched for IRAM_ATTR; refusing " + "to link because IRAM_ATTR functions would end up in flash instead of " + "SRAM and would crash on an ISR while flash is busy".format(build_dir) + ) def _pre_link(target, source, env): _patch_build_dir(_patchers, env.subst("$BUILD_DIR")) -_variant = _detect(env.get("CPPDEFINES", [])) -_patchers = _patchers_for(_variant) if _variant else () +_variant = _detect(env) +if _variant is None: + raise RuntimeError( + "ESPHome: could not determine LibreTiny variant from build flags. " + "patch_linker.py needs USE_LIBRETINY_VARIANT_* to route IRAM_ATTR " + "into SRAM; without it, ISR handlers would silently end up in flash." + ) +if _variant not in KNOWN_VARIANTS: + raise RuntimeError( + "ESPHome: unknown LibreTiny variant {!r}; patch_linker.py does not " + "know how to route IRAM_ATTR into SRAM for this family. Update " + "patch_linker.py.script before shipping firmware.".format(_variant) + ) + +_patchers = _patchers_for(_variant) 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