What is request_irq() and free_irq() in Linux Device Drivers (2026 Kernel Guide)-Linux Device Drivers Course

request_irq() and free_irq() in Linux Device Drivers (2026 Kernel Guide)
Free Linux Kernel Programming Course • Free Linux Device Drivers Course • EmbeddedPathashala
Level: Beginner to Intermediate
Kernel Version: 6.x
Reading Time: 12 min

If you are learning free Linux kernel programming or building your first Linux device driver, understanding how a driver registers and releases a hardware interrupt line is one of the most important skills you can pick up. In this lesson from our free Linux device drivers course, we break down request_irq(), its modern replacement devm_request_irq(), and the threaded interrupt API request_threaded_irq() — all updated for current 6.x kernels running on ARM64 and RISC-V boards used in today’s embedded systems.

What You Will Learn
How request_irq() registers an interrupt handler IRQF_* flag meanings devm_request_irq() managed allocation request_threaded_irq() for modern drivers Safely releasing an IRQ with free_irq() Common IRQ registration bugs

Prerequisites

  • Basic C programming
  • Linux kernel module basics (insmod/rmmod)
  • Concept of a device driver’s probe() function
  • A Linux VM or SBC (Raspberry Pi / BeagleBone) with kernel headers installed

What Is request_irq() in the Linux Kernel?

request_irq() is the kernel API a device driver calls to tell the interrupt subsystem: “when this hardware line fires, please call my function.” It is normally invoked from the driver’s probe method, when the device is being initialized, not from the module’s init() function directly — this matters because on modern kernels most drivers are written against the platform driver or device tree probe model rather than being hardcoded.

Interrupt Registration Flow
Device Tree / ACPI describes IRQ line │ ▼ platform_driver.probe() is called by kernel │ ▼ driver calls request_irq() / devm_request_irq() │ ▼ kernel maps IRQ number to your handler function │ ▼ hardware event occurs → CPU jumps to your handler

The request_irq() Function Signature

The function takes five arguments that have stayed stable across kernel releases:

int request_irq(unsigned int irq,
                 irq_handler_t handler,
                 unsigned long flags,
                 const char *name,
                 void *dev);
Parameter Meaning
irqThe IRQ number, usually obtained from the device tree via platform_get_irq() on modern drivers, or from a bus-specific structure for PCI/USB devices.
handlerPointer to your interrupt service routine. Must return IRQ_HANDLED or IRQ_NONE.
flagsA bitmask such as IRQF_SHARED or IRQF_TRIGGER_FALLING that configures how the line behaves.
nameA short driver name shown in /proc/interrupts, useful for debugging.
devAn opaque pointer (usually your driver’s private struct) passed back to your handler; also required to identify the instance when the line is shared.

Common IRQF_* Flags

FlagUse Case
IRQF_SHAREDMultiple devices share the same physical line (common on PCIe). Your handler must check whether the interrupt really belongs to it.
IRQF_TRIGGER_RISINGFires on a rising signal edge — common for GPIO buttons and sensors.
IRQF_TRIGGER_FALLINGFires on a falling edge.
IRQF_ONESHOTRequired when using threaded interrupts without a hard-IRQ handler, keeping the line masked until the thread finishes.

A Simple, Original Example: GPIO Button Driver

Below is a small, original example (not taken from any book or external source) showing a minimal GPIO interrupt registration pattern you would find in a modern platform driver targeting an embedded Linux board.

#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>

struct mybtn_dev {
    struct gpio_desc *gpiod;
    int irq;
};

static irqreturn_t mybtn_isr(int irq, void *dev_id)
{
    struct mybtn_dev *priv = dev_id;

    /* Keep this short: just note the event, do heavy work later */
    dev_info(NULL, "mybtn: button pressed on irq %d\n", irq);

    return IRQ_HANDLED;
}

static int mybtn_probe(struct platform_device *pdev)
{
    struct mybtn_dev *priv;
    int ret;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->gpiod = devm_gpiod_get(&pdev->dev, "button", GPIOD_IN);
    if (IS_ERR(priv->gpiod))
        return PTR_ERR(priv->gpiod);

    priv->irq = gpiod_to_irq(priv->gpiod);
    if (priv->irq irq;

    ret = devm_request_irq(&pdev->dev, priv->irq, mybtn_isr,
                            IRQF_TRIGGER_FALLING, "mybtn-irq", priv);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, priv);
    return 0;
}

Why devm_request_irq() Is Preferred Today

Most modern drivers avoid plain request_irq() in favor of the “devm” (device-managed) variant, devm_request_irq(). It ties the lifetime of the IRQ registration to the underlying struct device, so the kernel automatically calls free_irq() for you when the device is removed or the probe fails partway through. This removes an entire class of bugs where developers forget to release the interrupt in an error path.

