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 匹配优先级与顺序

内核在匹配时,会按照以下顺序进行:

  1. 精确匹配: 驱动 of_match_table 中的 compatible 字符串与设备树节点 compatible 属性中的第一个字符串进行比对。
  2. 遍历匹配: 如果第一个字符串不匹配,则依次尝试设备树节点 compatible 属性中的后续字符串。
  3. 驱动优先: 一旦找到匹配项,立即停止搜索,使用该驱动。
常见陷阱: 设备树节点中 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 函数被调用,整个绑定流程涉及多个内核子系统。下图展示了完整的绑定流程。

设备树与驱动绑定完整流程 阶段1:设备树解析 Bootloader 加载 DTB 内核解析为 device_node 树 阶段2:Platform设备创建 遍历 device_node 创建 platform_device 阶段3:驱动注册 platform_driver_register() 添加到 bus 链表 阶段4:匹配过程(核心) 遍历 of_match_table 逐个取出 compatible 比对 compatible 字符串 精确匹配,无通配符 匹配成功 返回 of_device_id 指针 阶段5:probe 函数调用 platform_driver.probe() 执行 驱动获取资源并初始化硬件 阶段6:设备绑定完成 设备加入驱动设备链表,正常运行

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. 检查 /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, ...) 导出设备表,便于模块加载时自动匹配。