Example of modifying the interrupt controller in a BSP when using InterruptAttach()

Updated: April 19, 2023

By default, when you use InterruptAttach() calls to attach an ISR to a specific interrupt, the ISR is executed on the core that receives the interrupt. Routing an interrupt to a specific core isn’t done in the kernel, but can be done in the startup code of a board support package where you would program the interrupt controller (PIC/GIC) initialization code.

You can call a new function to modify the interrupt controller to deliver an interrupt to a specific CPU (CPU0 – CPUn where n represents the CPU number), presuming the interrupt controller provides this support. By default, interrupts are delivered to CPU0 with the exception of IPIs which are delivered to specific CPUs.

The following example is for an NXP i.MX 8 ARMv8-based (aarch64) system with a GICv2 interrupt controller. Rather than modify the startup library directly, you can modify board-specific code in your BSP ($BSP_ROOT_DIR/hardware/startup/boards/imx8x/imx_init_intrinfo.c). Here's what you would do in the imx_init_intrinfo.c as shown in the bolded code :

In the init_intrinfo(void) function in the imx_init_intrinfo.c), you can call a custom function to route interrupts to a specific CPU that you create.

...
...
/**
 * Example function to Route SPI Interrupts to a Specific CPU
 *
 * @param gicd  GIC distributor base address.
 * @param aff3 (ARM_GICD_IROUTERn[39:32]) — affinity level 3, the least
 *                                           significant affinity level field
 * @param aff2 (ARM_GICD_IROUTERn[23:16]) — affinity level 2, an intermediate
 *                                           affinity level field
 * @param aff1 (ARM_GICD_IROUTERn[15:8])  — affinity level 1, an intermediate
 *                                           affinity level field
 * @param aff0 (ARM_GICD_IROUTERn[7:0])   — affinity level 0, the most significant
 *                                           affinity level field
 */
void gic_v3_intr_route(paddr_t gicd, uint8_t aff3, uint8_t aff2,
                       uint8_t aff1, uint8_t aff0)
{
    unsigned    itn, spi_vectors, i;

    /* Calculate the number of interrupt lines */
    uint32_t const gicd_typer = in32(gicd + ARM_GICD_TYPER);
    itn = ((gicd_typer & ARM_GICD_TYPER_ITLN) + 1) * 32;
    if (debug_flag > 1) {
        kprintf("GICv3: %d interrupts\n", itn);
    }

    spi_vectors = min(1020, itn) - 32;

    /* Route all SPI interrupts to the specific CPU */
    for (i = 0; i < spi_vectors; i++) {
        ((uint64_t *)(gicd + ARM_GICD_IROUTERn))[i+32] = ((uint64_t)aff3 << 32) |
                                                         ((uint64_t)aff2 << 16) |
                                                         ((uint64_t)aff1 << 8)  |
                                                         ((uint64_t)aff0 << 0);
    }

    kprintf("Route %d SPI interrupt to CPU affinity %d.%d.%d.%d\n",
             spi_vectors, aff3, aff2, aff1, aff0);
}

/**
 * Initialize Generic Interrupt Controller (GIC).
 */
void init_intrinfo(void)
{
    /* Initialize GIC  */
    gic_v3_init(IMX_GIC_GICD_BASE, IMX_GIC_GICR_BASE, IMX_GIC_GICC_BASE, 0, 0);

    /* Example to route all SPI interrupts to CPU1 (affinity 0.0.0.1) */
    gic_v3_intr_route(IMX_GIC_GICD_BASE, 0, 0, 0, 1);

    /* Add the interrupt callouts */
    add_interrupt_array(pci_steer_intr, sizeof(pci_steer_intr));
}

...
/** @} */ /* End of startup */