Device Tree Debugging for Embedded Linux: Overlays, Common Errors, and Validation
Practical guide to debugging device tree issues in embedded Linux, covering overlay mechanics, common DTS errors, validation tools, and runtime debugging techniques.


The device tree is supposed to be a clean hardware description that separates board-specific details from driver code. In practice, it is one of the most frequent sources of bugs in embedded Linux bring-up. Drivers fail silently because a node is misconfigured, overlays do not apply as expected, and validation tools catch only a fraction of real-world errors. This guide covers the debugging techniques that actually work.
How the device tree flows through the system
Understanding the flow helps locate where problems originate:
- DTS source files (
.dts,.dtsi) are written by the developer - DTC (Device Tree Compiler) compiles them into a DTB (Device Tree Blob)
- The bootloader (U-Boot) may modify the DTB at runtime (fixups, overlays)
- The kernel receives the DTB and uses it to configure drivers and subsystems
Bugs can be introduced at any stage. The DTS might have a typo. DTC might not warn about semantic errors. U-Boot fixups might overwrite your settings. A kernel driver might expect a property format different from what you provided.
Common DTS errors
Missing status = "okay"
Nodes default to status = "disabled" in many SoC-level DTSI files. If you don't override it in your board DTS, the driver never probes:
/* SoC DTSI */
uart3: serial@12340000 {
compatible = "vendor,uart";
reg = <0x12340000 0x1000>;
status = "disabled";
};
/* Your board DTS — this is required! */
&uart3 {
status = "okay";
pinctrl-0 = <&uart3_pins>;
pinctrl-names = "default";
};
Incorrect reg addresses
A wrong base address means the driver reads garbage or hangs. Cross-reference every reg property with the SoC TRM. Off-by-one errors in the address or size are common when converting between hex and decimal.
Missing or wrong clock references
Many drivers refuse to probe if their clock reference is missing or points to the wrong clock. The error message is often unhelpful:
vendor-uart 12340000.serial: could not get clock
Check: is the clock node enabled? Does the clocks property point to the correct clock ID? Does the clock provider node have the right #clock-cells?
Pin mux conflicts
Two peripherals assigned to the same pins. The second one to probe fails, often silently. The pinctrl subsystem logs conflicts at boot if you enable CONFIG_PINCTRL_DEBUG.
Interrupt number mismatches
SoC TRMs number interrupts differently than the device tree expects. ARM GIC interrupts: the DT uses the GIC SPI number (peripheral interrupt minus 32 on most SoCs). Getting this wrong means the driver never receives interrupts.
Validation tools
DTC warnings
DTC itself catches syntax errors and some semantic issues:
dtc -I dts -O dtb -W all -o board.dtb board.dts
The -W all flag enables additional warnings. Pay attention to warnings about missing #address-cells, #size-cells, and compatible strings.
dt-schema (YAML schema validation)
The kernel includes YAML schemas for many bindings. Validate your DTB against them:
make dtbs_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/serial/vendor,uart.yaml
This catches missing required properties and wrong property types. It does not catch wrong values (e.g., wrong clock ID).
fdtdump / fdtget
Inspect the compiled DTB:
fdtdump board.dtb | less
fdtget board.dtb /soc/serial@12340000 status
Useful for verifying that your DTS changes actually made it into the compiled blob.
Runtime debugging
/proc/device-tree (or /sys/firmware/devicetree)
The running kernel exposes the device tree it received. Compare it to what you expected:
# Check a specific property
cat /proc/device-tree/soc/serial@12340000/status
# List all nodes
find /proc/device-tree -name compatible -exec sh -c 'echo "$1: $(cat "$1")"' _ {} \;
dmesg driver probe messages
Enable verbose driver probing:
# Kernel command line
loglevel=8 initcall_debug
Look for "probe" and "failed" messages. Many drivers log why they failed to probe, but the messages scroll past quickly during boot.
debugfs
Many subsystems expose debug info under /sys/kernel/debug/:
# Pin control
cat /sys/kernel/debug/pinctrl/*/pinmux-pins
# Clock tree
cat /sys/kernel/debug/clk/clk_summary
# GPIO
cat /sys/kernel/debug/gpio
# Regulators
cat /sys/kernel/debug/regulator/regulator_summary
These files show the actual runtime state, which may differ from what the device tree requested.
Device tree overlays
Overlays allow modifying the device tree at runtime, typically applied by the bootloader. Common uses:
- Enabling a peripheral for a specific board variant
- Adding a cape or shield description
- Adjusting configuration without rebuilding the base DTB
Creating an overlay
/dts-v1/;
/plugin/;
&uart3 {
status = "okay";
};
&i2c1 {
temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
Compiling and applying
# Compile
dtc -I dts -O dtb -@ -o overlay.dtbo overlay.dts
# Apply in U-Boot
fdt apply overlay.dtbo
# Or apply in Linux (ConfigFS)
mkdir /sys/kernel/config/device-tree/overlays/my-overlay
cat overlay.dtbo > /sys/kernel/config/device-tree/overlays/my-overlay/dtbo
Common overlay problems
Symbols not found: The base DTB must be compiled with -@ (enable symbols) for overlays to reference nodes by label. Without symbols, the overlay cannot find &uart3.
Overlay order matters: If two overlays modify the same node, the order of application determines the final state. Document the required order.
Properties not merged as expected: Overlays replace properties, not merge them. If the base DTB has pinctrl-0 = <&pins_a> and the overlay sets pinctrl-0 = <&pins_b>, the result is <&pins_b>, not <&pins_a &pins_b>.
Debugging workflow
When a peripheral does not work after porting:
- Check status: Is the node enabled? (
fdtget board.dtb /path/to/node status) - Check compatible: Does the
compatiblestring match a driver? (grep -r "compatible_string" drivers/) - Check clocks: Is the clock enabled and correct? (runtime:
/sys/kernel/debug/clk/clk_summary) - Check pins: Are pins muxed correctly? (runtime:
/sys/kernel/debug/pinctrl/*/pinmux-pins) - Check interrupts: Is the interrupt number correct? (
cat /proc/interrupts) - Check dmesg: Does the driver report a probe failure?
- Check power domain: Is the peripheral's power domain enabled?
Work through this list systematically. Most device tree bugs are found in steps 1–4.
Integration with ProteanOS
ProteanOS board support packages include device tree sources that follow mainline kernel conventions. The porting guide covers how to create and submit device tree files for new boards. The porting checklist includes device tree validation as a milestone.
For build system integration, device tree compilation is part of the standard ProKit kernel build process.
Summary
Device tree debugging is detective work. The device tree is a static description interpreted by dynamic code, and the gap between what you wrote and what the kernel understands is where bugs hide. Use validation tools before boot, runtime debug files after boot, and work through the seven-step checklist when things do not work. Document your device tree as thoroughly as your code—the next person debugging it will be you, six months from now.