Bootloaders
Device Tree
The same Linux kernel binary runs on wildly different ARM/RISC-V boards without being recompiled for each one — the device tree is why. It's a data structure describing a board's hardware (what's on the I2C bus, which pins are GPIO, where the interrupt controller lives) that the kernel reads at boot instead of having that layout hardcoded into it.
- What it is
- The compiled data structure describing a board's hardware, loaded alongside the kernel and parsed at boot instead of being hardcoded.
- Mental model
- A device tree is a hardware datasheet the kernel reads, not a program it runs — pure description, zero logic.
- Key numbers
.dtssource →dtccompiler →.dtbbinary blob; an add-on board's extra nodes ship as a separate.dtbooverlay merged in at boot.- Reach for it when
- Bringing up new hardware, wiring in a new sensor board, or a correctly-wired device that Linux still refuses to see.
What a device tree actually is
Before device trees, ARM Linux support meant hardcoding every board's
hardware layout directly into kernel C code — a new board revision
could mean a new kernel build. A device tree replaces that with a
data-only description: a tree of nodes
(representing buses, controllers, and devices), each with
properties — the most important being
compatible, a string the kernel matches against a
driver's own of_device_id table to decide which driver
binds to which node. This is exactly the "job posting and résumé"
match described in Linux
drivers — the device tree posts the jobs, drivers hold the
résumés, and the kernel bus code does the matching.
Source, compiler, blob: .dts → .dtb
Device trees are written by hand as human-readable
.dts (device tree source) files, then
compiled by dtc (the device tree
compiler) into a compact binary .dtb
(device tree blob) that the bootloader actually loads into RAM.
/dts-v1/;
/ {
compatible = "raspberrypi,4-model-b";
soc {
i2c1: i2c@7e804000 {
compatible = "brcm,bcm2835-i2c";
reg = <0x7e804000 0x1000>;
interrupts = <2 21>;
status = "okay";
imu@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
};
};
};
};
Reading this: i2c@7e804000 is a node named for its
register base address; reg gives the actual
address/size the kernel maps; interrupts ties it to a
specific interrupt line; the nested imu@68 node
describes a sensor at I2C address 0x68 sitting on that
bus — the exact device tree entry that has to exist before any code
on the Motion/IMU page can
actually talk to that chip.
# compile a .dts source file into a .dtb blob
dtc -I dts -O dtb -o board.dtb board.dts
Overlays: .dtbo
Recompiling and replacing a board's entire base device tree just to
add one sensor is both wasteful and risky. A device tree
overlay is a small, separate tree — compiled to a
.dtbo — that patches specific nodes
into (or modifies nodes in) an already-loaded base tree, without
touching the rest of it. This is exactly how add-on boards (Raspberry
Pi HATs, BeagleBone capes) add their hardware to the tree without the
base board's .dtb ever needing to know about every
possible add-on in advance.
/dts-v1/;
/plugin/;
/ {
compatible = "raspberrypi,4-model-b";
fragment@0 {
target = <&i2c1>;
__overlay__ {
status = "okay";
imu@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
};
};
};
};
/* compile as an overlay with -@ */
/* dtc -@ -I dts -O dtb -o imu-overlay.dtbo imu-overlay.dts */
target = <&i2c1> points the overlay at the
i2c1 node already defined in the base tree (by its
label, the i2c1: from the base .dts
above) — the overlay only makes sense layered on top of a base tree
that actually has a node with that label.
fdt apply) before the kernel boots, or at runtime via the kernel's configfs overlay interface.Where the DTB fits in the boot sequence
The bootloader is responsible for getting a valid device tree blob into memory before it ever jumps to the kernel — the kernel has no way to discover its own hardware without one.
Inspecting a live system
The kernel exposes the device tree it actually booted with, which is invaluable for confirming an overlay applied or a node looks the way you expect — independent of what the source files on disk say.
# the live tree the running kernel actually parsed
ls /sys/firmware/devicetree/base/
cat /proc/device-tree/compatible
# dump the live tree back into readable .dts form
dtc -I fs -O dts /sys/firmware/devicetree/base | less
# inspect a .dtb file directly, without a running kernel at all
fdtdump board.dtb
This is the same "what's actually bound" instinct as
Linux drivers'
i2cdetect/ls /sys/bus/platform/devices
commands — trust what the running kernel reports, not what the
source file on disk says it should be.
Real-world applications
- Add-on boards (HATs, capes) — a Raspberry Pi HAT ships its own
.dtboso the base Pi image never needs to know about every possible add-on in advance. - Vendor BSPs supporting many board revisions — one kernel image plus a directory of dozens of
.dtbfiles, with U-Boot selecting the right one at boot (often by reading a board-ID EEPROM). - Custom sensor bring-up — wiring a new I2C/SPI sensor onto a carrier board means writing (or extending) a device tree node before any driver or userspace code in this notebook's Sensors pages can reach it.
- Runtime hardware reconfiguration — the kernel's configfs overlay interface lets a running system apply a
.dtbowithout rebooting — used for FPGA-based boards that change their own peripheral set at runtime.
Gotchas
- A
compatiblestring that doesn't exactly match a driver'sof_device_idtable means the driver silently never binds — no error, the device just never appears. status = "disabled"on a node means the kernel deliberately skips it — a node existing in the tree is not the same as it being active.- An overlay's
targetlabel has to exist in the exact base tree actually loaded — vendor BSPs often ship several base.dtbvariants, and an overlay written against the wrong one fails to apply with a vague error, not a helpful one. - Editing a
.dtssource file does nothing until it's recompiled and the resulting.dtbis actually the one the bootloader loads — a stale.dtbleft over from a previous build is a common source of "I changed it but nothing happened."