Earlier in our introductory article on RISC-V interrupt handling, we explored the basics of RISC-V architecture, its privilege levels, register set, and control and status registers (CSRs) that are crucial for managing interrupts. In this article, we will take a deep dive into interrupt handling in RISC-V architecture, including different interrupt controllers, how the RISC-V core handles an interrupt, and example ISR implementations. Understanding interrupt handling in RISC-V architecture is increasingly important as RISC-V gains adoption across embedded, automotive, and high-performance computing domains.
RISC-V Core Local Interrupt Controller (CLIC)
The RISC-V Core Local Interrupt Controller (CLIC) is a key component for interrupt handling in RISC-V architecture. It receives interrupt signals and routes them to the hart for processing. The CLIC is responsible for managing the interrupt sources that are local to a specific RISC-V core, including the Local Interrupts that are generated within the RISC-V core — such as timer interrupts or software interrupts — as well as the External Interrupts generated by external devices or peripherals connected to the RISC-V core. Understanding the RISC-V PLIC and CLIC interrupt controllers explained together is essential for building efficient interrupt-driven RISC-V systems.
The CLIC provides a range of features to simplify interrupt handling in RISC-V architecture, including:
- Configurable interrupt priority and levels.
- Support for interrupt masking.
- Mapping the privilege levels and trigger types.
- Support for both direct and vectored interrupt handling.
- Integrated support for hardware threads (HARTs).
With support for up to 4096 interrupts, CLIC forms the basis of interrupt handling in RISC-V architecture, which is crucial for real-time and embedded applications. The management of HARTs in RISC-V through the CLIC is one of the distinguishing features of the RISC-V interrupt model.
RISC-V Platform-Level Interrupt Controller (PLIC)
While the CLIC manages the local interrupts within a RISC-V core, the RISC-V Platform-Level Interrupt Controller (PLIC) is responsible for handling interrupts that are global to the entire RISC-V platform or system-on-chip (SoC). Together, the RISC-V PLIC and CLIC interrupt controllers form a comprehensive framework — the RISC-V PLIC and CLIC interrupt controllers explained as a two-tier model: local (CLIC) and global (PLIC).
The PLIC serves as a centralized interrupt controller, providing the following key features:
Interrupt Prioritization:
The PLIC can prioritize interrupts based on their importance, ensuring that critical interrupts are handled first. This is central to efficient interrupt handling in RISC-V architecture.
Interrupt Masking:
The PLIC allows for selective masking of interrupts, enabling fine-grained control over the interrupt sources that are handled by the system. This is especially useful in systems with many HARTs in RISC-V requiring independent interrupt management.
By working in conjunction with the CLIC, the PLIC provides a comprehensive interrupt handling solution for RISC-V-based systems, ensuring efficient and scalable interrupt management across the entire platform. This model also supports Porting Linux to RISC-V, where the kernel requires a well-defined interrupt controller interface to manage interrupts across all harts.
Interrupt Flow in RISC-V
The interrupt handling flow in RISC-V architecture can be summarized as follows:
- Interrupt Occurs: An interrupt is generated, either by a local source (e.g., timer) or an external source (e.g., peripheral). This initiates the interrupt handling in RISC-V architecture sequence.
- Interrupt Detection: The CLIC or PLIC detects the interrupt and determines its source and priority.
- Interrupt Prioritization: If multiple interrupts are pending, the CLIC or PLIC prioritizes them based on their configured importance.
- Interrupt Dispatch: The CLIC or PLIC dispatches the highest-priority interrupt to the appropriate RISC-V core or HART. In multi-core systems, understanding HARTs in RISC-V is critical here, as each hart can independently receive and process interrupts.
- Interrupt Handling: The interrupted PC is stored on to the mepc register. The RISC-V core or HART enters the appropriate privilege mode (typically M-Mode), sets the CSR. The cause of the trap is stored in the mcause CSR and jumps to the interrupt vector table (stored in mtvec) to execute the Interrupt Service Routine (ISR).
- Interrupt Return: After the ISR has completed, the RISC-V core or HART returns to the previous execution state and resumes normal operation using mret/sret instructions.
Understanding this interrupt flow is crucial for designing and implementing efficient interrupt handling mechanisms in RISC-V-based systems, and forms the foundation for Porting Linux to RISC-V on new hardware platforms.
Example Interrupt Service Routine for RISC-V
To illustrate the implementation of an Interrupt Service Routine (ISR) in RISC-V, let's consider a simple example of an ISR implemented via Direct handling mode. This example demonstrates interrupt handling in RISC-V architecture for a common use case:
// Interrupt Service Routine
void isr() {
// Save the current context (register state, etc.)
…
// Determine the source of the interrupt
uint32_t mcause = read_csr(mcause);
if (mcause == TIMER)
{
// Handle the timer interrupt
handle_timer_interrupt();
}
else if (mcause == DMA)
{
// Handle the DMA interrupt
handle_dma_interrupt();
}
else …..
// Clear the interrupt source
clear_interrupt_source();
// Restore the previous context and return from the interrupt
return_from_interrupt();
}
This example illustrates the basic structure and steps involved in implementing an ISR in a RISC-V-based system. In a real-world application, the ISR would likely be more complex, handling multiple interrupt sources and performing more sophisticated interrupt handling in RISC-V architecture logic.
With respect to storing and restoring execution contexts, all these nuances are taken care by the GCC extension attribute interrupt. The below lines create ISRs for both machine and supervisor modes, supporting RISC-V systems with multiple HARTs in RISC-V privilege levels:
void machine_isr (void) __attribute__ ((interrupt ('machine')));
void supervisor_isr (void) __attribute__ ((interrupt ('supervisor')));
For vectored mode, a more elaborate table must be created such as:
void riscv_mtvec_table(void) __attribute__ ((naked, section('.text.mtvec_table') ,aligned(16)));
void riscv_mtvec_table(void) {
__asm__ volatile (
'.org riscv_mtvec_table + 0*4;'
'jal zero,riscv_mtvec_exception;' /* 0 */
'.org riscv_mtvec_table + 1*4;'
'jal zero,riscv_mtvec_ssi;' /* 1 */
'.org riscv_mtvec_table + 3*4;'
'jal zero,riscv_mtvec_msi;' /* 3 */
'.org riscv_mtvec_table + 5*4;'
'jal zero,riscv_mtvec_sti;' /* 5 */
'.org riscv_mtvec_table + 7*4;'
'jal zero,riscv_mtvec_mti;' /* 7 */
'.org riscv_mtvec_table + 9*4;'
'jal zero,riscv_mtvec_sei;' /* 9 */
'.org riscv_mtvec_table + 11*4;'
'jal zero,riscv_mtvec_mei;' /* 11 */
'.org riscv_mtvec_table + 16*4;'
…
);
}
Then the vector table address riscv_mtvec_table address must be written to mtvec with the mode set to 1.
#define RISCV_MTVEC_MODE_VECTORED 1
// Setup the IRQ handler entry point, set the mode to vectored
csr_write_mtvec((uint_xlen_t) riscv_mtvec_table | RISCV_MTVEC_MODE_VECTORED);
Reference: Vectored Interrupts | Five EmbedDev (five-embeddev.com)
Our cross-domain embedded expertise enables efficient interrupt handling and optimized performance in RISC-V-based systems, while Embedded OS Porting and BSP Development Services ensure seamless integration and reliable operation across platforms.
Conclusion
In this comprehensive article, we have explored the key concepts of interrupt handling in RISC-V architecture. We discussed the RISC-V Core Local Interrupt Controller (CLIC) and the RISC-V Platform-Level Interrupt Controller (PLIC) — together providing the full picture of RISC-V PLIC and CLIC interrupt controllers explained. We also examined HARTs in RISC-V and how they influence interrupt dispatching in multi-core designs.
By understanding the interrupt flow in RISC-V and exploring example Interrupt Service Routines, we have provided a practical foundation for implementing robust and efficient interrupt-driven systems. This knowledge is also essential for Porting Linux to RISC-V, where the OS must correctly configure the CLIC and PLIC for all HARTs in RISC-V.
As the RISC-V architecture continues to gain momentum, we hope that this article has equipped you with the basic knowledge and insights necessary to tackle the challenges of interrupt handling in RISC-V architecture in your embedded projects. To dive deeper into the world of RISC-V and learn more about interrupt handling, I recommend checking out the official RISC-V documentation and resources available online.
