ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入解析Linux Platform设备与驱动匹配机制:从设备树到probe调用

深入解析Linux Platform设备与驱动匹配机制:从设备树到probe调用 去年我在调试一块 i.MX6ULL 板子时遇到过一种特别迷惑的现象驱动模块加载之后没有任何报错insmod 输出干干净净/proc/devices 里却没有我想看到的设备register_chrdev 明明执行了但 probe 就是不出来。后来把内核日志翻到底才发现 platform_driver_register 确实被调到了可设备与驱动匹配的环节根本没把我写的驱动和我设备树里的节点对上。也是从那时候起我才把 Linux 驱动开发里的 Platform 设备与驱动匹配机制从头到尾认真梳理了一遍终于懂了为什么论坛里隔三差五就有人问“probe 为什么不调用”。这篇就结合我实际调试 i.MX6ULL 的经验把设备与驱动是怎么匹配的、匹配不上时要查哪些地方讲透。适合已经能写简单字符设备驱动、正准备跨入设备树驱动和 probe 模式的人阅读。1. 从地址写死的驱动到 device/driver 分离Platform 到底在解决什么问题新手入门 i.MX6ULL 的时候一般都会先接触一种很原始的 LED 驱动写法在 file_operations 的 open 或 ioctl 里直接 ioremap 寄存器写某个 GPIO 的 MUX、方向、数据寄存器然后 register_chrdev 把设备注册进内核。这套代码能跑也能控制灯可一旦项目要换引脚、换板子、换一颗相近的芯片就得打开源码一个个改地址重新编译。硬件信息像胶水一样糊在驱动逻辑里时间一长代码根本没法维护。Linux 设备模型其实就是冲着这个问题来的。内核把“设备本身”和“驱动本身”拆成两类对象。设备对象负责描述硬件资源基地址是多少、中断号是多少、时钟名字是什么驱动对象负责描述操作逻辑怎么初始化、怎么读、怎么写、怎么关机。两者之间通过总线结构做媒人而总线最核心的职责之一就是提供 match 回调把一个驱动和一块设备“对上眼”。在 i.MX6ULL 这种 ARM SoC 上SoC 内部的大多数外设并不像 USB、PCI 那样有真实的物理总线可以枚举于是内核捏造出了一条虚拟总线叫 platform bus也翻译成平台总线。UART、GPIO、I2C 控制器、网卡 MAC、LCD 控制器这些没有物理总线枚举机制、但 CPU 可以直接访问的设备统统挂在这条虚拟总线上。打个比方驱动就是你开的一家店里面有一套完整的服务流程设备就是上门来的客人身上带着身份证和需求单Platform 总线则像一个拥有全市户口的政务窗口先把客人的资料录入系统再拿着你的营业范围去核对。platform_match 就是这个窗口里负责核对的那个人。过去那种裸寄存器驱动相当于你在路边摆摊看到谁像你的客户就直接拉过来完全不走系统登记Single 板子能行项目一多必然失控。还有一个值得注意的点Platform 这个名字很容易让人以为只有在 ARM 上才有。实际上 x86 上也有很多设备没有强制的总线枚举机制同样会落到 platform bus 上。你在 Linux 里看到 /sys/bus/platform/devices/ 下面那一大串基本都是这一类“住在 SoC 里、不挂在 PCI/USB 下”的设备。1.1 软件层面的双层结构代码上Platform 机制涉及两个关键结构体platform_device 和 platform_driver。它们并不是凭空设计的而是在通用 device_driver 和 device 结构体基础上包了一层额外携带了驱动开发最关心的资源信息和接口信息。platform_device 结构体里除了内嵌 struct device 之外还有 resource 数组用来描述内存地址、中断号等传统资源。即使现在大家都用设备树probe 里读取寄存器地址时背后仍然会把这些信息从设备树解析成 resource再通过 platform_get_resource 或 devm_platform_ioremap_resource 拿回来。platform_driver 则是给驱动注册的入口它包含 probe、remove、shutdown 回调同时有一个指向 struct device_driver 的 driver 成员。启动时你写 platform_driver_register实际上主要是在注册内嵌的那个通用驱动对象而设备和驱动的匹配最后也就是拿这个通用驱动对象和设备对象进行比较。用 i.MX6ULL 最常见的外设举例系统里的 ecspi、fec、uart1 这些节点假如不是挂在 i2c、spi 这种专门总线上指控制器本身内核就会把它们注册成 platform_device。对应驱动里声明 struct platform_driver提供 probe注册到 platform bus。只要 match 成功probe 就会被调用然后你在这个函数里完成寄存器映射、申请中断、注册字符设备或者框架接口。2. 设备端是怎么来的设备树节点与 platform_device 的转换链路既然驱动要匹配设备那就得先搞清楚设备对象是从哪条流水线里生产出来的。对 i.MX6ULL 这种现代 ARM 平台来说答案几乎都是设备树。少数老内核 or 板级文件还在用的 platform_device_register 方式现在已经不是主流但理解它对你排查老代码仍有帮助。最直白的一句话设备树不是直接交给驱动使用的它要经过内核解析转变成一颗 struct device_node 树然后其中符合条件的一部分节点会被转换成 struct platform_device注册到 platform bus 上。完成这个转换的核心代码在 drivers/of/platform.c常见入口叫 of_platform_default_populate_init。Linux 启动时会从根节点往下遍历如果节点的 compatible 属性表明它是一个“平台总线下的普通设备”或者所在的父总线 compatible 带 simple-bus、fsl,aips-bus 这类标识子节点就逐层被递归创建为 platform_device。整个工作发生在内核很早期的 initcall 阶段所以你完全不用自己在驱动里手动把设备树节点注册为 platform_device更不要在 probe 里再 platform_device_register。比如 i.MX6ULL 官方设备树里经常能看到这样的节点片段uart1 { pinctrl-names default; pinctrl-0 pinctrl_uart1; status okay; };板上实际做的是在 dts/dtsi 里把串口控制器的属性配好。内核启动后这个节点如果挂在了合适的父总线下就会成为一个带 of_node 的 platform_device。你再打开 /sys/bus/platform/devices/ 就能看到它。2.1 设备身份的核心是 compatible 而不是节点名这里要强调一个非常容易搞混的点对设备树来的 platform_device它的“身份”不是节点名而是 compatible 属性。compatible 相当于设备的身份证号是一个字符串通常写成“厂商,型号”的形式。厂商在设备树里声明自己是谁驱动在 of_match_table 里声明自己能认谁两边能对上match 才成立。i.MX6ULL 的串口节点 compatible 经常是这样的compatible fsl,imx6ul-uart, fsl,imx6q-uart, fsl,imx21-uart;驱动侧imx 串口驱动里通常有这样一个表static const struct of_device_id imx_uart_dt_ids[] { { .compatible fsl,imx6q-uart }, { .compatible fsl,imx53-uart }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx_uart_dt_ids);比较的时候设备树里那一串 compatible 会从左到右逐个去 of_match_table 里扫描找到任意一个完全相等的字符串就算匹配成功。注意是“完全相等”不是包含、不是正则、不是模糊拼写。compatible 里多一个字母、少一个短横线都会直接匹配失败。这也是新手排查 probe 不执行时最容易被忽视的原因之一。2.2 老内核里没有设备树那时靠什么匹配如果去看一些比较老的 i.MX6ULL 教程或者拿到一份陈旧的内核代码你可能会看到板级 board 文件里这样写static struct resource led_resources[] { DEFINE_RES_MEM(0x020C406C, 0x4), }; static struct platform_device led_device { .name imx6ull-led, .id -1, .num_resources ARRAY_SIZE(led_resources), .resource led_resources, }; static int __init led_device_init(void) { return platform_device_register(led_device); }这种不是从设备树创建的 platform_device它的 dev-of_node 是空的所以设备树那套 of_match_table 匹配规则根本不参与。platform_match 会落到 id_table 或者最后的 name 比较那条规则上去。你会看到很多老驱动常年维持一个 platform_device_id 表也就是为了兼容这种没有设备树 or 没有 of_node 的老式注册路径。我自己刚开始从板级文件思维跳到设备树思维时最吃亏的就是以为自己把 .name 改成和设备树节点名一样就能匹配上结果怎么改都不 probe。原因后面讲平台匹配源码时会说清楚设备树时代的核心身份字段已经变了老一套的 name 匹配不是主要规则。3. 配对时的真实现场platform_match 里的优先级排序与常见误区现在我们终于可以进入本文最核心的地方Platform 总线上设备和驱动到底是怎么比较的。直接打开内核源码具体路径是 drivers/base/platform.c里面有一个 platform_match 函数。不同内核版本细节略有差异但 Linux 4.x/5.x 里逻辑
返回列表