Linux platform驱动匹配表与设备树解析流程平台总线的核心匹配入口在 drivers/base/platform.c 的 platform_match 函数。该函数是 struct bus_type platform_bus_type 的 .match 回调每次新设备注册或新驱动注册时由设备核心层调用。其返回值为非零表示匹配成功。cstatic int platform_match(struct device *dev, struct device_driver *drv){struct platform_device *pdev to_platform_device(dev);struct platform_driver *pdrv to_platform_driver(drv);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI */if (acpi_driver_match_device(dev, drv))return 1;/* Then try the ID table */if (pdrv-id_table)return platform_match_id(pdrv-id_table, pdev) ! NULL;/* fall-back to driver name match */return strcmp(pdev-name, drv-name) 0;}代码中匹配顺序是固定的OF 匹配优先、ACPI 其次、ID table 第三、最后是 name 回退。这个顺序有明确的设计意图设备树的 compatible 字符串是最准确的设备标识而 name 回退是为了兼容旧版驱动和没有设备树的平台。实际系统中一旦 CONFIG_OF 被启用但 drv-of_match_table 为 NULLof_driver_match_device 会快速返回 false不会造成额外的查找开销。这里存在一个容易被忽略的边界点当 dev-of_node 非空但驱动没有提供 of_match_tableof_driver_match_device 返回 0但 acpi_driver_match_device 也返回 0非 ACPI 平台此时如果 pdrv-id_table 也为 NULL则降级到 name 匹配。这意味着一个带设备节点的 platform_device 可能因为驱动的 name 字符串和 platform_device.name 一致而绑定即便 devicetree 中完全没有该驱动的 compatible 条目——这是合法的但容易导致调试困惑。of_driver_match_device 在 drivers/of/device.c 中实现cint of_driver_match_device(struct device *dev, const struct device_driver *drv){if (!drv-of_match_table)return 0;return of_match_device(drv-of_match_table, dev) ! NULL;}其核心是遍历 of_match_table对每个条目调用 of_match_node。of_match_table 是一张以 {}} 结尾的 struct of_device_id 数组cstruct of_device_id {char name[32];char type[32];char compatible[128];const void *data;};name、type、compatible 三个字段中实际匹配中最关键的正是 compatible 字符串。在编写驱动时常见的问题是struct of_device_id 的 compatible 数组长度被定义为 128 字节若设备树中 compatible 字符串长度超过这个限制匹配会静默失败且内核不会给出警告——__of_device_is_compatible 中直接调用 strncmp截断的部分不会被比较。of_match_node 遍历整个 matches 数组对每个条目调用 __of_device_is_compatiblecstruct of_device_id *of_match_node(struct of_device_id *matches,const struct device_node *node){if (!matches)return NULL;while (matches-name[0] || matches-type[0] || matches-compatible[0]) {int match 1;if (matches-name[0])match node-name !strcmp(matches-name, node-name);if (matches-type[0])match node-type !strcmp(matches-type, node-type);if (matches-compatible[0])match __of_device_is_compatible(node, matches-compatible);if (match)return matches;matches;}return NULL;}这里匹配逻辑是 AND 语义只有 name、type、compatible 中所有非空的字段都匹配时才算匹配成功。但实践中几乎所有的设备树匹配场景只使用 compatible 字段name 和 type 基本留空。__of_device_is_compatible 的关键实现在 drivers/of/base.ccint __of_device_is_compatible(const struct device_node *device,const char *compat){const char *cp;int cplen, l;cp __of_get_property(device, compatible, cplen);if (cp NULL)return 0;while (cplen 0) {if (of_compat_cmp(cp, compat, strlen(compat)) 0)return 1;l strlen(cp) 1;cp l;cplen - l;}return 0;}compatible 属性是多个以 \0 分隔的字符串的串联匹配时逐个比较。匹配算法是前缀匹配还是精确匹配关键在 of_compat_cmp 的定义。当开启了 CONFIG_OF_DYNAMIC 时它被定义为 strncmp未开启时是 strcmp。这一点极易出错在开启了 CONFIG_OF_DYNAMIC 的内核中strncmp(cp, compat, strlen(compat)) 意味着如果驱动的 compatible 是 vendor,device-a而 DT 中的 compatible 字符串是 vendor,device-awesome则 strncmp 比较前 strlen(vendor,device-a) 个字节后返回 0误判为匹配成功。这是一个经典的缺陷commit b6488f8d5e57 (of: fix size when dts override phandle) 修复了该问题将 CONFIG_OF_DYNAMIC 分支改为也使用 strcmp。从性能角度看__of_device_is_compatible 每次匹配时都需要从根节点开始重新遍历 compatible 属性中的所有字符串。对于设备树深层嵌套节点配合大量 compatible 条目的场景每次 probe 触发的匹配都需要 O(m * n) 的比较复杂度m 是驱动表中 of_device_id 条目数n 是该节点 compatible 属性的字符串数。驱动核心层没有对匹配结果做缓存——每次注册设备或驱动时都会对所有已注册的驱动或设备重新遍历匹配。在系统启动的热路径上如果存在大量 platform 设备和驱动platform_match 会成为线性扫描的瓶颈。关于竞态条件platform_match 本身是纯读操作不涉及加锁。然而dev-of_node 在设备生命周期中可能出现变化。在某些使用 OF_DYNAMIC 和 DT overlay 的系统中Overlay 被卸载后会修改 dev-of_node 指针或释放 device_node 的内存而同一时刻仍有驱动正在匹配或 probe。内核通过 of_node_get/of_node_put 引用计数来保护 device_node 的生命周期但 platform_match 在被调用时并不持有该引用计数。调用路径是 driver_attach / bus_probe_device - device_initial_probe - really_probe在 really_probe 中会在 probe 前后对 dev-of_node 调用 of_node_get/of_node_put但匹配阶段的引用保护是通过调用者处的 get_device 隐式保证的——只要 device 本身未被释放其 of_node 引用的设备节点就不能被释放因为设备核心在初始化时会对 dev-of_node 调用 of_node_get。但如果 DT overlay 在被卸载时更改了某现有设备的 of_node 指向如 of_detach_node 后再 of_attach_node匹配时的读取可能不一致。接下来看 ID table 匹配。platform_match_id 遍历 struct platform_device_id 数组cstatic const struct platform_device_id *platform_match_id(const struct platform_device_id *id,struct platform_device *pdev){while (id-name[0]) {if (strcmp(pdev-name, id-name) 0) {pdev-id_entry id;return id;}id;}return NULL;}注意 pdev-id_entry 在此处被设置这一设置没有使用任何内存屏障。在 SMP 系统中如果另一个 CPU 在匹配完成后通过 platform_get_device_id 读取 id_entry可能存在读取到陈旧值的问题。不过实践中 pdev-id_entry 只会在 probe 路径上被读取且 probe 和匹配发生在同一线程上下文因此不会存在数据竞争。关于 ID table 匹配的一个隐含行为是id-name 与 pdev-name 做严格字符串比较。而 pdev-name 来源于 platform_device.name该值在设备注册时由 platform_device_register_full 设置可能来源于设备树的 of_modalias_node 生成的别名。of_device_get_modalias位于 drivers/of/device.c截取 compatible 字符串的第一个条目的 vendor 之后的部分作为 modalias然后 platform_device 的内部 name 会根据这个值来设定。这意味着 ID table 匹配实际上间接依赖了设备树中的 compatible 字符串内容只是经过了 modalias 的转换。对于 ACPI 匹配分支acpi_driver_match_device 在非 ACPI 的 DT-only 系统上有 #define acpi_driver_match_device(...) (0) 的空操作优化编译器会将其优化掉内联后为零开销。最后name 回退匹配是直接的字符串比较creturn strcmp(pdev-name, drv-name) 0;这是最原始的匹配方式它基于 platform_driver.driver.name 与 platform_device.name 做匹配。但这里有一个鲜为人知的细节当 platform_device.name 在设备注册时通过 platform_device_alloc 分配时name 是被 kstrdup 拷贝的。而 platform_driver.name 通常指向一个静态字符串。如果驱动被卸载后重新加载模块卸载再加载pdev-name 仍然指向之前注册时拷贝的字符串匹配依然正常。但如果设备本身也经历了重注册例如热插拔或 DT overlay 操作name 字符串的生命周期就需要额外关注——kfree 后的 name 指针访问是 use-after-free。不过标准的热移除流程 platform_device_unregister 会释放 name而重注册时会重新分配不会出现悬空指针除非有人绕过 API 直接修改 name 字段而这在内核审查中是被禁止的。关于 of_match_table 的存放位置它位于 struct device_driver 中cstruct device_driver {const char *name;struct bus_type *bus;const struct of_device_id *of_match_table;const struct acpi_device_id *acpi_match_table;...};所有匹配表都是 const 修饰的只读数据共同驻留在 .init.rodata 或 .rodata 段中。在 CONFIG_OF 被禁用时of_match_table 字段仍然存在但 of_driver_match_device 被编译为 (0) 的内联空函数不会访问该字段。这里有个 ABI 兼容性微妙的点of_match_table 在结构体布局中与 acpi_match_table 顺序固定且处于 struct device_driver 的末尾附近任何在中间插入字段的改动都会影响所有驱动——这也是为什么 driver core 的结构体布局非常稳定极少增加或重排字段。值得注意的是 module_platform_driver 宏展开后的匹配表注册时机。驱动注册通过 platform_driver_register - driver_register - bus_add_driver 完成。bus_add_driver 在驱动加入总线链表后会立即对该总线上的所有设备执行 driver_attach这意味着在 module_init 函数的 platform_driver_register 被调用时可能已经存在成百上千个已注册的 platform_device每个都需要遍历匹配一遍。如果系统中有大量设备和驱动这会导致启动阶段的 O(n*m) 匹配风暴。deferred_probe 机制可以缓解此问题当匹配成功但 probe 需要的资源不可用时设备被加入 deferred_probe_list后续在资源可用时触发重试但匹配过程在每次重试时都会被重新执行。设备树覆盖overlay场景下of_overlay_apply 触发的 of_platform_populate 会导致新的平台设备注册进而触发新一轮的匹配。如果 overlay 从属的设备树节点 compatible 条目与多个驱动匹配先注册的驱动会获得绑定机会。这里存在竞态两个内核模块同时加载各自的驱动注册在不同 CPU 上而 overlay 设备注册也在进行中最终绑定结果取决于总线锁 bus-p-klist_devices.k_lock 的内部自旋锁顺序该顺序由链表遍历先后决定而不可预测。对于 platform_match 的调用栈跟踪可以通过bashecho p:platform_match platform_match dev0($arg1):u32 drv0($arg2):u32 /sys/kernel/debug/tracing/kprobe_eventsecho 1 /sys/kernel/debug/tracing/events/kprobes/platform_match/enablecat /sys/kernel/debug/tracing/trace_pipe在启动阶段观察哪些设备与哪些驱动在尝试匹配对调试 probe 顺序问题非常有效。最后PCI/USB 等其他总线有枚举机制而 platform 总线的匹配完全依赖匹配表的线性扫描。因此在拥有大量平台设备的系统中如 SoC 全功能板级支持匹配过程可能占用启动时间的显著比例。initcall_debug 参数可以量化每个驱动注册耗时但没有内置机制统计 platform_match 本身的 CPU 开销。通过 ftrace 设置 function_graph tracer 并过滤 platform_match 可以精确测量延迟。一个典型的优化是确保 of_match_table 中条目按匹配频度排序——最可能匹配的放在最前面因为 of_match_node 的第一命中即返回不会继续遍历后续条目。
Linux platform驱动匹配表与设备树解析流程
发布时间:2026/6/14 8:04:03
Linux platform驱动匹配表与设备树解析流程平台总线的核心匹配入口在 drivers/base/platform.c 的 platform_match 函数。该函数是 struct bus_type platform_bus_type 的 .match 回调每次新设备注册或新驱动注册时由设备核心层调用。其返回值为非零表示匹配成功。cstatic int platform_match(struct device *dev, struct device_driver *drv){struct platform_device *pdev to_platform_device(dev);struct platform_driver *pdrv to_platform_driver(drv);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI */if (acpi_driver_match_device(dev, drv))return 1;/* Then try the ID table */if (pdrv-id_table)return platform_match_id(pdrv-id_table, pdev) ! NULL;/* fall-back to driver name match */return strcmp(pdev-name, drv-name) 0;}代码中匹配顺序是固定的OF 匹配优先、ACPI 其次、ID table 第三、最后是 name 回退。这个顺序有明确的设计意图设备树的 compatible 字符串是最准确的设备标识而 name 回退是为了兼容旧版驱动和没有设备树的平台。实际系统中一旦 CONFIG_OF 被启用但 drv-of_match_table 为 NULLof_driver_match_device 会快速返回 false不会造成额外的查找开销。这里存在一个容易被忽略的边界点当 dev-of_node 非空但驱动没有提供 of_match_tableof_driver_match_device 返回 0但 acpi_driver_match_device 也返回 0非 ACPI 平台此时如果 pdrv-id_table 也为 NULL则降级到 name 匹配。这意味着一个带设备节点的 platform_device 可能因为驱动的 name 字符串和 platform_device.name 一致而绑定即便 devicetree 中完全没有该驱动的 compatible 条目——这是合法的但容易导致调试困惑。of_driver_match_device 在 drivers/of/device.c 中实现cint of_driver_match_device(struct device *dev, const struct device_driver *drv){if (!drv-of_match_table)return 0;return of_match_device(drv-of_match_table, dev) ! NULL;}其核心是遍历 of_match_table对每个条目调用 of_match_node。of_match_table 是一张以 {}} 结尾的 struct of_device_id 数组cstruct of_device_id {char name[32];char type[32];char compatible[128];const void *data;};name、type、compatible 三个字段中实际匹配中最关键的正是 compatible 字符串。在编写驱动时常见的问题是struct of_device_id 的 compatible 数组长度被定义为 128 字节若设备树中 compatible 字符串长度超过这个限制匹配会静默失败且内核不会给出警告——__of_device_is_compatible 中直接调用 strncmp截断的部分不会被比较。of_match_node 遍历整个 matches 数组对每个条目调用 __of_device_is_compatiblecstruct of_device_id *of_match_node(struct of_device_id *matches,const struct device_node *node){if (!matches)return NULL;while (matches-name[0] || matches-type[0] || matches-compatible[0]) {int match 1;if (matches-name[0])match node-name !strcmp(matches-name, node-name);if (matches-type[0])match node-type !strcmp(matches-type, node-type);if (matches-compatible[0])match __of_device_is_compatible(node, matches-compatible);if (match)return matches;matches;}return NULL;}这里匹配逻辑是 AND 语义只有 name、type、compatible 中所有非空的字段都匹配时才算匹配成功。但实践中几乎所有的设备树匹配场景只使用 compatible 字段name 和 type 基本留空。__of_device_is_compatible 的关键实现在 drivers/of/base.ccint __of_device_is_compatible(const struct device_node *device,const char *compat){const char *cp;int cplen, l;cp __of_get_property(device, compatible, cplen);if (cp NULL)return 0;while (cplen 0) {if (of_compat_cmp(cp, compat, strlen(compat)) 0)return 1;l strlen(cp) 1;cp l;cplen - l;}return 0;}compatible 属性是多个以 \0 分隔的字符串的串联匹配时逐个比较。匹配算法是前缀匹配还是精确匹配关键在 of_compat_cmp 的定义。当开启了 CONFIG_OF_DYNAMIC 时它被定义为 strncmp未开启时是 strcmp。这一点极易出错在开启了 CONFIG_OF_DYNAMIC 的内核中strncmp(cp, compat, strlen(compat)) 意味着如果驱动的 compatible 是 vendor,device-a而 DT 中的 compatible 字符串是 vendor,device-awesome则 strncmp 比较前 strlen(vendor,device-a) 个字节后返回 0误判为匹配成功。这是一个经典的缺陷commit b6488f8d5e57 (of: fix size when dts override phandle) 修复了该问题将 CONFIG_OF_DYNAMIC 分支改为也使用 strcmp。从性能角度看__of_device_is_compatible 每次匹配时都需要从根节点开始重新遍历 compatible 属性中的所有字符串。对于设备树深层嵌套节点配合大量 compatible 条目的场景每次 probe 触发的匹配都需要 O(m * n) 的比较复杂度m 是驱动表中 of_device_id 条目数n 是该节点 compatible 属性的字符串数。驱动核心层没有对匹配结果做缓存——每次注册设备或驱动时都会对所有已注册的驱动或设备重新遍历匹配。在系统启动的热路径上如果存在大量 platform 设备和驱动platform_match 会成为线性扫描的瓶颈。关于竞态条件platform_match 本身是纯读操作不涉及加锁。然而dev-of_node 在设备生命周期中可能出现变化。在某些使用 OF_DYNAMIC 和 DT overlay 的系统中Overlay 被卸载后会修改 dev-of_node 指针或释放 device_node 的内存而同一时刻仍有驱动正在匹配或 probe。内核通过 of_node_get/of_node_put 引用计数来保护 device_node 的生命周期但 platform_match 在被调用时并不持有该引用计数。调用路径是 driver_attach / bus_probe_device - device_initial_probe - really_probe在 really_probe 中会在 probe 前后对 dev-of_node 调用 of_node_get/of_node_put但匹配阶段的引用保护是通过调用者处的 get_device 隐式保证的——只要 device 本身未被释放其 of_node 引用的设备节点就不能被释放因为设备核心在初始化时会对 dev-of_node 调用 of_node_get。但如果 DT overlay 在被卸载时更改了某现有设备的 of_node 指向如 of_detach_node 后再 of_attach_node匹配时的读取可能不一致。接下来看 ID table 匹配。platform_match_id 遍历 struct platform_device_id 数组cstatic const struct platform_device_id *platform_match_id(const struct platform_device_id *id,struct platform_device *pdev){while (id-name[0]) {if (strcmp(pdev-name, id-name) 0) {pdev-id_entry id;return id;}id;}return NULL;}注意 pdev-id_entry 在此处被设置这一设置没有使用任何内存屏障。在 SMP 系统中如果另一个 CPU 在匹配完成后通过 platform_get_device_id 读取 id_entry可能存在读取到陈旧值的问题。不过实践中 pdev-id_entry 只会在 probe 路径上被读取且 probe 和匹配发生在同一线程上下文因此不会存在数据竞争。关于 ID table 匹配的一个隐含行为是id-name 与 pdev-name 做严格字符串比较。而 pdev-name 来源于 platform_device.name该值在设备注册时由 platform_device_register_full 设置可能来源于设备树的 of_modalias_node 生成的别名。of_device_get_modalias位于 drivers/of/device.c截取 compatible 字符串的第一个条目的 vendor 之后的部分作为 modalias然后 platform_device 的内部 name 会根据这个值来设定。这意味着 ID table 匹配实际上间接依赖了设备树中的 compatible 字符串内容只是经过了 modalias 的转换。对于 ACPI 匹配分支acpi_driver_match_device 在非 ACPI 的 DT-only 系统上有 #define acpi_driver_match_device(...) (0) 的空操作优化编译器会将其优化掉内联后为零开销。最后name 回退匹配是直接的字符串比较creturn strcmp(pdev-name, drv-name) 0;这是最原始的匹配方式它基于 platform_driver.driver.name 与 platform_device.name 做匹配。但这里有一个鲜为人知的细节当 platform_device.name 在设备注册时通过 platform_device_alloc 分配时name 是被 kstrdup 拷贝的。而 platform_driver.name 通常指向一个静态字符串。如果驱动被卸载后重新加载模块卸载再加载pdev-name 仍然指向之前注册时拷贝的字符串匹配依然正常。但如果设备本身也经历了重注册例如热插拔或 DT overlay 操作name 字符串的生命周期就需要额外关注——kfree 后的 name 指针访问是 use-after-free。不过标准的热移除流程 platform_device_unregister 会释放 name而重注册时会重新分配不会出现悬空指针除非有人绕过 API 直接修改 name 字段而这在内核审查中是被禁止的。关于 of_match_table 的存放位置它位于 struct device_driver 中cstruct device_driver {const char *name;struct bus_type *bus;const struct of_device_id *of_match_table;const struct acpi_device_id *acpi_match_table;...};所有匹配表都是 const 修饰的只读数据共同驻留在 .init.rodata 或 .rodata 段中。在 CONFIG_OF 被禁用时of_match_table 字段仍然存在但 of_driver_match_device 被编译为 (0) 的内联空函数不会访问该字段。这里有个 ABI 兼容性微妙的点of_match_table 在结构体布局中与 acpi_match_table 顺序固定且处于 struct device_driver 的末尾附近任何在中间插入字段的改动都会影响所有驱动——这也是为什么 driver core 的结构体布局非常稳定极少增加或重排字段。值得注意的是 module_platform_driver 宏展开后的匹配表注册时机。驱动注册通过 platform_driver_register - driver_register - bus_add_driver 完成。bus_add_driver 在驱动加入总线链表后会立即对该总线上的所有设备执行 driver_attach这意味着在 module_init 函数的 platform_driver_register 被调用时可能已经存在成百上千个已注册的 platform_device每个都需要遍历匹配一遍。如果系统中有大量设备和驱动这会导致启动阶段的 O(n*m) 匹配风暴。deferred_probe 机制可以缓解此问题当匹配成功但 probe 需要的资源不可用时设备被加入 deferred_probe_list后续在资源可用时触发重试但匹配过程在每次重试时都会被重新执行。设备树覆盖overlay场景下of_overlay_apply 触发的 of_platform_populate 会导致新的平台设备注册进而触发新一轮的匹配。如果 overlay 从属的设备树节点 compatible 条目与多个驱动匹配先注册的驱动会获得绑定机会。这里存在竞态两个内核模块同时加载各自的驱动注册在不同 CPU 上而 overlay 设备注册也在进行中最终绑定结果取决于总线锁 bus-p-klist_devices.k_lock 的内部自旋锁顺序该顺序由链表遍历先后决定而不可预测。对于 platform_match 的调用栈跟踪可以通过bashecho p:platform_match platform_match dev0($arg1):u32 drv0($arg2):u32 /sys/kernel/debug/tracing/kprobe_eventsecho 1 /sys/kernel/debug/tracing/events/kprobes/platform_match/enablecat /sys/kernel/debug/tracing/trace_pipe在启动阶段观察哪些设备与哪些驱动在尝试匹配对调试 probe 顺序问题非常有效。最后PCI/USB 等其他总线有枚举机制而 platform 总线的匹配完全依赖匹配表的线性扫描。因此在拥有大量平台设备的系统中如 SoC 全功能板级支持匹配过程可能占用启动时间的显著比例。initcall_debug 参数可以量化每个驱动注册耗时但没有内置机制统计 platform_match 本身的 CPU 开销。通过 ftrace 设置 function_graph tracer 并过滤 platform_match 可以精确测量延迟。一个典型的优化是确保 of_match_table 中条目按匹配频度排序——最可能匹配的放在最前面因为 of_match_node 的第一命中即返回不会继续遍历后续条目。