- Code Samples
- The Machine Mode Interrupts
- Installing an Interrupt Handler with GCC
- Enable and Service an Interrupt in C++
- Pre-emption
- Running the Example
Code Samples
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
registers
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 mti
, mei
, msi
.
For a simple embedded application platform-specific machine-level
interrupt sources can extend the mip
and mie
registers to use the
bits above 16 to gain more interrupts without an external interrupt
controller.
However, it is expected most cores would be integrated with an
external interrupt controller. The SiFive target device uses the PLIC
(Platform Local Interrupt
Controller)
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
mtimecmp
is set. - Our IRQ handler is installed to mtvec. For simplicity direct mode is used.
- The particular interrupt we need is enabled in
mie
, in this casemti
. - The global interrupt enable
mie
in 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 mtimecmp
.
Pre-emption
The mie
bit controls pre-emption. The action on interrupt as explained here, however in summary:
- The
mie
is cleared on entry to the ISR and the previous value is saved to thempie
. - By default pre-emption cannot occur, however you may set
mie
within your handler and enable pre-emption. - On exit the
mpie
is restored tomie
, if you enable pre-emption you should make sure to savempie
and alsompp
in mstatus.
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 mti
interrupt.