Saravana Pandian Annamalai
16 June 2024 Categories: Technology,

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 in to the interrupt handling including different interrupt controllers, how RISC core handles an interrupt along with example ISR implementations.

RISC-V Core Local Interrupt Controller (CLIC)

The RISC-V Core Local Interrupt Controller (CLIC) is a key component for interrupt handling that receives interrupt signals and routes it 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 that are generated by external devices or peripherals connected to the RISC-V core.

The CLIC provides a range of features to simplify interrupt handling, 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 RISC-V interrupt handling which is crucial for real-time and embedded applications.

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).

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.

Interrupt Masking:

The PLIC allows for selective masking of interrupts, enabling fine-grained control over the interrupt sources that are handled by the system.

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.

Interrupt Flow in RISC-V

The interrupt handling flow in RISC-V 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).
  • 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.
  • 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.

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:

// 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 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.

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)

Conclusion

In this comprehensive article, we have explored the key concepts of interrupt handling in the RISC-V architecture. We discussed the RISC-V Core Local Interrupt Controller (CLIC) and the RISC-V Platform-Level Interrupt Controller (PLIC), which are essential components for efficient interrupt management.

By understanding the interrupt flow in RISC-V and exploring example Interrupt Service Routines, we provided a practical foundation for implementing robust and efficient interrupt-driven systems.

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 your RISC-V 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.

Subscribe to our Blog