RISC-V Interrupts
Interrupt handling in RISC-V
Hi, I’ve read RISC-V manual vol.2, presentations and many other resources on Interrupt handling, like riscv simulator source code. All these resources and other posts lack straight forward clarifications so I’ll try to go over my understanding.
I don’t want to go into gory details but rather a reasoning method, to make sense of how interrupt handling should be set up.
I’ll assume that there is a machine with PLIC as source of external interrupts at privilege mode M or S, and there are 3 privilege modes M, S and U that a HART can work in.
Let’s say we want to go over how HART would decide how to handle incoming interrupt.
1. Check pending interrupts.
Compare mip and mie registers, something like logical mip & mie. At this point we know if there are any pending interrupts that are also handled in this HART.
If we want to handle any external interrupt, no matter what privilege mode HART is in, we need to set it up in CSR mie (not to confuse with CSR mstatus.mie field).
So if we know that PLIC will generate interrupts at S-mode and we want to handle them, we need to set mie.SEIE.
If we want to handle a timer interrupt with privilege mode M - we set mie.MTIE, and so on for every interrupt type and privilege.
If you forget to set up proper bit in CSR mie for expected type and mode, then it will just be ignored.
If at this point there will be no interrupts that are both pending and flagged as handled, HART will continue uninterrupted.
2. Check interrupt privilege mode.
If there is some interrupt pending, we further check what to do with it.
All concurrent pending interrupts are checked in order of their well defined priority but I don’t want to go into that to not muddy the waters. Let’s say there is only one pending interrupt.
Now we look at privilege mode of both HART and pending interrupt.
There can be 3 cases here:
a) interrupt privilege < current HART privilege.
That’s the simplest case. If interrupt has lesser privilege mode than what is currently execute, we ignore that interrupt. Nothing happens.
For example: HART could be in M-mode and PLIC generated interrupt with S-mode. S < M so this interrupt will not change what HART is executing.
b) interrupt privilege > current HART privilege.
That’s also rather simple. If interrupt has privilege higher than current HART mode it will always stop current execution and handle that interrupt. We don’t look at any other bits - we said we want to handle some interrupt in CSR mie, such interrupt came in, it has privilege higher than what HART is currently doing so HART must go and handle such interrupt.
For example, we wanted to handle a TIMER interrupt at M-mode so we set mie.MTIE bit.
If HART is executing code in U-mode then it will be interrupted because privilege M > U. The same story is if HART was in S-mode, M > S so HART will go handle such interrupt.
c) interrupt privilege == current HART privilege.
This case is not so straight forward because it involves one more flag.
CSR mstatus has these weird bits named mstatus.MIE and mstatus.SIE. This actually tripped me badly once. These flags are called Machine/Supervisor Interrupt Enabled. I think these are named very unfortunately because they don’t actually do what their names advertise.
Up to this point we never mentioned these flags and yet we made decisions if HART will be interrupted or not. Like in b) interrupt might have privilege M, HART work in S-mode. Because M > S so HART will go and handle that interrupt. There is no check if mstatus.MIE is set or not. That’s confusing as hell.
These mstatus.MIE and mstatus.SIE flags are actually useful in case, where interrupt and HART have the same privilege level. These flags make it possible to configure if in such situation HART should be interrupted or not.
If HART mode M == interrupt mode M and mstatus.MIE is set then we will handle this interrupt. If mstatus.MIE is not set, this interrupt will not be handled at all.
Same case when HART mode S == interrupt mode S. If mstatus.SIE is set then our interrupt at privilege level S will interrupt our HART’s S-mode execution to handle it.
At this point we know exactly if HART will be interrupted or not.
3. Check where to handle interrupt.
If previous checks lead to decision that HART will be interrupted, what’s left is to figure out where it will be handled.
By default all interrupts will try to be handled in M-mode by jumping to handler in CSR mtvec.
What we can do is to delegate interrupts from M-mode to S-mode.
There’s this CSR mideleg that has a flag for every interrupt type and mode, just like CSR mie, where we set flags for interrupts that we want to be handled.
If flag in mideleg for the exact type and mode of interrupt as we want to handle is set, then HART will not jump to mtvec, but switch to S-mode and jump to handler from stvec.
That’s it. Now we know if, when and where interrupt will be handled.
That’s the way I reasoned about interrupts in my hobby OS that seems to be handling them correctly, be it by luck or by proper implementation.
RISCV · OSDEV
risc-v osdev trap