Create your own
Lesson illustration

DTB Compilation, Decompilation, and Error Resolution

Welcome back. In the previous lesson, you learned to read board-level Device Tree changes as a hardware contract: inherited SoC facts describe the silicon, while the board DTS enables only the interfaces that are actually routed and populated. This time you will turn that understanding into an engineering workflow: produce a DTB, inspect the effective compiled tree, validate it at multiple levels, and correct an address-cell error rather than merely silencing a warning.

The immediate target is a small local lab, not an AM62x board deployment. That is intentional: it lets you learn the toolchain and failure signatures safely before modifying a vendor kernel tree. The same validation loop will later protect your AM62x CAN-FD, Ethernet, GPIO, and sensor changes.


What a successful DTB workflow proves

A .dts file is source code for a hardware description; a .dtb is its flattened binary representation. During a normal kernel build, the DTS is first preprocessed—expanding includes and macros—then compiled by the Device Tree Compiler, dtc.

There are three distinct questions to answer:

QuestionToolingWhat it catches
Is the source structurally well formed?dtcSyntax errors, unresolved labels, malformed reg length, duplicate names, selected generic consistency checks
Does the description obey the hardware binding?Kernel dtbs_checkMissing required properties, invalid compatible strings, invalid phandle specifiers, illegal values and node layouts
Did Linux receive and use the intended tree?Boot logs, /sys/firmware/devicetree/base, driver evidenceWrong deployed DTB, U-Boot fixups, probe failures, wiring or pinmux problems

The distinction matters. dtc understands tree structure but does not understand every hardware binding. It can compile a tree containing an invented compatible string or an electrically impossible pin selection. Schema validation provides the next level of assurance, and boot-time evidence provides the final one.

The GPIO controller mapping image illustrates this separation between description and hardware reality. Its reg property corresponds to a peripheral memory-map entry; its interrupts property corresponds to an interrupt-controller table entry. The values shown are specific to the illustrated platform, not AM62x values to reuse.

A GPIO controller node’s `reg` and `interrupts` properties are mapped to the controller’s physical register range and interrupt-table entry. The diagram is a conceptual illustration; its AM335x-era addresses and interrupt values must not be copied to an AM62x tree.

manual.txt

Read the relevant parts of DTC manual to establish the supported source and binary formats, the essential command-line model, and the role of fdtdump.

In “3) Command Line,” read the input, output, and checking overview. Focus on the distinction between dts, dtb, and the filesystem representation (fs), then note that the compiler performs sanity checks in addition to format conversion. In “IV - Utility Tools,” read the short fdtdump subsection to see where it fits: use it for a readable dump when diagnosing a binary artifact, but prefer dtc -I dtb -O dts when you need editable decompiled source.


Compile and decompile: inspect the artifact, not only the source

First verify that dtc exists on the Ubuntu host:

dtc --version

If it is not installed, install the Ubuntu package device-tree-compiler using the APT workflow established earlier, then re-run the version command.

For a standalone DTS with no kernel-specific includes, compilation is direct:

dtc -I dts -O dtb -o lab.dtb lab.dts

The options mean:

  • -I dts: interpret the input as Device Tree Source.
  • -O dtb: produce a binary Device Tree Blob.
  • -o lab.dtb: write the result to the named artifact.

Decompile the resulting binary immediately:

dtc -I dtb -O dts -o lab.decompiled.dts lab.dtb

This is not a textual round trip. The decompiled output normally loses comments, original include-file boundaries, formatting choices, and much of the source-level intent. It shows what the consumer receives after the tree has been flattened: merged nodes, final overridden property values, numeric phandles where appropriate, and binary-encoded values rendered as DTS cells.

For an additional, read-only binary inspection:

fdtdump lab.dtb | less

Use fdtdump when a DTB is the only artifact available—for example, one extracted from a boot partition—and you need a quick readable view. Use decompilation when you want to compare a compiled artifact against expected final properties.

For a real AM62x kernel tree, do not normally invoke dtc directly on a board DTS. AM62x DTS files rely on kernel include paths, C preprocessor macros, and generated headers. Let the kernel build system create the final command line.

3.2.1. Users Guide — Processor SDK AM62x Documentation

Read Texas Instruments’ AM62x SDK guidance to locate board DTS files and connect a board source file to its generated DTB.

In Section “3.2.1.5.2 Compiling the Device Tree Binaries,” read the AM62x DTB build guidance. Pay particular attention to the AM62x SK mapping from k3-am625-sk.dts to its DTB and to the stated arch/arm64/boot/dts/ti source and output location. On a derivative board, first confirm the actual board DTS and vendor-kernel revision; never assume the reference-board filename is the DTB loaded by your boot flow.

A typical targeted kernel build follows this pattern:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
    arch/arm64/boot/dts/ti/<your-board>.dtb

