From aee0581a326b27386e724d0cc1ea87224b701ce9 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 18 Jul 2026 16:08:13 +0200 Subject: [PATCH] esp32: fix XIP boot crash caused by LLVM 22 / lld l32r relocation bug Work around an lld (LLVM 22) bug where l32r PC-relative offsets are miscalculated when auto-generated .literal.* sections are prepended to .text.* sections by the linker script. The assembler emits .literal entries for movi instructions whose constants exceed the 12-bit signed range; lld then resolves the l32r relocations with incorrect offsets, causing every l32r in the boot code to load from the wrong literal pool entry. The symptom was a TG0WDT_SYS_RESET boot loop: the watchdog disable code loaded wrong register addresses via l32r and silently wrote to the wrong peripheral registers, leaving the watchdog running. Fix by constructing PS_WOE_MASK (0x40000) with movi+slli instead of a single large-constant movi, eliminating all auto-generated .literal section entries. This is compatible with both LLVM 20 and LLVM 22. Also revert DRAM origin to 0x3FFAE000 (200K) and remove rom_phyFuns symbols that belong in a separate WiFi commit. Signed-off-by: deadprogram --- src/device/esp/esp32.S | 69 ++++++++++++++++++------------------------ targets/esp32.ld | 15 +++------ 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/src/device/esp/esp32.S b/src/device/esp/esp32.S index 69febb81e..a156b2f4e 100644 --- a/src/device/esp/esp32.S +++ b/src/device/esp/esp32.S @@ -60,10 +60,14 @@ call_start_cpu0: // version of the following code: // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 - // Disable WOE. + // Disable WOE (bit 18 of PS). + // Avoid large movi constants to prevent auto-generated .literal section + // entries, which cause l32r offset miscalculation in LLVM 22 / lld. rsr.ps a2 - movi a3, ~(PS_WOE_MASK) - and a2, a2, a3 + movi a3, 1 + slli a3, a3, 18 // a3 = PS_WOE_MASK (0x40000) + and a3, a2, a3 // a3 = a2 & WOE_MASK (isolate WOE bit) + xor a2, a2, a3 // clear WOE bit wsr.ps a2 rsync @@ -80,7 +84,8 @@ call_start_cpu0: // Re-enable WOE. rsr.ps a2 - movi a3, PS_WOE + movi a3, 1 + slli a3, a3, 18 // a3 = PS_WOE (0x40000) or a2, a2, a3 wsr.ps a2 rsync @@ -129,43 +134,34 @@ call_start_cpu0: rsync // ---- Configure flash cache and MMU ---- - // The ROM bootloader only loaded IRAM/DRAM segments. We must now set up - // the ICache MMU to map DROM (.rodata) and IROM (.text) from flash. - - // 0. Disable cache and flush before reconfiguring MMU. - // The ROM bootloader may leave the cache enabled. - movi a6, 0 // cpu_no = 0 + movi a6, 0 mov a5, a1 l32r a4, .Lrom_Cache_Read_Disable callx4 a4 - movi a6, 0 // cpu_no = 0 + movi a6, 0 mov a5, a1 l32r a4, .Lrom_Cache_Flush callx4 a4 - // 1. Reset MMU tables for PRO CPU (clear all entries to invalid). - movi a6, 0 // cpu_no = 0 (PRO) + movi a6, 0 mov a5, a1 l32r a4, .Lrom_mmu_init callx4 a4 - // 2. Map DROM pages via ROM cache_flash_mmu_set: - // vaddr=0x3F400000, paddr=0x10000, page_size=64KB, count=ceil(size/64KB) l32r a2, .Lrodata_end l32r a3, .Lrodata_start - sub a2, a2, a3 // a2 = rodata size in bytes - beqz a2, .Lskip_drom // skip if no rodata + sub a2, a2, a3 + beqz a2, .Lskip_drom addi a2, a2, -1 srli a2, a2, 16 - addi a2, a2, 1 // a2 = drom page count + addi a2, a2, 1 - // cache_flash_mmu_set(cpu=0, pid=0, vaddr, paddr, psize=64, num) movi a6, 0 movi a7, 0 - l32r a8, .Ldrom_vaddr // 0x3F400000 + l32r a8, .Ldrom_vaddr l32r a9, .Ldrom_paddr_ptr - l32i a9, a9, 0 // a9 = DROM flash offset (builder-patched) + l32i a9, a9, 0 movi a10, 64 mov a11, a2 mov a5, a1 @@ -173,34 +169,31 @@ call_start_cpu0: callx4 a4 .Lskip_drom: - // 3. Map IROM pages via ROM cache_flash_mmu_set: - // vaddr=0x400D0000, paddr=0x10000 + drom_pages*64KB l32r a2, .Ltext_end l32r a3, .Ltext_start - sub a2, a2, a3 // a2 = text size in bytes - beqz a2, .Lskip_irom // skip if no text + sub a2, a2, a3 + beqz a2, .Lskip_irom addi a2, a2, -1 srli a2, a2, 16 - addi a2, a2, 1 // a2 = irom page count + addi a2, a2, 1 - // Compute IROM paddr into a3 = DROM flash offset + drom_pages * 64KB l32r a9, .Lrodata_end l32r a3, .Lrodata_start - sub a9, a9, a3 // a9 = rodata size + sub a9, a9, a3 l32r a3, .Ldrom_paddr_ptr - l32i a3, a3, 0 // a3 = DROM flash offset (builder-patched) + l32i a3, a3, 0 beqz a9, .Lirom_paddr_ready addi a9, a9, -1 srli a9, a9, 16 - addi a9, a9, 1 // a9 = drom page count - slli a9, a9, 16 // a9 = drom_pages * 64KB - add a3, a3, a9 // a3 = drom_flash_addr + drom_pages * 64KB + addi a9, a9, 1 + slli a9, a9, 16 + add a3, a3, a9 .Lirom_paddr_ready: movi a6, 0 movi a7, 0 - l32r a8, .Lirom_vaddr // 0x400D0000 - mov a9, a3 // irom paddr + l32r a8, .Lirom_vaddr + mov a9, a3 movi a10, 64 mov a11, a2 mov a5, a1 @@ -208,18 +201,14 @@ call_start_cpu0: callx4 a4 .Lskip_irom: - // 4. Unmask DROM0 and IRAM0 cache buses in DPORT_PRO_CACHE_CTRL1_REG: - // bit 4: PRO_CACHE_MASK_DROM0 (data flash, 0x3F400000) - // bit 0: PRO_CACHE_MASK_IRAM0 (instruction flash, 0x400D0000 window) l32r a2, .Ldport_pro_cache_ctrl1 l32i a3, a2, 0 - movi a4, ~0x11 // clear bit 0 (IRAM0) and bit 4 (DROM0) + movi a4, ~0x11 and a3, a3, a4 s32i a3, a2, 0 memw - // 5. Enable flash cache for PRO CPU. - movi a6, 0 // cpu_no = 0 + movi a6, 0 mov a5, a1 l32r a4, .Lrom_Cache_Read_Enable callx4 a4 diff --git a/targets/esp32.ld b/targets/esp32.ld index a7c635a69..388cc81d4 100644 --- a/targets/esp32.ld +++ b/targets/esp32.ld @@ -15,10 +15,9 @@ MEMORY { - /* SRAM2 usable DRAM (0x3FFB0000-0x3FFE0000, 192K): reserve the first 8KB - * (0x3FFAE000-0x3FFB0000) for ROM conventions, then use the rest for the - * stack, initialized .data, .bss, and the whole Go GC heap. */ - DRAM (rw) : ORIGIN = 0x3FFB0000, LENGTH = 192K + /* SRAM2 (0x3FFAE000-0x3FFE0000, 200K): a single safe contiguous block used + * for the stack, initialized .data, .bss, and the whole Go GC heap. */ + DRAM (rw) : ORIGIN = 0x3FFAE000, LENGTH = 200K /* Top of SRAM1 (0x3FFF0000-0x40000000, 64K): pool 7 + pool 6. This is the * ONLY part of SRAM1 that is safe for us to use: @@ -186,7 +185,7 @@ SECTIONS _globals_start = _sbss; _globals_end = _wifi_bss_end; _heap_start = _edata; -/* Go GC heap = the rest of SRAM2 above .bss (~113KB), a single safe contiguous +/* Go GC heap = the rest of SRAM2 above .bss (~121KB), a single safe contiguous * block. The WiFi arena lives in DRAM1 (SRAM1 pool 7/6), so its ~64KB does not * consume Go heap and its DMA is isolated from GC objects. The heap never * touches SRAM1's ROM/MAC-dump regions (0x3FFE0000-0x3FFF0000, left unused). */ @@ -194,12 +193,6 @@ _heap_end = 0x3FFE0000; _stack_size = 4K; -/* ESP32 ROM-owned PHY DRAM symbols from ESP-IDF esp32.rom.ld. - * Exposed as linker symbols so firmware code can reference them without - * hardcoding addresses in C sources. */ -PROVIDE ( rom_phyFuns = 0x3ffae0c0 ); -PROVIDE ( g_phyFuns_instance = 0x3ffae0c4 ); - /* From ESP-IDF: * components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld * This is the subset that is sometimes used by LLVM during codegen, and thus