设备树之DTS与DTB格式

目录

 

一、设备树

二、DTS格式

2.1 属性

2.2 节点

2.3 引用其他节点

2.4 小总结

三、DTB格式

3.1 结构

3.2 分析


一、设备树

对于点灯字符设备驱动程序可以有三种写法,首先是传统方法,这种方式直接在程序中写死,其次是利用总线设备驱动模型,利用分离的思想,将程序规划为两部分,对于驱动部分一般是稳定的,在设备部分更改资源,最后是利用设备树方法,可以使用设备树直接指明引脚,而驱动写法的核心不变,差别就在于如何指定硬件资源,这三种方法有各自的优缺点

 优点缺点
传统方法简单不易扩展,需重新编译
总线设备驱动模型易扩展,只需要修改资源稍微复杂 ,冗余代码多(如果有多个版本的硬件就会有多个版本的程序),需重新编译
设备树易扩展,无冗余代码,不需重新编译内核或者驱动 ,只需要提供不一样的设备树文件复杂

对于设备树,通过dts文件来指定资源,内核会根据.dts文件分配设置注册platform_device(平台设备),当更改单板时,只需要重新定义dts文件就可以了,dts文件最终会编译为dtb文件,启动单板的时候,既要启动内核,也要传入dtb文件,因此更改单板时,不需要重新编译驱动程序,只需要提供一个不一样的dtb文件

 

二、DTS格式

写设备树我们是写出dts文件,文件写出来之后根据编译器dtc编译为二进制dtb文件,DTS文件布局(layout)如下,第一行表示版本,第二行表示保留的的内存区域,比如板子有64M内存,里面想留下4M给自己使用,不想让内核使用,就可以定义这个选项,如果想让内核使用全部内存就可以省略这个选项

/dts-v1/;
[memory reservations]        /* 格式为:/memreserve/ <address> <length>; */
/{                           /* /表示根,设备树的起点 */
    [property definitions]   /* 首先有属性来描述硬件 */
    [child nodes]            /* 子节点,在子节点中还可以有子节点 */
};

 

2.1 属性

对于属性"[property definitions]"的格式有两种,即无值和有值两种方式,属性后需要分号

Property格式1:
[label:] property-name = value;
Property格式2(没有值):
[label:] property-name = value;

如下led节点,可以知道属性中的value值的形式有多种

led {
	compatible = "jz2440_led";
	pin = <S3C2410_GPF>;  //S3C2410_GPF为一个宏
};

对于value的值有三种表示:arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示)、string(字符串)、bytestring(1个或多个字节),这些值什么含义,完全取决于驱动程序

示例:

interrupts = <17 0xc>;(里面每一个数据都是32位数据)

clock-frequency = <0x00000001 0x00000000>; (64bit数据使用2个cell来表示 )

compatible = "simple-bus"; (A null-terminated string有结束符的字符串)

A bytestring(字节序列) : local-mac-address = [00 00 12 34 56 78]; 或者local-mac-address = [000012345678]; (每个byte使用2个16进制数来表示 )

可以是各种值的组合, 用逗号隔开: compatible = "ns16550", "ns8250";example = <0xf00f0000 19>, "a strange property format";

 

特殊、默认的属性:

在根节点中

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

如下,若CPU为64位的,则需要两个32位来表示reg,因此可以写为#address-cells = <1>;  对于reg中还可以写多个内存,如reg = <0x30000000 0x40000000 0 4096>; 第一个是地址单元 第二个表示大小,可以写多个address size

compatible:定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即这个板子兼容哪些平台, uImage是用来选择单板的,uImage支持很多单板每个单板都会有一个machine_desc结构体 ,如下设备树优先去寻找 “samsung,smdk2440”的machine_desc,如果找不到的话再来找 "samsung,s3c24xx"

chosen节点可以设置内核command line参数, 跟u-boot中设置的bootargs作用一样

cpus节点代表有多个CPU,对于个人的单板只有一个CPU,就可以只写一个子节点,在子节点中需要写device_type = "cpu";上面的例子没有引用cpus节点,cpu子节点中用reg属性用来标明自己是哪一个cpu, 所以 /cpus 中有以下2个属性: #address-cells:在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address),#size-cells:在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size),必须设置为0

