I am writing an interrupt routine for RISC-V. Interrupts occur in U-mode and are handled in S-mode.
Upon an interrupt, I want to save the user execution context on the kernel stack of the thread that was interrupted. A pointer to the kernel stack is saved in the sscratch
register.
I want to swap the values in sp
and sscratch
, and then store all 32 general-purpose registers on the kernel stack.
Is there a way to perform this swap without using any general-purpose registers?
I can't temporarily store registers on the user stack, because I can't make any assumptions about the available space on the user stack.
Yes, there's a CSR swap capability, which allows exchanging a CSR and a general purpose register. Using this, you can now use the pointer to store other general registers (and then later have to swap back to get the general register's original value).
csrrw t0, uscratch # swap CSR and general register t0
This csrrw
instruction reads the csr first, the writes it. It can read to one general purpose register and write from another, but, using the same gp reg for both read and write accomplishes a swap.
So, the register fields of this csrrw
instruction, namely xs1
and xd
, are the same general register, t0
, to accomplish the swap.
What I do is store a pointer to the currently running thread's thread control block (TCB) in the "scratch" CSR register. On handling an interrupt and with interrupts off, swap that CSR with one of the general purpose registers and then can start storing the other general purpose registers right away. Have to swap back in some sense to get the last register.
For a context switch, I reload all the general registers, and then put that other thread's TCB pointer in the "scratch" CSR, before letting it resume.
I dynamically create a separate TCB for each new (user/top-level/main) thread.
There's also a fixed array of, say, 10 TCBs to use for nested interrupts. Among other things, each TCB includes a pointer to the next TCB to use for an interrupt, so the top-level threads all have that pointer refer to the first element of that TCB array, and each element of that array refers there to the next element in that array.
Thus, when a top-level thread is interrupted, the first element of that array becomes the TCB for the interrupt handling code itself (if it wants to turn interrupts back on), and if then interrupted, the next element in the TCB array is the one used for that nested interrupt.
Only top-level threads participate in context swap, the interrupted handlers must complete before resumption of any top-level thread.
I also divide interrupts into two categories, ones that stores only just as many registers as needed for its handling and then resume the interrupted code, and ones that start that way but go on to store all the registers so as to allow for context switch.
In my implementation, a TCB also has a reference to kernel data structures, and, state information about the thread, such a runnable/blocked, priority, if blocked, then on what, etc..