认识设备树(二)——设备树文件的格式

目录

  • 前言
  • 1 DTS文件的格式
    • 1.1 DTS文件的总体布局
    • 1.2 memory reservations的格式
    • 1.3 属性的格式
      • 1.3.1 有关属性名
      • 1.3.2 有关属性值
    • 1.4 节点的格式
      • 1.4.1 推荐的节点名
      • 1.4.2 节点的路径名
    • 1.5 一些特定的属性
      • 1.5.1 #address-cells
      • 1.5.2 #size-cells
      • 1.5.3 compatible
      • 1.5.4 model
      • 1.5.5 phandle
      • 1.5.6 interrupt-controller
      • 1.5.7 interrupt-parent
      • 1.5.8 reg
    • 1.6 一些特殊的节点
      • 1.6.1 /aliases
      • 1.6.2 /chosen
    • 1.7 必要的节点和必要的属性
    • 1.8 label(标签)的使用
    • 1.9 编写设备树文件
      • 1.9.1 在DTS文件中包含其他文件
      • 1.9.2 如何在设备树文件中描述设备
  • 2 DTB文件的格式
    • 2.1 struct ftd_header
    • 2.2 memory reservation block
    • 2.3 structure block
    • 2.4 strings block
  • 参考文献

前言

本文主要关注的是DTS文件的格式,书写规范,以及DTB文件的格式。

1 DTS文件的格式

1.1 DTS文件的总体布局

设备树源文件通常以dts为后缀,其总体布局如下:

/* 设备树文件支持c语言的注释符 */
// 下面是设备树的总体布局
/dts-v1/;
[memory reservations]
/ {
	[property definitions]
	[child nodes]
};

以上各项的含义分别为:

名称 含义
/dts-v1/ 设备树文件的版本
memory reservations 指定保留内存,内核不会使用保留内存
/ 根节点(使用花括号括出属于根节点的内容)
property definitions 根节点的属性,用来描述硬件
child nodes 孩子节点(使用花括号括出属于孩子节点的内容)

1.2 memory reservations的格式

该项是可选项,如果想要保留一段内存不让内核使用,可用如下格式指定:

/*
	address    指定要保留的内存的起始地址
	length     指定要保留的内存的长度
*/
/memreserve/ 
;

需要注意的是,addresslength都是64位

1.3 属性的格式

  • 属性有值
    [label:] property-name = value;
  • 属性没有值
    [label:] property-name;

其中各项的含义:

名称 含义
label 标签
property-name 属性名
value 属性值

1.3.1 有关属性名

属性名的长度为1~31个字符,可以自己取,只要能够提供可以解读该属性名的驱动即可。也有一些属性名有着特定的含义,比如compatible用于表示哪个或哪些驱动支持该设备。对于自己命名的属性,并非所有字符均可组成属性名,它需要由以下字符组成:
认识设备树(二)——设备树文件的格式_第1张图片

1.3.2 有关属性值

属性值有以下四种:

  1. array of cells
    一个cell就是一个u32类型的数据,一个或多个cell用尖括号括起来,并以空格隔开就可以作为一种合法的属性值,如example = <0x1 0x2 0x3>;
  2. 含有结束符的字符串
    example = "example value";
  3. 字节序列
    用方括号括起一个或多个字节,字节之间可用也可不用空格隔开,且字节以两位16进制数表示,如example = [12 34 56 78];
  4. 以上三种值的混合(以逗号隔开)
    compatible = "fsl,mpc8641", "ns16550";

1.4 节点的格式

节点的格式如下:

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

节点名node-name长为1~31个字符,这些字符可以是:
认识设备树(二)——设备树文件的格式_第2张图片
每个设备节点代表一个设备,因此节点名的命名通常要和设备相应,比如以太网控制器,我们可以给其取名ethernet。考虑到一个SoC中可能有多个以太网控制器,为了做区分,我们通常在其节点名后面加上设备的地址,也就是上文中出现的可选部分@unit-address。仍以以太网控制器为例,加入两个以太网控制器的寄存器组的首地址分别为0xfe002000和0xfe003000,那么相应的节点名可以取为ethernet@fe002000ethernet@fe003000

不难看出,设备节点允许嵌套,假设节点b嵌套于节点a中,那么节点a是节点b的父节点。根节点的名字比较特殊,就是一个斜杠/,其他的设备节点都是根节点的孩子,或者孩子的孩子…因此,所有的设备节点呈现出一个树状的层次结构(设备树因此得名),下图就是一个例子:
认识设备树(二)——设备树文件的格式_第3张图片
值得一提的是,并不是所有的节点都是代表一个设备的设备节点,有一些特殊的节点,不代表任何设备,且有着特定的含义,这些节点在这里我们先不提,后文再专门总结。

1.4.1 推荐的节点名

关于节点的命名,官方有一些推荐的命名,具体可见devicetree-specification-v0.32.2.2节

1.4.2 节点的路径名

