What are Device Tree Phandles For Interrupts in Linux-Embedded Linux Course In Hyderabad

PREV_LEC NEXT_LEC

Device Tree Phandles For Interrupts
How Linux device tree nodes reference an interrupt controller across the tree — free embedded linux course, bootloader-to-kernel chapter
Chapter 3 · Lecture 7
Free Linux Device Drivers Course
Beginner to Intermediate

A device tree isn’t just a tree — it’s a tree with a few extra cross-links layered on top. A serial port lives under one branch of the hierarchy, but it needs to reach across to a completely different branch to say “this is the interrupt controller I fire IRQs on.” That cross-link mechanism is called a phandle, and understanding it is essential to reading real-world device trees, which is exactly why it gets its own lecture in this free embedded systems course.

In this lecture we’ll unpack what a phandle actually is under the hood, how the label syntax (&label) turns into one at compile time, and how the interrupt-parent and interrupts properties use phandles to wire a device to its interrupt controller.

Keywords covered in this lecture
device tree phandle
interrupt-parent
interrupt-cells
interrupts property
interrupt controller node
free linux kernel development course

What You Will Learn

  • What a phandle actually is once the device tree is compiled to a binary blob
  • How the &label syntax generates a phandle automatically
  • How an interrupt controller node advertises itself with #interrupt-cells
  • How interrupt-parent and interrupts connect a device to that controller
  • How to trace an IRQ number from device tree source to a running kernel’s /proc/interrupts
  • Common phandle mistakes and how the device tree compiler catches (or doesn’t catch) them

Prerequisites

This lecture builds directly on the previous one, “Device Tree reg Property Explained.” You should be comfortable with basic node/property syntax and the concept of #address-cells/#size-cells, since interrupt controllers use an analogous #interrupt-cells mechanism.

Why Trees Alone Aren’t Enough

The parent-child hierarchy in a device tree naturally expresses one kind of relationship: physical bus containment. A UART sits under the SoC’s bus node because that’s where it’s memory-mapped. But a device also has other relationships that don’t follow the bus hierarchy at all — which interrupt controller handles its IRQ line, which clock feeds it, which regulator powers it. These are cross-tree references, and the device tree format handles all of them the same way: with a phandle.

What a Phandle Actually Is

In the source-level .dts syntax, you write a phandle reference using a label and an ampersand, like &intc. But that’s just source-level sugar. Once the device tree compiler (dtc) processes the file, it assigns every referenced node a small unique 32-bit integer, and it inserts that integer as an implicit phandle property on the target node. Every place you wrote &intc gets replaced with that same 32-bit number in the compiled .dtb.

Label syntax vs. what actually ends up in the compiled .dtb
Source (.dts): Compiled (.dtb, conceptually):intc: interrupt-controller@… { interrupt-controller@… {
… phandle = <0x01>;
}; …
};
serial0 {
interrupt-parent = <&intc>; serial0 {
}; interrupt-parent = <0x01>;
};

This is exactly why phandle values are almost never written by hand in real device trees — they’re compiler-generated, and hand-picking them risks collisions. The &label syntax exists precisely so you never have to think about the raw integer.

The Interrupt Controller Side: #interrupt-cells

