1. 设备树基础概念
1.1 什么是设备树(Device Tree)
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点(Node)和属性(Property)来向操作系统内核传递板级硬件信息。设备树源文件(.dts)经过设备树编译器(DTC)编译为二进制设备树块(.dtb),在系统启动时由引导加载程序传递给内核。
核心定义:设备树是一个硬件描述文件,它告诉内核“这块板子上有哪些设备、它们挂接在哪个总线上、使用什么资源(中断、地址、时钟等)”,从而让内核无需硬编码板级信息。
一个典型的设备树节点结构如下:
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
cpus {
cpu@0 {
compatible = "arm,cortex-a8";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
&i2c0 {
status = "okay";
clock-frequency = <100000>;
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
pagesize = <32>;
};
};
};
关键语法:每个节点以
节点名@地址 标识,compatible 属性是驱动匹配的核心字段,reg 描述地址/中断等资源,status 控制设备启用/禁用。
1.2 为什么需要设备树
在设备树出现之前,Linux 内核使用大量的板级文件(board-*.c)来硬编码硬件信息。这种做法在 ARM 架构中导致了严重的代码膨胀和维护灾难。设备树的引入解决了以下核心问题:
| 对比维度 | 传统板级文件方式 | 设备树方式 |
|---|---|---|
| 硬件描述位置 | 硬编码在 arch/arm/mach-*/board-*.c |
独立 .dts 文件,与内核源码分离 |
| 内核维护负担 | 每块新板子都要修改内核源码,重新编译 | 只需编写新的 .dts 文件,内核无需改动 |
| 多平台支持 | 一个内核镜像只能支持少数板子 | 同一内核镜像通过更换 .dtb 支持不同硬件 |
| 驱动与硬件解耦 | 驱动直接引用板级宏定义 | 驱动通过 compatible 匹配,资源从设备树获取 |
| 社区接受度 | ARM 社区强烈反对,维护成本高 | Linux 主线强制要求,所有新平台必须使用 |
重要里程碑:自 Linux 3.x 起,ARM 架构强制要求使用设备树。PowerPC、RISC-V、x86(ACPI 之外)等架构也广泛采用。设备树已成为嵌入式 Linux 硬件描述的事实标准。
1.3 设备树与平台设备(Platform Device)的关系
平台设备(Platform Device)是 Linux 内核中用于表示“非可枚举总线设备”的抽象层。设备树与平台设备的关系可以概括为:设备树是硬件描述源,平台设备是内核中的运行时对象。设备树节点在系统启动时被内核解析,并自动创建对应的平台设备。
整个流程分为三个关键阶段:
- 设备树解析阶段:内核启动早期,
unflatten_device_tree()将.dtb展开为device_node树结构。 - 平台设备创建阶段:
of_platform_populate()遍历设备树节点,为每个compatible属性匹配的节点创建platform_device。 - 驱动匹配阶段:平台总线(platform bus)根据
platform_device的name或compatible与platform_driver进行匹配,调用probe()函数。
核心结论:设备树节点 → 平台设备(platform_device)→ 平台驱动(platform_driver)→ probe() 执行。设备树是“图纸”,平台设备是“零件”,平台驱动是“装配工”。
下表总结了设备树节点与平台设备之间的映射关系:
| 设备树元素 | 平台设备对应 | 说明 |
|---|---|---|
compatible = "vendor,device" |
platform_device.name / driver.of_match_table |
驱动匹配的核心标识,优先级最高 |
reg = <addr size> |
platform_device.resource[] (IORESOURCE_MEM) |
内存地址范围,驱动通过 platform_get_resource() 获取 |
interrupts = <num> |
platform_device.resource[] (IORESOURCE_IRQ) |
中断号,驱动通过 platform_get_irq() 获取 |
status = "okay" / "disabled" |
设备是否注册到平台总线 | disabled 节点不会创建平台设备 |
自定义属性(如 clock-frequency) |
通过 of_property_read_*() 在 probe 中读取 |
驱动使用设备树 API 获取自定义配置 |
重要区分:并非所有设备树节点都创建平台设备。挂接在 SPI 总线下的子节点创建
spi_device,挂接在 I2C 总线下的子节点创建 i2c_client。平台设备主要用于“内存映射型”设备(如 GPIO 控制器、时钟控制器、DMA 控制器等)。本课程后续章节将详细讲解 SPI 和 I2C 设备节点的编写方法。