zoukankan      html  css  js  c++  java
  • 《设备树 — 基本语法(二)》

    1.设备树

      以上一节为例子,一个leddrv.c驱动可以对应board_A.c、board_B.c、board_C.c等一系列的单板。一旦修改了代码,就要重新编译加载。而且随着板卡的增多,这么.c文件也会越来越多。

      因此,linux就开始引入设备树。设备树其实是一个配置文件,里面定义了硬件相关的资源。这样就取代了这些board_A.c、board_B.c、board_C.c这些用来描述硬件资源的文件。

    2.设备树在开发板上的体现

    ls /sys/firmware

      

       devicetree目录下是以目录结构呈现dtb文件,根节点对应base目录,每一个节点对应一个目录,每一个属性对应一个文件。

      

       

      

      这些属性的值如果是字符串,可以使用 cat 命令把它打印出来;对于数值,可以用hexdump 把它打印出来。 

      

       uboot会在启动内核时把设备树在内存中的地址告诉内核。

    3.设备树语法

    DTS 文件布局:

    /dts-v1/; // 表示版本
    [memory reservations] // 格式为: /memreserve/ <address> <length>;
    / {
      [property definitions]
      [child nodes]
    };

    node 的格式:

      设备树中的基本单元,被称为“node”,其格式为:

    [label:] node-name[@unit-address] {
        [properties definitions]
        [child nodes]
    };

       如果该node没有reg属性,那么该节点名字中必须不能包括@和unit-address。

      unit-address的具体格式是和设备挂在哪个bus上相关。例如对于cpu,其unit-address就是从0开始编址,以此加一。而具体的设备,例如以太网控制器,其unit-address就是寄存器地址。@unit-address通常用区分名字相同的外设备。

      按照惯例,如果一个node有一个reg属性,那么这个node-name必须包括unit-address,这是reg属性的第一个address值。

      label 是标号,可以省略。label 的作用是为了方便地引用 node,比如:

    /dts-v1/;
    / {
        uart0: uart@fe001000 {
            compatible="ns16550";
            reg=<0xfe001000 0x100>;
        };
    };

      可以使用下面 2 种方法来修改 uart@fe001000 这个 node:

    // 在根节点之外使用 label 引用 node:
    &uart0 {
        status = “disabled”;
    };
    或在根节点之外使用全路径:
    &{/uart@fe001000} {
        status = “disabled”;
    };

    cpu addressing

    在讨论寻址时,CPU节点代表了最简单的情况。 每个CPU都分配有一个唯一的ID,并且没有与CPU ID相关联的大小。

      cpus {
            #address-cells = <1>;
            #size-cells = <0>;
            cpu@0 {
                compatible = "arm,cortex-a9";
                reg = <0>;
            };
            cpu@1 {
                compatible = "arm,cortex-a9";
                reg = <1>;
            };
        };

      在cpus节点,#address-cells被设置成了1,#size-cells被设置成了0。这是说子reg值是单独的uint32,它用无大小字段表示地址。在此情况下,这两个cpu分配到的地址为0和1。Cpu节点的#size-cells是0因为每个cpu只分配到了一个单独的地址。
      仍然需要注意reg值需要与节点名的值相匹配。按照惯例,如果一个节点有一个reg属性,那么这个节点名称必须包括unit-address,这是reg属性的第一个address值。

    memory mapped devices  

      与在cpu节点中单独的address值不同,内存映射设备被分配了一系列将要响应的地址,因此不仅需要包含内存的基地址而且还需要映射地址的长度,因此需要使用#size-cells用来表示在每个子reg元组中长度字段的大小。在以下示例中,每个address值为1 cell(32 bits),每个长度值也是1 cell,这在32 bit系统是比较典型的。64 bit设备也许会为#address-cells和#size-cells使用数值2,在device tree中获取64 bit addressing。

    /dts-v1/;
    
    / {
        #address-cells = <1>;
        #size-cells = <1>;
        ...
        serial@101f0000 {
            compatible = "arm,pl011";
            reg = <0x101f0000 0x1000 >;
        };
    
        serial@101f2000 {
            compatible = "arm,pl011";
            reg = <0x101f2000 0x1000 >;
        };
    
        gpio@101f3000 {
            compatible = "arm,pl061";
            reg = <0x101f3000 0x1000
                   0x101f4000 0x0010>;
        };
    
        interrupt-controller@10140000 {
            compatible = "arm,pl190";
            reg = <0x10140000 0x1000 >;
        };
    
        spi@10115000 {
            compatible = "arm,pl022";
            reg = <0x10115000 0x1000 >;
        };
        ...
    };
    

      

    non memory mapped devices

      处理器总线的其它设备为非内存映射设备。他们有地址范围,但不能被CPU直接寻址。母设备的驱动程序将代替CPU进行间接访问。以i2c设备为例,每个设备都分配了一个地址,但没有长度或范围与之相匹配。这与CPU地址分配很相似。

     i2c@1,0 {
         compatible = "acme,a1234-i2c-bus";
         #address-cells = <1>;
         #size-cells = <0>;
         reg = <1 0 0x1000>;
         rtc@58 {
             compatible = "maxim,ds1338";
             reg = <58>;
         };
     };
    

      

    ranges(address translations)

      我们已经讨论过如何向设备分配地址,但此时这些地址只是本地设备节点,还没有说明如何从那些地址里映射到cpu可以使用的地址。根节点经常描述地址空间的CPU视图。根节点的子节点已经使用了CPU的address domain,所以不需要任何明确的映射。例如,serial@101f0000设备被直接分配了地址0x101f0000。
      根节点的非直接子节点是无法使用CPU的address domain的。为了在deivce tree获取内存映射地址必须指定如何从一个域名将地址转换到另一个。Ranges属性就用于此目的。以下是添加了ranges属性的device tree示例。

    /dts-v1/;
    / {
        compatible = "acme,coyotes-revenge";
        #address-cells = <1>;
        #size-cells = <1>;
        ...
        external-bus {
            #address-cells = <2>
            #size-cells = <1>;
            ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
                      1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
                      2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash
    
            ethernet@0,0 {
                compatible = "smc,smc91c111";
                reg = <0 0 0x1000>;
            };
    
            i2c@1,0 {
                compatible = "acme,a1234-i2c-bus";
                #address-cells = <1>;
                #size-cells = <0>;
                reg = <1 0 0x1000>;
                rtc@58 {
                    compatible = "maxim,ds1338";
                    reg = <58>;
                };
            };
    
            flash@2,0 {
                compatible = "samsung,k8f1315ebm", "cfi-flash";
                reg = <2 0 0x4000000>;
            };
        };
    };
    

      Ranges是一个地址转换列表。每个输入ranges表格的是包含子地址的元组,母地址和子地址空间的范围大小。每个字段的大小都由获取的子地址的#address-cells值,母地址的#address-cell值和子地址的#size-cells值而定。以外部总线为例,子地址是2 cells,母地址是1 cell,大小也为1 cell。转换三个ranges:

    • Offset 0 from chip select 0 is mapped to address range 0x10100000…0x1010ffff
    • Offset 0 from chip select 1 is mapped to address range 0x10160000…0x1016ffff
    • Offset 0 from chip select 2 is mapped to address range 0x30000000…0x30ffffff

      例如上面的总线是有片选的,就需要描述片选及片选的偏移量,在说明地址时,还需要说明地址映射范围。


    4.中断映射

      与遵循树的自然结构而进行的地址转换不同,机器上的任何设备都可以发起和终止中断信号。另外地址的编址也不同于中断信号,前者是设备树的自然表示,而后者者表现为独立于设备树结构的节点之间的链接。 下图显示了设备的自然结构以及每个节点在逻辑中断树中的位置。

      

     上图包括以下部分:

    • open-pic中断控制器是中断树的根
    • 中断树根有三个子设备,它们将中断直接路由到open-pic
      • device1
      • PCI host controller
      • GPIO Controller
    • 存在三个中断域; 一个以开放式pic节点为根,一个在PCI主桥节点,一个在GPIO Controller节点上
    • 有两个nexus节点; 一个位于PCI主桥,一个位于GPIO控制器。

     下面显示了具有PCI总线控制器和采样中断的设备片段

    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* /
    	>;
    	};
    };
    

      

    5.dtsi文件

      设备树文件不需要我们从零写出来,内核支持了某款芯片比如 imx6ull,在内核的arch/arm/boot/dts目录下就有了能用的设备树模板,一般命名为xxxx.dtsi。“i”表示“include”,被别的文件引用的。
      
      我们使用某款芯片制作出了自己的单板,所用资源跟 xxxx.dtsi 是大部分相同,小部分不同,所以需要引脚 xxxx.dtsi 并修改。
      dtsi 文件跟 dts 文件的语法是完全一样的。


      dts 中可以包含.h 头文件,也可以包含 dtsi 文件,在.h 头文件中可以定义一些宏。

    /dts-v1/;
    #include <dt-bindings/input/input.h>
    #include "imx6ull.dtsi"
    / {
    ……
    };

    6.常用的属性

    #address-cells、#size-cells

      cell 指一个 32 位的数值:

      address-cells:address 要用多少个 32 位数来表示;

      size-cells:size 要用多少个 32 位数来表示大小。

      比如一段内存,怎么描述它的起始地址和大小?

      下例中,address-cells 为 1,所以 reg 中用 1 个数来表示地址,即用 0x80000000 来表示地址;size-cells 为 1,所以 reg 中用 1 个数来表示大小,即用 0x20000000 表示大小:

    / {
        #address-cells = <1>;
        #size-cells = <1>;
        memory {
            reg = <0x80000000 0x20000000>;
        };
    };    

    compatible

      “compatible”表示“兼容”,对于某个 LED,内核中可能有 A、B、C 三个驱动都支持它,那可以这样写:

    led {
        compatible = “A”, “B”, “C”;
    };

      内核启动时,就会为这个 LED 按这样的优先顺序为它找到驱动程序:A、B、C。

      根节点下也有 compatible 属性,用来选择哪一个“machine desc”:一个内核可以支持machine A,也支持 machine B,内核启动后会根据根节点的 compatible 属性找到对应的machine desc 结构体,执行其中的初始化函数。

      compatible 的值,建议取这样的形式:"manufacturer,model",即“厂家名,模块名”。

    model

      model 属性与 compatible 属性有些类似,但是有差别。
      compatible 属性是一个字符串列表,表示可以你的硬件兼容 A、B、C 等驱动;
      model 用来准确地定义这个硬件是什么。

      比如根节点中可以这样写:

    / {
        compatible = "samsung,smdk2440", "samsung,mini2440";
        model = "jz2440_v3";
    };

      它表示这个单板,可以兼容内核中的“smdk2440”,也兼容“mini2440”。

      从 compatible 属性中可以知道它兼容哪些板,但是它到底是什么板?用 model 属性来明确。

    status

      dtsi 文件中定义了很多设备,但是在你的板子上某些设备是没有的。这时你可以给这个设备节点添加一个 status 属性,设置为“disabled”:

    &uart1 {
        status = "disabled";
    };

    reg

      reg 的本意是 register,用来表示寄存器地址。

      但是在设备树里,它可以用来描述一段空间。反正对于 ARM 系统,寄存器和内存是统一编址的,即访问寄存器时用某块地址,访问内存时用某块地址,在访问方法上没有区别。

      reg 属性的值,是一系列的“address size”,用多少个 32 位的数来表示 address 和 size,由其父节点的#address-cells、#size-cells 决定。

    name(过时了,建议不用)

      它的值是字符串,用来表示节点的名字。在跟 platform_driver 匹配时,优先级最低。

      compatible 属性在匹配过程中,优先级最高。

    device_type(过时了,建议不用)

      它的值是字符串,用来表示节点的类型。在跟 platform_driver 匹配时,优先级为中。
      compatible 属性在匹配过程中,优先级最高。

       memory@30000000 {
            device_type = "memory";
            reg = <0x30000000 0x20000000>;
        }; 
    

      device_type属性定义了该node的设备类型,例如cpu、serial等。对于memory node,其device_type必须等于memory。

    根节点

      dts 文件中必须有一个根节点:

    /dts-v1/;
    / {
        model = "SMDK24440";
        compatible = "samsung,smdk2440";
        #address-cells = <1>;
        #size-cells = <1>;
    };

      根节点中必须有这些属性:

    #address-cells // 在它的子节点的 reg 属性中, 使用多少个 u32 整数来描述地址(address)
    #size-cells // 在它的子节点的 reg 属性中, 使用多少个 u32 整数来描述大小(size)
    compatible // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备
        // 即这个板子兼容哪些平台
        // uImage : smdk2410 smdk2440 mini2440 ==> machine_desc
    model // 咱这个板子是什么
        // 比如有 2 款板子配置基本一致, 它们的 compatible 是一样的
        // 那么就通过 model 来分辨这 2 款板子

    CPU 节点

      一般不需要我们设置,在 dtsi 文件中都定义好了:

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu0: cpu@0 {
            .......
        }
    };    

       多核的通常会设置cpu的频率。

    cpus {
            #address-cells = <1>;
            #size-cells = <0>;
     
            cpu0: cpu@0 {
                device_type = "cpu";
                compatible = "arm,cortex-a15";
                reg = <0x0>;
                clock-frequency = <1600000000>;
            };  
     
            cpu1: cpu@1 {
                device_type = "cpu";
                compatible = "arm,cortex-a15";
                reg = <0x1>;
                clock-frequency = <1600000000>;
            };  
     
            cpu2: cpu@2 {
                device_type = "cpu";
                compatible = "arm,cortex-a15";
                reg = <0x2>;
                clock-frequency = <1600000000>;
            };  
     
            cpu3: cpu@3 {
                device_type = "cpu";
                compatible = "arm,cortex-a15";
                reg = <0x3>;
                clock-frequency = <1600000000>;
            };
        };
    

      

    memory 节点

      芯片厂家不可能事先确定你的板子使用多大的内存,所以 memory 节点需要板厂设置,比如:

    memory {
        reg = <0x80000000 0x20000000>;
    };

    chosen 节点

      我们可以通过设备树文件给内核传入一些参数,这要在 chosen 节点中设置 bootargs 属性

    chosen {
        bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";    
    };

    6.编译、更换设备树

    6.1在内核中直接make

      设置 ARCH、CROSS_COMPILE、PATH 这三个环境变量后,进入 ubuntu 上板子内核源码的目录,执行如下命令即可编译 dtb 文件:

    make dtbs V=1

      编译成功会在arch/arm/boot/dts/目录下有相关单板的dtb文件。

      然后在板子启动后相对应位置替换一下dtb文件就可以了。

    单独编译:

    make dtbs CROSS_COMPILE=arm-none-linux-gnueabi-
    

    反编译:

    dtc -I dtb -O dts -o tmp.dts s5pv210-x210.dtb 
    

      

      

     https://blog.csdn.net/u012489236?t=1

    https://blog.csdn.net/qq_16777851/article/details/88958098

  • 相关阅读:
    在vs code中进行本地调试和开启本地服务器
    按highcharts中column形式转对象展现格式
    forEach、for in 、 for of三者的区别
    一位数左边补0,slice也可以
    js判断对象是否为空
    谷歌最佳实践
    谷歌最佳实践
    谷歌最佳实践
    谷歌最佳实践
    企业代码版本管理之争:TrunkBased vs GitFlow vs AoneFlow vs OneFlow vs ExeFlow
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/12470081.html
Copyright © 2011-2022 走看看