4、CPU节点配置:cpus节点、cpu-map、cpu节点属性
好,咱们进入设备树多核配置的核心部分了。说实话,CPU节点的配置是整个设备树里最「敏感」的一块——配错了轻则少核,重则系统起不来。我这些年调试过的板子里,至少有三成启动问题都出在CPU节点上。
先理清一个概念:设备树里描述CPU,不是简单列几个核就完事。你得告诉内核:有多少个核?每个核是什么架构?主频多少?它们之间怎么互联?嗯,这些信息都藏在 cpus 节点和它的子节点里。
4.1 cpus节点:CPU家族的「户口本」
cpus 节点是所有CPU描述的老祖宗。它本身不描述具体CPU,而是定义一些全局属性,比如地址位数、缓存大小等。我习惯把它比作「CPU家族的户口本」——先说明这个家族的基本情况,再逐个介绍每个成员。
看一个典型的 cpus 节点:
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
reg = <0x0 0x0>;
compatible = "arm,cortex-a72";
...
};
cpu@1 {
device_type = "cpu";
reg = <0x0 0x1>;
compatible = "arm,cortex-a72";
...
};
};
这里 #address-cells = <2> 是什么意思?说白了,就是告诉解析器:每个CPU的编号(reg属性)要用两个32位整数来表示。为什么是2?因为有些ARM芯片的MPIDR寄存器是64位的,低32位存集群ID和核ID,高32位可能存其他信息。我见过有人图省事写成1,结果四核芯片只认出来两个核——血的教训。
#size-cells 必须设为0。因为CPU节点不需要描述地址范围,它只需要一个编号。设成非0值会导致解析器行为异常,内核启动时可能直接panic。
4.2 cpu-map:多簇CPU的「导航图」
现在很多芯片不止一个CPU簇。比如一个大核簇(Cortex-A76)加一个小核簇(Cortex-A55),这就是所谓的big.LITTLE架构。这时候光靠 cpus 节点就不够了——你得告诉内核:哪些核属于同一个簇?哪个核是主核?
cpu-map 就是干这个的。它像一张导航图,把物理CPU编号映射到逻辑拓扑上。我去年调一个联发科平台时,就靠它解决了大小核调度问题。
看个例子:
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu-map {
cluster0 {
core0 {
cpu = <&cpu0>;
};
core1 {
cpu = <&cpu1>;
};
};
cluster1 {
core0 {
cpu = <&cpu2>;
};
core1 {
cpu = <&cpu3>;
};
};
};
cpu0: cpu@0 {
device_type = "cpu";
reg = <0x0 0x0>;
...
};
cpu1: cpu@100 {
device_type = "cpu";
reg = <0x0 0x100>;
...
};
// 以此类推
};
这里 cluster0 和 cluster1 代表两个簇。每个簇下面有 core0、core1 等子节点,通过 cpu 属性引用具体的CPU节点。为什么要这么麻烦?因为物理上CPU的编号可能不连续——比如大核从0x000开始,小核从0x100开始。没有 cpu-map,内核根本不知道哪个核在哪个簇里。
cpu-map 不是必须的,但强烈建议加上。为什么?因为Linux内核的调度器(如EAS)依赖这个信息来做功耗优化。不加的话,大小核调度策略可能失效,导致功耗飙升。我曾经在一个项目里漏掉了 cpu-map,结果小核永远跑不满,大核一直满载——电池续航直接砍半。
4.3 cpu节点属性:每个CPU的「身份证」
每个CPU子节点都有自己的属性。这些属性就像身份证上的信息,告诉内核这个CPU是什么、能跑多快、有什么特性。咱们挑几个关键的说说。
4.3.1 reg属性:CPU的唯一编号
reg 是CPU节点的核心属性。它对应硬件上的MPIDR寄存器值,内核用它来区分不同的CPU。注意,这个值必须是唯一的,不能重复。
对于ARM64,reg 通常是64位的:
cpu@0 {
reg = <0x0 0x0>; // 低32位: 0x0, 高32位: 0x0
};
cpu@1 {
reg = <0x0 0x1>; // 低32位: 0x0, 高32位: 0x1
};
为什么是两段?因为 #address-cells = <2>。如果芯片只有4个核,且MPIDR只用低8位,你可以写成 #address-cells = <1>,然后 reg = <0x0>、reg = <0x1> 这样。但我个人建议统一用2,方便以后扩展。
reg 值一定要跟硬件手册上的MPIDR一一对应,别想当然。
4.3.2 clock-frequency属性:CPU的主频
clock-frequency 指定CPU的当前运行频率,单位是Hz。这个属性不是必须的,但很多驱动会用它来做频率相关的初始化。
cpu@0 {
clock-frequency = <2000000000>; // 2GHz
};
注意,这个值只是「初始频率」。实际运行中,内核的cpufreq子系统会根据负载动态调整频率。所以,别指望设了这个值就能锁频——那是 clocks 属性和时钟控制器的事。
我见过有人把 clock-frequency 写成 2000(单位是MHz),结果内核以为CPU只有2kHz,调度器直接罢工。嗯,单位一定要用Hz,别偷懒。
4.3.3 其他常用属性
除了 reg 和 clock-frequency,还有几个属性值得关注:
| 属性名 | 说明 | 示例值 |
|---|---|---|
device_type |
固定为 "cpu",告诉内核这是个CPU节点 | "cpu" |
compatible |
CPU的架构和型号,用于匹配驱动 | "arm,cortex-a72" |
enable-method |
CPU启动方式,如 "psci"、"spin-table" | "psci" |
next-level-cache |
指向L2或L3缓存节点 | &L2_0 |
cpu-idle-states |
指向CPU idle状态描述节点 | &CPU_SLEEP_0 |
这里重点说一下 enable-method。它决定了CPU怎么从启动状态进入工作状态。对于ARM64,现在主流是用PSCI(Power State Coordination Interface)。你想想看,如果没有这个属性,内核怎么知道该调用哪个接口来唤醒其他核?
reg 是身份标识,compatible 是驱动匹配,enable-method 是启动协议。少一个,内核就可能「迷路」。
4.4 实战:一个完整的CPU节点配置
最后,咱们看一个完整的例子。这是一个双簇、每簇4核的ARM64芯片配置:
/dts-v1/;
/ {
model = "MyCustomBoard";
compatible = "mycompany,myboard";
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu-map {
cluster0 {
core0 { cpu = <&cpu0>; };
core1 { cpu = <&cpu1>; };
core2 { cpu = <&cpu2>; };
core3 { cpu = <&cpu3>; };
};
cluster1 {
core0 { cpu = <&cpu4>; };
core1 { cpu = <&cpu5>; };
core2 { cpu = <&cpu6>; };
core3 { cpu = <&cpu7>; };
};
};
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a76";
reg = <0x0 0x0>;
enable-method = "psci";
clock-frequency = <2200000000>;
next-level-cache = <&L2_0>;
};
cpu1: cpu@100 {
device_type = "cpu";
compatible = "arm,cortex-a76";
reg = <0x0 0x100>;
enable-method = "psci";
clock-frequency = <2200000000>;
next-level-cache = <&L2_0>;
};
// ... 其他CPU类似
cpu4: cpu@10000 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x10000>;
enable-method = "psci";
clock-frequency = <1800000000>;
next-level-cache = <&L2_1>;
};
// ... 小核簇其他CPU
};
L2_0: l2-cache0 {
compatible = "cache";
cache-level = <2>;
};
L2_1: l2-cache1 {
compatible = "cache";
cache-level = <2>;
};
};
看到没?大核簇的reg从0x000开始,小核簇从0x10000开始。这样内核通过 cpu-map 就知道哪些核属于同一个簇,调度器就能做出更智能的决策。
嗯,CPU节点配置就讲到这里。下一节咱们聊聊CPU的启动方式——spin-table和PSCI,这可是多核启动的关键。到时候我会分享一个我调试PSCI时遇到的「诡异」问题,保证让你印象深刻。