DTS(设备树)

device tree的基本单元是node,这些node被组织成树状结构。除了root node,每个node都只有一个parent。一个device tree文件中只能有一个root node。每个node中包含了若干的property/value来描述该node的一些特性。每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性(后面会描述这个property),那么该节点名字中必须不能包括@和unit-address。unit-address的具体格式是和设备挂在那个bus上相关。例如对于cpu,其unit-address就是从0开始编址,以此加一。而具体的设备,例如以太网控制器,其unit-address就是寄存器地址。root node的node name是确定的,必须是“/”。

如果一个device node中包含了有寻址需求(要定义reg property)的sub node(child node,和sub node是一样的意思),那么就必须要定义这两个属性。“#”是number的意思,#address-cells这个属性是用来描述sub node中的reg属性的地址域特性的,也就是说需要用多少个u32的cell来描述该地址域。同理可以推断#size-cells的含义,下面对reg的描述中会给出更详细的信息。

reg属性定义了访问该device node的地址信息,该属性的值被解析成任意长度的(address,size)数组,具体用多长的数据来表示address和size是在其parent node中定义(#address-cells和#size-cells)。对于device node,reg描述了memory-mapped IO register的offset和length。对于memory node,定义了该memory的起始地址和长度。

本例中的物理内存的布局并没有通过memory node传递,其实我们可以使用command line传递,我们command line中的参数“mem=64M@0x30000000”已经给出了具体的信息。我们用另外一个例子来加深对本节描述的各个属性以及memory node的理解。假设我们的系统是64bit的,physical memory分成两段,定义如下:

RAM: starting address 0x0, length 0x80000000 (2GB)
RAM: starting address 0x100000000, length 0x100000000 (4GB)

对于这样的系统,我们可以将root node中的#address-cells和#size-cells这两个属性值设定为2,可以用下面两种方法来描述物理内存:

方法1:

memory@0 {
    device_type = "memory";
    reg = <0x000000000 0x00000000 0x00000000 0x80000000
              0x000000001 0x00000000 0x00000001 0x00000000>;
};

方法2:

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

memory@100000000 {
    device_type = "memory";
    reg = <0x000000001 0x00000000 0x00000001 0x00000000>;
};


概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。比如在ARM Linux内,一个**.dts(device tree source)文件对应一个ARM的machine,一般放置在内核的"arch/arm/boot/dts/"目录内,比如exynos4412参考板的板级设备树文件就是"arch/arm/boot/dts/exynos4412-origen.dts"。这个文件可以通过$make dtbs命令编译成二进制的.dtb文件**供内核驱动使用。

基于同样的软件分层设计的思想,由于一个SoC可能对应多个machine,如果每个machine的设备树都写成一个完全独立的**.dts文件**,那么势必相当一些**.dts文件有重复的部分,为了解决这个问题,Linux设备树目录把一个SoC公用的部分或者多个machine共同的部分提炼为相应的 .dtsi 文件。这样每个**.dts就只有自己差异的部分,公有的部分只需要"include"相应的.dtsi文件**, 这样就是整个设备树的管理更加有序。


DTS即Device Tree Source 设备树源码, 是一种描述硬件的数据结构

DTS的加载过程

  1. 用户根据解自己的硬件配置和系统运行参数,编写DTS文件
  2. DTC(Device Tree Compiler)将DTS文件变成适合机器处理的DTB文件(Device Tree binary )
  3. 系统启动时,通过bootloader的交互式命令加载DTB到内核

Device Tree描述的信息

  • CPU的数量和类别
  • 内存基地址和大小
  • 总线和桥
  • 外设连接
  • 中断控制器和中断使用情况
  • GPIO控制器和GPIO使用情况
  • Clock控制器和Clock使用情况

Device Tree的结构

由一系列被命名的结点(node)和属性(property)组成,

/   -------------根节点

@  ------------如果设备有地址,则由此符号指定

&   -------------引用节点

:   ---------------冒号前的label是为了方便引用给节点起的别名,此label一般使用为&label

,   -------------- 属性名称中可以包含逗号。如compatible属性的名字 组成方式为"[manufacturer], [model]",加入厂商名是为了避免重名。自定义属性名中通常也要有厂商名,并以逗号分隔。

#  -------------并不表示注释。如 #address-cells ,#size-cells 用来决定reg属性的格式。

    -----------空属性并不一定表示没有赋值。如 interrupt-controller 一个空属性用来声明这个node接收中断信号

“”   --------------引号中的为字符串,字符串数组:”strint1”,”string2”,”string3”

< >  ------------尖括号中的为32位整形数字,整形数组<12 3 4>

[ ]    --------------方括号中的为32位十六进制数,十六机制数据[0x11 0x12 0x13]  其中0x可省略

结点本身可包含子结点。

/ {  //根节点
	node1 {   //子结点
		a-string-property = "A string";  
		a-string-list-property = "first string", "second string";  
		a-byte-data-property = [0x01 0x23 0x34 0x56];  
		child-node1 {  //子节点的子节点
			first-child-property;  
			second-child-property = <1>;  
			a-string-property = "Hello, world";  
		};  
		child-node2 {  
		};  
	};  
	node2 {  //子节点
		an-empty-property;  
		a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */  
		child-node1 {  
		};  
	};  
};
#address-cells = <1>;  
#size-cells = <1>;  
external-bus {  
		#address-cells = <2>  
		#size-cells = <1>;  
		ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet  
                     1 0  0x10160000   0x10000     // Chipselect 2, i2c controller  
                     2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash  
  
		ethernet@0,0 {  
			compatible = "smc,smc91c111";  
			reg = <0 0 0x1000>;  
			interrupts = < 5 2 >;  
		};  
  
		i2c@1,0 {  
			compatible = "acme,a1234-i2c-bus";  
			#address-cells = <1>;  
			#size-cells = <0>;  
			reg = <1 0 0x1000>;  
			rtc@58 {  
				compatible = "maxim,ds1338";  
				reg = <58>;  
				interrupts = < 7 3 >;  
			};  
		};  
  
		flash@2,0 {  
			compatible = "samsung,k8f1315ebm", "cfi-flash";  
			reg = <2 0 0x4000000>;  
		};  
	};  
}; 

