@@ -181,22 +181,141 @@ _kernel_vector:
181181// -----------------------------------------------------------------------
182182// Offset 0x340 — User exception / level - 1 interrupt
183183//
184- // Entire handler is inline — no jump, no stack access, no memory loads.
185- // Just disable all CPU interrupts via INTENABLE and return.
186- // Buffered() re-enables INTENABLE after draining the hardware FIFO.
184+ // Save a0 and jump to the full handler below the vector table.
187185// -----------------------------------------------------------------------
188186 .org _vector_table + 0x340
189187 . global _level1_vector
190188_level1_vector:
191- wsr a0 , EXCSAVE1 // save a0
192- movi a0 , 0
193- wsr a0 , INTENABLE // disable ALL CPU interrupts
194- rsr a0 , EXCSAVE1 // restore a0
195- rfe // return from exception
189+ wsr a0 , EXCSAVE1 // save a0 — only scratch register available
190+ j _handle_level1 // jump to full handler (PC - relative , no literal pool)
196191
197192// -----------------------------------------------------------------------
198193// Offset 0x3C0 — Double exception (stub — loops forever)
199194// -----------------------------------------------------------------------
200195 .org _vector_table + 0x3C0
201196_double_vector:
202197 j _double_vector
198+
199+ // -----------------------------------------------------------------------
200+ // Level - 1 interrupt handler — lives outside the vector table so there
201+ // is no 64 - byte size constraint.
202+ //
203+ // Saves the interrupted context on the current stack , clears PS.EXCM
204+ // (so window overflow/underflow work) , calls the Go handleInterrupt
205+ // dispatcher , restores context , and returns via rfe.
206+ //
207+ // We call handleInterrupt via callx4 (window rotation by 4 ). This is
208+ // required because:
209+ // - callx0 does not set PS.CALLINC , so the Go function 's " entry"
210+ // instruction would use whatever CALLINC the interrupted code left ,
211+ // causing incorrect window rotation and a garbage stack pointer.
212+ // - callx0 puts the return address in a0 with the raw PC (0x42xxx for
213+ // flash) , whose top 2 bits ( 01 ) cause retw to decrement WindowBase
214+ // by 1 even though nothing was incremented.
215+ //
216+ // With callx4 , CALLINC is explicitly set to 1 and the return address
217+ // in a4 has the top 2 bits set to 01 — matching the window rotation
218+ // th at entry performs. After retw , WindowBase is correctly restored.
219+ // Our a0..a3 (including a1 , the frame pointer) are NOT in the callee's
220+ // register window (callee uses physical regs + 4 .. + 19 ) , so a1 is
221+ // preserved across the call without needing EXCSAVE1.
222+ // -----------------------------------------------------------------------
223+ // Literal data for l32r (must be at a lower address than the l32r).
224+ .balign 4
225+ .LhandleInterrupt_addr:
226+ . word handleInterrupt
227+
228+ . global _handle_level1
229+ _handle_level1:
230+ // --- allocate 96 - byte exception frame on the interrupted stack ---
231+ // Layout (offsets from a1 after adjustment):
232+ // 0 : a0 4 : a1(orig) 8 : a2 12 : a3 16 : a4 20 : a5
233+ // 24 : a6 28 : a7 32 : a8 36 : a9 40 : a10 44 : a11
234+ // 48 : a12 52 : a13 56 : a14 60 : a15
235+ // 64 : SAR 68 : EPC1 72 : PS
236+ addi a0 , a1 , - 96 // a0 = new frame pointer
237+ s32i a1 , a0 , 4 // save original a1 ( SP )
238+ mov a1 , a0 // a1 = frame pointer
239+
240+ rsr a0 , EXCSAVE1 // recover original a0
241+ s32i a0 , a1 , 0 // save original a0
242+
243+ // Save general registers a2..a15.
244+ s32i a2 , a1 , 8
245+ s32i a3 , a1 , 12
246+ s32i a4 , a1 , 16
247+ s32i a5 , a1 , 20
248+ s32i a6 , a1 , 24
249+ s32i a7 , a1 , 28
250+ s32i a8 , a1 , 32
251+ s32i a9 , a1 , 36
252+ s32i a10 , a1 , 40
253+ s32i a11 , a1 , 44
254+ s32i a12 , a1 , 48
255+ s32i a13 , a1 , 52
256+ s32i a14 , a1 , 56
257+ s32i a15 , a1 , 60
258+
259+ // Save special registers.
260+ rsr a2 , SAR
261+ s32i a2 , a1 , 64
262+ rsr a2 , EPC1
263+ s32i a2 , a1 , 68
264+
265+ // Clear PS.EXCM (bit 4 ) so window overflow/underflow exceptions work
266+ // during the Go call . Set PS.INTLEVEL= 1 to prevent re - entry of
267+ // level - 1 interrupts.
268+ rsr a2 , PS
269+ s32i a2 , a1 , 72 // save PS (with EXCM= 1 set by hardware)
270+ movi a3 , ~ 0x1F // mask: clear INTLEVEL (bits 0 - 3 ) + EXCM (bit 4 )
271+ and a2 , a2 , a3
272+ movi a3 , 1 // INTLEVEL = 1
273+ or a2 , a2 , a3
274+ wsr a2 , PS
275+ rsync
276+
277+ // Call the Go interrupt dispatcher via callx4.
278+ // callx4 explicitly sets PS.CALLINC= 1 and puts the return address
279+ // (with top 2 bits = 01 ) in a4. After entry rotates the window by
280+ // 4 , the callee sees: a0 = our a4 (return addr) , a1 = our a5 - N.
281+ // We set a5 = our frame pointer so the callee gets a valid stack.
282+ mov a5 , a1
283+ l32r a2 , .LhandleInterrupt_addr
284+ callx4 a2
285+ // After retw , WindowBase is restored. a0..a3 are preserved because
286+ // they are outside the callee's register window.
287+
288+ // --- restore context ---
289+
290+ // Restore PS (restores EXCM= 1 ).
291+ l32i a2 , a1 , 72
292+ wsr a2 , PS
293+ rsync
294+
295+ // Restore special registers.
296+ l32i a2 , a1 , 64
297+ wsr a2 , SAR
298+ l32i a2 , a1 , 68
299+ wsr a2 , EPC1
300+
301+ // Restore general registers a15..a2.
302+ l32i a15 , a1 , 60
303+ l32i a14 , a1 , 56
304+ l32i a13 , a1 , 52
305+ l32i a12 , a1 , 48
306+ l32i a11 , a1 , 44
307+ l32i a10 , a1 , 40
308+ l32i a9 , a1 , 36
309+ l32i a8 , a1 , 32
310+ l32i a7 , a1 , 28
311+ l32i a6 , a1 , 24
312+ l32i a5 , a1 , 20
313+ l32i a4 , a1 , 16
314+ l32i a3 , a1 , 12
315+ l32i a2 , a1 , 8
316+
317+ // Restore a0 and a1 (a1 must be last since it is the frame pointer).
318+ l32i a0 , a1 , 0
319+ l32i a1 , a1 , 4 // restores original SP (deallocates frame)
320+
321+ rfe
0 commit comments