Skip to content

Commit c97bbcc

Browse files
committed
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>
1 parent f71b630 commit c97bbcc

4 files changed

Lines changed: 1089 additions & 21 deletions

File tree

src/internal/task/task_stack_esp32.S

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//go:build tinygo
22

3+
// See Xtensa ISA Reference Manual for details.
4+
// https://www.cadence.com/content/dam/cadence-www/global/en_US/documents/tools/silicon-solutions/compute-ip/isa-summary.pdf
5+
36
.section .text.tinygo_startTask,"ax",@progbits
47
.global tinygo_startTask
58
.type tinygo_startTask, %function
@@ -68,14 +71,12 @@ tinygo_swapTask:
6871

6972
// After the recursive spill returns, the physical register file still
7073
// has WindowStart bits set for the spill helper frames.
71-
// We will clear WindowStart completely (to 0) right before the retw.n
72-
// below, after the stack switch is done. This prevents stale overflow
73-
// when the new goroutine's calls rotate back into these panes.
74+
// These are dropped below, once the stack switch is done.
7475

75-
// Restore interrupts.
76-
l32i a4, sp, 4 // reload saved PS
77-
wsr.ps a4
78-
rsync
76+
// Mask interrupts until WindowStart matches the new stack, or one lands in
77+
// the gap and clobbers the a0-a3 that retw.n reloads.
78+
// Xtensa ISA Reference Manual, RETW, section 8.3.253, p.565.
79+
l32i a4, sp, 4 // reload saved PS, restored below
7980

8081
// At this point, the following is true:
8182
// WindowStart == 1 << WindowBase
@@ -97,16 +98,20 @@ tinygo_swapTask:
9798
// register also stores the parent register window.
9899
l32i.n a0, sp, 0
99100

100-
// Clear ALL WindowStart bits. With all windows spilled to the stack,
101-
// we must ensure no stale WS bits remain: the retw.n below will trigger
102-
// underflow4 to load the new goroutine's registers from the new stack
103-
// (which sets the appropriate WS bit via rfwu). Any stale WS bits
104-
// (from spill helpers or the old goroutine) would cause spurious
105-
// overflows of garbage register values into memory.
106-
movi a5, 0
101+
// Drop every WindowStart bit but this window's: stale bits overflow garbage,
102+
// and an all-zero WindowStart marks the running frame dead.
103+
// Xtensa ISA Reference Manual, WINDOWSTART (SR 73), Table 5-148.
104+
rsr a6, WINDOWBASE
105+
movi a5, 1
106+
ssl a6
107+
sll a5, a5 // a5 = 1 << WindowBase
107108
wsr a5, WINDOWSTART
108109
rsync
109110

111+
// Window state now matches the new stack, so unmask interrupts.
112+
wsr.ps a4
113+
rsync
114+
110115
// Return into the new stack. This instruction will trigger a window
111116
// underflow, reloading the saved registers from the stack.
112117
retw.n

src/internal/task/task_stack_esp32.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package task
99
// a7: stack frame pointer (optional, normally unused in TinyGo)
1010
// Sources:
1111
// http://cholla.mmto.org/esp8266/xtensa.html
12-
// https://0x04.net/~mwk/doc/xtensa.pdf
12+
// https://www.cadence.com/content/dam/cadence-www/global/en_US/documents/tools/silicon-solutions/compute-ip/isa-summary.pdf
1313

