恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Linux Platform总线与设备树匹配机制及i.MX6ULL驱动实践

  • 首页
  • 资讯中心
  • /
  • Linux Platform总线与设备树匹配机制及i.MX6ULL驱动实践

相关资讯

帕玛强尼RM35-03机芯改装:三文鱼配色与碳纤维材质技术解析 2026/9/4 15:48:16
如何快速完成微信聊天记录导出:留痕完整指南 2026/9/4 15:48:16
MATLAB与STK联合仿真:航天系统自动化分析与参数化研究 2026/9/4 15:48:16

最新资讯

Unity实现罗斯科风格城市建造游戏:从着色器到建造系统全流程
Cursor AI 高阶对话技巧:结构化提示与上下文构建实战指南
电源适配器定制化选型——从参数匹配到全场景适配的落地指南
PyTorch训练代码实战:从数据加载到模型保存的完整指南
基于YOLO的NSFW内容检测:从模型训练到工程部署全流程解析
电力巡检异物检测:从168张VOC数据集到YOLOv8模型实战

今日推荐

爬虫防护实操:出海网站拦截恶意采集、垃圾爬虫、无效刷量,CDN 精准防护落地指南
STM32H743 SPI从机DMA双缓冲通信实战
CPU开盖降温教程:20元成本让温度直降30度的原理与实践

本周热门

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析
数字电路时序基石:深入理解建立时间与保持时间
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

Linux Platform总线与设备树匹配机制及i.MX6ULL驱动实践

