设备树

参考文档:

  DTS入门知识:https://blog.csdn.net/u014717231/article/details/53139968

  kernel中文档:/linux-4.16.2/Documentation/devicetree

一、设备树的介绍

1.1 基本介绍  

  设备树首先使用的是 PowerPC等其他体系架构下的Flattened Device Tree(FDT),Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)

  当前不管是在  u-boot 还是在 kernel 中都应用了设备树。设备树用于实现驱动代码与设备信息相分离

  在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。

  在ARM Linux内,一个.dts(device tree source)文件对应一个ARM的machine。u-boot 和 kernel 存放设备树的地方不同:

  u-boot:u-boot-2018.03/arch/arm/dts

  kernel:linux-4.16.2/arch/arm/boot/dts

  可以通过 make dtbs 命令将 .dts  文件编译成 .dtb 二进制文件供内核驱动使用。

  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等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

  它替代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的设计目标就是如此

1.2 加载过程

  如果要使用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。

1.3 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,它是无法动态识别的,需要在devicetree中描述。同样的道理,在computersystem中,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/dts(此为u-boot)目录下,rk3399的很多 machine 的很多.dts都include了rk3399.dtsi。

  正常情况下所有的dts文件以及dtsi文件都含有一个根节点”/”,这样include之后就会造成有很多个“根节点”。按理说 device tree既然是一个树,那么其只能有一个根节点,所有其他的节点都是派生于根节点的child node。其实Device TreeCompiler会对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就是寄存器地址。rootnode的node name是确定的,必须是“/”。

  在一个树状结构的device tree中,如何引用一个node呢?要想唯一指定一个node必须使用full path,例如/node-name-1/node-name-2/node-name-N

1.4 dts 结构例子

 1 / { "/" 表示root结点,该结点下有两个子结点node1和node2
 2 
 3     node1 { 结点"node1"下又含有子结点,本例中为"child-node1""child-node2",各结点都有一系列属性
 4 
 5         a-string-property = "Astring";属性是字符串
 6 
 7         a-string-list-property = "firststring", "second string";字符串数组
 8 
 9         a-byte-data-property = [0x01 0x23 0x340x56];二进制数组
10 
11         child-node1 {
12 
13             first-child-property;
14 
15             second-child-property = <1>;Cells(由u32整数组成)
16 
17             a-string-property = "Hello,world";
18 
19         };
20 
21         child-node2 {
22 
23         };
24 
25     };
26 
27     node2 {
28 
29         an-empty-property;属性为空
30 
31         a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
32 
33         child-node1 {
34 
35         };
36 
37     };
38 
39 };

二、dts 语法

2.1 name@unit-address

  rk3399 总共有6个核心,在 u-boot-2018.03/arch/arm/dts/rk3399-firefly.dts 中定义了这6个核心。

  CPU字节点的命名遵循的组织形式为:<name>[@<unit-address>],<>中的内容是必选项,[]中的则为可选项。

  name是一个ASCII字符串,用于描述结点对应的设备类型,如网卡适配器对应的结点name宜为ethernet,表示这个是网卡。如果一个结点描述的设备有地址,则应该给出@unit-address。多个相同类型设备结点的name可以一样,只要unit-address不同即可,设备的unit-address地址也经常在其对应结点的reg属性中给出。

 1         /* CPU节点的描述 */
 2         /* node-name@unit-address,unit-address的具体格式是和设备挂在哪个bus上相关 */
 3         /* 如果该node没有reg属性,那么该节点名字中必须不能包括@和unit-address */
 4         /* 对于 cpu,其 unit-address 就是从0开始编址,依次加一 */
 5         cpu_l0: cpu@0 {
 6             device_type = "cpu";
 7             compatible = "arm,cortex-a53", "arm,armv8";    /* 核心为 arm 的 cortex-a53,属于 arm 系列的 armv8 */
 8             reg = <0x0 0x0>;
 9             enable-method = "psci";
10             #cooling-cells = <2>; /* min followed by max */
11             clocks = <&cru ARMCLKL>;
12         };

