Everything we’ve covered so far in this chapter — nodes, the reg property, phandles, include files — exists in human-readable .dts/.dtsi source. But the kernel never reads that text directly. Somewhere between your source files and the running kernel, a tool called the device tree compiler, dtc, turns all of it into a compact binary format that the bootloader can load quickly and the kernel can parse without a text parser at boot time. This lecture closes out the device tree portion of this free embedded systems course by walking through that compilation step in real detail.
What You Will Learn
- What
dtcis and where it lives, both in the kernel tree and as a standalone package - The full pipeline from
.dts/.dtsisource to a bootable.dtb - How to compile a device tree with includes, and how the kernel build system automates this
- Useful
dtccommand-line flags for warnings, symbols, and decompilation - How to sanity-check a compiled
.dtbbefore flashing it to a board
Prerequisites
This lecture assumes familiarity with device tree source syntax, the reg property, phandles, and include files from the previous three lectures in this free linux kernel development course.
What dtc Actually Does
The device tree compiler reads one or more .dts/.dtsi source files (already flattened by the C preprocessor if they use #include), validates their syntax, resolves every &label reference into a numeric phandle, and emits a compact, self-describing binary format called the Flattened Device Tree (FDT), stored in a file conventionally ending in .dtb — a device tree blob.
You’ll find a copy of dtc inside the Linux kernel source tree itself, at scripts/dtc/dtc, built automatically as part of a normal kernel build. It’s also packaged standalone on most Linux distributions, which is handy for compiling and inspecting device trees without a full kernel build tree on hand.
$ dtc –version
Version: DTC 1.7.0
Compiling a Simple Device Tree
For a self-contained source file that doesn’t use #include, compiling is a single direct command:
DTC: dts->dts on file “ep-simple.dts”
$ file ep-simple.dtb
ep-simple.dtb: Device Tree Blob version 17, size=1240, boot CPU=0, string block size=142, DT structure block size=1032
Compiling a Device Tree That Uses #include
The moment your source uses C-style #include (covered in the previous lecture), you need to run it through the C preprocessor first — dtc itself doesn’t understand #include or #define, only the flattened Open Firmware /include/ form. This is exactly what the kernel’s own build system automates for you when you run a normal kernel build, but it’s useful to see the manual two-step to understand what’s happening underneath:
ep-demo-board.dts ep-demo-board.pre.dts$ dtc -I dts -O dtb ep-demo-board.pre.dts -o ep-demo-board.dtb
DTC: dts->dts on file “ep-demo-board.pre.dts”
In an actual kernel build tree, you never run these two commands by hand — the kernel’s Makefile-based build system (kbuild) does this automatically for every .dts file listed in your platform’s Makefile:
DTC arch/arm64/boot/dts/ep/ep-demo-board.dtb$ ls arch/arm64/boot/dts/ep/*.dtb
arch/arm64/boot/dts/ep/ep-demo-board.dtb
Useful dtc Command-Line Options
| Flag | Purpose |
|---|---|
-I <format> |
Input format: dts (source text) or dtb (binary, for decompiling) |
-O <format> |
Output format: dtb (compile) or dts (decompile) |
-o <file> |
Output file path |
-W <warning> |
Enable/disable a specific class of warning (e.g. unit-address mismatches) |
-@ |
Generate a __symbols__ node, needed for device tree overlays to resolve phandles at runtime |
-f |
Force output even if the compiler detected non-fatal errors |
ep-demo-board.pre.dts -o ep-demo-board.dtb
ep-demo-board.pre.dts:14.5-18: Warning (unit_address_vs_reg): \
node has a reg or ranges property, but no unit name
That particular warning is one of the most common ones beginners hit: if a node’s name includes a @unit-address suffix, the address portion must match the first value in its reg property exactly. A mismatch here almost always means a typo, and it’s worth fixing rather than ignoring.
Decompiling: Reading a .dtb Back as Text
We’ve used this already in the previous lecture, but it deserves its own callout here: dtc works in both directions. Feeding it a .dtb as input and asking for .dts output lets you inspect exactly what a bootloader or kernel is actually working with — including a .dtb you didn’t build yourself, such as one already flashed on a board.
$ head -n 15 live-tree.dts
/dts-v1/;/ {
compatible = “ep,demo-board”, “ep,socx”;
model = “EP Demo Board Rev A”;
#address-cells = <0x02>;
#size-cells = <0x02>;
…
This is one of the fastest ways to confirm exactly what device tree a board actually booted with, versus what you think you built and flashed — the two are not always the same, especially when a bootloader applies its own overlay on top before handing control to the kernel.
A Minimal Build-and-Boot Sanity Check
Here’s a small, complete original example you can run end to end to see every stage of the pipeline for yourself, using a fictional demo node rather than any real board’s tree:
compatible = “ep,check-demo”;
#address-cells = <1>;
#size-cells = <1>;demo@60000000 {
compatible = “ep,demo-block”;
reg = <0x60000000 0x1000>;
status = “okay”;
};
};
$ dtc -I dtb -O dts ep-check.dtb -o ep-check.roundtrip.dts
$ diff <(grep -v ‘^/’ ep-check.dts) <(grep -v ‘^/’ ep-check.roundtrip.dts)
$ echo $?
0
A clean diff (aside from the auto-generated header comments dtc adds on decompile) confirms the compiler preserved your intent exactly — a useful habit any time you’re debugging whether a device tree bug is in your source or somewhere later in the boot pipeline.
Where the .dtb Goes Next
Once compiled, the .dtb doesn’t get linked into the kernel image itself in most workflows — it’s a separate file that the bootloader loads into memory alongside the kernel and passes a pointer to, which is exactly the handoff mechanism we covered earlier in this bootloader course. Some platforms embed the .dtb directly appended to the kernel image instead; check your specific bootloader’s boot script to see which convention your platform uses.
Common Mistakes and Troubleshooting
- Running dtc directly on a file with #include. Without preprocessing first, dtc will fail on the raw
#include/#definelines it doesn’t understand — always preprocess first, or let kbuild handle it. - Ignoring unit-address warnings. A
unit_address_vs_regwarning almost always points at a real typo between a node’s@addresssuffix and itsregproperty. - Forgetting
-@when building a base tree meant to host overlays. Without the generated__symbols__node, overlay files can’t resolve phandle references into the base tree at runtime. - Assuming the flashed .dtb matches your source. Always decompile the live tree from the board itself when debugging, rather than trusting that what you built is what’s actually running.
Best Practices
- Let kbuild handle preprocessing and compilation for in-tree boards; only invoke
cpp/dtcmanually when debugging the pipeline itself. - Enable relevant
-Wwarnings during development of a new board file, and treat them as real bugs, not noise. - Keep a habit of decompiling your own compiled output to confirm it matches your intent before flashing to hardware.
- Use
-@on any base tree you expect to extend with device tree overlays later.
Summary and Key Takeaways
dtc, the device tree compiler, turns human-readable.dts/.dtsisource into a binary Flattened Device Tree (.dtb).- Source using C-style
#include/#definemust be run throughcppfirst — kbuild automates this for in-tree builds. dtcworks both directions:-I dts -O dtbto compile,-I dtb -O dtsto decompile.- Enabling
-Wwarnings during development catches common mistakes like unit-address mismatches early. - Always verify what’s actually running on a board by decompiling its live tree, not just trusting your build output.
Conclusion
With this lecture, we’ve followed the device tree all the way from hand-written source to the binary blob a bootloader hands off to a booting Linux kernel — completing the device tree portion of this free embedded linux bootloader course. The reg property, phandles, include files, and compilation together form a complete, if compact, hardware description language, and being fluent in reading and building it is one of the most transferable skills in embedded Linux bring-up work. Next in this free embedded systems course, we’ll pick up where the kernel takes over after this handoff and look at early kernel boot.
Frequently Asked Questions
What does the device tree compiler dtc actually produce?
A compact binary format called the Flattened Device Tree, stored in a .dtb file, compiled from human-readable .dts/.dtsi source text.
Where is dtc located in the Linux kernel source?
At scripts/dtc/dtc, built automatically as part of a normal kernel build; it’s also available as a standalone package on most distributions.
Why can’t dtc directly compile a .dts file that uses #include?
Because dtc only understands the Open Firmware /include/ directive, not C-style #include/#define; those need to be resolved by the C preprocessor first.
How do I decompile a .dtb back into readable text?
Run dtc -I dtb -O dts on the binary file, which produces a text representation of exactly what’s encoded in the blob.
What does the -@ flag do when compiling a device tree?
It generates a __symbols__ node in the output, which device tree overlays need to resolve phandle references into the base tree at runtime.
What does a unit_address_vs_reg warning from dtc mean?
It means a node’s @unit-address suffix doesn’t match the first value in its reg property, almost always indicating a typo.
Does the kernel build system run dtc automatically?
Yes — running a target like make dtbs invokes preprocessing and dtc automatically for every device tree file listed in the platform’s build files.
Continue the Free Embedded Linux Bootloader Course
That completes the Device Tree topic — next, we move into what happens as the Linux kernel takes over after handoff.
