From e2bae546448bf087b4228f06717ec9ca3c8df417 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Oct 2026 18:01:26 -0500 Subject: [PATCH] [libretiny] Keep zero-size allocations from hanging the LN882x --- esphome/components/libretiny/__init__.py | 7 +++++ .../components/libretiny/ln882x_zero_malloc.c | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 esphome/components/libretiny/ln882x_zero_malloc.c diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 8cd7f3e7b62..b6c41e9e5c9 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -32,6 +32,7 @@ from esphome.storage_json import StorageJSON from . import gpio # noqa: F401 from .const import ( COMPONENT_BK72XX, + COMPONENT_LN882X, CONF_GPIO_RECOVER, CONF_LOGLEVEL, CONF_SDK_SILENT, @@ -490,6 +491,12 @@ async def component_to_code(config): # Not enabled on RTL87xx/LN882x — costs more heap than it saves there. cg.add_build_flag("-DconfigSUPPORT_STATIC_ALLOCATION=1") + # LN882x: route FreeRTOS allocations through ln882x_zero_malloc.c, which + # turns a zero-size request into one byte so it cannot trip the SDK's + # malloc-failed hook (an LN_ASSERT spin until the watchdog resets). + if config[CONF_COMPONENT_ID] == COMPONENT_LN882X: + cg.add_build_flag("-Wl,--wrap=pvPortMalloc") + # RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake # required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220) # RTL8720C (ambz2) requires FreeRTOS 10.x so this only applies to RTL8710B diff --git a/esphome/components/libretiny/ln882x_zero_malloc.c b/esphome/components/libretiny/ln882x_zero_malloc.c new file mode 100644 index 00000000000..9e6a1b00902 --- /dev/null +++ b/esphome/components/libretiny/ln882x_zero_malloc.c @@ -0,0 +1,27 @@ +/* + * LN882x: never ask the FreeRTOS heap for zero bytes. + * + * The LN882H SDK builds FreeRTOS with configUSE_MALLOC_FAILED_HOOK, and its + * hook (components/kernel/FreeRTOS/hooks.c) ends in LN_ASSERT(0), which spins + * until the watchdog resets the chip. heap_5 returns NULL for a zero-size + * request, so a harmless malloc(0) runs that hook. LibreTiny's scan handler + * makes exactly that request when a scan finds no networks, which is what + * happens right after an access point drops the device: the chip hangs and + * resets about ten seconds later. + * + * Linked with -Wl,--wrap=pvPortMalloc for LN882x only, so every caller, + * LibreTiny's malloc wrappers included, goes through here. Drop this once + * LibreTiny stops allocating zero entries in its LN882H scan handler. + */ + +#ifdef USE_LN882X + +#include + +void *__real_pvPortMalloc(size_t size); // NOLINT(readability-identifier-naming) + +void *__wrap_pvPortMalloc(size_t size) { // NOLINT(readability-identifier-naming) + return __real_pvPortMalloc(size == 0 ? 1 : size); +} + +#endif // USE_LN882X