2.2 compatible

  compatible = "<manufacturer>,<model>";

  定义了系统的名称,Linux内核透过root结点"/"的compatible 属性即可判断它启动的是什么machine。

  在.dts文件的每个设备,都有一个compatible属性,compatible 属性用于驱动和设备的绑定。compatible 属性是一个字符串的列表,列表中的第一个字符串表征了结点代表的确切设备,形式为"<manufacturer>,<model>",其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。

  例如:

1         cpu_l0: cpu@0 {
2             device_type = "cpu";
3             compatible = "arm,cortex-a53", "arm,armv8";    /* 核心为 arm 的 cortex-a53,属于 arm 系列的 armv8 */
4             reg = <0x0 0x0>;
5             enable-method = "psci";
6             #cooling-cells = <2>; /* min followed by max */
7             clocks = <&cru ARMCLKL>;
8         };

2.3 reg、address-cells 和 size-cells

  引用:https://www.cnblogs.com/youchihwang/p/7050846.html

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

  • reg:属性与 adress-cells 和 size-cells 进行对应
  • #address-cells:基地址、片选号等绝对起始地址所占字长,1为32位,2为64位
  • #size-cells:长度所占字长,1为32位,2为64位

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

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

  address-cells和size-cells 表明子设备结点如何写地址(reg属性地址的编写格式正是参考这两个变量的值)

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

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

