request_irq and free_irq
Registering interrupt handlers with the kernel
The basic API
#include <linux/interrupt.h>
/* Register an interrupt handler */
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id);
/* Returns 0 on success, negative errno on failure */
/* Unregister: dev_id must match what was passed to request_irq */
void free_irq(unsigned int irq, void *dev_id);
/* Managed version: automatically freed when device is removed */
int devm_request_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, unsigned long flags,
const char *name, void *dev_id);
Why this API has barely changed while everything under it has
request_irq()'s shape has been almost untouched since the early 2.6 series, even as the hardware it sits on top of went through IOAPIC, MSI, MSI-X, per-CPU vectors, and (on other architectures) entirely different controller families like ARM's GIC. That stability isn't an accident or a case of the API being frozen in amber — it's the direct payoff of the irq_desc/flow-handler/irq_chip separation covered on the Interrupt Handling Overview page: because a driver's handler only ever talks to a Linux IRQ number, and everything controller-specific lives behind irq_chip ops the driver never touches, an entirely new interrupt controller can be plugged into the kernel without any existing driver's request_irq() call needing to change.
The one real change to the handler signature itself is its third argument. Through Linux 2.6.18, a handler was irqreturn_t (*)(int, void *, struct pt_regs *) — the raw register state at the moment of interrupt was passed to every handler, "just in case." (The irq_handler_t typedef itself arrived just before this: a separate, immediately-preceding commit in the same October 2006 series, "IRQ: Typedef the IRQ handler function type", introduced the name for whatever the handler signature happened to be at that point.) David Howells's "IRQ: Maintain regs pointer globally rather than passing to IRQ handlers" (October 2006) then dropped the pt_regs argument for 2.6.19 (confirmed via tag-diff: present at v2.6.18, gone at v2.6.19), leaving today's irqreturn_t (*)(int, void *). Almost nothing actually used regs (the commit's own message names the real consumers: profile_tick() for profiling and update_process_times() for user-vs-kernel CPU time accounting — the kind of thing that surfaces in /proc/stat and per-process /proc/<pid>/stat, not /proc/interrupts — and both could get at the current register state through get_irq_regs() instead), and passing a live pointer into every handler was a standing invitation to misuse it for something other than its original diagnostic purpose. Removing an argument nobody needed, once there was an equally capable alternative for the handful of callers who did, is the kind of API cleanup that's much easier to justify than to reverse — which is presumably why, two decades on, it's still the only breaking change this signature has ever had.
Handler return values
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
#define IRQ_NONE (0) /* not my interrupt */
#define IRQ_HANDLED (1) /* handled the interrupt */
#define IRQ_WAKE_THREAD (2) /* wake the threaded handler */
- Return
IRQ_NONEif the interrupt wasn't from your device (important for shared IRQs) - Return
IRQ_HANDLEDwhen you've handled it - Return
IRQ_WAKE_THREADto wake the threaded handler (see Threaded IRQs)
IRQF flags
/* Trigger type (usually already configured by firmware/devicetree) */
IRQF_TRIGGER_RISING /* edge: low → high */
IRQF_TRIGGER_FALLING /* edge: high → low */
IRQF_TRIGGER_HIGH /* level: active high */
IRQF_TRIGGER_LOW /* level: active low */
/* Sharing */
IRQF_SHARED /* interrupt line shared with other devices */
/* Threading */
IRQF_ONESHOT /* disable IRQ line until thread handler completes */
IRQF_NO_THREAD /* force non-threaded even if requested */
/* Power management */
IRQF_NO_SUSPEND /* don't disable during suspend */
IRQF_FORCE_RESUME /* force-enable on resume even if disabled */
/* Misc */
IRQF_NOBALANCING /* exclude from IRQ balancing */
IRQF_PERCPU /* per-CPU interrupt */
A minimal driver example
#include <linux/interrupt.h>
#include <linux/module.h>
struct my_device {
int irq;
void __iomem *regs;
/* ... */
};
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
u32 status;
/* Read interrupt status register */
status = readl(dev->regs + STATUS_REG);
/* Check if this is our interrupt */
if (!(status & MY_IRQ_BIT))
return IRQ_NONE;
/* Acknowledge (clear) the interrupt */
writel(MY_IRQ_BIT, dev->regs + STATUS_CLEAR_REG);
/* Do minimal work here — queue deferred processing */
schedule_work(&dev->work);
return IRQ_HANDLED;
}
static int my_driver_probe(struct platform_device *pdev)
{
struct my_device *dev;
int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
dev->irq = platform_get_irq(pdev, 0);
dev->regs = devm_ioremap_resource(&pdev->dev, res);
INIT_WORK(&dev->work, my_work_handler);
/* devm_request_irq: automatically freed when device is removed */
ret = devm_request_irq(&pdev->dev, dev->irq, my_irq_handler,
0, dev_name(&pdev->dev), dev);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", dev->irq);
return ret;
}
return 0;
}
Shared IRQs
On legacy systems (e.g., PCI INTx), multiple devices share a single IRQ line. Each registers with IRQF_SHARED:
/* Both devices register on the same IRQ */
request_irq(irq, handler_a, IRQF_SHARED, "device-a", dev_a);
request_irq(irq, handler_b, IRQF_SHARED, "device-b", dev_b);
/* When the IRQ fires, both handlers are called in sequence:
kernel calls handler_a(irq, dev_a), then handler_b(irq, dev_b)
Each must check its own hardware status register and return IRQ_NONE
if not its interrupt */
Requirements for shared IRQs:
1. IRQF_SHARED must be set by all sharing drivers
2. dev_id must be unique (typically the device struct pointer)
3. Each handler must be able to determine if it was its device
disable_irq / enable_irq
/* Disable an IRQ and wait for any running handler to complete */
disable_irq(irq); /* sleeps until handler done */
disable_irq_nosync(irq); /* returns immediately */
/* Re-enable */
enable_irq(irq);
/* Balanced: nested calls (depth counter) */
disable_irq(irq); /* depth=1 */
disable_irq(irq); /* depth=2 */
enable_irq(irq); /* depth=1, still disabled */
enable_irq(irq); /* depth=0, now enabled */
disable_irq() increments irq_desc->depth. The IRQ is physically disabled when depth goes from 0 to 1 (first disable call) and re-enabled when it returns to 0 (last enable call).
synchronize_irq
free_irq() already calls synchronize_irq() internally before returning, so any in-flight handlers are guaranteed complete when free_irq() returns:
devm_request_irq() handles this automatically at device removal, so prefer it over manual request_irq() + free_irq().
Getting the IRQ number
/* From platform resources */
irq = platform_get_irq(pdev, 0);
/* From device tree */
irq = of_irq_get(np, 0);
/* From PCI */
irq = pci_irq_vector(pdev, 0); /* MSI or legacy */
/* From GPIO */
irq = gpiod_to_irq(gpio);
Further reading
- IRQ Descriptor and irq_chip — What's registered under the hood
- Threaded IRQs — Moving work out of hardirq context
- Workqueues — For deferred work from interrupt handlers