An interrupt controller node advertises two things: that it is an interrupt controller (via the boolean property interrupt-controller), and how many 32-bit cells each of its interrupt specifiers needs (via #interrupt-cells). This mirrors #address-cells from the previous lecture — it’s a property in the “provider” node that tells every “consumer” node how to format its reference.

An original interrupt controller node for a fictional EP-SoC1 platform
epintc: interrupt-controller@50000000 {
compatible = “ep,socx-intc”;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x50000000 0x1000>;
};

Here #interrupt-cells = <2> means every device that references epintc must supply exactly two cells in its interrupts property: conventionally, the IRQ line number first, and a trigger-type flag second (for example, 1 for edge-triggered, 4 for level-triggered — the exact encoding is defined by that controller’s own binding document, since it’s controller-specific, not part of the core device tree spec).

The Device Side: interrupt-parent and interrupts

A device that wants to receive interrupts sets two properties: interrupt-parent, a phandle pointing at the controller it’s wired to, and interrupts, the cell values that controller needs to identify the specific line.

A UART node wired to the EP-SoC1 interrupt controller
uart2@50044000 {
compatible = “ep,socx-uart”;
reg = <0x50044000 0x1000>;
clock-frequency = <24000000>;
interrupt-parent = <&epintc>;
interrupts = <18 4>;
// IRQ line 18, level-triggered (per ep,socx-intc binding)
};

Note that interrupt-parent doesn’t have to be set on every single node — if omitted, the kernel walks up the tree looking for the nearest ancestor that defines it, similar to how #address-cells is inherited. Many device trees set a single interrupt-parent at the root or SoC node once, and only override it on the rare device wired to a secondary controller.

Cascaded Controllers: Phandles Chaining Together

Real SoCs often have more than one interrupt controller — a primary GIC-style controller plus a secondary controller for GPIO-based interrupts, for example. In that case the secondary controller node is itself both a consumer (it has its own interrupt-parent pointing at the primary controller) and a provider (it defines its own #interrupt-cells for its children).

Role Property it defines Property it consumes
Primary controller #interrupt-cells, interrupt-controller none
Secondary (cascaded) controller #interrupt-cells, interrupt-controller interrupt-parent, interrupts
Leaf device none interrupt-parent, interrupts

Tracing an IRQ From dts to a Running Kernel

Once your board boots with a device tree like the one above, you can confirm the phandle chain resolved correctly by checking which Linux IRQ number your device landed on. Here’s a small original platform driver that logs its resolved IRQ at probe time — using our own ep,socx-uart compatible string, not reused from any textbook example:

ep_irqcheck.c — log the resolved Linux IRQ number at probe time
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>static irqreturn_t ep_irqcheck_handler(int irq, void *dev_id)
{
return IRQ_HANDLED;
}static int ep_irqcheck_probe(struct platform_device *pdev)
{
int irq = platform_get_irq(pdev, 0);
int ret;if (irq < 0)
return irq;dev_info(&pdev->dev, “device tree interrupts property resolved to Linux IRQ %d\n”, irq);ret = devm_request_irq(&pdev->dev, irq, ep_irqcheck_handler,
0, “ep_irqcheck”, pdev);
return ret;
}static const struct of_device_id ep_irqcheck_ids[] = {
{ .compatible = “ep,socx-uart” },
{ }
};
MODULE_DEVICE_TABLE(of, ep_irqcheck_ids);

static struct platform_driver ep_irqcheck_driver = {
.probe = ep_irqcheck_probe,
.driver = {
.name = “ep_irqcheck”,
.of_match_table = ep_irqcheck_ids,
},
};
module_platform_driver(ep_irqcheck_driver);
MODULE_LICENSE(“GPL”);
MODULE_DESCRIPTION(“EmbeddedPathashala phandle/interrupt demo”);

Build, insert, and check dmesg / proc/interrupts
$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_irqcheck.ko
$ dmesg | tail -n 2
[ 14.221093] ep_irqcheck 50044000.uart2: device tree interrupts property resolved to Linux IRQ 47$ cat /proc/interrupts | grep ep_irqcheck
47: 0 ep_gic 18 Level ep_irqcheck

The device tree’s raw 18 from interrupts = <18 4> became Linux IRQ 47 after the interrupt controller driver’s domain mapping — that translation is exactly what the phandle chain made possible, without your driver ever needing to know the controller’s internal numbering scheme.

Common Mistakes and Troubleshooting

  • Wrong number of interrupt cells. If the controller declares #interrupt-cells = <2> but a device only supplies one, the compiler may not catch it — you’ll get a boot-time device tree parsing warning at best, or a misinterpreted trigger flag at worst.
  • Referencing a label that doesn’t exist. dtc will fail the build with an “undefined label” error — this one, at least, is a hard error, not a silent bug.
  • Forgetting interrupt-controller; on the provider node. Some tooling and the kernel’s own parsing depend on this boolean marker being present in addition to #interrupt-cells.
  • Assuming interrupts numbering matches Linux IRQ numbers. The raw number in interrupts is controller-specific hardware line numbering; the Linux virtual IRQ number is assigned later by the irqdomain code and can differ.

Best Practices

  • Always set a single interrupt-parent at the SoC or root node when there’s only one primary controller, and only override it locally for devices on a cascaded controller.
  • Check the specific controller’s binding documentation for what each interrupt cell means — the encoding is controller-defined, not fixed by the core spec.
  • Use cat /proc/interrupts and dtc -O dts together when debugging — one shows you the resolved kernel state, the other shows you the compiled source of truth.

Summary and Key Takeaways

  • A phandle is a compiler-generated 32-bit ID that lets one node reference another across the tree hierarchy.
  • The &label syntax in .dts source is resolved into a raw phandle integer by dtc.
  • Interrupt controllers advertise #interrupt-cells; consumers must match that cell count in their interrupts property.
  • interrupt-parent is inherited from ancestors if not explicitly set on a device.
  • The raw device tree interrupt number and the Linux virtual IRQ number are two different things, connected via irqdomain mapping.

Conclusion

Phandles are the glue that turns a device tree from a simple containment hierarchy into a full description of a real SoC’s wiring — interrupts today, and clocks, regulators, and pinctrl groups in later lectures of this free linux device drivers course. Once you can mentally translate &label into “a compiler-generated pointer to that node,” the rest of the device tree’s cross-referencing properties become much easier to read at a glance.

Frequently Asked Questions

What is a phandle in a Linux device tree?

A compiler-generated 32-bit identifier that lets one device tree node reference another node anywhere else in the tree, independent of the parent-child hierarchy.

How does the &label syntax relate to a phandle?

The label is source-level syntax; the device tree compiler resolves every &label reference into the target node’s compiler-assigned phandle integer in the compiled .dtb.

What does #interrupt-cells control?

It’s set on an interrupt controller node and tells consumer devices how many 32-bit cells their interrupts property must supply to describe a line on that controller.

What happens if interrupt-parent is not set on a device?

The kernel walks up the device tree to the nearest ancestor that does define interrupt-parent, similar to how address-cells is inherited.

Does the raw interrupts number match the Linux IRQ number?

No. The raw number is hardware line numbering specific to that controller; the kernel’s irqdomain code maps it to a separate Linux virtual IRQ number.

Can a device tree have more than one interrupt controller?

Yes, cascaded controllers are common — a secondary controller is both a phandle consumer of the primary controller and a phandle provider for its own children.

What error do I get for referencing an undefined label?

The device tree compiler (dtc) fails the build with an explicit undefined-label error, unlike a mismatched cell count which may not be caught.

 

Continue the Free Embedded Linux Bootloader Course

Next up: splitting shared hardware definitions across device tree include files with .dtsi.

PREV_LEC NEXT_LEC

Leave a Reply

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