4、reg属性深度解析:reg属性格式、address-cells与size-cells、多段内存区域描述、实战:描述一个SPI Flash
好,咱们今天来啃一块硬骨头——reg属性。
说实话,我刚接触设备树那会儿,最头疼的就是这个reg。看着一堆数字,完全不知道它在说什么。后来做BSP调优,踩了不少坑,才慢慢摸透了它的脾气。
今天我就把这块掰开了揉碎了讲给你听。保证你学完就能自己写。
4.1 reg属性到底长什么样?
先看一个最简单的例子:
uart@ff010000 {
compatible = "ns16550";
reg = <0xff010000 0x1000>;
};
这个reg里有两个数字:0xff010000 和 0x1000。
第一个是起始地址,第二个是长度。说白了,就是告诉内核:「我的寄存器从0xff010000开始,占了4KB的空间,你可别让别人碰这里。」
核心公式:
reg = < 地址1 长度1 地址2 长度2 ... >
每一对「地址+长度」描述一段连续的内存区域。
嗯,这里要注意:地址和长度都是32位整数。那如果地址是64位的怎么办?这就引出了下一个话题。
4.2 address-cells 和 size-cells——reg的翻译官
你想想看,CPU读设备树的时候,怎么知道reg里哪个数字是地址、哪个是长度?
答案就在父节点里:#address-cells 和 #size-cells。
- #address-cells:表示地址需要几个32位整数来描述
- #size-cells:表示长度需要几个32位整数来描述
举个例子:
soc {
#address-cells = <1>;
#size-cells = <1>;
uart@ff010000 {
reg = <0xff010000 0x1000>;
};
};
这里address-cells=1,size-cells=1。所以reg里每两个数一组:第一个是地址,第二个是长度。
那64位地址怎么写?看这个:
soc {
#address-cells = <2>;
#size-cells = <1>;
memory@8000000000 {
reg = <0x00000008 0x00000000 0x10000000>;
};
};
地址用了两个32位数拼成64位:0x00000008_00000000。长度还是1个数:0x10000000(256MB)。
我的小技巧:
我习惯在写设备树前,先看一眼父节点的address-cells和size-cells。这俩值决定了reg的解析方式。搞反了,地址和长度就全乱了。
4.3 多段内存区域描述
一个设备可能有多段不连续的地址空间。比如一个网卡,控制寄存器在一块区域,DMA缓冲在另一块区域。
这时候reg可以写多组:
ethernet@ff300000 {
compatible = "my-gmac";
reg = <0xff300000 0x1000 /* 控制寄存器 */
0xff310000 0x2000>; /* DMA描述符区 */
};
内核解析时,会按顺序把每组地址-长度对存到struct resource数组里。驱动里用platform_get_resource()按索引取就行了。
我曾经踩过的坑:
有一次我写SPI Flash的reg,把地址和长度写反了。结果内核报错说「resource overlap」。查了两天才发现,原来是address-cells和size-cells没对上。从那以后,我每次写reg都会先确认父节点的这俩属性。
4.4 实战:描述一个SPI Flash
好,理论说完了。咱们来写一个真实的SPI Flash设备树节点。
假设硬件是这样的:
- SPI控制器挂在APB总线上,地址0x10000000,大小4KB
- Flash芯片是Winbond W25Q128,容量16MB
- Flash的地址范围从0x20000000到0x20FFFFFF
先画个结构图,帮你理清关系:
好,现在写设备树:
/ {
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
spi@10000000 {
compatible = "my-spi-controller";
reg = <0x10000000 0x1000>;
interrupts = <0 31 4>;
clocks = <&clk 10>;
#address-cells = <1>;
#size-cells = <1>;
flash@0 {
compatible = "winbond,w25q128", "jedec,spi-nor";
reg = <0x20000000 0x1000000>;
spi-max-frequency = <104000000>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "bootloader";
reg = <0x00000000 0x00100000>;
};
partition@1 {
label = "kernel";
reg = <0x00100000 0x00800000>;
};
partition@2 {
label = "rootfs";
reg = <0x00900000 0x00700000>;
};
};
};
};
};
};
这里有几个关键点:
- SPI控制器的reg:描述它自己的寄存器空间
- Flash的reg:描述Flash在CPU地址空间中的映射范围
- 分区reg:描述每个分区在Flash内部的偏移和大小
注意:
Flash的reg地址0x20000000,是CPU访问Flash的映射基地址,不是Flash芯片内部的地址。芯片内部地址从0开始,由驱动通过SPI命令访问。
我个人习惯在写这种多层嵌套的reg时,先画个地址映射表:
| 节点 | 地址 | 长度 | 说明 |
|---|---|---|---|
| spi@10000000 | 0x10000000 | 0x1000 | SPI控制器寄存器 |
| flash@0 | 0x20000000 | 0x1000000 | Flash内存映射区(16MB) |
| partition@0 | 0x00000000 | 0x00100000 | Bootloader(1MB) |
| partition@1 | 0x00100000 | 0x00800000 | Kernel(8MB) |
| partition@2 | 0x00900000 | 0x00700000 | Rootfs(7MB) |
你看,有了这个表,每个reg在说什么就一目了然了。
避坑指南:
我曾经在调试一个SPI Flash驱动时,发现分区读取总是偏移。查了半天,原来是分区reg的地址写成了绝对地址(0x20000000+偏移),而不是相对Flash基地址的偏移。记住:分区的reg是相对地址,不是绝对地址。
好了,reg属性的核心内容就这些。说白了就是三件事:
- 搞清楚父节点的address-cells和size-cells
- 按「地址+长度」的格式写reg
- 多段区域就写多组
下次你写设备树的时候,照着这个思路来,基本不会出错。
可可精品资料库