上两篇博客描述了一些设备树的基础知识和一些简单常见的设备树撰写,然而撰写设备树的很大一部分目的是用于代替平台总线Platform的 device 部分。
所以,这篇文章我们讲述这些设备树节点最终是怎么进入 Linux 驱动模型的,设备树是怎样代替 device.c 的。

1.从设备树源码的Platform的总体流程
整体流程可以描述为如下内容:
bash
.dts / .dtsi
↓ 通过 dtc 编译
.dtb
↓ 由 bootloader 传给内核
内核早期解析 dtb
↓
展开成 device_node 树
↓
of_platform_populate()
↓
生成 platform_device
↓
挂到 platform bus
↓
platform_driver 根据 of_match_table 匹配 compatible
↓
调用 probe()
↓
驱动在 probe 中读取 reg / irq / gpio / clock 等资源
1.1 第一阶段
首先,我们通过DTC编译把 dts 设备树源文件编译为 dtb文件,如:
bash
dtc -I dts -O dtb -o xxx.dtb xxx.dts
然后通过bootloader 把 DTB 传给内核:
.dtb 生成之后,接下来并不是驱动立刻去解析它,而是由 bootloader(例如 U-Boot)在启动内核时把这份 DTB 一起传进去。Linux 官方文档把这个过程称为 Linux 的 device tree usage model:bootloader 负责提供硬件描述,内核在启动初期接收并展开它。
1.2 第二阶段
内核收到 DTB 后,首先做的不是马上创建设备,而是先把二进制设备树展开成一棵内核内部可访问的树,也就是 device_node 树。
这棵树里的每个节点,都会对应一个 struct device_node。
也就是说,内核眼里的设备树,不再是文本,而是:一组互相连接的 device_node 节点对象。
所以你后面看到的很多 of_* 函数,本质上都是在对这棵 device_node 树做操作,比如:
- 找节点
- 读属性
- 解析地址
- 解析中断
- 找父子关系
这也是为什么设备树相关接口都叫 of_*------它们是 OF(Open Firmware / Device Tree)框架下的一套 API。
1.3 第三阶段:platform_device的诞生
我们刚刚讲过设备树的很大一部分目的是替换平台总线中的device,所以设备树节点device_node是要转换成platform_device的。
而 platform 设备的创建,关键入口之一就是:
cpp
of_platform_populate()
函数的作用可以理解为:
把设备树中的合适节点,转换成 platform_device,并挂到 platform 总线。
**注意:**然而内核并不会把设备树里的所有节点都转换成 platform_device!
of_platform_populate有着严格的准入规则:
- **根节点下的子节点:**只要含有 compatible 属性,通常会被转换为 platform_device。
- **特定的"总线"子节点:**带有 simple-bus、simple-mfd、isa、arm,amba-bus 等属性的节点,其子节点也会被递归地转化为 platform_device。(例如 RK3588 的 DTS 中,大量外设节点都在一个 compatible = "simple-bus" 的根节点之下)。
注意: 挂载在 I2C、SPI 等具体物理总线控制器下的子节点(如摄像头 Sensor),在这里不会被转换为 platform_device。它们会在 I2C/SPI 主机控制器驱动初始化时,由总线特定的代码(如 of_i2c_register_devices)解析,并转化为 i2c_client 或 spi_device。
1.4 第四阶段
当 platform_device 被注册到 platform bus 后,Linux 驱动模型就开始进行设备-驱动匹配。
在设备树场景下,匹配最关键的是:
1. 设备树节点里的 compatible
例如:
cpp
compatible = "myvendor,myled";
2. 驱动里的 of_match_table
例如:
cpp
static const struct of_device_id myled_of_match[] = {
{ .compatible = "myvendor,myled" },
{ }
};
MODULE_DEVICE_TABLE(of, myled_of_match);
然后平台驱动会这样注册:
cpp
static struct platform_driver myled_driver = {
.probe = myled_probe,
.driver = {
.name = "myled",
.of_match_table = myled_of_match,
},
};
这时 platform bus 会拿:
- 节点的 compatible
- 驱动的 of_match_table
进行匹配。匹配成功后,就会进入驱动的 probe()。
2.核心 of_ API 解析
当流程进入你编写的 probe 函数时,你需要从硬件设备中提取配置。此时,你需要大量使用 of_ 开头的 API(定义在 include/linux/of.h 等头文件中)。
所有获取属性的操作,都围绕着 platform_device->dev.of_node 展开。
1. 节点查找函数 (通常用于非标准获取,如查找引用节点)
- of_find_node_by_path(const char *path): 通过绝对路径寻找节点(如 "/soc/gpio@fe740000")。
- of_find_compatible_node(struct device_node *from, const char *type, const char *compatible): 通过 compatible 属性全局查找目标节点。
- of_get_parent(const struct device_node *node): 获取当前节点的父节点。