- Code Samples
- The Machine Mode Interrupts
- Installing an Interrupt Handler with GCC
- Enable and Service an Interrupt in C++
- Running the Example
These code samples relate to this article:
The Machine Mode Interrupts
The RISC-V ISA is not specialized for embedded applications (compared to, say, the ARM Cortex-M). Keeping this in mind, the base ISA core local interrupt (CLINT) handing is limited - an interrupt controller is not in the core ISA specification.
A more general desciption is included in SiFive’s Interrupt Cookbook. This article focuses on building a small example in C and C++ to implement CLINT machine mode interrupt handling for any RISC-V core.
The machine level ISA standard machine mode interrupt
define the available interrupts. In a given privilege level there is a
timer, an external interrupt and a software interrupt, for machine
mode those are
For a simple embedded application platform-specific machine-level
interrupt sources can extend the
mie registers to use the
bits above 16 to gain more interrupts without an external interrupt
However, it is expected most cores would be integrated with an
external interrupt controller. The SiFive target device uses the PLIC
(Platform Local Interrupt
which connects to the
mei external interrupt.
The CLIC defines a new core local interrupt controller more appropriate for embedded applications.
Installing an Interrupt Handler with GCC
We need to use some GCC compiler extensions when declaring functions used as interrupt handlers.
Firstly, the function needs to be declared an interrupt handler. This will enable the correct register context saving and use of the
mret instruction on return.
Secondly, we should ensure the function is aligned correctly, as the
mtvec register will clip the 2 lsb.
Enable and Service an Interrupt in C++
The example for this post is on github baremetal-startup-cxx/src/startup.cpp.
In the example below the timer interrupt is enabled. We need to ensure 4 things to get a timer interrupt:
- The timer compare register
- Our IRQ handler is installed to mtvec. For simplicity direct mode is used.
- The particular interrupt we need is enabled in
mie, in this case
- The global interrupt enable
miein mstatus is set.
In direct mode servicing the interrupt requires us to read the cause from mcause. We need to clear the interrupt at the source, pending bits are automatically cleared. The interrupt enables of the core are automatically managed by hardware at entry/exit of the handler.
For the timer interrupt below
mip.mti is cleared by updating
mie bit controls pre-emption. The action on interrupt as explained here, however in summary:
mieis cleared on entry to the ISR and the previous value is saved to the
- By default pre-emption cannot occur, however you may set
miewithin your handler and enable pre-emption.
- On exit the
mpieis restored to
mie, if you enable pre-emption you should make sure to save
Running the Example
The C example program and C++ example program can be compiled with CMake and a RISC-V cross compiler, such as xPack GNU RISC-V Embedded GCC. It has been tested with a SiFive HiFive RevB, but is designed to be a generic 32 bit RISC-V example.
It enables a period 1 second machine timer, and the ISR
irq_entry demultiplexes the interrupt cause and saves the timestamp value to a global variable
timestamp on an