4. 内核中设备树的解析机制
设备树 (Device Tree) 在内核中并非直接以文本形式存在,而是被编译为二进制 .dtb 文件,并在启动时由 Bootloader 传递给内核。内核需要一套标准化的接口来解析这颗“树”,提取硬件资源(如地址、中断、GPIO、时钟等)并传递给驱动程序。这套接口就是 OF (Open Firmware) API。
对于 Platform 驱动而言,of_match_table 是连接设备树节点与驱动 Probe 函数的“桥梁”。本章将深入解析 OF API 的核心函数,并阐明 of_match_table 在驱动匹配流程中的关键作用。
4.1 OF API 核心函数族
OF API 定义在 include/linux/of.h 和 drivers/of/ 目录下,主要分为以下几类:
| 分类 | 核心函数 | 功能描述 |
|---|---|---|
| 节点查找 | of_find_node_by_path() |
通过设备树路径(如 /soc/i2c@1234)查找节点 |
of_find_node_by_name() |
通过节点名称查找 | |
of_find_compatible_node() |
通过 compatible 属性查找 | |
| 属性读取 | of_property_read_u32() |
读取 32 位整型属性 |
of_property_read_string() |
读取字符串属性 | |
of_property_read_u32_array() |
读取整型数组属性 | |
| 资源获取 | of_iomap() |
将 reg 属性中的地址范围映射到虚拟地址空间 |
of_irq_get() |
获取中断号 | |
of_get_named_gpio() |
获取 GPIO 编号 | |
| 匹配与数据 | of_match_node() |
根据 of_device_id 表匹配节点,返回匹配到的表项 |
of_device_get_match_data() |
获取匹配表项中的 .data 指针,用于传递平台特定数据 |
最佳实践: 在 Probe 函数中,优先使用
device_property_read_*() 系列通用 API(如 device_property_read_u32()),它们内部会调用 OF API 或 ACPI API,使驱动同时支持设备树和 ACPI 两种固件接口。
4.2 of_match_table 的作用与匹配流程
of_match_table 是 struct platform_driver 中的一个成员,类型为 const struct of_device_id *。它的核心作用有两个:
- 驱动与设备树节点的匹配依据:内核通过遍历该表,与设备树节点的
compatible属性进行字符串比较,决定是否调用该驱动的 Probe 函数。 - 携带平台特定数据:每个
of_device_id表项包含一个.data字段,可以指向任意类型的私有数据(如硬件版本号、寄存器偏移表等),Probe 函数通过of_device_get_match_data()获取。
下面是一个典型的 of_device_id 表定义:
static const struct of_device_id my_driver_of_match[] = {
{
.compatible = "vendor,my-device-v1",
.data = (void *)DEVICE_V1,
},
{
.compatible = "vendor,my-device-v2",
.data = (void *)DEVICE_V2,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
驱动注册时,内核的 Platform 总线匹配逻辑 会执行以下步骤:
匹配流程:
1. 内核遍历设备树中所有节点,为每个节点创建
struct platform_device。
2. 当新设备注册到 Platform 总线时,总线调用 platform_match()。
3. platform_match() 优先检查 of_match_table:调用 of_match_node(),将设备节点的 compatible 属性与驱动表中的每个表项比较。
4. 如果找到匹配项,返回匹配到的 of_device_id 指针,并触发 Probe 函数。
5. 如果未匹配,则回退到 id_table(传统非设备树匹配)或设备名称匹配。
4.3 设备树资源解析与 Probe 流程 SVG 图
下图展示了从设备树节点到驱动 Probe 函数的完整资源解析与匹配流程:
4.4 典型 Probe 函数中的 OF API 调用示例
以下代码展示了一个完整的 Probe 函数,演示如何使用 OF API 从设备树节点中提取资源:
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct of_device_id *match;
struct resource *res;
void __iomem *base;
int irq, gpio;
u32 val;
/* 1. 获取匹配表项中的私有数据 */
match = of_match_device(dev->driver->of_match_table, dev);
if (!match)
return -ENODEV;
priv->version = (enum hw_version)match->data;
/* 2. 使用 of_iomap 映射寄存器地址 */
base = of_iomap(dev->of_node, 0);
if (!base)
return -ENOMEM;
/* 3. 获取中断号 */
irq = of_irq_get(dev->of_node, 0);
if (irq < 0)
dev_warn(dev, "no irq specified\n");
/* 4. 读取自定义属性 */
if (of_property_read_u32(dev->of_node, "clock-frequency", &val) == 0)
priv->clock_freq = val;
/* 5. 获取 GPIO */
gpio = of_get_named_gpio(dev->of_node, "enable-gpios", 0);
if (gpio < 0)
dev_dbg(dev, "no enable gpio\n");
/* 注册设备等后续操作... */
return 0;
}
注意事项:
of_iomap()返回的虚拟地址需要在 Remove 函数中通过iounmap()释放。of_irq_get()返回的是 Linux 内核中断号,可直接用于request_irq()。- 如果设备树中未定义某个可选属性,OF API 会返回错误,驱动应做好容错处理。
4.5 of_match_table 与传统 id_table 的对比
| 对比维度 | of_match_table | id_table (传统) |
|---|---|---|
| 匹配依据 | 设备树节点的 compatible 属性 |
Platform 设备名称字符串 |
| 数据传递 | 通过 .data 字段传递任意类型指针 |
通过 driver_data 传递 unsigned long |
| 适用场景 | 设备树使能的内核(ARM64、RISC-V、ARM32 with DT) | 旧版非设备树平台或 ACPI 平台 |
| 宏声明 | MODULE_DEVICE_TABLE(of, ...) |
MODULE_DEVICE_TABLE(platform, ...) |
| 匹配优先级 | 最高,优先于 id_table 和设备名称匹配 | 次高,仅在 of_match_table 未匹配时使用 |
4.6 小结
OF API 是内核解析设备树的“手术刀”,而 of_match_table 则是驱动与设备树节点之间的“契约”。掌握以下要点即可在实际开发中熟练运用:
- 匹配机制:
compatible属性字符串必须与of_device_id表中的条目完全一致。 - 数据传递:利用
.data字段传递硬件版本或配置信息,避免为每个硬件版本编写独立驱动。 - 资源获取:Probe 函数中优先使用
of_iomap、of_irq_get、of_get_named_gpio等专用 API,而非手动解析reg或interrupts属性。 - 通用化:考虑使用
device_property_read_*()系列 API 编写与固件接口无关的驱动。