mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-21 04:49:04 +00:00
esp32s3: fix register-window corruption under interrupt load
Remove the C3 bluetooth hook addresses from esp32s3.ld (on the S3 they point into the ROM md5/crc thunk table, and being bare assignments they also shadowed the blob's own definitions), keep the interrupt frame clear of the 16-byte windowed-ABI save area below SP, and make tinygo_swapTask hold INTLEVEL across the stack switch while keeping the running frame's WINDOWSTART bit set. Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
@@ -72,10 +72,13 @@ tinygo_swapTask:
|
|||||||
// below, after the stack switch is done. This prevents stale overflow
|
// below, after the stack switch is done. This prevents stale overflow
|
||||||
// when the new goroutine's calls rotate back into these panes.
|
// when the new goroutine's calls rotate back into these panes.
|
||||||
|
|
||||||
// Restore interrupts.
|
// Reload the saved PS, but do NOT restore it yet: everything from the stack
|
||||||
|
// switch below through the retw.n must stay masked. Once sp points at the
|
||||||
|
// new task's stack, the memory below sp is that task's save area -- the
|
||||||
|
// a0-a3 the retw.n is about to reload through a window underflow. An
|
||||||
|
// interrupt landing in that gap builds its frame there and overwrites them,
|
||||||
|
// and a garbage a0 makes the next retw an Illegal Instruction.
|
||||||
l32i a4, sp, 4 // reload saved PS
|
l32i a4, sp, 4 // reload saved PS
|
||||||
wsr.ps a4
|
|
||||||
rsync
|
|
||||||
|
|
||||||
// At this point, the following is true:
|
// At this point, the following is true:
|
||||||
// WindowStart == 1 << WindowBase
|
// WindowStart == 1 << WindowBase
|
||||||
@@ -97,16 +100,24 @@ tinygo_swapTask:
|
|||||||
// register also stores the parent register window.
|
// register also stores the parent register window.
|
||||||
l32i.n a0, sp, 0
|
l32i.n a0, sp, 0
|
||||||
|
|
||||||
// Clear ALL WindowStart bits. With all windows spilled to the stack,
|
// Drop every WindowStart bit except this window's. Stale bits (from the
|
||||||
// we must ensure no stale WS bits remain: the retw.n below will trigger
|
// spill helpers or the old goroutine) would overflow garbage registers into
|
||||||
// underflow4 to load the new goroutine's registers from the new stack
|
// memory. This window's own bit has to stay set: zeroing WindowStart marks
|
||||||
// (which sets the appropriate WS bit via rfwu). Any stale WS bits
|
// the running frame dead, so an interrupt arriving here returns through a
|
||||||
// (from spill helpers or the old goroutine) would cause spurious
|
// retw that underflows into a save area nothing ever wrote. Keeping it
|
||||||
// overflows of garbage register values into memory.
|
// costs nothing -- WindowStart[WindowBase - n] is still clear, so the
|
||||||
movi a5, 0
|
// retw.n below underflows as intended.
|
||||||
|
rsr a6, WINDOWBASE
|
||||||
|
movi a5, 1
|
||||||
|
ssl a6
|
||||||
|
sll a5, a5 // a5 = 1 << WindowBase
|
||||||
wsr a5, WINDOWSTART
|
wsr a5, WINDOWSTART
|
||||||
rsync
|
rsync
|
||||||
|
|
||||||
|
// Now that the new task's window state is consistent, take interrupts back.
|
||||||
|
wsr.ps a4
|
||||||
|
rsync
|
||||||
|
|
||||||
// Return into the new stack. This instruction will trigger a window
|
// Return into the new stack. This instruction will trigger a window
|
||||||
// underflow, reloading the saved registers from the stack.
|
// underflow, reloading the saved registers from the stack.
|
||||||
retw.n
|
retw.n
|
||||||
|
|||||||
@@ -299,13 +299,37 @@ _handle_kernel_exc:
|
|||||||
|
|
||||||
.global _handle_level1
|
.global _handle_level1
|
||||||
_handle_level1:
|
_handle_level1:
|
||||||
// --- allocate 96-byte exception frame on the interrupted stack ---
|
// --- EXCCAUSE 5 (alloca / MOVSP) is not a fault, it is a request ---
|
||||||
|
//
|
||||||
|
// MOVSP raises this whenever it moves the stack pointer while the caller's
|
||||||
|
// register window is still live in the register file: the hardware is
|
||||||
|
// asking us to spill that window, not reporting an error. LLVM emits MOVSP
|
||||||
|
// for any call that needs a 7th argument on the stack (the windowed ABI
|
||||||
|
// passes six in registers), so it shows up in ordinary C and Go code alike.
|
||||||
|
//
|
||||||
|
// Without this, such a call takes a fatal user exception. The check goes
|
||||||
|
// first, before any state is touched, so a0 is still in EXCSAVE1 where
|
||||||
|
// _xt_alloca_exc wants it and every other cause falls through to the
|
||||||
|
// handler below completely unchanged.
|
||||||
|
rsr a0, EXCCAUSE
|
||||||
|
bnei a0, 5, 1f
|
||||||
|
j _xt_alloca_exc
|
||||||
|
1:
|
||||||
|
rsr a0, EXCSAVE1 // restore a0 clobbered by the EXCCAUSE read
|
||||||
|
|
||||||
|
// --- allocate the exception frame on the interrupted stack ---
|
||||||
// Layout (offsets from a1 after adjustment):
|
// Layout (offsets from a1 after adjustment):
|
||||||
// 0: a0 4: a1(orig) 8: a2 12: a3 16: a4 20: a5
|
// 0: a0 4: a1(orig) 8: a2 12: a3 16: a4 20: a5
|
||||||
// 24: a6 28: a7 32: a8 36: a9 40: a10 44: a11
|
// 24: a6 28: a7 32: a8 36: a9 40: a10 44: a11
|
||||||
// 48: a12 52: a13 56: a14 60: a15
|
// 48: a12 52: a13 56: a14 60: a15
|
||||||
// 64: SAR 68: EPC1 72: PS
|
// 64: SAR 68: EPC1 72: PS 76: WINDOWBASE 80: WINDOWSTART
|
||||||
addi a0, a1, -96 // a0 = new frame pointer
|
//
|
||||||
|
// 128, not the 88 the layout needs: the 16 bytes below the interrupted SP
|
||||||
|
// are the windowed-ABI caller save area, written by window overflow and
|
||||||
|
// read back by underflow. Clobbering them corrupts the interrupted code's
|
||||||
|
// a0, which surfaces later as an Illegal Instruction on its retw. ESP-IDF
|
||||||
|
// reserves the same gap (the 0x20 in XT_STK_FRMSZ).
|
||||||
|
addi a0, a1, -128 // a0 = new frame pointer
|
||||||
s32i a1, a0, 4 // save original a1 (SP)
|
s32i a1, a0, 4 // save original a1 (SP)
|
||||||
mov a1, a0 // a1 = frame pointer
|
mov a1, a0 // a1 = frame pointer
|
||||||
|
|
||||||
@@ -339,6 +363,15 @@ _handle_level1:
|
|||||||
// level-1 interrupts.
|
// level-1 interrupts.
|
||||||
rsr a2, PS
|
rsr a2, PS
|
||||||
s32i a2, a1, 72 // save PS (with EXCM=1 set by hardware)
|
s32i a2, a1, 72 // save PS (with EXCM=1 set by hardware)
|
||||||
|
|
||||||
|
// Window state as it was at the fault. Reading it from the C handler
|
||||||
|
// instead describes the handler: getting there costs a callx4, and the
|
||||||
|
// printfs rotate and spill windows of their own.
|
||||||
|
rsr a3, WINDOWBASE
|
||||||
|
s32i a3, a1, 76
|
||||||
|
rsr a3, WINDOWSTART
|
||||||
|
s32i a3, a1, 80
|
||||||
|
|
||||||
movi a3, ~0x1F // mask: clear INTLEVEL (bits 0-3) + EXCM (bit 4)
|
movi a3, ~0x1F // mask: clear INTLEVEL (bits 0-3) + EXCM (bit 4)
|
||||||
and a2, a2, a3
|
and a2, a2, a3
|
||||||
movi a3, 1 // INTLEVEL = 1
|
movi a3, 1 // INTLEVEL = 1
|
||||||
@@ -412,6 +445,52 @@ _handle_level1:
|
|||||||
|
|
||||||
rfe
|
rfe
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Alloca (MOVSP) exception handler.
|
||||||
|
//
|
||||||
|
// Rotates back to the window that executed MOVSP, fixes up PS.OWB to match
|
||||||
|
// the new WINDOWBASE, and falls into the window underflow handler that
|
||||||
|
// corresponds to the caller's call size. The underflow handler spills the
|
||||||
|
// window and returns to the interrupted MOVSP via rfwu, which then completes.
|
||||||
|
//
|
||||||
|
// All of the interruptee's registers are intact except a0, which is in
|
||||||
|
// EXCSAVE1. PS.EXCM is set by the hardware, so this cannot be interrupted.
|
||||||
|
// The registers of the base-save area are free scratch here, because taking
|
||||||
|
// this exception means they have already been spilled and the underflow
|
||||||
|
// handler will restore them.
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
.balign 4
|
||||||
|
.global _xt_alloca_exc
|
||||||
|
_xt_alloca_exc:
|
||||||
|
rsr a0, WINDOWBASE // grab WINDOWBASE before rotw changes it
|
||||||
|
rotw -1 // WINDOWBASE goes to a4, new a0-a3 are scratch
|
||||||
|
rsr a2, PS
|
||||||
|
extui a3, a2, 8, 4 // a3 = PS.OWB (shift 8, 4 bits)
|
||||||
|
xor a3, a3, a4 // bits that changed from old to current WB
|
||||||
|
rsr a4, EXCSAVE1 // restore the interruptee's a0 (now in a4)
|
||||||
|
slli a3, a3, 8
|
||||||
|
xor a2, a2, a3 // flip those bits in PS.OWB
|
||||||
|
wsr a2, PS // PS.OWB now matches the new WINDOWBASE
|
||||||
|
rsync
|
||||||
|
|
||||||
|
// Dispatch on the call size encoded in the interruptee's return address:
|
||||||
|
// bits 31:30 of a0 are 00/01 for call4, 10 for call8, 11 for call12.
|
||||||
|
//
|
||||||
|
// The canonical sequence branches straight to the underflow vectors with
|
||||||
|
// _bbci.l, but that has only an 8-bit displacement and this handler lives
|
||||||
|
// outside the vector table, well out of reach. Inverting each test and
|
||||||
|
// putting the vector target on a full-range `j` is the same dispatch
|
||||||
|
// without the range limit.
|
||||||
|
_bbsi.l a4, 31, 1f
|
||||||
|
j _window_underflow4
|
||||||
|
1:
|
||||||
|
rotw -1 // interruptee's a0 moves to a8
|
||||||
|
_bbsi.l a8, 30, 2f
|
||||||
|
j _window_underflow8
|
||||||
|
2:
|
||||||
|
rotw -1
|
||||||
|
j _window_underflow12
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Default weak espradio_user_exception: infinite loop halt.
|
// Default weak espradio_user_exception: infinite loop halt.
|
||||||
// Overridden by the strong definition in espradio's isr.c when linked.
|
// Overridden by the strong definition in espradio's isr.c when linked.
|
||||||
|
|||||||
+1017
-2
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user