4、设备树匹配机制:of_match_table原理、compatible字符串匹配规则、设备树与驱动绑定的流程
设备树(Device Tree)是嵌入式 Linux 系统中描述硬件拓扑与资源的关键数据结构。Platform 驱动与设备树节点的匹配,本质上是驱动中 of_match_table 所声明的 compatible 字符串与设备树节点中 compatible 属性的逐项比对过程。本章深入解析这一匹配机制的核心原理、字符串匹配规则以及完整的绑定流程。
4.1 of_match_table 原理
of_match_table 是 struct platform_driver 中的一个关键成员,其类型为 struct of_device_id 数组。该数组以空结构体结尾,用于声明该驱动能够支持的设备类型。
核心数据结构:
struct of_device_id {
char name[32]; // 设备名称(通常不用于匹配)
char type[32]; // 设备类型(通常不用于匹配)
char compatible[128]; // 兼容性字符串,主匹配依据
const void *data; // 私有数据指针
};
驱动注册时,内核会遍历该数组,将每个元素的 compatible 字段与设备树节点的 compatible 属性进行比对。匹配成功后,内核会调用驱动的 probe 函数,并将匹配到的 of_device_id 指针传递给驱动。
最佳实践:驱动中应始终使用
of_match_table 来声明支持的设备,而非依赖 id_table(传统非设备树匹配方式)。这确保了驱动与设备树描述的解耦与可移植性。
4.1.1 of_match_table 与 id_table 对比
| 对比项 | of_match_table | id_table |
|---|---|---|
| 匹配依据 | 设备树 compatible 字符串 | Platform 设备名称 |
| 适用场景 | 设备树使能的系统(ARM64、RISC-V、ARM32 with DT) | 传统非设备树系统或 ACPI 系统 |
| 匹配优先级 | 高(内核优先尝试 of_match) | 低(仅当 of_match 失败时使用) |
| 数据结构 | struct of_device_id 数组 | struct platform_device_id 数组 |
| 私有数据传递 | 通过 .data 字段(void*) | 通过 .driver_data(kernel_ulong_t) |
4.2 compatible 字符串匹配规则
compatible 字符串是设备树节点与驱动之间最核心的匹配纽带。其格式与匹配规则遵循严格约定。
4.2.1 字符串格式规范
- 厂商前缀 + 设备型号: 格式为
"厂商,设备型号",例如"ti,am335x-uart"、"fsl,imx6q-uart"。 - 通用兼容性: 可包含多个字符串,按从具体到通用的顺序排列。例如:
"ti,am335x-uart"、"ns16550a"。 - 通配符: 内核匹配时 不支持 通配符(如
*、?),必须精确字符串匹配。
4.2.2 匹配优先级与顺序
内核在匹配时,会按照以下顺序进行:
- 精确匹配: 驱动
of_match_table中的compatible字符串与设备树节点compatible属性中的第一个字符串进行比对。 - 遍历匹配: 如果第一个字符串不匹配,则依次尝试设备树节点
compatible属性中的后续字符串。 - 驱动优先: 一旦找到匹配项,立即停止搜索,使用该驱动。
常见陷阱: 设备树节点中
compatible 字符串的顺序至关重要。最具体的兼容字符串应放在第一位,通用兼容字符串放在后面。驱动编写时,应优先匹配最具体的字符串,避免使用过于通用的字符串导致误匹配。
4.2.3 匹配规则示例
假设设备树节点定义如下:
uart0: serial@44e09000 {
compatible = "ti,am335x-uart", "ns16550a";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
驱动 of_match_table 声明:
static const struct of_device_id my_uart_of_match[] = {
{ .compatible = "ti,am335x-uart", .data = &am335x_uart_data },
{ .compatible = "ns16550a", .data = &ns16550a_data },
{ /* sentinel */ }
};
匹配过程:
- 内核首先尝试匹配
"ti,am335x-uart"→ 成功,使用am335x_uart_data。 - 如果驱动中未声明
"ti,am335x-uart",则尝试匹配"ns16550a"→ 成功,使用ns16550a_data。
4.3 设备树与驱动绑定的流程
从系统启动到驱动 probe 函数被调用,整个绑定流程涉及多个内核子系统。下图展示了完整的绑定流程。
4.3.1 绑定流程详细步骤
| 步骤 | 执行者 | 动作描述 |
|---|---|---|
| 1 | Bootloader / 内核早期启动 | 加载 DTB 到内存,内核解析为 device_node 树结构。 |
| 2 | 内核 of_platform_populate() |
遍历 device_node 树,为每个包含 compatible 属性的节点创建 platform_device。 |
| 3 | 驱动模块 / 内核初始化 | 调用 platform_driver_register() 注册驱动,驱动被添加到 Platform 总线驱动链表。 |
| 4 | Platform 总线匹配函数 | 内核调用 platform_match(),优先尝试 of_match_device() 进行设备树匹配。 |
| 5 | of_match_device() |
遍历驱动 of_match_table,与设备节点 compatible 属性逐项比对。 |
| 6 | 内核总线核心 | 匹配成功后,调用驱动的 probe 函数,传递匹配到的 of_device_id 指针。 |
| 7 | 驱动 probe 函数 |
使用 of_device_id->data 获取私有数据,调用 of_* API 读取设备树资源(reg、interrupts、GPIO 等),初始化硬件。 |
4.3.2 关键内核函数调用链
platform_driver_register()
└─> __platform_driver_register()
└─> driver_register()
└─> bus_add_driver()
└─> driver_attach()
└─> bus_for_each_dev()
└─> __driver_attach()
└─> driver_match_device()
└─> platform_match()
├─> of_match_device() // 设备树匹配优先
└─> platform_match_id() // 传统 id_table 匹配
└─> driver_probe_device()
└─> really_probe()
└─> dev->bus->probe() // 调用 platform_driver.probe()
调试技巧: 如果驱动未成功 probe,可以通过以下方式排查:
1. 检查
2. 检查
3. 使用
4. 开启内核调试选项
1. 检查
/sys/bus/platform/devices/ 下是否存在对应设备节点。2. 检查
/sys/bus/platform/drivers/<driver_name>/ 下是否有设备链接。3. 使用
of_device_is_compatible() 在内核代码中手动验证 compatible 字符串是否匹配。4. 开启内核调试选项
CONFIG_OF_UNITTEST 和 CONFIG_OF_OVERLAY 进行单元测试。
4.4 驱动中 of_match_table 的典型实现
以下是一个完整的 Platform 驱动框架,展示了 of_match_table 的正确使用方式:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
/* 私有数据结构体 */
struct my_device_data {
int version;
int flags;
};
/* 不同硬件版本的私有数据 */
static const struct my_device_data v1_data = {
.version = 1,
.flags = 0x01,
};
static const struct my_device_data v2_data = {
.version = 2,
.flags = 0x03,
};
/* of_match_table 声明 */
static const struct of_device_id my_driver_of_match[] = {
{
.compatible = "vendor,device-v1",
.data = &v1_data,
},
{
.compatible = "vendor,device-v2",
.data = &v2_data,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
/* probe 函数 */
static int my_driver_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
const struct my_device_data *data;
/* 获取匹配到的 of_device_id */
match = of_match_device(my_driver_of_match, &pdev->dev);
if (!match) {
dev_err(&pdev->dev, "Failed to find matching device id\n");
return -ENODEV;
}
/* 获取私有数据 */
data = (const struct my_device_data *)match->data;
dev_info(&pdev->dev, "Probing device version %d\n", data->version);
/* 读取设备树资源 */
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENXIO;
}
/* 硬件初始化逻辑 */
// ...
return 0;
}
/* remove 函数 */
static int my_driver_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "Removing device\n");
// 清理逻辑
return 0;
}
/* platform_driver 结构体 */
static struct platform_driver my_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my_device_driver",
.of_match_table = my_driver_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example Platform Driver with of_match_table");
关键点总结:
of_match_table是驱动与设备树匹配的核心,必须正确声明并以空结构体结尾。compatible字符串匹配遵循精确匹配原则,不支持通配符,顺序决定匹配优先级。- 绑定流程从设备树解析开始,经过 Platform 设备创建、驱动注册、匹配、最终调用 probe 函数。
- 驱动中应使用
of_match_device()获取匹配到的of_device_id,进而获取私有数据。 - 使用
MODULE_DEVICE_TABLE(of, ...)导出设备表,便于模块加载时自动匹配。