Replace <your-board> with the DTS basename selected by your kernel configuration and board. If your vendor SDK supplies a toolchain environment, use its documented CROSS_COMPILE prefix rather than assuming aarch64-linux-gnu-.

The generated file usually appears under:

arch/arm64/boot/dts/ti/

For a broad rebuild during active board work:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs

Targeted builds are usually the better iteration loop: they reduce build time and make it clearer which artifact you intend to deploy.


The address-cell rule: the parent defines a child’s reg encoding

The most common conceptual error is to interpret reg locally. Its encoding is defined by the parent node.

Suppose a parent I2C controller declares:

#address-cells = <1>;
#size-cells = <0>;

Every direct child’s reg must therefore contain:

  • one cell for its I2C address;
  • zero cells for a size.

A sensor at I2C address 0x48 is correctly described as:

sensor@48 {
        compatible = "vendor,example-sensor";
        reg = <0x48>;
};

The node name’s unit address, @48, agrees with the first address encoded in reg.

The controller itself follows a different rule. If it is a child of an SoC bus declaring one address cell and one size cell, the controller’s own reg needs two cells:

i2c@2000 {
        reg = <0x2000 0x100>;

        #address-cells = <1>;
        #size-cells = <0>;

        /* These cell declarations affect children, not i2c@2000 itself. */
};

This “look upward first” rule prevents a large class of errors:

  1. Find the node containing the reg property.
  2. Move to its immediate parent.
  3. Read the parent’s #address-cells and #size-cells.
  4. Count the cells required for each address-size tuple.
  5. Check that the unit address agrees with the first encoded address when applicable.

Device Tree 101 10:00 AM UTC+1 session

Watch these two excerpts from Device Tree 101 by Bootlin. They distinguish generic compiler checks from binding-based semantic validation, then give a precise explanation of address and size cells.

Watch binding validation first. Focus on why a DTS that passes dtc can still violate the binding, and on the purpose of the kernel dtbs_check target. Then watch cell encoding. Track the key rule: #address-cells and #size-cells belong to a parent and define the reg layout of that parent’s direct children.


Lab: create, diagnose, and fix an address-cell error

Create a working directory that is separate from a vendor kernel checkout:

mkdir -p ~/dtb-lab
cd ~/dtb-lab

Create address-cells-broken.dts with the following deliberately faulty source:

/dts-v1/;

/ {
        compatible = "grasp,dtb-validation-lab";
        #address-cells = <2>;
        #size-cells = <2>;

        soc {
                compatible = "simple-bus";
                #address-cells = <1>;
                #size-cells = <1>;
                ranges = <0x0 0x0 0x10000000 0x00100000>;

                i2c@2000 {
                        compatible = "vendor,example-i2c";
                        reg = <0x2000 0x100>;

                        #address-cells = <1>;
                        #size-cells = <0>;

                        sensor@48 {
                                compatible = "vendor,example-sensor";
                                reg = <0x00 0x48>;
                        };
                };
        };
};

Compile it while retaining the full diagnostic output:

dtc -I dts -O dtb -o address-cells-broken.dtb \
    address-cells-broken.dts 2>&1 | tee address-cells-broken.log

The precise wording depends on the installed dtc version, but expect a warning in the reg_format family. It should indicate that the sensor@48 node’s reg length does not agree with its parent’s address- and size-cell declarations.

Do not be misled if dtc still creates a .dtb. A generated binary is not evidence that the design is valid. Treat any warning as a defect requiring an explanation or correction.

Trace the error mechanically:

NodeParent’s cell ruleRequired reg representationActual representation
i2c@2000soc has one address cell and one size cell<base length><0x2000 0x100> — correct
sensor@48i2c@2000 has one address cell and zero size cells<i2c-address><0x00 0x48> — incorrect: two cells

The incorrect sensor node is not a 64-bit address. It is an I2C child whose reg must encode one bus address. Correct the property in a new file, address-cells-fixed.dts:

/dts-v1/;

/ {
        compatible = "grasp,dtb-validation-lab";
        #address-cells = <2>;
        #size-cells = <2>;

        soc {
                compatible = "simple-bus";
                #address-cells = <1>;
                #size-cells = <1>;
                ranges = <0x0 0x0 0x10000000 0x00100000>;

                i2c@2000 {
                        compatible = "vendor,example-i2c";
                        reg = <0x2000 0x100>;

                        #address-cells = <1>;
                        #size-cells = <0>;

                        sensor@48 {
                                compatible = "vendor,example-sensor";
                                reg = <0x48>;
                        };
                };
        };
};

Now compile and decompile the corrected artifact:

dtc -I dts -O dtb -o address-cells-fixed.dtb \
    address-cells-fixed.dts

dtc -I dtb -O dts -o address-cells-fixed.decompiled.dts \
    address-cells-fixed.dtb

Inspect the final node:

grep -A4 -B1 'sensor@48' address-cells-fixed.decompiled.dts

