Enable and Disable IRQ in Linux Kernel Device Drivers (Kernel 6.x Guide)-Best Linux Device Driver Training Online

Enable and Disable IRQ in Linux Kernel Device Drivers (Kernel 6.x Guide)
Free Linux Device Drivers Course — EmbeddedPathashala
enable disable irq linux kernel local_irq_save disable_irq_nosync Free Linux Kernel Course Free Device Drivers Course

Knowing how to enable disable irq linux kernel style APIs work is essential once you start writing real device drivers, because there are moments when your driver code must guarantee that no interrupt (or one specific interrupt) fires while a critical section of code is running. This lecture, part of our free Linux kernel programming course, walks through every commonly used enable/disable API with fresh, original examples updated for kernel 6.x.

What You Will Learn
local_irq_disable/enable local_irq_save/restore disable_irq / disable_irq_nosync enable_irq disable_hardirq Deadlock avoidance

Prerequisites

  • Completion of the previous lecture on threaded vs hardirq handlers
  • A working Linux kernel module build environment (kernel 6.x headers)
  • Basic understanding of spinlocks is helpful

Two Categories of IRQ Control

There are really two different problems that “enable/disable IRQ” APIs solve, and beginners often mix them up. The first category disables all interrupts on the current CPU core. The second category disables one specific IRQ line, system-wide, regardless of which core it fires on.

Local CPU Control vs Per-Line Control
Local CPU (this core only)

local_irq_disable(), local_irq_enable(), local_irq_save(), local_irq_restore()

vs
Per IRQ Line (system-wide)

disable_irq(), disable_irq_nosync(), enable_irq(), disable_hardirq()

local_irq_save() and local_irq_restore(): Protecting a Critical Section

When your driver needs to touch a data structure that is also accessed inside its own interrupt handler, you must prevent that handler from running on the current core while you’re working. The safest pattern is local_irq_save() combined with local_irq_restore(), because it remembers the previous interrupt state instead of blindly turning interrupts back on.

#include <linux/interrupt.h>
#include <linux/spinlock.h>

struct ep_ring_buffer {
    u32 head;
    u32 tail;
    u32 data[64];
};

static struct ep_ring_buffer ep_rb;

static void ep_rb_push(u32 value)
{
    unsigned long flags;

    local_irq_save(flags);           /* save state, disable local IRQs */

    ep_rb.data[ep_rb.head % 64] = value;
    ep_rb.head++;

    local_irq_restore(flags);        /* restore previous IRQ state */
}

static irqreturn_t ep_sensor_handler(int irq, void *dev_id)
{
    ep_rb_push(ep_sensor_read_fifo());
    return IRQ_HANDLED;
}
Tip: Prefer spin_lock_irqsave() over raw local_irq_save() whenever the data is also touched from another CPU core, since local_irq_save() only protects against the current core’s own interrupts, not other cores running in parallel.

disable_irq() vs disable_irq_nosync(): Per-Line Control

Sometimes you don’t want to freeze the entire CPU — you only want to stop one specific device’s interrupt line, for example while you reconfigure that device. disable_irq() and disable_irq_nosync() do exactly this.

#include <linux/interrupt.h>

static void ep_reconfigure_device(struct ep_dev *edev)
{
    /* Stop new interrupts on this line and wait for any handler
     * currently running (on another CPU) to finish first */
    disable_irq(edev->irq);

    ep_hw_reprogram_registers(edev);

    enable_irq(edev->irq);
}

static irqreturn_t ep_dev_handler(int irq, void *dev_id)
{
    struct ep_dev *edev = dev_id;

    /* Called from inside an interrupt context ourselves,
     * so we must use the "nosync" variant to avoid deadlock */
    if (ep_hw_error_detected(edev))
        disable_irq_nosync(edev->irq);

    return IRQ_HANDLED;
}

disable_irq() vs disable_irq_nosync() vs disable_hardirq()

API Waits for Handler to Finish? Safe to Call From
disable_irq()Yes (synchronizes)Process context only
disable_irq_nosync()NoInterrupt context or process context
disable_hardirq()Waits for hardirq only; returns false if threaded handlers are activeAtomic context (GPL-only)