发布时间:2026/9/4 15:48:16
Linux Platform总线与设备树匹配机制及i.MX6ULL驱动实践 搞嵌入式Linux驱动开发的人早晚都会遇到Platform这套机制。我自己第一次正儿八经碰i.MX6ULL时就对着设备树里几百个节点发懵明明外设都在芯片内部代码里也没看到谁去“枚举”硬件怎么驱动一加载就自动找到设备了后来把Platform总线和匹配流程翻了一遍才真正把设备树、驱动模型、probe调用之间的关系串起来。这篇文章就围绕i.MX6ULL把Platform设备与驱动匹配机制从设计缘由到内核源码再到手写驱动完整拆一遍适合刚入门Linux驱动、或者已经在写驱动但总感觉“匹配”这步像黑盒的读者。1. Platform总线的来源与设计思路1.1 为什么SoC内部外设需要一条“虚拟总线”先想一个问题UART、I2C、SPI、USB这些控制器在芯片上都有对应的物理总线协议外设挂在总线上能被自动扫描和枚举。但SoC内部集成的GPIO、DMA、LCD控制器、以太网MAC这些设备它们并不挂在某个可枚举的物理总线上不存在“设备插入”这种事件也没有硬件ID寄存器能被总线控制器轮询。那驱动怎么找到设备内核为此引入一条虚拟总线叫Platform总线也有人叫“平台总线”。它专门用来管理那些直接集成在SoC上、地址映射固定的设备。这类设备在代码中统一叫platform_device对应的驱动叫platform_driver。命名虽然叫platform但它不是某个具体硬件而是软件抽象出来的“挂载点”把所有非可枚举设备统一纳入Linux设备模型管理。i.MX6ULL是NXP基于Cortex-A7内核设计的应用处理器片内集成了大量这样的控制器。你在设备树里看到的gpio、uart、i2c、epit、adc等节点最终在内核启动阶段都会转换成platform_device挂在Platform总线上。驱动侧的匹配、probe、remove则统一由Platform总线完成调度。1.2 从device、driver、bus三层模型看PlatformLinux设备模型的核心逻辑可以简化成三层device描述“有什么硬件”driver描述“能驱动什么硬件”bus负责把两者牵线。Platform总线就是bus的具体实例之一。device(设备树节点/平台设备) ----- platform_bus_type ----- driver(驱动) | | └---------匹配成功后调用probe()在内核源码里Platform总线定义在drivers/base/platform.cbus_type结构体叫platform_bus_type。它最重要的一个回调就是.match函数也就是整篇文章的主角——设备与驱动的匹配逻辑。当内核每次注册一个platform_driver或者注册一个platform_device时都会遍历总线上另一侧的设备/驱动调用.match检查是否配对。配对成功内核立即调用驱动的.probe方法驱动正式开始接管硬件。理解了这个结构就明白为什么写驱动时不需要手动找设备只要设备树里有对应节点驱动匹配成功后probe必然被调用。你所有初始化代码都写在probe里就行。1.3 i.MX6ULL上典型的Platform设备例子i.MX6ULL内部外设数量很多下面列几个我实际接触过的典型例子外设设备树节点路径示例驱动源码位置GPIO控制器soc/gpio0209c000drivers/gpio/gpio-mxc.cUART串口soc/serial02020000drivers/tty/serial/imx.cI2C控制器soc/i2c021a0000drivers/i2c/busses/i2c-imx.cLCD控制器soc/lcdif021c8000drivers/video/fbdev/mxsfb.c看门狗soc/wdog020bc000drivers/watchdog/imx2_wdt.c这些驱动文件里清一色都声明了platform_driver和of_device_id匹配表。例如imx串口驱动里会有一张表列出所有兼容的芯片型号字符串I2C驱动也类似。内核启动时设备树里的serial节点会被解析为platform_device然后Platform总线在注册驱动时用这张表比对节点compatible属性匹配上就probe。整个“外设无物理总线可枚举”的问题就这样被Platform总线解决了。2. 匹配机制内核源码拆解2.1 platform_match的执行顺序与核心逻辑设备与驱动匹配的核心函数是platform_match位于drivers/base/platform.c。不同内核版本细节略有差异但主干逻辑基本一致。我整理过一份简化流程对应Linux 5.4左右的内核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. 先尝试驱动自带的id_table */ if (pdrv-id_table) if (platform_match_id(pdev, pdrv-id_table) ! NULL) return 1; /* 2. 检查驱动是否强制覆盖设备绑定 */ if (pdev-driver_override) return !strcmp(pdev-driver_override, drv-name); /* 3. ACPI匹配x86/ARM服务器场景下使用 */ if (acpi_driver_match_device(dev, drv)) return 1; /* 4. 设备树匹配of_device_id表比对 */ if (of_driver_match_device(dev, drv)) return 1; /* 5. 最原始方式设备名与驱动名直接比较 */ return (strcmp(pdev-name, drv-name) 0); }注意每一步成功都会立刻返回1匹配流程立即结束。这套顺序在不同内核版本稍有调整但思路一致。对我们写设备树驱动的场景第4步才是重点。第5步是早年间没有设备树时用的老办法——platform_device的名字和platform_driver-driver.name完全一致现在一些杂项设备或测试驱动还能看到这种写法。2.2 设备树节点与of_device_id的匹配细节设备树匹配过程中内核真正执行的是of_driver_match_device它会遍历设备树节点的compatible属性列表和驱动of_device_id表中的compatible字符串。设备树里一个节点可以写多个compatible比如led_test { compatible myvendor,led-test, myvendor,generic-led; ... };驱动侧of_device_id表这样声明static const struct of_device_id led_test_of_match[] { { .compatible myvendor,led-test }, { .compatible myvendor,generic-led }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, led_test_of_match);匹配时内核把设备树节点里每个compatible字符串依次与驱动of_device_id表里的每个compatible做字符串比较完全一致才算通过。也就是说设备树写“A,B”驱动表里必须有一个完全相等的“A,B”少个逗号、多个空格、大小写不一致全都不行。补充一个细节of_device_id结构里还有.name和.type字段可用来匹配节点name属性和device_type属性但实际设备树中很少有节点设置device_type所以绝大多数驱动只填.compatible。还有很多人问MODULE_DEVICE_TABLE有什么用它主要把这张表导出到模块的modinfo信息里让insmod/modprobe能识别这个模块支持哪些硬件同时帮助udev在热插拔时自动加载模块。虽然Platform设备不是热插拔的但养成写这个宏的习惯是对的。2.3 id_table老方法 vs 新设备树方法在没有大规模使用设备树的时代或者使用板级文件(arch/arm/mach-xxx)注册设备时经常用platform_device_id表来匹配设备。这种表主要匹配的是platform_device的name字段static const struct platform_device_id led_test_id_table[] { { .name led-test, .driver_data 0 }, { } }; MODULE_DEVICE_TABLE(platform, led_test_id_table);假设板级文件里注册了一个名字叫“led-test”的platform_device或者设备树节点没有compatible只有节点名那么驱动加载时platform_match_id就会命中probe照常执行。在老内核和部分x86平台驱动里这种方式依然常见。两者对比如下匹配依据设备树方式platform_device_id方式设备侧信息节点compatible属性platform_device.name驱动侧信息of_device_id.compatibleid_table[].name推荐场景所有设备树平台老平台、无设备树场景携带自定义数据通过driver_data也可id_table[].driver_data查找函数of_driver_match_deviceplatform_match_idi.MX6ULL这种现代BSP基本都走设备树所以建议直接写of_device_id表。但遇到一些复用老驱动的场景比如某些从3.x内核迁移过来的老代码可能还在用id_table多了解没坏处。2.4 匹配优先级、driver_override与手动绑定实际调试时偶尔会遇到一个platform_device同时满足多种匹配条件的情况。比如设备树节点里的name刚好和设备驱动名一致但compatible不一致谁先谁后就是顺序问题。上面源码注释已经给出优先级一般先id_table再ACPI再设备树最后是名字比较。不过大厂BSP可能会有定制查源码最准确。除了自动匹配内核也支持手动指定某设备强制使用某驱动这就是driver_override机制。它通常通过sysfs接口操作echo led_test_drv /sys/bus/platform/devices/led_test/driver_override echo led_test /sys/bus/platform/drivers_probe操作完后总线会忽略其他匹配结果强制把led_test设备绑定到led_test_drv驱动。这个机制在驱动调试初期非常有用尤其是设备树写错了、不想反复改dtb重启时可以临时手动强制绑定验证驱动本身是否正常。3. 在i.MX6ULL上从零写一个Platform驱动3.1 准备硬件与编译环境手头有一块i.MX6ULL开发板即可。大多数板子会用NXP官方BSP或正点原子/野火适配的内核版本一般在4.1.15到5.4之间。开发环境推荐Ubuntu 18.04或20.04虚拟机安装交叉编译工具链。以arm-linux-gnueabihf-为例确认工具链存在arm-linux-gnueabihf-gcc -v还需要准备对应的内核源码树并且已经编译过。编译外部模块时内核会用源码目录里的Makefile和生成的头文件所以先完整编译一次内核可以避免很多奇怪问题。源码路径假设放在~/kernel/linux-imx后面所有KERNELDIR都指向这里。我习惯用板子自带的内核源码版本而不是随便下载主线因为NXP BSP会在arch/arm/mach-imx、include/dt-bindings等目录加入很多官方补丁直接影响设备树宏定义和pinctrl配置。用错版本很容易在编译设备树时报一堆找不到宏的错误。3.2 第一步添加设备树节点以控制i.MX6ULL一个GPIO点灯为例写一个Platform测试设备。先看原理图假设LED接在GPIO1_IO04上低电平点亮。在imx6ull对应的dts里找一个合适的位置添加节点比如在根节点下添加/ { led_test { compatible myvendor,led-test; pinctrl-names default; pinctrl-0 pinctrl_led_test; led-gpio gpio1 4 GPIO_ACTIVE_LOW; status okay; }; };其中的pinctrl_led_test需要在iomuxc节点下补充引脚复用配置参照IMX6ULL的pinctrl格式iomuxc { pinctrl_led_test: led-testgrp { fsl,pins MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x10b0 ; }; };MX6UL_PAD_GPIO1_IO04__GPIO1_IO04这个宏定义在arch/arm/boot/dts/imx6ul-pinfunc.h中0x10b0是引脚配置寄存器值包括上下拉、驱动强度、速率等配置。低有效用GPIO_ACTIVE_LOW这是从include/dt-bindings/gpio/gpio.h里来的宏。添加完重新编译设备树make dtbs把生成的imx6ull-xxx.dtb拷贝到开发板替换原来的dtb后重启。启动后在/proc/device-tree或/sys/firmware/devicetree/base下能看到led_test节点说明设备树已经生效。3.3 第二步写Platform驱动框架代码驱动代码放到一个独立目录比如~/led_test_drv/创建led_test.c。下面是一个最简但完整的platform驱动包含匹配表、probe、remove和基本的文件操作接口方便验证匹配流程。#include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/of_gpio.h #include linux/gpio/consumer.h #include linux/fs.h #include linux/uaccess.h #include linux/miscdevice.h static struct gpio_desc *led_gpiod; /* 设备树匹配表 */ static const struct of_device_id led_test_of_match[] { { .compatible myvendor,led-test }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, led_test_of_match); static int led_test_open(struct inode *inode, struct file *filp) { return 0; } static long led_test_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { return 0; } static const struct file_operations led_test_fops { .owner THIS_MODULE, .open led_test_open, .unlocked_ioctl led_test_ioctl, }; static struct miscdevice led_test_miscdev { .minor MISC_DYNAMIC_MINOR, .name led_test, .fops led_test_fops, }; static int led_test_probe(struct platform_device *pdev) { struct device *dev pdev-dev; int ret; dev_info(dev, led_test probe success\n); /* 从设备树获取GPIO使用新的gpiod API */ led_gpiod devm_gpiod_get(dev, led, GPIOD_OUT_LOW); if (IS_ERR(led_gpiod)) { ret PTR_ERR(led_gpiod); dev_err(dev, failed to get led gpio: %d\n, ret); return ret; } ret misc_register(led_test_miscdev); if (ret) { dev_err(dev, failed to register misc device\n); return ret; } dev_info(dev, led_test driver ready\n); return 0; } static void led_test_remove(struct platform_device *pdev) { misc_deregister(led_test_miscdev); if (!IS_ERR_OR_NULL(led_gpiod)) gpiod_set_value(led_gpiod, 0); dev_info(pdev-dev, led_test driver removed\n); } static struct platform_driver led_test_driver { .probe led_test_probe, .remove led_test_remove, .driver { .name led_test, .of_match_table led_test_of_match, }, }; module_platform_driver(led_test_driver); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(i.MX6ULL platform device match test driver);注意这里用devm_gpiod_get(dev, led, ...)去获取设备树里led-gpio属性对应的GPIO。函数会从设备树属性名“led-gpio”自动推断出con_id是“led”最终拿到对应的gpio_desc。这套gpiod API比老的gpio_request更推荐它内部配合设备树和pinctrl子系统完成引脚申请和配置而且devm前缀意味着资源随设备自动释放probe失败或驱动卸载时不用手动清理省掉不少麻烦。3.4 第三步编写Makefile并编译加载同目录创建Makefile内容如下KERNELDIR : /home/user/linux-imx ARCH ? arm CROSS_COMPILE ? arm-linux-gnueabihf- obj-m : led_test.o all: $(MAKE) -C $(KERNELDIR) M$(PWD) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) modules clean: $(MAKE) -C $(KERNELDIR) M$(PWD) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) clean把KERNELDIR换成你实际的内核源码路径然后执行make。能正常生成led_test.ko就说明编译通过。把ko文件拷贝到开发板用insmod加载insmod led_test.ko正常现象有两种一是驱动以模块方式加载且和设备树节点匹配成功probe立即调用dmesg里能看到“led_test probe success”二是把驱动编进内核那probe会在设备树节点创建并注册platform_device时被调用内核启动日志里能看到相同信息。3.5 怎么确认匹配真的成功了匹配成功后Linux设备模型会在sysfs下建立关联符号链接。这是判断“设备和驱动是否绑定”的最直接证据。ls -l /sys/bus/platform/devices/led_test/如果看到driver符号链接指向/sys/bus/platform/drivers/led_test说明绑定成功lrwxrwxrwx 1 root root 0 Jan 1 00:00 driver - ../../bus/platform/drivers/led_test反过来在驱动目录下也能看到设备ls -l /sys/bus/platform/drivers/led_test/里面会出现led_test设备名。同时/sys/bus/platform/drivers/led_test/目录下还有bind、unbind、uevent等接口文件可用来手动解绑和绑定设备调试时很好用。驱动卸载用rmmod led_testremove回调会执行/sys/bus/platform/devices/led_test/driver链接会消失说明设备和驱动成功解绑。3.6 probe里怎么拿到设备树资源匹配成功只是开始驱动真正干活还需要从设备树节点获取各种资源。除了GPIO常见的有寄存器地址、中断号、时钟、DMA通道等。下面列几个高频API都是在probe里用的/* 获取寄存器物理地址和长度 */ struct resource *res; res platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res) { unsigned long start res-start; unsigned long end res-end; } /* 获取中断号 */ int irq platform_get_irq(pdev, 0); /* 从设备树获取GPIO对应属性 xxx-gpios */ struct gpio_desc *gpio devm_gpiod_get(dev, xxx, GPIOD_IN); /* 读取设备树里自定义的整型属性 */ u32 val; of_property_read_u32(dev-of_node, max-speed, val); /* 获取时钟 */ struct clk *clk devm_clk_get(dev, NULL);在i.MX6ULL点灯例子里如果用老的gpio_request API需要自己把设备树里的gpio1 4 GPIO_ACTIVE_LOW转换成gpio number计算公式是GPIO1组对应bank 0GPIO1_IO04就是03244GPIO5_IO03就是4323131。但gpiochip的base号在系统里可能动态分配直接算数字不保险所以我强烈建议用gpiod系列API。gpiod_get直接返回描述符不再依赖全局gpio编号这也是内核社区推荐的方向。4. 常见问题与排查技巧实录4.1 probe没执行先把这五步走一遍匹配不成功最常见的表现就是insmod后dmesg干干净净没有任何probe日志。遇到这种情况我一般不急着改代码先按下面的顺序排查确认设备树节点存在且status不是disabledls /sys/firmware/devicetree/base/led_test cat /sys/firmware/devicetree/base/led_test/status cat /sys/firmware/devicetree/base/led_test/compatible确认驱动已经注册到Platform总线ls /sys/bus/platform/drivers/led_test/看设备跟驱动是否已经绑定有没有driver链接ls -l /sys/bus/platform/devices/led_test/手动触发一次匹配排除module加载顺序问题echo led_test /sys/bus/platform/drivers/led_test/bind拉出内核日志里所有platform相关消息dmesg | grep -i platform第四步的bind接口在设备已经驱动绑定过时会报错但如果设备树节点和驱动都正常而之前没自动绑定手动bind往往能看到具体报错。4.2 compatible属性低级错误清单compatible匹配是设备树驱动最常用的匹配方式踩坑也最多。我整理过一张速查表每次匹配不上就逐项核对检查项典型错误字符串完全一致设备树多写一个空格或逗号大小写“MyVendor”写成“myvendor”厂商前缀漏掉厂商名只写设备名逗号位置“myvendor,led-test”写成“myvendor-led-test”of_device_id表末尾忘记加sentinel空结构体status属性设备树节点被status disabled屏蔽MODULE_DEVICE_TABLE宏没写导致modinfo信息缺失of_device_id表末尾的sentinel很容易漏。没有结尾空项内核遍历时会越界匹配结果不可预期。写表时宁可多写一行{ /* sentinel */ }也不能省。4.3 模块加载报错与编译问题编译驱动时经常遇到“Unable to handle kernel NULL pointer dereference”这类运行时错误多数跟资源获取失败但没检查返回值有关。probe里每步都检查返回值、用dev_err输出错误码是保命习惯。加载模块时如果报insmod: ERROR: could not insert module led_test.ko: Invalid parameters常见原因是内核版本与模块编译环境不一致比如用5.4内核头文件编出来的模块insmod到4.1.15内核里。内核不强制模块版本号完全一致时还能加载但一旦用了两个内核差异较大的数据结构就可能出问题。最稳妥做法是在开发板同版本的内核源码树下编译模块。另外有时候insmod能加载但/sys/bus/platform/drivers下没有驱动目录说明platform_driver_register本身失败了。可能是同名驱动已经注册过或者driver.name和系统里其他驱动冲突。4.4 设备树改了但没生效改了dts重新编译dtb后如果发现节点还是老样子别急着怀疑代码先确认板子启动用的是不是新dtb。有些板卡用u-boot环境变量指定dtb分区有些是从boot分区读取要确保文件真正覆盖了。在板子上查看设备树实际内容ls /proc/device-tree/ cat /proc/device-tree/led_test/compatible/proc/device-tree是调试设备树的利器里面内容就是内核解析后的结果。如果节点没出现要么dtb没更新成功要么设备树编译时语法出错内核回退到旧dtb。还有一个隐藏坑i.MX6ULL的pinctrl配置。GPIO要正常工作不仅要设设备树led-gpio属性还需要在iomuxc里把对应引脚复用为GPIO功能。如果忘了配pinctrl或配置宏错误probe可能成功但操作GPIO没反应。检查/sys/kernel/debug/pinctrl/下引脚占用状态能帮助确认。5. 几个能提升开发效率的实用技巧做i.MX6ULL驱动调试除了上面这些我自己还有几个固定习惯分享给大家。第一测试驱动时不要每次都编进内核烧写固件。先用module方式编译在板子上insmod/rmmod验证匹配和probe逻辑没问题后再把代码编进内核或做成开机自动加载。这样迭代速度快很多省掉反复烧写的几分钟一天下来能省不少时间。第二学会看/sys/bus/platform下的目录结构比反复dmesg更直观。driver链接存在与否就是绑定状态的最直接反映。我经常写一句话脚本watch -n 1 ls -l /sys/bus/platform/devices/led_test/driver 21加载驱动前后观察这条命令输出变化匹配流程一目了然。第三如果驱动里同时支持多个设备树节点可以用of_device_id的.data字段区分硬件版本。每个compatible项可以携带不同的driver_dataprobe里通过of_match_device获取当前匹配到的数据const struct of_device_id *match; match of_match_device(led_test_of_match, pdev-dev); if (match) { unsigned long data (unsigned long)match-data; /* 根据data区分版本 */ }第四设备树节点不要写得太多没用的属性。匹配机制只看compatible和status其他属性都是留给驱动读的。保持节点干净以后排查问题容易很多。从个人经验看Platform匹配机制理解透了Linux驱动开发的半壁江山就算拿下了。它不只适用于i.MX6ULL几乎所有现代ARM Linux平台都遵循同一套路设备树描述硬件Platform总线完成匹配probe里初始化驱动。这套模型真正理解之后再去看其他子系统驱动会顺畅得多。调试过程中遇到匹配不上我建议按文中的sysfs路径一层层剥开看别急着怀疑内核大多数时候问题都出在compatible字符串或者设备树节点本身。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号