DTS即Device Tree Source 设备树源码, Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF),现在小编就来说说关于linux运维磁盘使用教程学习,linuxdevicetree?下面内容希望能帮助到你,我们来一起看看吧!

linux运维磁盘使用教程学习,linuxdevicetree

linux运维磁盘使用教程学习,linuxdevicetree

一、dts产生原因

DTS即Device Tree Source 设备树源码, Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。

它替代arch/ARM/plat-xxx和arch/arm/mach-xxx中的板级spec代码,便于code管理。

ARM平台的相关code相关规范调整:

1、ARM的核心代码仍然保存在arch/arm目录下

2、ARM SoC core architecture code保存在arch/arm目录下

3、ARM SOC的周边外设模块的驱动保存在drivers目录下

4、ARM SOC的特定代码在arch/arm/mach-xxx目录下

5、ARM SOC board specific的代码被移除,由Device Tree机制来负责传递硬件拓扑和硬件资源信息。

本质上,Device Tree改变了原来用hardcode方式将HW配置信息嵌入到内核代码的方法,改用Bootloader传递一些参数。

如果我们认为kernel是一个black box,那么其输入参数应该包括:

a.识别platform的信息 b. runtime的配置参数 c.设备的拓扑结构以及特性

对于嵌入式系统,在系统启动阶段,bootloader会加载内核并将控制权转交给内核,此外,还需要把上述的三个参数信息传递给kernel,以便kernel可以有较大的灵活性。在Linux kernel中,Device Tree的设计目标就

是如此。

二、dts基本知识2.1 dts加载过程

如果要使用Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成Device Tree source file。通过DTC(Device Tree Compiler),可以将这些适合人类阅读的Device Tree source file变成适合机器处理的Device Tree binary file(有一个更好听的名字,DTB,device tree blob)。在系统启动的时候,boot program(例如:firmware、bootloader)可以将保存在flash中的DTB copy到内存(当然也可以通过其他方式,例如可以通过bootloader的交互式命令加载DTB,或者firmware可以探测到device的信息,组织成DTB保存在内存中),并把DTB的起始地址传递给client program(例如OS kernel,bootloader或者其他特殊功能的程序)。对于计算机系统(computer system),一般是firmware->bootloader->OS,对于嵌入式系统,一般是bootloader->OS。

2.2 dts描述信息

Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):

CPU的数量和类别

内存基地址和大小

总线和桥

外设连接

中断控制器和中断使用情况

gpio控制器和GPIO使用情况

Clock控制器和Clock使用情况

它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

是否Device Tree要描述系统中的所有硬件信息?答案是否定的。基本上,那些可以动态探测到的设备是不需要描述的,例如USB device。不过对于SOC上的usb host controller,它是无法动态识别的,需要在device tree中描述。同样的道理,在computer system中,PCI device可以被动态探测到,不需要在device tree中描述,但是PCI bridge如果不能被探测,那么就需要描述之。

.dts文件是一种ASCII 文本格式的Device Tree描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM Linux在,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录。由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的machine对应的.dts就include这个.dtsi。

譬如在arch/arm/boot/dts/qcom/目录下,高通的很多.dtsi都include了skeleton.dtsi或者skeleton64.dtsi。

正常情况下所有的dts文件以及dtsi文件都含有一个根节点”/”,这样include之后就会造成有很多个“根节点”。 按理说 device tree既然是一个树,那么其只能有一个根节点,所有其他的节点都是派生于根节点的child node。其实Device Tree Compiler会对DTS的node进行合并,最终生成的DTB中只有一个root node.

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 tree中,如何引用一个node呢?要想唯一指定一个node必须使用full path,例如/node-name-1/node-name-2/node-name-N

2.3 dts组成结构示例

/ { "/" 表示root结点,该结点下有两个子结点node1和node2

node1 { 结点"node1"下又含有子结点,本例中为"child-node1" 和 "child-node2",各结点都有一系列属性

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>;Cells(由u32整数组成)

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 {

};

};

};

上述.dts文件并没有什么真实的用途,但它基本表征了一个Device Tree源文件的结构。

2.4 dts语法

完整的Device Tree可以将一个PCB摆在你眼前,下面我们一起来看一下:

