Linux设备树学习(一)基本知识点

参考:https://blog.csdn.net/qq_35031421/article/details/105002645

一、设备树结构示例

jz2440 的设备树:
s3c2440-jz2440.dts
	  (include)----> s3c2440.dtsi
        	(include)----> s3c24xx.dtsi
               		 (include)----> skeleton.dtsi
        	(include)----> s3c2440-pinctrl.dtsi 

make dtbs 后的设备树:
s3c2440-jz2440.dtb

设备树是一种描述硬件的数据结构。
(1) DTS: .dts文件是设备树的源文件。
(2) DTSI:一个系列的多款开发板可能包含很多共同的部分,共同的部分一般被提炼为一个或多个.dtsi 文件。
(3) DTC: DTC是将.dts编译为.dtb的工具,相当于gcc。
(4) DTB: .dtb文件是 .dts 被 DTC 编译后的二进制格式的设备树文件,它可以被linux内核解析。

设备树像上述一层层包含的好处:
(1)在同名节点中,后出现的同种属性会覆盖前面的属性,比如,

s3c2440-jz2440.dts
	  (include)----> s3c2440.dtsi

这两个设备树中假如都有/uart0节点,但是status属性不同,那么会以s3c2440.dtsi的为准!!!!!!
(2)在同名节点中,不同种属性会合并到同名节点中,比如,
s3c2440-jz2440.dts有/uart0节点的status属性,s3c2440.dtsi有/uart0节点的reg属性,那么会合并为/uart0的属性!

二、节点

设备树中节点命名格式
node-name@unit-address
node-name: 设备节点的名称。节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设;
unit-address: 一般表示设备寄存器首地址或者index。如果某个节点没有寄存器或者地址的话 “unit-address” 可以不要;

节点示例:

在上图中:多个cpu 和 ethernet依靠不同的unit-address 来分辨;可见,node-name相同的情况下,可以通过不同的unit-address定义不同的设备节点。

2.1 特殊节点

/ 根节点

没有node-name 或者 unit-address,它被定义为 /。

/aliases 子节点

aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。例如:定义 flexcan1 和 flexcan2 的别名是 can0 和 can1。

aliases {
	can0 = &flexcan1;
	can1 = &flexcan2;
};

/memory 子节点

所有设备树都需要一个memory设备节点,它描述了系统的物理内存布局。如果系统有多个内存块,可以创建多个memory节点,或者可以在单个memory节点的reg属性中指定这些地址范围和内存空间大小。
例如:一个64位的系统有两块内存空间:RAM1: 起始地址是0x0,地址空间是 0x80000000;RAM2: 起始地址是0x10000000,地址空间也是0x80000000;同时根节点/下的 #address-cells = <2>和#size-cells = <2>,这个memory节点描述为:

memory@0 {
	device_type = "memory";
	reg = <0x00000000 0x00000000 0x00000000 0x80000000
		   0x00000000 0x10000000 0x00000000 0x80000000>;
};

或者:

memory@0 {
	device_type = "memory";
	reg = <0x00000000 0x00000000 0x00000000 0x80000000>;
};
memory@10000000 {
	device_type = "memory";
	reg = <0x00000000 0x10000000 0x00000000 0x80000000>;
};

/chosen 子节点

chosen 并不是一个真实的设备, 主要是为了向 Linux 内核传递数据,重点是 bootargs 参数。

chosen {
	bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};

/cpus 和 /cpus/cpu* 子节点

/cpus节点下有1个或多个cpu子节点,
/cpus节点的 #size-cells 必须设置为0,
/cpus/cpu* 子节点中用reg属性用来标明自己是哪一个cpu。

