参考文档:
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 与驱动