下面以一个最简单的machine为例来看如何写一个.dts文件。假设此machine的配置如下:1个双核ARM Cortex-A9 32位处理器; ARM的local bus上的内存映射区域分布了2个串口(分别位于0x101F1000 和 0x101F2000)、GPIO控制器(位于0x101F3000)、SPI控制器(位于0x10115000)、中断控制器(位于0x10140000)和一个external bus桥; External bus桥上又连接了SMC SMC91111 Ethernet(位于0x10100000)、I2C控制器(位于0x10160000)、64MB NOR Flash(位于0x30000000); External bus桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338实时钟(I2C地址为0x58)。

其对应的.dts文件为:

/ {

compatible = "acme,coyotes-revenge";

#address-cells = <1>; 子结点需要一个cell描述地址

#size-cells = <1>; 子结点需要一个cell描述长度

interrupt-parent = <&intc>;

cpus {

#address-cells = <1>;

#size-cells = <0>;

cpu@0 {

compatible = "arm,cortex-a9";

reg = <0>;

};

cpu@1 {

compatible = "arm,cortex-a9";

reg = <1>;

};

};

serial@101f1000 { 串口

compatible = "arm,pl011";

reg = <0x101f1000 0x1000 >;

interrupts = < 1 0 >;

};

serial@101f2000 { 串口

compatible = "arm,pl011";

reg = <0x101f2000 0x1000 >;

interrupts = < 2 0 >;

};

gpio@101f3000 { GPIO控制器

compatible = "arm,pl061";

reg = <0x101f3000 0x1000

0x101f4000 0x0010>;

interrupts = < 3 0 >;

};

intc: interrupt-controller@10140000 { 中断控制器

compatible = "arm,pl190";

reg = <0x10140000 0x1000 >;

interrupt-controller;

#interrupt-cells = <2>;

};

spi@10115000 { spi控制器

compatible = "arm,pl022";

reg = <0x10115000 0x1000 >; 起始地址为0x10115000,长度为0x1000

interrupts = < 4 0 >;

};

external-bus { external bus桥

#address-cells = <2> 子结点需要两个cell描述地址,片选

#size-cells = <1>; 子结点需要一个cell描述长度

ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet 片选0 0,地址0x10100000 ,长度0x10000

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>; rtc需要一个cell描述地址

#size-cells = <0>; rtc不需要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>;

};

};

};

注释不是太多,下面来详细分类解释:

2.4.1 compatible

上述.dts文件中,root结点"/"的compatible 属性compatible = "acme,coyotes-revenge";定义了系统的名称,它的组织形式为:<manufacturer>,<model>。Linux内核透过root结点"/"的compatible 属性即可判断它启动的是什么machine。 在.dts文件的每个设备,都有一个compatible属性,compatible属性用户驱动和设备的绑定。compatible属性是一个字符串的列表,列表中的第一个字符串表征了结点代表的确切设备,形式为"<manufacturer>,<model>",其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。

如在arch/arm/boot/dts/vexpress-v2m.dtsi中的Flash结点:

flash@0,00000000 {

compatible = "arm,vexpress-flash", "cfi-flash";

reg = <0 0x00000000 0x04000000>,

<1 0x00000000 0x04000000>;

bank-width = <4>;

};

compatible属性的第2个字符串"cfi-flash"明显比第1个字符串"arm,vexpress-flash"涵盖的范围更广。

2.4.2 name@unit-address

接下来root结点"/"的cpus子结点下面又包含2个cpu子结点,描述了此machine上的2个CPU,并且二者的compatible 属性为"arm,cortex-a9"。 注意cpus和cpus的2个cpu子结点的命名,它们遵循的组织形式为:<name>[@<unit-address>],<>中的内容是必选项,[]中的则为可选项。name是一个ASCII字符串,用于描述结点对应的设备类型,如网卡适配器对应的结点name宜为ethernet,表示这个是网卡。如果一个结点描述的设备有地址,则应该给出@unit-address。多个相同类型设备结点的name可以一样,只要unit-address不同即可,如本例中含有cpu@0、cpu@1以及serial@101f0000与serial@101f2000这样的同名结点。设备的unit-address地址也经常在其对应结点的reg属性中给出。

2.4.3 reg address-cells size-cells

