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相关问题都能定位到。

DTS配置检查流程图 DTS配置检查流程 开始DTS检查 检查 reg 属性 地址范围、对齐、无重叠 检查 status 属性 必须为 "okay",父节点也需使能 检查分区表对齐 地址单元、块对齐、U-Boot一致 检查 memory 节点 内存范围、DMA映射、无空洞 全部通过? DTS 检查通过 修正DTS后重新编译 提示 检查内核日志中的 "OF: fdt" 相关输出 可快速定位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是硬件和内核之间的桥梁,这座桥搭歪了,后面的路就没法走。