• 设备树


    参考文档:

      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 与驱动

      

      

      

  • 相关阅读:
    嵌入式交叉编译环境的搭建
    linux驱动模块编写规范以及Makefiel文件的编写规范
    socket通信
    傀儡进程脱壳三步曲
    Thymeleaf 学习笔记-实例demo(中文教程)
    IntelliJ IDEA 快捷键
    github团队协作教程
    thymeleaf 学习笔记-基础篇(中文教程)
    二维码的生成
    .Net Core Web Api实践(四)填坑连接Redis时Timeout performing EVAL
  • 原文地址:https://www.cnblogs.com/kele-dad/p/8824212.html
Copyright © 2020-2023  润新知