From 0c17b918fb43af31e4b3af0d1b53b3da2c45d2ea Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 10 Jul 2026 19:21:30 +0200 Subject: [PATCH] esp32: implement flash XIP (execute-in-place) support Add full flash XIP support for ESP32, enabling code and read-only data to execute/load directly from flash via the MMU cache rather than consuming precious SRAM. This increases available RAM from ~328KB to effectively unlimited for code/rodata, while keeping ~121KB for the Go heap. Changes: - src/device/esp/esp32.S: Add MMU initialization in call_start_cpu0 - Call ROM bootloader mmu_init() and cache_flash_mmu_set() to map DROM/IROM - Enable flash cache via ROM Cache_Read_Enable() - Fix tinygo_scanCurrentStack to spill all register windows for GC - targets/esp32-interrupts.S: Add exception diagnostics - targets/esp32.ld: Major linker script restructure for XIP - Add DROM (4MB @ 0x3F400000) and IROM (4MB @ 0x400D0000) regions - Move .rodata to DROM, main .text to IROM (both flash-mapped) - Keep boot code, vectors, and WiFi blob IRAM sections in SRAM0 - Create WiFi arena in SRAM1 pool 7/6 (64KB @ 0x3FFF0000) - Move .bss and heap to SRAM2 (200KB @ 0x3FFAE000), avoiding ROM/MAC regions - Add _drom_flash_addr variable (patched by builder with flash offset) - targets/esp32.json: Add linker wrap flags for malloc/free and WiFi functions Signed-off-by: deadprogram --- src/device/esp/esp32.S | 243 ++++++++++++++++++++++++++++++++++- targets/esp32-interrupts.S | 71 ++++++++++- targets/esp32.json | 7 + targets/esp32.ld | 255 ++++++++++++++++++++++++++----------- 4 files changed, 496 insertions(+), 80 deletions(-) diff --git a/src/device/esp/esp32.S b/src/device/esp/esp32.S index 1179a2daa..69febb81e 100644 --- a/src/device/esp/esp32.S +++ b/src/device/esp/esp32.S @@ -10,6 +10,49 @@ .section .text.call_start_cpu0 1: .long _stack_top +.Lmain_addr: + .long main +.Lrom_mmu_init: + .long 0x400095a4 // mmu_init(int cpu_no) +.Lrom_cache_flash_mmu_set: + .long 0x400095e0 // cache_flash_mmu_set(cpu, pid, vaddr, paddr, pgsz, pgcnt) +.Lrom_Cache_Read_Enable: + .long 0x40009a84 // Cache_Read_Enable(int cpu_no) +.Lrom_Cache_Read_Disable: + .long 0x40009ab8 // Cache_Read_Disable(int cpu_no) +.Lrom_Cache_Flush: + .long 0x40009a14 // Cache_Flush(int cpu_no) +.Lrodata_start: + .long _rodata_start +.Lrodata_end: + .long _rodata_end +.Ltext_start: + .long _text_start +.Ltext_end: + .long _text_end +.Ldport_pro_cache_ctrl1: + .long 0x3FF00044 // DPORT_PRO_CACHE_CTRL1_REG +.Ldrom_paddr_ptr: + .long _drom_flash_addr // pointer to builder-patched DROM flash offset +.Ldrom_vaddr: + .long 0x3F400000 // DROM virtual base address +.Lirom_vaddr: + .long 0x400D0000 // IROM virtual base address +.Lmmu_table_base: + .long 0x3FF10000 // PRO CPU Flash MMU table +.Lrtc_wdt_protect: + .long 0x3FF480A4 // RTC_CNTL_WDTWPROTECT_REG +.Lrtc_wdt_key: + .long 0x50D83AA1 // WDT write-protect key +.Lrtc_wdt_config0: + .long 0x3FF4808C // RTC_CNTL_WDTCONFIG0_REG +.Ltimg0_wdt_protect: + .long 0x3FF5F064 // TIMG0_WDTWPROTECT_REG +.Ltimg0_wdt_config0: + .long 0x3FF5F048 // TIMG0_WDTCONFIG0_REG +.Lvector_table: + .long _vector_table + .global call_start_cpu0 call_start_cpu0: // We need to set the stack pointer to a different value. This is somewhat @@ -47,11 +90,203 @@ call_start_cpu0: wsr.cpenable a2 rsync - // Jump to the runtime start function written in Go. - call4 main + // Disable the RTC and TIMG0 watchdogs before configuring the flash cache. + // The ROM bootloader leaves them running; a fault during cache setup would + // otherwise reset the chip. The Go runtime re-disables them once it starts. + l32r a2, .Lrtc_wdt_protect + l32r a3, .Lrtc_wdt_key + s32i a3, a2, 0 // unlock WDT write-protect + memw + l32r a2, .Lrtc_wdt_config0 + movi a3, 0 + s32i a3, a2, 0 // disable WDT (write 0 to config0) + memw + // Disable TG0 WDT (Timer Group 0 Main Watchdog). + // TIMG0_WDTWPROTECT_REG = 0x3FF5F064, TIMG0_WDTCONFIG0_REG = 0x3FF5F048 + l32r a2, .Ltimg0_wdt_protect + l32r a3, .Lrtc_wdt_key // same unlock key 0x50D83AA1 + s32i a3, a2, 0 + memw + l32r a2, .Ltimg0_wdt_config0 + movi a3, 0 + s32i a3, a2, 0 + memw + + // Set VECBASE to our vector table. Must happen before any callx4 so that + // register-window overflow exceptions route to our handlers. + l32r a2, .Lvector_table + wsr.vecbase a2 + rsync + + // Clear PS.EXCM so window overflow exceptions work properly. + rsr.ps a2 + movi a3, ~0x1F + and a2, a2, a3 + movi a3, 0x20 // PS.UM = 1 + or a2, a2, a3 + wsr.ps a2 + 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 + mov a5, a1 + l32r a4, .Lrom_Cache_Read_Disable + callx4 a4 + + movi a6, 0 // cpu_no = 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) + 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 + addi a2, a2, -1 + srli a2, a2, 16 + addi a2, a2, 1 // a2 = drom page count + + // 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 a9, .Ldrom_paddr_ptr + l32i a9, a9, 0 // a9 = DROM flash offset (builder-patched) + movi a10, 64 + mov a11, a2 + mov a5, a1 + l32r a4, .Lrom_cache_flash_mmu_set + 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 + addi a2, a2, -1 + srli a2, a2, 16 + addi a2, a2, 1 // a2 = irom page count + + // 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 + l32r a3, .Ldrom_paddr_ptr + l32i a3, a3, 0 // a3 = DROM flash offset (builder-patched) + 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 +.Lirom_paddr_ready: + + movi a6, 0 + movi a7, 0 + l32r a8, .Lirom_vaddr // 0x400D0000 + mov a9, a3 // irom paddr + movi a10, 64 + mov a11, a2 + mov a5, a1 + l32r a4, .Lrom_cache_flash_mmu_set + 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) + and a3, a3, a4 + s32i a3, a2, 0 + memw + + // 5. Enable flash cache for PRO CPU. + movi a6, 0 // cpu_no = 0 + mov a5, a1 + l32r a4, .Lrom_Cache_Read_Enable + callx4 a4 + + isync + + // ---- Jump to main (in IROM/flash, now accessible) ---- + mov a5, a1 + l32r a4, .Lmain_addr + callx4 a4 + + // If main returns, loop forever. +1: j 1b + +// ----------------------------------------------------------------------- +// tinygo_scanCurrentStack — Spill all Xtensa register windows to the +// stack, then call tinygo_scanstack(sp) so the conservative GC can +// discover live heap pointers that are currently in physical registers. +// +// On RISC-V / ARM the equivalent function pushes callee-saved registers +// before the call. On Xtensa windowed ABI the same effect is achieved +// by forcing hardware window-overflow for every occupied pane: each +// overflow saves the four registers in that pane to the stack frame +// pointed to by the pane's a1 (sp). After all panes are flushed, a +// scan from the current sp to stackTop covers every live value. +// +// Without this spill the conservative GC misses heap pointers held only +// in physical registers, frees live objects, and later crashes jumping +// through a freed/garbage function pointer (e.g. a goroutine trampoline). +// ----------------------------------------------------------------------- .section .text.tinygo_scanCurrentStack + .global tinygo_scanCurrentStack tinygo_scanCurrentStack: - // TODO: save callee saved registers on the stack - j tinygo_scanstack + entry a1, 48 + + // Disable interrupts while flushing register windows. + rsr a4, PS + s32i a4, a1, 0 // save PS for later restore + rsil a4, 3 // XCHAL_EXCM_LEVEL + + // Flush all register windows using recursive call4. + // For NAREG=64 (16 panes), 15 recursive levels cover all panes + // except the current one (which is kept active). + movi a6, 15 + call4 .Lscan_spill + + // Restore interrupts. + l32i a4, a1, 0 + wsr.ps a4 + rsync + + // Pass current sp to tinygo_scanstack. + // call4 maps caller's a5→callee's a1 (stack ptr for callee's entry) + // and caller's a6→callee's a2 (first argument = sp). + mov a5, a1 // callee's a1 = valid stack pointer + mov a6, a1 // callee's a2 = sp argument + call4 tinygo_scanstack + + retw + + .balign 4 +.Lscan_spill: + entry a1, 16 + beqz a2, .Lscan_spill_done + addi a2, a2, -1 + mov a6, a2 + call4 .Lscan_spill +.Lscan_spill_done: + retw diff --git a/targets/esp32-interrupts.S b/targets/esp32-interrupts.S index f172b7d09..e215b05f9 100644 --- a/targets/esp32-interrupts.S +++ b/targets/esp32-interrupts.S @@ -364,8 +364,75 @@ _handle_level1: rfe // ----------------------------------------------------------------------- -// Exception halt: infinite loop for unhandled exceptions. +// Exception halt: dump EXCCAUSE and EPC1 over UART0, then loop forever. +// Helps diagnose unhandled CPU exceptions (crashes) which would otherwise +// look like a silent freeze (especially with the watchdogs disabled). // ----------------------------------------------------------------------- +.Lexc_uart_fifo: + .long 0x3FF40000 // UART0 FIFO +.Lexc_uart_stat: + .long 0x3FF4001C // UART0 STATUS (TXFIFO_CNT bits 23:16) .Lexception_halt: + // Emit marker "\nEXC " then EXCCAUSE (8 hex), ' ', EPC1 (8 hex), '\n'. + l32r a4, .Lexc_uart_fifo + l32r a5, .Lexc_uart_stat + movi a6, 10 // '\n' + call0 .Lexc_putc + movi a6, 'E' + call0 .Lexc_putc + movi a6, 'X' + call0 .Lexc_putc + movi a6, 'C' + call0 .Lexc_putc + movi a6, ' ' + call0 .Lexc_putc + rsr a7, EXCCAUSE + call0 .Lexc_puthex + movi a6, ' ' + call0 .Lexc_putc + rsr a7, EPC1 + call0 .Lexc_puthex + movi a6, ' ' + call0 .Lexc_putc + rsr a7, EXCVADDR + call0 .Lexc_puthex + movi a6, ' ' + call0 .Lexc_putc + l32i a7, a1, 0 // saved a0 (return address of faulting frame) + call0 .Lexc_puthex + movi a6, 10 // '\n' + call0 .Lexc_putc +1: waiti 0 - j .Lexception_halt + j 1b + +// Emit char in a6 (clobbers a3). a4=fifo, a5=status. Returns via a0 (call0). + .align 4 +.Lexc_putc: + s32i a6, a4, 0 +2: l32i a3, a5, 0 + extui a3, a3, 16, 8 + bnez a3, 2b + ret + +// Emit 32-bit value in a7 as 8 hex chars (clobbers a6,a8,a9,a3). +// Uses a10 as return save since call0 to .Lexc_putc clobbers a0. + .align 4 +.Lexc_puthex: + mov a10, a0 // save return address + movi a9, 28 // shift +3: + ssr a9 + srl a8, a7 + extui a8, a8, 0, 4 // nibble + movi a6, 10 + bge a8, a6, 4f + addi a6, a8, '0' + j 5f +4: addi a6, a8, 'a' - 10 +5: call0 .Lexc_putc + addi a9, a9, -4 + bgez a9, 3b + mov a0, a10 // restore return address + ret + diff --git a/targets/esp32.json b/targets/esp32.json index 73215a96a..fcc969e0e 100644 --- a/targets/esp32.json +++ b/targets/esp32.json @@ -7,6 +7,13 @@ "scheduler": "tasks", "serial": "uart", "linker": "ld.lld", + "ldflags": [ + "--wrap=malloc", + "--wrap=calloc", + "--wrap=free", + "--wrap=realloc", + "--wrap=ppCheckTxConnTrafficIdle" + ], "default-stack-size": 8192, "rtlib": "compiler-rt", "libc": "picolibc", diff --git a/targets/esp32.ld b/targets/esp32.ld index 2d71a162d..388cc81d4 100644 --- a/targets/esp32.ld +++ b/targets/esp32.ld @@ -1,34 +1,141 @@ -/* Linker script for the ESP32 */ +/* Linker script for the ESP32 (flash XIP) + * + * The ESP32 has 520KB of internal SRAM: + * - SRAM0 (192KB): 0x40070000-0x4009FFFF — upper 64KB used by ICache when + * flash XIP is active. Usable IRAM: 0x40080000 (128KB). + * - SRAM1 (128KB): 0x3FFE0000-0x3FFFFFFF — data only. + * - SRAM2 (200KB): 0x3FFAE000-0x3FFDFFFF — data only. + * + * Flash is memory-mapped via the cache: + * - DROM (read-only data): 0x3F400000, up to 4MB (64KB pages via MMU) + * - IROM (executable code): 0x400D0000, up to 4MB (64KB pages via MMU) + * The startup code in IRAM configures the MMU to map flash pages. + * The TinyGo builder places DROM/IROM content at page-aligned flash offsets. + */ MEMORY { - /* Data RAM. Allows byte access. - * There are various data RAM regions: - * SRAM2: 0x3FFA_E000..0x3FFD_FFFF (72 + 128 = 200K) - * SRAM1: 0x3FFE_0000..0x3FFF_FFFF (128K) - * This gives us 328K of contiguous RAM, which is the largest span possible. - * SRAM1 has other addresses as well but the datasheet seems to indicate - * these are aliases. - */ - DRAM (rw) : ORIGIN = 0x3FFAE000, LENGTH = 200K + 128K /* Internal SRAM 1 + 2 */ + /* 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 - /* Instruction RAM. */ - IRAM (x) : ORIGIN = 0x40080000, LENGTH = 128K /* Internal SRAM 0 */ + /* Top of SRAM1 (0x3FFF0000-0x40000000, 64K): pool 7 + pool 6. This is the + * ONLY part of SRAM1 that is safe for us to use: + * - 0x3FFE0000-0x3FFE0440 : ROM PRO data (ROM writes it) — avoid + * - 0x3FFE0440-0x3FFE8000 : ROM boot-stack area — NOT free during WiFi; + * putting the arena here corrupts DMA and breaks + * association ("auth expired") — avoid + * - 0x3FFE8000-0x3FFF0000 : pool 8, "used for MAC dump" by the WiFi MAC — + * corrupts anything placed here — avoid + * - 0x3FFF0000-0x40000000 : pool 7/6 — safe (verified: arena + .bss both + * work here). Used for the WiFi arena. + * The arena (DMA buffers) owns this whole 64K block; .bss stays in SRAM2. */ + DRAM1 (rw) : ORIGIN = 0x3FFF0000, LENGTH = 64K + + IRAM (x) : ORIGIN = 0x40080000, LENGTH = 128K /* SRAM0 (usable portion) */ + + DROM (r) : ORIGIN = 0x3F400000, LENGTH = 4M /* Flash data bus (page-aligned) */ + IROM (rx) : ORIGIN = 0x400D0000, LENGTH = 4M /* Flash instruction bus (page-aligned) */ } -/* The entry point. It is set in the image flashed to the chip, so must be - * defined. - */ ENTRY(call_start_cpu0) SECTIONS { - /* Constant literals and code. Loaded into IRAM for now. Eventually, most - * code should be executed directly from flash. - * Note that literals must be before code for the l32r instruction to work. + /* Constant global variables, stored in flash (DROM). + * The builder places this at a page-aligned flash offset. */ - .text : ALIGN(4) + .rodata : ALIGN(4) { + _rodata_start = ABSOLUTE(.); + *(.rodata*) + . = ALIGN (4); + _rodata_end = ABSOLUTE(.); + } >DROM + + /* Put the stack at the bottom of DRAM, so that the application will + * crash on stack overflow instead of silently corrupting memory. + */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += _stack_size; + _stack_top = .; + } >DRAM + + /* Global variables that are mutable and zero-initialized. Kept in SRAM2 + * (DRAM): the WiFi blob's .bss holds structures the MAC/DMA touches, and + * SRAM1 has no room for both .bss and an adequately-sized arena in its only + * safe region (pool 7/6, 64K) — so .bss stays in SRAM2 and the arena owns + * the SRAM1 block. */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN (4); + _sbss = ABSOLUTE(.); + *(.bss .bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >DRAM + + /* Reserved zero-initialized storage for WiFi blob global variables. + * On ESP32-S3 these live at fixed ROM DRAM addresses; on ESP32 the blob + * doesn't export them so we allocate space here. */ + .wifi_bss (NOLOAD) : ALIGN(4) + { + _wifi_bss_start = ABSOLUTE(.); + /* Function pointers patched by netif.c (6 × 4 bytes) */ + PROVIDE(g_config_func = ABSOLUTE(.)); . += 4; + PROVIDE(g_net80211_tx_func = ABSOLUTE(.)); . += 4; + PROVIDE(g_timer_func = ABSOLUTE(.)); . += 4; + PROVIDE(s_michael_mic_failure_cb = ABSOLUTE(.)); . += 4; + PROVIDE(g_tx_done_cb_func = ABSOLUTE(.)); . += 4; + PROVIDE(s_encap_amsdu_func = ABSOLUTE(.)); . += 4; + /* Pointer-sized globals used by pp/lmac (3 × 4 bytes) */ + PROVIDE(pp_wdev_funcs = ABSOLUTE(.)); . += 4; + PROVIDE(our_tx_eb = ABSOLUTE(.)); . += 4; + PROVIDE(our_wait_eb = ABSOLUTE(.)); . += 4; + PROVIDE(lmacConfMib_ptr = ABSOLUTE(.)); . += 4; + . = ALIGN(4); + _wifi_bss_end = ABSOLUTE(.); + } >DRAM + + /* WiFi blob arena (DMA buffers): owns all of DRAM1 (SRAM1 pool 7/6, + * 0x3FFF0000-0x40000000, 64K) — the only SRAM1 region safe from ROM/MAC + * writes. Outside the Go GC heap; DMA-capable; isolated from GC objects. */ + .arena (NOLOAD) : ALIGN(16) + { + _arena_start = ABSOLUTE(.); + } >DRAM1 + _arena_end = ORIGIN(DRAM1) + LENGTH(DRAM1); + ASSERT((_arena_end - _arena_start) >= 32K, "arena region < 32K") + + /* Mutable global variables, initialized by the ROM bootloader. */ + .data : ALIGN(4) + { + . = ALIGN (4); + _sdata = ABSOLUTE(.); + *(.data .data.*) + *(.dram*) + . = ALIGN (4); + /* DROM flash physical address, patched by the TinyGo ESP image builder + * with the flash offset where the .rodata (DROM) segment was placed. + * The startup code reads this to program the flash cache MMU. */ + _drom_flash_addr = ABSOLUTE(.); + LONG(0x10000); + . = ALIGN (4); + _edata = ABSOLUTE(.); + } >DRAM + + /* Check that the boot ROM stack (for the APP CPU) does not overlap with the + * data that is loaded by the boot ROM. + */ + ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") + + /* IRAM segment: boot code, interrupt vectors, and WiFi/BLE blob code + * that must run from RAM. */ + .iram : ALIGN(4) + { + /* Boot entry point and its literals */ *(.literal.call_start_cpu0) *(.text.call_start_cpu0) @@ -40,69 +147,49 @@ SECTIONS *(.literal._handle_level1) *(.text._handle_level1) - *(.literal .text) - *(.literal.* .text.*) + /* WiFi/BLE blob IRAM sections */ + *(.iram*) + *(.wifislprxiram*) + *(.wifiextrairam*) + *(.wifi0iram*) + *(.wifislpiram*) + *(.wifirxiram*) + *(.wifiorslpiram*) + *(.iram1*) + *(.coexiram*) + + . = ALIGN(4); + _iram_end = .; } >IRAM - /* Put the stack at the bottom of DRAM, so that the application will - * crash on stack overflow instead of silently corrupting memory. - * See: http://blog.japaric.io/stack-overflow-protection/ */ - .stack (NOLOAD) : - { - . = ALIGN(16); - . += _stack_size; - _stack_top = .; - } >DRAM - - /* Constant global variables. - * They are loaded in DRAM for ease of use. Eventually they should be stored - * in flash and loaded directly from there but they're kept in RAM to make - * sure they can always be accessed (even in interrupts). + /* IROM segment: main code executed from flash via MMU cache. + * The builder places this at a page-aligned flash offset. */ - .rodata : ALIGN(4) + .text : ALIGN(4) { - *(.rodata) - *(.rodata.*) - } >DRAM + _text_start = ABSOLUTE(.); + *(.literal .text) + *(.literal.* .text.*) + *(.phyiram*) + . = ALIGN(4); + _text_end = ABSOLUTE(.); + } >IROM - /* Mutable global variables. - */ - .data : ALIGN(4) + /DISCARD/ : { - _sdata = ABSOLUTE(.); - *(.data) - *(.data.*) - _edata = ABSOLUTE(.); - } >DRAM - - /* Check that the boot ROM stack (for the APP CPU) does not overlap with the - * data that is loaded by the boot ROM. There may be ways to avoid this - * issue if it occurs in practice. - * The magic value here is _stack_sentry in the boot ROM ELF file. - */ - ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") - - /* Global variables that are mutable and zero-initialized. - * These must be zeroed at startup (unlike data, which is loaded by the - * bootloader). - */ - .bss (NOLOAD) : ALIGN(4) - { - . = ALIGN (4); - _sbss = ABSOLUTE(.); - *(.bss) - *(.bss.*) - . = ALIGN (4); - _ebss = ABSOLUTE(.); - } >DRAM + *(.eh_frame) + } } -/* For the garbage collector. - */ -_globals_start = _sdata; -_globals_end = _ebss; -_heap_start = _ebss; -_heap_end = ORIGIN(DRAM) + LENGTH(DRAM); +/* For the garbage collector. */ +_globals_start = _sbss; +_globals_end = _wifi_bss_end; +_heap_start = _edata; +/* 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). */ +_heap_end = 0x3FFE0000; _stack_size = 4K; @@ -209,3 +296,23 @@ __umodsi3 = 0x4000c7d0; __umulsidi3 = 0x4000c7d8; __unorddf2 = 0x400637f4; __unordsf2 = 0x40063478; + +/* From ESP-IDF: + * components/esp_rom/esp32/ld/esp32.rom.ld + * ROM functions needed for WiFi/PHY support. + */ +PROVIDE(ets_delay_us = 0x40008534); +PROVIDE(ets_printf = 0x40007d54); +PROVIDE(ets_install_putc1 = 0x40007d18); +PROVIDE(ets_install_uart_printf = 0x40007d28); +PROVIDE(intr_matrix_set = 0x4000681c); +PROVIDE(rtc_get_reset_reason = 0x400081d4); +PROVIDE(phy_get_romfuncs = 0x40004100); +PROVIDE(uart_tx_one_char = 0x40009200); +PROVIDE(uart_rx_one_char = 0x400092d0); +PROVIDE(ets_intr_lock = 0x400067b0); +PROVIDE(ets_intr_unlock = 0x400067c4); +PROVIDE(ets_isr_attach = 0x400067ec); +PROVIDE(ets_isr_mask = 0x400067fc); +PROVIDE(ets_isr_unmask = 0x40006808); +PROVIDE(roundup2 = 0x4000ab7c);