拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Linux Platform设备与驱动匹配机制详解:基于i.MX6ULL的实战解析

开篇先聊一个我前几天调板子时遇到的场景新画的i.MX6ULL核心板首次点灯设备树里明明加了LED节点驱动源码也编译进了内核可/sys/class/leds下就是死活不出现设备。查了半小时最后发现问题出在compatible字符串多打了一个空格。这种时候你就会深刻理解搞懂Linux Platform设备与驱动匹配机制比多写几百行代码都要值钱。这篇文章不打算从教科书式的设备模型讲起而是围绕i.MX6ULL这颗NXP的Cortex-A7处理器把Platform总线、设备树、驱动匹配这条链路彻底捋一遍。内容涵盖Platform总线为什么要存在、设备和驱动是怎么牵上线的、匹配优先级怎么排、设备树节点如何和驱动结构体一一对应、以及线上排查匹配失败时常用的调试手段和几个我踩过的坑。不管是刚入门嵌入式Linux的新手还是做了一年半载驱动开发想回头补基础的朋友这篇文章都能给你一些参考。1. Linux设备模型与Platform总线定位1.1 为什么需要有Platform总线很多初学者第一次接触Platform总线时都会犯迷糊CPU内部的外设明明直接挂在内存总线上怎么又冒出来一个Platform总线要理解这块得先从Linux设备模型的核心抽象说起。Linux内核管理硬件设备的方式本质上是把物理世界里的设备—控制器—接口抽象成软件世界里的三条主线struct device描述设备本身struct device_driver描述驱动struct bus_type描述两者之间的连接方式。像I2C设备挂在I2C控制器上SPI设备挂在SPI控制器上USB设备挂在USB总线上这些都有真实的物理总线可循。但问题来了SoC内部大量外设——比如GPIO控制器、UART串口、DMA控制器、以太网MAC——它们没有传统的物理总线而是通过地址总线直接映射到内存空间。这类设备在Linux里统称为“Platform设备”。Platform总线就是一个虚拟总线它的职责是把这些挂靠在CPU内存总线上的设备管理起来。你可以把它想象成物业的临时登记处小区里的固定车位属于买了车位的人真实物理总线设备但那些没有固定车位、临时停靠的车辆也得有个统一管理的机制。Platform总线干的就是这件事——给所有不挂在物理总线上的设备提供一个统一的注册和匹配框架。i.MX6ULL内部几乎所有的外设都属于这个范畴UART1到UART8、ECSPI控制器、I2C控制器、GPIO Bank、SDMA控制器全部是Platform设备。所以你在移植BSP或编写外设驱动时打交道最多的就是Platform这条线。1.2 Platform总线在设备模型中的位置从软件层级看Platform总线的位置很清晰。它本身是一个struct bus_type实例在内核启动阶段通过platform_bus_init注册到内核设备模型的核心层。它维护了两条链表一条是已注册的platform_device列表一条是已注册的platform_driver列表。每次有新的device或driver注册进来总线核心就会触发一次匹配检查看看新来的房客能不能和现有的房东配上对。这个机制的关键优势在于解耦。传统写法是驱动里直接初始化寄存器地址、中断号等硬件信息代码和硬件强绑定换个板子就得改驱动源码。引入Platform机制后硬件信息通过设备树Device Tree或板级文件描述驱动只负责操作逻辑两者通过标准接口完成匹配。这样驱动代码的可移植性和可维护性就上来了。在内核源码中Platform总线的实现位于drivers/base/platform.c。这个文件不算长但极其核心。它包含了platform_driver_register、platform_device_register、platform_match等一系列关键函数的实现。理解了这个文件的核心逻辑就等于掌握了Linux设备模型的一条主脉。2. 设备端与驱动端的注册流程拆解2.1 platform_device如何诞生在传统的、没有设备树的时代platform_device是在C代码里手动定义和注册的。典型的写法是这样的static struct resource led_resources[] { [0] { .start GPIO1_IO03_PIN, .end GPIO1_IO03_PIN, .flags IORESOURCE_MEM, }, }; 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) { platform_device_register(led_device); return 0; }这种做法的缺点一目了然每换一块板子即使只是改个GPIO引脚都要重新编译内核或内核模块。代码里到处散落着板级硬件信息维护成本极高。设备树出现后platform_device的诞生方式完全变了。现在内核在启动阶段会解析dtb文件设备树二进制把每一个compatible属性为特定值的节点自动转换成platform_device。设备树里的一个节点标准情况下对应一个platform_device。举个例子i.MX6ULL的设备树里UART1的节点长这样uart1 { pinctrl-names default; pinctrl-0 pinctrl_uart1; status okay; };内核解析时会为这个节点分配一个struct platform_device并把节点里的reg属性、interrupts属性、pinctrl信息等统统转换成platform_device里对应的资源结构。开发者不需要再手动管理资源的生命周期设备树解析机制全部承包了。2.2 platform_driver如何注册驱动端的注册相对统一无论是老内核还是新内核入口都是platform_driver_register。以常见的字符设备驱动为例通常会在module_init函数里调用它。static const struct of_device_id led_of_match[] { { .compatible myvendor,imx6ull-led }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, led_of_match); static struct platform_driver led_driver { .probe led_probe, .remove led_remove, .driver { .name imx6ull-led, .of_match_table led_of_match, }, }; module_platform_driver(led_driver);module_platform_driver是一个封装宏展开后相当于在module_init里调用platform_driver_register在module_exit里调用platform_driver_unregister。驱动加载后就会进入Platform总线的匹配流程。这里有个细节需要注意driver结构体里的name字段和of_match_table的作用不一样。name用于legacy的ID匹配而of_match_table专门用于设备树匹配。在设备树一统天下的今天of_match_table才是主角。2.3 注册顺序对匹配的影响Platform总线的匹配不要求设备先注册还是驱动先注册。设备先来驱动后来总线会立即对新驱动做一次匹配扫描驱动先来设备后来同样会在新设备注册时触发匹配。这个机制由bus核心的bus_probe_device和device_attach实现二者都会遍历已有列表进行匹配尝试。这一点在实际开发中很重要。很多人把驱动编译成模块后modprobe加载时发现probe函数没有被调用第一反应是设备树没配好。其实还有一种可能设备树解析早于模块加载设备已经注册完了模块加载后内核会触发一次新的匹配过程正常情况下probe应该被调用。如果probe始终未被调用那大概率是匹配条件不满足而不是顺序问题。3. 匹配机制的核心四种匹配方式的优先级3.1 platform_match函数源码解析Platform总线的匹配核心是platform_match函数位于drivers/base/platform.c。这个函数的逻辑决定了设备与驱动之间能否建立绑定关系。内核源码中它的实现大致如下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. 尝试OF类型匹配即设备树匹配 */ 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(pdrv-id_table, pdev) ! NULL; /* 4. 直接比较驱动名和设备名 */ return (strcmp(pdev-name, drv-name) 0); }这个函数的执行顺序就是匹配的优先级顺序设备树匹配优先其次是ACPI匹配再次是ID table匹配最后是简单的名字比较。在ARM嵌入式平台ACPI基本用不到所以实际起作用的主要是第1、3、4种。3.2 OF匹配设备树匹配详解设备树匹配是所有匹配方式中优先级最高、也是当前最主流的一种。它的关键是of_match_table里的每个entry核心字段是compatible字符串。匹配时内核会比较设备树节点的compatible属性和驱动of_match_table里的compatible字符串。设备树节点里可能有多个compatible值通常第一个是精确的芯片型号后面的是兼容的通用型号内核会按顺序逐个比较只要有一个能对上就算匹配成功。举例说明设备树里一个节点led { compatible myvendor,imx6ull-led, gpio-leds; ... };驱动的of_match_tablestatic const struct of_device_id led_of_match[] { { .compatible myvendor,imx6ull-led }, { .compatible gpio-leds }, { /* Sentinel */ } };这种情况下第一个compatible字符串就能匹配上。如果设备树里只写了gpio-leds驱动的两张表也都保留同样能匹配成功。这种多级兼容的设计是为了让同一个驱动可以支持多个硬件版本。匹配成功后驱动结构体里的.data字段会被保存在dev-driver_data中probe函数里可以通过of_device_get_match_data(dev)取回这个自定义数据。这个技巧在管理多型号芯片时特别有用比如同一个驱动兼容两颗不同寄存器地址的PHY芯片可以用.data区分。3.3 ID table匹配与名字匹配ID table匹配是设备树出现之前的主要匹配方式。驱动的id_table是一个struct platform_device_id数组里面定义了驱动支持的设备名字列表。匹配时会遍历id_table看有没有哪个名字和platform_device的名字一致。static const struct platform_device_id led_id_table[] { { imx6ull-led, (kernel_ulong_t)led_data }, { }, }; MODULE_DEVICE_TABLE(platform, led_id_table);需要注意使用ID table匹配时platform_device_id里的name字段会覆盖driver结构体里的name字段。这个机制在驱动支持多设备名时很灵活比如同一个驱动可以支持imx6ull-led和imx6ul-led两个设备名。最后一种是最原始的名字比较直接把platform_device的name和platform_driver的driver.name做字符串比较。这种方式在设备树时代几乎已经看不到了但老版本内核或某些特殊框架如regmap、gpiochip中可能还会遇到了解一下有备无患。3.4 匹配成功后发生了什么匹配成功只是第一步。接下来总线核心会调用驱动里的probe函数。probe是驱动真正开始初始化硬件的地方俗称探测函数。在probe里驱动通常会做这几件事获取设备资源platform_get_resource、devm_ioremap_resource获取寄存器物理地址并映射虚拟地址。获取中断号platform_get_irq获取设备树里定义的interrupts属性。注册字符设备、创建类、创建设备节点register_chrdev、class_create、device_create。初始化硬件设置寄存器、申请GPIO、配置时钟。probe执行成功后设备进入已绑定状态/sys/bus/platform/devices目录下能看到设备/sys/bus/platform/drivers目录下能看到驱动并且两者之间有symlink指向对方。4. i.MX6ULL上基于设备树的Platform驱动实操4.1 最小硬件场景一颗LED外设纸上谈兵再多不如动手写一个。这里以i.MX6ULL开发板上最常见的一颗板载LED为例完整演示从设备树到probe函数的全过程。先看硬件原理。i.MX6ULL的GPIO1_IO03引脚接了LED正极负极通过电阻接地。要点亮LED只需要把GPIO1_IO03配置为输出模式并输出高电平。第一步写设备树节点。在i.MX6ULL的设备树源文件通常是imx6ull.dtsi或具体的板级dts文件中添加一个自定义节点myled { compatible myvendor,imx6ull-led; reg 0x020c406c 0x4; pinctrl-names default; pinctrl-0 pinctrl_myled; led-gpio gpio1 3 GPIO_ACTIVE_HIGH; status okay; };compatible属性定义了该设备与驱动的匹配关键字。reg属性在这里仅作为示例实际开发中GPIO的控制不需要直接操作寄存器内核的GPIO子系统会帮我们管理。pinctrl-0引用了全局pinmux配置把GPIO1_IO03复用为GPIO功能。对应的pinmux节点要加在iomuxc节点下iomuxc { pinctrl_myled: myledgrp { fsl,pins MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0 ; }; };宏MX6UL_PAD_GPIO1_IO03__GPIO1_IO03定义在imx6ul-pinfunc.h头文件中表示把GPIO1_IO03这个引脚复用为GPIO1_IO03功能。后面的0x10b0是引脚配置值包括上拉/下拉、驱动强度、速度等设置具体含义可以查参考手册的IOMUX章节。第二步编写驱动代码。创建一个myled.c文件#include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/of_gpio.h #include linux/gpio.h #include linux/delay.h static int led_gpio; static const struct of_device_id myled_of_match[] { { .compatible myvendor,imx6ull-led }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, myled_of_match); static int myled_probe(struct platform_device *pdev) { struct device *dev pdev-dev; enum of_gpio_flags flags; led_gpio of_get_named_gpio_flags(dev-of_node, led-gpio, 0, flags); if (!gpio_is_valid(led_gpio)) { dev_err(dev, failed to get led gpio\n); return -EINVAL; } gpio_request(led_gpio, myled); gpio_direction_output(led_gpio, 1); dev_info(dev, myled probed, gpio%d\n, led_gpio); return 0; } static int myled_remove(struct platform_device *pdev) { gpio_free(led_gpio); return 0; } static struct platform_driver myled_driver { .probe myled_probe, .remove myled_remove, .driver { .name myled, .of_match_table myled_of_match, }, }; module_platform_driver(myled_driver); MODULE_LICENSE(GPL);第三步编译并加载。假设内核源码已经配置好用如下命令编译模块make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- M$(pwd) modules把生成的myled.ko拷贝到开发板加载insmod myled.ko正常的加载日志应该是myled myled: myled probed, gpio3如果probe没有被调用检查设备树是否生效/proc/device-tree下是否有myled节点。4.2 为什么of_match_table比driver.name更可靠实际开发中我发现很多初学者在写驱动时只填了driver.name没有填of_match_table。在设备树模式下这会导致匹配失败吗答案是分情况。如果你用的是设备树且驱动没有设置of_match_table那么of_driver_match_device这一步会直接失败接着走到名字比较那一步此时比较的是platform_device的name和driver.name。设备树节点解析出来的platform_device的name默认是节点名去掉地址后的名字实际是device_node的name字段但不同版本内核处理并不完全一致这种依赖并不可靠。所以设备树模式下务必通过of_match_table来声明匹配关系。这不仅是机制上的惯例更是稳定性上的要求。compatible字符串是设备树里硬件身份的身份证号驱动只有认准身份证号才能保证不会认错硬件。4.3 probe中的资源获取技巧probe函数里的资源获取是驱动开发者的日常操作。针对i.MX6ULL平台最常用的三个获取函数platform_get_resource(pdev, IORESOURCE_MEM, 0)获取内存类资源返回struct resource包含物理起始地址和长度。配合devm_ioremap_resource使用可以一步完成物理地址到虚拟地址的映射。platform_get_irq(pdev, 0)获取中断号直接返回int类型的中断号。of_get_named_gpio_flags(node, xxx-gpio, 0, flags)获取GPIO资源需要设备树节点里定义类似xxx-gpio gpio1 3 GPIO_ACTIVE_HIGH这样的属性。这三个函数背后其实都对应了设备树里不同的属性。reg属性映射到IORESOURCE_MEMinterrupts属性映射到IRQ自定义的xxx-gpio属性则需要通过DT API手动获取。理解了设备树属性到资源的映射关系probe里就不会一头雾水了。提示在probe里优先使用devm_系列的资源管理函数如devm_ioremap_resource、devm_gpio_request、devm_request_irq。这些函数会在设备解绑时自动释放资源省去手动清理的麻烦也避免因异常退出导致资源泄漏。5. 匹配失败排查与调试实录5.1 典型匹配失败的三种表现匹配失败是驱动开发中最常见的问题之一表现形式通常有三种。第一种设备树节点存在但/sys/bus/platform/devices下找不到对应设备。这种情况通常是设备树编译或加载有问题设备树文件没有正确编译进dtb或者uboot传给内核的dtb不是修改后的版本。第二种设备节点在但/sys/bus/platform/drivers下没有驱动绑定。这种说明驱动没有加载成功或者匹配条件不满足。第三种设备节点和驱动都在但没有symlink指向对方。这种最典型说明匹配过程确实执行了但没有匹配上。重点检查compatible字符串是否完全一致包括大小写、连字符、前缀的厂商名。5.2 利用sysfs追踪匹配状态内核的sysfs接口是排查这类问题的利器。设备注册后以下节点非常有用# 查看所有platform设备 ls /sys/bus/platform/devices/ # 查看某个设备绑定的驱动 ls -l /sys/bus/platform/devices/myled/driver # 查看某个驱动的详细信息 cat /sys/bus/platform/drivers/myled/of_match_tableof_match_table这个文件会列出驱动支持的compatible字符串列表和设备树里的compatible属性逐一比对就能快速找到问题。另一个有用的调试接口是/sys/kernel/debug/driver_probe。这个方法需要开启内核的DEBUG_DRIVER配置。开启后驱动匹配的每个环节都会有详细的日志输出。在系统启动参数中追加dyndbgfile drivers/base/platform.c p就能在串口控制台或dmesg里看到platform_match的完整执行过程。5.3 我踩过的几个坑第一个坑compatible字符串不一致。上文提到过我曾在字符串尾部多加了一个空格导致匹配失败。这类肉眼极难发现的问题最好的排查手段就是把设备树里的compatible和驱动里的of_match_table用十六进制对比。# 设备树里的compatible cat /proc/device-tree/myled/compatible | od -An -tx1 # 驱动里的compatible cat /sys/bus/platform/drivers/myled/of_match_table | od -An -tx1第二个坑status属性没设成okay。很多SoC的默认dtsi里外设节点默认是status disabled。如果板级dts里忘记改成okay内核会直接忽略这个节点自然不会生成platform_device。这个问题在i.MX6ULL上尤其常见因为NXP的官方dtsi里大部分外设默认都是disabled。第三个坑设备树节点路径不对。新手上路经常把节点放到根节点下面其实锁在外设节点外面。有些外设控制器要求节点放在特定的总线节点下否则无法正确解析reg地址范围。建议先参考NXP官方BSP里类似外设的写法至少先保证目录结构一致。第四个坑probe返回值不为0。有时匹配成功但probe函数里某个调用失败返回错误码比如gpio_request失败、devm_ioremap_resource失败会导致设备与驱动解绑。此时/sys/bus/platform/devices/myled/driver目录消失很容易误判为匹配失败。遇到这种情况重点看dmesg的最后几行报错日志比盲目改compatible高效得多。5.4 匹配失败排查速查表现象可能原因排查命令/手段设备节点不存在设备树未编译/未加载ls /proc/device-tree/查看节点设备存在但无驱动绑定compatible不匹配对比两个compatible字符串hex值设备绑定后自动解绑probe返回错误dmesg设备树节点disabledstatus属性不是okay直接查看dtb反编译结果模块加载后无反应自动化加载工具缓存问题depmod后重新modprobe6. 面试与原理追问Match机制的底层细节6.1 热插拔场景下的动态匹配Platform总线虽然主要服务SoC内部设备但它也支持热插拔场景。常见的例子是开发板上通过GPIO模拟的SD卡检测、USB Hub控制等附加设备。热插拔场景下的动态匹配其实还是走同一套platform_match逻辑。触发时机不同设备插入时由总线检测到新设备并创建新的platform_device随后自动触发匹配驱动加载时也会对已存在的设备做一次全量匹配。这种机制的好处是驱动可以做成模块按需加载。比如SD卡控制器驱动可以编译为模块系统启动时不加载插入SD卡后再自动加载。i.MX6ULL平台上SD卡控制器usdhc就是这样工作的。6.2 与真实总线匹配的异同很多人面试时会被问到Platform总线的匹配机制和I2C、SPI等真实总线的匹配机制有什么区别核心区别在于设备发现的来源。真实总线上设备是由控制器通过物理协议扫描发现的比如I2C控制器发起寻址如果有设备ACK就认为该地址有设备存在。这种是动态枚举。而Platform总线上的设备不是靠扫描发现的它是在内核解析设备树时静态创建出来的。换句话说真实总线是控制器主动找设备Platform总线是OS事先知道有哪些设备把它们一一登记。这个区别决定了匹配策略的差异。真实总线通常需要更多层次的匹配比如I2C还需要考虑设备地址范围Platform总线则相对简单核心就是compatible或id_table。6.3 高频Linux驱动面试题匹配顺序面试中高频考查点就是platform_match的顺序。标准答案如下OF匹配设备树compatible优先。ACPI匹配次之。ID table匹配再次。最后是driver.name和设备名直接比较。延伸问法通常是设备树匹配时compatible属性有多个字符串会怎样答案是按顺序与驱动的of_match_table里的每个entry比较整体复杂度是O(N×M)级别的遍历不过实际数量都很少性能不是问题。另外还会追问为什么compatible前面要加厂商前缀答案是为了避免命名冲突。不同厂商可能定义同名的设备类型加上厂商前缀如myvendor,就能在全局范围内唯一标识特定厂商的特定设备。6.4 从driver_register到match的完整调用链在代码层面理清调用链对深挖问题很有帮助。platform_driver_register最终会调用总线核心的driver_register而后者内部会调用bus_add_driver。bus_add_driver做了两件事把驱动添加到总线的驱动链表然后调用driver_attach。driver_attach遍历总线上所有已注册的设备对每个设备调用__device_attach最终触发platform_match。反向路径也一样。platform_device_register内部会调用device_adddevice_add最后调用bus_probe_device同样会遍历已注册的驱动列表进行匹配尝试。所以无论是设备先来还是驱动先到最终都会汇聚到platform_match这个函数上。代码层面看似两条路径殊途同归。7. 效率优化与模块化开发经验7.1 利用module_platform_driver宏简化代码很多驱动源码里能看到module_platform_driver这个宏它确实能简化代码结构内部自动生成module_init和module_exit入口函数。不过有一些需要注意的地方。当驱动需要额外的初始化逻辑时不能直接用这个宏而要展开成手写形式static int __init myled_init(void) { int ret; /* 自定义初始化 */ ret do_something(); if (ret) return ret; return platform_driver_register(myled_driver); } static void __exit myled_exit(void) { platform_driver_unregister(myled_driver); } module_init(myled_init); module_exit(myled_exit);这样做的好处是可以在驱动注册前完成一些准备工作比如共享内存池的申请、全局状态变量的初始化等。7.2 多实例设备的匹配策略有时同一颗SoC上有多个同类型外设比如两路SPI控制器。这种情况下一个驱动要支持多个设备实例。匹配机制天然支持这种情况因为每个设备树的实例都会生成独立的platform_device驱动只需写一份probe函数会被多次调用每次传入的设备指针不同。在of_match_table里通常会同时列出多个兼容型号。比如static const struct of_device_id spi_imx_of_match[] { { .compatible fsl,imx6ull-spi, .data imx6ull_spi_data }, { .compatible fsl,imx6ul-spi, .data imx6ul_spi_data }, { /* Sentinel */ } };这样同一个驱动既能支持i.MX6ULL也能兼容i.MX6UL。probe里通过of_device_get_match_data获取对应的硬件参数结构体根据参数差异初始化不同版本的寄存器配置。7.3 驱动模块卸载时需要注意什么模块卸载看起来简单实际有几个坑。platform_driver_unregister执行时内核会检查该驱动是否还有绑定的设备。如果设备仍然存在且正在使用内核会调用驱动的remove函数并解绑设备然后才真正注销驱动。所以在remove函数里要确保所有资源都被正确释放请求的GPIO要gpio_free注册的字符设备要unregister_chrdev创建的设备节点要device_destroy。顺序建议与probe相反。probe里先申请资源后创建设备remove先销毁设备后释放资源严谨一点不会错。另外如果驱动模块被modprobe -r强制卸载时有进程正在进行I/O操作可能导致内核崩溃。这种情况要么保证所有使用者都已经关闭设备文件要么在内核配置里开启自动解除引用机制。8. 一个容易被忽略的点与设备树相关的生命周期管理从头到尾聊了匹配机制最后补一个容易被忽略但很重要的点设备树节点和platform_device的生命周期管理。在设备树模式下platform_device是从device_node创建出来的但两者生命周期并不完全同步。device_node的生命周期由设备树子系统管理platform_device的生命周期由Platform总线管理。对于设备树中status为okay的节点内核会在of_platform_bus_probe时创建对应的platform_device。调研的时候我发现很多开发者在设备树里删除节点时还保留驱动模块导致模块加载后找不到设备dmesg里会报Failed to create device link之类的警告。理解了这个生命周期关系就能明白设备树节点是硬件的静态描述platform_device是设备模型中的动态实例驱动模块是操作逻辑。三者之间的生命周期管理本质上就是Linux设备模型的经典三角关系。最后说句实在话驱动开发和匹配机制这部分内容说实话不复杂但细节很多。我见过不少同事设备树配了半天驱动probe就是不执行最后发现只是compatible字符串没对上。也见过有人为了匹配成功把of_match_table里的compatible写了十几个变体其实都是白费功夫——只要严格按照设备树节点里的compatible抄一遍百分之九十九都能匹配上。对于i.MX6ULL这样的平台熟练使用/sys/bus/platform目录、dmesg日志和debugfs这几个接口基本能解决大部分匹配问题。后续如果你想继续深入可以研究一下drivers/base/dd.c里的设备与驱动绑定流程以及drivers/base/platform.c里资源管理的更深入用法。这些底层知识虽然平时用不到但真遇到疑难杂症时能帮你少走很多弯路。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门