From acaf6f0799fd0a564788bc798d5e1467421a4007 Mon Sep 17 00:00:00 2001
From: Our Air Quality <info@ourairquality.org>
Date: Sun, 17 Jun 2018 12:25:22 +1000
Subject: [PATCH] Load/store exception handler: handle re-entry via a NMI.

The NMI can asynchronously interrupt the load/store exception handler. This
does occur frequently as the NMI handler code does invoke load/store
exceptions, and the load/store exception handler is heavly used. This was
corrupting the load/store exception handler saved state and thus randomly
corrupting registers a0 to a6 of the interruptee.
---
 core/exception_vectors.S | 70 ++++++++++++++++++++++++++++++++--------
 1 file changed, 56 insertions(+), 14 deletions(-)

diff --git a/core/exception_vectors.S b/core/exception_vectors.S
index a390cd3..ee22249 100644
--- a/core/exception_vectors.S
+++ b/core/exception_vectors.S
@@ -23,7 +23,7 @@
 #define CAUSE_LOADSTORE 3
 #define CAUSE_LVL1INT 4
 
-        .section .bss
+        .section .data
 
 /* Stack space for NMI handler
 
@@ -37,15 +37,26 @@ NMIHandlerStack:
         .skip 0x200
 .NMIHandlerStackTop:
 
+/* The Load Store exception handler uses a separate stack to store the
+ * interruptee registers. It does not appear to be practical to use the
+ * interuptee stack, which must in invalid at some points? This exception is
+ * synchronouns and the handler does not call back into itself. However it may
+ * be interrupted by a NMI which in turn may re-enter this exception
+ * handler. The NMI is responsible for switching the stack pointer to be used by
+ * this exception handler. Room is allocated for up to 3 stack frames, a base
+ * and two NMI reentry frames, and each frame is 7 words wide.
+ */
+#define LoadStoreErrorHandlerStackFrameSize (7 * 4)
+
         .balign 16
+        .global LoadStoreErrorHandlerStack
 LoadStoreErrorHandlerStack:
-        .word   0       # a0
-        .word   0       # (unused)
-        .word   0       # a2
-        .word   0       # a3
-        .word   0       # a4
-        .word   0       # a5
-        .word   0       # a6
+        .skip LoadStoreErrorHandlerStackFrameSize * 3
+
+        .balign 4
+        .global LoadStoreErrorHandlerStackPointer
+LoadStoreErrorHandlerStackPointer:
+        .word   0
 
         .balign 4
         .global debug_saved_ctx
@@ -137,10 +148,20 @@ DoubleExceptionVector:
 LoadStoreErrorHandler:
         .type   LoadStoreErrorHandler, @function
 
-        /* Registers are saved in the address corresponding to their register
-         * number times 4.  This allows a quick and easy mapping later on when
-         * needing to store the value to a particular register number. */
-        movi    sp, LoadStoreErrorHandlerStack
+        /* Registers are saved in stack frame offsets corresponding to their
+         * register number times 4.  This allows a quick and easy mapping later
+         * on when needing to store the value to a particular register
+         * number.
+         *
+         * This handler may be interrupted asynchronously by the NMI. The NMI
+         * handler is responsible for switching the load/store handler stack
+         * pointer and that avoids that overhead here. This handler is
+         * synchronous so the NMI handler can modify and restore the load/store
+         * stack pointer safely.
+         */
+        movi    sp, LoadStoreErrorHandlerStackPointer
+        l32i    sp, sp, 0
+
         s32i    a0, sp, 0
         s32i    a2, sp, 0x08
         s32i    a3, sp, 0x0c
@@ -537,6 +558,12 @@ call_user_start:
         .global call_user_start
         .type   call_user_start, @function
 
+        /* Initialize the load/store error handler stack pointer. There are no
+         * load/store exceptions before this point. */
+        movi    a2, LoadStoreErrorHandlerStackPointer
+        movi    a3, LoadStoreErrorHandlerStack
+        s32i    a3, a2, 0
+
         movi    a2, VecBase
         wsr     a2, vecbase
         call0   sdk_user_start
@@ -553,6 +580,9 @@ NMIExceptionHandler:
         .type   NMIExceptionHandler, @function
 
         wsr     sp, excsave3        # excsave3 holds user stack
+
+        /* Load the NMI handler stack pointer which is already offset by -0x40
+         * to create a frame to store the interruptee state. */
         movi    sp, .NMIHandlerStackTop - 0x40
         s32i    a0, sp, 0x00
         s32i    a2, sp, 0x04
@@ -579,19 +609,31 @@ NMIExceptionHandler:
         wsr     a0, ps
         rsync
 
-        /* mark the stack overflow point before we call the actual NMI handler */
+        /* Mark the stack overflow point before we call the actual NMI handler */
         movi    a0, NMIHandlerStack
         movi    a2, NMI_STACK_CANARY
         s32i    a2, a0, 0x00
 
+        /* Switch the load/store error handler stack. */
+        movi    a2, LoadStoreErrorHandlerStackPointer
+        l32i    a3, a2, 0
+        addi    a3, a3, LoadStoreErrorHandlerStackFrameSize
+        s32i    a3, a2, 0
+
         call0   sdk_wDev_ProcessFiq
 
-        /* verify we didn't overflow */
+        /* Verify we didn't overflow */
         movi    a0, NMIHandlerStack
         l32i    a3, a0, 0
         movi    a2, NMI_STACK_CANARY
         bne     a3, a2, .NMIFatalStackOverflow
 
+        /* Restore the load/store error handler stack. */
+        movi    a2, LoadStoreErrorHandlerStackPointer
+        l32i    a3, a2, 0
+        addi    a3, a3, -LoadStoreErrorHandlerStackFrameSize
+        s32i    a3, a2, 0
+
         l32i    a0, sp, 0x3c
         wsr     a0, sar
         l32i    a0, sp, 0x38