1. 为什么ARM Linux离不开设备树:从arch/arm/mach-xxx的板级文件说起
1.1 设备树要解决的根本问题:板级代码泛滥
先说个实际经历。前段时间帮朋友排查一块RK3568的板子,内核起来后一路看dmesg,发现串口0没注册、网口也没起来。查遍rootfs、uboot之后,最后在设备树里找到了问题:uart0节点status="disabled",pinctrl引到了别的功能上。这种问题在嵌入式Linux里太典型了——只要你的板子和设备树信息对不上,后面所有工作都白搭。
要理解设备树为什么会存在,得先看一段历史。在设备树引入ARM Linux之前,每换一块开发板,内核里就得新增一个mach-xxx.c文件,板子的内存大小、时钟频率、串口地址、GPIO初始化全部硬编码在C代码里。那时候arch/arm目录下躺着上百个板级文件,内核被一堆板级代码拖累得越来越臃肿。更致命的是,社区想合入一个新板子的支持,就得被迫接收一大堆“只有这块板子能用”的初始化代码,代码质量和可维护性都很难控制。
PCI和x86平台为什么没有这个问题?因为PCI总线有标准枚举机制,设备自己会报“我是谁、我需要什么资源”,操作系统启动时扫描总线就能拿到完整信息,根本不需要为每块主板单独写内核代码。但ARM的嵌入式平台五花八门,SoC内部外设基本是内存映射,没法靠总线自动枚举。所以内核社区最终采用了设备树这套方案:把硬件描述和内核代码彻底解耦。板子是什么样,就写一个dts文件告诉你;驱动代码只负责“按名找资源”,不关心具体板子怎么接的线。
| 维度 | x86/PCI平台 | ARM平台 |
|---|---|---|
| 硬件识别方式 | 总线枚举+ACPI表 | 设备树节点描述 |
| 板级差异隔离 | BIOS/ACPI层处理 | dts文件处理 |
| 更换硬件需改什么 | 一般不需要改内核 | 改/换设备树文件 |
| 驱动获取硬件信息 | PCI配置空间查询 | 解析设备树节点资源 |
1.2 DTS、DTSI、DTB、DTC:这些文件到底各管什么
实际开发中,你接触最多的就是.dts和.dtsi后缀的文件,还有编译出来的.dtb。很多人一开始分不清这三者的关系,我用最直白的话解释一遍。
- .dts:描述“一块具体板子”的设备树源文件,类似于“我手上这块开发板用了哪个触摸屏、哪个网卡芯片、GPIO按键怎么接”。
- .dtsi:被.dts包含的公共代码片段,描述“同一颗SoC有哪些外设、默认的寄存器地址、时钟、中断”,在SoC厂商的SDK里你会看到rk3568.dtsi这种文件。
- .dtb:dts被DTC编译后生成的二进制文件,内核启动时bootloader会把它加载到内存,然后传给内核解析。
- DTC:设备树编译器,通常在Linux内核源码的scripts/dtc目录下,也可以单独安装设备树编译器工具包。
它们的关系可以理解为:.dtsi是芯片原厂画好的“户型图基础框架”,.dts是“针对某一套具体硬件的装修方案”,编译后得到.dtb这一个“最终施工图”。内核启动时拿到的就是.dtb。修改设备树时,你一般不需要动dtsi,而是新建或修改dts,用include把公共dtsi导入,再覆盖其中节点。
1.3 设备树的边界:它到底管什么、不管什么
设备树里放的是“硬件长什么样”,但驱动真正的执行逻辑、协议栈、算法处理都不在设备树里。设备树是一个描述了设备地址、中断、时钟、GPIO、DMA、电源域等信息的“静态数据库”,内核根据这些信息创建设备、匹配驱动、注册中断、配置时钟。所以写设备树你不需要关系驱动里怎么操作寄存器,但你要确保节点里的地址、中断号、时钟索引和SoC手册一致,否则驱动就算写得再好,也取不到正确资源。
这种“描述与代码分离”的设计是理解设备树的钥匙。驱动通过接口去查询“我的中断是几号”“我用的时钟是哪个”,而不是自己硬编码数值。说得夸张一点,同一份驱动代码,换一个dts文件,就能适配完全不同的板卡外设连接方案。这也是为什么懂设备树的人调试新板子特别快——硬件改动了,优先改描述,而不是改驱动。
2. 设备树的核心语法与解析流程:从dts到dtb再到probe
2.1 节点、属性、compatible:三大件必须吃透
设备树本质上是一棵树,根节点是“/”,下面挂各种子节点。每个节点就是一个设备或总线,用花括号包裹;节点里有一些key-value对,叫做属性(property)。最重要的属性就是compatible,它就像设备的“身份证姓名”,驱动靠它来认亲。
看一个最简单的RK3568开发板上串口节点的写法:
/ { model = "MyBoard RK3568 Board"; compatible = "myboard,rk3568", "rockchip,rk3568"; chosen { stdout-path = &uart2; }; }; &uart2 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart2m1_xfer>; };先说compatible。内核匹配驱动的顺序是“设备树节点中的compatible值”和“驱动结构体里of_device_id表中的compatible值”逐一比对。上面这个板级节点有两个compatible,第一个是“myboard,rk3568”更具体,第二个“rockchip,rk3568”更通用。匹配时先与第一个尝试匹配,不中再试第二个。
再看&uart2这种写法。它并不是创建一个新节点,而是引用并覆盖dtsi里已经定义好的uart2节点。dtsi里通常已经写好了寄存器地址、中断号、时钟等,dts里只需要改status和pinctrl,就能把某个外设“激活”并接到正确的引脚上。这也是为什么嵌入式开发中,改设备树大部分工作都是在做引用覆盖,而不是重头写一棵新树。
2.2 从dts到dtb:DTC编译与反编译实操
在Linux内核源码目录下编译设备树,常用命令是:
make ARCH=arm64 dtbs也可以单独编译某一个dtb文件,以RK3568为例:
make ARCH=arm64 qcom_defconfig # 先加载一个配置,如果SDK里已有config可跳过 make ARCH=arm64 dtbs # 编译全部dtb make ARCH=arm64 rk3568-evb1-v10.dtb # 只编其中一个如果只是验证dts语法、不依赖完整内核代码树,也可以直接用dtc命令:
dtc -I dts -O dtb -o myboard.dtb myboard.dts dtc -I dtb -O dts -o myboard.dts myboard.dtb第一行把dts编译成dtb,第二行把dtb反编译成可读的dts。反编译在排查问题时很有用:你手上的固件不确定用的是哪个dts,或者怀疑烧进去的dtb和源文件对不上,反编译一下看节点内容立刻就知道。
需要提醒的是,dtc编译报错时,出错信息往往是“unit name warn”这类的warning,而不是error。很多人看到warning就跳过,结果跑起来发现外设没注册。我的习惯是打开-fix-suppress-phandle-errors这类选项,并且保证编译过程尽量零warning,尤其是地址长度、phandle引用这种实际问题,后面调试起来代价更大。
2.3 内核如何解析设备树:从device_node到platform_device
当bootloader把dtb传给内核后,内核在启动早期会做一次完整的解析,把dtb展开成一颗device_node树,存到全局链表里。这个阶段并不创建设备,只是把“硬件描述”登记到内核的“台账”里。
等对应的总线初始化时,内核会遍历device_node,根据节点状态创建设备。最典型的是平台设备(platform_device):很多SoC内部外设挂在虚拟的platform bus上,内核启动时会调用of_platform_default_populate,把设备树里status="okay"的节点自动变成platform_device注册到总线上,之后总线再去匹配驱动。
对于I2C、SPI这类带控制器的总线,流程稍有不同。I2C控制器节点会被解析成i2c_adapter,然后系统遍历该控制器的子节点,为每个子节点创建i2c_client。所以你在设备树里写“触摸屏挂在i2c2上,地址是0x38”,内核就会在i2c2总线上注册一个地址为0x38的i2c_client,驱动再通过i2c_client访问设备。这个过程完全由设备树驱动,驱动代码里不需要硬编码“哪个设备在哪个总线哪个地址”。
内核解析完设备树之后,sysfs里也能看到结果。路径/sys/firmware/devicetree/base/对应设备树的根节点,每个节点就是一个目录,属性就是文件。查看节点内容可以用:
cat /sys/firmware/devicetree/base/compatible这个命令直接输出板级的compatible字符串,可以用来确认内核实际加载到的设备树版本和你预期是否一致。
3. 设备树与驱动的匹配链路:compatible、of_device_id与probe的协同
3.1 驱动怎么被“喊”起来:of_device_id匹配过程
设备树节点有了,驱动怎么知道哪个设备是自己的菜?关键在驱动结构体里的of_device_id表。以常见的I2C触摸屏驱动为例:
static const struct of_device_id goodix_ts_of_match[] = { { .compatible = "goodix,gt9147" }, { .compatible = "goodix,gt9271" }, { .compatible = "goodix,gt9286" }, { .compatible = "goodix,gt967" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, goodix_ts_of_match);当内核创建好设备(无论是platform_device还是i2c_client)后,总线会调用驱动的_match接口,将设备的compatible属性和驱动of_device_id表中的compatible逐一比较。匹配成功后,调用驱动的probe函数。probe里面做的事情一般是“获取设备树里配置的资源、注册中断、初始化硬件、创建设备节点”。
这里有个初学者最容易忽略的点:compatible字符串必须完全一致。设备树里写“goodix,gt9147”和驱动表里写“goodix,GT9147”都不行,大小写、逗号后的空格都会被当成不同字符串。所以当你的驱动明明加载了却不走probe时,第一步永远是比对两边compatible是不是一字不差。
3.2 资源获取不再靠硬编码:从设备树读出地址、中断、GPIO
设备树不仅承担“匹配”职责,还负责把设备需要的外部资源告诉驱动。就拿最基本的platform driver为例,驱动里获取物理地址和中断号的经典写法是:
struct resource *res; void __iomem *base; int irq; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); irq = platform_get_irq(pdev, 0);这里的资源哪来的?就是内核在解析设备树节点时,把reg属性转换成了IORESOURCE_MEM资源,把interrupts属性转换成了中断号。所以你修改设备树里reg的地址,驱动不需要重新编译;只要重新编译dtb并更新到板子上,驱动读取到的资源就会跟着变。
GPIO和时钟也是同样套路。设备树里配置一个LED节点:
myled: my-led { compatible = "myboard,led"; gpios = <&gpio4 22 GPIO_ACTIVE_LOW>; default-state = "off"; };驱动里用gpiod_get获取GPIO描述符,用devm_clk_get获取时钟句柄,完全通过名字去找。这样硬件连接变了,只改dts就行。
| 资源类型 | dts属性 | 驱动获取接口 |
|---|---|---|
| 寄存器地址/长度 | reg + #address-cells/#size-cells | platform_get_resource |
| 中断号 | interrupts | platform_get_irq |
| GPIO | gpios / gpio-names | devm_gpiod_get / gpiod_get_optional |
| 时钟 | clocks / clock-names | devm_clk_get |
| 复位 | resets / reset-names | devm_reset_control_get |
| 电源 | vdd-supply | devm_regulator_get |
3.3 一个常见误区:改了dts为什么驱动probe没执行
很多人改完设备树,把status改成okay了,compatible也对上了,但驱动就是不走probe。这时需要按这个顺序排查。
第一,确认dtb有没有真的被bootloader加载。很多板子在uboot里固化了一个dtb路径或dtb分区,你改了内核源码树里的dts,但没重新打包/烧写resource分区或boot.img,加载的还是老dtb。
第二,确认节点status是不是okay。很多人只看自己写的部分,却忘了dtsi里某个父节点或者对应的pinctrl节点是disabled。
第三,确认compatible是否确实能被设备树反编译看到。用前面说的反编译方法,或者直接看/sys/firmware/devicetree/base/对应路径下的compatible文件内容。
第四,确认设备是否真的创建了。platform总线可以用ls /sys/bus/platform/devices/查看,I2C设备可以通过i2cdetect -y 总线号查看。设备都没创建,probe自然永远不触发。
这四步是设备树驱动开发最常碰到的排查链路,按顺序走下来,90%的“probe不执行”问题都能定位到根因。
4. RK3568平台的设备树实战:多dtb选择、修改与部署
4.1 openHarmony和Linux SDK里的多设备树到底咋选
最近很多人问:瑞芯微rk3568设备树在SDK里一堆,openharmony的rk3568也有许多设备树,到底该选哪个?其实这层困惑不怪大家——RK3568这颗芯片被太多方案商用了,内存颗粒不同、显示接口不同、摄像头不同、Wi-Fi模组不同,都会导致设备树内容有差异。
设备树文件名的规律通常是“芯片名-板子名-版本号.dts”,比如:
- rk3568-evb1-ddr4-v10.dts
- rk3568-evb2-lpddr4-v10.dts
- rk3568-nvr-demo-v10.dts
选择的关键不是“哪个新选哪个”,而是“你的板和哪个文件名描述的内容最接近”。先从几个维度去抠差异:板子用的是DDR4还是LPDDR4/LPDDR4X?显示输出是HDMI、eDP、LVDS还是MIPI DSI?MIPI摄像头用的sensor型号是什么?Wi-Fi模组是AP6255、AP6398还是RTL8821?每个变量都会影响dtb的最终选择。
还有一种更稳妥的办法是把所有候选dtb反编译出来,grep一下关键节点:
dtc -I dtb -O dts -o rk3568-evb1.dts rk3568-evb1.dtb grep -n "hdmi\|edp\|lvds\|dsi" rk3568-evb1.dts对比完就明白,每个dts的差异点基本集中在显示、摄像头、无线模组这几个主板上最容易变化的区域。选错dtb的表现也很典型:屏幕不亮、触摸没反应、Wi-Fi扫不到热点,但串口和控制台往往是好的,因为串口节点在所有variant里基本保持一致。
4.2 在Ubuntu环境下修改RK3568设备树的常规流程
如果用的是官方或方案商的SDK,修改设备树的流程基本是三步:改dts、编译dtb、打包更新。以Ubuntu开发机环境为例,假设SDK放在kernel目录下:
cd kernel vim arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dts make ARCH=arm64 rockchip_linux_defconfig make ARCH=arm64 dtbs编译完成后,生成的dtb在arch/arm64/boot/dts/rockchip/目录下。下一步是把dtb替换到boot镜像或resource分区里。不同SDK打包方式有差异,但不少RK平台的SDK是这样做的:
./mkimage.sh或者单独更新dtb分区:
upgrade_tool di -resource out/.../resource.img还有一种最常见的场景是只想在Ubuntu里快速改一下设备树、验证某个外设能不能被识别,并不想重编整个SDK。这时可以直接用dtc把现有dtb反编译成dts,改完再编译成dtb,然后用rkdeveloptool或upgrade_tool烧写到对应分区。这种方式适合小改动,比如把一个GPIO口的状态改一下、把某个外设status从disabled改成okay,完全不需要动内核代码。
4.3 常见修改动作示例:加一个SPI设备、打开一个串口
改设备树最终要落到具体动作上。举一个我经常做的例子:在一块RK3568板子上挂一个SPI接口的Flash,需要新增节点。
先看板子上SPI控制器挂哪个总线,查原理图确认CS脚、时钟引脚;然后在dts里加:
&spi1 { status = "okay"; spidev0: spi-flash@0 { compatible = "jedec,spi-nor"; reg = <0>; spi-max-frequency = <50000000>; }; };这里reg = <0>表示片选0,macimum频率根据Flash手册设置。如果是自研板需要特别确认form factor。改完编译烧写后,在/sys/class/spi_master/spi1/下就能看到spi1.0,对应的MTD设备也会出现在/dev/mtd*里。
再举个打开串口的例子。RK3568有很多组UART,但大多数默认是disabled。如果板子上引出了uart4的TX/RX,那么要去dtsi里找到uart4节点,然后在板级dts里:
&uart4 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart4m1_xfer>; };这里pinctrl-names和pinctrl-0是最关键的,它决定了芯片的引脚复用功能到底配置成UART还是GPIO。很多人打开串口后发现引脚不出数据,大概率就是pinctrl配置和实际引出的引脚对应不上。
5. 高频驱动问题排查:USB转串口、调试器与GPU硬件解码
5.1 USB转串口芯片驱动:CH340、CP2102、FTDI在Linux下的机制
热词里CH340串口驱动、cp2102驱动、ftdi串口驱动的搜索量一直很高,说明这是个真实痛点。先说结论:在主流Linux发行版和ARM Linux SDK里,这些常见的USB转串口芯片驱动大多已经编进内核或者以模块形式存在,正常情况下不需要自己去下载第三方驱动。
以USB设备的行为来看,芯片插入后,内核先识别到USB设备,然后根据VID/PID匹配对应驱动,创建设备节点。常见列表如下:
| 芯片型号 | 内核模块 | 常见VID:PID | 生成的设备节点 |
|---|---|---|---|
| CH340/CH341 | ch341 | 1a86:7523 | /dev/ttyUSB0 |
| CP2102 | cp210x | 10c4:ea60 | /dev/ttyUSB0 |
| FTDI FT232R | ftdi_sio | 0403:6001 | /dev/ttyUSB0 |
| 通用USB转串口 | cdc_acm | 因设备而异 | /dev/ttyACM0 |
排查这类驱动问题的思路也非常固定。
第一步,插上USB,执行lsusb,看设备ID是否被系统识别。如果lsusb里都没有,先检查硬件线缆和USB口,问题不在驱动。
第二步,执行dmesg | tail,看内核有没有打印出类似ch341-uart或cp210x的注册日志。有日志说明驱动已加载。
第三步,看/dev/ttyUSB0或/dev/ttyACM0是否出现。出现说明设备节点创建成功。
第四步,如果设备节点出现但操作权限不足,提示permission denied,就要把用户加入dialout组或配置udev规则:
sudo usermod -aG dialout $USER这条命令在很多发行版上都适用,加完重新登录生效。我遇到过太多人卡在最后一步,原本以为驱动没装好,其实只是/dev/ttyUSB0的读写权限问题。
5.2 ST-Link、J-Link在Linux下的驱动与权限配置
调试器在Linux下的问题也很有代表性。ST-Link、J-Link这类调试器本质上也是USB设备,内核可能已经有驱动或者只需安装厂商提供的工具链。很多人以为要专门去装驱动,其实关键点在于权限和工具链。
ST-Link在Linux下最常用的工具链是OpenOCD和stlink-tools。连接ST-Link后,lsusb能看到STMicroelectronics设备。如果OpenOCD提示无法连接,大概率是权限问题:当前用户没有访问USB设备的权限。解决方法同样是配置udev规则:
sudo cat > /etc/udev/rules.d/99-stlink.rules <<EOF SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="3748", MODE="0666", GROUP="plugdev" EOF sudo udevadm control --reload-rules sudo udevadm triggerJ-Link的流程差不多。SEGGER官方提供Linux版JLink软件包,解压后直接运行JLinkExe即可。如果JLink也提示权限问题,需要注意SEGGER自己安装时会写入/opt/SEGGER下的udev规则,但使用自编译OpenOCD时,要确保udev规则中的设备ID覆盖了你手中J-Link的版本。
这里额外提醒一点:不少调试器无法连接其实不是驱动或权限问题,而是USB线质量问题或者Type-C接口没有插入到位。先看lsusb输出,再谈权限和驱动,这是调试USB设备的基本思路。
5.3 GPU设备树和视频硬件解码:为什么只改设备树不够
热词里出现gpu驱动开发和chromium rockchip硬件解码,这属于比较进阶的话题。在RK3568平台上,GPU是Mali-G52,设备树里有一个gpu节点,内核通过devfreq框架管理GPU频率,驱动可以是内核自带的panfrost,也可能是Rockchip提供的Mali驱动。
设备树里的GPU节点并不复杂,无非是compatible、reg、interrupts、clocks这些属性。但如果想让Chromium在RK3568上实现完整的硬件视频解码,单纯改设备树根本不够。硬件解码链路涉及用户态MPP(Media Process Platform)、V4L2的m2m设备、DRM显示框架以及Chromium侧的视频解码器实现。
设备树这里的作用只是告诉内核“VPU/GPU/显示控制器分别在哪、用什么中断和时钟”,真正能不能硬解,还要看用户态组件和内核驱动版本是否配套。所以当你在RK3568上遇到Chromium不能硬解视频时,先把dmesg里mpp_service、vpu_service的注册信息打印出来,确认内核是否识别到了编解码硬件,再去排查用户态库。设备树只是第一道门,门后面还有好几道坎。
6. 设备树开发中最容易踩的坑与实用检查方法
6.1 status = "disabled"和节点覆盖:改了却不生效的常见原因
设备树开发里最经典的问题就是:我明明把节点status改成okay了,怎么reboot之后还是没生效?这种问题十有七八是“改错了文件”。
很多SoC的dtsi里已经把某个外设定义好了,但板级dts里可能引用了它,也可能没有。你在dtsi文件里改status,板级dts一旦对这个节点做了引用覆盖,优先级以板级dts为准。反过来,如果你在另一个专门的dts文件里改,但这个文件根本没被最终的dts包含,那就当然不生效。
排查方法也简单:编译完dtb后反编译,查看该节点的status是不是你期望的okay。例如:
dtc -I dtb -O dts -o out.dts out.dtb grep -n -A 5 "uart4" out.dts如果反编译结果显示status还是disabled,那说明你改的文件不在编译路径里。这种情况再往下查Makefile或者dts的include关系。
还有一个很隐蔽的坑:节点名和label搞混。比如uart4节点label是&uart4,但你直接在根节点下新建了一个“uart4”而不是用&uart4引用,这样内核就会把它当成一个不存在的地址设备,驱动也不会匹配。
6.2 引脚复用冲突:两个功能抢同一个物理引脚
pinctrl是设备树里最容易踩雷的部分。同一个物理引脚,不同时间段可能复用为UART、I2C、SPI或GPIO。当你把两个外设都配到同一个pin上时,后注册或者先注册的那个驱动可能会拿到pinctrl配置,但实际电气连接已经乱套了。
典型表现:外设A和B功能都能在设备树里看到,但A工作正常、B完全没反应,或者A/B互相干扰、系统启动时pinctrl报错。这类问题的排查思路不是看代码,而是去读芯片的引脚复用表。
在RK3568平台上,你可以通过内核日志搜pinctrl相关错误。同时在设备树里,要留意pinctrl-0引用的是不是同一个节点:
&uart4 { pinctrl-0 = <&uart4m1_xfer>; };如果把UART引脚配置成了uart4m1_xfer,而另一处又用uart4m1_cts_rts或其他外设引用了同一个pin group,就会冲突。排查这类问题时,我习惯把候选dts反编译后,用grep搜索同一个pin定义被哪个节点引用了,交叉对比pin group名称。
6.3 一套实用的“设备树体检”命令清单
调试设备树相关问题时,我有一套固定的体检流程,每个新板子来了都会先跑一遍。这套流程能快速筛选出内核对设备树的解析结果,帮你判断“是设备树写错了、还是没加载对、还是驱动压根没匹配上”。
# 查看内核实际解析到的设备树根节点compatible cat /sys/firmware/devicetree/base/compatible # 列出所有platform设备 ls /sys/bus/platform/devices/ # 查看某个具体设备是否注册成功 ls /sys/bus/platform/devices/ | grep mydevice # 查看中断请求是否有设备挂上 cat /proc/interrupts | grep myirq # 查看内核启动阶段设备树解析日志 dmesg | grep -i "of_" | head -50 dmesg | grep -i "Device Tree"多加一句,很多板子在bootloader阶段会有意挑选不同dtb,比如Rockchip的Uboot会读取extlinux.conf或parameter分区里的dtb路径。如果发现自己反复编译烧写还是旧配置,先去uboot环境变量里看boot_fdtfile或者kernel命令行有没有指定dtb名称。
6.4 设备树调试的最后一招:在驱动里打印of_match_table匹配状态
如果上述检查都正常但驱动就是不probe,可以临时在驱动代码的probe入口和匹配函数里加打印。具体做法是在platform_driver结构体初始化时,检查drv->driver.of_match_table是否被正确指向你的of_device_id数组。常见BUG是只写了MODULE_DEVICE_TABLE宏,但忘了给platform_driver设置of_match_table字段。
static struct platform_driver my_driver = { .probe = my_probe, .driver = { .name = "my_device", .of_match_table = my_of_match, }, };如果是I2C设备,则对应i2c_driver里也要有of_match_table。很多时候,驱动模块加载后没有输出任何错误,但就是没触发probe,原因就是驱动侧没把匹配表挂上。这个问题在复制代码时特别容易发生,因为新旧内核版本的driver结构体字段名有时候会不一样,编译过了不一定代表挂载对了。
写在最后:我的设备树调试习惯
做了这么多年嵌入式Linux,我越来越觉得设备树调试的核心心法就一句话:先确认内核实际看到什么,再谈修改什么。很多人一上来就改代码、改配置,改了半天reboot起来发现还是老样子,其实就是没确认dtb有没有加载对、节点有没有生效。我现在拿到一块新板子,不是先急着写驱动,而是先花十五到二十分钟把设备树过一遍:内存节点容量对不对、串口pinctrl有没有配错、关键外设status是不是okay、uboot环境变量引导的dtb路径是什么。这套习惯帮我省了太多debug时间,也希望这篇文章能帮你少踩几个坑。