/ {
	model = "SMDK24440";
	compatible = "samsung,smdk2440";

	#address-cells = <1>;
	#size-cells = <1>;
		

    memory {
    device_type = "memory";       //这是大家约定的,必须写这个
    reg = <0x30000000 0x4000000>; //reg用来表示内存的起始地址和大小
    };
/*
	cpus {
		cpu {
			compatible = "arm,arm926ej-s";
		};
	};
*/	
	chosen {
		bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
	};

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

 

2.2 节点

对于节点"[child nodes]"的格式如下,可以看出节点可以有自己的子节点,节点结束后需要分号

[label:] node-name[@unit-address] {   //label可以写也可以不写,写的时候别人引用该节点就比较方便,而node-name比如上面写的led
    [properties definitions]
    [child nodes]
};

对于node-name后面可以加上地址,组合起来就是节点的名字,假设有两个memory(内存)节点在同一级别就可以来用区别,用@加上内存的首地址来表示名字的不同,在memory的子目录下可以有同名的memory目录,当然没有这么古怪的节点,这里只是举例

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

memory@0{
    device_type = "memory";
    reg = <0 4096>;  
};

 

2.3 引用其他节点

利用节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的phandle值一样)

pic@10000000 {
    phandle = <1>;
    interrupt-controller;
};

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

还有另外一种方式,实质也是利用了phandle属性,使用label来引用节点

PIC: pic@10000000 {   //编译器dtc会在父节点插入一个 phandle = <xxx>; 然后会把下面的引用<&PIC>替换为<xxx>
    interrupt-controller;    
};

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

举例,在dts中会把共同的地方抽出来然后写为dtsi文件

  • dtsi文件
// SPDX-License-Identifier: GPL-2.0
/*
 * SAMSUNG SMDK2440 board device tree source
 *
 * Copyright (c) 2018 weidongshan@qq.com
 * dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
 */
 
#define S3C2410_GPA(_nr)	((0<<16) + (_nr))
#define S3C2410_GPB(_nr)	((1<<16) + (_nr))
#define S3C2410_GPC(_nr)	((2<<16) + (_nr))
#define S3C2410_GPD(_nr)	((3<<16) + (_nr))
#define S3C2410_GPE(_nr)	((4<<16) + (_nr))
#define S3C2410_GPF(_nr)	((5<<16) + (_nr))
#define S3C2410_GPG(_nr)	((6<<16) + (_nr))
#define S3C2410_GPH(_nr)	((7<<16) + (_nr))
#define S3C2410_GPJ(_nr)	((8<<16) + (_nr))
#define S3C2410_GPK(_nr)	((9<<16) + (_nr))
#define S3C2410_GPL(_nr)	((10<<16) + (_nr))
#define S3C2410_GPM(_nr)	((11<<16) + (_nr))

/dts-v1/;

/ {
	model = "SMDK24440";
	compatible = "samsung,smdk2440";

	#address-cells = <1>;
	#size-cells = <1>;
		
	memory {  /* /memory */
		device_type = "memory";
		reg =  <0x30000000 0x4000000 0 4096>;		
	};

	
/*
	cpus {
		cpu {
			compatible = "arm,arm926ej-s";
		};
	};
*/	
	chosen {
		bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
	};

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

  • dts文件
// SPDX-License-Identifier: GPL-2.0
/*
 * SAMSUNG SMDK2440 board device tree source
 *
 * Copyright (c) 2018 weidongshan@qq.com
 * dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
 */
 
/dts-v1/;

#include "jz2440.dtsi"

/ {
    led {
		pin = <S3C2410_GPF(6)>;
	};
};

这样后面写的属性会覆盖前面的属性,反汇编"./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/jz2440.dtb ",查看tmp.dts可以看到led节点中的pin引脚就被覆盖了

/dts-v1/;

/ {
        model = "SMDK24440";
        compatible = "samsung,smdk2440";
        #address-cells = <0x1>;
        #size-cells = <0x1>;

        memory {
                device_type = "memory";
                reg = <0x30000000 0x4000000 0x0 0x1000>;
        };

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

        led {
                compatible = "jz2440_led";
                pin = <0x50006>;
        };
};

上面这种方式比较麻烦,若不想重头写led节点可以修改dtsi中的led节点加上label

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

修改dts中的led节点,反汇编之后一样会看到节点的变化

/dts-v1/;

#include "jz2440.dtsi"
&LED {
   pin = <S3C2410_GPF(7)>;
};

 

2.4 小总结

在内核文档中Documentation\devicetree\usage-model.txt,其中对设备树进行了三部分的总结,首先是平台识别信息,其次是运行时的配置,最后是设备枚举

Linux uses DT data for three major purposes:
1) platform identification,
2) runtime configuration, and
3) device population.

举例

  • 平台信息
model = "SMDK24440";
compatible = "samsung,smdk2440";

#address-cells = <1>;
#size-cells = <1>;
  • 运行时的配置,设置bootargs传给内核,还有/memreserve/ 0x33000000 0x10000;
