In-Depth Analysis of Linux Device Trees
1. Basic Concepts and Historical Background of Device Trees
1.1 What is a Device Tree?
A Device Tree is a data structure that describes hardware configuration, using a tree structure to represent system hardware resources in text form, including CPU, memory, buses, peripherals, etc. The core idea of the Device Tree is to separate hardware description from the operating system kernel, allowing the same kernel image to support different hardware platforms.
// Simple example of a Device Tree
/dts-v1/;
/ {
model = "My Board";
compatible = "my-company,my-board";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
};
memory@0 {
device_type = "memory";
reg = <0x00000000 0x40000000>;
};
uart@101f1000 {
compatible = "ns16550";
reg = <0x101f1000 0x1000>;
interrupts = <0 0 4>;
};
};
1.2 Development History of Device Trees
| Period | Technical Solution | Advantages and Disadvantages |
|---|---|---|
| Before 2000 | Hard-coded hardware information | Difficult kernel porting, code redundancy |
| 2000-2005 | Open Firmware | Used in PowerPC architecture, limited cross-platform capability |
| 2005-2011 | Platform Device (platform_device) | Partially decoupled, but still requires kernel recompilation |
| 2011 to present | Flattened Device Tree (FDT) | Truly separates hardware description from the kernel |
Key Turning Point: In 2011, Linus Torvalds strongly advocated for the adoption of Device Trees in ARM architecture discussions, marking the widespread adoption of Device Trees in embedded Linux.
2. Working Principles and Core Mechanisms of Device Trees
2.1 Device Tree Data Flow Architecture
Boot Phase
Compile Phase
DTS Source File
DTC Compiler
DTB Binary File
Bootloader Loads
Kernel Parses
Device Node Creation
Driver Matching
Device Initialization
2.2 Core Data Structures of Device Trees
// Core data structure of Device Tree in the kernel
struct device_node {
const char *name; // Node name
const char *type; // Device type
phandle phandle; // Node handle
const char *full_name; // Full path name of the node
struct property *properties; // Property list
struct device_node *parent; // Parent node
struct device_node *child; // Child node
struct device_node *sibling;// Sibling node
struct kobject kobj; // kobject for sysfs
unsigned long _flags; // Internal flags
void *data; // Device private data
};
struct property {
char *name; // Property name
int length; // Property value length
void *value; // Property value pointer
struct property *next; // Next property
unsigned long _flags; // Internal flags
unsigned int unique_id; // Unique ID
};
2.3 Device Tree and Driver Matching Mechanism
Device Trees achieve driver matching through the <span>compatible</span> property:
// Definition in Device Tree
mydevice@0x10000000 {
compatible = "vendor,device-model", "generic-driver";
reg = <0x10000000 0x1000>;
interrupts = <0 25 4>;
};
// Matching table in the driver
static const struct of_device_id my_driver_match[] = {
{ .compatible = "vendor,device-model" },
{ .compatible = "generic-driver" },
{}
};
MODULE_DEVICE_TABLE(of, my_driver_match);
// Platform driver structure
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-device",
.of_match_table = my_driver_match,
},
};
3. Detailed Explanation of Device Tree Syntax and Semantics
3.1 Device Tree Node Structure
// Basic syntax of a node
[label:] node-name[@unit-address] {
[properties]
[child-nodes]
};
// Actual example
i2c@1c22000 {
compatible = "allwinner,sun6i-a31-i2c";
reg = <0x01c22000 0x400>;
interrupts = <0 7 4>;
clocks = <&bus_gates 110>;
resets = <&apb1_rst 0>;
#address-cells = <1>;
#size-cells = <0>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
};
3.2 Detailed Explanation of Core Properties
| Property Name | Data Type | Function | Example |
|---|---|---|---|
| compatible | String List | Key for driver matching | <span>"vendor,model"</span> |
| reg | Integer Array | Register address range | <span><0x1000 0x100></span> |
| interrupts | Integer Array | Interrupt number, trigger type | <span><0 25 4></span> |
| #address-cells | Integer | Number of address cells for child nodes | <span><1></span> |
| #size-cells | Integer | Number of size cells for child nodes | <span><1></span> |
| status | String | Device status | <span>"okay"</span>, <span>"disabled"</span> |
Please open in WeChat client
3.3 Device Tree Inclusion and Overlay Mechanism
// base.dtsi - Basic definitions
/ {
model = "My Board";
compatible = "my-company,my-board";
memory@0 {
device_type = "memory";
reg = <0x00000000 0x40000000>;
};
};
// board.dts - Board-specific configuration
#include "base.dtsi"
/ {
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
};
leds {
compatible = "gpio-leds";
led0 {
label = "heartbeat";
gpios = <&pio 0 10 0>;
linux,default-trigger = "heartbeat";
};
};
};
4. Compilation and Loading Process of Device Trees
4.1 Detailed Compilation Process
Kernel DTB Binary DTC Compiler DTS Preprocessor DTS Source File Kernel DTB Binary DTC Compiler DTS Preprocessor DTS Source File Preprocessing Phase Compilation Phase Kernel Usage Phase Preprocessed DTS Generates Flattened Device Tree Binary Bootloader Passes DTB Parses Device Tree Creates Device Nodes Driver Matching Initialization
4.2 Kernel Parsing Code Flow
// Core function call chain for Device Tree parsing
start_kernel()
--> setup_arch(&command_line)
--> unflatten_device_tree()
--> __unflatten_device_tree()
--> unflatten_dt_nodes()
// Driver matching process
driver_register()
--> bus_add_driver()
--> driver_attach()
--> __driver_attach()
--> driver_match_device()
--> of_driver_match_device()
// Device creation process
of_platform_populate()
--> of_platform_bus_create()
--> of_platform_device_create_pdata()
5. Device Tree and Driver Interaction Mechanism
5.1 Accessing Device Tree APIs in Drivers
#include <linux/of.h>
#include <linux/of_device.h>
// 1. Get device node
struct device_node *np = pdev->dev.of_node;
// 2. Read string property
const char *name = NULL;
of_property_read_string(np, "device-name", &name);
// 3. Read integer property
u32 reg_value;
of_property_read_u32(np, "reg", ®_value);
// 4. Read array property
int array[10];
int count = of_property_read_variable_u32_array(np, "values", array, 0, 10);
// 5. Get GPIO descriptor
struct gpio_desc *gpio = gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW);
// 6. Get interrupt number
int irq = platform_get_irq(pdev, 0);
// 7. Get register resource
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
void __iomem *base = devm_ioremap_resource(&pdev->dev, res);
5.2 Example of Accessing Complex Devices
// Device Tree usage in a complex device driver
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct my_private_data *priv;
int ret;
// Allocate private data structure
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
// Get register base address
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
// Get clock
priv->clk = devm_clk_get(dev, "core");
if (IS_ERR(priv->clk)) {
dev_err(dev, "failed to get core clock\n");
return PTR_ERR(priv->clk);
}
// Get reset control
priv->reset = devm_reset_control_get_optional(dev, NULL);
if (IS_ERR(priv->reset)) {
dev_err(dev, "failed to get reset control\n");
return PTR_ERR(priv->reset);
}
// Get interrupt
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0) {
dev_err(dev, "failed to get IRQ\n");
return priv->irq;
}
// Parse custom properties
if (of_property_read_bool(np, "big-endian"))
priv->flags |= BIG_ENDIAN_FLAG;
u32 dma_mask;
if (!of_property_read_u32(np, "dma-mask", &dma_mask))
dma_set_mask(dev, dma_mask);
// Register interrupt handler
ret = devm_request_irq(dev, priv->irq, my_irq_handler,
IRQF_SHARED, dev_name(dev), priv);
if (ret) {
dev_err(dev, "failed to request IRQ %d: %d\n", priv->irq, ret);
return ret;
}
platform_set_drvdata(pdev, priv);
return 0;
}
6. Advanced Features of Device Trees
6.1 Device Tree Overlay Mechanism
Device Tree overlays allow dynamic modification of the device tree at runtime, commonly used for modular hardware:
// Base Device Tree
/ {
fragment@0 {
target = <&i2c1>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
touchscreen@38 {
compatible = "edt,edt-ft5406";
reg = <0x38>;
interrupt-parent = <&gpio>;
interrupts = <5 2>;
};
};
};
};
6.2 Conditional Compilation and Configuration
// Using preprocessing for conditional configuration
/dts-v1/;
#include "base.dtsi"
/ {
model = "My Board Rev ";
leds {
compatible = "gpio-leds";
#ifdef BOARD_REV_A
led0 {
gpios = <&pio 0 10 0>;
};
#else
led0 {
gpios = <&pio 1 5 0>;
};
#endif
};
};
7. Practical Example: Simple GPIO Device Implementation
7.1 Device Tree Definition
// my-gpio-device.dts
/dts-v1/;
/plugin/;
/ {
compatible = "my-company,my-board";
fragment@0 {
target = <&gpio>;
__overlay__ {
my_gpio_pins: my_gpio_pins {
pins = "PA10", "PA11";
function = "gpio_out";
};
};
};
fragment@1 {
target-path = "/";
__overlay__ {
my_gpio_device {
compatible = "my-company,my-gpio-device";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&my_gpio_pins>;
led-gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>, /* PA10 */
<&pio 0 11 GPIO_ACTIVE_HIGH>; /* PA11 */
button-gpios = <&pio 0 12 GPIO_ACTIVE_LOW>;
device-name = "my-custom-device";
clock-frequency = <100000>;
};
};
};
};
7.2 Corresponding Driver Program
// my-gpio-driver.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/of.h>
struct my_gpio_data {
struct gpio_desc *leds[2];
struct gpio_desc *button;
int irq;
struct device *dev;
};
static irqreturn_t button_irq_handler(int irq, void *dev_id)
{
struct my_gpio_data *priv = dev_id;
// Read button state and control LED
int state = gpiod_get_value(priv->button);
gpiod_set_value(priv->leds[0], state);
gpiod_set_value(priv->leds[1], !state);
dev_info(priv->dev, "Button state: %d\n", state);
return IRQ_HANDLED;
}
static int my_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct my_gpio_data *priv;
int ret, i;
u32 clock_freq;
const char *device_name;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
// Get LED GPIOs
for (i = 0; i < 2; i++) {
priv->leds[i] = devm_gpiod_get_index(dev, "led", i, GPIOD_OUT_LOW);
if (IS_ERR(priv->leds[i])) {
dev_err(dev, "failed to get LED GPIO %d\n", i);
return PTR_ERR(priv->leds[i]);
}
}
// Get button GPIO
priv->button = devm_gpiod_get(dev, "button", GPIOD_IN);
if (IS_ERR(priv->button)) {
dev_err(dev, "failed to get button GPIO\n");
return PTR_ERR(priv->button);
}
// Get device tree properties
ret = of_property_read_string(np, "device-name", &device_name);
if (ret)
device_name = "default";
ret = of_property_read_u32(np, "clock-frequency", &clock_freq);
if (ret)
clock_freq = 100000; // Default value
dev_info(dev, "Device %s probed, clock frequency: %d Hz\n",
device_name, clock_freq);
// Set up interrupt
priv->irq = gpiod_to_irq(priv->button);
ret = devm_request_irq(dev, priv->irq, button_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"my-gpio-button", priv);
if (ret) {
dev_err(dev, "failed to request IRQ: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, priv);
return 0;
}
static int my_gpio_remove(struct platform_device *pdev)
{
struct my_gpio_data *priv = platform_get_drvdata(pdev);
// Turn off LEDs
gpiod_set_value(priv->leds[0], 0);
gpiod_set_value(priv->leds[1], 0);
dev_info(&pdev->dev, "Device removed\n");
return 0;
}
static const struct of_device_id my_gpio_of_match[] = {
{ .compatible = "my-company,my-gpio-device" },
{}
};
MODULE_DEVICE_TABLE(of, my_gpio_of_match);
static struct platform_driver my_gpio_driver = {
.probe = my_gpio_probe,
.remove = my_gpio_remove,
.driver = {
.name = "my-gpio-device",
.of_match_table = my_gpio_of_match,
},
};
module_platform_driver(my_gpio_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple GPIO device driver with Device Tree support");
8. Debugging and Diagnostics of Device Trees
8.1 Common Tool Commands
| Tool Command | Function Description | Usage Example |
|---|---|---|
<span>dtc</span> |
Device Tree Compiler | <span>dtc -I dtb -O dts -o output.dts input.dtb</span> |
<span>fdtdump</span> |
Display DTB content | <span>fdtdump input.dtb</span> |
<span>dtc -O dtb</span> |
Compile DTS to DTB | <span>dtc -O dtb -o output.dtb input.dts</span> |
<span>of_find_node_by_name</span> |
Kernel debugging function | Find node in driver |
<span>cat /proc/device-tree</span> |
View loaded device tree | <span>find /proc/device-tree -type f</span> |
8.2 Debugging Tips and Best Practices
# 1. Check Device Tree syntax
dtc -I dts -O dtb -o /dev/null my-device.dts
# 2. Decompile existing DTB
dtc -I dtb -O dts -o extracted.dts /boot/device_tree.dtb
# 3. View the device tree parsed by the kernel
ls /proc/device-tree/
cat /proc/device-tree/compatible
# 4. Check if the device matched successfully
cat /sys/firmware/devicetree/base/device@1000/compatible
dmesg | grep -i "device tree"
# 5. Debug driver matching
echo -n "my-company,my-device" > /sys/bus/platform/drivers/my-driver/new_id
8.3 Kernel Debugging Configuration
// Add debugging output in the driver
#define DEBUG
static int my_driver_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
// Print device tree information
dev_dbg(&pdev->dev, "Device tree node: %s\n", np->full_name);
// Iterate properties
struct property *prop;
for_each_property_of_node(np, prop) {
dev_dbg(&pdev->dev, "Property: %s\n", prop->name);
}
return 0;
}
9. Design Patterns and Best Practices for Device Trees
9.1 Summary of Design Principles
| Principle | Description | Example |
|---|---|---|
| Integrity of Hardware Description | Complete description of hardware connections | Correct addresses, interrupts, clocks |
| Compatibility First | Use standard compatible strings | <span>"vendor,model"</span> format |
| Hierarchical Design | Reasonable use of node hierarchy | SoC level → Board level → Peripheral level |
| Reusability | Use includes and references | <span>.dtsi</span> base file |
| Maintainability | Clear comments and structure | Group related properties |
9.2 Common Pitfalls and Solutions
// Error example - Missing necessary properties
mydevice@10000000 {
compatible = "my-company,my-device";
// Missing reg property!
};
// Correct example
mydevice@10000000 {
compatible = "my-company,my-device";
reg = <0x10000000 0x1000>;
interrupts = <0 25 4>;
clocks = <&myclock>;
clock-names = "core";
};
10. Conclusion
The Linux Device Tree is one of the core technologies of modern embedded Linux systems, greatly enhancing system portability and maintainability by separating hardware descriptions from kernel code.
Key Points Review:
- • Device Trees achieve a complete separation of hardware description from the kernel
- • The
<span>compatible</span>property is the core mechanism for driver matching - • Proper use of Device Trees can significantly improve code reusability
- • Device Tree debugging requires a combination of compilation tools and kernel debugging features
By mastering the principles and practical techniques of Device Trees, developers can more efficiently engage in embedded Linux system development, building more flexible and maintainable embedded solutions.