3. 设备树(DTS)配置检查:reg属性、status属性、分区表对齐、memory节点
设备树,说白了就是内核的「硬件说明书」。rootfs挂不上,很多时候不是文件系统本身的问题,而是内核压根没找到正确的存储设备。我见过太多人一上来就查驱动、查文件系统格式,结果折腾半天,最后发现是DTS里一个地址写错了。
这一节,咱们就聚焦DTS里跟rootfs挂载最相关的几个点。你按这个顺序查,能省下不少冤枉时间。
3.1 reg属性:地址和大小,一个bit都不能错
reg属性定义了设备的地址空间。对于存储设备(比如eMMC、NAND Flash、SD卡控制器),它告诉内核:这个设备在总线上占用的地址范围是多少。
我遇到过最典型的案例:某款i.MX6平台,eMMC的reg写成了<0x0219c000 0x4000>,但实际硬件手册上是0x0219c000起始,长度0x1000。多写了0x3000字节,结果跟另一个外设的地址空间重叠了。内核在初始化时直接报resource conflict,eMMC驱动加载失败,rootfs自然也就找不到了。
检查要点:
- 核对芯片手册或硬件原理图,确认
reg的起始地址和长度 - 注意地址对齐:很多控制器要求
reg的起始地址按4KB或1MB对齐 - 检查是否有地址重叠:多个设备不能占用同一段地址空间
举个例子,一个标准的eMMC控制器节点应该是这样的:
&usdhc2 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc2>;
pinctrl-1 = <&pinctrl_usdhc2_100mhz>;
pinctrl-2 = <&pinctrl_usdhc2_200mhz>;
bus-width = <8>;
non-removable;
status = "okay";
/* reg属性通常在SoC的.dtsi中定义,但如果你在板级dts中覆盖,务必核对 */
reg = <0x0219c000 0x1000>;
};
注意,reg的格式是<address length>。如果是64位地址,需要用两个<>,比如reg = <0x0 0x40000000 0x0 0x1000000>。这个细节很容易被忽略,尤其是从32位平台移植到64位平台的时候。
3.2 status属性:你确定它被「使能」了吗?
这个属性看起来简单,但坑最多。status只有两个值你需要关心:"okay"和"disabled"。其他值(比如"ok"、"enable")内核都不认,一律当作"disabled"处理。
我曾经帮一个客户排查问题,他们的SD卡在U-Boot下能读写,但进了内核就找不到。我看了半天DTS,发现status = "ok"——少写了一个a。内核直接忽略了整个节点,驱动根本没加载。
避坑指南:
- 永远写
status = "okay",不要写"ok"、"enable"、"yes" - 检查父节点(比如
&usdhc2)的status,父节点被禁用,子节点使能也没用 - 有些SoC的
.dtsi里默认把外设节点status = "disabled",你必须在板级.dts里显式改成"okay"
另外,如果你用&node的方式引用节点,记得检查引用是否正确。我见过有人引用了错误的&usdhc1,结果使能的是另一个控制器,真正的rootfs设备还是禁用的。
3.3 分区表对齐:内核和U-Boot的「默契」
rootfs通常放在某个分区里。内核怎么知道分区在哪?要么通过partitions节点在DTS里硬编码,要么通过解析GPT/MBR分区表。不管哪种方式,对齐问题都容易出幺蛾子。
先看DTS里硬编码分区的情况:
&flash {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "bootloader";
reg = <0x0 0x100000>;
};
partition@1 {
label = "kernel";
reg = <0x100000 0x800000>;
};
partition@2 {
label = "rootfs";
reg = <0x900000 0x700000>;
};
};
};
这里有个关键点:reg的起始地址和大小,必须跟U-Boot里烧录的分区完全一致。我遇到过U-Boot里rootfs从0x900000开始,但DTS里写成了0x900000——看起来一样对吧?但U-Boot用的是0x900000(十六进制),DTS里也是0x900000,没问题。问题出在#address-cells和#size-cells上。
对齐检查清单:
| 检查项 | 说明 |
|---|---|
| 地址单元匹配 | #address-cells和#size-cells必须与父节点一致,否则地址解析会错位 |
| 分区大小对齐 | NAND Flash的块大小通常是128KB或256KB,分区起始和大小必须是块大小的整数倍 |
| U-Boot与内核一致 | U-Boot的mtdparts或partitions定义必须与DTS完全一致 |
| GPT/MBR兼容性 | 如果用GPT分区,确保内核配置了CONFIG_PARTITION_ADVANCED和CONFIG_EFI_PARTITION |
对于NAND Flash,对齐问题尤其致命。如果分区起始地址不是块大小的整数倍,内核的MTD子系统会直接报错,拒绝挂载。我调试过一个项目,rootfs分区起始地址是0x100000,但NAND的块大小是0x20000,看起来没问题?实际上U-Boot擦除时按块擦,但内核的MTD分区检查更严格,差一个字节都不行。
3.4 memory节点:内核能看到多少内存?
这个跟rootfs挂载有什么关系?关系大了。如果memory节点配置的内存范围不对,内核可能无法正确映射DMA缓冲区,导致存储控制器无法正常工作。
典型的memory节点:
/ {
memory@40000000 {
device_type = "memory";
reg = <0x40000000 0x80000000>; /* 2GB内存,从0x40000000开始 */
};
};
我见过一个案例:板子上实际有2GB内存(地址范围0x40000000 ~ 0xBFFFFFFF),但DTS里只配了1GB(0x40000000 0x40000000)。结果内核在分配DMA缓冲区时,把一些外设的DMA地址映射到了不存在的物理内存上,eMMC控制器读写直接超时,rootfs自然挂不上。
检查方法:
- 启动时看内核日志:
Memory: 1024MB available,确认数值跟硬件一致 - 用
cat /proc/iomem查看系统内存布局,确认没有空洞 - 如果使用设备树覆盖(DT overlay),确保覆盖后的
memory节点正确
另外,有些SoC有多个内存区域(比如DDR和OCRAM),memory节点只能描述主内存。其他内存区域通常由各自的外设节点管理,不要混在一起。
3.5 一张图理清DTS检查流程
说了这么多,咱们用一张图把整个检查流程串起来。你按这个顺序走一遍,90%的DTS相关问题都能定位到。
嗯,这张图把咱们刚才讲的四个检查点串起来了。你从reg开始,一路检查到memory,任何一个环节出问题,都可能导致内核无法正确识别存储设备,rootfs自然也就挂不上了。
3.6 实战:从内核日志定位DTS问题
最后分享一个实用技巧。当rootfs挂载失败时,别急着改DTS,先看内核日志。用dmesg | grep -i "of\|fdt\|memory\|partition"过滤一下,通常能直接看到问题所在。
我常用的排查命令:
# 查看设备树是否被正确加载
dmesg | grep "OF: fdt"
# 查看内存节点解析结果
dmesg | grep "Memory"
# 查看分区信息
dmesg | grep "partition"
# 查看具体设备的状态
dmesg | grep "usdhc\|mmc\|nand"
举个例子,如果看到这样的日志:
[ 0.456789] OF: fdt: ignoring memory range 0x40000000 - 0x7fffffff (overlap)
那说明memory节点有重叠,赶紧回去检查reg属性。如果看到:
[ 0.789012] mmc0: error -22 while initializing card
那可能是reg地址配置错误,或者status没使能。总之,日志是最好的老师,比你自己瞎猜快得多。
我的习惯:每次修改DTS后,我都会在启动时抓一份完整的dmesg日志,然后用diff对比前后差异。这样能快速发现修改带来的副作用,比如某个设备突然报resource conflict了。
好了,DTS配置检查就讲到这里。你按这个流程走一遍,大部分跟设备树相关的rootfs挂载问题都能解决。记住,DTS是硬件和内核之间的桥梁,这座桥搭歪了,后面的路就没法走。