mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
172 lines
6.3 KiB
Plaintext
172 lines
6.3 KiB
Plaintext
# pylint: disable=E0602
|
|
Import("env") # noqa
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
# ESPHome marks ISR code IRAM_ATTR, which on LibreTiny maps to a per-family
|
|
# section routed into RAM-executable memory (see esphome/core/hal.h).
|
|
#
|
|
# This script is NOT loaded on BK72xx (IRAM_ATTR is a no-op there; the SDK
|
|
# masks FIQ+IRQ around flash writes). On the remaining families:
|
|
# - RTL8710B: hal.h uses section(".image2.ram.text"); stock linker consumes it.
|
|
# - RTL8720C: hal.h uses section(".sram.text"); stock linker consumes it.
|
|
# - LN882H: stock linker has no glob for ".sram.text", so we inject
|
|
# KEEP(*(.sram.text*)) into ".flash_copysection" (> RAM0 AT> FLASH).
|
|
#
|
|
# All families also get a post-link summary showing where IRAM_ATTR landed.
|
|
|
|
|
|
_MARKER = "/* esphome .sram.text */"
|
|
# Strong assignments (not PROVIDE) so the symbols are always emitted in the
|
|
# ELF; PROVIDE symbols with no references can be garbage-collected.
|
|
_KEEP_LINE = (
|
|
" __esphome_sram_text_start = .; "
|
|
"KEEP(*(.sram.text*)) "
|
|
"__esphome_sram_text_end = .; "
|
|
+ _MARKER + "\n"
|
|
)
|
|
_LN_COPY = re.compile(r"(\.flash_copysection\s*:\s*\{\s*\n)")
|
|
|
|
|
|
def _detect(env):
|
|
prefix = "USE_LIBRETINY_VARIANT_"
|
|
# 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 = frozenset({
|
|
"LN882H",
|
|
"RTL8710B",
|
|
"RTL8720C",
|
|
})
|
|
|
|
|
|
def _inject_keep(host_section):
|
|
"""Return a patcher that injects _KEEP_LINE at the top of `host_section`."""
|
|
def patch(content):
|
|
if _MARKER in content:
|
|
return content
|
|
return host_section.sub(r"\1" + _KEEP_LINE, content, count=1)
|
|
return patch
|
|
|
|
|
|
# Variants not listed here intentionally have no .ld patcher:
|
|
# - RTL8710B: hal.h uses section(".image2.ram.text") which the stock linker
|
|
# already routes into .ram_image2.text (> BD_RAM).
|
|
# - RTL8720C: stock linker already consumes *(.sram.text*).
|
|
# - BK72xx (all): SDK masks FIQ+IRQ around flash writes, IRAM_ATTR is no-op.
|
|
_PATCHERS_BY_VARIANT = {
|
|
"LN882H": (_inject_keep(_LN_COPY),),
|
|
}
|
|
|
|
|
|
def _patchers_for(variant):
|
|
return _PATCHERS_BY_VARIANT.get(variant, ())
|
|
|
|
|
|
def _pre_link(target, source, env):
|
|
build_dir = env.subst("$BUILD_DIR")
|
|
ld_files = [f for f in os.listdir(build_dir) if f.endswith(".ld")]
|
|
patched = 0
|
|
for name in ld_files:
|
|
path = os.path.join(build_dir, name)
|
|
with open(path, "r", encoding="utf-8") as fh:
|
|
original = fh.read()
|
|
if _MARKER in original:
|
|
patched += 1
|
|
continue
|
|
content = original
|
|
for fn in _patchers:
|
|
content = fn(content)
|
|
if content != original:
|
|
with open(path, "w", encoding="utf-8") as fh:
|
|
fh.write(content)
|
|
print("ESPHome: patched {} for IRAM_ATTR placement".format(name))
|
|
patched += 1
|
|
if not patched:
|
|
raise RuntimeError(
|
|
"ESPHome: no .ld in {} was patched for IRAM_ATTR. Update the "
|
|
"regex in patch_linker.py.script (_PATCHERS_BY_VARIANT).".format(
|
|
build_dir
|
|
)
|
|
)
|
|
|
|
|
|
# Substrings matched against demangled names as a fallback on RTL8720C,
|
|
# where we cannot inject __esphome_sram_text_start/end markers.
|
|
_FALLBACK_SUBSTRINGS = ("wake_loop_any_context", "wake_loop_isrsafe",
|
|
"enable_loop_soon_any_context")
|
|
|
|
|
|
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
|
|
nm = env.subst("$NM")
|
|
try:
|
|
out = subprocess.check_output(
|
|
[nm, "--defined-only", "--demangle", elf], text=True
|
|
)
|
|
except (OSError, subprocess.CalledProcessError) as exc:
|
|
print("ESPHome: IRAM_ATTR summary unavailable (nm failed: {})".format(exc))
|
|
return
|
|
start = end = None
|
|
fallback = []
|
|
for line in out.splitlines():
|
|
parts = line.split(maxsplit=2)
|
|
if len(parts) != 3:
|
|
continue
|
|
addr_str, _kind, name = parts
|
|
if name == "__esphome_sram_text_start":
|
|
start = int(addr_str, 16)
|
|
elif name == "__esphome_sram_text_end":
|
|
end = int(addr_str, 16)
|
|
elif "veneer" not in name and any(s in name for s in _FALLBACK_SUBSTRINGS):
|
|
fallback.append(int(addr_str, 16))
|
|
print("ESPHome: IRAM_ATTR placement summary ({}):".format(_variant))
|
|
if start is not None and end is not None:
|
|
print(" .sram.text: {} bytes at 0x{:08x} - 0x{:08x}".format(end - start, start, end))
|
|
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(" no IRAM_ATTR symbols found")
|
|
|
|
|
|
if (_variant := _detect(env)) 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)
|
|
)
|
|
|
|
if _patchers := _patchers_for(_variant):
|
|
# 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", _pre_link)
|
|
|
|
# Post-link summary for every family that reaches this script.
|
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", _post_link)
|