mirror of
https://github.com/esphome/esphome.git
synced 2026-06-01 01:19:45 +08:00
[libretiny] Make IRAM_ATTR functional on RTL87xx and LN882H (#15766)
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Has been cancelled
CI / Test components batch (${{ matrix.components }}) (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
Stale / stale (push) Has been cancelled
Lock closed issues and PRs / lock (push) Has been cancelled
Publish Release / Initialize build (push) Has been cancelled
Publish Release / Build and publish to PyPi (push) Has been cancelled
Publish Release / Build ESPHome amd64 (push) Has been cancelled
Publish Release / Build ESPHome arm64 (push) Has been cancelled
Publish Release / Publish ESPHome docker to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome docker to ghcr (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to ghcr (push) Has been cancelled
Publish Release / deploy-ha-addon-repo (push) Has been cancelled
Publish Release / deploy-esphome-schema (push) Has been cancelled
Publish Release / version-notifier (push) Has been cancelled
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Has been cancelled
CI / Test components batch (${{ matrix.components }}) (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
Stale / stale (push) Has been cancelled
Lock closed issues and PRs / lock (push) Has been cancelled
Publish Release / Initialize build (push) Has been cancelled
Publish Release / Build and publish to PyPi (push) Has been cancelled
Publish Release / Build ESPHome amd64 (push) Has been cancelled
Publish Release / Build ESPHome arm64 (push) Has been cancelled
Publish Release / Publish ESPHome docker to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome docker to ghcr (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to ghcr (push) Has been cancelled
Publish Release / deploy-ha-addon-repo (push) Has been cancelled
Publish Release / deploy-esphome-schema (push) Has been cancelled
Publish Release / version-notifier (push) Has been cancelled
This commit is contained in:
@@ -65,3 +65,8 @@ async def to_code(config):
|
|||||||
@pins.PIN_SCHEMA_REGISTRY.register("bk72xx", PIN_SCHEMA)
|
@pins.PIN_SCHEMA_REGISTRY.register("bk72xx", PIN_SCHEMA)
|
||||||
async def pin_to_code(config):
|
async def pin_to_code(config):
|
||||||
return await libretiny.gpio.component_pin_to_code(config)
|
return await libretiny.gpio.component_pin_to_code(config)
|
||||||
|
|
||||||
|
|
||||||
|
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||||
|
def copy_files() -> None:
|
||||||
|
libretiny.copy_files()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
@@ -24,6 +25,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.core.config import BOARD_MAX_LENGTH
|
from esphome.core.config import BOARD_MAX_LENGTH
|
||||||
|
from esphome.helpers import copy_file_if_changed
|
||||||
from esphome.storage_json import StorageJSON
|
from esphome.storage_json import StorageJSON
|
||||||
|
|
||||||
from . import gpio # noqa
|
from . import gpio # noqa
|
||||||
@@ -465,6 +467,11 @@ async def component_to_code(config):
|
|||||||
# it for project source files only. GCC uses the last -O flag.
|
# it for project source files only. GCC uses the last -O flag.
|
||||||
build_src_flags += " -Os"
|
build_src_flags += " -Os"
|
||||||
cg.add_platformio_option("build_src_flags", build_src_flags)
|
cg.add_platformio_option("build_src_flags", build_src_flags)
|
||||||
|
# IRAM_ATTR is a no-op on BK72xx (SDK masks FIQ+IRQ around flash ops).
|
||||||
|
# On other families, patch_linker.py routes .sram.text into the right
|
||||||
|
# RAM-executable output section and prints a post-link placement summary.
|
||||||
|
if FAMILY_COMPONENT[config[CONF_FAMILY]] != COMPONENT_BK72XX:
|
||||||
|
cg.add_platformio_option("extra_scripts", ["pre:patch_linker.py"])
|
||||||
# dummy version code
|
# dummy version code
|
||||||
cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)"))
|
cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)"))
|
||||||
# decrease web server stack size (16k words -> 4k words)
|
# decrease web server stack size (16k words -> 4k words)
|
||||||
@@ -549,3 +556,13 @@ async def component_to_code(config):
|
|||||||
_configure_lwip(config)
|
_configure_lwip(config)
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
|
||||||
|
# Called by writer.py
|
||||||
|
def copy_files() -> None:
|
||||||
|
script_dir = Path(__file__).parent
|
||||||
|
patch_linker_file = script_dir / "patch_linker.py.script"
|
||||||
|
copy_file_if_changed(
|
||||||
|
patch_linker_file,
|
||||||
|
CORE.relative_build_path("patch_linker.py"),
|
||||||
|
)
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ async def to_code(config):
|
|||||||
@pins.PIN_SCHEMA_REGISTRY.register("{COMPONENT_LOWER}", PIN_SCHEMA)
|
@pins.PIN_SCHEMA_REGISTRY.register("{COMPONENT_LOWER}", PIN_SCHEMA)
|
||||||
async def pin_to_code(config):
|
async def pin_to_code(config):
|
||||||
return await libretiny.gpio.component_pin_to_code(config)
|
return await libretiny.gpio.component_pin_to_code(config)
|
||||||
|
|
||||||
|
|
||||||
|
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||||
|
def copy_files() -> None:
|
||||||
|
libretiny.copy_files()
|
||||||
'''
|
'''
|
||||||
|
|
||||||
BASE_CODE_BOARDS = '''
|
BASE_CODE_BOARDS = '''
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
# 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)
|
||||||
@@ -65,3 +65,8 @@ async def to_code(config):
|
|||||||
@pins.PIN_SCHEMA_REGISTRY.register("ln882x", PIN_SCHEMA)
|
@pins.PIN_SCHEMA_REGISTRY.register("ln882x", PIN_SCHEMA)
|
||||||
async def pin_to_code(config):
|
async def pin_to_code(config):
|
||||||
return await libretiny.gpio.component_pin_to_code(config)
|
return await libretiny.gpio.component_pin_to_code(config)
|
||||||
|
|
||||||
|
|
||||||
|
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||||
|
def copy_files() -> None:
|
||||||
|
libretiny.copy_files()
|
||||||
|
|||||||
@@ -65,3 +65,8 @@ async def to_code(config):
|
|||||||
@pins.PIN_SCHEMA_REGISTRY.register("rtl87xx", PIN_SCHEMA)
|
@pins.PIN_SCHEMA_REGISTRY.register("rtl87xx", PIN_SCHEMA)
|
||||||
async def pin_to_code(config):
|
async def pin_to_code(config):
|
||||||
return await libretiny.gpio.component_pin_to_code(config)
|
return await libretiny.gpio.component_pin_to_code(config)
|
||||||
|
|
||||||
|
|
||||||
|
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||||
|
def copy_files() -> None:
|
||||||
|
libretiny.copy_files()
|
||||||
|
|||||||
@@ -337,8 +337,8 @@ class Application {
|
|||||||
/// @see esphome::wake_loop_threadsafe() in wake.h for platform details.
|
/// @see esphome::wake_loop_threadsafe() in wake.h for platform details.
|
||||||
void wake_loop_threadsafe() { esphome::wake_loop_threadsafe(); }
|
void wake_loop_threadsafe() { esphome::wake_loop_threadsafe(); }
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
/// Wake from ISR (ESP32 only).
|
/// Wake from ISR (ESP32 and LibreTiny).
|
||||||
static void IRAM_ATTR wake_loop_isrsafe(BaseType_t *px) { esphome::wake_loop_isrsafe(px); }
|
static void IRAM_ATTR wake_loop_isrsafe(BaseType_t *px) { esphome::wake_loop_isrsafe(px); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,35 @@
|
|||||||
#define IRAM_ATTR __attribute__((noinline, long_call, section(".time_critical")))
|
#define IRAM_ATTR __attribute__((noinline, long_call, section(".time_critical")))
|
||||||
#define PROGMEM
|
#define PROGMEM
|
||||||
|
|
||||||
|
#elif defined(USE_LIBRETINY)
|
||||||
|
|
||||||
|
// IRAM_ATTR places a function in executable RAM so it is callable from an
|
||||||
|
// ISR even while flash is busy (XIP stall, OTA, logger flash write).
|
||||||
|
// Each family uses a section its stock linker already routes to RAM:
|
||||||
|
// RTL8710B → .image2.ram.text, RTL8720C → .sram.text. LN882H is the
|
||||||
|
// exception: its stock linker has no matching glob, so patch_linker.py
|
||||||
|
// injects KEEP(*(.sram.text*)) into .flash_copysection at pre-link.
|
||||||
|
//
|
||||||
|
// BK72xx (all variants) are left as a no-op: their SDK wraps flash
|
||||||
|
// operations in GLOBAL_INT_DISABLE() which masks FIQ + IRQ at the CPU for
|
||||||
|
// the duration of every write, so no ISR fires while flash is stalled and
|
||||||
|
// the race IRAM_ATTR guards against cannot occur. The trade-off is that
|
||||||
|
// interrupts are delayed (not dropped) by up to ~20 ms during a sector
|
||||||
|
// erase, but that is an SDK-level choice and cannot be changed from this
|
||||||
|
// layer.
|
||||||
|
#if defined(USE_BK72XX)
|
||||||
|
#define IRAM_ATTR
|
||||||
|
#elif defined(USE_LIBRETINY_VARIANT_RTL8710B)
|
||||||
|
// Stock linker consumes *(.image2.ram.text*) into .ram_image2.text (> BD_RAM).
|
||||||
|
#define IRAM_ATTR __attribute__((noinline, section(".image2.ram.text")))
|
||||||
|
#else
|
||||||
|
// RTL8720C: stock linker consumes *(.sram.text*) into .ram.code_text.
|
||||||
|
// LN882H: patch_linker.py.script injects *(.sram.text*) into
|
||||||
|
// .flash_copysection (> RAM0 AT> FLASH).
|
||||||
|
#define IRAM_ATTR __attribute__((noinline, section(".sram.text")))
|
||||||
|
#endif
|
||||||
|
#define PROGMEM
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#define IRAM_ATTR
|
#define IRAM_ATTR
|
||||||
@@ -28,8 +57,51 @@
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#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. 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
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
|
/// Returns true when executing inside an interrupt handler.
|
||||||
|
/// always_inline so callers placed in IRAM keep the detection in IRAM.
|
||||||
|
__attribute__((always_inline)) inline bool in_isr_context() {
|
||||||
|
#if defined(USE_ESP32)
|
||||||
|
return xPortInIsrContext() != 0;
|
||||||
|
#elif defined(USE_ESP8266)
|
||||||
|
// ESP8266 has no reliable single-register ISR detection: PS.INTLEVEL is
|
||||||
|
// non-zero both in a real ISR and when user code masks interrupts. The
|
||||||
|
// ESP8266 wake path is context-agnostic (wake_loop_impl uses esp_schedule
|
||||||
|
// which is ISR-safe) so this helper is unused on this platform.
|
||||||
|
return false;
|
||||||
|
#elif defined(USE_RP2040)
|
||||||
|
uint32_t ipsr;
|
||||||
|
__asm__ volatile("mrs %0, ipsr" : "=r"(ipsr));
|
||||||
|
return ipsr != 0;
|
||||||
|
#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;
|
||||||
|
// non-zero means we're in a handler.
|
||||||
|
uint32_t ipsr;
|
||||||
|
__asm__ volatile("mrs %0, ipsr" : "=r"(ipsr));
|
||||||
|
return ipsr != 0;
|
||||||
|
#else
|
||||||
|
// Host and any future platform without an ISR concept.
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void yield();
|
void yield();
|
||||||
uint32_t millis();
|
uint32_t millis();
|
||||||
uint64_t millis_64();
|
uint64_t millis_64();
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ extern "C" {
|
|||||||
extern TaskHandle_t esphome_main_task_handle;
|
extern TaskHandle_t esphome_main_task_handle;
|
||||||
|
|
||||||
/// Wake the main loop task from another FreeRTOS task. NOT ISR-safe.
|
/// Wake the main loop task from another FreeRTOS task. NOT ISR-safe.
|
||||||
static inline void esphome_main_task_notify() {
|
/// always_inline so callers placed in IRAM do not reference a flash-resident copy.
|
||||||
|
__attribute__((always_inline)) static inline void esphome_main_task_notify() {
|
||||||
TaskHandle_t task = esphome_main_task_handle;
|
TaskHandle_t task = esphome_main_task_handle;
|
||||||
if (task != NULL) {
|
if (task != NULL) {
|
||||||
xTaskNotifyGive(task);
|
xTaskNotifyGive(task);
|
||||||
@@ -28,26 +29,14 @@ static inline void esphome_main_task_notify() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Wake the main loop task from an ISR. ISR-safe.
|
/// Wake the main loop task from an ISR. ISR-safe.
|
||||||
static inline void esphome_main_task_notify_from_isr(BaseType_t *px_higher_priority_task_woken) {
|
__attribute__((always_inline)) static inline void esphome_main_task_notify_from_isr(
|
||||||
|
BaseType_t *px_higher_priority_task_woken) {
|
||||||
TaskHandle_t task = esphome_main_task_handle;
|
TaskHandle_t task = esphome_main_task_handle;
|
||||||
if (task != NULL) {
|
if (task != NULL) {
|
||||||
vTaskNotifyGiveFromISR(task, px_higher_priority_task_woken);
|
vTaskNotifyGiveFromISR(task, px_higher_priority_task_woken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
|
||||||
/// Wake the main loop from any context (ISR or task). ESP32-only (needs xPortInIsrContext).
|
|
||||||
static inline void esphome_main_task_notify_any_context() {
|
|
||||||
if (xPortInIsrContext()) {
|
|
||||||
int px_higher_priority_task_woken = 0;
|
|
||||||
esphome_main_task_notify_from_isr(&px_higher_priority_task_woken);
|
|
||||||
portYIELD_FROM_ISR(px_higher_priority_task_woken);
|
|
||||||
} else {
|
|
||||||
esphome_main_task_notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
// === ESP32 — IRAM_ATTR entry points ===
|
// === ESP32 / LibreTiny — IRAM_ATTR entry points ===
|
||||||
#ifdef USE_ESP32
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
void IRAM_ATTR wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken) {
|
void IRAM_ATTR wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken) {
|
||||||
esphome_main_task_notify_from_isr(px_higher_priority_task_woken);
|
esphome_main_task_notify_from_isr(px_higher_priority_task_woken);
|
||||||
}
|
}
|
||||||
void IRAM_ATTR wake_loop_any_context() { esphome_main_task_notify_any_context(); }
|
void IRAM_ATTR wake_loop_any_context() { wake_main_task_any_context(); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// === ESP8266 / RP2040 ===
|
// === ESP8266 / RP2040 ===
|
||||||
|
|||||||
+19
-9
@@ -28,17 +28,27 @@ extern volatile bool g_main_loop_woke;
|
|||||||
// === ESP32 / LibreTiny (FreeRTOS) ===
|
// === ESP32 / LibreTiny (FreeRTOS) ===
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
/// Wake the main loop from any context (ISR or task).
|
||||||
/// IRAM_ATTR entry point — defined in wake.cpp.
|
/// always_inline so callers placed in IRAM keep the whole wake path in IRAM.
|
||||||
void wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken);
|
__attribute__((always_inline)) inline void wake_main_task_any_context() {
|
||||||
/// IRAM_ATTR entry point — defined in wake.cpp.
|
if (in_isr_context()) {
|
||||||
void wake_loop_any_context();
|
BaseType_t px_higher_priority_task_woken = pdFALSE;
|
||||||
|
esphome_main_task_notify_from_isr(&px_higher_priority_task_woken);
|
||||||
|
#ifdef portYIELD_FROM_ISR
|
||||||
|
portYIELD_FROM_ISR(px_higher_priority_task_woken);
|
||||||
#else
|
#else
|
||||||
/// LibreTiny: IRAM_ATTR is not functional and the FreeRTOS port does not
|
// ARM9 FreeRTOS port (BK72xx) does not define portYIELD_FROM_ISR; the IRQ
|
||||||
/// provide vTaskNotifyGiveFromISR/portYIELD_FROM_ISR, so ISR-safe wake
|
// exit sequence performs the context switch if one was requested.
|
||||||
/// is not possible. xTaskNotifyGive is used as the best available option.
|
(void) px_higher_priority_task_woken;
|
||||||
inline void wake_loop_any_context() { esphome_main_task_notify(); }
|
|
||||||
#endif
|
#endif
|
||||||
|
} else {
|
||||||
|
esphome_main_task_notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IRAM_ATTR entry points — defined in wake.cpp.
|
||||||
|
void wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken);
|
||||||
|
void wake_loop_any_context();
|
||||||
|
|
||||||
inline void wake_loop_threadsafe() { esphome_main_task_notify(); }
|
inline void wake_loop_threadsafe() { esphome_main_task_notify(); }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user