The essential evidence is that:

  • compilation completes with no diagnostic;
  • sensor@48 contains reg = <0x48>;;
  • the parent I2C node still declares #address-cells = <1>; and #size-cells = <0>;;
  • the unit address and encoded bus address agree.

Record the original diagnostic, corrected DTS, commands, tool version, and resulting DTB hash in the private project repository. This is the kind of compact bring-up evidence that later supports a credible design review without exposing proprietary board details.


Schema validation in a real kernel tree

The lab above validates a structural rule, but its fictional compatible strings have no kernel binding. In actual AM62x work, validate against the real binding associated with the node’s compatible.

First, find the relevant binding from the kernel source:

grep -RIn 'your,compatible-string' Documentation/devicetree/bindings/

Then run a focused schema check from the configured kernel tree:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
    DT_SCHEMA_FILES="Documentation/devicetree/bindings/<binding-path>.yaml" \
    dtbs_check

Replace <binding-path> with the real path discovered by the search. A focused check reduces noise while you iterate. Before integration or release, also run the full architecture-relevant check:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs_check

Typical schema findings include:

  • a required supply, clock, interrupt, reset, or pin-control property is absent;
  • a compatible value is not permitted by the binding;
  • an array has the wrong number of cells;
  • a GPIO, clock, or interrupt phandle carries the wrong number of provider-defined specifier cells;
  • a child node is placed under the wrong bus or has an invalid reg form.

Schema validation answers “does this description follow the documented software contract?” It does not prove that a sensor is physically populated at that address, that a CAN transceiver is powered, or that a pinmux setting matches the schematic. Those remain board-level verification tasks.


A compact phandle-error triage method

Although this lesson’s correction used address cells, phandle failures follow the same evidence-driven pattern.

If compilation reports an unresolved label such as a reference to &gateway_sensor_pins that does not exist, work through these checks:

  1. Find the consumer.
    Identify the property containing the reference, such as pinctrl-0, clocks, reset-gpios, or interrupt-parent.

  2. Find the provider.
    Search the complete include chain for the label definition. Remember that a label may exist in an SoC .dtsi, a common board .dtsi, or the top-level board DTS.

  3. Check that the provider is appropriate.
    A label resolving successfully does not prove it identifies the correct GPIO controller, pinctrl state, or clock controller.

  4. Check provider cell counts.
    For example, a clock reference must supply exactly the number of cells defined by that provider’s #clock-cells; a GPIO specifier follows #gpio-cells.

  5. Inspect the final DTB.
    Compile and decompile it. This exposes the merged result after board-level overrides and includes have been applied.

The following pattern is valid only when the referenced pin-state label is actually defined in the resulting tree:

&main_i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&gateway_i2c1_pins_default>;
        status = "okay";
};

A common failure is to copy this consumer fragment but omit the pin-controller state definition, rename its label, or define it in a DTS file that the final board DTS does not include.


Validate the DTB Linux actually received

Once you begin deploying DTBs to the AM62x board, compare the intended build artifact with the tree that Linux receives. U-Boot may apply fixups, select a different file than expected, or populate /chosen at boot.

On the target, decompile the live Device Tree filesystem:

dtc -I fs -O dts -o running-tree.dts /sys/firmware/devicetree/base

Then inspect the relevant node:

grep -A12 -B2 'main_i2c1' running-tree.dts

The fs input format reads the directory-and-property-file representation exported by Linux. It is particularly valuable when the source DTS looks correct but the booted system behaves as though the node were disabled or incorrectly configured.

For a board peripheral, close the validation loop with runtime evidence:

  • confirm the expected compatible device was probed in the kernel log;
  • inspect the corresponding subsystem entry in /sys;
  • verify the expected driver has bound;
  • for an I2C component, confirm its bus address and driver binding;
  • for GPIO or CAN, verify the associated controller and pin state are active without conflicting with another board function.

Key takeaways

A dependable Device Tree change is verified in layers.

  • Compile standalone DTS files with dtc -I dts -O dtb; decompile binaries with dtc -I dtb -O dts.
  • For AM62x kernel DTS files, use the kernel build system so preprocessing, include paths, macros, and the configured target architecture are handled correctly.
  • A successful .dtb file alone is insufficient: dtc diagnostics, schema validation, and runtime evidence answer different questions.
  • A child node’s reg encoding is defined by its parent’s #address-cells and #size-cells.
  • The parent’s cell declarations affect its children, not its own reg.
  • dtbs_check validates a DTS against YAML bindings and catches semantic errors that dtc cannot understand.
  • /sys/firmware/devicetree/base reveals the Device Tree Linux actually received after firmware and U-Boot processing.

Next, you will connect this compiled DTB to the boot contract itself: the ARM64 U-Boot-to-kernel register handoff, early head.S execution, MMU activation, and entry into start_kernel().

Can't find a good explanation? Sign up and we'll make it for you

Sign up