美文网首页
Linux 内核学习(4)---- DeviceTree

Linux 内核学习(4)---- DeviceTree

作者: 特立独行的佩奇 | 来源:发表于2022-03-27 13:47 被阅读0次

    DTS简介

    在Linux内核早期的时候,每个嵌入式系统的板载信息(总线,设备的寄存器地址等)都是Hardcode在arch/<cpu>/match-xxx/board-xxx.c之类的文件中,即必须存在这样的C文件,里面定义了板子上的硬件的地址等信息,这样做会造成很多很多的大量相似的没多少用的重复代码;为了解决这个问题,而引入了DTS(device tree source)
    DTS即Device Tree Source 设备树源码, Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)
    本质上,Device Tree改变了原来用hardcode方式将HW 配置信息嵌入到内核代码的方法,改用Bootloader传递一个DB的形式。
    每个嵌入式系统的描述硬件信息的DTS文件存放在arch/<cpu>/boot/dts/目录下,比如mediatek 的mt7622-rfb1.dts位于linux-5.4.6/arch/arm64/boot/dts/mediatek/ 目录下

    #include <dt-bindings/input/input.h>
    #include <dt-bindings/gpio/gpio.h>
    
    #include "mt7622.dtsi"
    #include "mt6380.dtsi"
    
    / {
        model = "MediaTek MT7622 RFB1 board";
        compatible = "mediatek,mt7622-rfb1", "mediatek,mt7622";
    
        aliases {
            serial0 = &uart0;
        };
    
        chosen {
            stdout-path = "serial0:115200n8";
            bootargs = "earlycon=uart8250,mmio32,0x11002000 swiotlb=512";
        };
    
        cpus {
            cpu@0 {
                proc-supply = <&mt6380_vcpu_reg>;
                sram-supply = <&mt6380_vm_reg>;
            };
    
            cpu@1 {
                proc-supply = <&mt6380_vcpu_reg>;
                sram-supply = <&mt6380_vm_reg>;
            };
        };
    

    DTS文件编译加载流程

    如果要使用Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成Device Tree source file;通过DTC(Device Tree Compiler),可以将的Device Tree Source file变成适合机器处理的Device Tree Binary File(DTB,Device Tree Blob);在系统启动的时候,Bootloader可以将保存在FLASH中的DTB copy到内存,并把DTB的起始地址传递给kernel
    dts文件编译加载流程如下图所示:

    dts编译加载流程.png
    Device Tree可以描述的信息包括CPU的数量和类别、内存基地址和大小、总线和桥、外设连接、中断控制器和中断使用情况、GPIO控制器和GPIO使用情况、Clock控制器和Clock使用情况等等
    DeviceTree 是一种描述了电路板上CPU、总线、设备组成的树形数据结构,Bootloader会将这些信息传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_Device、I2c_Client、Spi_Device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备

    DeviceTree 不需要描述所有的硬件信息,那些可以动态探测到的设备是不需要描述的,例如USB device,不过对于SOC上的Usb Hostcontroller,它是无法动态识别的,需要在Device Tree中描述;同样的道理,在Computer System中,PCI Device可以被动态探测到,不需要在Device Tree中描述,但是PCI Bridge如果不能被探测,所以需要描述

    DeviceTree 相关文件描述:
    dts:DT源文件称为dts文件,是ASCII格式文本文件,一般一个dts文件对应一个Machine,ARM架构下dts文件存放于arch/arm/boot/dts/目录下

    dtsi:多个Machine/SoC公用的dt文件,i代表includedtc;
    dtsi和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi,即#include"skeleton.dtsi“或者 /include/ "skeleton.dtsi"

    dtb:DeviceTree Blob,由dtc编译dts文件生成的二进制目标文件

    dt.img:多个dtb文件打包形成dt.img,以适配多个Machine,dts/dtb的结构是标准化的,dt.img有头信息和多个dtb组成,因为没有统一的标准,不同的厂商头信息可能是不同的

    DTS文件构成

    Device Tree的基本单元是node,这些node被组织成树状结构,除了root node,每个node都只有一个parent;一个device tree文件中只能有一个root node

    • root node的node name是确定的,必须是“/”
    • 每个node中包含了若干的property/value来描述该node的一些特性。
    • 每个node用节点名字(nodename)标识,节点名字的格式是node-name@unit-address。
      如果该node没有reg属性(后面会描述这个property),那么该节点名字中必须不能包括@和unit-address;unit-address的具体格式是和设备挂在那个bus上相关,例如对于cpu,其unit-address就是从0开始编址,以此加一;而具体的设备,例如以太网控制器,其unit-address就是寄存器地址
      正常情况下所有的dts文件以及dtsi文件都含有一个根节点”/”,Device Tree Compiler会对DTS的node进行合并,最终生成的DTB中只有一个 root node.

    DTS 常见Node和常见属性

    Chosen Node
    chosen {
        stdout-path = "serial0:115200n8";
        bootargs = "earlycon=uart8250,mmio32,0x11002000 swiotlb=512";
    };
    

    chosen node 主要用来描述由系统指定的runtime parameter,它并没有描述任何硬件设备节点信息。原先通过tag list传递的一些linux kernel运行的参数,可以通过chosen节点来传递。如command line可以通过bootargs这个property来传递。如果存在chosen node,它的parent节点必须为“/”根节点。

    Aliases Node
    aliases {
        serial0 = &uart0;
    };
    uart0: serial@11002000 {
        compatible = "mediatek,mt7622-uart",
                 "mediatek,mt6577-uart";
        reg = <0 0x11002000 0 0x400>;
        interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_LOW>;
        clocks = <&topckgen CLK_TOP_UART_SEL>,
             <&pericfg CLK_PERI_UART0_PD>;
        clock-names = "baud", "bus";
        status = "disabled";
    };
    &uart0 {
        pinctrl-names = "default";
        pinctrl-0 = <&uart0_pins>;
        status = "okay";
    };
    

    aliases node用来定义别名,类似C++中引用。上面是一个在.dtsi中的典型应用,当使用uart0时,也即使用serial@11002000,使得引用节点变得简单方便。例:当.dts include 该.dtsi时,将uart0的status属性赋值为okay,则表明该主板上的serial@11002000处于enable状态;反之,status赋值为disabled,则表明该主板上的serial@11002000处于disable状态。

    Memory Node
    memory {
        reg = <0 0x40000000 0 0x20000000>;
    };
    

    对于memory node,device_type必须为memory,由之前的描述可以知道该memory node是以0x00000000为起始地址,以0x80000000为结束地址的1GB的空间。
    其中的reg 属性:
    reg的组织形式为reg = <address1 length1 [address2 length2][address3 length3] ... >,其中的每一组address length表明了设备使用的一个地址范围。address为1个或多个32位的整型(即cell),而length则为cell的列表或者为空(若#size-cells = 0)。address和length字段是可变长的,父结点的#address-cells和#size-cells分别决定了子结点的reg属性的address和length字段的长度。

    Compatible 属性
    / {
        model = "MediaTek MT7622 RFB1 board";
        compatible = "mediatek,mt7622-rfb1", "mediatek,mt7622";
    
        aliases {
            serial0 = &uart0;
        };
    

    compatible属性为string list,用来将设备匹配对应的driver驱动,优先级为从左向右。即compatible实现了原先内核版本3.x之前,platform_device中.name的功能
    上述.dts文件中,root结点"/"的compatible 属性mediatek,mt7622-rfb1", "mediatek,mt7622;定义了系统的名称,它的组织形式为:<manufacturer>,<model>,Linux内核透过root结点"/"的compatible 属性即可判断它启动的是什么machine

    Interrupts 属性
    scpsys: scpsys@10006000 {
        compatible = "mediatek,mt7622-scpsys",
                 "syscon";
        #power-domain-cells = <1>;
        reg = <0 0x10006000 0 0x1000>;
        interrupts = <GIC_SPI 165 IRQ_TYPE_LEVEL_LOW>,
                 <GIC_SPI 166 IRQ_TYPE_LEVEL_LOW>,
                 <GIC_SPI 167 IRQ_TYPE_LEVEL_LOW>,
                 <GIC_SPI 168 IRQ_TYPE_LEVEL_LOW>;
        infracfg = <&infracfg>;
        clocks = <&topckgen CLK_TOP_HIF_SEL>;
        clock-names = "hif_sel";
    };
    

    <>中第一个u32表示中断类型,第二个是中断号,第三个是中断触发条件
    若子节点使用到中断(中断号、触发方法等等),则需用interrupt属性来指定,该属性的数值长度受中断控制器中#inrerrupt-controller值③控制,即interrupt属性<>中数值的个数为#inrerrupt-controller的值;本例中#inrerrupt-controller=<2>,因而④中interrupts的值为<0x3d 0>形式,具体每个数值的含义由驱动实现决定。

    Ranges属性
     power-domains = <&scpsys MT7622_POWER_DOMAIN_HIF0>;
     bus-range = <0x00 0xff>;
     ranges = <0x82000000 0 0x20000000 0x0 0x20000000 0 0x10000000>;
     status = "disabled";
    
     pcie0: pcie@0,0 {
         reg = <0x0000 0 0 0 0>;
         #address-cells = <3>;
         #size-cells = <2>;
         #interrupt-cells = <1>;
         ranges;
         status = "disabled";
    

    ranges属性为地址转换表,这在pcie中使用较为常见,它表明了该设备在到parent节点中所对用的地址映射关系。ranges格式长度受当前节点#address-cell、parent节点#address-cells、当前节点#size-cell所控制。顺序为ranges=<前节点#address-cell, parent节点#address-cells , 当前节点#size-cell。
    注:对于相同名称的节点,dtc会根据定义的先后顺序进行合并,其相同属性,取后定义的那个。

    相关文章

      网友评论

          本文标题:Linux 内核学习(4)---- DeviceTree

          本文链接:https://www.haomeiwen.com/subject/boaejrtx.html