Recipe for configuring external interrupts on Hazard3
This post contains a quick recipe for a minimal Hazard3 external interrupt configuration on RP2350 using vectored interrupts. It is heavily based on code samples from Five EmbedDev's Vectored Interrupts article.
Vector table setup
We begin by setting up our vector table and declaring our handler functions. On RP2350, we have three types of interrupts: external interrupt, timer interrupt, and soft interrupt. When vectoring is enabled, interrupts trigger a jump to address base + 4 * cause where base is the base address of the vector table and cause is either 3 for soft interrupts, 7 for timer interrupt, or 11 for external interrupt. In addition to said entries, we will have a exception handler at base. Note that on RP2350, the base address of the vector table must be aligned to 64 bytes when vectoring is enabled.
__attribute__ ((interrupt("machine"), aligned(4)))
void default_handler(void);
__attribute__ ((interrupt("machine"), weak, alias("default_handler")))
void MTVEC_EXCEPTION_Handler(void);
__attribute__ ((interrupt("machine"), weak, alias("default_handler")))
void MTVEC_MSI_Handler(void);
__attribute__ ((interrupt("machine"), weak, alias("default_handler")))
void MTVEC_MTI_Handler(void);
__attribute__ ((interrupt("machine"), weak, alias("default_handler")))
void MTVEC_MEI_Handler(void);
__attribute__ ((naked, aligned(64)))
void __vector_table(void)
{
__asm__ volatile (
".org __vector_table + 0*4;"
"jal zero, MTVEC_EXCEPTION_Handler;"
".org __vector_table + 3*4;"
"jal zero, MTVEC_MSI_Handler;"
".org __vector_table + 7*4;"
"jal zero, MTVEC_MTI_Handler;"
".org __vector_table + 11*4;"
"jal zero, MTVEC_MEI_Handler;"
: /* no output */
: /* no input */
: /* no clobbers */);
}
Initialization
For simple Hazard3 interrupt configuration, the initialization sequence is rather straightforward:
- Disable interrupts by setting bit
3ofmstatusregister. - Write
0tomieregister. - Write the address of
__vector_tableOR'd with0x1tomtvec. Setting the first bit of the register enables vectoring. - Enable external interrupts by setting bit
11ofmieregister. - Enable interrupts by setting bit
3ofmstatusregister.
static inline void csr_set_bits_mstatus(uint32_t mask) {
__asm__ volatile ("csrrs zero, mstatus, %0"
: /* output: none */
: "r" (mask) /* input : register */
: /* clobbers: none */);
}
static inline void csr_clr_bits_mstatus(uint32_t mask) {
__asm__ volatile ("csrrc zero, mstatus, %0"
: /* output: none */
: "r" (mask) /* input : from register */
: /* clobbers: none */);
}
static inline void csr_write_mtvec(uint32_t value) {
__asm__ volatile ("csrw mtvec, %0"
: /* output: none */
: "r" (value) /* input : from register */
: /* clobbers: none */);
}
static inline void csr_write_mie(uint32_t value) {
__asm__ volatile ("csrw mie, %0"
: /* output: none */
: "r" (value) /* input : from register */
: /* clobbers: none */);
}
static inline void csr_set_bits_mie(uint32_t mask) {
__asm__ volatile ("csrrs zero, mie, %0"
: /* output: none */
: "r" (mask) /* input : register */
: /* clobbers: none */);
}
void irq_init(void) {
csr_clr_bits_mstatus(0x8U);
csr_write_mie(0);
csr_write_mtvec((uint32_t)&__vector_table | 0x1U);
csr_set_bits_mie(0x800);
csr_set_bits_mstatus(0x8);
}
Enable IRQ
To enable a single external interrupt request (IRQ), a corresponding entry in the external interrupt enable array must be set in meiea (CSR offset 0xbe0) register. Write-only and self-clearing field covering bits [4:0] of the register represent the array index and bits in field covering [31:16] represent which interrupts are disabled/enabled in the given entry. For example, the RP2350 UART0_IRQ interrupt source has IRQ number 33, and thus the index is 33 / 16 = 2 and bit 33 % 16 = 1 of the disable/enable field needs to be set in order to enable the IRQ.
static inline void csr_set_bits_meiea(uint32_t mask) {
__asm__ volatile ("csrrs zero, 0xbe0, %0"
: /* output: none */
: "r" (mask) /* input : register */
: /* clobbers: none */);
}
void irq_enable(uint32_t irq_no)
{
uint32_t index = irq_no / 16U;
uint32_t mask = 1U << (irq_no % 16U);
csr_set_bits_meiea(index | (mask << 16));
}
External interrupt handler
In all its simplicity, the external interrupt handler will:
- Save the context of the caller.
- Poll and execute the next pending interrupt until there are none.
- Restore the context of the caller.
The next pending external interrupt is obtained by checking the value in bits [10:2] of the register meinext. If bit 31 of the register is set, there are no external interrupts pending.
void (*const irq_handlers[52])(void) = {
/* ... */
[33] = &UART0_IRQ_Handler,
/* ... */
};
static inline void irq_save_context(void)
{
__asm__ volatile (
"addi sp, sp, -64;"
"sw ra, 0(sp);"
"sw t0, 4(sp);"
"sw t1, 8(sp);"
"sw t2, 12(sp);"
"sw a0, 16(sp);"
"sw a1, 20(sp);"
"sw a2, 24(sp);"
"sw a3, 28(sp);"
"sw a4, 32(sp);"
"sw a5, 36(sp);"
"sw a6, 40(sp);"
"sw a7, 44(sp);"
"sw t3, 48(sp);"
"sw t4, 52(sp);"
"sw t5, 56(sp);"
"sw t6, 60(sp);");
}
static inline void irq_restore_context(void)
{
__asm__ volatile (
"lw ra, 0(sp);"
"lw t0, 4(sp);"
"lw t1, 8(sp);"
"lw t2, 12(sp);"
"lw a0, 16(sp);"
"lw a1, 20(sp);"
"lw a2, 24(sp);"
"lw a3, 28(sp);"
"lw a4, 32(sp);"
"lw a5, 36(sp);"
"lw a6, 40(sp);"
"lw a7, 44(sp);"
"lw t3, 48(sp);"
"lw t4, 52(sp);"
"lw t5, 56(sp);"
"lw t6, 60(sp);"
"addi sp, sp, 64;");
}
__attribute__ ((interrupt ("machine"), aligned(4)))
void MTVEC_MEI_Handler(void)
{
irq_save_context();
uint32_t val = csr_read_meinext();
while ((val & (1U << 31)) == 0) {
uint32_t irq_no = val >> 2;
if (irq_handlers[irq_no] != 0x0)
(irq_handlers[irq_no])();
val = csr_read_meinext();
}
irq_restore_context();
}