Comprehensive explanation of Device Tress

Comprehensive explanation of Device Tress

Let’s break down the device tree concept step by step, using concrete Raspberry Pi examples and real-world scenarios. I’ll explain it as if we’re working with a Raspberry Pi 4 (but the principles apply broadly).

Part 1: Device Tree Basics – The "Map" of Hardware

What Problem Does the Device Tree Solve?

Imagine you’re the Linux kernel. When you boot up, you need to know:

  • Where is the RAM?
  • How many CPU cores are there?
  • What peripherals exist (GPIO, I2C, USB controllers)?
  • What drivers to load for each device?

Before device trees, this information was hardcoded into the kernel (in C code). This meant a different kernel was needed for every hardware variation (e.g., Raspberry Pi 3 vs. Pi 4). Device trees solve this by externalizing hardware descriptions into separate files (.dts or .dtb).

Part 2: Anatomy of a Device Tree – A Raspberry Pi Example

Let’s look at a simplified device tree for a Raspberry Pi 4.

(Note: Real Pi DTS files are complex; this is a simplified version for clarity.)

Device Tree Structure

/dts-v1/;  // Device Tree Version 1

/ {  // Root node (represents the entire system)
    compatible = "raspberrypi,4-model-b";  // Matches the board
    model = "Raspberry Pi 4 Model B";

    // Memory description
    memory@0 {  // Node name: "memory" at address 0
        device_type = "memory";
        reg = <0 0 0x3b400000>;  // RAM: Starts at 0, size 0x3b400000 (1GB)
    };

    // CPU description
    cpus {
        #address-cells = <1>;  // CPU addresses are 1 cell (number)
        #size-cells = <0>;     // No size field for CPUs
        cpu@0 {  // First CPU core
            device_type = "cpu";
            compatible = "arm,cortex-a72";  // Tells kernel to use Cortex-A72 driver
            reg = <0>;  // CPU ID = 0
        };
        cpu@1 {  // Second CPU core
            device_type = "cpu";
            compatible = "arm,cortex-a72";
            reg = <1>;
        };
        // ... (more CPU cores)
    };

    // System-on-Chip (SoC) peripherals
    soc {
        #address-cells = <2>;  // Addresses are 2 cells (e.g., 0x7e200000)
        #size-cells = <1>;     // Sizes are 1 cell
        ranges = <0x7e000000 0x0 0xfe000000 0x1800000>;  // Maps peripheral bus addresses

        // GPIO controller
        gpio: gpio@7e200000 {  // Node label: 'gpio'
            compatible = "brcm,bcm2711-gpio";  // Matches the GPIO driver
            reg = <0x7e200000 0xb4>;  // Base address & size
            gpio-controller;  // Marks this node as a GPIO controller
            #gpio-cells = <2>;  // Specifies how many cells (values) describe a GPIO pin
        };

        // UART (serial port)
        uart0: serial@7e201000 {
            compatible = "brcm,bcm2835-pl011";  // Driver for PL011 UART
            reg = <0x7e201000 0x1000>;  // Address range
            interrupts = <2 25>;  // Interrupt ID 25, triggered by interrupt controller #2
            status = "okay";
        };
    };
};
        

Key Concepts Explained

1. Nodes and Hierarchy

  • Nodes represent hardware components (like /cpus, /soc/gpio).
  • The tree mirrors the hardware’s physical/logical layout. For example:

/ (root)
├── cpus
│   ├── cpu@0
│   └── cpu@1
└── soc
    ├── gpio@7e200000
    └── serial@7e201000
        

2. Properties

  • compatible: The most important property! It tells the kernel which driver to use.
  • Example: compatible = "brcm,bcm2835-pl011" tells the kernel to load the PL011 UART driver.
  • reg: Physical address and size of a device’s registers.
  • Example: reg = <0x7e201000 0x1000> means the UART registers start at 0x7e201000 and span 0x1000 bytes.
  • interrupts: Specifies which interrupt line(s) a device uses.
  • Example: <2 25> means interrupt number 25 from interrupt controller #2.

3. Labels and References

  • Labels (e.g., gpio: gpio@7e200000) allow other nodes to reference a device.
  • Example: A sensor node might reference &gpio to specify which GPIO controller it uses.

4. #address-cells and #size-cells

  • Define how addresses and sizes are represented in child nodes.
  • Example: In the soc node:
  • #address-cells = <2>: Addresses are two 32-bit numbers (e.g., 0x7e200000).
  • #size-cells = <1>: Sizes are one 32-bit number.

