PLIC
The PLIC is the RISC-V equivalent of the PIC for x86 platforms. Without it, no external interrupts can be received (on "standard" platforms, anyways).
Quickstart
First, figure out the start address & size of the PLIC MMIO region & map it. In the DTB there should be an entry like this:
plic@c000000 { phandle = < 0x03 >; riscv,ndev = < 0x35 >; reg = < 0x00 0xc000000 0x00 0x210000 >; interrupts-extended = < 0x02 0x0b 0x02 0x09 >; interrupt-controller; compatible = "riscv,plic0"; #interrupt-cells = < 0x01 >; #address-cells = < 0x00 >; };
Then, determine which interrupt source(s) to enable. For example, to receive interrupts from the UART device, interrupt 0x0A must be enabled:
uart@10000000 { interrupts = < 0x0a >; interrupt-parent = < 0x03 >; clock-frequency = < 0x384000 >; reg = < 0x00 0x10000000 0x00 0x100 >; compatible = "ns16550a"; };
Interrupts are enabled on a per-context basis. The exact meaning of a context is implementation-dependent but generally even-numbered contexts (0, 2, ...) refer to M-mode of hart 0, 1, ... while odd-numbered contexts (1, 3, ...) refer to S-mode of hart 0, 1, ...
For example, to enable interrupt 0x0A for S-mode in hart 0 the 10th bit at base + 0x2000 + 0x80 * context + source / 32
must be toggled. Note that the registers are all 32 bits!
The priority and priority thresholds must be set to actually enable the interrupt. base + 0x0 + 0x4 * source
must be set to any non-zero value and base + 0x200000 + 0x1000 * context
to any value lower than the priority.
Finally, enable any interrupts on the UART device:
uart_base[0] = 0x00; // Disable DLAB uart_base[1] = 0x01; // Enable data available interrupts
If the trap handler is set up properly, an external interrupt should be triggered when any characters are sent.
Handling interrupts
To begin handling an interrupt, the interrupt must be claimed by reading base + 0x200000 + 0x4 + 0x1000 * context
, which will return the source. This automatically clears the pending bit.
The interrupt must be claimed before returning to userspace! Not doing so will cause it to be triggered again immediately.
When the interrupt handler has finished, write the source to the same address to indicate it is completed.
Note: the interrupt should not be marked as completed until whatever caused it has actually been dealt with, e.g. don't mark an interrupt from UART as completed until all the data has been read.
See Also
External Links
- RISC-V Platform-Level Interrupt Controller Specification
- The RISC-V Instruction Set Manual, Volume II: Privileged Architecture (draft 2018-12-01). Chapter 7 describes the PLIC in more detail. Supposedly it was moved to another document but I'm unable to find it.