Skip to content

device tree overlay

Marek Bykowski edited this page Sep 15, 2026 · 2 revisions

Devicetree Overlay Mechanics: Labels, Symbols, and Fixups

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.

The mental model

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

Step 1 — the base tree

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.

Step 2 — the overlay, compiled with zero knowledge of the base

/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 the target property inside fragment@0, cell 0, there's a phandle that needs to be patched once you know what my_widget resolves to."

Step 3 — actually apply the overlay (the "linking" step)

$ 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:

  1. Read the overlay's __fixups__ table: "I need my_widget."
  2. Looked it up in the base's __symbols__ table: "that's /soc/widget@1000."
  3. Read that node's real phandle value from the base (0x01).
  4. Patched the overlay's 0xffffffff placeholder with the real 0x01.
  5. Walked to that resolved target node and merged the overlay's properties onto it — color becomes "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.

One real-world wrinkle: fdtoverlay vs fdtoverlaymerge

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.

Clone this wiki locally