3. 设备树与硬件匹配

这一节,我们来聊聊设备树里最核心的一个机制——compatible属性匹配。

说白了,就是内核怎么知道,你写的这个设备树节点,对应哪个驱动?

我刚开始学设备树的时候,也困惑过:我在dts里写了个“led-gpios”,驱动里也写了“led-gpios”,它们怎么就对上号了?

嗯,答案就在compatible这个属性里。

3.1 compatible属性的作用与格式

compatible属性,是设备树节点和驱动之间“对暗号”的地方。

它的值是一个字符串列表,格式通常是:

compatible = "厂商,设备型号";

比如:

compatible = "ti,am335x-gpio", "ti,omap-gpio";

这里有两个字符串,内核匹配时,会从左到右依次尝试。

第一个最精确,第二个是兼容型号。

我个人习惯,尽量把最精确的型号写在最前面。这样匹配效率高,也方便后续维护。

小技巧: 如果你写的是通用驱动,可以用通配符吗?不行。compatible不支持通配符,必须精确匹配字符串。

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);

流程是这样的:

  1. 内核解析设备树,发现led@0节点。
  2. 创建platform_device,compatible设为"mycompany,myled"。
  3. 遍历已注册的platform_driver,找到myled_driver。
  4. 比对compatible,匹配成功。
  5. 调用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;
}
核心要点: 设备树节点 → platform_device → 匹配compatible → 调用probe。这就是整个绑定流程。

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被包含
注意: 如果你修改了设备树,一定要重新编译dtb,并确保系统加载的是新的dtb。我曾经犯过这个错,改了dts但忘了编译,折腾了半天。

3.6 知识体系结构图

下面这张图,展示了设备树与platform驱动绑定的完整逻辑:

设备树节点 compatible = "mycompany,myled" 解析 platform_device 包含compatible信息 匹配 platform_driver of_match_table 匹配成功 probe函数 获取资源,初始化设备 匹配失败 驱动不加载,设备不可用 检查compatible是否一致

这张图很直观:设备树节点被解析成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匹配机制,说白了就是“对暗号”。你对上了,驱动就干活;对不上,谁都不认识谁。

掌握了这个机制,设备树和驱动的绑定就不再神秘了。

可可精品资料库