Embedded Linux Essentials
intermediate
Weight: 4/10

Linux device tree

Understand device tree syntax, node structure, compatible strings, overlays, and how the kernel uses DTB to discover and configure hardware on embedded Linux systems.

embedded-linux
device-tree
dts
dtb
compatible
overlays
pinctrl

Quick Cap

Device tree (DT) is a data structure that describes hardware to the Linux kernel — what peripherals exist, where their registers are mapped, which interrupts they use, and how they connect to buses. Before device tree, every ARM board needed a custom board file compiled into the kernel. Device tree moved hardware description out of the kernel source into separate .dts files, making one kernel binary support many boards. Understanding DT syntax and the driver binding mechanism is essential for any embedded Linux role.

Key Facts:

  • DTS (source) is compiled by DTC (compiler) into DTB (binary blob) that the bootloader passes to the kernel
  • compatible property is the key to driver matching — it connects a DT node to the right kernel driver
  • reg specifies register base address and size; interrupts specifies IRQ connections
  • status = "okay" enables a device; status = "disabled" disables it (the #1 debugging gotcha)
  • Overlays (.dtbo) modify the base DTB at runtime — used for hardware add-ons (capes, HATs, mezzanine boards)
  • The kernel never hardcodes peripheral addresses on ARM — everything comes from the device tree

Deep Dive

At a Glance

CharacteristicDetail
File formatDTS (source, human-readable) → DTB (binary, passed to kernel)
CompilerDTC (Device Tree Compiler), part of kernel source
Passed byBootloader (U-Boot) loads DTB and passes address to kernel
PurposeDescribe hardware topology, register maps, interrupts, clocks, GPIOs
ReplacesBoard files (arch/arm/mach-*), ACPI tables (on some ARM)
Location in sourcearch/arm/boot/dts/ (32-bit) or arch/arm64/boot/dts/vendor/ (64-bit)

The DTS → DTB Pipeline

px-2 py-1 rounded text-sm font-mono border
my-board.dts (board-specific: enables devices, sets properties)
│ #include
soc-base.dtsi (SoC-level: defines all peripherals, status="disabled")
┌─────────┐
│ DTC │ Device Tree Compiler
└────┬────┘
my-board.dtb Binary blob (~20-100 KB)
│ U-Boot loads to RAM at fdt_addr
Linux kernel Parses DTB at boot, creates platform_device for each node

The .dtsi include pattern: SoC vendors provide a base .dtsi file that defines every peripheral on the SoC with status = "disabled". Board-specific .dts files include the base and selectively enable devices with status = "okay" and set board-specific properties (pin assignments, I2C addresses, clock frequencies).

Node Structure

A device tree node describes one hardware block. Here is a typical I2C temperature sensor node:

dts
&i2c1 {
status = "okay";
clock-frequency = <400000>; /* 400 kHz Fast mode */
temperature-sensor@48 {
compatible = "ti,tmp102"; /* Matches driver's of_match_table */
reg = <0x48>; /* I2C slave address */
interrupt-parent = <&gpio3>;
interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
#thermal-sensor-cells = <0>;
};
};

Key properties every embedded Linux developer must know:

PropertyPurposeExample
compatibleIdentifies the device — kernel searches for a matching driver"ti,tmp102", "bosch,bme280"
regRegister base address + size (memory-mapped) or bus address (I2C/SPI)<0x40010000 0x400> or <0x48>
interruptsIRQ number and trigger type<7 IRQ_TYPE_EDGE_FALLING>
clocksPhandle to clock source(s)<&rcc SPI1_CK>
status"okay" = enabled, "disabled" = ignored by kernel"okay"
#address-cellsNumber of 32-bit cells for child address<1> (32-bit) or <2> (64-bit)
#size-cellsNumber of 32-bit cells for child size<1> or <0> (no size, e.g., I2C)

How compatible Strings Work

The compatible property is a list of strings, most-specific first:

dts
compatible = "mycompany,custom-sensor", "bosch,bme280";

The kernel tries to match each string against registered drivers. If no driver matches "mycompany,custom-sensor", it falls back to "bosch,bme280". This fallback mechanism enables forward compatibility — a new SoC revision can list its specific compatible first and a generic one as fallback.

Format convention: "vendor,device" — the vendor prefix is registered in Documentation/devicetree/bindings/vendor-prefixes.yaml.

Device Tree Overlays

Overlays (.dtbo) modify the base DTB at runtime without recompiling. They are loaded by the bootloader (U-Boot fdt apply) or at runtime on systems that support it.

Use cases:

  • Hardware add-ons: BeagleBone capes, Raspberry Pi HATs — the overlay enables the right peripherals for the attached board
  • Factory configuration: same base image, different overlays for product variants
  • Development: quickly enable/disable peripherals without rebuilding the entire DTB
dts
/* overlay: enable SPI1 and add a display */
/dts-v1/;
/plugin/;
&spi1 {
status = "okay";
display@0 {
compatible = "ilitek,ili9341";
reg = <0>; /* CS0 */
spi-max-frequency = <32000000>;
};
};

Device Tree vs Alternatives

ApproachUsed ByHardware DescriptionRuntime Modifiable
Device TreeARM, RISC-V, PowerPCExternal .dtb fileYes (overlays)
ACPIx86, some ARM serversFirmware tables (DSDT/SSDT)Limited
Board files (legacy)Old ARM Linux (pre-3.x)Hardcoded C structs in kernelNo (requires recompile)

Device tree won because it separates hardware description from the kernel — one kernel image works on many boards by using different DTB files.

GPIO and Pinctrl in Device Tree

Pin multiplexing (pinctrl) and GPIO assignment are configured in device tree:

dts
&pinctrl {
i2c1_pins: i2c1-pins {
pins = "PA9", "PA10";
function = "i2c1";
bias-pull-up;
};
};
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
status = "okay";
};