Real-World Use Cases

  • Temporarily stopping a UART’s RX interrupt while flushing or resetting its FIFO.
  • Disabling a GPIO interrupt line while a debounce timer is settling.
  • Protecting a shared ring buffer between an interrupt handler and a driver’s read() implementation.
  • Safely quiescing a network interrupt line before a hardware reset, using disable_irq_nosync() inside netpoll-style atomic code paths.

Common Mistakes and Troubleshooting

  • Calling disable_irq() from inside the handler of the same IRQ. This deadlocks, because disable_irq() waits for the handler to finish — but the handler is waiting on disable_irq(). Always use disable_irq_nosync() in that situation.
  • Holding a spinlock while calling disable_irq(). If the handler you’re waiting for needs that same lock, you get a classic self-deadlock.
  • Forgetting to pair every disable_irq() with exactly one enable_irq(). These calls nest as a counter internally; mismatched calls leave the line permanently disabled or generate a kernel warning.
  • Using local_irq_disable() when you meant disable_irq(). The former disables everything on the core; the latter disables just one line. Mixing them up hides bugs during testing on single-core systems.

Best Practices

Practice Why
Keep critical sections under local_irq_save() as short as possibleLonger sections increase interrupt latency system-wide.
Prefer spin_lock_irqsave() for SMP-safe codelocal_irq_save() alone doesn’t protect against other CPUs.
Match every disable_irq()/disable_irq_nosync() with an enable_irq()Prevents a permanently stuck IRQ line.

Performance and Security Considerations

Disabling all local interrupts, even briefly, increases response latency for every other device on that core, which matters a lot on real-time or low-latency embedded systems. From a security angle, an interrupt handler that can be disabled by unprivileged user-triggered code paths (for example, through a poorly designed ioctl) could be abused to create a denial-of-service condition, so always restrict who can trigger driver code paths that call disable_irq().

Summary / Key Takeaways

  • local_irq_save/restore protects against this CPU’s own interrupts during a critical section.
  • disable_irq()/enable_irq() control one specific IRQ line system-wide and must be called in matching pairs.
  • disable_irq_nosync() is mandatory when disabling a line from within its own interrupt context, to avoid deadlock.
  • disable_hardirq() is a specialized, atomic-context-safe variant used in cases like netpoll.

Conclusion

Getting comfortable with how to enable disable irq linux kernel APIs behave will save you from some of the nastiest bugs in driver development — deadlocks and interrupt storms. Use the right tool for the right job: local CPU control for short critical sections, and per-line control for reconfiguring or quiescing a specific device. In the next lecture of this free Linux device drivers course, we’ll look at the non-maskable interrupt (NMI) and how the kernel’s magic SysRq facility uses it for live debugging.

FAQ

Q1. What is the difference between local_irq_disable() and disable_irq()?
local_irq_disable() disables all interrupts on the current CPU core. disable_irq() disables only one specific IRQ line across the whole system.

Q2. Why does the kernel provide both disable_irq() and disable_irq_nosync()?
disable_irq() waits for any currently running handler on that line to finish, which is safe from process context but can deadlock if called from inside the handler itself. disable_irq_nosync() skips that wait.

Q3. Can I call local_irq_save() inside an interrupt handler?
Yes, though interrupts are typically already disabled in a hardirq handler on that core; it’s more commonly used in process-context code that shares data with a handler.

Q4. What happens if I call enable_irq() more times than disable_irq()?
The kernel logs a warning because the internal disable counter would go negative — always keep calls balanced.

Q5. Is disable_hardirq() available to any kernel module?
No, it’s exported GPL-only, so only GPL-compatible modules can use it.

Q6. Does disabling one IRQ line affect other devices?
No, disable_irq() and enable_irq() only affect the specific IRQ line you pass in, leaving all other interrupt lines untouched.

Q7. What is the safest default choice for protecting shared data?
In most modern drivers, spin_lock_irqsave()/spin_unlock_irqrestore() is preferred over raw local_irq_save(), since it also handles SMP safety.

Continue Learning — Free Linux Kernel Programming Course

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

Visit EmbeddedPathashala Next Lecture →

2 Comments

Leave a Reply

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