4. platform驱动匹配机制:of_match_table、id_table、name三种匹配方式详解
驱动匹配,说白了就是内核怎么找到你的设备。我刚开始接触platform驱动时,总觉得这步很简单——不就是名字对上就行吗?后来踩了几个坑才发现,这里面的门道还真不少。
今天咱们就聊聊platform驱动的三种匹配方式。我个人习惯把它们分成两类:设备树时代的匹配和传统板级匹配。嗯,咱们一个一个来看。
4.1 匹配机制的整体框架
先看一张图,帮你快速建立整体认知:
从图上你能看到,内核会按优先级依次尝试三种匹配方式。一旦匹配成功,就不再往下走了。这个顺序很重要,我待会儿会细说。
4.2 of_match_table:设备树时代的首选
现在做嵌入式Linux开发,设备树几乎是标配。所以of_match_table成了最常用的匹配方式。我个人也推荐优先使用它。
它的原理很简单:驱动声明自己支持哪些设备,内核拿设备树节点的compatible属性来比对。
看个例子:
// 驱动侧:定义匹配表
static const struct of_device_id my_driver_of_match[] = {
{ .compatible = "vendor,my-device-v1", },
{ .compatible = "vendor,my-device-v2", },
{ /* sentinel */ } // 空结尾,必须的
};
// 驱动结构体中引用
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_device",
.of_match_table = my_driver_of_match,
},
};
设备树那边长这样:
// 设备树节点
my_device: my-device@1c00000 {
compatible = "vendor,my-device-v1";
reg = <0x1c00000 0x1000>;
interrupts = <0 42 4>;
};
关键点:匹配时,内核会遍历驱动的of_match_table,只要有一个compatible字符串和设备树节点的compatible属性值相同,就算匹配成功。
我在项目中遇到过一个问题:设备树里compatible写了多个值,比如"vendor,my-device-v1", "vendor,my-device-v2"。内核匹配时,只要驱动表里有一个能对上就行,不会要求全部匹配。
小技巧:写compatible字符串时,建议遵循"厂商,型号"的格式。比如"ti,am335x-uart"。这样既规范,又能避免命名冲突。
4.3 id_table:传统板级匹配的利器
在没有设备树的年代,或者你还在用传统板级文件注册设备时,id_table就派上用场了。
它的匹配逻辑是:拿设备注册时的名字,去驱动的id_table里找。找到了就匹配。
// 定义ID表
static const struct platform_device_id my_id_table[] = {
{ "my_device_v1", (kernel_ulong_t)&my_v1_data },
{ "my_device_v2", (kernel_ulong_t)&my_v2_data },
{ },
};
// 驱动结构体
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_device",
},
.id_table = my_id_table,
};
设备注册时:
// 传统方式注册设备
static struct platform_device my_device = {
.name = "my_device_v1",
.id = -1,
.dev = {
.platform_data = &my_platform_data,
},
};
注意:id_table匹配时,比较的是platform_device.name和platform_device_id.name。驱动结构体里的driver.name在这里不参与匹配!
我曾经踩过一个坑:驱动里driver.name写的是"my_driver",id_table里写的是"my_device",设备注册的名字也是"my_device"。结果死活匹配不上。后来才发现,id_table匹配根本不看driver.name。嗯,这个细节很容易被忽略。
4.4 name:最朴素的匹配方式
如果前两种方式都没匹配上,内核会尝试最后一种——直接用驱动名和设备名做字符串比较。
说白了就是:驱动结构体里的driver.name和设备注册时的name字段,两个字符串完全相等就匹配。
// 驱动侧
static struct platform_driver my_driver = {
.probe = my_probe,
.driver = {
.name = "my_device", // 就靠这个匹配
},
};
// 设备侧
static struct platform_device my_device = {
.name = "my_device", // 必须和驱动名一样
.id = -1,
};
重要提醒:这种匹配方式最简单,但也是优先级最低的。只要驱动设置了of_match_table或id_table,内核就不会走到name匹配这一步。
你想想看,如果你的驱动同时设置了of_match_table和id_table,那name匹配基本就是个摆设。不过我个人习惯还是会填上driver.name,万一哪天调试用得上呢。
4.5 三种匹配方式的优先级与选择建议
咱们总结一下,内核匹配的完整流程是这样的:
- 先查 of_match_table:如果驱动有这张表,就遍历它,和设备树的
compatible比对。 - 再查 id_table:如果上一步没匹配上,或者驱动没有
of_match_table,就查id_table。 - 最后用 name:前两步都没结果,才用
driver.name和device.name做字符串比较。
我建议你按这个原则来选择:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 使用设备树 | of_match_table | 标准做法,兼容性好,支持多个设备变体 |
| 传统板级文件 | id_table | 可以携带私有数据,比纯name匹配灵活 |
| 简单测试/快速原型 | name匹配 | 代码最少,但扩展性差,不推荐正式项目用 |
我的经验:新项目一律用of_match_table。即使你现在不用设备树,也建议加上。哪天要迁移到设备树,驱动代码基本不用改。
4.6 匹配成功后的回调:probe函数
匹配成功后,内核会调用驱动的probe函数。这时候,你可以通过参数拿到匹配到的设备信息。
static int my_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
const struct platform_device_id *id_entry;
// 判断是通过哪种方式匹配的
if (pdev->dev.of_node) {
// 通过 of_match_table 匹配
match = of_match_device(my_driver_of_match, &pdev->dev);
if (match)
pr_info("匹配到设备树设备: %s\n", match->compatible);
} else if (pdev->id_entry) {
// 通过 id_table 匹配
id_entry = pdev->id_entry;
pr_info("匹配到ID表设备: %s\n", id_entry->name);
} else {
// 通过 name 匹配
pr_info("通过name匹配到设备: %s\n", pdev->name);
}
return 0;
}
这段代码展示了如何在probe里区分匹配来源。我在调试时经常用这个技巧,能快速定位问题出在设备树配置还是驱动代码上。
4.7 避坑指南
最后分享几个我踩过的坑:
- of_match_table 结尾忘了加空结构体:内核遍历时找不到结束标志,会越界访问。我曾经因为这个查了一整天。
- compatible 字符串大小写不一致:内核做的是精确字符串比较,大小写不同就算不匹配。
- 同时设置了 of_match_table 和 id_table,但 id_table 里也有设备树设备名:这会导致混乱,建议只保留一种匹配方式。
- driver.name 和 device.name 不一致:如果只用name匹配,这两个必须一模一样。
嗯,匹配机制就讲到这里。记住一句话:设备树时代,优先用 of_match_table;传统板级,用 id_table;name 匹配留给简单场景。这样你的驱动代码既规范又容易维护。