pinctrl-names and pinctrl-0 tell the kernel which pin configuration to apply when the device is active. Multiple configurations (e.g., "default", "sleep") support power management — pins can be reconfigured when entering low-power modes.

⚠️Common Trap: status = disabled

The #1 device tree debugging issue is forgetting status = "okay". SoC base .dtsi files disable all peripherals by default. If your board .dts file does not explicitly set status = "okay", the kernel ignores the node entirely — no driver probes, no error message, just silence. Always check status first when a peripheral is not working.

Reading Bindings Documentation

Every compatible string should have a corresponding binding document in Documentation/devicetree/bindings/. These documents specify:

  • Required and optional properties
  • Valid values for each property
  • Example node

When writing a device tree node, always consult the binding doc — guessing property names or values leads to subtle bugs.

💡Interview Insight

When an interviewer asks "how do you add support for a new peripheral?", the answer involves both device tree AND driver code: (1) add a node in the .dts file with the correct compatible string and properties, (2) ensure the kernel has a driver with a matching compatible string in its of_match_table. If either side is missing or mismatched, the peripheral will not work.

Debugging Story: SPI Display Not Working After Board Bring-Up

A team was bringing up a custom board with an SPI-connected LCD display. The display driver was proven (it worked on the evaluation board). They added the SPI node to their device tree, loaded the driver module, but the display stayed black. No errors in dmesg.

Investigation:

  1. cat /sys/bus/spi/drivers/ili9341/ — driver was registered ✅
  2. ls /sys/bus/spi/devices/ — no SPI devices listed ❌
  3. Checked the device tree: the SPI controller node had status = "disabled" (inherited from the SoC .dtsi). They had enabled the display child node but forgot to enable the parent SPI controller.

Fix: Add status = "okay" to the &spi1 controller node. After recompiling the DTB, the SPI bus probed, the display device appeared, the driver matched, probe() was called, and the display came to life.

The lesson: Always check the entire node hierarchy — enabling a child device does nothing if the parent bus controller is still disabled.

What Interviewers Want to Hear

  • You understand why device tree exists (decouples hardware description from kernel code)
  • You can read a DT node and explain what each property does (compatible, reg, interrupts, status)
  • You know how compatible strings bind devices to drivers
  • You can explain the DTS → DTC → DTB → kernel pipeline
  • You know about overlays and when to use them
  • You can debug common DT issues (disabled status, wrong compatible, bad reg address)

Interview Focus

Classic Interview Questions

Q1: "What is device tree and why does Linux use it?"

