Dan's Blog

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:

  1. Disable interrupts by setting bit 3 of mstatus register.
  2. Write 0 to mie register.
  3. Write the address of __vector_table OR'd with 0x1 to mtvec. Setting the first bit of the register enables vectoring.
  4. Enable external interrupts by setting bit 11 of mie register.
  5. Enable interrupts by setting bit 3 of mstatus register.


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:

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();
}

End of the recipe