在文件系统中有个术语叫文件的路径名(pathname),在按照树状结构组织的众多文件中,用以唯一标识某个文件。类似的,节点也有路径名的概念。将根节点类比为根目录,以上图为例,其中cpu0节点的路径名为/cpus/cpu@0。我们在给节点命名时,必须保证每个节点拥有唯一的路径名(注意区别于每个节点拥有唯一的节点名)。

1.5 一些特定的属性

设备树文件中有一些特定的属性,他们拥有约定俗成的名称和含义,在devicetree-specification中,这些属性被称为Standard Properties,我们在使用这些属性时,应当遵守相应的约定。这些属性有很多,我将在下文中介绍它们中的一部分。

1.5.1 #address-cells

该属性的值表示在该节点的子节点的reg属性中,使用使用多少个cell,也即使用多少个u32整数来表示地址(对于32位系统,一个u32整数就够了;而对于64位系统,需要两个u32整数)。

1.5.2 #size-cells

该属性的值表示在该节点的子节点的reg属性中,使用多少个cell,也即使用多少个u32整数来表示大小(一段地址空间的长度)。

1.5.3 compatible

其值为一个或多个字符串,用来描述支持该设备的驱动程序。比如,该属性位于根节点时,用于指定内核中哪个machine_desc可以支持本设备,即当前设备与哪些平台兼容。其值的格式一般是"manufacturer,model",其中manufacturer表示厂家,model表示型号(厂家的哪型产品)。

当该属性的值有多个字符串时,从左往右,从最特殊到最一般。举例来说,compatible = "samsung,smdk2416", "samsung,s3c2416";作为根节点的属性时,第一个字符串指示了一个具体的开发板型号,而第二个字符串要更一般,只指示了SoC的型号。在linux初始化时,会优先找支持"samsung,smdk2416"machine_desc用以初始化硬件,找不到时才退而求其次——"samsung,s3c2416"

1.5.4 model

其值为一个字符串,用来描述当前设备的型号(单板的名字)。当多个设备的compatible相同时,可以通过model来进一步区分多个设备。

1.5.5 phandle

该属性可以为节点指定一个全局唯一的数字标识符。这个标识符可以被需要引用该节点的另一个节点使用。举例来说,现有一个中断控制器:

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

还有一个可以产生中断的设备,且这个设备的中断信号线连接到了上述中断控制器,为了描述清楚这种关系,该设备的设备节点就需要引用中断控制器的节点:

another-device-node {
	interrupt-parent = <1>; /* 数字1就唯一标识了节点pic@10000000 */
};

1.5.6 interrupt-controller

这是一个没有值的属性,用在中断控制器的设备节点中,以表明这个节点描述的是一个中断控制器。

1.5.7 interrupt-parent

该属性用于可以产生中断,且中断信号连接到某中断控制器的设备的设备节点,用于表示该设备的中断信号连接到了哪个中断控制器。该属性的值通常是中断控制器设备节点的数字标识(phandle),具体示例在上文已经出现过了。

1.5.8 reg

reg属性描述了设备资源在其父总线定义的地址空间内的地址。通俗的说,该属性使用一对或多对(地址,长度)来描述设备所占的地址空间。至于地址长度使用多少个cell来表示呢?这取决于上文介绍的#address-cells、#size-cells属性的值。

举个例子,当:

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

那么reg = <0x3000 0x20 0xFE00 0x100>,表示该属性所属的设备占据了两块内存空间,第一块是以0x3000为起始的32字节内存块;第二块是以0xFE00为起始的256字节内存块。

1.6 一些特殊的节点

上文已经提到,有一些特殊的节点不代表任何设备,而是有着特定的用途,本节就将介绍一些这样的节点。

1.6.1 /aliases

/aliases节点应当作为根节点的孩子节点,用于定义一个或多个别名属性,每条别名属性会为一个设备节点的路径名设置一个别名,别名即为别名属性的属性名,属性值则是设备节点的路径名。如下面这个例子所示:

aliases {
	serial0 = "/simple-bus@fe000000/serial@llc500";
	ethernet0 = "/simple-bus@fe000000/ethernet@31c000";
};

别名只能由1~31个下面的字符组成:
认识设备树(二)——设备树文件的格式_第4张图片
别名通常以数字结尾,比如别名为i2c1,设备树的初始化程序在解析别名属性时,会将数字1记录在struct alias_prop结构的id成员中,使用of_alias_get_id可以获得这个数字。因为本文主要介绍设备树文件的格式,因此这里不再深究这部分内容。

1.6.2 /chosen

/chosen节点应当用作根节点的孩子节点,有以下可选属性:

  • bootargs
  • stdout-path
  • stdin-path

顾名思义,该节点可以指定启动参数、标准输出和标准输入,一个例子如下:

/ {
	......
	chosen {
		bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
	};
	......
};

1.7 必要的节点和必要的属性

一个完整的设备树文件(DTS文件),有一些节点是必须要有的,这些必要的节点有:

  • /
    无需多说,很难想象一个没有根节点的设备树是什么样子的。
  • /cpus
    一个/cpus节点,该节点须作为根节点的孩子节点。对于一块板子,cpu是必不可少的,不然无法允许操作系统,更谈不上设备树了。
  • /memory
    至少一个/memory节点,该节点须作为根节点的孩子节点。当前的计算机架构,内存也是必不可少的。

