3、设备树与硬件匹配:compatible属性匹配机制、设备树与platform驱动绑定
这一节,我们来聊聊设备树里最核心的一个机制——compatible属性匹配。
说白了,就是内核怎么知道,你写的这个设备树节点,对应哪个驱动?
我刚开始学设备树的时候,也困惑过:我在dts里写了个“led-gpios”,驱动里也写了“led-gpios”,它们怎么就对上号了?
嗯,答案就在compatible这个属性里。
3.1 compatible属性的作用与格式
compatible属性,是设备树节点和驱动之间“对暗号”的地方。
它的值是一个字符串列表,格式通常是:
compatible = "厂商,设备型号";
比如:
compatible = "ti,am335x-gpio", "ti,omap-gpio";
这里有两个字符串,内核匹配时,会从左到右依次尝试。
第一个最精确,第二个是兼容型号。
我个人习惯,尽量把最精确的型号写在最前面。这样匹配效率高,也方便后续维护。
3.2 匹配机制:内核是怎么找到驱动的?
内核启动时,会遍历所有注册的platform驱动。
每个驱动都有一个of_match_table,里面存放了它支持的compatible列表。
当设备树节点被解析成platform_device后,内核会拿节点的compatible去和驱动的of_match_table比对。
匹配上了,就调用驱动的probe函数。
匹配不上?那这个设备就“无人认领”,驱动不会加载。
我曾经遇到过一个坑:我在dts里写的是“mydev,led”,驱动里写的是“mydev,led-v2”。
结果怎么都匹配不上,查了半天才发现是字符串不一致。
所以,compatible字符串一定要一字不差,大小写、连字符都不能错。
3.3 设备树与platform驱动绑定的完整流程
我们来看一个实际例子。
假设我有一个LED设备,设备树节点如下:
myled: led@0 {
compatible = "mycompany,myled";
reg = <0x01c20800 0x10>;
gpios = <&pio 7 6 GPIO_ACTIVE_HIGH>;
};
对应的platform驱动:
static const struct of_device_id myled_of_match[] = {
{ .compatible = "mycompany,myled" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myled_of_match);
static struct platform_driver myled_driver = {
.driver = {
.name = "myled",
.of_match_table = myled_of_match,
},
.probe = myled_probe,
.remove = myled_remove,
};
module_platform_driver(myled_driver);
流程是这样的:
- 内核解析设备树,发现led@0节点。
- 创建platform_device,compatible设为"mycompany,myled"。
- 遍历已注册的platform_driver,找到myled_driver。
- 比对compatible,匹配成功。
- 调用myled_probe函数。
在probe函数里,你就可以通过device_node来获取资源了:
static int myled_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct gpio_desc *led_gpio;
// 获取gpios属性
led_gpio = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW);
if (IS_ERR(led_gpio))
return PTR_ERR(led_gpio);
// 获取reg属性
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
dev_info(dev, "myled probed successfully\n");
return 0;
}
3.4 匹配优先级与回退机制
compatible列表里如果有多个字符串,内核会按顺序匹配。
比如:
compatible = "mycompany,myled-v2", "mycompany,myled";
内核会先尝试匹配"mycompany,myled-v2"。
如果找不到对应的驱动,再尝试"mycompany,myled"。
这种机制的好处是:新硬件可以兼容旧驱动。
你想想看,如果你升级了硬件,但驱动还没更新,只要旧驱动支持"mycompany,myled",新设备也能正常工作。
我建议,每个设备节点至少保留一个通用的compatible值,作为回退方案。
3.5 常见匹配失败的原因
匹配失败,是大家经常遇到的问题。我总结了几种情况:
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 驱动没加载 | 驱动没编译进内核,或没insmod | 检查内核配置,或手动加载模块 |
| compatible不匹配 | 字符串写错了,大小写或连字符不对 | 仔细比对dts和驱动里的字符串 |
| of_match_table没设置 | 驱动里忘了加of_match_table | 检查驱动结构体,确保有of_match_table |
| 设备树没被编译 | dts文件没被编译进dtb | 检查Makefile,确保dts被包含 |
3.6 知识体系结构图
下面这张图,展示了设备树与platform驱动绑定的完整逻辑:
这张图很直观:设备树节点被解析成platform_device,然后和platform_driver的of_match_table进行compatible匹配。匹配成功,调用probe;匹配失败,设备就“躺尸”了。
3.7 避坑指南
最后,分享几个我踩过的坑:
- compatible字符串不要加空格。比如"mycompany, myled"是不行的,逗号后面不能有空格。
- of_match_table要以空结构体结尾。就是{ /* sentinel */ },否则内核不知道列表结束。
- MODULE_DEVICE_TABLE宏不能少。这个宏告诉内核模块加载系统,这个驱动支持哪些设备。
- 设备树里reg属性要和驱动里platform_get_resource对应。reg的第一个地址对应index 0,第二个对应index 1,以此类推。
嗯,compatible匹配机制,说白了就是“对暗号”。你对上了,驱动就干活;对不上,谁都不认识谁。
掌握了这个机制,设备树和驱动的绑定就不再神秘了。
可可精品资料库