← Previous Lecture | Next Lecture →
Every interrupt-driven peripheral signals the CPU using one of two basic electrical schemes: level-triggered or edge-triggered. This choice affects how your driver must behave, and getting it wrong is a classic source of “my interrupt only fires once” or “my interrupt keeps firing in a storm” bugs. This lecture in our free Linux device drivers course explains both, using modern hardware (Raspberry Pi 4/5-class SoCs and typical GPIO expanders) as examples instead of legacy platforms.
What You Will Learn
- The electrical difference between level-triggered and edge-triggered interrupts
- Why level-triggered interrupts require you to explicitly clear the condition in your handler
- Why edge-triggered interrupts are easy to use but easy to miss
- How trigger type is described in the device tree on modern ARM-based boards
- How to inspect trigger type and firing counts from user space
Prerequisites
This is the third lecture in the interrupt handling module. Make sure you’ve gone through the earlier lectures on request_irq()/devm_request_irq() and interrupt flags first, since the trigger flags mentioned there are used again here.
Level-Triggered Interrupts
A level-triggered line stays asserted for as long as the underlying condition is true. If a GPIO expander’s interrupt output goes high because a button is held down, that line remains high until something explicitly clears the condition — even after your handler function returns. If you don’t clear it, the CPU sees the line still asserted and calls your handler again immediately, over and over, which is commonly called an interrupt storm.
| Idle (Low) | → | Asserted (High) — stays high | → | Driver must clear condition to return Low |
Because of this, your handler’s very first job on a level-triggered interrupt is usually to identify the cause and acknowledge/deassert it at the hardware register level, for example by writing to a status/clear register on the peripheral, before doing anything else.
Edge-Triggered Interrupts
An edge-triggered line only fires once, exactly at the moment the signal transitions — for example the moment it goes from low to high (rising edge) or high to low (falling edge). There’s no ongoing “asserted” state to clear; the controller latches a single event per transition.
| Low | rising edge → | Single interrupt event fires | → | Line returns High, no action needed to “clear” it |
Edge-triggered interrupts are simpler to work with because the driver doesn’t need deep knowledge of the peripheral’s internal status registers just to stop the interrupt from re-firing. The trade-off is that if the CPU is briefly unable to service the interrupt (for example, interrupts are disabled for too long), a very short pulse can be missed entirely, since there’s no “still asserted” state left behind to catch later.
Comparison Table
| Aspect | Level-Triggered | Edge-Triggered |
|---|---|---|
| Fires when | Signal level is asserted, repeatedly until cleared | Signal transitions (rising or falling edge) |
| Risk if mishandled | Interrupt storm if condition isn’t cleared | Missed event if the transition happens while masked |
| Driver complexity | Higher — must know how to clear the source | Lower — no explicit clear step needed |
| Typical use | Status/error conditions that persist until handled | Button presses, GPIO state changes, one-shot events |
Configuring Trigger Type in the Device Tree
On virtually all modern ARM-based single-board computers, trigger type is described declaratively in the device tree rather than hardcoded in driver flags. A typical GPIO interrupt entry looks like this:
&gpio {
mydev_irq_pin: mydev-irq-pin {
interrupt-parent = <&gpio>;
interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
};
};
mydev@20 {
compatible = "epathashala,mydev";
reg = <0x20>;
interrupt-parent = <&gpio>;
interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
};
The second cell of the interrupts property, IRQ_TYPE_EDGE_FALLING in this example, is what tells the interrupt controller driver which trigger type to configure for that line. Common values include IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_HIGH, and IRQ_TYPE_LEVEL_LOW.
Inspecting Interrupts from User Space
You can check how many times an interrupt has fired, and confirm your driver is actually connected to the line you expect, using:
cat /proc/interrupts
Look for the row matching your driver’s name string (the one you passed to request_irq()) and watch the per-CPU counters increase as you trigger the hardware event.
Common Mistakes and Troubleshooting Tips
- Not clearing a level-triggered source: Results in the handler being called continuously (an interrupt storm) since the line never deasserts.
- Assuming edge-triggered can’t be missed: Extremely short pulses combined with a long period of disabled interrupts elsewhere in the system can still cause a missed event.
- Mismatched device tree trigger type vs hardware datasheet: If the device tree says rising edge but the peripheral actually asserts on falling edge, the interrupt may never fire at all. Always cross-check against the peripheral’s datasheet.
Best Practices
- Always read the peripheral’s datasheet to confirm whether it uses level or edge triggering before writing the device tree entry.
- For level-triggered peripherals, clear the interrupt source as the very first action in your handler.
- Use
cat /proc/interruptsas your first troubleshooting step whenever an interrupt-driven driver “does nothing.”
Key Takeaways
- Level-triggered interrupts remain asserted until explicitly cleared and risk interrupt storms if mishandled.
- Edge-triggered interrupts fire once per transition and are simpler, but very short pulses can be missed.
- On modern platforms, trigger type is normally set through the device tree’s
interruptsproperty rather than driver code.
Frequently Asked Questions
Q1. How do I know if my peripheral is level-triggered or edge-triggered?
Check the peripheral’s datasheet under its interrupt/IRQ pin description — it will explicitly state the trigger behavior.
Q2. What causes an interrupt storm?
It happens on level-triggered lines when the driver’s handler returns without clearing the condition at the hardware level, so the CPU sees the line still asserted and immediately re-enters the handler.
Q3. Can an edge-triggered interrupt be shared between devices?
It’s technically possible but far less common and harder to get right than sharing a level-triggered line, since there’s no persistent “still asserted” state for the second driver to check.
Q4. Where do I set the trigger type on a modern embedded Linux board?
In the device tree, using the second cell of the interrupts property, for example IRQ_TYPE_EDGE_RISING or IRQ_TYPE_LEVEL_HIGH.
Q5. Does IRQF_TRIGGER_RISING in driver code override the device tree setting?
Behavior here depends on the interrupt controller driver; in general it’s best to keep the two consistent rather than relying on one overriding the other.
Continue with the next module in our free Linux kernel programming course to go deeper into kernel timers, threads, and workqueues.

2 Comments