Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 239 additions & 4 deletions src/device/esp/esp32.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Loading
Loading