本节摘要:内核总线模型用"总线、设备、驱动"三个角色统一了硬件接入方式。本节拆解匹配机制的生命周期、匹配表的写法、probe 与 remove 的契约,并回答一个问题:一块焊死在板上的 GPIO 设备,凭什么也要走"总线"流程?
设想一家相亲交易所:一侧登记着"待认领的硬件",另一侧登记着"有意愿的驱动",中间的撮合人拿着双方的卡片比对条件——对上了,安排双方见面(probe);对不上,各自等待。内核总线模型就是这个交易所的工程化实现,而且比任何比喻都严谨:登记有先后,后到的一方负责触发匹配;匹配成功后的见面有一份严格的行为契约;任何一方离场,另一方都会收到通知。
总线模型只有三种角色。总线是撮合平台,维护两张链表:设备表与驱动表。设备是硬件的代表,携带着"我是谁"的身份信息。驱动是操作方法的集合,携带着"我认识谁"的匹配表。
铁律是登记触发匹配:任何一方注册进总线时,内核都会遍历对面的链表,为每个候选者调用总线的匹配函数。设备先注册、驱动后注册,匹配发生在驱动注册的那一刻;反过来亦然。这条规则解释了第 7 章排错实录里的一个经典案例——probe 迟迟不来,原因往往是匹配条件根本对不上,而非"没注册"。
为什么焊死在板上的设备也要走总线?PCIe 这类总线有枚举协议,设备"自己走上门";而 GPIO、内存映射外设没人替它们登记,内核便造了一条虚拟的平台总线,让设备树展开出的板载设备在此登记。统一的模型换来统一的机制:匹配、probe、电源管理、热插拔,平台设备与 PCIe 设备共享同一套基础设施。
平台驱动用匹配表声明自己认识的硬件,核心字段是与设备树节点 compatible 属性比对的字符串:
#include <linux/platform_device.h> static const struct of_device_id led_of_match[] = { { .compatible = "vendor,board-led" }, /* 厂商前缀加设备名 */ { /* 哨兵结尾,必须保留 */ } }; MODULE_DEVICE_TABLE(of, led_of_match); /* 供模块自动加载工具识别 */ static struct platform_driver led_platform_driver = { .probe = led_probe, .remove = led_remove, .driver = { .name = "board-led", .of_match_table = led_of_match, }, }; module_platform_driver(led_platform_driver); /* 一行宏完成注册与注销 */
匹配流程值得在脑中过一遍:设备树里 compatible 为"vendor,board-led"的节点被展开成平台设备并注册;平台驱动注册时,总线遍历设备链表,比对每台设备的 compatible 与匹配表;命中即调用驱动的 probe,并把设备指针递进去。匹配表就是驱动与设备树之间的契约文本——第 4.2 节写设备树时,字符串必须一字不差。
⚠️ 常见坑:匹配表漏写哨兵项,或 compatible 字符串与设备树节点差一个字符。后果都一样:加载成功、日志安静、probe 永不执行。排查方法是查设备的匹配状态目录——第 7 章给出完整手法。
probe 是"确认接管"的仪式:内核把设备交给你,你在这里取资源、映射寄存器、注册中断、创建字符设备,最后返回零表示接管成功。remove 是归还仪式:probe 里做的每件事都要在这里逆序撤销。
static int led_probe(struct platform_device *pdev) { struct resource *res; void __iomem *base; int ret; /* 取寄存器资源并托管映射:失败自动回收,无需手写回滚 */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) return PTR_ERR(base); /* 取中断资源并托管注册(设备树提供中断号) */ ret = devm_request_irq(&pdev->dev, platform_get_irq(pdev, 0), button_isr, IRQF_TRIGGER_RISING, "board-led", NULL); if (ret) return ret; /* 后续:创建字符设备、登记设备模型——第 2 章的三步 */ return 0; } static int led_remove(struct platform_device *pdev) { /* 托管资源自动释放,这里只处理手工申请的部分 */ return 0; }
devm_ 托管族在 3.1 节亮过相,这里看它的完整威力:probe 里任一步失败直接 return,已申请资源由框架自动回收;remove 函数因此经常只剩一行返回。托管机制把"错误处理"从驱动的体力活清单里划掉了大半。
两个契约细节别忽略。其一,probe 可能被多次调用——同型设备装了几台,就探几次,驱动必须每次都独立初始化、互不干扰;其二,probe 里可以睡眠(进程上下文),但必须快速失败——拿不到的资源立刻返回错误,别让总线枚举干等。

总线模型还送了一份大礼:设备的电源事件会自动路由到驱动的电源回调。系统休眠时,总线按依赖顺序调用每台设备驱动的挂起回调;唤醒时逆序恢复;设备空闲时,运行时电源管理框架可让设备自动进入低功耗态。第 6.3 节专门展开这套机制——这里只需记住:接入总线模型,电源管理的入场券就到手了,游离在模型之外的驱动(比如手工注册的杂项设备)要补这套机制得自己造轮子。
追问一:设备与驱动的注册有先后吗?先装驱动、后展开设备树,还能匹配上吗? 能,这正是"后注册的一方触发匹配"的价值所在。内核启动早期设备树先展开,平台设备先入册;驱动模块后装入,注册瞬间总线翻一遍设备表,当场撮合。反过来,驱动先在册、设备后出现(热插拔或晚展开的节点),注册动作同样触发对驱动表的扫描。双方地位对等,谁最后到场谁负责敲门——理解了这一点,"模块装入成功但 probe 没来"这类问题的排查面就清晰了:要么匹配条件不符,要么设备那一侧压根没注册。
追问二:一个驱动能同时服务多种设备吗? 能,靠的是匹配表的多行登记。匹配表本质是一个数组,每行一个支持的兼容标识,末行哨兵收尾;不同板子的设备树节点只要 compatible 落在这个清单里,都会被撮合给同一个驱动。probe 内部再按设备差异取各自的资源——驱动描述"能力",设备树描述"实例",能力清单写宽些,实例有多少都不怕。这个"一对多"的形状,正是第 8 章讲一份驱动服务无数板子的技术前提。
撮合机制有了,另一边的"硬件描述"从哪来?下一节打开设备树——把整块板子的外设写成一棵数据之树。