From 67131474516c44790c7def73bb663ab866257ea3 Mon Sep 17 00:00:00 2001 From: "rick.chan" Date: Mon, 8 Jun 2020 11:58:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E7=9B=B8=E5=AF=B9=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E7=9A=84=20Device=20Tree=20=E8=B5=84=E6=96=99.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: rick.chan --- .../Kernel/Modules/DeviceTree/DeviceTree.md | 468 +++++++++++++++++- 1 file changed, 454 insertions(+), 14 deletions(-) diff --git a/Software/Development/OperatingSystem/Linux/Kernel/Modules/DeviceTree/DeviceTree.md b/Software/Development/OperatingSystem/Linux/Kernel/Modules/DeviceTree/DeviceTree.md index 9ae2aa6..9f86ecc 100644 --- a/Software/Development/OperatingSystem/Linux/Kernel/Modules/DeviceTree/DeviceTree.md +++ b/Software/Development/OperatingSystem/Linux/Kernel/Modules/DeviceTree/DeviceTree.md @@ -1,6 +1,8 @@ # Device Tree -## 设备树的起源 +## 概述 + +### 设备树的起源 linux 2.6 及之前,大量板级信息被硬编码到内核里,十分庞大,大量代码冗余。 @@ -16,7 +18,11 @@ linux 2.6 之后,引入了设备树。设备树源于 OpenFirmware,描述硬 本质上是画一棵 CPU、总线、设备组成的树,Linux 内核会把设备树展开成 platform_device、i2c_client、spi_device 等设备,而这些设备用到的内存、中断等资源,也会传递个内核,内核会将这些资源绑定给展开的相应设备。 -## DTSI/DTS/DTC/DTB +不使用 DT 时,kernel 包含了硬件的完整描述信息,bootloader 加载单独的一个二进制文件(kernel 镜像文件 uImage 或 zImage)并执行它,bootloader 通过寄存器 r2 传递 ATAGS(为一些附加信息,如 RAM 大小和地址、cmdline 等)给 kernel,通过寄存器 r1 传递一个机器类型(machine type,用于告诉内核将启动哪一款板卡)整数给 kernel。 + +使用 DT 时,kernel 包含的硬件完整打桩信息被提取为一个二进制文件 DTB,bootloader 则需要加载 kernel 镜像(uImage 或 zImage)以及 DTB(arch/arm/boot/dts/ 目录下的 DTS 文件<一个板卡一个 dts 文件>通过 DTC 编译成 DTB 文件),bootloader 通过寄存器 r2 传递 DTB 文件(该文件也包含了 RAM 信息、cmdline 等信息)所在地址给 kernel,而原先传递板卡类型整数的r1则不需要再关注了。 + +### DTSI/DTS/DTC/DTB dtsi:可被 #include 的设备树源文件; @@ -24,10 +30,30 @@ dts:设备树源文件; dtc:编译 dts 和 dtsi 后得到的设备树文件,dts 及 dtsi 中的内容被组合或覆盖,该文件为源码形式,Linux 内核无法识别; -dtb:编译 dtc 后得到的二进制设备树文件,Linux 内核可加载和识别其中的内容。 +dtb:即 Device Tree Blob,编译 dtc 后得到的二进制设备树文件,Linux 内核可加载和识别其中的内容。 如果谋 dts 文件引用了谋 dtsi 文件,可以在 dts 中覆盖 dtsi 中的部分内容。 +### Binding + +对于 Device Tree 中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为“.txt”。这些文档位于内核的 Documentation/devicetree/bindings 目录,其下又分为很多子目录。 + +### DTB 的编译 + +DTB(Devicetree Blob) 是 DTS 的二进制文件格式,Kernel 使用 DTC 工具将 DTS 源文件编译成 DTB,bootloader 再将 DTB 文件传递给 Kernel 解析。 + +### Bootloader + +不遵守标准书写的 DTS 文件在编译的时候会报错。 + +Uboot mainline 从 v1.1.3 开始支持 Device Tree,其对 ARM 的支持则是和 ARM 内核支持 Device Tree 同期完成。为了使能 Device Tree,需要编译 Uboot 的时候在 config 文件中加入 + +```cpp +#define CONFIG_OF_LIBFDT +``` + +当我们将 DTB 文件在 Uboot 里加载到内存中后,通过 fdt addr 0xnnnnnnnn 命令来设置 DTB 文件对应地址,这样就可以使用 fdt resize、fdt print 等命令对 DTB 文件进行操作了。对于ARM,使用 bootz kernel_addr initrd_addr dtb_addr 命令来启动 kernel,dtb_addr 作为 bootz 或 bootm 最后一个参数,第一个参数为内核镜像的地址,第二个参数为 initrd 的地址,如不存在,使用“-“代替。 + ## DTS 语法 设备树包含一个根节点和多个子节点,如果在 dts/dtsi 文件中写了多个根节点,则在编译后被组合成一个根节点。子节点可嵌套。 节点会被展开为 device,其 compatible 属性用于与 driver 的 compatible 属性项匹配,如果匹配成功则调用该 driver 的 probe 函数。 @@ -39,10 +65,10 @@ DTS 中使用“//”进行行注释或“/**/”进行块注释。 节点使用: ```dts -[label]:[@] {} +[label]:[@] {} ``` -的格式来定义。挂到内存空间的设备,其 unit-address 一般是内存地址。别的地方可以通过“&label”来引用该节点。 +的格式来定义。挂到内存空间的设备,其 unit-address 一般是 node 中“reg”属性描述的开始地址。label 为可选项,如果存在,则在 DTS 的其他地方可以通过“&label”来引用该节点。 ### 属性 @@ -87,7 +113,7 @@ model = "fsl,MPC8349EMITX"; #### phandle -“phandle”属性通用一个唯一的 id 来标识一个 Node,在 property 可以使用这个 id 来引用 Node。 +“phandle”属性通过一个唯一的 id 来标识一个 Node,在 property 可以使用这个 id 来引用 Node。 Value type: u32 @@ -152,12 +178,19 @@ reg = ; #### interrupt -* interrutt-controller:属性为空,表明“我是中断控制器”; -* #interrupt-cells:表明连接此中断控制器的设备的中断属性的 cell 大小,也就是 interrupt = <> 属性的 cell 大小; -* interrupt-parent:设备节点通过这个关键字指定其依附的中断控制器 phandle,如果没有指定,则继承父节点的 interrupt-parent 配置; -* interrupt:设备节点里使用,一般包含中断号、触发方法等。具体有多少个 cell,由 #interrupt-cells 决定,每个 cell 的具体含义,一般由驱动决定。 +和中断相关的 node 可以分成3种: -多个中断可以用 interrupts 描述。interrupts 属性后面,会有不同的参数,有时是两个,有时是三个。 +* Interrupt Generating Devices,中断发生设备,这种设备可以发生中断; +* Interrupt Controllers,中断控制器,处理中断; +* Interrupt Nexus,中断联结,路由中断给中断控制器。 + +##### Interrupt Generating Devices Property + +* interrupt:属性用来定义设备的中断解析,一般包含中断号、触发方法等。 +* interrupts:属性用来定义设备的中断解析,一般包含中断号、触发方法等。 +* interrupt-parent:属性用来制定当前设备的 Interrupt Controllers/Interrupt Nexus,phandle 指相对应的 node,如果没有指定,则继承父节点的 interrupt-parent 配置。 + +interrupt 和 interrupts 都根据其 interrupt-parent node 中定义的“#interrupt-cells”来解析。比如 #interrupt-cells=2,那根据 2 个 cells 为单位来解析 interrupt/interrupts 属性。理论上每个 cell 的具体含义,一般由驱动决定,但一般而言 interrupt/interrupts 属性后面,会有两个或三个固定参数。 两个的时候一般是这样出现: @@ -166,7 +199,7 @@ interrupt-parent = <&gpio2>; interrupts = <29 0>; ``` -一般这样表明:中断控制器是 GPIO2,然后使用它的 29 号中断。(这里的 29 号,就是指 29 号引脚),0 是指触发的方式(上升沿、下降沿等)。 +表明:中断控制器是 GPIO2,然后使用它的 29 号中断。(这里的 29 号,就是指 29 号引脚),0 是指触发的方式(上升沿、下降沿等)。 三个的时候一般是这样出现: @@ -196,21 +229,386 @@ IPI、PPI、SPI、SGI 是 ARM 规范的中断,含义如下: #define IRQ_TYPE_LEVEL_LOW 8 ``` +##### Interrupt Controllers Property + +* #interrupt-cells:用来规定连接到该中断控制器上的设备的”interrupts”属性的解析长度; +* interrupt-controller:用来声明当前 node 为中断控制器。 + +##### Interrupt Nexus Property + +* #interrupt-cells:用来规定连接到该中断控制器上的设备的”interrupts”属性的解析长度。 +* interrupt-map:用来描述 interrupt nexus 设备对中断的路由。解析格式为 5 元素序列:child unit address, child interrupt specifier, interrupt-parent, parent unit address, parent interrupt specifier。其中:“child unit address”的 cells 长度由子节点的 “#address-cells” 指定; “child interrupt specifier”的 cells 长度由子节点的“#interrupt-cells”指定; “interrupt-parent” phandle 指向 interrupt controller 的引用; “parent unit address”的 cells 长度由父节点的“#address-cells”指定; “parent interrupt specifier”的 cells 长度由父节点的“#interrupt-cells”指定。 + +举例: + +```dts +soc { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + open-pic { + clock-frequency = <0>; + interrupt-controller; + #address-cells = <0>; + #interrupt-cells = <2>; + }; + pci { + #interrupt-cells = <1>; + #size-cells = <2>; + #address-cells = <3>; + interrupt-map-mask = <0xf800 0 0 7>; + interrupt-map = < + /* IDSEL 0x11 - PCI slot 1 */ + 0x8800 0 0 1 &open-pic 2 1 /* INTA */ + 0x8800 0 0 2 &open-pic 3 1 /* INTB */ + 0x8800 0 0 3 &open-pic 4 1 /* INTC */ + 0x8800 0 0 4 &open-pic 1 1 /* INTD */ + /* IDSEL 0x12 - PCI slot 2 */ + 0x9000 0 0 1 &open-pic 3 1 /* INTA */ + 0x9000 0 0 2 &open-pic 4 1 /* INTB */ + 0x9000 0 0 3 &open-pic 1 1 /* INTC */ + 0x9000 0 0 4 &open-pic 2 1 /* INTD */ + >; + }; +}; +``` + +For example, the first row of the interrupt-map table specifies the mapping for INTA of slot 1. The components of that row are shown here + child unit address: 0x8800 0 0 + child interrupt specifier: 1 + interrupt parent: &open-pic + parent unit address: (empty because #address-cells = <0> in the open-pic node) + parent interrupt specifier: 2 1 + ### 标准节点 #### Root +每个 DeviceTree 只有一个根节点。根节点需要有以下必备属性: + +* #address-cells:u32, Specifies the number of \ cells to represent the address in the reg property in children of root; +* #size-cells:u32, Specifies the number of \ cells to represent the size in the reg property in children of root; +* model:string, Specifies a string that uniquely identifies the model of the system board. The recommended format is “manufacturer,model-number”; +* compatible:stringlist, Specifies a list of platform architectures with which this platform is compatible. This property can be used by operating systems in selecting platform specific code. The recommended form of the property value is:"manufacturer,model". + +For example: + +```dts +compatible = "fsl,mpc8572ds" +``` + #### aliases +由于 Device tree 是树状结构,当要引用一个 node 的时候要指明相对于 root node 的full path,例如“/node-name-1/node-name-2/node-name-N“。如果多次引用,每次都要写这么复杂的字符串多少是有些麻烦,因此可以在 aliases 节点定义一些设备节点 full path 的缩写: + +```dts +aliases { + serial0 = "/simple-bus@fe000000/serial@llc500"; + ethernet0 = "/simple-bus@fe000000/ethernet@31c000"; +}; +``` + #### memory +用来传递内存布局: + +* device_type:string,Value shall be “memory”; +* reg:prop-encoded-array, Consists of an arbitrary number of address and size pairs that specify the physical address and size of the memory ranges; +* initial-mapped-area:prop-encoded-array, Specifies the address and size of the Initial Mapped Area Is a prop-encoded-array consisting of a triplet of (effective address, physical address, size). The effective and physical address shall each be 64-bit (\ value), and the size shall be 32-bits (\ value). + +举例: + +```dts +// RAM: starting address 0x0, length 0x80000000 (2GB) +// RAM: starting address 0x100000000, length 0x100000000 (4GB) + +\ { + #address-cells = <2>; + #size-cells = <2>; + + memory@0 { + device_type = "memory"; + reg = <0x000000000 0x00000000 0x00000000 0x80000000 + 0x000000001 0x00000000 0x00000001 0x00000000>; + }; +} +``` + #### chosen +chosen node 主要用来描述由系统 firmware 指定的 runtime parameter。如果存在 chosen 这个 node,其 parent node 必须是名字是“/”的根节点。原来通过 tag list 传递的一些 linux kernel 的运行时参数可以通过 Device Tree 传递。 + +bootargs:string,用来传递 cmdline 参数; +linux,initrd-start:用来传递 initrd 的开始地址; +stdout-path:string,用来指定标准输出设备; +stdin-path:string,用来指定标准输入设备。 + +举例: + +```dts +/* chosen */ +chosen { + bootargs = "console=tty0 console=ttyMT0,921600n1 root=/dev/ram"; +}; +``` + #### cpus -#### +cpus 节点也是必须的,下面举个具体例子: -// TODO: +```dts +cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a35"; + reg = <0x000>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1248000000>; + }; + + cpu1: cpu@001 { + device_type = "cpu"; + compatible = "arm,cortex-a35"; + reg = <0x001>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1248000000>; + }; + + cpu2: cpu@002 { + device_type = "cpu"; + compatible = "arm,cortex-a35"; + reg = <0x002>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1248000000>; + }; + + cpu3: cpu@003 { + device_type = "cpu"; + compatible = "arm,cortex-a35"; + reg = <0x003>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1248000000>; + }; + + cpu4: cpu@100 { + device_type = "cpu"; + compatible = "arm,cortex-a53"; + reg = <0x100>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1378000000>; + }; + + cpu5: cpu@101 { + device_type = "cpu"; + compatible = "arm,cortex-a53"; + reg = <0x101>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1378000000>; + }; + + cpu6: cpu@102 { + device_type = "cpu"; + compatible = "arm,cortex-a53"; + reg = <0x102>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1378000000>; + }; + + cpu7: cpu@103 { + device_type = "cpu"; + compatible = "arm,cortex-a53"; + reg = <0x103>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1378000000>; + }; + + cpu8: cpu@200 { + device_type = "cpu"; + compatible = "arm,cortex-a73"; + reg = <0x200>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1638000000>; + }; + + cpu9: cpu@201 { + device_type = "cpu"; + compatible = "arm,cortex-a73"; + reg = <0x201>; + enable-method = "psci"; + cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>, + <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>; + cpu-release-addr = <0x0 0x40000200>; + clock-frequency = <1638000000>; + }; + + cpu-map { + cluster0 { + core0 { + cpu = <&cpu0>; + }; + + + core1 { + cpu = <&cpu1>; + }; + + core2 { + cpu = <&cpu2>; + }; + + core3 { + cpu = <&cpu3>; + }; + + }; + + cluster1 { + core0 { + cpu = <&cpu4>; + }; + + core1 { + cpu = <&cpu5>; + }; + + core2 { + cpu = <&cpu6>; + }; + + core3 { + cpu = <&cpu7>; + }; + + }; + + cluster2 { + core0 { + cpu = <&cpu8>; + }; + + core1 { + cpu = <&cpu9>; + }; + + }; + }; + + idle-states { + entry-method = "arm,psci"; + + LEGACY_MCDI: legacy-mcdi { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x0000001>; + entry-latency-us = <600>; + exit-latency-us = <600>; + min-residency-us = <1200>; + }; + + LEGACY_SODI: legacy-sodi { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x0000002>; + entry-latency-us = <600>; + exit-latency-us = <600>; + min-residency-us = <1200>; + }; + + LEGACY_SODI3: legacy-sodi3 { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x0000003>; + entry-latency-us = <600>; + exit-latency-us = <600>; + min-residency-us = <1200>; + }; + + LEGACY_DPIDLE: legacy-dpidle { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x0000004>; + entry-latency-us = <600>; + exit-latency-us = <600>; + min-residency-us = <1200>; + }; + + LEGACY_SUSPEND: legacy-suspend { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x0000005>; + entry-latency-us = <600>; + exit-latency-us = <600>; + min-residency-us = <1200>; + }; + + MCDI: mcdi { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x0010001>; + entry-latency-us = <600>; + exit-latency-us = <600>; + min-residency-us = <1200>; + }; + + SODI: sodi { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x1010002>; + entry-latency-us = <800>; + exit-latency-us = <1000>; + min-residency-us = <2000>; + }; + + SODI3: sodi3 { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x1010003>; + entry-latency-us = <800>; + exit-latency-us = <1000>; + min-residency-us = <2000>; + }; + + DPIDLE: dpidle { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x1010004>; + entry-latency-us = <800>; + exit-latency-us = <1000>; + min-residency-us = <2000>; + }; + + SUSPEND: suspend { + compatible = "arm,idle-state"; + arm,psci-suspend-param = <0x1010005>; + entry-latency-us = <800>; + exit-latency-us = <1000>; + min-residency-us = <2000>; + }; + + }; + }; +``` ### DTS 示例 @@ -986,6 +1384,42 @@ out_string:读取到的字符串值。 负值:读取失败。 +#### of_property_read_string_index 函数 + +**头文件:** + +```cpp +#include +``` + +**函数原型:** + +```cpp +int of_property_read_string_index(const struct device_node *np, + const char *propname, + int index, const char **output); +``` + +**说明:** + +Find and read a string from a multiple strings property. + +**参数:** + +np:设备节点; + +proname:要读取的属性名字; + +index:index of the string in the list of strings + +out_string:读取到的字符串值。 + +**返回值:** + +0:读取成功; + +负值:读取失败。 + #### of_property_read_bool 函数 **头文件:** @@ -1426,3 +1860,9 @@ node:设备节点。 **返回值:** 成功则返回 platform_device 指针,失败返回 NULL。 + +## 参考资料 + +* [Device Tree 详解](http://kernel.meizu.com/device-tree.html) +* [ARM Linux 3.x的设备树(Device Tree)](https://blog.csdn.net/21cnbao/article/details/8457546) +* [Linux Kernel DT(Device Tree)](https://www.jianshu.com/p/923b380366bb)