soc: espressif: load all LP-SRAM data sections under MCUboot - #117359
soc: espressif: load all LP-SRAM data sections under MCUboot#117359mdrinnan wants to merge 1 commit into
Conversation
The .metadata load header emitted for MCUboot builds describes only two of the four loadable LP-SRAM sections. Its LP_DATA descriptor points at .rtc.data, which collects RTC_DATA_ATTR / RTC_RODATA_ATTR and is empty in any application that does not use those attributes, while .rtc.force_fast and .rtc.force_slow are described by no descriptor at all. Both are present in the flashed image but never copied, so the zero-initialised ESP-IDF statics they hold come up as whatever LP-SRAM last contained. On ESP32-C6 that leaves s_sleep_sub_mode_ref_cnt[] (sleep_modes.c) with garbage reference counts. get_sleep_flags() then reports the ADC/TSEN monitor, ultra-low and RTC-fast-uses-XTAL sub-modes as active, so esp_deep_sleep_start() keeps the LP peripheral domain powered and skips sar_periph_ctrl_power_disable(). On a Seeed XIAO ESP32-C6 this measured at roughly eight times the deep-sleep current of the same application booted without MCUboot. Absolute figures depend on the board and the measurement setup; see the pull request for the numbers and caveats. Point the LP_DATA descriptor at .rtc.force_fast through .rtc.force_slow, and move .rtc.force_slow ahead of the NOLOAD .rtc.bss / .rtc_noinit sections so the four loadable LP sections are adjacent in both VMA and LMA. The reorder is required, not cosmetic: correcting the descriptor alone leaves .rtc.force_slow displaced by any non-empty NOLOAD section, so the loader writes it to the wrong address and overwrites .rtc.bss / .rtc_noinit in the process. Applies to the SoCs whose LP sections all share one memory region. esp32, esp32s2 and esp32s3 split them across two or three regions and need a separate change; see the pull request for details. Signed-off-by: Martin Drinnan <mrdrinnan@gmail.com>
|
Hello @mdrinnan, and thank you very much for your first Pull Request (PR) to the Zephyr Project! All PRs must pass our Continuous Integration (CI) pipeline before merging. When the pipeline run for your PR completes, you are expected to investigate the results, fix any errors, and update your PR for a fresh round of review. Since this is your first contribution, a project community member must manually approve your CI run (this helps us avoid abuse of our CI system). A bot should assign some reviewers who can start the run for you soon. As a heads-up, you will probably have to update your PR to fix CI issues and address review feedback in order to get it ready for merge. Some key rules for updating your PR are:
Also, see:
If you are stuck or need help, you can join us on Discord and ask questions; many community members try to help new contributors there 😊. Try to pick a Discord channel that is associated with the technical details of your request. If you're not sure, use the #general channel. |
b2ed368 to
fe2f5c5
Compare
fe2f5c5 to
f5ee7f5
Compare
|
@mdrinnan very nice catch, thanks for this! |
.rtc.text : ALIGN_WITH_INPUT
.rtc.force_fast : ALIGN_WITH_INPUT
.rtc.data : ALIGN_WITH_INPUT
.rtc.force_slow : ALIGN_WITH_INPUT` |
|
Since runtime breaking is silent, perhaps we could add an ASSERT in all those linkers like: ASSERT((LOADADDR(.rtc.force_slow) + SIZEOF(.rtc.force_slow) - LOADADDR(.rtc.force_fast)) ==
(ADDR(.rtc.force_slow) + SIZEOF(.rtc.force_slow) - ADDR(.rtc.force_fast)),
"LP-SRAM loadable sections must be contiguous with equal VMA and LMA offsets") |
Bug Introduction
An ESP32-C6 application chainloaded by MCUboot draws 7x more deep-sleep current than the same application booted directly.
This fix is for SoCs whose LP sections all live in one memory region — esp32c3, esp32c5, esp32c6, esp32h2, esp32p4.
A reproducer is available here: https://github.com/mdrinnan/zephyr-esp32-lp-sram-repro
Root cause
LP-SRAM sections are not all loaded. The Espressif SoC linker scripts emit an
esp_image_load_header_t(.metadata) that the MCUboot Espressif port uses to copy the application into RAM. Its two LP descriptors cover only two of the four loadable LP-SRAM sections:.rtc.text.rtc.force_fastRTC_FAST_ATTR.rtc.dataRTC_DATA_ATTR,RTC_RODATA_ATTR.rtc.force_slowRTC_SLOW_ATTR.rtc.datais empty in any application not using those attributes, so LP_DATA is typically a zero-length descriptor while the sections that do need loading are described by nothing. The bytes are in the flashed image, they are simply never copied. Direct boot is unaffected — the ROM loader uses the esptool segment list — which is why this presents as "MCUboot costs current".On ESP32-C6 the uninitialised statics include
s_sleep_sub_mode_ref_cnt[]fromesp_hw_support/sleep_modes.c. Garbage counts makeget_sleep_flags()report the ADC/TSEN monitor, ultra-low and RTC-fast-uses-XTAL sub-modes active, soesp_deep_sleep_start()keeps the LP peripheral domain powered and skipssar_periph_ctrl_power_disable(). This is the ESP32-C6 instance of discussion #106900, which reports the same MCUboot-dependent regression on ESP32-S3.Fix
Two changes per linker script, both required:
.rtc.force_fastthrough.rtc.force_slowinstead of the usually-empty.rtc.data..rtc.force_slowmoves ahead of the NOLOAD.rtc.bss/.rtc_noinitsections, so the four loadable LP sections are adjacent in both VMA and LMA.Change 2 is not cosmetic. With only change 1, a non-empty
.rtc_noinitor.rtc.bsspushes.rtc.force_slowpast the descriptor's VMA span while the LMAs stay contiguous, so the loader writes.rtc.force_slowto the wrong address and overwrites.rtc.bss/.rtc_noinit.Putting the retained data in LP_DATA rather than LP_IRAM is deliberate: the MCUboot port loads LP_DATA only when
reset_reason != RESET_REASON_CORE_DEEP_SLEEPand loads LP_IRAM unconditionally, so retained sections must keep the conditional path.Scope
Fixed: the SoCs whose LP sections all live in one memory region — esp32c3, esp32c5, esp32c6, esp32h2, esp32p4.
Deliberately not fixed:
esp32s3rtc_iram_seg/rtc_slow_seg. Both descriptors fit, but.rtc.force_fastwould land in the unconditionally-loaded LP_IRAM descriptor and lose deep-sleep retention. Needs a decision on intended behaviour.esp32,esp32s2_reserved[4], but requires a coordinated MCUboot change.esp32c2and thedefault_appcpu.ldvariants emit no LP descriptors and are unaffected.Testing
Build (hardware-free, all five targets). A test application populates every LP section using only section attributes, and a script reads the built ELF's
.metadataand asserts that each loadable LP section is covered by exactly one descriptor with VMA offset equal to LMA offset — that second assertion is what catches the interleaved-NOLOAD case; a coverage-only check passes the incomplete fix. Built--sysbuildwithSB_CONFIG_BOOTLOADER_MCUBOOT=yonesp32c3_devkitm,esp32c5_devkitc,esp32c6_devkitc,esp32h2_devkitm,esp32p4_function_ev_boardagainstmainatb6a5e6e8aa9: before 5/5 fail (.rtc.force_fastand.rtc.force_slowcovered by 0 descriptors on every target), after 5/5 pass.Hardware (ESP32-C6). Seeed XIAO ESP32-C6, PPK2 into the BAT pad at 3700 mV, time-averaged over deep-sleep gaps. Absolutes are board-specific; the relative change is the point. No application-side mitigation compiled in, so the delta is attributable to the linker fix alone.
A reproducer is available here: https://github.com/mdrinnan/zephyr-esp32-lp-sram-repro