4. platform驱动匹配机制:of_match_table、id_table、name三种匹配方式详解

驱动匹配,说白了就是内核怎么找到你的设备。我刚开始接触platform驱动时,总觉得这步很简单——不就是名字对上就行吗?后来踩了几个坑才发现,这里面的门道还真不少。

今天咱们就聊聊platform驱动的三种匹配方式。我个人习惯把它们分成两类:设备树时代的匹配和传统板级匹配。嗯,咱们一个一个来看。

4.1 匹配机制的整体框架

先看一张图,帮你快速建立整体认知:

platform驱动匹配机制总览 驱动(Driver) of_match_table (设备树兼容表) id_table (传统ID匹配表) name (驱动名字) 设备(Device) compatible 属性 (设备树节点) 设备名 (传统板级注册) 设备名 (platform_device.name) 匹配方式1 匹配方式2 匹配方式3 匹配优先级:of_match_table → id_table → name 内核按顺序尝试,匹配成功即停止

从图上你能看到,内核会按优先级依次尝试三种匹配方式。一旦匹配成功,就不再往下走了。这个顺序很重要,我待会儿会细说。

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 三种匹配方式的优先级与选择建议

咱们总结一下,内核匹配的完整流程是这样的:

  1. 先查 of_match_table:如果驱动有这张表,就遍历它,和设备树的compatible比对。
  2. 再查 id_table:如果上一步没匹配上,或者驱动没有of_match_table,就查id_table。
  3. 最后用 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 匹配留给简单场景。这样你的驱动代码既规范又容易维护。