device tree overlay - MarekBykowski/readme GitHub Wiki
A minimal, from-scratch walkthrough of how &label references in a devicetree
overlay actually get resolved — using a toy example with no USB/board-specific
content, so the mechanism is the only thing visible.
A label reference in an overlay is a deferred lookup, not a compile-time guarantee. The overlay carries a request ("I need the phandle for this label"). The base tree carries an answer key (a symbol table). Whoever finally has both in hand — a build-time merge tool, or the kernel/bootloader at boot — is the only place actual resolution happens.
This maps almost exactly onto ordinary compile-and-link:
| Devicetree | Software equivalent |
|---|---|
dtc -@ compiling a /plugin/; overlay |
gcc -c producing an object file with an unresolved external symbol |
__fixups__ table |
the object file's relocation table |
__symbols__ table (on the base) |
a shared library's exported symbol table |
fdtoverlay / fdtoverlaymerge
|
the linker |
A plain, complete devicetree with one labeled node:
/dts-v1/;
/ {
compatible = "example,board";
soc {
my_widget: widget@1000 {
compatible = "example,widget";
status = "okay";
color = "red";
};
};
};
Compile it with -@ (this is what tells dtc to generate a symbol table):
$ dtc -@ -I dts -O dtb -o base.dtb base.dts
Decompile it back to see what dtc actually added:
/dts-v1/;
/ {
compatible = "example,board";
soc {
widget@1000 {
compatible = "example,widget";
status = "okay";
color = "red";
phandle = <0x01>;
};
};
__symbols__ {
my_widget = "/soc/widget@1000";
};
};
Two things happened automatically:
- The labeled node got a real numeric
phandle = <0x01>. - A
__symbols__node was added, mapping the label name to the node's absolute path. This is the base's exported symbol table.
/dts-v1/;
/plugin/;
&my_widget {
color = "blue";
};
Compile it alone — dtc has never seen base.dts at this point:
$ dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts
Decompile the standalone result:
/dts-v1/;
/ {
fragment@0 {
target = <0xffffffff>;
__overlay__ {
color = "blue";
};
};
__fixups__ {
my_widget = "/fragment@0:target:0";
};
};
dtc can't resolve &my_widget — no label by that name exists anywhere in
this file's own text. Because this is a /plugin/; file compiled with
-@, dtc doesn't error out. It defers:
-
target = <0xffffffff>— a dummy placeholder value, not a real phandle. -
__fixups__ { my_widget = "/fragment@0:target:0"; }— literally "at thetargetproperty insidefragment@0, cell 0, there's a phandle that needs to be patched once you know whatmy_widgetresolves to."
$ fdtoverlay -i base.dtb -o final.dtb overlay.dtbo
Decompile the final result:
/dts-v1/;
/ {
compatible = "example,board";
soc {
widget@1000 {
compatible = "example,widget";
status = "okay";
color = "blue";
phandle = <0x01>;
};
};
__symbols__ {
my_widget = "/soc/widget@1000";
};
};
What fdtoverlay did:
- Read the overlay's
__fixups__table: "I needmy_widget." - Looked it up in the base's
__symbols__table: "that's/soc/widget@1000." - Read that node's real
phandlevalue from the base (0x01). - Patched the overlay's
0xffffffffplaceholder with the real0x01. - Walked to that resolved target node and merged the overlay's properties
onto it —
colorbecomes"blue"(overridden),status="okay"stays untouched (the overlay never mentioned it).
The result is an ordinary, complete devicetree again — no leftover
fragments, no __fixups__, no placeholder. Fully resolved.
This example used fdtoverlay, which collapses everything into one final,
flat tree — correct when the "base" you're applying onto really is the last
step before boot.
In build pipelines where the "base" is itself still another overlay
(e.g. a per-board .dtbo that will later be applied onto a more fundamental
tree at actual device boot time), the tool used instead is
fdtoverlaymerge. It runs the exact same symbol/fixup resolution described
above, but deliberately produces another mergeable overlay as output
(still fragments, still a placeholder-shaped structure) rather than
flattening everything down — because there's one more merge step still to
come, later, at boot.
Same resolution mechanism either way. Only the shape of the output differs, based on whether what you're merging onto is the final tree or one more overlay away from final.