// RK3288:
cpus {
		#address-cells = <1>;
		#size-cells = <0>;

		cpu0: cpu@500 {
			device_type = "cpu";
			compatible = "arm,cortex-a12";
			reg = <0x500>;
			resets = <&cru SRST_CORE0>;
			operating-points = <
				/* KHz    uV */
				1608000 1350000
				1512000 1300000
			>;
			#cooling-cells = <2>; /* min followed by max */
			clock-latency = <40000>;
			clocks = <&cru ARMCLK>;
		};
		cpu1: cpu@501 {
			device_type = "cpu";
			compatible = "arm,cortex-a12";
			reg = <0x501>;
			resets = <&cru SRST_CORE1>;
		};
		cpu2: cpu@502 {
			device_type = "cpu";
			compatible = "arm,cortex-a12";
			reg = <0x502>;
			resets = <&cru SRST_CORE2>;
		};
		cpu3: cpu@503 {
			device_type = "cpu";
			compatible = "arm,cortex-a12";
			reg = <0x503>;
			resets = <&cru SRST_CORE3>;
		};
	};

2.2 节点属性

compatible

manufacturer,model
manufacturer : 表示厂商; model : 一般是模块对应的驱动名字。设备树中的设备 与 内核中的驱动 利用该值进行绑定。
例如:
compatible = "fsl,mpc8641", "ns16550";
上面的compatible有两个属性,分别是 “fsl,mpc8641” 和 “ns16550”;设备首先会使用第一个属性值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件;如果没找到,就使用第二个属性值查找。
注:一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。

model

用于标识开发板(名字),推荐的格式是“manufacturer,model-name”
model = "Samsung S3C2416 SoC";

phandle

phandle属性为devicetree中唯一的节点指定一个数字标识符,节点中的phandle属性,它的取值必须是唯一的(不要跟其他的phandle值一样),例如:

pic@10000000 {
    phandle = <1>;
    interrupt-controller;
};
another-device-node {
    interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点
};

注:DTS中的大多数设备树将不包含显式的phandle属性,当DTS被编译成二进制DTB格式时,DTC工具会自动插入phandle属性。

status

字符串,描述设备的状态信息,可选的状态如下表所示:
status值 描述
“okay” 表明设备是可操作的。
“disabled” 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于 disabled 的具体含义还要看设备的绑定文档。
“fail” 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。
“fail-sss” 含义和“fail”相同,后面的 sss 部分是检测到的错误内容

#address-cells 和 #size-cells

#address-cells#size-cells 表明了子节点应该如何编写 reg 属性值:起始地址(address)和地址长度(size)。
#address-cells 决定了在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address),
#size-cells 决定了在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)、(len)。

reg = <address1 size1 address2 size2 address3 size3 ……>

例如一个64位的处理器:

soc {
	#address-cells = <2>; /*子节点的reg中,addr占64位*/
	#size-cells = <1>;    /*子节点的reg中,size占32位*/
	serial {
		compatible = "xxx";
		reg = <0x4600 0x5000 0x100>;  /*地址信息是64位:0x00004600 00005000,长度信息是32位:0x00000100*/
		};
};

reg 属性

reg 属性的值一般是 (address, length) ,reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。
例如:一个设备有两个寄存器块,一个的地址是0x3000,占据32字节;另一个的地址是0xFE00,占据256字节,表示如下:
reg = <0x3000 0x20 0xFE00 0x100>;
注:上述对应#address-cells = <1>;(32位) #size-cells = <1>;(32位)

device_type 属性

此属性只能用于 cpu 节点或者 memory 节点。

