设备树节点详解:根节点、CPU节点、内存节点、chosen节点、aliases节点
好,咱们今天来啃设备树里最核心的几个节点。说实话,我刚接触设备树那会儿,看着那一堆花括号和逗号,头都大了。后来发现,其实就那么几个关键节点,搞懂了它们,整个设备树的骨架就搭起来了。
设备树说白了就是一个描述硬件的数据结构。它告诉内核:你有哪些CPU、内存多大、外设挂在哪。今天咱们就一个一个拆开看。
核心要点:设备树的根节点是"/",所有其他节点都是它的孩子。CPU、内存、chosen、aliases这几个节点,是设备树里最基础、最常用的节点。
1. 根节点(Root Node)
根节点是设备树的起点。它用斜杠"/"表示,所有其他节点都挂在这个节点下面。你可以把它想象成文件系统的根目录。
根节点里通常会定义一些全局属性,比如板子型号、兼容性等。我个人习惯在根节点里至少放三个属性:model、compatible和#address-cells、#size-cells。
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
#address-cells = <1>;
#size-cells = <1>;
// 其他节点...
};
compatible属性特别重要。内核通过它来匹配对应的驱动。我遇到过一个问题:板子换了型号但忘了改compatible,结果内核加载了错误的驱动,外设全都不工作。嗯,从那以后我每次改板子都会检查这个属性。
小技巧:compatible的值一般写成"厂商,型号"的格式,比如"ti,am335x-bone-black"。内核匹配时,会从左到右依次尝试,所以越具体的型号放越前面。
2. CPU节点
CPU节点描述处理器的信息。它通常放在根节点下面,名字叫"cpus"。每个CPU核心是一个子节点。
为什么需要这个节点?内核启动时要初始化MMU、设置缓存策略、确定时钟频率等。没有CPU节点,内核连自己跑在什么硬件上都不知道。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a8";
reg = <0x0>;
clock-frequency = <1000000000>;
};
};
注意看reg属性,它表示CPU的编号。如果是多核处理器,每个核心的reg值不同。我记得有一次调试SMP(对称多处理)时,发现第二个核心死活起不来,最后发现是reg写成了0x1,但硬件上第二个核心的ID是0x1...其实应该是0x1没错,但地址映射搞错了。细节决定成败啊。
注意:CPU节点的device_type属性必须写"cpu",这是内核约定的。写错了内核会直接忽略这个节点。
3. 内存节点
内存节点告诉内核物理内存的起始地址和大小。它通常放在根节点下,名字叫"memory"。
你想想看,内核启动时第一个要干的事就是知道内存布局。没有这个节点,内核连内存管理都做不了。
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 起始地址0x80000000,大小512MB
};
reg属性里两个值:第一个是起始物理地址,第二个是内存大小(字节)。这里0x20000000就是512MB。
我在项目中遇到过一个问题:板子有1GB内存,但内核只识别了512MB。查了半天,发现是#size-cells设成了1,导致高32位地址被截断了。后来改成#size-cells = <2>才搞定。说白了,地址宽度一定要和硬件匹配。
关键点:内存节点的device_type必须写"memory",reg里的地址和大小要跟硬件实际布局一致。多块内存可以写多个reg值。
4. chosen节点
chosen节点是个特殊的节点。它不描述硬件,而是描述内核启动时的参数。比如启动命令行、控制台设备、initrd位置等。
这个节点是内核和bootloader之间的桥梁。U-Boot会把一些启动参数填到这里,内核启动时直接读取。
chosen {
bootargs = "console=ttyO0,115200 root=/dev/mmcblk0p2 rw";
stdout-path = &uart0;
};
bootargs就是内核启动参数,跟你在U-Boot里设的bootargs环境变量一样。stdout-path指定控制台设备,内核启动时会把日志输出到这里。
我个人习惯把bootargs放在设备树里,而不是U-Boot的环境变量中。为什么?因为设备树跟着镜像走,换板子时不容易忘。我曾经吃过亏:U-Boot环境变量被意外清空了,板子起不来,折腾了半天才发现是bootargs丢了。
建议:chosen节点里还可以加linux,initrd-start和linux,initrd-end,指定initramfs的位置。不过现在大多数板子都用设备树里的bootargs就够了。
5. aliases节点
aliases节点是个"外号"列表。它给设备树里的节点起个简短的名字,方便引用。比如你有一个串口节点叫serial@44e09000,每次写全称太麻烦,给它起个外号叫serial0。
aliases {
serial0 = &uart0;
serial1 = &uart1;
ethernet0 = &cpsw_emac0;
mmc0 = &mmc1;
};
这里&uart0是引用语法,指向设备树里定义的uart0节点。内核启动时,会解析aliases节点,建立索引。比如你想知道哪个设备是第一个串口,直接查"serial0"就行了。
aliases节点在调试时特别有用。我记得有一次调试网卡驱动,内核报错说找不到"ethernet0"。我检查了设备树,发现aliases里写的是ethernet0 = &cpsw_emac0,但实际节点名是cpsw_emac@4a100000,引用错了。改过来就好了。
注意:aliases里的名字不能重复,也不能包含特殊字符。一般用"设备类型+数字"的格式,比如serial0、mmc1、i2c2等。
知识体系图
下面这张图展示了今天讲的几个节点之间的关系。你可以看到,根节点是老大,下面挂着cpus、memory、chosen、aliases等子节点。每个子节点又有自己的属性和子节点。
总结一下
今天咱们讲了五个核心节点:
- 根节点:设备树的起点,定义全局属性
- CPU节点:描述处理器信息,每个核心一个子节点
- 内存节点:告诉内核物理内存布局
- chosen节点:传递启动参数,连接bootloader和内核
- aliases节点:给节点起外号,方便引用
这几个节点搞明白了,设备树的基本框架就掌握了。下次咱们再深入看看外设节点怎么写,比如GPIO、I2C、SPI这些。嗯,今天就到这儿吧。
一句话记住:根节点是老大,CPU和内存是硬件基础,chosen是启动配置,aliases是快捷方式。把这四个节点写对,设备树就成功了一半。
可可精品资料库