第四节:根节点与模型 — 设备树的“身份证”

好,咱们今天聊聊设备树里最顶层的东西——根节点。说白了,它就是整棵设备树的起点,所有其他节点都挂在它下面。你想想看,一棵树要是没有根,那还叫树吗?

4.1 根节点的写法

根节点的写法其实特别简单。就是一个斜杠 /,后面跟一对大括号。我刚开始学的时候,还纳闷过:就这么简单?嗯,确实就这么简单。

/ {
    // 这里是根节点的内容
    // 所有其他节点都放在这里
};

注意最后的分号,千万别漏了。我在项目中遇到过好几次,同事写的设备树编译不过去,最后发现就是根节点结尾少了个分号。这种低级错误,排查起来还挺费时间的。

根节点里可以放什么?说白了,就两类东西:

  • 属性:描述整个系统的特性,比如型号、版本号
  • 子节点:挂在根下面的各种设备,比如CPU、内存、总线

重要提醒:每个设备树文件有且只有一个根节点。你要是写了两个根节点,编译器会直接报错。这个坑我踩过,当时还以为是编译器坏了,折腾了半天才发现是自己手滑多写了一个。

4.2 model属性的作用

model属性,我管它叫设备树的“身份证”。它用来描述这块板子是什么型号。比如你手头有一块i.MX6ULL的开发板,那model就写成这样:

/ {
    model = "Freescale i.MX6 ULL 14x14 EVK Board";
};

为什么要写model?我给你说个真实案例。有一次我在调试一块板子,内核启动时死活加载不对驱动。后来发现,是因为设备树里没写model属性,内核不知道这是哪块板子,匹配错了设备。从那以后,我写设备树的第一件事,就是把model写上。

model属性的规范,我总结了几点:

规范项 说明 示例
格式 字符串,用双引号括起来 "厂商名 产品名"
内容 厂商 + 产品型号 + 版本(可选) "TI AM335x BeagleBone Black"
唯一性 同一系列的不同板子,model必须不同 别把A版和B版写成一样的
大小写 建议保持厂商官方写法 别把"i.MX"写成"imx"

我的小技巧:写model的时候,最好去查一下厂商的官方文档,看看他们是怎么命名的。比如NXP的板子,官方叫什么你就写什么。这样内核匹配的时候,成功率最高。

4.3 根节点里的其他常见属性

除了model,根节点里还经常放这几个属性。我挑几个常用的说说:

compatible属性

这个属性跟model有点像,但更偏向于软件匹配。它告诉内核:我这块板子兼容哪个平台。写法是这样的:

/ {
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
};

注意看,这里有两个字符串。第一个是具体的板子型号,第二个是通用的芯片型号。内核匹配的时候,会从左到右依次尝试。为什么要这样写?

我举个例子你就明白了。假设你写了一个驱动,支持所有i.MX6ULL的板子。那驱动里只需要匹配"fsl,imx6ull"就行了。但如果某块板子有特殊需求,驱动也可以匹配更具体的"fsl,imx6ull-14x14-evk"。这种设计,说白了就是给驱动开发者留了灵活度。

#address-cells 和 #size-cells

这两个属性,我刚开始学的时候觉得挺绕的。简单说,它们决定了子节点里地址和长度用几个32位来表示。

/ {
    #address-cells = <1>;   // 地址用1个32位
    #size-cells = <1>;      // 长度用1个32位
};

如果是64位的系统,可能会写成2:

/ {
    #address-cells = <2>;   // 地址用2个32位,拼成64位
    #size-cells = <2>;      // 长度用2个32位
};

注意:这两个属性如果不写,默认值都是1。但我建议你最好显式写出来,别偷懒。我曾经接手过一个项目,前任工程师没写#size-cells,结果内存映射的时候长度算错了,系统启动到一半就崩了。排查了整整两天才找到原因。

4.4 一个完整的根节点示例

说了这么多,咱们来看一个完整的例子。这是我之前做的一个项目里的设备树根节点:

/ {
    model = "Acme Cortex-A9 Development Board";
    compatible = "acme,cortex-a9-dev", "acme,cortex-a9";

    #address-cells = <1>;
    #size-cells = <1>;

    // 下面是子节点
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a9";
            reg = <0>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  // 512MB内存
    };

    chosen {
        bootargs = "console=ttyAMA0,115200 root=/dev/mmcblk0p2";
    };
};

你看,根节点下面挂了三个子节点:cpus、memory和chosen。每个子节点都有自己的属性和子节点。这就是设备树的层级结构——从根开始,一层层往下展开。

4.5 避坑指南

最后,我把自己这些年踩过的坑总结一下,你写的时候注意避开:

  1. model别写错:大小写、拼写都要跟官方一致。我见过有人把"i.MX"写成"IMX",结果内核匹配不上。
  2. 分号别漏:每个节点结尾都要有分号。这是C语言风格,漏了编译器不会给你好脸色看。
  3. 根节点只有一个:别手滑写了两个根节点。如果你想把多个设备树合并,用/include/指令,别直接复制粘贴。
  4. #address-cells和#size-cells要匹配:父节点和子节点之间,这两个属性的值要一致。否则地址解析会出问题。
  5. chosen节点别乱放:chosen节点是给bootloader用的,一般放在根节点下。别自作主张挪到别的地方去。

嗯,关于根节点和model,今天就聊这么多。下一节咱们讲讲设备树里最常用的节点——CPU节点怎么写。到时候我会分享一些调试CPU频率的小技巧,敬请期待。