1414
import (
1515
"unsafe"

targets/esp32s3-interrupts.S

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

22
// Xtensa interrupt/exception vector table for the ESP32-S3.
33
//
4+
// See Xtensa ISA Reference Manual for details on the vector table layout and exception handling.
5+
// https://www.cadence.com/content/dam/cadence-www/global/en_US/documents/tools/silicon-solutions/compute-ip/isa-summary.pdf
6+
//
47
// The ESP32-S3 uses an Xtensa LX7 core with the windowed register ABI.
58
// Interrupt vectors are placed at fixed offsets from the VECBASE special
69
// register. We only handle level-1 (user) interrupts for now.
@@ -299,13 +302,26 @@ _handle_kernel_exc:
299302

300303
.global _handle_level1
301304
_handle_level1:
302-
// --- allocate 96-byte exception frame on the interrupted stack ---
305+
// EXCCAUSE 5 (AllocaCause) is a MOVSP window-spill request, not a fault, so
306+
// it is checked before any state is touched: a0 is still in EXCSAVE1.
307+
// Xtensa ISA Reference Manual, Table 4-64 "Exception Causes".
308+
rsr a0, EXCCAUSE
309+
bnei a0, 5, 1f
310+
j _xt_alloca_exc
311+
1:
312+
rsr a0, EXCSAVE1 // restore a0 clobbered by the EXCCAUSE read
313+
314+
// --- allocate the exception frame on the interrupted stack ---
303315
// Layout (offsets from a1 after adjustment):
304316
// 0: a0 4: a1(orig) 8: a2 12: a3 16: a4 20: a5
305317
// 24: a6 28: a7 32: a8 36: a9 40: a10 44: a11
306318
// 48: a12 52: a13 56: a14 60: a15
307-
// 64: SAR 68: EPC1 72: PS
308-
addi a0, a1, -96 // a0 = new frame pointer
319+
// 64: SAR 68: EPC1 72: PS 76: WINDOWBASE 80: WINDOWSTART
320+
//
321+
// 128, not the 84 the layout needs: the low 16 bytes are the interruptee's
322+
// windowed-ABI base save area, as in ESP-IDF's XT_STK_FRMSZ (+0x20).
323+
// https://github.com/espressif/esp-idf/blob/master/components/xtensa/include/xtensa_context.h
324+
addi a0, a1, -128 // a0 = new frame pointer
309325
s32i a1, a0, 4 // save original a1 (SP)
310326
mov a1, a0 // a1 = frame pointer
311327

@@ -339,6 +355,14 @@ _handle_level1:
339355
// level-1 interrupts.
340356
rsr a2, PS
341357
s32i a2, a1, 72 // save PS (with EXCM=1 set by hardware)
358+
359+
// Captured here, not in the C handler: getting there costs a callx4 and the
360+
// printfs rotate and spill windows, so they would report their own state.
361+
rsr a3, WINDOWBASE
362+
s32i a3, a1, 76
363+
rsr a3, WINDOWSTART
364+
s32i a3, a1, 80
365+
342366
movi a3, ~0x1F // mask: clear INTLEVEL (bits 0-3) + EXCM (bit 4)
343367
and a2, a2, a3
344368
movi a3, 1 // INTLEVEL = 1
@@ -412,6 +436,37 @@ _handle_level1:
412436

413437
rfe
414438

439+
// Alloca (MOVSP) exception: rotate back to the MOVSP window, fix PS.OWB, then
440+
// fall into the matching window underflow handler, which spills and rfwu's back.
441+
// Ported from ESP-IDF _xt_alloca_exc:
442+
// https://github.com/espressif/esp-idf/blob/master/components/xtensa/xtensa_vectors.S
443+
.balign 4
444+
.global _xt_alloca_exc
445+
_xt_alloca_exc:
446+
rsr a0, WINDOWBASE // grab WINDOWBASE before rotw changes it
447+
rotw -1 // WINDOWBASE goes to a4, new a0-a3 are scratch
448+
rsr a2, PS
449+
extui a3, a2, 8, 4 // a3 = PS.OWB (shift 8, 4 bits)
450+
xor a3, a3, a4 // bits that changed from old to current WB
451+
rsr a4, EXCSAVE1 // restore the interruptee's a0 (now in a4)
452+
slli a3, a3, 8
453+
xor a2, a2, a3 // flip those bits in PS.OWB
454+
wsr a2, PS // PS.OWB now matches the new WINDOWBASE
455+
rsync
456+
457+
// Dispatch on the call size in bits 31:30 of the interruptee's a0; plain `j`
458+
// because _bbci.l's 8-bit range cannot reach the underflow vectors.
459+
// Xtensa ISA Reference Manual, RETW, section 8.3.253, p.565 (a0 encoding).
460+
_bbsi.l a4, 31, 1f
461+
j _window_underflow4
462+
1:
463+
rotw -1 // interruptee's a0 moves to a8
464+
_bbsi.l a8, 30, 2f
465+
j _window_underflow8
466+
2:
467+
rotw -1
468+
j _window_underflow12
469+
415470
// -----------------------------------------------------------------------
416471
// Default weak espradio_user_exception: infinite loop halt.
417472
// Overridden by the strong definition in espradio's isr.c when linked.

0 commit comments

Comments
 (0)