第四节:根节点与模型 — 设备树的“身份证”
好,咱们今天聊聊设备树里最顶层的东西——根节点。说白了,它就是整棵设备树的起点,所有其他节点都挂在它下面。你想想看,一棵树要是没有根,那还叫树吗?
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 避坑指南
最后,我把自己这些年踩过的坑总结一下,你写的时候注意避开:
- model别写错:大小写、拼写都要跟官方一致。我见过有人把"i.MX"写成"IMX",结果内核匹配不上。
- 分号别漏:每个节点结尾都要有分号。这是C语言风格,漏了编译器不会给你好脸色看。
- 根节点只有一个:别手滑写了两个根节点。如果你想把多个设备树合并,用/include/指令,别直接复制粘贴。
- #address-cells和#size-cells要匹配:父节点和子节点之间,这两个属性的值要一致。否则地址解析会出问题。
- chosen节点别乱放:chosen节点是给bootloader用的,一般放在根节点下。别自作主张挪到别的地方去。
嗯,关于根节点和model,今天就聊这么多。下一节咱们讲讲设备树里最常用的节点——CPU节点怎么写。到时候我会分享一些调试CPU频率的小技巧,敬请期待。