- 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.
static void irq_entry(void) __attribute__ ((interrupt ("machine")));Secondly, we should ensure the function is aligned correctly, as the mtvec register will clip the 2 lsb.
#pragma GCC push_options
// Force the alignment for mtvec.BASE.
#pragma GCC optimize ("align-functions=4")
void irq_entry(void) {
...
}
#pragma GCC pop_optionsEnable 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
mtimecmpis 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
miein mstatus is set.
mtimer.set_time_cmp(std::chrono::seconds{1});
// Setup the IRQ handler entry point
riscv::csrs.mtvec.write( reinterpret_cast<std::uintptr_t>(irq_entry));
// Timer interrupt enable
riscv::csrs.mie.mti.set();
// Global interrupt enable
riscv::csrs.mstatus.mie.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.
auto this_cause = riscv::csrs.mcause.read();
if (this_cause & riscv::csr::mcause_data::interrupt::BIT_MASK) {
this_cause &= 0xFF;
// Known exceptions
switch (this_cause) {
case riscv::interrupts::mti :
// Reset the timer to keep up the one second tick.
mtimer.set_time_cmp(std::chrono::seconds{1});
break;
}
}Pre-emption
The mie bit controls pre-emption. The action on interrupt as explained here, however in summary:
- The
mieis cleared on entry to the ISR and the previous value is saved to thempie. - By default pre-emption cannot occur, however you may set
miewithin your handler and enable pre-emption. - On exit the
mpieis restored tomie, if you enable pre-emption you should make sure to savempieand alsomppin 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.
Five EmbedDev