The Platform Interrupt Controller (PIC) framework provides a unified abstraction layer for managing hardware interrupts in RT-Thread's Device Driver Model. It supports various interrupt controllers including ARM GIC (Generic Interrupt Controller) v1/v2/v3, and provides comprehensive interrupt management capabilities including cascading, MSI support, and inter-processor interrupts (IPI).
The PIC framework consists of:
pic.c): Central interrupt management and routingstruct rt_pic_irq): Individual interrupt descriptorstruct rt_pic_ops): Hardware-specific callbacksISR Management: Interrupt Service Routine registration and execution
┌──────────────────────────────────────────┐
│ Application/Drivers │
│ (Call rt_pic_attach_irq, enable, etc.) │
└──────────────────┬───────────────────────┘
│
┌──────────────────▼───────────────────────┐
│ PIC Framework (pic.c) │
│ - IRQ allocation & management │
│ - ISR dispatch │
│ - Cascading support │
│ - Affinity & priority management │
└──────────────────┬───────────────────────┘
│
┌──────────────────▼───────────────────────┐
│ PIC Operations (rt_pic_ops) │
│ - irq_enable/disable │
│ - irq_mask/unmask │
│ - irq_set_priority │
│ - irq_set_affinity │
│ - irq_send_ipi (for multi-core) │
└──────────────────┬───────────────────────┘
│
┌──────────────────▼───────────────────────┐
│ Hardware Interrupt Controller │
│ (GICv2, GICv3, etc.) │
└──────────────────────────────────────────┘
RT-Thread Components
└── Device Drivers
└── Using Device Driver Model with DeviceTree (RT_USING_DM)
└── Using Programmable Interrupt Controller (PIC) (RT_USING_PIC)
gic: interrupt-controller@08000000 {
compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic";
#interrupt-cells = <3>;
#address-cells = <1>;
interrupt-controller;
reg = <0x08000000 0x1000>, /* Distributor */
<0x08010000 0x1000>; /* CPU interface */
};
/* Consumer example: UART using GIC */
uart0: serial@09000000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0x09000000 0x1000>;
interrupts = <0 5 4>; /* SPI, IRQ 5, IRQ_TYPE_LEVEL_HIGH */
interrupt-parent = <&gic>;
};
gic: interrupt-controller@08000000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
#address-cells = <2>;
#size-cells = <2>;
interrupt-controller;
reg = <0x0 0x08000000 0 0x10000>, /* Distributor */
<0x0 0x080A0000 0 0xF60000>; /* Redistributor */
gic_its: msi-controller@08020000 {
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>;
reg = <0x0 0x08020000 0 0x20000>;
};
};
/* Consumer with interrupts */
timer {
compatible = "arm,armv8-timer";
interrupts = <1 13 0xff08>, /* Physical Secure PPI */
<1 14 0xff08>, /* Physical Non-Secure PPI */
<1 11 0xff08>, /* Virtual PPI */
<1 10 0xff08>; /* Hypervisor PPI */
interrupt-parent = <&gic>;
};
For ARM GIC, interrupts property has 3 cells:
IRQ_TYPE_EDGE_RISING = 1IRQ_TYPE_EDGE_FALLING = 2IRQ_TYPE_LEVEL_HIGH = 4IRQ_TYPE_LEVEL_LOW = 8rt_err_t rt_pic_attach_irq(int irq, rt_isr_handler_t handler, void *uid,
const char *name, int flags);
Attach an interrupt service routine to an IRQ.
Parameters:
irq: IRQ number to attach tohandler: ISR function pointer void (*handler)(int irq, void *param)uid: User-defined parameter passed to the handlername: Descriptive name for the interrupt (for debugging)flags: Interrupt flags (currently unused, pass 0)Returns:
RT_EOK: Success-RT_EINVAL: Invalid IRQ number-RT_ENOMEM: Out of memoryExample:
void uart_isr(int irq, void *param)
{
struct uart_device *uart = (struct uart_device *)param;
/* Handle interrupt */
rt_kprintf("UART interrupt occurred\n");
}
/* In driver probe function */
static rt_err_t uart_probe(struct rt_platform_device *pdev)
{
struct uart_device *uart;
int irq;
/* Get IRQ from device tree */
irq = rt_platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* Attach ISR */
rt_err_t ret = rt_pic_attach_irq(irq, uart_isr, uart, "uart0", 0);
if (ret != RT_EOK)
return ret;
/* Enable the interrupt */
rt_pic_irq_enable(irq);
return RT_EOK;
}
rt_err_t rt_pic_detach_irq(int irq, void *uid);
Detach an interrupt service routine from an IRQ.
Parameters:
irq: IRQ numberuid: Must match the uid passed to rt_pic_attach_irqReturns:
RT_EOK: Success-RT_EINVAL: Invalid IRQ or UID mismatchvoid rt_pic_irq_enable(int irq);
void rt_pic_irq_disable(int irq);
Enable or disable an interrupt at the PIC level.
Parameters:
irq: IRQ numberExample:
/* Enable interrupt before starting operation */
rt_pic_irq_enable(uart_irq);
/* Disable during critical sections */
rt_pic_irq_disable(uart_irq);
/* ... critical code ... */
rt_pic_irq_enable(uart_irq);
void rt_pic_irq_mask(int irq);
void rt_pic_irq_unmask(int irq);
Mask or unmask an interrupt (similar to enable/disable but may have different semantics on some hardware).
Parameters:
irq: IRQ numbervoid rt_pic_irq_ack(int irq);
Acknowledge an interrupt (required for some edge-triggered interrupts).
Parameters:
irq: IRQ numbervoid rt_pic_irq_eoi(int irq);
Signal End-Of-Interrupt to the PIC.
Parameters:
irq: IRQ numberrt_err_t rt_pic_irq_set_priority(int irq, rt_uint32_t priority);
Set the priority of an interrupt.
Parameters:
irq: IRQ numberpriority: Priority value (lower = higher priority typically)Returns:
RT_EOK: Success-RT_ENOSYS: Not supported by hardwareExample:
/* Set high priority for critical timer interrupt */
rt_pic_irq_set_priority(timer_irq, 0);
/* Set lower priority for UART */
rt_pic_irq_set_priority(uart_irq, 128);
rt_uint32_t rt_pic_irq_get_priority(int irq);
Get the current priority of an interrupt.
Parameters:
irq: IRQ numberReturns: Current priority value
rt_err_t rt_pic_irq_set_affinity(int irq, rt_bitmap_t *affinity);
Set which CPUs can handle a specific interrupt.
Parameters:
irq: IRQ numberaffinity: Bitmap of allowed CPUsReturns:
RT_EOK: Success-RT_ENOSYS: Not supportedExample:
RT_IRQ_AFFINITY_DECLARE(cpumask);
/* Route interrupt to CPU 0 and CPU 2 */
rt_bitmap_clear(cpumask, RT_BITMAP_LEN(RT_CPUS_NR));
RT_IRQ_AFFINITY_SET(cpumask, 0);
RT_IRQ_AFFINITY_SET(cpumask, 2);
rt_pic_irq_set_affinity(eth_irq, cpumask);
rt_err_t rt_pic_irq_get_affinity(int irq, rt_bitmap_t *out_affinity);
Get the current CPU affinity for an interrupt.
Parameters:
irq: IRQ numberout_affinity: Output bitmapReturns:
RT_EOK: Success-RT_EINVAL: Invalid IRQrt_err_t rt_pic_irq_set_triger_mode(int irq, rt_uint32_t mode);
Set the trigger mode for an interrupt.
Parameters:
irq: IRQ numbermode: Trigger mode
RT_IRQ_MODE_EDGE_RISING: Rising edgeRT_IRQ_MODE_EDGE_FALLING: Falling edgeRT_IRQ_MODE_EDGE_BOTH: Both edgesRT_IRQ_MODE_LEVEL_HIGH: High levelRT_IRQ_MODE_LEVEL_LOW: Low levelReturns:
RT_EOK: Success-RT_ENOSYS: Not supportedrt_uint32_t rt_pic_irq_get_triger_mode(int irq);
Get the current trigger mode.
Parameters:
irq: IRQ numberReturns: Current trigger mode
void rt_pic_irq_send_ipi(int irq, rt_bitmap_t *cpumask);
Send an inter-processor interrupt to specified CPUs.
Parameters:
irq: IPI number (typically in range 0-15)cpumask: Bitmap of target CPUsExample:
RT_IRQ_AFFINITY_DECLARE(targets);
/* Send IPI to CPU 1 */
rt_bitmap_clear(targets, RT_BITMAP_LEN(RT_CPUS_NR));
RT_IRQ_AFFINITY_SET(targets, 1);
rt_pic_irq_send_ipi(0, targets); /* IPI 0 */
rt_err_t rt_pic_irq_set_state(int irq, int type, rt_bool_t state);
rt_err_t rt_pic_irq_get_state(int irq, int type, rt_bool_t *out_state);
Set or get interrupt state (pending, active, masked).
Parameters:
irq: IRQ numbertype: State type
RT_IRQ_STATE_PENDING: Interrupt is pendingRT_IRQ_STATE_ACTIVE: Interrupt is being servicedRT_IRQ_STATE_MASKED: Interrupt is maskedstate / out_state: State valueReturns:
RT_EOK: Success-RT_ENOSYS: Not supported#include <rtthread.h>
#include <rtdevice.h>
#include <drivers/pic.h>
#include <drivers/platform.h>
struct gpio_int_device
{
struct rt_device parent;
void *base;
int irq;
rt_uint32_t pin_mask;
};
/* GPIO interrupt service routine */
static void gpio_isr(int irq, void *param)
{
struct gpio_int_device *gpio = (struct gpio_int_device *)param;
rt_uint32_t status;
/* Read interrupt status register */
status = readl(gpio->base + GPIO_INT_STATUS);
/* Clear interrupt */
writel(status, gpio->base + GPIO_INT_CLEAR);
/* Handle each pin */
for (int i = 0; i < 32; i++)
{
if (status & (1 << i))
{
rt_kprintf("GPIO pin %d triggered\n", i);
/* Notify application or trigger event */
}
}
/* Send EOI to PIC */
rt_pic_irq_eoi(irq);
}
static rt_err_t gpio_int_probe(struct rt_platform_device *pdev)
{
struct gpio_int_device *gpio;
struct rt_device *dev = &pdev->parent;
int irq;
rt_err_t ret;
gpio = rt_calloc(1, sizeof(*gpio));
if (!gpio)
return -RT_ENOMEM;
/* Map registers */
gpio->base = rt_dm_dev_iomap(dev, 0);
if (!gpio->base)
{
ret = -RT_ERROR;
goto err_free;
}
/* Get IRQ from device tree */
irq = rt_platform_get_irq(pdev, 0);
if (irq < 0)
{
ret = irq;
goto err_iounmap;
}
gpio->irq = irq;
/* Set interrupt trigger mode - rising edge */
ret = rt_pic_irq_set_triger_mode(irq, RT_IRQ_MODE_EDGE_RISING);
if (ret != RT_EOK && ret != -RT_ENOSYS)
{
rt_kprintf("Failed to set trigger mode: %d\n", ret);
goto err_iounmap;
}
/* Set interrupt priority (high priority) */
ret = rt_pic_irq_set_priority(irq, 32);
if (ret != RT_EOK && ret != -RT_ENOSYS)
{
rt_kprintf("Failed to set priority: %d\n", ret);
}
/* Attach interrupt handler */
ret = rt_pic_attach_irq(irq, gpio_isr, gpio, "gpio-int", 0);
if (ret != RT_EOK)
{
rt_kprintf("Failed to attach IRQ: %d\n", ret);
goto err_iounmap;
}
/* Configure GPIO pins for interrupt */
writel(0xFFFFFFFF, gpio->base + GPIO_INT_ENABLE); /* Enable all pins */
writel(0x00000000, gpio->base + GPIO_INT_MASK); /* Unmask all */
/* Enable interrupt at PIC level */
rt_pic_irq_enable(irq);
/* Store device data */
rt_platform_set_drvdata(pdev, gpio);
rt_kprintf("GPIO interrupt controller probed successfully (IRQ %d)\n", irq);
return RT_EOK;
err_iounmap:
rt_dm_dev_iounmap(dev, gpio->base);
err_free:
rt_free(gpio);
return ret;
}
static rt_err_t gpio_int_remove(struct rt_platform_device *pdev)
{
struct gpio_int_device *gpio = rt_platform_get_drvdata(pdev);
/* Disable interrupt */
rt_pic_irq_disable(gpio->irq);
/* Detach handler */
rt_pic_detach_irq(gpio->irq, gpio);
/* Disable GPIO interrupts */
writel(0x00000000, gpio->base + GPIO_INT_ENABLE);
/* Cleanup */
rt_dm_dev_iounmap(&pdev->parent, gpio->base);
rt_free(gpio);
return RT_EOK;
}
static const struct rt_ofw_node_id gpio_int_ofw_ids[] =
{
{ .compatible = "myvendor,gpio-interrupt-controller" },
{ /* sentinel */ }
};
static struct rt_platform_driver gpio_int_driver =
{
.name = "gpio-int",
.ids = gpio_int_ofw_ids,
.probe = gpio_int_probe,
.remove = gpio_int_remove,
};
static int gpio_int_drv_register(void)
{
rt_platform_driver_register(&gpio_int_driver);
return 0;
}
INIT_DEVICE_EXPORT(gpio_int_drv_register);
For implementing a custom interrupt controller driver:
#include <rtthread.h>
#include <drivers/pic.h>
struct my_pic_data
{
void *base;
struct rt_pic pic;
};
static void my_pic_irq_enable(struct rt_pic_irq *pirq)
{
struct rt_pic *pic = pirq->pic;
struct my_pic_data *priv = pic->priv_data;
/* Enable interrupt in hardware */
writel(1 << pirq->hwirq, priv->base + MY_PIC_ENABLE_REG);
}
static void my_pic_irq_disable(struct rt_pic_irq *pirq)
{
struct rt_pic *pic = pirq->pic;
struct my_pic_data *priv = pic->priv_data;
/* Disable interrupt in hardware */
writel(1 << pirq->hwirq, priv->base + MY_PIC_DISABLE_REG);
}
static void my_pic_irq_ack(struct rt_pic_irq *pirq)
{
struct rt_pic *pic = pirq->pic;
struct my_pic_data *priv = pic->priv_data;
/* Acknowledge interrupt */
writel(1 << pirq->hwirq, priv->base + MY_PIC_ACK_REG);
}
static void my_pic_irq_eoi(struct rt_pic_irq *pirq)
{
struct rt_pic *pic = pirq->pic;
struct my_pic_data *priv = pic->priv_data;
/* Send EOI signal */
writel(1 << pirq->hwirq, priv->base + MY_PIC_EOI_REG);
}
static rt_err_t my_pic_irq_set_priority(struct rt_pic_irq *pirq, rt_uint32_t priority)
{
struct rt_pic *pic = pirq->pic;
struct my_pic_data *priv = pic->priv_data;
/* Set priority in hardware register */
writel(priority, priv->base + MY_PIC_PRIORITY_REG(pirq->hwirq));
return RT_EOK;
}
static int my_pic_irq_map(struct rt_pic *pic, int hwirq, rt_uint32_t mode)
{
int irq;
/* Allocate software IRQ number */
irq = pic->irq_start + hwirq;
/* Configure trigger mode in hardware */
/* ... */
return irq;
}
static rt_err_t my_pic_irq_parse(struct rt_pic *pic, struct rt_ofw_cell_args *args,
struct rt_pic_irq *out_pirq)
{
if (args->args_count != 2)
return -RT_EINVAL;
/* Parse: <hwirq trigger_type> */
out_pirq->hwirq = args->args[0];
out_pirq->mode = args->args[1];
return RT_EOK;
}
static const struct rt_pic_ops my_pic_ops =
{
.name = "my-pic",
.irq_enable = my_pic_irq_enable,
.irq_disable = my_pic_irq_disable,
.irq_ack = my_pic_irq_ack,
.irq_eoi = my_pic_irq_eoi,
.irq_set_priority = my_pic_irq_set_priority,
.irq_map = my_pic_irq_map,
.irq_parse = my_pic_irq_parse,
};
static rt_err_t my_pic_probe(struct rt_platform_device *pdev)
{
struct my_pic_data *priv;
rt_err_t ret;
priv = rt_calloc(1, sizeof(*priv));
if (!priv)
return -RT_ENOMEM;
/* Map registers */
priv->base = rt_dm_dev_iomap(&pdev->parent, 0);
if (!priv->base)
{
ret = -RT_ERROR;
goto err_free;
}
/* Initialize PIC structure */
priv->pic.ops = &my_pic_ops;
priv->pic.priv_data = priv;
/* Allocate IRQ range (32 interrupts) */
ret = rt_pic_linear_irq(&priv->pic, 32);
if (ret != RT_EOK)
{
rt_kprintf("Failed to allocate IRQs\n");
goto err_iounmap;
}
/* Configure each IRQ */
for (int i = 0; i < 32; i++)
{
rt_pic_config_irq(&priv->pic, i, i); /* Map 1:1 */
}
/* Call user extensions if needed */
rt_pic_user_extends(&priv->pic);
/* Initialize hardware */
writel(0xFFFFFFFF, priv->base + MY_PIC_DISABLE_REG); /* Disable all */
writel(0xFFFFFFFF, priv->base + MY_PIC_CLEAR_REG); /* Clear pending */
rt_platform_set_drvdata(pdev, priv);
rt_kprintf("My PIC initialized with %d IRQs\n", 32);
return RT_EOK;
err_iounmap:
rt_dm_dev_iounmap(&pdev->parent, priv->base);
err_free:
rt_free(priv);
return ret;
}
static const struct rt_ofw_node_id my_pic_ofw_ids[] =
{
{ .compatible = "myvendor,my-pic" },
{ /* sentinel */ }
};
static struct rt_platform_driver my_pic_driver =
{
.name = "my-pic",
.ids = my_pic_ofw_ids,
.probe = my_pic_probe,
};
RT_PIC_OFW_DECLARE(my_pic, my_pic_ofw_ids, my_pic_probe);
Always disable interrupts during cleanup:
rt_pic_irq_disable(irq);
rt_pic_detach_irq(irq, uid);
Handle spurious interrupts:
static void my_isr(int irq, void *param)
{
if (!check_interrupt_source())
return; /* Spurious interrupt */
/* Handle interrupt */
}
Use EOI for level-triggered interrupts:
static void my_isr(int irq, void *param)
{
handle_interrupt();
clear_interrupt_source(); /* Clear in device first */
rt_pic_irq_eoi(irq); /* Then send EOI to PIC */
}
Set appropriate priorities:
Consider CPU affinity on multi-core systems:
/* Pin network interrupt to CPU 0 for better cache locality */
RT_IRQ_AFFINITY_DECLARE(mask);
rt_bitmap_clear(mask, RT_BITMAP_LEN(RT_CPUS_NR));
RT_IRQ_AFFINITY_SET(mask, 0);
rt_pic_irq_set_affinity(net_irq, mask);
Implement minimum required operations:
irq_enable, irq_disableirq_ack or irq_eoiirq_map, irq_parseUse rt_pic_linear_irq for simple IRQ allocation:
ret = rt_pic_linear_irq(pic, num_irqs);
Support cascading for hierarchical interrupt controllers:
struct rt_pic_irq *parent_pirq = rt_pic_find_pirq(parent_pic, parent_irq);
rt_pic_cascade(child_pirq, parent_irq);
Implement statistics support (optional):
#ifdef RT_USING_PIC_STATISTICS
/* Framework tracks this automatically */
#endif
Check interrupt is enabled:
rt_pic_irq_enable(irq);
Verify device interrupt is not masked:
Confirm device tree configuration:
interrupts propertyinterrupt-parent is correctCheck trigger mode:
Always acknowledge/clear interrupts:
/* Clear source BEFORE EOI */
clear_device_interrupt();
rt_pic_irq_eoi(irq);
For level-triggered, clear condition:
Verify IRQ number:
rt_kprintf("Attached to IRQ %d\n", irq);
Check MAX_HANDLERS:
Confirm attachment succeeded:
ret = rt_pic_attach_irq(irq, isr, param, "name", 0);
if (ret != RT_EOK)
rt_kprintf("Attach failed: %d\n", ret);
-RT_ENOSYSProfile with statistics:
/* Enable RT_USING_PIC_STATISTICS */
/* Check /proc or debug output for timing data */
interrupt-parent resolutionrt_platform_get_irq for IRQ retrievalcomponents/drivers/pic/pic.c - Core implementationcomponents/drivers/include/drivers/pic.h - API header