
我从一块 i.MX6ULL 开发板开始接触嵌入式 Linux 驱动入门时最先搞明白的不是 GPIO 操作也不是中断子系统而是设备树里那个compatible和驱动里那个of_match_table究竟是怎么“看对眼”的。很多教程把 Platform 设备驱动模型当作一个框架讲讲了总线、设备、驱动三个概念但真正写代码、改设备树的时候还是容易懵我明明加载了驱动probe 就是不执行设备树明明加了对齐的节点驱动却找不到设备。这篇文章就围绕 i.MX6ULL 这颗 NXP Cortex-A7 核心的芯片把 Platform 设备与驱动匹配机制从原理到源码、从设备树到内核调用链彻底讲透再用一套可复现的代码和调试方法带你把整个流程走通。适合刚接触 Linux 驱动开发的新手也适合写过字符设备驱动但没系统梳理过设备模型的人参考。1. 为什么要有 Platform 这套机制从写死驱动说起1.1 硬编码驱动的维护噩梦早年写嵌入式 Linux 驱动比如 2.6 时代早期一个简单的 GPIO 点灯驱动代码里经常是直接ioremap(0x020C406C, ...)或者更粗暴地把寄存器物理地址用宏定义死。这种驱动的特点是只要换一块板子、换一个 GPIO 引脚、换一个外设接法就必须改源码重新编译甚至同一颗 SoC 上的不同板卡也要各维护一份代码。我见过一个项目里维护了七八份“几乎一样但地址名不同”的 UART 驱动改动一个 bug 需要在每个文件里分别修一遍那种痛苦经历过的人都懂。这种模式的问题本质上是把“硬件是谁、硬件在哪里”和“驱动怎么操作硬件”这两件事完全耦合了。驱动本来应该关心“怎么控制”结果连“控制谁”也被写进了驱动里。于是硬件连接一变驱动的逻辑就跟着要动。1.2 设备模型三件套总线、设备、驱动Linux 内核为了解决这个问题引入了设备模型Device Model核心抽象是总线bus、设备device、驱动driver。总线负责把设备与驱动联系起来一段设备“挂”到总线上驱动“注册”到总线上只要两者匹配总线就负责调起驱动里的初始化函数让驱动去“服务”这个设备。Platform平台总线就是这套模型里最特殊的一条虚拟总线。它专门用于那些不依赖 PCI、USB、I2C 这类物理总线协议的设备。SoC 内部的大多数控制器——GPIO、UART、I2C、SPI、DMA、以太网 MAC——都属于这一类它们“挂”在 SoC 内部总线上没有标准的热插拔和枚举机制必须由系统主动告知设备的存在。于是内核用platform_device表示一个平台设备用platform_driver表示一个平台驱动两者都注册到 platform_bus_type 这条虚拟总线上再由总线核心的匹配机制完成配对。1.3 设备树时代硬件拓扑由 DTS 描述到了设备树Device Tree时代硬件描述从 C 代码里彻底剥离出去。板卡上接了哪些外设、外设挂在哪个控制器下、寄存器地址、中断号、引脚复用关系全部用 DTS/DTB 描述。内核启动时会解析 DTB把设备树里的节点逐个变成platform_device或者挂在相应控制器总线下的i2c_client、spi_device等注册到对应的总线上。这个“设备树节点变成 platform_device”的动作正是不少新手没想明白的关键点设备树里写了一个 node不代表你写一个同名驱动的 name它俩就自动匹配上了。设备树节点的compatible属性才是驱动和设备建立联系的“接头暗号”。所以我把匹配机制单独拎出来讲因为它直接决定了你的驱动能不能被“自动”拉起也直接决定了 probe 函数会不会执行。2. 匹配是怎么发生的platform_match 的完整逻辑2.1 compatible 属性的作用设备树里每个节点都有一个compatible属性用来表示该设备兼容的型号比如led { compatible myvendor,board-led; reg 0x020C406C 0x4; };compatible的取值建议遵循厂商,型号的格式厂商域名倒序或者缩写都行关键在于全局唯一性。比如fsl,imx6ull-uart表示这是 NXP飞思卡尔i.MX6ULL 的 UART。之所以强调格式是因为内核中可能存在大量compatible如果写得太泛比如只写一个led很容易跟内核里其他驱动冲突也可能被别的不相关驱动误匹配。驱动侧要声明自己能驱动哪些设备靠的是struct of_device_id数组static const struct of_device_id led_of_match[] { { .compatible myvendor,board-led }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, led_of_match);这个数组的.compatible字符串就是用来和设备树节点的compatible属性逐条比较的。2.2 四种匹配方式与优先级内核的匹配入口是platform_match函数代码在drivers/base/platform.c里。它按照一定的优先级依次尝试多种匹配方式完整逻辑可以整理成一张表匹配方式触发条件比较内容1. OF 风格匹配设备和驱动都支持设备树设备节点compatible与驱动of_match_table里的.compatible比较2. ACPI 匹配启用了 ACPI 的 x86/ARM64 服务器平台设备ACPI_HANDLE与驱动acpi_match_table比较3. id_table 匹配驱动指定了platform_device_id数组设备name与id_table中的.name比较4. 纯 name 匹配以上都不满足设备name与驱动driver.name直接字符串比较在实际的 i.MX6ULL 项目中走的基本都是第 1 种 OF 匹配。内核源码platform_match开头就分了一个大方向static 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); /* 1. 基于设备树 compatible 的匹配 */ if (of_driver_match_device(dev, drv)) return 1; /* 2. ACPI 匹配 */ if (acpi_driver_match_device(dev, drv)) return 1; /* 3. 基于 id_table 的匹配 */ if (pdrv-id_table) return platform_match_id(pdev, pdrv-id_table) ! NULL; /* 4. 基于 driver name 与 device name 的直接匹配 */ return (strcmp(pdev-name, drv-name) 0); }等一下这里要特别注意一点of_driver_match_device不是“只要双方都支持设备树就一定匹配”它的内部比较逻辑在of_match_device最终落到对每个of_device_id条目执行of_device_is_compatible也就是把设备树节点的compatible属性里所有字符串逐一与驱动of_device_id的.compatible做比对任何一个相等就返回匹配。一个设备树节点可以写多个 compatible 字符串越靠前的表示越精确的型号越靠后的表示兼容的通用型号compatible myvendor,board-led-v2, myvendor,board-led;驱动里的of_match_table也可以注册多个条目。匹配时任意一项相等就算成功。2.3 为什么优先用 compatible可能有同学会问既然也可以用platform_device_id或 name 匹配那为什么现在主流做法都是 compatible我的理解是compatible 对应的是“设备是什么”而 name 对应的是“设备叫什么”。设备树是描述硬件的硬件工程师换了一个厂商的 PHY 芯片compatible 就跟着换驱动可以分别注册对不同型号的支持而 name 更像是软件层面的标识如果依赖 name 匹配相当于又把硬件信息写回到驱动代码里回到了硬编码的老路。而且设备树方式还天然支持多板卡适配。同一颗 i.MX6ULLA 板卡接了 A 型号的音频芯片B 板卡接了 B 型号的音频芯片设备树各自声明各自的 compatible驱动用一个of_match_table把两种芯片都列进去一份驱动跑两块板子。如果用 name 匹配就得注册两个不同名字的驱动功能相同代码却要复制一份维护成本明显更高。现代内核社区基本形成一种默认约定支持设备树的平台ARM、RISC-V、ARM64 等新驱动里的匹配都优先通过of_match_table完成这也是我在项目里沿用至今的做法。3. 在 i.MX6ULL 上写一个 Platform 驱动的完整流程3.1 驱动侧定义 of_device_id 并注册下面我用一个精简但完整的例子演示在 i.MX6ULL 上编写一个 platform 驱动。工程上建议按“设备树节点 platform_driver 框架 具体操作代码”拆分文件但为了文章便于阅读我把核心代码集中到一起。#include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/of_device.h static int demo_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct resource *res; void __iomem *base; int irq; dev_info(dev, probe called\n); /* 1. 获取设备树节点的 compatible */ dev_info(dev, compatible: %s\n, of_device_is_compatible(dev-of_node, myvendor,board-demo) ? match : not match); /* 2. 读取 reg 属性获得物理地址区间 */ res platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, failed to get mem resource\n); return -ENXIO; } dev_info(dev, reg physical: %pa, size: %pa\n, res-start, res-end); /* 3. 映射寄存器地址之后就可以使用 readl/writel 操作 */ base devm_ioremap_resource(dev, res); if (IS_ERR(base)) { dev_err(dev, failed to ioremap\n); return PTR_ERR(base); } /* 4. 获取中断号如果设备树里有 interrupt 属性 */ irq platform_get_irq(pdev, 0); if (irq 0) dev_info(dev, got irq: %d\n, irq); return 0; } static void demo_remove(struct platform_device *pdev) { struct device *dev pdev-dev; dev_info(dev, remove called\n); } static const struct of_device_id demo_of_match[] { { .compatible myvendor,board-demo }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, demo_of_match); static struct platform_driver demo_driver { .probe demo_probe, .remove demo_remove, .driver { .name demo-platform, .of_match_table demo_of_match, }, }; module_platform_driver(demo_driver); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(i.MX6ULL platform driver demo); MODULE_ALIAS(platform:demo-platform);这段代码的重点module_platform_driver是个宏展开后等价于module_init(platform_driver_register)module_exit(platform_driver_unregister)省得手写一堆样板代码。它内部会把demo_driver.driver注册到 platform bus 上。demo_driver.driver.name是驱动在 sysfs 里的名字也是非设备树情况下 name 匹配的依据。如果走设备树 compatible 匹配这个名字主要起标识作用。.of_match_table demo_of_match是设备树匹配的核心这里的compatible必须和设备树节点完全一致。3.2 设备侧在设备树中添加节点i.MX6ULL 的设备树源文件通常位于内核源码目录arch/arm/boot/dts/imx6ull.dtsiSoC 级和对应的板级 dts比如imx6ull-myboard.dts。如果仅仅是测试驱动建议在板级 dts 的根节点下添加一个简单子节点不必修改 SoC 级文件方便你用多块不同板子反复试验。/ { demo_device { compatible myvendor,board-demo; reg 0x020C406C 0x4; interrupts GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH; }; };注意我这里的寄存器地址和中断号只是演示。实际使用要替换成你在 i.MX6ULL 参考手册里查到的正确外设地址并且需要确保相应的时钟、引脚复用已经由其他驱动或 bootloader 配置好。否则即使驱动匹配成功访问寄存器也可能达不到预期效果。修改完 DTS 后重新编译设备树make dtbs如果用的是 U-Boot通常把编译出来的imx6ull-*.dtb放到 SD 卡 / EMMC 的 boot 分区重启即可生效。3.3 probe 执行的全过程从注册到进入 probe 的调用链驱动编译并加载后正常情况下内核日志会输出demo-platform demo_device: probe called demo-platform demo_device: compatible: match demo-platform demo_device: reg physical: 0x20c406c, size: 0x4这段输出说明驱动和设备已经成功配对probe 被调用。整个过程可以拆成三个阶段第一阶段系统启动或驱动加载时platform_driver_register把demo_driver注册到 platform bus。总线核心会对该总线上已有的每一个设备调用匹配函数如果有设备匹配成功就立即执行 probe。第二阶段设备树在启动早期被解析of_platform_default_populate_init会遍历设备树根节点下的所有节点为compatible属性不为空的节点创建platform_device并注册到平台总线。这一过程甚至早于驱动加载。第三阶段当第二阶段的平台设备挂在总线上而第一阶段注册驱动时触发匹配总线核心会调用driver_probe_device最终流转到platform_drv_probe再由它调用你写的demo_probe。值得强调的是不管谁先谁后只要设备和驱动都到了同一条总线上且匹配成功probe 就会被触发。这也是为什么你把 platform 驱动编译成模块在系统起来后再insmod依然能正常执行 probe 的原因。如果 probe 没有执行要么是设备没注册成功要么是匹配条件不满足要么是驱动注册本身失败了我们到第 5 节详细排查。4. 源码级分析从注册到 probe 的内核调用链4.1 驱动注册入口背后的机制platform_driver_register 这个 API 是所有平台驱动都绕不开的入口。看一下内核源码它的实现脉络int platform_driver_register(struct platform_driver *drv) { drv-driver.bus platform_bus_type; drv-driver.probe platform_drv_probe; drv-driver.remove platform_drv_remove; ... return driver_register(drv-driver); }这里最关键的一步是设置了drv-driver.bus platform_bus_type它把通用设备驱动模型挂到了平台总线上。driver_register内部做的事情很多把驱动加入总线的驱动链表、创建 sysfs 属性、然后调用bus_add_driver。其中driver_attach会遍历该总线上的所有设备逐个执行__driver_attach。__driver_attach的核心逻辑很直观static int __driver_attach(struct device *dev, void *data) { struct device_driver *drv data; ... if (!driver_match_device(drv, dev)) return 0; ... driver_probe_device(drv, dev); ... }翻译过来就是拿当前驱动去匹配总线上的每一个设备匹配不到就不管匹配到了就执行 probe 流程。4.2 device 注册入口与反向遍历设备侧的注册函数是platform_device_register或者设备树场景下的of_platform_device_create。它把platform_device注册到内核设备模型时最终会调用device_add里面同样存在一个反向的遍历过程总线会拿着新设备去和总线上已有的每个驱动做匹配bus_probe_device-device_initial_probe-__device_attach。__device_attach的逻辑可以简单概括为先看设备是否已经有 driver 绑定dev-driver不为空说明被绑定过如果没有则遍历总线上每个驱动调用driver_match_device匹配成功则调用driver_probe_device。所以无论注册顺序如何只要设备和驱动都上了总线总有某一方触发时会把另一方找到。这个“双向奔赴”的机制让驱动和设备谁先谁后都不重要也是 Linux 设备模型比传统“先注册设备再注册驱动”更健壮的原因。4.3 设备树节点是怎么变成 platform_device 的设备树节点本身不是内核的设备对象。内核在启动过程中会通过unflatten_device_tree把 DTB 解析成树状的device_node结构但真正的struct device还远没有创建。创建动作发生在of_platform_bus_create和of_platform_device_create_pdata这一串调用中。内核会遍历设备树里所有带compatible属性的节点为每个节点分配一个platform_device并把节点指针of_node赋值给它。这个of_node是后面匹配机制中读取compatible属性的关键驱动里dev-of_node拿到的就是这个。设备树里有些节点是不会变成 platform_device 的比如挂在一个 I2C 控制器节点下的子节点它会被 I2C 核心解析成i2c_client挂在 SPI 控制器下的会被解析成spi_device。它们的驱动匹配走的是各自总线的匹配逻辑和 platform bus 无关。这一点在阅读 dts 的时候要有意识地区分判断一个节点最终挂在什么总线上要看它父节点的compatible是什么而不是凭感觉。以 i.MX6ULL 为例i2c1节点下写一个compatible myvendor,some-sensor的子节点这个 sensor 最终不是 platform_device而是 i2c_client要用 i2c 驱动框架来写平台驱动是匹配不到的。这也是初学阶段经常“明明设备树没错、驱动却不 probe”的原因之一。5. 匹配不上的实战排查清单5.1 我最常遇到的四种失败场景场景一compatible 字符串不一致。设备树里写成myvendor,board-demo驱动of_match_table里写的是myvendor,board_demo一个是短横线一个是下划线肉眼不容易看出来但字符串比较是严格逐字节比对一个字符差都不行。这个问题的隐蔽之处是设备树交叉编译时不报错加载驱动也不报错驱动模块状态还是live就是 probe 不执行。解决方法是在驱动里打印出设备树节点的compatible值或者用下面的方法从用户态查看。场景二设备树节点没有生成 platform_device。如果设备树节点的父节点是一个未启用的控制器比如 I2C 控制器设置成status disabled那它下面的子节点都不会被枚举自然没有设备挂到总线上。这时候就算 compatible 完全一致也是白搭。排查方法是看/sys/bus/platform/devices/下有没有对应的目录或者在驱动的probe里加打印但在这种情况下 probe 根本不会被调用。场景三驱动模块本身没注册成功。如果platform_driver_register返回错误内核会打印类似driver_register failed的日志。常见的注册失败原因驱动名字冲突比如驱动的driver.name和某个已注册驱动重名或者of_match_table没有正确初始化导致内核编译报错被忽略后出现未定义符号。建议在模块加载后用lsmod看一下模块状态再用dmesg查驱动注册是否成功。场景四设备树里漏了compatible或者把它写成了device_type。device_type是另一种传统属性大部分现代 ARM 设备树中已经很少使用。如果只用device_type demo而不写compatible节点不一定被解析成 platform_device。这一点在新手自己写 DTS 测试节点时尤其常见。规范的写法始终是提供compatible属性。5.2 用 sysfs 和 debugfs 快速定位问题当驱动不 probe 的时候我建议通过 sysfs 梳理设备与驱动各自的存在状态而不是盲改代码。先看设备是否生成ls /sys/bus/platform/devices/demo_device如果有目录说明设备的 platform_device 已经创建没有则问题出在设备树解析阶段。再查看这个设备的 ueventcat /sys/bus/platform/devices/demo_device/uevent内核会输出类似OF_NAMEdemo_device OF_FULLNAME/demo_device OF_COMPATIBLE_N1 OF_COMPATIBLE_0myvendor,board-demo MODALIASof:Ndemo_deviceTNULLCmyvendor,board-demoMODALIAS里的Cmyvendor,board-demo就是用来和设备驱动匹配的标识。用户态的 udev 和内核的设备匹配都依赖这个字符串。看到这行至少能确认设备侧的compatible是正确的。再看驱动是否注册ls /sys/bus/platform/drivers/demo-platform/如果目录存在且驱动成功绑定设备则在这个目录下会有一个指向设备的符号链接比如demo_device - ../../../devices/platform/demo_device如果驱动目录存在但没有设备链接说明设备在总线上但匹配没成功。此时可以手动确认一下驱动的 modaliascat /sys/bus/platform/drivers/demo-platform/module/uevent或者比较/sys/bus/platform/drivers/demo-platform/of_match_table在较新内核中是否能显示出来。不同内核版本展示方式略有差异最直接的方法还是用设备树当前节点的 compatible 和驱动源码里的 of_device_id 逐字符比对。某些内核还开启了设备模型 trace 功能可以用 perf/trace 去跟踪driver_probe_device、platform_match的调用情况但对 i.MX6ULL 这种资源有限的嵌入式板子来说用 sysfs 加 dmesg 基本已经够用。如果确认还需要更底层的调试可以打开内核 trace 事件echo 1 /sys/kernel/debug/tracing/events/base/dev/enable cat /sys/kernel/debug/tracing/trace看到类似driver_probe_device: demo_device ... demo-platform的记录就说明匹配已经进入 probe 阶段。注意不是所有内核都默认挂载了 debugfs需要先执行mount -t debugfs none /sys/kernel/debug。5.3 从 dmesg 捕获关键日志在驱动开发初期我习惯人为制造几次失败来验证自己的排查思路。比如故意把设备树里的 compatible 改错一次加载驱动后 dmesg 的输出通常不会直接告诉你“匹配失败”因为匹配失败本身是正常流程不算错误。这时就要靠上面的 sysfs 方法确认。如果驱动注册成功了但 probe 没执行可以这样触发一次手动绑定来验证驱动本身是否正常# 先解绑如果已绑定 echo demo_device /sys/bus/platform/drivers/demo-platform/unbind # 再手动绑定 echo demo_device /sys/bus/platform/drivers/demo-platform/bind如果手动绑定成功且 probe 被调用说明驱动的 probe 逻辑没问题问题大概率出在自动匹配阶段。如果手动绑定也失败就要看 probe 内部是否有返回值错误比如platform_get_resource拿不到资源、devm_ioremap_resource地址冲突等这类错误会在 dmesg 里留下具体的failed to get mem resource之类的记录。在 dmesg 中还可以观察到 i.MX6ULL 启动阶段的总线 populate 过程of_platform_default_populate_init: no ranges; skipping /soc刚看到这行时我还以为设备树解析出错后来才意识到这是正常的提示表示某个总线节点没有声明地址映射范围不参与统一遍历。对应的节点的子设备会由其他机制注册不一定走 platform_populate。所以看到这类日志不用过度紧张关键是结合自己的设备节点是否存在来判断。6. i.MX6ULL 设备树平台总线关于兼容和实例化的几个实际经验6.1 不要迷信“改了 DTS 立刻生效”不少新手改完设备树后只在 U-Boot 里执行一次saveenv或者覆盖了 DTB 文件就以为万事大吉。实际上 i.MX6ULL 的 boot 流程中DTS 的加载可能来自多个位置U-Boot 环境变量fdt_file指定的 DTB、SD 卡 boot 分区里的imx6ull-*.dtb、EMMC 某个分区。如果内核实际加载的和你修改的不是同一个 dtb改了半天也看不到效果。我在调试时习惯先在 U-Boot 命令行里确认printenv fdt_file fatls mmc 1:1再用fdt addr和fdt list查看运行时设备树信息确认我要的节点是否已经存在于内存中的 DTB。必要时直接在 U-Boot 阶段用fdt set临时修改 compatiblenode 来验证匹配逻辑这样可以避免“改文件 重启 发现没生效”的低效循环。6.2 driver name 与 device name 容易混淆struct platform_driver的driver.name字段我通常命名为demo-platform而设备树节点 name 是demo_device。在非设备树的传统平台设备注册方式下如果platform_device.name等于demo-platform那 name 匹配也能成功但在设备树场景里我们并不依赖 name 匹配而是靠 compatible。所以这两个名字不同是正常的不要看到不同就以为自己搞错了。可如果设备树场景下你把driver.name改成与某个platform_device.name相同也会发生一种容易被忽略的效果匹配代码在 OF 匹配失败后会继续尝试 name 匹配意外地让一个“本来不该匹配”的设备被绑定驱动。所以我的习惯是给driver.name起一个唯一的、不跟任何设备重名的字符串避免误配对。6.3 利用 of_match_table 支持多型号复用在 i.MX6ULL 的项目里我们经常会做硬件版本迭代。比如第一版板子的 LED 控制器寄存器偏移跟第二版不一样但控制逻辑相同。传统做法是复制驱动改地址维护两份源码。有了设备树 platform 机制后可以让一份驱动注册多个 compatiblestatic const struct of_device_id led_of_match[] { { .compatible myvendor,board-led-v1, .data v1_cfg }, { .compatible myvendor,board-led-v2, .data v2_cfg }, { /* sentinel */ } };在 probe 里用of_device_get_match_data(dev)取出对应的配置结构体再按版本差异做寄存器偏移量设置。这样硬件改版时往往只需要改设备树驱动源码不用动固件升级也只需更新 dtb 文件。这套思路在维护多个硬件版本时节省的时间非常可观。更进一步如果只靠 dtb 切换就能适配新硬件驱动验证周期能大幅缩短。6.4 module_platform_driver 的遮挡效应很多教材和内核源码都用module_platform_driver宏它会在模块加载时自动调用 platform_driver_register。我遇到过一种情况驱动作弊性地在 init 函数里手动注册了一个miscdevice或者字符设备而忘记调用platform_driver_register导致驱动加载“成功”了字符设备也创建了但 probe 永远没有执行。原因是module_platform_driver确实包含了 register 逻辑但如果你在 init 函数里做了其他事情之后返回错误platform_driver_register 就不再执行了。排查办法是在模块加载的瞬间就看 dmesg 里对应驱动的注册流程modprobe demo_driver dmesg | tail -n 30如果内核打印出platform demo-platform: driver registered之类的信息说明驱动注册没问题如果什么也没有就需要检查是不是用错了模块初始化宏或者驱动源码里存在提前 return 的逻辑。6.5 在 probe 里访问设备树属性要判空这里还有一个新手写驱动容易踩的坑在 probe 中直接用of_property_read_u32(np, reg, val)读 reg 属性但设备树节点 reg 属性并不等价于给驱动准备的某个自定义属性。其实 reg 属于标准资源属性更适合用platform_get_resource来读取。如果只是为了驱动自定义配置设备树里应当增加自定义属性比如demo_device { compatible myvendor,board-demo; myvendor,delay-ms 100; };驱动读取时u32 delay; if (of_property_read_u32(pdev-dev.of_node, myvendor,delay-ms, delay)) { dev_warn(dev, use default delay\n); delay 20; }任何of_property_系列的读取函数都可能因为节点里没有对应属性而返回错误码代码里必须判断返回值并给出默认值。这是我从多次硬件调试中总结出来的经验宁可在驱动里多写几条防御逻辑也不要默认设备树一定会写得完整无缺。7. 一次实战调试记录probe 不进来的完整排查过程最后分享一次比较典型的调试经历。当时我在 i.MX6ULL 上移植一个 LCD 背光驱动把驱动文件编译成模块后 insmoddmesg 只看到模块加载成功probe 始终没有被调用。设备树里我已经写了节点compatible 也是一字不差地从驱动源码复制过去的。第一步我检查/sys/bus/platform/devices/下是否有背光节点对应的设备目录结果没有。也就是说设备树解析阶段就没生成 device问题根本不在匹配而在设备注册之前。第二步我查看设备树源文件发现背光节点被放在了某个 display 控制器节点下面而这个控制器节点的 status 是disabled。由于父节点没有启用整个子树不会被of_platform_populate处理背光子节点自然也不会生成 platform_device。把控制器节点打开后背光节点生成了probe 立刻触发了。第三步probe 触发后又暴露了第二个问题platform_get_resource拿到的 IRQ 号无效因为我把中断号写错了写到别的外设的中断号上去了。这个错误在设备树编译时不会报因为 interrupts 属性在语法上没有任何问题只有运行到 request_irq 时才发现中断已被占用或者超出控制器范围。那次排查让我把之前的理论全部串了起来设备树节点生成 platform_device 是有条件的compatible 匹配只是最后一个环节前面还有节点可达、父节点使能、控制器已初始化等多道关卡。每次遇到 probe 不执行我都从设备是否生成开始排查而不是一股脑在驱动代码里找问题。后来我在新板子上调试驱动时总结出一套固定的 minidump 流程启动后用ls /sys/bus/platform/devices/ | grep demo确认设备存在用grep -r demo /sys/bus/platform/devices/demo_device/uevent检查 compatible用dmesg | grep demo看驱动注册与 probe 日志最后用 sysfs 的 bind/unbind 手动触发。这套流程在全志、Rockchip 其他平台上也通用核心就是先把“设备存在性”和“驱动存在性”确认清楚再谈匹配。匹配机制本身并不复杂复杂的是整个设备模型链路里的各个环节都可能出错。如果你现在也卡在 probe 不执行的问题上建议按同样的顺序查一遍把第 5 节那张排查表过一轮大概率能定位到问题。驱动开发这东西很多时候不是不够聪明而是没掌握一套高效的排错顺序顺序对了问题往往三分钟就暴露出来了。