• Linux设备树(2)——设备树格式和使用


    一、设备树dts文件的语法规范

    1. DTS文件布局(layout)

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

    (1) 特殊的、默认的属性

    a. 根节点的:

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

    (2) /memory 节点

    device_type = "memory";
    reg             // 用来指定内存的地址、大小

    (3) /chosen 节点

    bootargs        // 内核 command lin e参数,跟u-boot中设置的bootargs作用一样

    (4) /cpus 节点

    /cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu,所以 /cpus 中有以下2个属性:

    #address-cells   // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
    #size-cells      // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size),必须设置为0

    2. Devicetree node格式:

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

    (1) Property的2种格式

    [label:] property-name = value;    //有值
    
    [label:] property-name;    //有值

    (2) Property的3种取值 

    arrays of cells:1个或多个32位数据,64位数据使用2个32位数据表示。
    string:字符串, 
    bytestring:1个或多个字节
    
    示例: 
    a. Arrays of cells,一个cell就是一个32位的数据
    interrupts = <17 0xc>;
    
    b. 64bit数据使用2个cell来表示
    clock-frequency = <0x00000001 0x00000000>;
    
    c. null-terminated string
    compatible = "simple-bus";
    
    d. bytestring(字节序列)
    local-mac-address = [00 00 12 34 56 78];  // 每个byte使用2个16进制数来表示
    local-mac-address = [000012345678];       // 每个byte使用2个16进制数来表示(中间也可以没有空格)
    
    e. 可以是各种值的组合, 用逗号隔开:
    compatible = "ns16550", "ns8250";    //是可以附多个值的,对每个字符串的获取可参考__of_device_is_compatible()
    example = <0xf00f0000 19>, "a strange property format";

    3. 引用其他节点

    (1) 通过phandle来引用 // 节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的phandle值一样),例子:

    pic@10000000 {
        phandle = <1>;
        interrupt-controller;
    };
    
    another-device-node {
        interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点
    };

    (2) 通过label来引用

    PIC: pic@10000000 {
        interrupt-controller;
    };
    
    another-device-node {
        /*
         * 使用label来引用上述节点,使用lable时实际上也是使用phandle来引用,
         * 在编译dts文件为dtb文件时,编译器dtc会在dtb中插入phandle属性。
        */
        interrupt-parent = <&PIC>;   
    };

    4. dts文件示例

    /dts-v1/;
    
    /memreserve/ 0x33f00000 0x100000 //预留1M内存,不给内核使用
    
    / {
        model = "SMDK24440";
        /*
         * 这里指定了两个值,从左到右依次匹配,只要有一个值匹配上了即可,匹配函数可见上
         * 面的__of_device_is_compatible().
         * 所有的字符串,一般是从具体到一般。
         * 也可以是前面是我们自己开发的平台的,后面是EVB的。利用EVB的进行匹配,自己的起
         * 说明作用。
         */
        compatible = "samsung,smdk2440", "samsung,smdk24xx";
    
        /*
         * 一个cells表示一个32bit的unsigned int。
         * 这里表示在其子节点里面,起始地址使用一个32bit的int表示,
         * 大小使用一个32bit的int表示。
         */
        #address-cells = <1>;
        #size-cells = <1>;
        
        /*解析成平台设备的设备名字为"30000000.memory",设备树中的路径名是"/memory@30000000"*/
        memory@30000000 {
            /*内存的device_type是约定好的,必须写为"memory"*/
            device_type = "memory";
            /*
             * 表示一段内存,起始地址是0x30000000,大小是0x4000000字节。
             * 若是reg=<0x30000000 0x4000000 0 4096> 则表示两段内存,另一段的
             * 起始地址是0,大小是4096字节。解析成这样的结果的原因是上面指定了
             * address-cells和size-cells都为1.
             */
            reg =  <0x30000000 0x4000000>;
            
            /*解析成平台设备的设备名字为"38000000.trunk",设备树中的路径名是"/memory@30000000/trunk@38000000"*/
            trunk@38000000 {
                device_type = "memory_1";
                reg =  <0x38000000 0x4000000>;
            };
        };
    
        /*指定命令行启动参数*/
        chosen {
            bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
        };
    
        
        led {
            compatible = "jz2440_led";
            pin = <S3C2410_GPF(5)>;
        };
        
        
        pic@10000000 {
            /*这个phandle必须是唯一的*/
            phandle = <1>;
            interrupt-controller;
        };
    
        another-device-node {
            interrupt-parent = <1>;   // 使用phandle值为1来引用上面的节点
        };
        
        /*上面的引用比较麻烦,可以使用下面的方法来引用lable*/
        PIC: pic@10000000 {
            interrupt-controller;
        };
        
        another-device-node {
            /*
             * 使用label来引用上述节点,使用lable时实际上也是
             * 使用phandle来引用,在编译dts文件为dtb文件时, 编译
             * 器dtc会在dtb中插入phandle属性
             */
            interrupt-parent = <&PIC>;
        };
    
    };

    5. dts文件对dtsi文件中节点的引用与改写

      设备树中把一些公共的部分写在 .dtsi 文件中,.dts 文件可以去包含 .dtsi 文件,两者的语法格式是相同的。若是把上面内容定义在 smdk2440.dtsi 文件中,使用基于smdk2440的平台的dts文件包含它,并且想覆盖led节点的方法是在dts文件中:

    (1) 若是led节点在dtsi中没有指定label,需要通过全路径引用

    /dts-v1/;
    #include "jz2440.dtsi"
    
    / {
        &led {
            pin = <S3C2410_GPF(6)>;
        };
    
    };

    (2) 若是在dtsi中指定了label,如在dtsi中的表示为

        Led1: led {
            compatible = "jz2440_led";
            pin = <S3C2410_GPF(5)>;
        };

    这样的话在dts文件中只需要下面操作即可:

    /dts-v1/;
    #include "jz2440.dtsi"
    
    &Led1 {
        pin = <S3C2410_GPF(6)>;
    }

    也就是后面写的属性会覆盖前面写的属性。

    使用lable后,不需要也不能写在根节点里面了,直接写。

    设备树中任何节点的路径名不能相同,否则就被认为是同一个设备树节点。

    可以通过反编译dtb文件来验证修改的正确性,将dtb转换为dts的方法: ./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/jz2440.dtb

    6. 若是设备树节点没有写status项,默认就是是能的

    static bool __of_device_is_available(const struct device_node *device)
    {
        const char *status;
        int statlen;
    
        if (!device)
            return false;
    
        status = __of_get_property(device, "status", &statlen);
        if (status == NULL)
            return true; //默认为使能
    
        if (statlen > 0) {
            if (!strcmp(status, "okay") || !strcmp(status, "ok")) //ok和okay都可以
                return true;
        }
        //表中的fail和fail-sss没做具体处理
        return false;
    }

    7. 相关资料

    DT官方文档: https://www.devicetree.org/specifications/

    官方文档(DTB格式): https://www.devicetree.org/specifications/

    内核文档: Documentation/devicetree/booting-without-of.txt

    内核文档: Documentation/devicetree/usage-model.txt

    二、设备树dtb的内存布局

    1. DTB文件布局:

                 ------------------------------
         base -> |  struct boot_param_header  |
                 ------------------------------
                 |      (alignment gap) (*)   |
                 ------------------------------
                 |      memory reserve map    |
                 ------------------------------
                 |      (alignment gap)       |
                 ------------------------------
                 |                            |
                 |    device-tree structure   |
                 |                            |
                 ------------------------------
                 |      (alignment gap)       |
                 ------------------------------
                 |                            |
                 |     device-tree strings    |
                 |                            |
          -----> ------------------------------
          |
          |
          --- (base + totalsize)

    “device-tree strings” 区域中存放dts中所有属性的名字,使用‘’隔开各个字符。如“compatible”、“#address-cells”、“#size-cells”、“device_type”、“reg”、“bootargs”等左值字符串。但是右值字符串不是存放在这里的。

    “memory reserve map” 中存放预留内存信息,例如:“/memreserve/ 0x33f00000 0x100000”,使用struct fdt_reserve_entry结构存储。

    “device-tree structure” 中存储所有的设备节点

    2. 注意,在dtb文件中数据的存放格式是大字节序的,大小字节序只对数值的存储有差别,对于字符串的存储是没有差别的。

    3. 相关结构体描述定义在:linux4.14.39/scripts/dtc/libfdt/fdt.h

    /*设备树的头部信息描述,用UE打开一个dtb文件,最开始的就是fdt_header*/
    struct fdt_header
    
    /*描述reserved的内存*/
    struct fdt_reserve_entry

    4. 参考文档:Documentationdevicetreeooting-without-of.txt

    三、设备树dtc,dtdiff工具

    1. dtc工具安装

    # apt-get install device-tree-compiler
    # dtc 
    # dtc  --help

    2. 由dts生成dtb:

    # dtc -I dts -O dtb -o devicetree.dtb jz2440.dts

    3. 由dtb生成dts

    # dtc -I dtb -O dts -o tmp.dts devicetree.dtb

    4. dtc --help

    Usage: dtc [options] <input file>
    
    Options: -[qI:O:o:V:d:R:S:p:fb:i:H:sW:E:hv]
      -q, --quiet                
        Quiet: -q suppress warnings, -qq errors, -qqq all
      -I, --in-format <arg>      
        Input formats are:
            dts - device tree source text
            dtb - device tree blob
            fs  - /proc/device-tree style directory
      -o, --out <arg>            
        Output file
      -O, --out-format <arg>     
        Output formats are:
            dts - device tree source text
            dtb - device tree blob
            asm - assembler source
      -V, --out-version <arg>    
        Blob version to produce, defaults to %d (for dtb and asm output)
      -d, --out-dependency <arg> 
        Output dependency file
      -R, --reserve <arg>        
        tMake space for <number> reserve map entries (for dtb and asm output)
      -S, --space <arg>          
        Make the blob at least <bytes> long (extra space)
      -p, --pad <arg>            
        Add padding to the blob of <bytes> long (extra space)
      -b, --boot-cpu <arg>       
        Set the physical boot cpu
      -f, --force                
        Try to produce output even if the input tree has errors
      -i, --include <arg>        
        Add a path to search for include files
      -s, --sort                 
        Sort nodes and properties before outputting (useful for comparing trees)
      -H, --phandle <arg>        
        Valid phandle formats are:
            legacy - "linux,phandle" properties only
            epapr  - "phandle" properties only
            both   - Both "linux,phandle" and "phandle" properties
      -W, --warning <arg>        
        Enable/disable warnings (prefix with "no-")
      -E, --error <arg>          
        Enable/disable errors (prefix with "no-")
      -h, --help                 
        Print this help and exit
      -v, --version              
        Print version and exit

    5. dtdiff 工具用于对比设备树的差别:

    # dtdiff devicetree.dtb devicetree_1.dtb 
    --- /dev/fd/63    2019-06-08 11:43:56.086042406 +0800
    +++ /dev/fd/62    2019-06-08 11:43:56.086042406 +0800
    @@ -17,6 +17,6 @@
     
         memory@30000000 {
             device_type = "memory";
    -        reg = <0x30000000 0x4000000>;
    +        reg = <0x30000000 0x4000002>;
         };
     };

    dtdiff使用的是两个dtb文件进行对比的,,它可以将节点名进行对齐对比,比diff -Naur对比两个目录下的dtc文件要方便。

    5. 一个使用场景

    通过反编译dtb获取的dts文件比较纯净,因为实际项目上可能有多个dtsi被包含进来,搞的人眼花缭乱。通过反编译得到的dts文件只需要看这一个文件即可。

    四、设备树节点变为 platform_device 的过程和与驱动的匹配过程

    1. 在dts文件中构造节点,每一个节点中都含有资源,充当平台设备的设备端。编译后生成 .dtb 文件传给内核,内核解析设备树后为每一个节点生成一个 device_node 结构,然后根据这个结构生成平台设备的设备端。根据设备树节点的 compatible 属性来匹配平台设备的驱动端。

    .dts ---> .dtb ---> struct device_node ---> struct platform_device
    
    注: 
    dts  - device tree source  // 设备树源文件
    dtb  - device tree blob    // 设备树二进制文件, 由dts编译得来
    blob - binary large object

    2. 来自dts的platform_device结构体 与 我们写的platform_driver 的匹配过程

      来自 dts 的 platform_device 结构体 里面有成员 ".dev.of_node",它里面含有各种属性, 比如 compatible, reg, pin等。我们写的 platform_driver 里面有成员 ".driver.of_match_table",它表示能支持哪些来自于 dts 的 platform_device。

      如果设备端的 of_node 中的 compatible 跟 驱动端的 of_match_table 中的 compatible 一致,就可以匹配成功,则调用platform_driver 中的 probe 函数。在probe函数中,可以继续从 of_node 中获得各种属性来确定硬件资源。

    platform_match(struct device *dev, struct device_driver *drv) // drivers/base/platform.c
        of_driver_match_device(dev, drv) // include/linux/of_device.h 
            of_match_device(drv->of_match_table, dev) // drivers/of/device.c
                of_match_node(matches, dev->of_node); // drivers/of/device.c
                    __of_match_node(matches, node); // drivers/of/device.c
                        //对于驱动of_device_id中给出的每一项都与设备树节点的compatible属性中的每一个值进行匹配,
                        //返回匹配度最高的计数值best_match
                        __of_device_is_compatible(node, matches->compatible, matches->type, matches->name);
    static int platform_match(struct device *dev, struct device_driver *drv)
    {
        struct platform_device *pdev = to_platform_device(dev);
        struct platform_driver *pdrv = to_platform_driver(drv);
    
        /* When driver_override is set, only bind to the matching driver */
        if (pdev->driver_override)
            return !strcmp(pdev->driver_override, drv->name);
    
        /* Attempt an OF style match first */
        if (of_driver_match_device(dev, drv))
            return 1;
    
        /* Then try ACPI style match */
        if (acpi_driver_match_device(dev, drv))
            return 1;
    
        /* Then try to match against the id table */
        if (pdrv->id_table)
            return platform_match_id(pdrv->id_table, pdev) != NULL;
    
        /* fall-back to driver name match */
        return (strcmp(pdev->name, drv->name) == 0);
    }

    platform_match分析:

    a. 如果 pdev->driver_override 被赋值,就直接使用它进行设备和驱动的名字进行匹配。
    b. 尝试使用设备树进行匹配
    c. 如果平台驱动端提供了 pdrv->id_table,则使用平台设备的名字与平台驱动 id_table 列表中的名字进行匹配。
    d. 否则直接匹配平台设备的名字和平台驱动的名字。

    3. 有可能compatible相同也不一定选择的就是这个匹配,因为有可能有匹配度更高的,比如除了compatible匹配上了以外,name和type也都匹配上了,那么匹配度就是最高的。

    of_match_table是struct device_driver的成员 const struct of_device_id *of_match_table,定义如下:

    struct of_device_id {
        char    name[32];
        char    type[32];
        char    compatible[128];
        const void *data;
    };

      通过设备树的compatible属性的匹配的规则就是:如果compatible属性不存在,就匹配type和name,但是权重极低。若是compatible属性存在,但是匹配补上就立即返回,不在进行后续的匹配。

    4. compatible 属性的书写规范为:"厂家,产品",例如: "jz2440,led"

    5. 设备树是平台总线设备模型的改进

      引入设备树之前,平台设备模型的资源定义在平台设备的设备端,引入设备树后定义在设备树中,可以说设备树是对平台设备模型的一种改进,本质上还是平台设备模型。

    6. 设备树dts文件的语法

    a. 可以使用一些C语言语法
    b. 使用/* */ 或 // 来注释
    c. 每一句源代码都使用 ";" 隔开

    比如:
    #define S3C2410_GPA(_nr) ((1<<16)+1) // dts文件中使用C语言的语法定义宏

    补充: 是 platform_match 进行匹配的原因

    start_kernel     // init/main.c
        rest_init();
            pid = kernel_thread(kernel_init, NULL, CLONE_FS); /*创建kernel_init内核线程*/
                        kernel_init
                            kernel_init_freeable();
                                do_basic_setup(void)
                                    /*负责设备节点,固件,平台设备初始化,sysfs文件框架搭建*/
                                    driver_init(void)
                                        platform_bus_init(void)
                                            bus_register(&platform_bus_type);
                                                .match    = platform_match,

    五. 设备树的/sys目录下的文件和驱动获取

    1. 设备树的sysfs属性都存在于of_node下,of_node(open firmare node) 这个目录下就是设备树中节点的成员了。可以直接打印里面的 reg 成员寄存器中的值

    # hexdump reg
    # hexdump -C reg 以字节和ASCII码的方式显示出来,可以自己加"-h"选项查看出来。

    例如:

    led {
        compatible = "jz2440_led"; 
        reg = <(5<<16)+5, 1>
    };
    # hexdump -C reg 的结果就是:
    00 05 00 05 00 00 00 01        // 设备树中是使用大字节序描述的。

    2. 驱动中对设备树节点属性的获取

    参考 linux4.14.39/include/linux/of.h,这里面的函数都是以 struct device_node 结构为参数的。

    设备树节点构造成的 struct device_node 结构存在于:

    struct platform_device; //include/linux/platform_device.h
        struct device dev; //include/linux/device.h
            struct device_node *of_node; //include/linux/of.h 对应的设备树节点

    3. 内核帮助文档

    (1) 驱动程序中使用的设备树节点的内容的编写方法在:documentation/devicetree/bindings

    (2) 整个设备树如何写参考EVB板的: arch/arm64/boot/dts/qcom

    4. 使用设备树编程

    一个写的好的驱动程序,会尽量确定所用的资源,只把不能确定的资源留给设备树,让设备树来指定。

    在设备树节点中填写哪些内容可以通过下面方法确定:
    a. 看文档 documentation/devicetree/bindings
    b. 参考同类型的单板的设备树文件 arch/arm/boot/dts
    c. 网上搜索
    d. 没有其它办法了,就去研究驱动源代码。

    5.在/sys/firmware/devicetree/向上层导出了设备树,也就是说设备树不仅可以配置内核,还可以配置上层应用程序.

    eg: 在system代码中通过/sys下的设备树节点文件来读取配置.
     /sys/firmware/devicetree/base/chosen/bootargs

  • 相关阅读:
    js的alert乱码问题
    (6)select语句
    (5)视图
    (4)索引
    (3)操作数据库
    (2)MySQL数据类型
    (1)MySQL概述
    RocketMQ安装使用
    uniapp打包h5
    面试必问 如何保证缓存与数据库的一致性
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/10992949.html
Copyright © 2020-2023  润新知