Model Answer Starter: "Device tree is a data structure that describes hardware to the kernel — peripheral addresses, interrupt connections, clock sources, pin assignments. Before device tree, every ARM board needed a custom board file compiled into the kernel, making one kernel build support only one board. Device tree externalizes this: the SoC vendor provides a base .dtsi, the board designer creates a .dts that selectively enables and configures peripherals, DTC compiles it to a binary .dtb, and U-Boot passes the DTB to the kernel at boot. One kernel binary can support dozens of boards by using different DTB files."

Q2: "How does a device tree node bind to a kernel driver?"

Model Answer Starter: "Through the compatible property. When the kernel parses the DTB, it creates a platform_device for each node with status='okay'. The platform bus compares each device's compatible string against the of_match_table in every registered platform driver. When there is a match, the bus calls the driver's probe() function, passing a reference to the device's DT node so the driver can read reg, interrupts, clocks, and other properties. The compatible string is a list — if the first string has no matching driver, the kernel tries the next one as a fallback."

Q3: "What are device tree overlays and when would you use them?"

Model Answer Starter: "Overlays are fragments that modify the base DTB at runtime. They can add new nodes, change properties, or enable/disable devices without recompiling the entire DTB. I use them for hardware add-on boards (like BeagleBone capes) where the base board DT is fixed but the add-on enables specific peripherals, for factory configuration where different product variants share one base image but use different overlays, and during development to quickly test different peripheral configurations."

Q4: "A peripheral is not working on your embedded Linux board. How do you debug it from a device tree perspective?"

Model Answer Starter: "I check four things in order: (1) Is the node's status set to 'okay'? The SoC dtsi often defaults to 'disabled'. (2) Is the parent bus controller also enabled? Enabling a child node does nothing if the parent bus is disabled. (3) Does the compatible string exactly match a registered driver's of_match_table? I check /sys/bus/platform/drivers/ to verify the driver is loaded. (4) Are the reg, interrupts, and clocks properties correct for this board's wiring? I compare against the schematic and the binding documentation."

Q5: "Explain the difference between DTS, DTSI, DTB, and DTC."

Model Answer Starter: "DTS is the device tree source file for a specific board — human-readable text. DTSI is an include file, typically for the SoC — it defines all peripherals with status disabled. The board DTS includes the SoC DTSI and selectively enables devices. DTC is the Device Tree Compiler that converts DTS to DTB, the binary blob. U-Boot loads the DTB into RAM and passes its address to the kernel. The kernel parses the DTB during boot to discover what hardware is present."

Trap Alerts

  • Don't say: "Device tree is like ACPI" — they serve similar purposes but DT is simpler, externally compiled, and dominant on embedded ARM
  • Don't forget: The status property — forgetting status = "okay" is the #1 cause of "everything compiles but nothing works"
  • Don't ignore: Parent node status — enabling a child device under a disabled bus controller does nothing

Follow-up Questions

  • "How do you handle pin multiplexing in device tree?"
  • "What is #address-cells and #size-cells and why are they needed?"
  • "How would you add a custom property to a device tree node that your driver reads?"
  • "What tools can you use to inspect or decompile a DTB?"
  • "How does U-Boot modify the device tree before passing it to the kernel?"

Practice

In a device tree node, what property determines which kernel driver handles the device?

A SoC's base .dtsi file sets all peripherals to status='disabled'. Your board .dts includes this .dtsi but your UART is not working. What is the most likely cause?

What does DTC do?

A device tree node has: compatible = 'myco,sensor-v2', 'generic,i2c-sensor'. What does the second string mean?

Real-World Tie-In

Multi-Variant Industrial Controller — A product line uses the same SoC but three board variants (basic, with CAN, with Ethernet+CAN). One base DTB ships in firmware; variant-specific overlays are selected at boot based on a GPIO-read board ID. This eliminates maintaining three separate kernel builds and reduces factory configuration to a single firmware image.

Custom Sensor Board Bring-Up — A team porting Linux to a custom board with an IMX6 SoC started with the evaluation board's DTS file. They changed the reg addresses for their custom I2C sensors, updated the pinctrl entries to match their schematic, and used fdtdump to decompile and verify the final DTB. The entire hardware adaptation was done in device tree without changing a single line of kernel code.