有些节点有着必要的属性,换句话说,在设备树文件中写了这些节点,那么就必须写上相应的必要的属性。而这些有着必要属性的节点则不一定是设备树文件的必要节点。下面就列出一些有着必要属性的节点,以及它们的必要属性:

节点名 节点的必要属性
/ #address-cells、#size-cells、model、compatible
/memory device_type、reg
/cpus #address-cells、#size-cells
/cpus/cpu* device_type、reg、clock-frequency、timebase-frequency
/cpus/cpu*/l?-cache compatible、cache-level

1.8 label(标签)的使用

在上文就提到过标签,只是没有细说,这里就介绍一下标签的使用。首先,标签名可由1~31个字符组成,这些字符可以是:
认识设备树(二)——设备树文件的格式_第5张图片
接下来,通过具体示例说明如何使用标签。仍以1.5.5节中断控制器的例子来说:

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

......

another-device-node {
	interrupt-parent = <1>; /* 数字1就唯一标识了节点pic@10000000 */
};

如果我们使用phandle来标识设备,当设备多了,数字标识符是比较难记忆的,可读性也差,此时可以使用标签:

PIC: pic@10000000 {
	interrupt-controller;
};

......

another-device-node {
	interrupt-parent = <&PIC>; /* 用标签来引用设备节点 */
};

还有一种常见的标签的用法,当我们需要修改某设备节点的属性,但又不想直接在原地修改(保持原来的内容不被破坏),此时可以在设备树文件的末尾重写该设备的相应属性,从而覆盖之前的内容:

/ {
	......
	device-node {
		p = "xxx";
		......
	};
	......
};

/* 重写device-node的属性p */
/ {	                         /* 根节点也要写上,完整的体现device-node的路径 */
	device-node {            /* 因此这样写比较麻烦(特别是在路径比较深的时候) */
		p = "yyy";
	};
};

使用标签的写法:

/ {
	......
	DN: device-node {
		p = "xxx";
		......
	};
	......
}

/* 重写device-node的属性p */
&DN {  /* 通过标签引用节点 */
	p = "yyy";
};

1.9 编写设备树文件

1.9.1 在DTS文件中包含其他文件

编写设备树文件时,我们通常会把多型设备的共性抽出来,写在DTSI文件(后缀为.dtsi)中,其语法与DTS文件一样。比如,多款使用了am335x的板子,因为使用了同一款SoC,描述设备时肯定会有一些相同的部分,可以把这部分抽出来,写到am335x.dtsi中,然后在具体的某型板子的设备树中包含相应的DTSI文件,包含的方式有:

  • /include/ “xxx.dtsi”
  • #include “xxx.dtsi”

设备树编译器还支持c语言的头文件,因此,如果有需要可以定义一些宏并在设备树文件中使用。

1.9.2 如何在设备树文件中描述设备

设备树写出来是给驱动程序看的,也就是说驱动程序怎么写的,相应的设备树就该怎么写;或者反过来,先约定好设备树怎么写,在相应的设计驱动。驱动和设备树有着对应的关系,这种对应关系也被称为bindings。具体的:

  • 对于上游芯片厂商,应当按照devicetree-specification推荐的设备树写法,遵守各种约定,确定好如何规范的描述设备,并提供相应的驱动程序。devicetree-specification-v0.3的第四章给出了一些推荐的做法。
  • 对于下游产品厂商,当使用芯片厂商的芯片做产品时,芯片厂商通常会提供驱动程序和设备树文件编写的参考文档,这些文档位于linux内核源码树的Documentation/devicetree/bindings目录下。如果芯片厂商没提供相应文档的话,就要读驱动的源码,知道驱动怎么写的,自然也就知道如何写设备树了。

2 DTB文件的格式

DTS文件只是文本文件,需要使用设备树编译器(DTC)将其编译为DTB文件(二进制文件),然后才能传递给内核,内核解析的是DTB文件。需要注意的是,DTB文件使用大端模式存储数值。整体上,该文件由四个部分组成:
认识设备树(二)——设备树文件的格式_第6张图片

2.1 struct ftd_header

认识设备树(二)——设备树文件的格式_第7张图片

2.2 memory reservation block

该部分由1.2节介绍的memory reservations编译得来,由一个或多个表项组成,每一项都描述了一块要保留的内存区域,每项由两个64位数值(起始地址、长度)组成:
在这里插入图片描述

2.3 structure block

认识设备树(二)——设备树文件的格式_第8张图片

2.4 strings block

该部分类似于ELF文件中的字符串表,存储了所有属性名(字符串),考虑到很多节点拥有一些同名的属性,集中存放属性名可以有效的节约DTB文件的空间,存放有属性的structure block部分只需要保存一个32位的偏移值——属性名的起始位置在strings block中的偏移。

参考文献

[1] devicetree-specification-v0.3
[2] 韦东山老师的视频教程

你可能感兴趣的:(#,linux,设备树)