universalisos/microkernel/ports/esp32/startup.S

131 lines
4.3 KiB
ArmAsm

/*
* ESP32 CALL0 boot + Xtensa vector table
*
* VECBASE is set to the base of .vectors (flash-XIP or IRAM depending
* on linker config). The vector table layout for ESP32 Xtensa LX6:
*
* VECBASE + 0x000 Window overflow 4
* VECBASE + 0x040 Window underflow 4
* VECBASE + 0x080 Window overflow 8
* VECBASE + 0x0C0 Window underflow 8
* VECBASE + 0x100 Window overflow 12
* VECBASE + 0x140 Window underflow 12
* VECBASE + 0x180 Level 2 interrupt
* VECBASE + 0x1C0 Level 3 interrupt
* VECBASE + 0x200 Level 4 interrupt
* VECBASE + 0x240 Level 5 interrupt
* VECBASE + 0x280 Level 6 / Debug exception THIS IS THE GDB STUB ENTRY
* VECBASE + 0x2C0 NMI (level 7)
* VECBASE + 0x300 Kernel exception
* VECBASE + 0x340 User exception
* VECBASE + 0x3C0 Double exception
* VECBASE + 0x400 Reset vector / _start
*
* Each vector slot is 64 bytes (0x40). The debug vector at 0x280 must:
* 1. Save a0 into EXCSAVE6 (so the handler can recover it)
* 2. Jump to the main debug handler (_uos_gdbstub_debug_entry)
*
* This matches the IDF _DebugExceptionVector pattern from xtensa_vectors.S.
*/
.section .vectors, "ax", @progbits
.global _start
.type _start, @function
.align 4
__vectors_base:
/* Window vectors (0x000 - 0x17F) — not used in CALL0 but must exist */
.fill 0x180, 1, 0x00
/* Level 2 interrupt (0x180) */
.fill 0x40, 1, 0x00
/* Level 3 interrupt (0x1C0) */
.fill 0x40, 1, 0x00
/* Level 4 interrupt (0x200) */
.fill 0x40, 1, 0x00
/* Level 5 interrupt (0x240) */
.fill 0x40, 1, 0x00
/* === Level 6 / Debug Exception (0x280) === */
.global _xt_level6_vector
.type _xt_level6_vector, @function
.align 4
_xt_level6_vector:
/* Save a0 into EXCSAVE6 (SR 214) only 3 instructions fit.
* The debug exception automatically sets PS to EPS6.
* a0 is the ONLY register we can clobber here. */
wsr a0, 214 /* EXCSAVE6 = a0 */
j _uos_gdbstub_debug_entry
/* Pad to 0x2C0 */
.fill 0x2C0 - (. - __vectors_base), 1, 0x00
/* NMI (0x2C0) — not handled */
.fill 0x40, 1, 0x00
/* Kernel exception (0x300) */
.fill 0x40, 1, 0x00
/* User exception (0x340) */
.fill 0x40, 1, 0x00
/* Double exception (0x3C0) */
.fill 0x40, 1, 0x00
/* === Reset vector / _start (0x400) ===
* Ensure we're exactly at VECBASE + 0x400 */
. = __vectors_base + 0x400
.align 4
_start:
/* Set VECBASE to the start of .vectors section.
* The linker places .vectors at the beginning of irom_seg (0x40820000
* for flash-XIP build, or 0x40080000 for IRAM build).
* VECBASE must point to __vectors_base. */
movi a2, __vectors_base
wsr a2, 231 /* VECBASE */
isync
/* Set up stack pointer: top of DRAM.
* ESP32 DRAM: 0x3FFB0000..0x40000000 (320KB usable).
* Use 0x3FFD0000 (128KB above DRAM base) as initial sp.
* This matches the linker_flash.ld DRAM segment. */
movi a1, 0x3FFD0000
/* Disable interrupts during setup but NOT above debug level (6).
* Setting INTLEVEL > 6 would mask debug exceptions, preventing
* the 'break' instruction from trapping.
* Use INTLEVEL=5 (max normal interrupt level, below debug). */
rsil a2, 5
/* Clear ICOUNT and ICOUNTLEVEL to prevent stray debug exceptions */
movi a3, 0
wsr a3, 236 /* ICOUNT */
wsr a3, 237 /* ICOUNTLEVEL */
/* DIAGNOSTIC: Write 'S' to UART0 FIFO to prove we reached _start.
* If 'S' appears on UART, execution IS reaching this point.
* If not, the problem is earlier (MCUboot handoff). */
movi a4, 0x3FF40000 /* UART0 FIFO (APB) */
movi a5, 0x53 /* 'S' */
s32i a5, a4, 0
/* DIAGNOSTIC: Instead of break 0,0, write '2' to prove we pass this point.
* Then fall through to esp32_app_entry to see if the app starts.
* This tests whether the code path AFTER break works. */
movi a5, 0x32 /* '2' */
s32i a5, a4, 0
/* Skip the break for now — test linear execution */
/* break 0, 0 */
/* After GDB continue: enter the microkernel */
call0 esp32_app_entry
/* If app_entry returns, idle loop */
1: waiti 0
j 1b
.size _start, . - _start