#include "rk3288.dtsi"
/ {
	memory@0 {
		device_type = "memory";
                      /*高32 低32 高32 低32*/
		reg = <0x0 0x0 0x0 0x80000000>;
	};

ranges 属性(略)

ranges属性值可以为空或者按照 (child-bus-address,parent-bus-address,length) 格式编写的数字矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
child-bus-address: 子总线地址空间的物理地址,由父节点的 #address-cells 确定此物理地址所占用的字长。
parent-bus-address: 父总线地址空间的物理地址,同样由父节点的 #address-cells 确定此物理地址所占用的字长。
length: 子地址空间的长度,由父节点的 #size-cells 确定此地址长度所占用的字长。

soc {
	compatible = "simple-bus";
	#address-cells = <1>;
	#size-cells = <1>;
	ranges = <0x0 0xe0000000 0x00100000>;
	serial {
		device_type = "serial";
		compatible = "ns16550";
		reg = <0x4600 0x100>;
		clock-frequency = <0>;
		interrupts = <0xA 0x8>;
		interrupt-parent = <&ipic>;
		};
};

soc的ranges值 <0x0 0xe0000000 0x00100000>,指定了一个 1024KB(0x00100000) 的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。
serial是串口设备节点, 它的reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过ranges属性的地址转换, serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。

name 属性(略)

name 属性值为字符串, name 属性用于记录节点名字, name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。

2.3 引用其他节点

phandle

pic@10000000 {
    phandle = <1>;
    interrupt-controller;
};

another-device-node {
    interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点
};

label

PIC: pic@10000000 {
    interrupt-controller;
};

another-device-node {
    interrupt-parent = <&PIC>;   // 使用label来引用上述节点, 
                                 // 使用lable时实际上也是使用phandle来引用, 
                                 // 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性
};

三、DTB格式

DTB文件主要包含四部分内容:
① struct ftd_header: 用来表明各个分部的偏移地址,整个文件的大小,版本号等;
② memory reservation block: 在设备树中使用/memreserve/ 定义的保留内存信息;
③ structure block: 保存节点的信息,节点的结构;
④ strings block: 保存属性的名字,单独作为字符串保存;
总结:
(1) DTB文件可以分为四个部分:struct ftd_header、memory reservation block、structure block、strings block;
(2) 最开始的为struct ftd_header,包含其它三个部分的偏移地址;
(3) memory reservation block记录保留内存信息;
(4) structure block保存节点的信息,节点的结构;
(5) strings block保存属性的名字,将属性名字单独作为字符串保存;

struct ftd_header结构体的定义如下:

struct fdt_header {
	uint32_t magic; /*它的值为0xd00dfeed,以大端模式保存*/
	uint32_t totalsize; /*整个DTB文件的大小*/
	uint32_t off_dt_struct; /*structure block的偏移地址*/
	uint32_t off_dt_strings; /*strings block的偏移地址*/
	uint32_t off_mem_rsvmap; /*memory reservation block的偏移地址*/
	uint32_t version; /*设备树版本信息*/
	uint32_t last_comp_version; /*向后兼容的最低设备树版本信息*/
	uint32_t boot_cpuid_phys; /*CPU ID*/
	uint32_t size_dt_strings; /*strings block的大小*/
	uint32_t size_dt_struct; /*structure block的大小*/
};

fdt_reserve_entry结构体如下:
该结构体用于表示memreserve的起始地址和内存空间的大小,它紧跟在struct ftd_header结构体后面。例如:/memreserve/ 0x33000000 0x10000,fdt_reserve_entry 结构体的成员 address = 0x33000000,size = 0x10000。

struct fdt_reserve_entry {
	uint64_t address;  /*64bit 的地址*/
	uint64_t size;    /*保留的内存空间的大小*/
};

structure block是用于描述设备树节点的结构,保存着节点的信息、节点的结构,它有5种标记类型:
① FDT_BEGIN_NODE (0x00000001):表示节点的开始,它的后面紧跟的是节点的名字;
② FDT_END_NODE (0x00000002):表示节点的结束;
③ FDT_PROP (0x00000003) :表示开始描述节点里面的一个属性,在FDT_PROP后面紧跟一个结构体如下所示:

struct {
	uint32_t len;       /*表示属性值的长度*/
	uint32_t nameoff;   /*属性的名字在string block的偏移*/
} 
注:这个结构体后紧跟着是属性值,属性的名字保存在字符串块(Strings block)中。

④ FDT_END (0x00000009):表示structure block的结束。

posted @ 2022-07-25 15:20  solonj  阅读(533)  评论(0编辑  收藏  举报