1. 驱动“装上了”却不跑 probe从 i.MX6ULL LED 驱动的第一个坑谈起1.1 最初直接用字符驱动思路写代码模块加载却一点动静没有我第一次在 i.MX6ULL 上调 GPIO LED 驱动时犯过一个很典型的错误。当时思路很简单照着 Linux 字符设备驱动的例子写一个 file_operationsmodule_init里申请 GPIO、注册 miscdevice 或 cdev然后用户程序 open/write 去点亮 LED。那一套其实没什么问题只要把设备树里对应的 GPIO 引脚复用配置好用户程序操作/dev/myled就能闪灯。后来换了种写法想用更“标准”的 platform 驱动风格来管理这个 LED 设备。于是写了 platform_driver 结构体、实现了 probe 函数、在驱动里用devm_gpiod_get()获取描述符注册用的是module_platform_driver()。编译没问题Makefile 也没写错insmod 之后模块确实加载进了内核modprobe 也显示加载成功。但 probe 始终没有执行。dmesg 里看不到任何 probe 成功的打印/proc/misc里也不会出现对应的次设备号——因为我当时把 LED 的点亮逻辑放在 probe 里probe 没跑整个驱动等于什么都没干。那段时间我反复检查代码甚至对 module_init 的入口打过断点最后才意识到问题根本不是函数有没有被调用而是我压根没理解 platform 驱动的工作原理。这里就得引出 topic 里最核心的概念platform device 与 platform driver 需要先“配对成功”内核才会调用这个 platform_driver 的 probe。你也可以把它理解成婚介所设备是征婚者驱动也是征婚者两边先得互相看对眼内核才会安排见面probe。如果匹配机制没走通probe 永远是个摆设。很多刚从裸机转过来的人一开始都会卡在这个地方。因为裸机程序里没有“设备”和“驱动”要不要匹配的问题寄存器就在那里函数想调就调。但 Linux 驱动模型把设备和驱动拆开了这是为了可移植性、可热插拔性、以及更灵活的硬件描述方式代价就是你要先弄明白平台总线那一套匹配规则。1.2 核心概念什么是 platform device、platform driver 和 platform bus在 i.MX6ULL 这类 SoC 上有大量外设并不挂在 I2C、SPI、PCI 这样的物理总线上而是通过内存映射方式挂在 CPU 的寄存器总线上。GPIO、UART、ECSPI、I2C 控制器、SDIO 控制器这些外设的寄存器都在芯片内部地址空间直接暴露给 CPU。内核不能为每一个外设都发明一种物理总线于是设计了一条虚拟总线把所有“直接挂在 CPU 总线旁的内存映射设备”统一管理起来这条虚拟总线就叫 platform bus。注意platform 这个名字不是指“某个特定的硬件平台”它指的是“这个设备所依赖的平台/设计”。你可以把它理解成 Linux 设备模型里专门负责承上启下的一层抽象。它的核心职责只有一个在 platform_device 和 platform_driver 之间做匹配。platform_device描述外设的存在比如我在设备树里定义了一个 LED那么这个 LED 节点将来就会被内核转成一个 platform_device。platform_driver描述让外设工作的代码逻辑比如这个 LED 驱动如何初始化 GPIO、如何控制电平。platform_bus_type内核维护的一条虚拟总线数据结构负责为 device 和 driver 牵线搭桥。这三者缺一不可。很多 Linux 驱动书里会强调“设备与驱动分离”真正的含义就是设备的硬件属性用设备树描述驱动的行为用 C 代码描述两者通过 platform bus 匹配后组合起来。好处很明显同一份驱动代码只要设备树里 compatible 字符串对得上几乎不用改动就能适配不同板卡。所以驱动不 probe先别怀疑代码逻辑最优先排除的一定是匹配问题。2. i.MX6ULL 的片上外设如何变成 platform_device设备树解析链路2.1 从 imx6ull.dtsi 开始看 SoC 总线层级既然匹配的前提是先有 platform_device那就得先知道设备是从哪来的。现代 ARM Linux 早已不用板级文件注册 platform_device而是通过设备树DTB描述硬件信息内核启动阶段解析设备树把每个可用的外设节点转换成 platform_device。i.MX6ULL 的设备树源文件分层很明显。imx6ull.dtsi是 SoC 级描述里面描述了芯片内部的总线结构和各类外设节点具体的开发板 dts 文件再 include 这个 dtsi允许板级覆盖一些属性。粗略结构类似这样/ { model Freescale i.MX6ULL; compatible fsl,imx6ull; soc { #address-cells 1; #size-cells 1; compatible simple-bus; aips1: aips-bus02000000 { compatible fsl,aips-bus, simple-bus; ... }; aips2: aips-bus02100000 { compatible fsl,aips-bus, simple-bus; ... }; }; };注意这里有一个非常重要的细节soc节点以及aips-bus这类节点的 compatible 里都包含了simple-bus。simple-bus是内核约定的一个标签表示这个总线节点下的子节点应该被当作普通的、可以直接映射到 CPU 地址空间的外设来枚举。内核启动时会有一个 initcall 调用of_platform_default_populate_init()它从设备树根节点开始遍历遇到compatible包含simple-bus的节点就会继续递归创建它下面的 platform_device。i.MX6ULL 的 soC 节点、aips1、aips2、以及它们下层的 gpio 控制器、uart、i2c 控制器等都会顺着这条链变成一个个 platform_device。所以你在 i.MX6ULL 上看到dmesg里有大量类似这样的输出platform 1c40000.serial: probe of 1c40000.serial returned 0 after 123 usecs platform 20a0000.gpio: probe of 20a0000.gpio returned 0 after 45 usecs这些设备名里的前缀1c40000、20a0000就是节点在 SoC 内部的内存基地址也是这些外设被访问的窗口。i.MX6ULL 的 SoC 外设资源就是靠这种“总线—地址—外设”的层级关系组织起来的。2.2 不是所有设备树节点都会出现在 platform 总线上这里藏着很多新手最容易踩的坑你会觉得“我在设备树里加了一个节点它就应该自动变成 platform_device”。但实际并非如此。设备树里的节点能不能被转成 platform_device和这个节点所在的位置、父节点的 compatible、以及节点自身的 status 都有直接关系。拿一个外接 I2C 触摸屏举例触摸屏芯片挂在 i2c 控制器的子节点下i2c1 { clock-frequency 100000; touchscreen38 { compatible xx,touchscreen; reg 0x38; }; };这个touchscreen38节点最终并不会被创建成 platform_device而是会由 i2c 控制器驱动在 probe 时注册 i2c_client然后通过 i2c bus 和 i2c_driver 做匹配。原因很简单它挂在 I2C 总线上不是直接挂在 CPU 总线旁的内存映射设备不再属于 platform bus 的管辖范围。反过来如果你打算驱动一个简单的、直接内存映射的控制器那么节点就应该放在能通过simple-bus链被递归遍历的位置。一般情况下开发板 dts 顶层加的根节点下的自定义节点只要节点本身可用也会在初始化阶段被处理成 platform_device。至于那些放在 i2c 子节点下却想当 platform_device 用的节点多半是白加了。在 i.MX6ULL 平台树中如果你自己定义一个“小外设”最稳妥的位置其实是把它放在自定义的、带compatible simple-bus的父节点下面或者干脆放在设备树根节点下。如果放在某些被其它专门 bus 驱动的控制器子节点里最后可能什么设备都不会生成。2.3 status disabled 与可用节点的差别设备树里很多节点默认是 disabled 的特别是 SoC dtsi 里那些用不到的外设。以 i.MX6ULL 的 I2C 节点为例imx6ull.dtsi 里写的是i2c1: i2c21a0000 { compatible fsl,imx6ul-i2c; reg 0x021a0000 0x4000; ... status disabled; };当板级 dts 里使用i2c1 { ... }添加外设时往往不会主动把 status 改掉而如果 status 一直保持 disabled内核解析设备树时直接会跳过这个节点不把它转成 platform_device。即使你写了一大堆子节点和 compatible也不会产生设备。因此平台驱动根本没设备可匹配时第一件事就是确认这个对应的设备树节点有没有被使能。status okay才是可用状态通常默认缺省也被视为 enabled但因为 SoC dtsi 里已经写死 disabled使用某些复用节点前必须显式改成 okay。这也是为什么很多例程里总能看到类似这样的代码iomuxc { pinctrl_myled: myledgrp { fsl,pins MX6UL_PAD_GPIO1_IO08__GPIO1_IO08 0x17059; }; }; / { myled { compatible myvendor,led-demo; pinctrl-names default; pinctrl-0 pinctrl_myled; led-gpios gpio1 8 GPIO_ACTIVE_LOW; status okay; }; };节点里那个status okay并不是随手的装饰它是告诉内核这个节点可用请把我的 platform_device 创建出来。3. platform_match 一锤定音四种匹配规则的先后顺序3.1 打开 drivers/base/platform.c 看真实判据设备