Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

Device Tree Dynamic Object format internals
-------------------------------------------

The Device Tree for most platforms is a static representation of
the hardware capabilities. This is insufficient for platforms
that need to dynamically insert Device Tree fragments into the
live tree.

This document explains the the Device Tree object format and
modifications made to the Device Tree compiler, which make it possible.

1. Simplified Problem Definition
--------------------------------

Assume we have a platform which boots using following simplified Device Tree.

---- foo.dts -----------------------------------------------------------------
	/* FOO platform */
	/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };
		};
	};
---- foo.dts -----------------------------------------------------------------

We have a number of peripherals that after probing (using some undefined method)
should result in different Device Tree configuration.

We cannot boot with this static tree because due to the configuration of the
foo platform there exist multiple conficting peripherals DT fragments.

So for the bar peripheral we would have this:

---- foo+bar.dts -------------------------------------------------------------
	/* FOO platform + bar peripheral */
	/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };

			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			};
		};
	};
---- foo+bar.dts -------------------------------------------------------------

While for the baz peripheral we would have this:

---- foo+baz.dts -------------------------------------------------------------
	/* FOO platform + baz peripheral */
	/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
			/* baz resources */
			baz_res: res_baz { ... };
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };

			/* baz peripheral */
			baz {
				compatible = "corp,baz";
				/* reference to another point in the tree */
				ref-to-res = <&baz_res>;
				... /* various properties and child nodes */
			};
		};
	};
---- foo+baz.dts -------------------------------------------------------------

We note that the baz case is more complicated, since the baz peripheral needs to
reference another node in the DT tree.

2. Device Tree Object Format Requirements
-----------------------------------------

Since the Device Tree is used for booting a number of very different hardware
platforms it is imperative that we tread very carefully.

2.a) No changes to the Device Tree binary format for the base tree. We cannot
modify the tree format at all and all the information we require should be
encoded using Device Tree itself. We can add nodes that can be safely ignored
by both bootloaders and the kernel. The plugin dtbs are optionally tagged
with a different magic number in the header but otherwise they're simple
blobs.

2.b) Changes to the DTS source format should be absolutely minimal, and should
only be needed for the DT fragment definitions, and not the base boot DT.

2.c) An explicit option should be used to instruct DTC to generate the required
information needed for object resolution. Platforms that don't use the
dynamic object format can safely ignore it.

2.d) Finally, DT syntax changes should be kept to a minimum. It should be
possible to express everything using the existing DT syntax.

3. Implementation
-----------------

The basic unit of addressing in Device Tree is the phandle. Turns out it's
relatively simple to extend the way phandles are generated and referenced
so that it's possible to dynamically convert symbolic references (labels)
to phandle values. This is a valid assumption as long as the author uses
reference syntax and does not assign phandle values manually (which might
be a problem with decompiled source files).

We can roughly divide the operation into two steps.

3.a) Compilation of the base board DTS file using the '-@' option
generates a valid DT blob with an added __symbols__ node at the root node,
containing a list of all nodes that are marked with a label.

Using the foo.dts file above the following node will be generated;

$ dtc -@ -O dtb -o foo.dtb -b 0 foo.dts
$ fdtdump foo.dtb
...
/ {
	...
	res {
		...
		phandle = <0x00000001>;
		...
	};
	ocp {
		...
		phandle = <0x00000002>;
		...
	};
	__symbols__ {
		res="/res";
		ocp="/ocp";
	};
};

Notice that all the nodes that had a label have been recorded, and that
phandles have been generated for them.

This blob can be used to boot the board normally, the __symbols__ node will
be safely ignored both by the bootloader and the kernel (the only loss will
be a few bytes of memory and disk space).

We generate a __symbols__ node to record nodes that had labels in the base
tree (or subsequent loaded overlays) so that they can be matched up with
references made to them in Device Tree objects.

3.b) The Device Tree fragments must be compiled with the same option but they
must also have a tag (/plugin/) that allows undefined references to nodes
that are not present at compilation time to be recorded so that the runtime
loader can fix them.

So the bar peripheral's DTS format would be of the form:

/dts-v1/;
/plugin/;	/* allow undefined references and record them */
/ {
	....	/* various properties for loader use; i.e. part id etc. */
	fragment@0 {
		target = <&ocp>;
		__overlay__ {
			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			}
		};
	};
};

Note that there's a target property that specifies the location where the
contents of the overlay node will be placed, and it references the node
in the foo.dts file.

$ dtc -@ -O dtb -o bar.dtbo -b 0 bar.dts
$ fdtdump bar.dtbo
...
/ {
	... /* properties */
	fragment@0 {
		target = <0xffffffff>;
		__overlay__ {
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			}
		};
	};
	__fixups__ {
	    ocp = "/fragment@0:target:0";
	};
};

No __symbols__ node has been generated (no label in bar.dts).
Note that the target's ocp label is undefined, so the phandle
value is filled with the illegal value '0xffffffff', while a __fixups__
node has been generated, which marks the location in the tree where
the label lookup should store the runtime phandle value of the ocp node.

The format of the __fixups__ node entry is

  <label> = "<local-full-path>:<property-name>:<offset>" 
	    [, "<local-full-path>:<property-name>:<offset>"...];

  <label> 		Is the label we're referring
  <local-full-path>	Is the full path of the node the reference is
  <property-name>	Is the name of the property containing the
			reference
  <offset>		The offset (in bytes) of where the property's
			phandle value is located.

Doing the same with the baz peripheral's DTS format is a little bit more
involved, since baz contains references to local labels which require
local fixups.

/dts-v1/;
/plugin/;	/* allow undefined label references and record them */
/ {
	....	/* various properties for loader use; i.e. part id etc. */
	fragment@0 {
		target = <&res>;
		__overlay__ {
			/* baz resources */
			baz_res: res_baz { ... };
		};
	};
	fragment@1 {
		target = <&ocp>;
		__overlay__ {
			/* baz peripheral */
			baz {
				compatible = "corp,baz";
				/* reference to another point in the tree */
				ref-to-res = <&baz_res>;
				... /* various properties and child nodes */
			}
		};
	};
};

Note that &bar_res reference.

$ dtc -@ -O dtb -o baz.dtbo -b 0 baz.dts
$ fdtdump baz.dtbo
...
/ {
	... /* properties */
	fragment@0 {
		target = <0xffffffff>;
		__overlay__ {
			res_baz {
				....
				phandle = <0x00000001>;
			};
		};
	};
	fragment@1 {
		target = <0xffffffff>;
		__overlay__ {
			baz {
				compatible = "corp,baz";
				... /* various properties and child nodes */
				ref-to-res = <0x00000001>;
			}
		};
	};
	__fixups__ {
		res = "/fragment@0:target:0";
		ocp = "/fragment@1:target:0";
	};
	__local_fixups__ {
		fragment@1 {
			__overlay__ {
				baz {
					ref-to-res = <0>;
				};
			};
		};
	};
};

This is similar to the bar case, but the reference of a local label by the
baz node generates a __local_fixups__ entry that records the place that the
local reference is being made. No matter how phandles are allocated from dtc
the run time loader must apply an offset to each phandle in every dynamic
DT object loaded. The __local_fixups__ node records the offset relative to the
start of every local reference within that property so that the loader can apply
the offset.