1 /* 根节点 */
2 / {
3     compatible = "rockchip,rk3399";    /* 系统名称为 rockchip 的 rk3399 */
4 
5     interrupt-parent = <&gic>;
6     #address-cells = <2>;            /* 基地址、片选号等绝对起始地址所占字长为64位 */
7     #size-cells = <2>;                /* 长度所占字长为64位 */

  

 1     /* APB 总线配置 */
 2     amba {
 3         compatible = "simple-bus";        /* DMA 配置在APB 总线上 */
 4         #address-cells = <2>;            /* #address-cells 设置为 2 */
 5         #size-cells = <2>;                /* #size-cells 设置为 2 */
 6         ranges;
 7 
 8         dmac_bus: dma-controller@ff6d0000 {
 9             compatible = "arm,pl330", "arm,primecell";
10             reg = <0x0 0xff6d0000 0x0 0x4000>;    /* 0x0 0xff6d0000 为 DMAC0 的存取地址,0x0,0x4000 为DMA的 size  */
11             interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH 0>,
12                      <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH 0>;
13             #dma-cells = <1>;
14             clocks = <&cru ACLK_DMAC0_PERILP>;
15             clock-names = "apb_pclk";
16         };
17 
18         dmac_peri: dma-controller@ff6e0000 {
19             compatible = "arm,pl330", "arm,primecell";
20             reg = <0x0 0xff6e0000 0x0 0x4000>;    /* 0x0 0xff6e0000 为 DMAC0 的存取地址,0x0,0x4000 为DMA的 size  */
21             interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH 0>,
22                      <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH 0>;
23             #dma-cells = <1>;
24             clocks = <&cru ACLK_DMAC1_PERILP>;
25             clock-names = "apb_pclk";
26         };
27     };

2.4 range 属性  

  当需要描述的设备不是本地设备时,就需要描述一个从设备地址空间到CPU地址空间的映射关系,这里就需要用到ranges属性。

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

  

2.5 interrupt

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

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

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

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

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

 1     pcie0: pcie@f8000000 {
 2         compatible = "rockchip,rk3399-pcie";
 3         reg = <0x0 0xf8000000 0x0 0x2000000>,
 4               <0x0 0xfd000000 0x0 0x1000000>;
 5         reg-names = "axi-base", "apb-base";
 6         /* 定义PCI使用3个cell,并且PCI的地址范围通过两个单位就可以解读。最后一个cell 为中断 */
 7         /* 需要用3个32位的cell来描述一个PCI地址,这三个cell分别代表物理地址高位、中位、低位:
 8             1 phys.high cell : npt000ss bbbbbbbb dddddfff rrrrrrrr
 9                 n:代表重申请空间标志
10                 p:代表预读空间(缓存)标志
11                 t:别名地址标志(这里没有使用)
12                 ss:空间代码
13                     00:设置空间
14                     01:IO空间
15                     10:32位存储空间
16                     11:64位存储空间
17                 bbbbbbbb: PCI总线号。PCI有可能是层次性架构,所以我们可能需要区分一些子-总线
18                 ddddd:设备号,通常由初始化设备选择信号IDSEL连接时申请。
19                 fff:功能序号,有些多功能PCI设备可能用到。
20                 rrrrrrrr:注册号,在设置周期使用。
21             2 phys.mid cell : hhhhhhh hhhhhhhh hhhhhhhh hhhhhhh
22             3 phys.low cell : llllllll llllllll llllllll llllllll
23             PCI地址为64位宽度,编码在phys.mid和phys.low中。真正重要的东西在于phys.high这一位空间中:
24         */
25         #address-cells = <3>;
26         #size-cells = <2>;
27         #interrupt-cells = <1>;        /* 中断控制器节点属性,需要一个U32单位做中断描述符 */
28         aspm-no-l0s;
29         bus-range = <0x0 0x1>;
30         clocks = <&cru ACLK_PCIE>, <&cru ACLK_PERF_PCIE>,
31              <&cru PCLK_PCIE>, <&cru SCLK_PCIE_PM>;
32         clock-names = "aclk", "aclk-perf",
33                   "hclk", "pm";
34         /*中断标识符列表
35             GIC_SPI:中断类型,属于共享接口中断 GIC中断分为 GIC_SPI 和 GIC_PPI 中断,可看 datasheet
36             49: 中断号
37             IRQ_TYPE_LEVEL_HIGH:高电平触发
38             0:GIC_PPT 私有中断类型才需要定义
39         */
40         interrupts = <GIC_SPI 49 IRQ_TYPE_LEVEL_HIGH 0>,
41                  <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH 0>,
42                  <GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH 0>;
43         interrupt-names = "sys", "legacy", "client";
44         interrupt-map-mask = <0 0 0 7>;
45         interrupt-map = <0 0 0 1 &pcie0_intc 0>,
46                 <0 0 0 2 &pcie0_intc 1>,
47                 <0 0 0 3 &pcie0_intc 2>,
48                 <0 0 0 4 &pcie0_intc 3>;
49         linux,pci-domain = <0>;
50         max-link-speed = <1>;
51         msi-map = <0x0 &its 0x0 0x1000>;
52         phys = <&pcie_phy>;
53         phy-names = "pcie-phy";
54         /*     0x83000000 0x0 0xfa000000     子节点地址
55                 0x83000000为phys.high,第一高字节为:1000 0011,n=1,ss = 11,重申请 64位 存储空间
56                 phys.mid为0
57                 phys.low为0xfa000000
58             这三个地址共同组成了 PCI 地址,表示从PCI总线的0xfa000000地址处申请出一个64位的存储空间。
59             0x0 0xfa000000                父节点地址
60             0x0 0x600000                地址空间长度
61             后边两个cell 0x0 0xfa000000 0x0 0x600000 代表到 CPU 空间后的参数,申请的地址被映射到CPU空间的0xfa000000地址处,大小共计0x600000(6M)。
62         */
63         ranges = <0x83000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x600000
64               0x81000000 0x0 0xfa600000 0x0 0xfa600000 0x0 0x100000>;
65         resets = <&cru SRST_PCIE_CORE>, <&cru SRST_PCIE_MGMT>,
66              <&cru SRST_PCIE_MGMT_STICKY>, <&cru SRST_PCIE_PIPE>,
67              <&cru SRST_PCIE_PM>, <&cru SRST_P_PCIE>,
68              <&cru SRST_A_PCIE>;
69         reset-names = "core", "mgmt", "mgmt-sticky", "pipe",
70                   "pm", "pclk", "aclk";
71         status = "disabled";
72 
73         pcie0_intc: interrupt-controller {
74             interrupt-controller;
75             #address-cells = <0>;
76             #interrupt-cells = <1>;
77         };
78     };

2.6 GPIO

  

  

2.7 CLK

  

  

三、dts 与驱动

  

  

  

原文地址:https://www.cnblogs.com/kele-dad/p/8824212.html