When writing low level interrupt service routines (ISRs) for RISC-V we must consider what happens when an interrupt occurs while another interrupt is being serviced. While this consideration is required for all processor architectures, RISC-V requires software to manage this situation.
This article will take a dive into using nested interrupts on RISC-V.
For the sake of simplicity this article only deals with a single core
operating in m
machine mode.
For example let’s consider the case of an mti
timer
interrupt occuring while an msi
software interrupt is being serviced.
One of the following must occur:
- The
mti
ISR must wait until the ISR ofmsi
completes until it is called. - The
msi
ISR must be paused while themti
ISR is called and completes servicing.
It should be noted that this is not restricted to just different
interrupt sources. Consider an external mei
interrupt that is
multiplexed by an external interrupt controller. We may wish to
service a higher priority mei
while servicing a lower priority
mei
.
Finally exceptions can occur during interrupt handling, so these must be considered.
Direct vs Vectored
Recall that RISC-V has two standard interrupts modes, direct and vectored modes.
Nested interrupts are possible in both modes, however it should be noted:
- In direct mode the same ISR will be called as a re-entrant function. If the function makes use of global state it will need to be done carefully.
- In vectored mode a different ISR will be called. This allows assumptions to be made about global state, for example a timer ISR wont be called recusively, and we may not need to save some CSR values.
- Finally it is possible in vectored mode to have an ISR is enabled recusively. That needs to be handled with care.
Enabling Nested Interrupt Handling
Recall these steps occur automatically before entry to an interrupt:
- mstatus.mpie = 1, save previous interrupt enable (which must have been true!).
- mstatus.mie = 0, interrupts are disabled.
To enable nested interrupts we simply need to re-enable interrupts:
- mstatus.mie = 1
It is possible to do that, but consider what will happen to the global state when the next interrupt is entered. Global state in the CSRs will be overwritten and the return address and return state will be lost.
Managing Global State
On entry to an ISR RISC-V specifies that:
- mcause.interrupt is set according to the type.;
- mcause.exception_code = I
- mstatus.mpie = 1, save previous interrupt enable. (See ISR stack.)
- mstatus.mpp = Previous privilege mode (m, s or u).
- mstatus.mie = 0, interrupts are disabled unless the ISR re-writes this.
- mepc is used to save Interrupted PC, save the return address.
At the entry to an ISR, before a enabling a nested interrupt this state needs to be saved. The extent of the state to be stored depends on the system. If vectored mode is used the mcause register may not be of interest.
At the exit of an ISR, nested interrupts must be disabled and the global state restored.
The flow is as follows:
- Save mstatus and mepc to a local variable.
- Optional: Make a copy of mcause if it is of interrest.
- Optional: Save mie and update it to enable only the interrupts of interest.
- Write 1 to mstatus.mie
- Do the work of the ISR
- Write 0 to mstatus.mie. (can be merged with the next step)
- Restore mstatus.mpie and mstatus.mpp from the saved
mstatus
value. - Restore mepc from a local variable.
- Execute
mret
Exceptions
In the case of exceptions, the mstatus.mpie will be of value. It is possible to enter an exception when mstatus.mie is disabled.
Ensure no exceptions can occur in an ISR while mstatus.mie is disabled as the global state will be clobbered and the return address in mepc lost.
For example no page faults or memory protection.
An Example in Code
Vectored mode will allow a different ISR functions for exceptions, and the various interrupts defined in the mip register.
However, when using an external interrupt controllers, such as the PLIC, only one ISR handler (the mei
) is used.
This leads to a few questions: x
- What are the use cases for multiplexing interrupts and exceptions with a single ISR function?
- How do we allow an ISR function to be interrupted, and called recursively.
- Can we do the same with vectored interrupts that are