属性,其实就是成对出现的name和value

compatible属性

  1. 组织形式----------",",manufacturer表征了结点代表的确切设备,特指。model表征可兼容的其他设备,泛指。
  2. 作用-----------用户驱动和设备的绑定

name属性

  1. 组织形式----------[@],name指结点对应的设备类型,@unit-address表示结点设备的地址
  2. 作用-----------标识设备,多个相同类型设备结点的name可以一样,只要unit-address不同即可

reg属性

  1. 组织形式---------reg =
  2. 作用----------表明了设备使用的一个地址范围。父结点#address-cells和#size-cells分别决定了子结点reg属性的address和length字段的长度,子节点有特殊需求的话,可以自己再定义,这样就可以摆脱父节点的控制。
  3. address-cells=2----------一个是片选序号,另一个是片选序号上的偏移量,表示挂载在外部总线上,需要通过片选线工作的一些模块。

ranges属性

  1. 组织形式---------片选0,偏移0,被映射到CPU地址空间的0x10100000~0x10110000中,地址长度为0x10000(第一行)。
  2. 作用---------地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射。由子节点的address-cells的值、父节点的address-cells的值和子节点的size-cells来决定。

描述中断连接需要四个属性:

intc: interrupt-controller@10140000 {  
		compatible = "arm,pl190";  
		reg = <0x10140000 0x1000 >;  
		interrupt-controller;  
		#interrupt-cells = <2>;  
	};

 1. interrupt-controller 一个空属性用来声明这个node接收中断信号;
2. #interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符;
3. interrupt-parent 标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的;
4. interrupts 一个中断标识符列表,表示每一个中断输出信号。

二个cell的情况

spi@10115000 {  
		compatible = "arm,pl022";  
		reg = <0x10115000 0x1000 >;  
		interrupts = < 4 0 >;  
	}; 

第一个值: 该中断位于他的中断控制器的索引;

第二个值:触发的type

固定的取值如下:

        1 (0001)= low-to-high edge triggered
        2 (0010)= high-to-low edge triggered
        4 (0100)= active high level-sensitive
        8 (1000)= active low level-sensitive

 