chosen {
	bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
  • 设备枚举
LED: led {
	compatible = "jz2440_led";
	pin = <S3C2410_GPF(5)>;
};

 

三、DTB格式

用dtc编译器把dts文件转换为dtb文件,首先会将宏展开,其次还有dtsi和dts编译为一个dtb文件,可以根据可读性强的代码进行帮我们找到错误,对于DTB格式,可以阅读官方文档:https://www.devicetree.org/specifications/ ,也可以阅读内核文档:Documentation/devicetree/booting-without-of.txt

 

3.1 结构

dtb文件分为四部分,首先是头部信息,其次是内存保留区、结构区和字符串区,对于保留区:例/memreserve/ 0x33f00000 0x100000;放在了memory reserve map,其中都是64位表示的,在dtb文件中数据存放的格式是以大端保存的,对于小端高位存放在高地址,对于大端低位存放在低地址

在官方文件中头部header的结构体,在文档描述了magic必须是D00DFEED,totalsize为文件大小,查看文件属性占用空间是4096而实际的大小是465个字节,465的16进制是1D1,因此是"01 D1 00 00",而off_dt_struct是structure block在文件中的偏移地址,off_dt_strings、off_mem_rsvmap同理,在strings block存放属性的名称,每一个名字有0字符结尾,属性的名字例如compatible用了多次为了节约空间,把属性的名字单独作为字符串保留在strings block里面

struct fdt__header {
    uint32_t magic;
    uint32_t totalsize;
    uint32_t off_dt_struct ;
    uint32_t off_dt_strings ;
    uint32_t off_mem_rsvmap;
    uint32_t version;
    uint32_t last_comp_version;
    uint32_t boot_cpuid_phys;
    uint32_t size_dt_strings;
    uint32_t size_dt_struct;
};

对于内存保留区在文档中也有个结构体用来表示 ,并且都是64位的表示地址和大小,在下面例子中"/memreserve/ 0x33f00000 0x100000;"在dts文件中在第四行和五行标红的地方

struct fdt_reserve_entry {
    uint64_t address;
    uint64_t size;
};

 

 

3.2 分析

  • dtb文件
// SPDX-License-Identifier: GPL-2.0
/*
 * SAMSUNG SMDK2440 board device tree source
 *
 * Copyright (c) 2018 weidongshan@qq.com
 * dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
 */
 
#define S3C2410_GPA(_nr)	((0<<16) + (_nr))
#define S3C2410_GPB(_nr)	((1<<16) + (_nr))
#define S3C2410_GPC(_nr)	((2<<16) + (_nr))
#define S3C2410_GPD(_nr)	((3<<16) + (_nr))
#define S3C2410_GPE(_nr)	((4<<16) + (_nr))
#define S3C2410_GPF(_nr)	((5<<16) + (_nr))
#define S3C2410_GPG(_nr)	((6<<16) + (_nr))
#define S3C2410_GPH(_nr)	((7<<16) + (_nr))
#define S3C2410_GPJ(_nr)	((8<<16) + (_nr))
#define S3C2410_GPK(_nr)	((9<<16) + (_nr))
#define S3C2410_GPL(_nr)	((10<<16) + (_nr))
#define S3C2410_GPM(_nr)	((11<<16) + (_nr))

/dts-v1/;

/memreserve/ 0x33f00000 0x100000;

/ {
	model = "SMDK24440";
	compatible = "samsung,smdk2440";

	#address-cells = <1>;
	#size-cells = <1>;
		
	memory {  /* /memory */
		device_type = "memory";
		reg =  <0x30000000 0x4000000 0 4096>;		
	};

	
/*
	cpus {
		cpu {
			compatible = "arm,arm926ej-s";
		};
	};
*/	
	chosen {
		bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
	};

	
	led {
		compatible = "jz2440_led";
		pin = <S3C2410_GPF(5)>;
	};
};
  • dts文件

 分析device-tree structure区域,是最复杂的区,用0x00000001表示根节点和子节点的开始,从0x00000002结束,整个根节点结束 0x00000009 

而节点的名字并不是放在strings block里面 而是紧跟着0x00000001后面加上节点名字,根节点没有名字,因此后面都0,直到0x00000003表示属性开始,那怎么表示名字和值呢,在属性后面有一个结构体,然后再加上val 如果val不是向4取整的话就会自动向4取整,对于第二个属性也一致

struct{
	uint32_t len;  //val长度 
	uint32_t nameoff; //表示属性名字在strings blosk的偏移
}

对于字符串的偏移值为188h, 第一个属性为model,大小为0x0A,在字符串区的偏移值为0,值为"53 4D 44 4B 32 34 34 34 30 00 00 00",自动向4取整,紧接着从第二属性开始即"00 00 00 03"

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值