Part 3: How the Kernel Uses the Device Tree

Step-by-Step Boot Process (Raspberry Pi)

  • Bootloader Stage:

The Raspberry Pi firmware (start.elf) loads the kernel (kernel8.img) and device tree blob (bcm2711-rpi-4-b.dtb) from the SD card.

The DTB is a compiled binary version of the .dts file.

  • Kernel Initialization:

The kernel parses the DTB to discover hardware:

Memory: Reserves RAM regions.

CPU Cores: Initializes all Cortex-A72 cores.

Peripherals: Matches drivers using compatible strings.

Example: The node with compatible = "brcm,bcm2835-pl011" triggers the PL011 UART driver.

  • Runtime Access:

The parsed device tree is exposed in /proc/device-tree. For example:

/proc/device-tree/
├── compatible
├── model
├── cpus
│   ├── cpu@0
│   └── cpu@1
└── soc
    ├── gpio@7e200000
    └── serial@7e201000
        

  • You can read raw properties using tools like hexdump or dtc (Device Tree Compiler).

Part 4: Device Tree Overlays – Dynamic Configuration

What Are Overlays?

Overlays (.dtbo files) are "patches" to the base device tree. They’re used to:

  • Enable/disable hardware at boot time.
  • Configure add-on boards (like Raspberry Pi HATs).

Example: Adding an I2C Temperature Sensor

  • Base Device Tree: Already defines the I2C controller:

i2c1: i2c@7e804000 {
    compatible = "brcm,bcm2835-i2c";
    reg = <0x7e804000 0x1000>;
    status = "disabled";  // Disabled by default
};
        

  • Overlay File (i2c-sensor-overlay.dts):

/dts-v1/;
/plugin/;  // Declare this as an overlay

// Fragment 0: Enable I2C1
&i2c1 {
    status = "okay";  // Enable the I2C controller
    clock-frequency = <100000>;  // Set I2C speed to 100 kHz

    // Fragment 1: Add the temperature sensor
    temp_sensor: lm75@48 {
        compatible = "nxp,lm75";
        reg = <0x48>;  // I2C address 0x48
    };
};
        

  • Compile and Apply the Overlay:

Compile the overlay:

dtc -@ -I dts -O dtb -o i2c-sensor.dtbo i2c-sensor-overlay.dts
        

Copy i2c-sensor.dtbo to /boot/overlays/.

Edit /boot/config.txt to add:

dtoverlay=i2c-sensor
        

Result:

  • On reboot, the kernel:
  • Enables I2C1.
  • Adds the lm75 temperature sensor node.
  • Loads the lm75 driver (if available).
  • The sensor appears in /sys/bus/i2c/devices/.

Part 5: Debugging Device Trees

Common Tools

  • dtc (Device Tree Compiler):

Decompile a .dtb to .dts:

dtc -I dtb -O dts -o extracted.dts /boot/bcm2711-rpi-4-b.dtb
        

fdtdump:

Quickly view a device tree blob:

fdtdump /boot/bcm2711-rpi-4-b.dtb
        

/proc/device-tree:

Explore the parsed device tree at runtime:

# List all nodes/properties:
find /proc/device-tree -type f

# Read the 'compatible' property:
cat /proc/device-tree/compatible
# Output: raspberrypi,4-model-bbrcm,bcm2711
        

Part 6: Why This Matters for Raspberry Pi Users

  • One Kernel, Multiple Boards: The same Raspbian OS image works on Pi 3, Pi 4, and Pi Zero because the kernel uses different DTBs (bcm2711-rpi-4-b.dtb vs. bcm2837-rpi-3-b.dtb).
  • HATs (Hardware Attached on Top): Overlays let you plug in a camera, sensor, or display without kernel changes.
  • Driver Matching: The compatible property ensures the correct driver is loaded (e.g., brcm,bcm2835-pwm for PWM controllers).

Summary

  • Device Tree = A blueprint of hardware, written in a structured format.
  • Nodes = Hardware components (CPU, GPIO, I2C).
  • Properties = Key-value pairs (addresses, interrupts, driver matches).
  • Overlays = Dynamic modifications for add-on hardware.

Next time you boot a Raspberry Pi, remember: the kernel isn’t hardcoded for your board. It’s the device tree telling it exactly where everything is! 🎯

Amir Naghizadeh

Embedded SWE @ Piod | V2X | Automative IoT

1mo

Very informative

Like
Reply

To view or add a comment, sign in

More articles by David Zhu

Insights from the community

Others also viewed

Explore topics