三个cell的情况

第一个值:中断号

第二个值:触发的类型

第三个值:优先级,0级是最高的,7级是最低的;其中0级的中断系统当做 FIQ处理。

 

DTS设备树描述文件中什么代表总线,什么代表设备

一个含有compatible属性的节点就是一个设备。包含一组设备节点的父节点即为总线。

---------------------------------------------------------


Linux中的设备树还包括几个特殊的节点,比如chosen,chosen节点不描述一个真实设备,而是用于firmware传递一些数据给OS,比如bootloader传递内核启动参数给内核。

引用 当我们找一个节点的时候,我们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名),借以省去冗长的路径。这样就可以实现类似函数调用的效果。编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写,使用引用可以避免移植者四处找节点,直接在板级.dts增改即可。

在设备树中,键值对是描述属性的方式,比如,Linux驱动中可以通过设备节点中的**"compatible"这个属性查找设备节点。 Linux设备树语法中定义了一些具有规范意义的属性,包括:compatibleaddressinterrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有 "mac_addr""gpio""clock""power""regulator"** 等等。

compatible 设备节点中对应的节点信息已经被内核构造成struct platform_device。驱动可以通过相应的函数从中提取信息。compatible属性是用来查找节点的方法之一,另外还可以通过节点名或节点路径查找指定节点

#address-cells,用来描述子节点 reg 属性的地址表中用来描述首地址的cell的数量

#size-cells,       用来描述子节点 reg 属性的地址表中用来描述地址长度的cell的数量

DTB的编译

DTB(Devicetree Blob)是DTS的二进制文件格式,Kernel使用DTC工具将DTS源文件编译成DTB,bootloader再将DTB文件传递给Kernel解析。

不遵守标准书写的DTS文件在编译的时候会报错。

DTB的文件结构

这里写图片描述
DTB文件的结构如上图所示,主要在3部分:

struct ftd_header。文件头结构;
structure block。存放含Node和Property的Value;
strings block。存放Property的Name;把Property Name单独分为一个区域的原因是,有很多Property Name是重复的,单独一个区域可以使用指针引用,节约空间。
dtb中的fdt_header的数据结构:

struct fdt_header {
    uint32_t magic;
    uint32_t totalsize;
    uint32_t off_dt_struct;
    uint32_t off_dt_strings;
    uint32_t off_mem_rsvmap;
    uint32_t version;
    uint32_t last_comp_version;
    uint32_t boot_cpuid_phys;
    uint32_t size_dt_strings;
    uint32_t size_dt_struct;
};

dtb中node header的数据结构:

struct fdt_node_header {
    fdt32_t tag;
    char name[0];   // node name 存放在structure block
};

dtb中property header的数据结构:

struct fdt_property {
    fdt32_t tag;
    fdt32_t len;
    fdt32_t nameoff;    // perperty name存放在strings block
    char data[0];
};

整个文件使用5种token来分割出node和property:

FDT_BEGIN_NODE (0x00000001)
FDT_END_NODE (0x00000002)
FDT_PROP (0x00000003)
FDT_NOP (0x00000004)
FDT_END (0x00000009)
可以使用hex编辑器来查看DTB文件的结构:
————————————————

设备树文件的格式为dts,包含的头文件格式为dtsi,dts文件是一种人可以看懂的编码格式。但是uboot和linux不能直接识别,他们只能识别二进制文件,所以需要把dts文件编译成dtb文件。dtb文件是一种可以被kernel和uboot识别的二进制文件。把dts编译成dtb文件的工具是dtc。Linux源码目录下scripts/dtc目录包含dtc工具的源码。在Linux的scripts/dtc目录下除了提供dtc工具外,也可以自己安装dtc工具,linux下执行:sudo apt-get install device-tree-compiler安装dtc工具。其中还提供了一个fdtdump的工具,可以反编译dtb文件。dts和dtb文件的转换如图所示。 
dtc工具的使用方法是:dtc –I dts –O dtb –o xxx.dtb xxx.dts,即可生成dts文件对应的dtb文件了。 


在编译linux内核时。也可以直接make dtbs生成dtb文件。
————————————————
 

你可能感兴趣的:(——Linux驱动)