设备的地址特性根据一下几个属性来控制:

● reg

● #address-cells

● #size-cells

reg意为region,区域。格式为:

reg = <address1 length1 [address2 length2] [address3 length3]>;

父类的address-cells和size-cells决定了子类的相关属性要包含多少个cell,如果子节点有特殊需求的话,可以自己再定义,这样就可以摆脱父节点的控制。

address-cells决定了address1/2/3包含几个cell,size-cells决定了length1/2/3包含了几个cell。

例子2.4.3.1

root结点的#address-cells = <1>;和#size-cells = <1>;决定了serial、gpio、spi等结点的address和length字段的长度分别为1

serial@101f1000 {

compatible = "arm,pl011";

reg = <0x101f1000 0x1000 >;地址0x101f1000,长度 0x1000

interrupts = < 1 0 >;

};

例子2.4.3.2

i2c控制器模块下的rtc模块。因为I2C设备只是被分配在一个地址上,不需要其他任何空间,所以只需要一个address的cell就可以描述完整,不需要size-cells

/ {

compatible = "acme,coyotes-revenge";

#address-cells = <1>; 子结点需要一个cell描述地址

#size-cells = <1>; 子结点需要一个cell描述长度

interrupt-parent = <&intc>;

...

external-bus {

#address-cells = <2>

#size-cells = <1>;

...

i2c@1,0 {

compatible = "acme,a1234-i2c-bus";

#address-cells = <1>;重新写address-cells

#size-cells = <0>;重新写address-cells

reg = <1 0 0x1000>;

rtc@58 {

compatible = "maxim,ds1338";

reg = <58>;只需要一个address cell,不需要cell再描述长度

};

};

...

};

例子2.4.3.3

当需要描述的设备不是本地设备时,就需要描述一个从设备地址空间到CPU地址空间的映射关系,这里就需要用到ranges属性。还是以上边的external-bus举例

ranges属性为一个地址转换表。表中的每一行都包含了子地址、父地址、在自地址空间内的区域大小。他们的大小(包含的cell)分别由子节点的address-cells的值、父节点的address-cells的值和子节点的size-cells来决定。

/ {

compatible = "acme,coyotes-revenge";

#address-cells = <1>; 子结点 需要一个cell描述地址

#size-cells = <1>; 子结点 需要一个cell描述长度

interrupt-parent = <&intc>;

...

0 0 两个cell,由子节点external-bus的address-cells=<2>决定;

0x10100000 一个cell,由父节点(/)的address-cells=<1>决定;

0x10000 一个cell,由子节点external-bus的size-cells=<1>决定。

最终第一行说明的意思就是:片选0,偏移0(选中了网卡),被映射到CPU地址空间的0x10100000~0x10110000中,地址长度为0x10000。

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

};

进阶例子2.4.3.4

pci@0x10180000 {

compatible = "arm,versatile-pci-hostbridge", "pci";

reg = <0x10180000 0x1000>;

interrupts = <8 0>;

bus-ranges = <0 0>;

#address-cells = <3>

#size-cells = <2>;

ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000

0x02000000 0 0xa0000000 0xa0000000 0 0x10000000

0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;

};

像之前描述过的本地总线一样,PCI地址空间与CPU地址空间是完全分离的,所以这里需要通过定义ranges属性进行地址转化。

#address-cells定义PCI使用3个cell,并且PCI的地址范围通过两个单位就可以解读。所以,首先的问题就是,为什么需要用3个32位的cell来描述一个PCI地址。

这三个cell分别代表物理地址高位、中位、低位:

1 phys.high cell : npt000ss bbbbbbbb dddddfff rrrrrrrr

2 phys.mid cell : hhhhhhh hhhhhhhh hhhhhhhh hhhhhhh

3 phys.low cell : llllllll llllllll llllllll llllllll

PCI地址为64位宽度,编码在phys.mid和phys.low中。真正重要的东西在于phys.high这一位空间中:

n:代表重申请空间标志(这里没有使用)

p:代表预读空间(缓存)标志

t:别名地址标志(这里没有使用)

ss:空间代码

00: 设置空间

01:IO空间

10:32位存储空间

11:64位存储空间

bbbbbbbb: PCI总线号。PCI有可能是层次性架构,所以我们可能需要区分一些子-总线

ddddd:设备号,通常由初始化设备选择信号IDSEL连接时申请。

fff:功能序号,有些多功能PCI设备可能用到。

rrrrrrrr:注册号,在设置周期使用。

回头再看这个ranges分表代表了什么。

ranges = <0x42000000 0 0x80000000 0x80000000 0 0x20000000

0x02000000 0 0xa0000000 0xa0000000 0 0x10000000

0x01000000 0 0x00000000 0xb0000000 0 0x01000000>;

父节点address-cells为1,子节点address-cells为3, 子节点size-cells为2。则第一行可以这样划分:

0x42000000 0 0x80000000 子节点地址| 0x80000000 父节点地址| 0 0x20000000 地址空间长度|

0x42000000为phys.high,第一高字节为01000010,则p为1,ss为10,即申请32位存储空间为缓存空间。phys.mid为0,phys.low为0x80000000,他们共同组成了PCI地址,即表示从PCI总线的0x80000000地址处申请出一个32位的存储空间作为缓存。后边的那个cell 0x80000000 0 0x20000000代表到CPU空间后的参数,申请的地址被映射到CPU空间的0x80000000地址处,大小共计0x20000000(512MB)。

2.4.5 interrupt

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

1. interrupt-controller 一个空属性用来声明这个node接收中断信号;

2. #interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符;

3. interrupt-parent 标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的;

4. interrupts 一个中断标识符列表,表示每一个中断输出信号。

如果有两个,第一个是中断号,第二个是中断类型,如高电平、低电平、边缘触发等触发特性。对于给定的中断控制器,应该仔细阅读相关文档来确定其中断标识该如何解析。

/ {

compatible = "acme,coyotes-revenge";

#address-cells = <1>;

#size-cells = <1>;

interrupt-parent = <&intc>; root结点指定中断控制器intc

...

intc: interrupt-controller@10140000 {

compatible = "arm,pl190";

reg = <0x10140000 0x1000 >;

interrupt-controller; 这个node接收中断信号

#interrupt-cells = <2>; 2个cell做中断描述符

};

对于ARM GIC中断控制器而言,#interrupt-cells为3,它3个cell的具体含义需要参考文档

kernel/Documentation/devicetree/bindings/arm/gic.txt

- #interrupt-cells : Specifies the number of cells needed to encode an

interrupt source. The type shall be a <u32> and the value shall be 3.

The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI

interrupts.

The 2nd cell contains the interrupt number for the interrupt type.

SPI interrupts are in the range [0-987]. PPI interrupts are in the

range [0-15].

The 3rd cell is the flags, encoded as follows:

bits[3:0] trigger type and level flags.

1 = low-to-high edge triggered

2 = high-to-low edge triggered

4 = active high level-sensitive

8 = active low level-sensitive

一个设备还可能用到多个中断号。对于ARM GIC而言,若某设备使用了SPI的168、169号2个中断,且都是高电平触发,则该设备结点的interrupts属性可定义为:

interrupts = <0 168 4>, <0 169 4>;

三、dts与驱动

●没有使用dts之前的bsp和driver

板级spec代码

#ifdef CONFIG_LEDS_CTL

struct platform_device s3c_device_leds_ctl = {

.name = "leds",

.id = -1,

};

#endif

driver

static struct platform_driver leds_driver = {

.probe = leds_probe,

.remove = leds_remove,

.suspend = leds_suspend,

.resume = leds_resume,

.driver = {

.name = DRIVER_NAME,//leds

.owner = THIS_MODULE,

},

};

●使用dts之后的bsp和driver

dts文件

&soc {

i2c@f9928000 { /* BLSP1 QUP6 */

status = "ok";

nfc-nci@76 {

compatible = "brcm,bcm2079x";

...

};

driver

static struct of_device_id bcm_match_table[] = {

{.compatible = "brcm,bcm2079x"},

{}

}

static struct i2c_driver bcm2079x_driver = {

.id_table = bcm2079x_id,

.probe = bcm2079x_probe,

.remove = bcm2079x_remove,

.driver = {

.owner = THIS_MODULE,

.name = "bcm2079x-i2c",

.of_match_table = bcm_match_table,

},

};

,