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值一样),例子:

[email protected]10000000 {
    phandle = <1>;
    interrupt-controller;
};

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

(2) 通过label来引用

PIC: [email protected]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",设备树中的路径名是"/[email protected]"*/
    [email protected]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",设备树中的路径名是"/[email protected]/[email protected]"*/
        [email protected]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)>;
    };

    [email protected]10000000 {
        /*这个phandle必须是唯一的*/
        phandle = <1>;
        interrupt-controller;
    };

    another-device-node {
        interrupt-parent = <1>;   // 使用phandle值为1来引用上面的节点
    };

    /*上面的引用比较麻烦,可以使用下面的方法来引用lable*/
    PIC: [email protected]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. 相关资料

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

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

内核文档: Documentation/devicetree/booting-without-of.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中所有属性的名字,使用‘\0’隔开各个字符。如“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. 参考文档:Documentation\devicetree\booting-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 @@

     [email protected]30000000 {
         device_type = "memory";
-        reg = <0x30000000 0x4000000>;
+        reg = <0x30000000 0x4000002>;
     };
 };

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语言的语法定义宏

五. 设备树的/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. 没有其它办法了,就去研究驱动源代码。

原文地址:https://www.cnblogs.com/hellokitty2/p/10992949.html

时间: 2024-10-07 02:46:28

Linux设备树(2)——设备树格式和使用的相关文章

ARM Linux 3.x的设备树(Device Tree)【转】

转自:http://blog.csdn.net/21cnbao/article/details/8457546 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] ARM Device Tree起源 Device Tree组成和结构 DTS device tree source DTC device tree compiler Device Tree Blob dtb Binding Bootloader Device Tree引发的BSP和驱动变更 常用OF API 总结

ARM Linux 3.x的设备树(Device Tree)

1.    ARM Device Tree起源 在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备.resource.i2c_board_info.spi_board_info以及各种硬件的platform_data.读者有兴趣可以统计下常见的s3c2410.s3c6410等板级目录,代码量在数万行.社区必须改变这种局面,

【转】ARM Linux 3.x的设备树(Device Tree)

原文网址:http://blog.csdn.net/21cnbao/article/details/8457546 1.    ARM Device Tree起源 Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a f*cking pain in the ass”,引发ARM Linux社区的地震,随后ARM社区进行了一系列的重大修正.在过去的ARM Linux中,arch/arm/plat-xxx和arch/a

关于linux ARM device tree设备树

关于linux ARM device tree设备树 关于linux device tree .dtb文件是如何加载到内核并解析的.见下图: 关于arm device tree的phandle的处理原理,见下图: 详细情况,见下面我的ARM device tree原理视频课程:https://edu.51cto.com/course/17175.html 具体请参考我的免费的linux各种驱动开发课程如下:https://edu.51cto.com/course/17138.html 另外我的相

让天堂的归天堂,让尘土的归尘土——谈Linux的总线、设备、驱动模型

公元1951年5月15日的国会听证上,美国陆军五星上将麦克阿瑟建议把朝鲜战争扩大至中国,布莱德利随后发言:"如果我们把战争扩大到共产党中国,那么我们会被卷入到一场错误的时间,错误的地点同错误的对手打的一场错误的战争中." 写代码,适用于同样的原则,那就是把正确的代码放到正确的位置而不是相反.同样的一个代码,可以出现在多个可能的位置,它究竟应该出现在哪里,是软件架构设计的结果,说白了一切都是为了高内核和低耦合. 1.   陷入绝境 下面我们设想一个名字叫做ABC的简单的网卡,它需要接在一

Linux中设备号及设备文件【转】

本文转载自:http://blog.csdn.net/ymangu666/article/details/39292651 主.次设备号 应用程序可以通过对/dev 目录下的设备文件读写,从而访问实际的设备.1)每个设备文件对应有两个设备号:主设备号,次设备号① 主设备号:标识该设备的类型,也表示了该设备所使用的驱动程序:  驱动程序在初始化时,会注册它的驱动及对应主设备号到系统中,可以通过/proc/devices 文件来驱动系统设备的主设备号.② 次设备号:表示使用同一设备驱动程序的不同硬件

linux /dev 常见特殊设备介绍与应用[loop,null,zero,full,random]

linux是文件型系统,所有硬件如软件都会在对于的目录下面有相应的文件表示.对于dev这个目录,我们知道它下面的文件,表示的是linux的设备.在windows系统中,设备大家很好理解,象硬盘,磁盘指的是实实在在硬件.而在文件系统的linux下面,都有对于文件与这些设备关联的.访问它们就可以放到实际硬件,想想还是linux灵活了.变成文件,操作该多简单了.不用调用以前com,prt等接口了.直接读文件,写文件就可以向设备发送读或者写操作了. 按照读写存储数据方式,我们可以把设备分为以下几种:字符

Linux SPI总线和设备驱动架构之一:系统概述【转】

转自:http://blog.csdn.net/droidphone/article/details/23367051/ 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 硬件结构 工作时序 软件架构 SPI控制器驱动程序 SPI通用接口封装层 SPI协议驱动程序 SPI通用设备驱动程序 SPI是"Serial Peripheral Interface" 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器.传感器.存储设备,SPI设备分为主设备和从设备两种,

Linux SPI总线和设备驱动架构之二:SPI通用接口层

通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了相应的数据结构,这些数据结构一部分是SPI设备.SPI协议驱动和SPI控制器的数据抽象,一部分是为了协助数据传输而定义的数据结构.另外,通用接口层还负责SPI系统与Linux设备模型相关的初始化工作.本章的我们就通过这些数据结构和API的讨论来对整个通用接口层进行深入的了解. /**********