Threaded Interrupts: request_threaded_irq()

Many drivers today, especially for I2C/SPI sensors, use request_threaded_irq() instead of a plain hard-IRQ handler. This lets you run the bulk of your interrupt-handling logic in a dedicated kernel thread, where sleeping and blocking calls are allowed — something a normal hard-IRQ handler cannot do.

static irqreturn_t sensor_hard_isr(int irq, void *dev_id)
{
    /* runs in real interrupt context, must be very fast */
    return IRQ_WAKE_THREAD;
}

static irqreturn_t sensor_thread_fn(int irq, void *dev_id)
{
    /* runs in process context: safe to sleep, use I2C, allocate memory */
    struct mysensor_dev *priv = dev_id;

    i2c_smbus_read_byte_data(priv->client, MYSENSOR_REG_STATUS);
    return IRQ_HANDLED;
}

devm_request_threaded_irq(&client->dev, client->irq,
                           sensor_hard_isr, sensor_thread_fn,
                           IRQF_ONESHOT, "mysensor", priv);

Releasing the Interrupt with free_irq()

If you registered the interrupt with plain request_irq(), you are responsible for calling free_irq(irq, dev_id) in your driver’s remove path, matching the exact dev_id pointer you originally passed in. Forgetting this — or freeing it with the wrong pointer on a shared line — is one of the most common driver bugs. This is exactly the class of bug that devm_request_irq() is designed to eliminate.

static void mybtn_remove(struct platform_device *pdev)
{
    struct mybtn_dev *priv = platform_get_drvdata(pdev);

    /* Not required here because devm_request_irq() was used,
       shown only for drivers using plain request_irq() */
    free_irq(priv->irq, priv);
}

Common Mistakes

  • Calling request_irq() before the device is fully initialized, so the handler races with incomplete state.
  • Forgetting IRQF_SHARED on lines that are genuinely shared, causing registration failures.
  • Mismatched dev_id between request_irq() and free_irq() on shared lines.
  • Doing blocking work (I2C/SPI transfers, memory allocation with GFP_KERNEL) inside a plain hard-IRQ handler instead of using a threaded IRQ.

Best Practices

  • Prefer devm_request_irq() or devm_request_threaded_irq() in new drivers.
  • Keep the hard-IRQ handler as small as possible; move real work to a thread, tasklet, or workqueue.
  • Always check the return value of request_irq() — a negative value means registration failed.
  • Use a descriptive name string; it shows up in /proc/interrupts and saves debugging time.

Security Considerations

Interrupt handlers run with high privilege and can be a target for fault-injection style attacks on embedded systems with physically accessible GPIO lines. Validate any data read from hardware registers before trusting it, and avoid exposing raw register values to user space without sanitization.

Summary / Key Takeaways

  • request_irq() registers a handler function for a given IRQ number.
  • devm_request_irq() is the modern, safer, automatically-cleaned-up variant.
  • request_threaded_irq() lets you safely do blocking work in response to an interrupt.
  • free_irq() must match the exact dev_id used at registration time.

Frequently Asked Questions

Q1. What is the difference between request_irq() and devm_request_irq()?
devm_request_irq() automatically releases the interrupt when the associated device is removed, while plain request_irq() requires you to call free_irq() yourself.

Q2. Can I sleep inside a function registered with request_irq()?
No. A plain hard-IRQ handler runs in atomic context. Use request_threaded_irq() if you need to sleep.

Q3. What does IRQF_SHARED mean?
It tells the kernel that this line may be shared with other devices, so every registered handler on that line will be called and must check if the interrupt was really meant for it.

Q4. Where do I find the IRQ number for my device?
For platform/device-tree devices, use platform_get_irq(). For GPIO lines, use gpiod_to_irq(). For PCI devices, it comes from the pci_dev structure.

Q5. What happens if request_irq() fails?
It returns a negative error code (commonly -EBUSY if the line is already taken without sharing, or -EINVAL for bad arguments). Always check the return value.

Q6. Is request_threaded_irq() slower than request_irq()?
The hard-IRQ portion is equally fast; only the threaded portion runs later in process context, so latency-sensitive work should still happen in the hard-IRQ callback.

Q7. Do I need IRQF_ONESHOT with threaded interrupts?
Yes, when you don’t provide a separate hard-IRQ handler, IRQF_ONESHOT keeps the line masked until your thread function completes, preventing re-entrancy.

Continue Learning Linux Device Drivers

This lesson is part of EmbeddedPathashala’s free Linux kernel programming and device drivers course.

Next Lecture: Interrupt Handler Context Rules →

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *