蜗窝科技

Device Tree(二):基本概念

linuxer · · 统一设备模型 · 阅读 236128 · 2014年5月存档

一、前言

一些背景知识(例如:为何要引入Device Tree,这个机制是用来解决什么问题的)请参考引入Device Tree的原因,本文主要是介绍Device Tree的基础概念。

简单的说,如果要使用Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成Device Tree source file。通过DTC(Device Tree Compiler),可以将这些适合人类阅读的Device Tree source file变成适合机器处理的Device Tree binary file(有一个更好听的名字,DTB,device tree blob)。在系统启动的时候,boot program(例如:firmware、bootloader)可以将保存在flash中的DTB copy到内存(当然也可以通过其他方式,例如可以通过bootloader的交互式命令加载DTB,或者firmware可以探测到device的信息,组织成DTB保存在内存中),并把DTB的起始地址传递给client program(例如OS kernel,bootloader或者其他特殊功能的程序)。对于计算机系统(computer system),一般是firmware->bootloader->OS,对于嵌入式系统,一般是bootloader->OS。

本文主要描述下面两个主题:

1、Device Tree source file语法介绍

2、Device Tree binaryfile格式介绍

二、Device Tree的结构

在描述Device Tree的结构之前,我们先问一个基础问题:是否Device Tree要描述系统中的所有硬件信息?答案是否定的。基本上,那些可以动态探测到的设备是不需要描述的,例如USB device。不过对于SOC上的usb host controller,它是无法动态识别的,需要在device tree中描述。同样的道理,在computer system中,PCI device可以被动态探测到,不需要在device tree中描述,但是PCI bridge如果不能被探测,那么就需要描述之。

为了了解Device Tree的结构,我们首先给出一个Device Tree的示例:

/ o device-tree
|- name = “device-tree”
|- model = “MyBoardName”
|- compatible = “MyBoardFamilyName”
|- #address-cells = <2>
|- #size-cells = <2>
|- linux,phandle = <0>
|
o cpus
| | - name = “cpus”
| | - linux,phandle = <1>
| | - #address-cells = <1>
| | - #size-cells = <0>
| |
| o PowerPC,970@0
| |- name = “PowerPC,970”
| |- device_type = “cpu”
| |- reg = <0>
| |- clock-frequency = <0x5f5e1000>
| |- 64-bit
| |- linux,phandle = <2>
|
o memory@0
| |- name = “memory”
| |- device_type = “memory”
| |- reg = <0x00000000 0x00000000 0x00000000 0x20000000>
| |- linux,phandle = <3>
|
o chosen
|- name = “chosen”
|- bootargs = “root=/dev/sda2”
|- linux,phandle = <4>

从上图中可以看出,device tree的基本单元是node。这些node被组织成树状结构,除了root node,每个node都只有一个parent。一个device tree文件中只能有一个root node。每个node中包含了若干的property/value来描述该node的一些特性。每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性(后面会描述这个property),那么该节点名字中必须不能包括@和unit-address。unit-address的具体格式是和设备挂在那个bus上相关。例如对于cpu,其unit-address就是从0开始编址,以此加一。而具体的设备,例如以太网控制器,其unit-address就是寄存器地址。root node的node name是确定的,必须是“/”。

在一个树状结构的device tree中,如何引用一个node呢?要想唯一指定一个node必须使用full path,例如/node-name-1/node-name-2/node-name-N。在上面的例子中,cpu node我们可以通过/cpus/PowerPC,970@0访问。

属性(property)值标识了设备的特性,它的值(value)是多种多样的:

1、可能是空,也就是没有值的定义。例如上图中的64-bit ,这个属性没有赋值。

2、可能是一个u32、u64的数值(值得一提的是cell这个术语,在Device Tree表示32bit的信息单位)。例如#address-cells = <1> 。当然,可能是一个数组。例如<0x00000000 0x00000000 0x00000000 0x20000000>

4、可能是一个字符串。例如device_type = “memory” ,当然也可能是一个string list。例如"PowerPC,970"

三、Device Tree source file语法介绍

了解了基本的device tree的结构后,我们总要把这些结构体现在device tree source code上来。在linux kernel中,扩展名是dts的文件就是描述硬件信息的device tree source file,在dts文件中,一个node被定义成:

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

“[]”表示option,因此可以定义一个只有node name的空节点。label方便在dts文件中引用,具体后面会描述。child node的格式和node是完全一样的,因此,一个dts文件中就是若干嵌套组成的node,property以及child note、child note property描述。

考虑到空泛的谈比较枯燥,我们用实例来讲解Device Tree Source file 的数据格式。假设蜗窝科技制作了一个S3C2416的开发板,我们把该development board命名为snail,那么需要撰写一个s3c2416-snail.dts的文件。如果把所有的开发板的硬件信息(SOC以及外设)都描述在一个文件中是不合理的,因此有可能其他公司也使用S3C2416搭建自己的开发板并命令pig、cow什么的,如果大家都用自己的dts文件描述硬件,那么其中大部分是重复的,因此我们把和S3C2416相关的硬件描述保存成一个单独的dts文件可以供使用S3C2416的target board来引用并将文件的扩展名变成dtsi(i表示include)。同理,三星公司的S3C24xx系列是一个SOC family,这些SOCs(2410、2416、2450等)也有相同的内容,因此同样的道理,我们可以将公共部分抽取出来,变成s3c24xx.dtsi,方便大家include。同样的道理,各家ARM vendor也会共用一些硬件定义信息,这个文件就是skeleton.dtsi。我们自下而上(类似C++中的从基类到顶层的派生类)逐个进行分析。

1、skeleton.dtsi。位于linux-3.14\arch\arm\boot\dts目录下,具体该文件的内容如下:

/ {
#address-cells = <1>;
#size-cells = <1>;
chosen { };
aliases { };
memory { device_type = “memory”; reg = <0 0>; };
};

device tree顾名思义是一个树状的结构,既然是树,必然有根。“/”是根节点的node name。“{”和“}”之间的内容是该节点的具体的定义,其内容包括各种属性的定义以及child node的定义。chosen、aliases和memory都是sub node,sub node的结构和root node是完全一样的,因此,sub node也有自己的属性和它自己的sub node,最终形成了一个树状的device tree。属性的定义采用property = value的形式。例如#address-cells和#size-cells就是property,而<1>就是value。value有三种情况:

1)属性值是text string或者string list,用双引号表示。例如device_type = “memory”

2)属性值是32bit unsigned integers,用尖括号表示。例如#size-cells = <1>

3)属性值是binary data,用方括号表示。例如binary-property = [0x01 0x23 0x45 0x67]

如果一个device node中包含了有寻址需求(要定义reg property)的sub node(后文也许会用child node,和sub node是一样的意思),那么就必须要定义这两个属性。“#”是number的意思,#address-cells这个属性是用来描述sub node中的reg属性的地址域特性的,也就是说需要用多少个u32的cell来描述该地址域。同理可以推断#size-cells的含义,下面对reg的描述中会给出更详细的信息。

chosen node主要用来描述由系统firmware指定的runtime parameter。如果存在chosen这个node,其parent node必须是名字是“/”的根节点。原来通过tag list传递的一些linux kernel的运行时参数可以通过Device Tree传递。例如command line可以通过bootargs这个property这个属性传递;initrd的开始地址也可以通过linux,initrd-start这个property这个属性传递。在本例中,chosen节点是空的,在实际中,建议增加一个bootargs的属性,例如:

“root=/dev/nfs nfsroot=1.1.1.1:/nfsboot ip=1.1.1.2:1.1.1.1:1.1.1.1:255.255.255.0::usbd0:off console=ttyS0,115200 mem=64M@0x30000000”

通过该command line可以控制内核从usbnet启动,当然,具体项目要相应修改command line以便适应不同的需求。我们知道,device tree用于HW platform识别,runtime parameter传递以及硬件设备描述。chosen节点并没有描述任何硬件设备节点的信息,它只是传递了runtime parameter。

aliases 节点定义了一些别名。为何要定义这个node呢?因为Device tree是树状结构,当要引用一个node的时候要指明相对于root node的full path,例如/node-name-1/node-name-2/node-name-N。如果多次引用,每次都要写这么复杂的字符串多少是有些麻烦,因此可以在aliases 节点定义一些设备节点full path的缩写。skeleton.dtsi中没有定义aliases,下面的section中会进一步用具体的例子描述之。

memory device node是所有设备树文件的必备节点,它定义了系统物理内存的layout。device_type属性定义了该node的设备类型,例如cpu、serial等。对于memory node,其device_type必须等于memory。reg属性定义了访问该device node的地址信息,该属性的值被解析成任意长度的(address,size)数组,具体用多长的数据来表示address和size是在其parent node中定义(#address-cells和#size-cells)。对于device node,reg描述了memory-mapped IO register的offset和length。对于memory node,定义了该memory的起始地址和长度。

本例中的物理内存的布局并没有通过memory node传递,其实我们可以使用command line传递,我们command line中的参数“mem=64M@0x30000000”已经给出了具体的信息。我们用另外一个例子来加深对本节描述的各个属性以及memory node的理解。假设我们的系统是64bit的,physical memory分成两段,定义如下:

RAM: starting address 0x0, length 0x80000000 (2GB)
RAM: starting address 0x100000000, length 0x100000000 (4GB)

对于这样的系统,我们可以将root node中的#address-cells和#size-cells这两个属性值设定为2,可以用下面两种方法来描述物理内存:

方法1:

memory@0 {
device_type = “memory”;
reg = <0x000000000 0x00000000 0x00000000 0x80000000
0x000000001 0x00000000 0x00000001 0x00000000>;
};

方法2:

memory@0 {
device_type = “memory”;
reg = <0x000000000 0x00000000 0x00000000 0x80000000>;
};

memory@100000000 {
device_type = “memory”;
reg = <0x000000001 0x00000000 0x00000001 0x00000000>;
};

2、s3c24xx.dtsi。位于linux-3.14\arch\arm\boot\dts目录下,具体该文件的内容如下(有些内容省略了,领会精神即可,不需要描述每一个硬件定义的细节):

#include “skeleton.dtsi”

/ {
compatible = “samsung,s3c24xx”; -------------------(A)
interrupt-parent = <&intc>; ----------------------(B)

aliases {
pinctrl0 = &pinctrl_0; ------------------------(C)
};

intc:interrupt-controller@4a000000 { ------------------(D)
compatible = “samsung,s3c2410-irq”;
reg = <0x4a000000 0x100>;
interrupt-controller;
#interrupt-cells = <4>;
};

serial@50000000 { ----------------------(E)
compatible = “samsung,s3c2410-uart”;
reg = <0x50000000 0x4000>;
interrupts = <1 0 4 28>, <1 1 4 28>;
status = “disabled”;
};

pinctrl_0: pinctrl@56000000 {------------------(F)
reg = <0x56000000 0x1000>;

wakeup-interrupt-controller {
compatible = “samsung,s3c2410-wakeup-eint”;
interrupts = <0 0 0 3>,
<0 0 1 3>,
<0 0 2 3>,
<0 0 3 3>,
<0 0 4 4>,
<0 0 5 4>;
};
};

……
};

这个文件描述了三星公司的S3C24xx系列SOC family共同的硬件block信息。首先提出的问题就是:为何定义了两个根节点?按理说Device Tree只能有一个根节点,所有其他的节点都是派生于根节点的。我的猜测是这样的:Device Tree Compiler会对DTS的node进行合并,最终生成的DTB只有一个root node。OK,我们下面开始逐一分析:

(A)在描述compatible属性之前要先描述model属性。model属性指明了该设备属于哪个设备生产商的哪一个model。一般而言,我们会给model赋值“manufacturer,model”。例如model = “samsung,s3c24xx”。samsung是生产商,s3c24xx是model类型,指明了具体的是哪一个系列的SOC。OK,现在我们回到compatible属性,该属性的值是string list,定义了一系列的modle(每个string是一个model)。这些字符串列表被操作系统用来选择用哪一个driver来驱动该设备。假设定义该属性:compatible = “aaaaaa”, “bbbbb"。那么操作操作系统可能首先使用aaaaaa来匹配适合的driver,如果没有匹配到,那么使用字符串bbbbb来继续寻找适合的driver,对于本例,compatible = “samsung,s3c24xx”,这里只定义了一个modle而不是一个list。对于root node,compatible属性是用来匹配machine type的(在device tree代码分析文章中会给出更细致的描述)。对于普通的HW block的节点,例如interrupt-controller,compatible属性是用来匹配适合的driver的。

(B)具体各个HW block的interrupt source是如何物理的连接到interruptcontroller的呢?在dts文件中是用interrupt-parent这个属性来标识的。且慢,这里定义interrupt-parent属性的是root node,难道root node会产生中断到interrupt controller吗?当然不会,只不过如果一个能够产生中断的device node没有定义interrupt-parent的话,其interrupt-parent属性就是跟随parent node。因此,与其在所有的下游设备中定义interrupt-parent,不如统一在root node中定义了。

intc是一个lable,标识了一个device node(在本例中是标识了interrupt-controller@4a000000 这个device node)。实际上,interrupt-parent属性值应该是是一个u32的整数值(这个整数值在Device Tree的范围内唯一识别了一个device node,也就是phandle),不过,在dts文件中中,可以使用类似c语言的Labels and References机制。定义一个lable,唯一标识一个node或者property,后续可以使用&来引用这个lable。DTC会将lable转换成u32的整数值放入到DTB中,用户层面就不再关心具体转换的整数值了。

关于interrupt,我们值得进一步描述。在Device Tree中,有一个概念叫做interrupt tree,也就是说interrupt也是一个树状结构。我们以下图为例(该图来自Power_ePAPR_APPROVED_v1.1):

it

系统中有一个interrrupt tree的根节点,device1、device2以及PCI host bridge的interrupt line都是连接到root interrupt controller的。PCI host bridge设备中有一些下游的设备,也会产生中断,但是他们的中断都是连接到PCI host bridge上的interrupt controller(术语叫做interrupt nexus),然后报告到root interrupt controller的。每个能产生中断的设备都可以产生一个或者多个interrupt,每个interrupt source(另外一个术语叫做interrupt specifier,描述了interrupt source的信息)都是限定在其所属的interrupt domain中。

在了解了上述的概念后,我们可以回头再看看interrupt-parent这个属性。其实这个属性是建立interrupt tree的关键属性。它指明了设备树中的各个device node如何路由interrupt event。另外,需要提醒的是interrupt controller也是可以级联的,上图中没有表示出来。那么在这种情况下如何定义interrupt tree的root呢?那个没有定义interrupt-parent的interrupt controller就是root。

(C)pinctrl0是一个缩写,他是/pinctrl@56000000的别名。这里同样也是使用了Labels and References机制。

(D)intc(node name是interrupt-controller@4a000000 ,我这里直接使用lable)是描述interrupt controller的device node。根据S3C24xx的datasheet,我们知道interrupt controller的寄存器地址从0x4a000000开始,长度为0x100(实际2451的interrupt的寄存器地址空间没有那么长,0x4a000074是最后一个寄存器),也就是reg属性定义的内容。interrupt-controller属性为空,只是用来标识该node是一个interrupt controller而不是interrupt nexus(interrupt nexus需要在不同的interrupt domains之间进行翻译,需要定义interrupt-map的属性,本文不涉及这部分的内容)。#interrupt-cells 和#address-cells概念是类似的,也就是说,用多少个u32来标识一个interrupt source。我们可以看到,在具体HW block的interrupt定义中都是用了4个u32来表示,例如串口的中断是这样定义的:

interrupts = <1 0 4 28>, <1 1 4 28>;

(E) 从reg属性可以serial controller寄存器地址从0x50000000 开始,长度为0x4000。对于一个能产生中断的设备,必须定义interrupts这个属性。也可以定义interrupt-parent这个属性,如果不定义,则继承其parent node的interrupt-parent属性。 对于interrupt属性值,各个interrupt controller定义是不一样的,有的用3个u32表示,有的用4个。具体上面的各个数字的解释权归相关的interrupt controller所有。对于中断属性的具体值的描述我们会在device tree的第三份文档-代码分析中描述。

(F)这个node是描述GPIO控制的。这个节点定义了一个wakeup-interrupt-controller 的子节点,用来描述有唤醒功能的中断源。

3、s3c2416.dtsi。位于linux-3.14\arch\arm\boot\dts目录下,具体该文件的内容如下(有些内容省略了,领会精神即可,不需要描述每一个硬件定义的细节):

#include “s3c24xx.dtsi”
#include “s3c2416-pinctrl.dtsi”

/ {
model = “Samsung S3C2416 SoC”;
compatible = “samsung,s3c2416”; ---------------A

cpus { ----------------------------B
#address-cells = <1>;
#size-cells = <0>;

cpu {
compatible = “arm,arm926ejs”;
};
};

interrupt-controller@4a000000 { -----------------C
compatible = “samsung,s3c2416-irq”;
};

……

};

(A)在s3c24xx.dtsi文件中已经定义了compatible这个属性,在s3c2416.dtsi中重复定义了这个属性,一个node不可能有相同名字的属性,具体如何处理就交给DTC了。经过反编译,可以看出,DTC是丢弃掉了前一个定义。因此,到目前为止,compatible = samsung,s3c2416。在s3c24xx.dtsi文件中定义了compatible的属性值被覆盖了。

(B)对于根节点,必须有一个cpus的child node来描述系统中的CPU信息。对于CPU的编址我们用一个u32整数就可以描述了,因此,对于cpus node,#address-cells 是1,而#size-cells是0。其实CPU的node可以定义很多属性,例如TLB,cache、频率信息什么的,不过对于ARM,这里只是定义了compatible属性就OK了,arm926ejs包括了所有的processor相关的信息。

(C)s3c24xx.dtsi文件和s3c2416.dtsi中都有interrupt-controller@4a000000这个node,DTC会对这两个node进行合并,最终编译的结果如下:

interrupt-controller@4a000000 {
compatible = “samsung,s3c2416-irq”;
reg = <0x4a000000 0x100>;
interrupt-controller;
#interrupt-cells = <0x4>;
linux,phandle = <0x1>;
phandle = <0x1>;
};

4、s3c2416-pinctrl.dtsi

这个文件定义了pinctrl@56000000 这个节点的若干child node,主要用来描述GPIO的bank信息。

5、s3c2416-snail.dts

这个文件应该定义一些SOC之外的peripherals的定义。

四、Device Tree binary格式

1、DTB整体结构

经过Device Tree Compiler编译,Device Tree source file变成了Device Tree Blob(又称作flattened device tree)的格式。Device Tree Blob的数据组织如下图所示:

dt

2、DTB header。

对于DTB header,其各个成员解释如下:

header field namedescription
magic用来识别DTB的。通过这个magic,kernel可以确定bootloader传递的参数block是一个DTB还是tag list。
totalsizeDTB的total size
off_dt_structdevice tree structure block的offset
off_dt_stringsdevice tree strings block的offset
off_mem_rsvmapoffset to memory reserve map。有些系统,我们也许会保留一些memory有特殊用途(例如DTB或者initrd image),或者在有些DSP+ARM的SOC platform上,有写memory被保留用于ARM和DSP进行信息交互。这些保留内存不会进入内存管理系统。
version该DTB的版本。
last_comp_version兼容版本信息
boot_cpuid_phys我们在哪一个CPU(用ID标识)上booting
dt_strings_sizedevice tree strings block的size。和off_dt_strings一起确定了strings block在内存中的位置
dt_struct_sizedevice tree structure block的size。和和off_dt_struct一起确定了device tree structure block在内存中的位置

3、 memory reserve map的格式描述

这个区域包括了若干的reserve memory描述符。每个reserve memory描述符是由address和size组成。其中address和size都是用U64来描述。

4、device tree structure block的格式描述

device tree structure block区域是由若干的分片组成,每个分片开始位置都是保存了token,以此来描述该分片的属性和内容。共计有5种token:

(1)FDT_BEGIN_NODE (0x00000001)。该token描述了一个node的开始位置,紧挨着该token的就是node name(包括unit address)

(2)FDT_END_NODE (0x00000002)。该token描述了一个node的结束位置。

(3)FDT_PROP (0x00000003)。该token描述了一个property的开始位置,该token之后是两个u32的数据,分别是length和name offset。length表示该property value data的size。name offset表示该属性字符串在device tree strings block的偏移值。length和name offset之后就是长度为length具体的属性值数据。

(4)FDT_NOP (0x00000004)。

(5)FDT_END (0x00000009)。该token标识了一个DTB的结束位置。

一个可能的DTB的结构如下:

(1)若干个FDT_NOP(可选)

(2)FDT_BEGIN_NODE

node name

paddings

(3)若干属性定义。

(4)若干子节点定义。(被FDT_BEGIN_NODE和FDT_END_NODE包围)

(5)若干个FDT_NOP(可选)

(6)FDT_END_NODE

(7)FDT_END

5、device tree strings bloc的格式描述

device tree strings bloc定义了各个node中使用的属性的字符串表。由于很多属性会出现在多个node中,因此,所有的属性字符串组成了一个string block。这样可以压缩DTB的size。

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。

标签:Devicetree

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(162 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • 烂番茄 主页

    各种概念,但是我也不晓得 - -小白飘过~~
  • Daniel Shieh

    已经写了两篇了,继续等待更进一步的,可以写一下内核和device tree是如何关联起来的吗?内部实现原理,这部分好像比较复杂,期待大牛的讲解。
    • linuxer

      @Daniel Shieh:大牛是谈不上,只是对linux kernel比较有热情。呵呵~~~ 对于新的3版本的内核,我也是正在学习,对以前的2.6版本的内核还算熟悉。内核和device tree如何关联会在下一篇文档中完成,我估计也要下个星期了。
  • zhoutao.temp

    因为项目的需求,开始接触"设备树",请问一下,有没有比较基础的文档或网页?如何操作设备文件、如何针对具体的硬件添加设备、如何编译等等,请问有没有操作性强的文档或网页。 如能回复,感激不尽。
    • linuxer

      @zhoutao.temp:起始linux-3.14\Documentation\devicetree目录下的文档已经提供了足够的信息,你可以参考
  • zhoutao.temp

    你好,我正在分析 CubieTruck 的设备树文件,有两个问题想请问你一下。 1. 下面脚本里,两个管脚是从哪里得来的? allwinner,pins = "PB18", "PB19"; allwinner,function = "i2c1"; 2. 这个 timer 的起始地址0x01c60000 从哪里来的? 还有interrupts 的四个中断是从哪里来的? 是不是有什么硬件手册? timer@01c60000 { reg = <0x01c60000 0x1000>; interrupts = "0 51 1", "0 52 1", "0 53 1", "0 54 1"; };
    • forion

      @zhoutao.temp:hi zhoutao.temp device tree的用途就是硬件的描述,所以里面的信息基本都是跟你的board相关的硬件信息。 1:这两个管脚,因为我没有接触过你这个板子,但是基本的思路就是你去代码里面看着两个管脚是怎么用的,然后对照datasheet来查找。 2:这timer的起始地址,一定是在芯片的datasheet里面的。有4个中断号,这个你要看code中,这个中断号注册的handler,才知道这几个中断到底是用来做什么的。 一定要把dts文件跟code结合来看,要不然光看硬件描述根本无法知道在软件上这个dts为什么要这么写。
      • linuxer

        @forion:多谢帮忙!
    • linuxer

      @zhoutao.temp:1、我想你用的是全志的CPU,全部的定义是这样的: i2c1_pins_a: i2c1@0 { allwinner,pins = "PB18", "PB19"; allwinner,function = "i2c1"; allwinner,drive = <0>; allwinner,pull = <0>; }; 这个device node定义了i2c1的pin configuration。你问两个管脚是从哪里得来的?我不是很清楚你的问题,我猜你想想知道在linux kernel中哪里定义的,具体的代码是在drivers/pinctrl目录下的全志相关的pinctrl相关的driver,我猜是pinctrl-sunxi.c,PB18和PB19定义在pinctrl-sunxi-pins.h以及pinctrl-sunxi.h中。如果你想问硬件的内容,参考其芯片的datasheet就OK了,在GPIO章节会描述相关内容 2、timer的地址以及中断信息都是在硬件datasheet或者user manual中有定义
      • zhoutao.temp

        @linuxer:感谢版本的回复,让我对设备树的理解又加深了一些。
  • zhoutao.temp

    你好, 刚发一电子邮件,内容与这个帖子内容相同。这个为更新版。非常感谢你热心的回复。有点不好意思了,我还有问题需要问一下。 1. 如何读懂 Cubietruck_HW 这个文件,并构造出一个以CPU为根的连接树。 2. 如何从 Cubietruck_HW ,user manual 和 DataSheet从这三个文件如何查出设备树的一些属性值? 以下面脚本为例,如何从这三个PDF文件看出要填写下面的value。 i2c1_pins_a: i2c1@0 { allwinner,pins = "PB18", "PB19"; allwinner,function = "i2c1"; allwinner,drive = <0>; allwinner,pull = <0>; }; ------------------------------------------------- ( 这三个文件的链接在下面列出了 )。 Cubietruck_HW.pdf : http://dl.cubieboard.org/hardware/A20_Cubietruck_HW_V10_130606.pdf A20 user manual.pdf : http://dl.cubieboard.org/software/ubuntuone/public/cubieboard/docs/A20_user%20manual%20V1.0%2020130322.pdf A20 DataSheet.pdf : http://dl.linux-sunxi.org/A20/A20%20Brief%202013-02-27.pdf _______________________________
    • linuxer

      @zhoutao.temp:1、http://dl.cubieboard.org/hardware/A20_Cubietruck_HW_V10_130606.pdf 是电路原理图,如果不是面对面,我很难告诉你如何你如何读懂它 2、我建议你先阅读A20 user manual.pdf的1.19章节,port controller,如果你知道这个章节描述的内容,自然也就能回答你提出的问题了
  • Frogcoder

    最近开始关注device tree,博主你的文章让人受益匪浅啊,灰常感谢分享!
    • linuxer

      @Frogcoder:多谢鼓励,能够帮助到大家是我们继续下去的动力。
  • forion

    需要提醒的是interrupt controller也是可以级联的,上图中没有表示出来。 ------------------------------------------------------------------------------- PCI host bridge设备中有一些下游的设备,也会产生中断,但是他们的中断都是连接到PCI host bridge上的interrupt controller(术语叫做interrupt nexus), ------------------------------------------------------------------------------- 这个PCI host bridge 就是一个interrupt controller,那这不就是级联的吗?
    • linuxer

      @forion:interrupt nexus和interrupt controller是有区别的,至少interrupt nexus是和总线行为相关联的,更详细的内容让我先思考一下
  • 741342765

    博主,都是用2440的开发板写的代码吗?
    • linuxer

      @741342765:我参与的平台比较多,但2440没有用过,我用过2410和2451这两个SOC
  • 常山黄豆

    @linuxer,#interrupt-cells 和#address-cells概念是类似的,也就是说,用多少个u32来标识一个interrupt source。你文章里面#interrupt-cells = <0x4>; 而我本地一个exynos5430的工程里面有#interrupt-cells = <0x3>; gpio的interrupt controller也有#interrupt-cells = <0x2>; 那我们如何理解cells不同定义时,interrupts< 7 0 0>里面各个cell的含义? 同样#gpio-cells也是一样的,他们各个cell的含义在哪里可以找到?
    • linuxer

      @常山黄豆:linux-3.14/Documentation/devicetree/bindings目录下应该可以找到你想要的东西。
      • 常山黄豆

        @linuxer:每看一次都有收获!
    • wowo

      @常山黄豆:Linuxer所说的document,可以参考: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt Documentation/devicetree/bindings/gpio/gpio.txt device tree只提供基本机制,如它规定了#gpio-cells、#interrupt-cells等关键字,这些机制保证了provider(如interrupt controller driver)和consumer(如uart driver)使用相对一致的语言对话。但对话的内容,还是要由provider根据自己的实际情况制定。这就是为什么会有Documentation/devicetree/bindings/这个目录,该目录存放了不同公司、不同软件模块所提供的DTS provider的规则。 不过,device tree可能会多做一些规定,如在Documentation/devicetree/bindings/interrupt-controller/interrupts.txt中规定#interrupt-cells中的1表示什么,2表示什么。但是,provider可以无视它(如果理由充分的话),只要在bindings/某个位置说明清楚即可。
  • uudiin 主页

    非常好的文章,对设备树有了更深入的了解 博主可以的话,做个友链,www.uudiin.com
    • 蜗蜗

      @uudiin:已经加入贵站,希望能多交流。
    • wangyunqian

      @uudiin:贵站关了?
  • cloudxxcloud

    interrupts = <1 0 4 28>, <1 1 4 28>; 这些值的含义 要和具体的代码 相关联?具体的代码一般都是在什么位置?64bit的arm 已经去掉mach目录和plat目录了
    • admin

      @cloudxxcloud:可以参考“Documentation/devicetree/bindings/interrupt-controller/interrupts.txt”,kernel是有些规定,但具体的driver,可以按照自己的想法实现。
  • cloudxxcloud

    pinctrl_0: pinctrl@56000000 比如这些命名 都是任意的吗?如:pinctrl_0: pinctrlxxxx@0x0aaaa;56000000这个值一般都是寄存器的基地值
    • admin

      @cloudxxcloud:是可以任意命名。使用地址命名,是其中的一种常规做法。
  • JJ

    方法1: memory@0 { device_type = "memory"; reg = <0x000000000 0x00000000 0x00000000 0x80000000 0x000000001 0x00000000 0x00000001 0x00000000>; }; 方法2: memory@0 { device_type = "memory"; reg = <0x000000000 0x00000000 0x00000000 0x80000000>; }; memory@100000000 { device_type = "memory"; reg = <0x000000001 0x00000000 0x00000001 0x00000000>; }; reg 是不是寫錯了?怎麼會有 9 個bit
    • wowo

      @JJ:应该是写错了,您看的真是仔细,赞!!
  • linuxer_j

    非常感谢博主的付出!
  • young

    这篇文章很受用,赞一个!
  • 阳光 主页

    文章还没有看完,实在忍不住,先赞一个.博主的文章真的,除了赞,不知道还能说什么.就是有了博主这样的人,我们很多人才进步的更快,说一声谢谢.
  • financer

    请问博主, linux,phandle 怎么解释啊
    • wowo

      @financer:linux,phandle和phandle是一个标示这个DTS node的句柄(想象一下文件句柄)。 在DTS的语法中,我们可以为某个node起一个别名,如下面的“gpu_vdd”: gpu_vdd: gpio-regulator { ... }; 在其它node中,可以引用这个别名,如: gpu@e0600000 { ... gpu-supply = <&gpu_vdd>; ... }; 但对DTS编译器来(dtc)说,别名很麻烦,没有数字来得实在,因此会自动把别名转为为phandle,如下(将DTB文件反编译后就可以看到): gpio-regulator { ... linux,phandle = <0x1e>; phandle = <0x1e>; }; gpu@e0600000 { ... gpu-supply = <0x1e>; ... };
      • financer

        @wowo:谢谢博主拔冗回答,再请问 linux,phandle phandle 必须同时使用才能定义一个句柄吗。linux是指类型的意思吗?谢谢
        • wowo

          @financer:不用,这两个只是不同时期的名字,现在kernel都兼容,具体可参考: ./drivers/of/fdt.c /* We accept flattened tree phandles either in * ePAPR-style "phandle" properties, or the * legacy "linux,phandle" properties. If both * appear and have different values, things * will get weird. Don't do that. */ 所以 "linux,phandle"是旧的属性,不建议使用了。
  • 蒸汽机车

    请教语法问题。 XXXX.dtsi文件这么定义 &mdss_mdp { dsi_hx8379a_wvga_vid: qcom,mdss_dsi_hx8379a_wvga_video { qcom,mdss-dsi-panel-name = "HX8379A wvga video mode dsi panel"; qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; ........... } XXX.dtsi 又有mdss_mdp: qcom,mdss_mdp@1a00000 { compatible = "qcom,mdss_mdp"; reg = <0x1a00000 0x90000>, } 第一个是引用,第二个是定义吗?引用可以重新赋值吗。 谢谢了~
    • wowo

      @蒸汽机车:它等同于这样: XXXX1.dtsi-------------------> qcom,mdss_mdp@1a00000 { dsi_hx8379a_wvga_vid: qcom,mdss_dsi_hx8379a_wvga_video { qcom,mdss-dsi-panel-name = "HX8379A wvga video mode dsi panel"; qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; ........... } XXX2.dtsi ---------------------> qcom,mdss_mdp@1a00000 { compatible = "qcom,mdss_mdp"; reg = <0x1a00000 0x90000>, } 同一个node可以定义在两个DTS文件中,dtc会合并成一个。 至于为什么会用“&mdss_mdp”的形式,可能他们觉得node的名字有点长吧。 PS:这种用法还是挺好玩的,呵呵~~~~~~~
      • 蒸汽机车

        @wowo:这样子,我看代码里很多这样写法。 再请教一下。 qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; mdss_dsi0也是一个节点。会在本节点展开吗。还是只是一个地址而已?
        • wowo

          @蒸汽机车:不会展开,它不是C语言中宏的概念。因为DTS的功能是描述,因此在kernel启动时会解析DTB,这个符号类似指针,将解析过程转到另一个node中。
          • 蒸汽机车

            @wowo:嗯 qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; 他这样做的意义是什么,语法上,是加入一个子节点的意思吗
          • 蒸汽机车

            @wowo:嗯,和指针是一个意思。 qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; 他这样做的意义是什么,语法上,是加入一个子节点的意思吗
            • wowo

              @蒸汽机车:不是加入子节点的意思,而是它需要使用其它节点。
              • 蒸汽机车

                @wowo:嗯,谢谢博主
      • enjoyer

        @wowo:借用@蒸汽机车 同学的 XXXX.dtsi文件这么定义 &mdss_mdp { dsi_hx8379a_wvga_vid: qcom,mdss_dsi_hx8379a_wvga_video { qcom,mdss-dsi-panel-name = "HX8379A wvga video mode dsi panel"; qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; ........... } XXX.dtsi 又有mdss_mdp: qcom,mdss_mdp@1a00000 { compatible = "qcom,mdss_mdp"; reg = <0x1a00000 0x90000>, } 如果这两个节点里面有相同的属性,如 &mdss_mdp { aa = <1>, } XXX.dtsi 又有mdss_mdp: qcom,mdss_mdp@1a00000 { compatible = "qcom,mdss_mdp"; reg = <0x1a00000 0x90000>, aa = <2>, } 那么两者合并的时候是不是有个先后顺序的;后者替换先者的?
        • linuxer

          @enjoyer:其实你自己试一下就知道了,将dts编译成二进制的dtb,然后反编译成dts,这样就知道DTC是如何处理的了,呵呵~~~这样至少省去了看DTC source code的麻烦了
          • enjoyer

            @linuxer:嗯,后来已尝试过,发现还是多尝试效果较好;
  • xie1230

    有一个问题想请教一下: 需要写一个i2c设备驱动,需要在一个i2c总线上加一个设备,dts文件应该要怎么写才好? 有点不能理解,改如何配置这个i2c总线的bus号等一些配置项? 这个是有标准的写法,还是需要自己定义驱动去识别的properties来配置i2c bus的?
    • xie1230

      @xie1230:又仔细看了一下actions 平台中的dts,里面各个i2c总线下,又会定义不同的设备,看来应该在这里面定义自己的设备了,不过还是有一个问题要确认一下,如果我在自己的设备驱动中需要使用dts中的某个配置,是可以自己定义properties的吧,通过of API读取?
      • wowo

        @xie1230:我们需要先理解一点:DTS文件描述的是platform bus上的platform设备。 这也是为什么linux kernel of模块只会帮忙解析DTS中第2级别(例如i2c controller)的节点,因为只有这一级别的节点才是根正苗红的platform device。 然后再回到i2c bus下面的设备,它已经不是platform设备了(虽然很多driver也会为其创建platform设备),因此,kernel of模块就不会帮忙解析。那么谁解析呢?i2c bus driver。这就是为什么i2c设备的DTS node放到了i2c controller下面,也因此就没有i2c bus number的困扰了。 最后,i2c device设备的DTS node,本质上也是DTS node,只要能拿到它的句柄,一样可以通过of接口解析其中的properties。
  • Bruce

    請問如果在makefile中看到在編譯時會取用多個DTB files 如果在不同的dts file都有各自的敘述 比方在 a.dtsi是這樣苗訴的 &mdss_dsi0 { qcom,dsi-pref-prim-pan = <&dsi_jdi_1080_vid>; pinctrl-names = "mdss_default", "mdss_sleep"; pinctrl-0 = <&mdss_dsi_active &mdss_te_active>; pinctrl-1 = <&mdss_dsi_suspend &mdss_te_suspend>; qcom,platform-enable-gpio = <&msm_gpio 97 0>; qcom,platform-reset-gpio = <&msm_gpio 25 0>; qcom,platform-bklight-en-gpio = <&msm_gpio 98 0>; }; b.dtsi卻是這樣 &mdss_dsi0 { qcom,dsi-pref-prim-pan = <&dsi_hx8379a_fwvga_video>; pinctrl-names = "mdss_default", "mdss_sleep"; pinctrl-0 = <&mdss_dsi_active>; pinctrl-1 = <&mdss_dsi_suspend>; qcom,platform-reset-gpio = <&msm_gpio 25 0>; }; 如此一來會怎麼樣呢?
    • wowo

      @Bruce:后面包含进来的会覆盖前面的。下面有一些关于这的讨论,您可以参考一下。
  • Bruce

    @wowo:先謝謝博主,另外想再請問我目前遇到的情況 在makefile中平台會include很多dtb files dtb-$(CONFIG_ARCH_MSM8916) += msm8916-cdp.dtb \ msm8916-cdp-smb1360.dtb \ msm8916-mtp.dtb \ msm8916-mtp-smb1360.dtb \ msm8916-qrd-skuh.dtb \ msm8916-qrd-skuhf.dtb \ msm8916-qrd-skui.dtb \ msm8916-qrd-skuic.dtb \ msm8916-qrd-skuid.dtb \ msm8916-qrd-skut1.dtb \ apq8016-mtp.dtb \ apq8016-mtp-smb1360.dtb \ apq8016-sbc.dtb \ msm8939-qrd-skuk.dtb \ msm8939-qrd-skul.dtb \ msm8939-cdp.dtb \ msm8939-cdp-jdi.dtb \ msm8939-cdp-jdi-a216-fhd.dtb \ msm8939-mtp.dtb \ msm8939-mtp-jdi.dtb \ msm8939-mtp-smb1360.dtb \ msm8939-mtp-wcd9306.dtb \ msm8936-qrd-skuk.dtb \ msm8936-cdp.dtb \ msm8936-cdp-smb1360.dtb \ msm8936-mtp.dtb \ msm8936-mtp-smb1360.dtb \ msm8936-mtp-wcd9306.dtb \ msmtellurium-sim.dtb \ msmtellurium-rumi.dtb \ msm8929-cdp.dtb \ msm8929-mtp.dtb \ msm8929-mtp-smb1360.dtb \ msm8929-mtp-jdi-smb1360.dtb \ msm8929-mtp-wcd9306-smb1360.dtb \ msm8929-qrd-skul.dtb \ msm8929-qrd-skulb.dtb \ msm8929-cdp-jdi.dtb 在這種情況下要如何確保自己修改某一個dts而不會被蓋過去呢? 將自己的dtb在makefile中順序往下移動嗎? 或是有什麼建議的管理方式呢? 謝謝
    • wowo

      @Bruce:DTS的include,隐含的是板级的派生和继承,建议您还是把这个先理清楚,至于怎么改DTS,就简单了。
  • newhand

    假设我们的系统是64bit的,physical memory分成两段,定义如下: RAM: starting address 0x0, length 0x80000000 (2GB) RAM: starting address 0x100000000, length 0x100000000 (4GB) memory@0 { device_type = "memory"; reg = <0x000000000 0x00000000 0x00000000 0x80000000 0x000000001 0x00000000 0x00000001 0x00000000>; }; 為什麼不是 memory@0 { device_type = "memory"; reg = <0x000000000 0x00000000 0x00000000 0x80000000 0x000000001 0x00000000 0x00000001 0x100000000>; }; 第二個不是4G嗎?
    • linuxer

      @newhand:前面已经假设了系统是64bit的,因此reg中的那些bit是按照64bit来解析的: <0x000000000 0x00000000 0x00000000 0x80000000 ------------------------------------ start address (0) length (2G) 0x000000001 0x00000000 0x00000001 0x00000000>; ------------------------------------ start address (4G地址空间开始的位置) length (4G)
      • newhand

        @linuxer:謝謝版主~~~!!
  • newhand

    板大您好: 小弟還有些問題,以高通8974平台为例中,會有些 qcom,i2c-bus-freq qcom,sda-gpio 這種屬性,試問這類型的屬性都專屬高通?和我要怎麼知道有這些屬性??
    • linuxer

      @newhand:在linux-4.0\Documentation\devicetree\bindings\i2c或者E:\home\linux-4.0\linux-4.0\Documentation\devicetree\bindings\arm目录下可以找到这些属性的定义
      • newhand

        @linuxer:謝謝!!
  • linuxer2010

    写的很好,对我帮助特别大,我要把网站上所有的文章都读过来。
  • Hanks

    比有些平台介绍自己的dts格式的描述文档还清晰!必须赞!
  • justforfun

    timer11: timer@48088000 { compatible = "ti,omap2-timer"; reg = <0x48088000 0x80>; interrupts = <0 47 0x4>; ti,hwmods = "timer11"; 这和下面的是什么意思,这又是什么写法? ti,timer-ipu; ti,timer-pwm; };
    • linuxer

      @justforfun:那些是OMAP Timer bindings,具体请参考Documentation\devicetree\bindings\arm\omap\timer.txt文件
  • 路人甲

    楼主真的对device tree了解很深入啊,佩服
  • 小白

    大大,请问dtb文件如何进行反编译的
    • linuxer

      @小白:dtc的source code位于linux/scripts/dtc目录下,你一看便知
      • 小白

        @linuxer:感谢大大的回答,但是还出现下面的问题还请指教下 lcl dtc # dtc -I dtb -O dts /workspace/wtr4905/ap-msm8916-wtr4905-5-1/out/debug/target/product/L3_project_demo/obj/KERNEL_OBJ/arch/arm64/boot/msm8916-qrd-skuh-L3_project_demo.dtb 执行这命令后提示程序“dtc”尚未安装。 您可以使用以下命令安装: 是我的命令有问题还是要安装dtc的
        • firo 主页

          @小白:可以确定层主真小白:-) 你可以用绝对路径指定dtc, or 把 dtc所在的路径加到PATH里面.
          • firo 主页

            @firo:也可以用./dtc 这种相对路径写法.
          • 小白

            @firo:我已经进入到dtc目录下执行的,应该不用加上路径的了吧,我看了网上说高通的dtc不可直接使用的,而在android源码上是可以执行反编译的,不知道是不是和高通平太有关呢,请大大指教一下
            • firo 主页

              @小白:在dtc目录下执行./dtc 试试.
              • kker

                @firo:这个我试过 root@linux:android# out/target/product/msm8909/obj/KERNEL_OBJ/scripts/dtc/dtc -I dtb -O dts out/target/product/msm8909/obj/KERNEL_OBJ/arch/arm/boot/dts/msm8909-1gb-qrd-skua.dtb > /arm/msm8909-1gb-qrd-skua-stu.dts
  • 太阳 主页

    看了你的文章学到了不少,但我发现dts中会用到include 想请教一下: #include /include/ /*/include/ 这几个有啥差别来着,还望赐教
    • wowo

      @太阳:哈哈,这个问题相当“刁钻”啊! 我找了很久,没有这方面的资料,自己猜测如下: 1. /include/的方式,应该是dts脚本的标准语法,可以从dtc的词法分析器的代码中看到(kernel/scripts/dtc/dtc-lexer.l): <*>"/include/"{WS}*{STRING} { char *name = strchr(yytext, '\"') + 1; yytext[yyleng-1] = '\0'; push_input_file(name); } start 2. #include的方式,应该是兼容C语言的语法,因此dtc在某个阶段应该调用C的解释器了(我没有细看代码,不能确认)。 3. /*/include/ 这种方式是对的??
      • 太阳 主页

        @wowo:刚刚在代码注释了一下编译验证了一下,结果如下。 第一个是在本地同一个目录搜索文件 第二个是可以从其他的路径准确的说是Makefile 额外添加了路径的地方搜索文件 第三个应该是少了个*/ /*/include xxx*/是注释。 你知道yyparse源码放哪里的?
        • wowo

          @太阳:第二个之所以能从指定路径中搜索,是不是正因为它是使用C的编译环境? yyparse应该和lex yacc有关吧,我不太了解。
          • 太阳 主页

            @wowo:我感觉用/include/类似于c环境 #include 只能从当前目录搜索
            • wowo

              @太阳:抱歉,把你的表述看反了。我平时一般都用#include的方式,知道怎么用就行了,没必要深究。
    • linuxer

      @太阳:#include 是经典的c代码的风格 /include/ 是device tree source定义的 这两种格式都是被dtc支持的,没有什么区别。 /*/include/ 这个好象没有见过,你能给出一个例子吗?
  • wal

    参考标签在反编译的DTS文件中显示为数字
    • wowo

      @wal:是的。
      • gzz

        @wowo:wowo,在root node中如果有一个A:A{}子节点,然后在这个dts中的根节点/{}外面又增加了 &A{ xxxxxx 其他node信息 } 这种方式是不是就是只是给A节点增加信息,本质就是只有一个A node,只是分开来描述而已,是这么理解吗? ps:文章里貌似没有出现过这样处理方式,在节点内部&label倒是有说明,两者应该是不一样的吧?
        • wowo

          @gzz:是的,这样理解没有问题。 这两者本质上是一样的,只是用法不同而已,区别在于是否在平级引用而已。
  • gzz

    wowo,如果一个dts里面引用了某个dtsi的label,但这个include dtsi是放在这个dts的末尾,即没有在引用前就include。不知道dtc是会按顺序预处理,还是说是include相关的会提请加载预处理。
    • gzz

      @gzz:尝试了下,貌似label的引用和include的位置无关,应该是提前于加载类似于makefile的include处理,而不是C编译的处理。
  • feibob

    想请教一下: 如果一个GPIO口是复用的, 比如它同时有GPIO功能和PWM功能, 那么在dtsi文件中, 系统同时定义了两种用法定义,gpio7和pwm0, 那我们如何判断它在最终的使用中是作何解释的? 又或者应该是如何打开其中一项功能,并关闭另一项功能的呢?
    • linglongqion

      @feibob:看驱动的注册顺序
      • feibob

        @linglongqion:那这样说来, 就是看Kernel解读这些文件的顺序了? 还是在Kernel中有相应的流程来控制?
    • 云

      @feibob:硬件配置,不是DT事。 引用一段,供大家参考: • The Device Tree is really a hardware description language. • It should describe the hardware layout, and how it works. • But it should not describe which particular hardware configuration you’re interested in. • As an example: o You may describe in the DT whether a particular piece of hardware supports DMA or not. o But you may not describe in the DT if you want to use DMA or not.
      • emeralddream

        @云:根据你提供的资料,比如我在节点1里有: irq_gpio_off = <&gpio2 GPIO_B1 IRQ_TYPE_EDGE_FALLING> 是不是必须在驱动里 of_get_named_gpio_flags(node, "irq_gpio_off", 0, &flag);这一句之后 gpio2-b1才被配置成gpio模式(假设该gpio初始是i2c_scl模式)。
      • emeralddream

        @云:根据你提供的资料,比如我在节点1里有: irq_gpio_off = <&gpio2 GPIO_B1 IRQ_TYPE_EDGE_FALLING> 是不是必须在驱动里 of_get_named_gpio_flags(node, "irq_gpio_off", 0, &flag);这一句之后 gpio2-b1才被配置成gpio模式(假设该gpio初始是i2c_scl模式)。
  • quan

    请问一下,这个aliases节点具体有什么用处,我在不同的SOC下面发现aliases节点包含的设备节点都不一样, 引用设备节点不是使用&label的方式就可以了吗,为什么还要这个aliases,他是用来告诉操作系统某些信息的吧
    • 郭健

      @quan:aliases节点用来定义alias property的,每个SOC平台都可以根据自己的喜好随意定义,没有什么特别的,就是为了方便而已。
      • quan

        @郭健:谢谢你的回答,这相当于aliases节点没有什么用是吗,那是为了方便什么了?
    • wowo

      @quan:alias的作用,相当于为node起一个别名,然后其它的node就可以引用这个别名。它的好处是: 有些node在不同的版型里面,真名可能有变化,例如gpio,有些版型可能叫做gpio0、gpio1、…,而有些可能叫做gpioa、gpiob、…,原因是node起名的依据应该尽量贴近硬件。 而其它driver(例如使用gpio的driver)引用gpio时,又不想随着gpio的真名的改变,统统改变一下,所以它们就可以引用gpio的别名。 别名尽量固定,更改真名的情况下,只需要更改alias处即可,不需要更改每一个用到的driver。
      • quan

        @wowo:不好意思,还是不怎么明白,驱动为什么会去调用这个别名了
      • quan

        @wowo:设备树中引用某个节点,直接使用&label就行,与aliases节点没有关系对吧
        • wowo

          @quan:因为真名或者label都有可能会变,最不可能变的是别名。
          • quan

            @wowo:驱动或者内核在什么地方会使用这个别名,可否给一个实例 谢谢
            • wowo

              @quan:抱歉,我上面的描述不准确,aliases的主要目的是方便某一类设备的driver的编写,以gpio举例。 CPU可能有多个gpio controller,DTS中通常这样描述: gpio1: gpio@1 { compatible = "fsl,imx28-gpio"; ... }; gpio2: gpio@2 { compatible = "fsl,imx28-gpio"; ... }; ... 需要注意的是,这样多个gpio的nodes的compatible 是一样的,因此driver probe的时候无法知道到底是哪一个node,1?2? 那用什么方法让driver知道呢?可以在每个node里面自己加一个id,例如'id = <1>;',driver自行解析即可。但这样不通用,因此kernel就提供了一个标准方法,aliases: aliases { gpio0 = &gpio0; gpio1 = &gpio1; gpio2 = &gpio2; gpio3 = &gpio3; gpio4 = &gpio4; }; 这样driver在获得某一个gpio node(&gpio1)后,就可以以该node指针以及aliases的关键字(gpio)为参数,调用of_alias_get_id接口,获得对应的id。
              • quan

                @wowo:非常感谢,你的回复,我好好理一理
        • 郭健

          @quan:定义一个lable,然后通过&label来引用当然可以,但是你也许需要多个不同的名字呢?比如你的名字叫做quan(是一个符号),我引用的时候就是&quan,但是你老婆引用的时候可能是老公,那么“老公”就是一个alias,也就是说 老公 = &quan 如果是朋友想要引用你,那么他们可能叫你“老Q”,那么“老Q”就是一个alias,也就是说 老Q = &quan 根据场合,定义不同的alias是不是会方便一些呢?
          • quan

            @郭健:不好意思,本人比较愚钝,不是太明白你说的,我觉得上面蜗蜗分析的和你说的完全不一样
            • 云

              @quan:他们说的都是对的,都是alias的使用场景:)
            • 郭健

              @quan:一样的啊,lable不就是定义在node name或者property 之前的符号吗?通过&就可以引用该符号而不必使用很长的full path。 你可以直接引用这些符号,也可以定义一些别名(alias)来代替(&lable)。如果你愿意,可以定义n个别名来表示对某个符号的引用,只要你愿意。
              • Bin

                @郭健:label在设备树中用,aliases中定义的别名在驱动源码中用
  • teensoul

    作者太棒了,感谢作者。DT这个系列看完,连评论都舍不得放过,写得太好了,完全是工作中的前辈手把手教啊。
    • wowo

      @teensoul:多谢鼓励:-)
  • emeralddream

    分析的很好。最近在工作上遇到点问题。 经常在.dtsi文件中的设备节点中有 status = “disable”,然后在.dts文件中用句柄获取这个节点后status = “okay”。 一直弄不清 disable 和 okay 的具体功能,似乎有些设备即使你不去status = ‘okay’,也能工作。希望版主解答一下。
    • wowo

      @emeralddream:status = “disable”和把这个dts节点删掉,效果相同。至于为什么不直接删掉呢?大家应该可以猜到吧:-)
      • emeralddream

        @wowo:现在原厂喜欢在dtsi文件中配置节点时status=disable,然后在dts中&xxx {status = "okay"};就是说如果不okay,这个设备节点就无法和驱动匹配,执行probe函数吗? 我现在遇到一个uart,用来抓log的。并没有在status = ‘okay’,为什么他依然能够工作?
      • emeralddream

        @wowo:现在原厂喜欢在dtsi文件中配置节点时status=disable,然后在dts中&xxx {status = "okay"};就是说如果不okay,这个设备节点就无法和驱动匹配,执行probe函数吗? 我现在遇到一个uart,用来抓log的。并没有在status = ‘okay’,为什么他依然能够工作?
        • wowo

          @emeralddream:是的。 没有status = ‘okay’没有关系,只要不显式的设置“status=disable”,都会编译进入的。
  • fox

    你好,请问下pop device是怎么实现的呢
    • wowo

      @fox:建议你先给大家普及一下pop device的概念吧?呵呵:-)
      • fox

        @wowo:我说的pop device指的是device和driver的匹配过程,dt引入后,他们的匹配过程还和dt之前一样吗?谢过wowo
        • wowo

          @fox:DT的目的是生成device结构(从之前的静态table变为现在的动态生成),后续的匹配过程,还是一样的。
          • fox

            @wowo:DT把device结构传到kernel之后,应该匹配什么驱动就是kernel那块通过比对compatible实现的吗?我也看过你关于kobject那块的文章,说是通过name匹配,这块有点不太清楚,还有热插拔设备和DT就没关系了吧。
            • fox

              @fox:还有如果kernel都一样,用在不同的arm 平台上,把dts传进去后,刚开始是匹配machine type,在表中查找和dtb中对应的machine type 这个是实现啥作用呢?我问题有点多了,先谢过wowo了:)
              • wowo

                @fox:这个问题,在“/device_model/dt-code-analysis.html”中有介绍,主要是在setup_arch中,调用setup_machine_fdt了。 machine Type在旧的mach-xxx中,比较有用,因为需要依据这个找到那个数据结构。但在dts时代,并不是特别重要了。
            • wowo

              @fox:是的,现在一般都是通过compatible了,以前是通过name,当然还有其它方式,具体细节,可以直接看匹配的地方的代码。 热拔插的device,一般都是bus的代码自动创建的,所以不会用DT指定。
              • fox

                @wowo:谢过wowo,我把你统一设备模型里面的文档挨个看了一遍,收获很大
              • fox

                @wowo:#address-cells和#size-cells究竟是啥意思呢?前者是地址,后者是代表地址长度吗?那上面的cpu #size-cells=<0>就是地址无效吗
  • wowo

    @fox,关于#address-cells和#size-cells,这篇文章中有讲解。简单的说,它们是针对本DTS节点中的"reg=<>;"字段的,分别规定了该字段中用于表示address的数据宽度和用于表述address范围的数据宽度,例如: memory@0 { device_type = "memory"; reg = <0x000000000 0x00000000 0x00000000 0x80000000>; }; reg表示memory的开始地址为0x0,长度为0x80000000. 其中#address-cells和#size-cells分别为2(两个dword)。 对于cpu #size-cells=<0>,则表示CPU节点中的reg,没有范围的概念(只有address,0,1等等)。
    • fox

      @wowo:那rangs和reg的区别是啥呢?我的理解是rangs表示该节点与子节点的地址,reg表示该节点自己的地址
      • linuxer

        @fox:ranges的定义: The ranges property provides a means of defining a mapping or translation between the address space of the bus (the child address space) and the address space of the bus node's parent (the parent address space). reg的定义: The reg property describes the address of the device's resources within the address space defined by its parent bus. Most commonly this means the offsets and lengths of memory-mapped IO register blocks, but may have a different meaning on some bus types. Addresses in the address space defined by root node are cpu real addresses. ranges是和父子设备的地址空间映射相关的 reg是定义设备自己的地址资源的(在自己的地址空间中)
  • 大头元

    首先感谢博主的分享,有个问题请教一下,我在看am335x-evm.dts时对0x94,0x90,0x70...这些是什么意思不太清楚,这个是怎么计算出来的啊? gpio_keys_s0: gpio_keys_s0 { pinctrl-single,pins = < 0x94 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_oen_ren.gpio2_3 */ 0x90 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_advn_ale.gpio2_2 */ 0x70 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_wait0.gpio0_30 */ 0x9c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ben0_cle.gpio2_5 */ >; };
    • wowo

      @大头元:建议参考这个文档: ./Documentation/devicetree/bindings/pinctrl/pinctrl-single.txt The pin configuration nodes for pinctrl-single are specified as pinctrl register offset and value pairs using pinctrl-single,pins. Only the bits specified in pinctrl-single,function-mask are updated. For example, setting a pin for a device could be done with: pinctrl-single,pins = <0xdc 0x118>; Where 0xdc is the offset from the pinctrl register base address for the device pinctrl register, and 0x118 contains the desired value of the pinctrl register. See the device example and static board pins example below for more information.
      • 大头元

        @wowo:谢谢哈,我找到了,在am33xx.dtsi文件中定义了两个ranges. ocp { ranges; l4_wkup: l4_wkup@44c00000 { ranges = <0 0x44c00000 0x280000>; scm: scm@210000 { ranges = <0 0x210000 0x2000>; am33xx_pinmux: pinmux@800 { compatible = "pinctrl-single"; reg = <0x800 0x238>; }; }; }; };
    • iwakan

      @大头元:@linuxer @wowo 请问 gpio_keys_s0: gpio_keys_s0 { pinctrl-single,pins = < 0x94 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_oen_ren.gpio2_3 */ 0x90 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_advn_ale.gpio2_2 */ 0x70 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_wait0.gpio0_30 */ 0x9c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ben0_cle.gpio2_5 */ >; }; 这个节点描述前面的偏移地址应该怎么(计算/查阅)得到呢?我刚学习,这些数据的来源不太了解……
      • iwakan

        @iwakan:就是数据的含义和来源不太了解,如何从源码或者手册中得到体现,如果问题问得不好还请指教。
        • wowo

          @iwakan:看datasheet,pinctorl(或者gpio)那一章节,找寄存器的偏移地址(要结合具体的硬件看,这里是am335x)。
          • iwakan

            @wowo:谢谢!
  • Jordon Wu

    @wow & all, 请教使用device tree如何实现lvds module的poweron/poweroff 的时序控制? 使用imx 接lvds过程中,panel对poweron/poweroff时有几个pin脚有时序上的要求(VCC->STBYB->Blacklight),看了fsl,imx-fb.txt bingding doc里面只有timeing相关的配置,像这种额外的pin脚时序配置如何处理? 搜索https://community.nxp.com/thread/353019 看到这里有自己实现了display-powerup/display-powerdown, 不知这种做法是否正确? 谢谢
    • Jordon Wu

      @Jordon Wu:或者哪个模块由类似的处理,也请帮忙指出,去参考学习下。
      • wowo

        @Jordon Wu:你给的例子中,做的已经很好了
  • jasonshi

    感谢
  • jasonshi

    讲的很好,很有帮助, 在实际中看到很多这样写的例子, 4、s3c2416-pinctrl.dtsi 这个文件定义了pinctrl@56000000 这个节点的若干child node,主要用来描述GPIO的bank信息。 这个文件中是这样写的: &pinctrl_0 { 。。。。。 。。。。。 } 不太理解这样的用法,这个&pinctrl_0 这里的用法是算什么呢? label吗? dtc编译会将它与s3c24xx.dtsi中的pinctrl_0: pinctrl@56000000 {。。。}进行合并操作吗? 请解惑,谢谢。
    • wowo

      @jasonshi:你理解的已经对了,合并操作,如果有重复,后面覆盖前面。
  • harveygoodday

    非常感谢蜗窝。看蜗窝的文章是一种享受。 真是达到了“慢下来,享受技术”的境界。
  • 风雪新86

    wowo; 为什么这个reg 起始地址不是从0开始?先谢过! memory { device_type = "memory"; reg = <0x80000000 0x7f000000>; /* 1024 MB */ };
    • wowo

      @风雪新86:这要看这段memory到底是怎么寻址的,从哪里开始都没有问题的。
      • 风雪新86

        @wowo:感谢wowo!
  • 麦兜要努力

    @wowo请教一下版主, 1. 本例中的物理内存的布局并没有通过memory node传递,其实我们可以使用command line传递,我们command line中的参数“mem=64M@0x30000000”已经给出了具体的信息。 如果dtsi中有定义以下的memory node,然后同时dtsi里面又要bootcmd,那到底是通过memory node去传递还是command line去传递呢?如何区分呢? memory@0{ device_type = "memory"; reg = <0x00000000 0x02000000>; }; chosen { bootargs = "console=ttyS0,38400 mem=32M@0x00000000 init=/init earlyprintk"; } 2.memory block定义有的节点有两种,一种是node name是memory@形态的,另外一种是node中定义了device_type属性并且其值是memory.参考dtsi里面的node,发现memory@形态中也有定义device_type,如 memory@0 { device_type = "memory"; reg = <0x20000000 0x1f000000>, <0xc0000000 0x3f000000>; }; or memory@70000000 { device_type = "memory"; reg = <0x70000000 0x20000000>; }; 感觉就有点混淆了?如果commandline里面没有传递mem=32M@0x00000000,发现memory@{}和memory{},在Virtual kernel memory layout的lowmem会有差别.. 希望能得到版主的解答!感激!
    • linuxer

      @麦兜要努力:第一个问题在本站的内存布局(/memory_management/memory-layout.html)文档中已经有描述,可以参考,简单来说: 1、dts中的memory node给出内存布局信息。 2、在过去,没有device tree的时代,mem这个命令行参数传递了memory bank的信息,内核根据这个信息来创建系统内存的初始布局。在ARM64中,由于强制使用device tree,因此mem这个启动参数失去了本来的意义,现在它只是定义了memory的上限(最大的系统内存地址),可以限制DTS传递过来的内存参数。 这是arm64的处理,至于arm平台,你可以自行看看代码是如何实现的。
  • sunshine.huang

    hi linuxer 请教个问题 关于这段: unit-address的具体格式是和设备挂在那个bus上相关。例如对于cpu,其unit-address就是从0开始编址,以此加一。而具体的设备,例如以太网控制器,其unit-address就是寄存器地址 有什么途径可以获取unit-address全部格式的说明呢?比如你列举了这几个是从哪里获取得来的,除了你列举的这些,其余的具体还有哪些,分别都是什么格式的? 谢谢!
    • hello world

      @sunshine.huang:Documentation/devicetree目录下可以找到所有相关的信息。
  • kobj

    是不是父节点中定义的变量,子节点中没有定义 都会从父节点中继承下来呢?
  • melo

    @linuxer @wowo 请问 突然想知道内核是如何知道设备树文件是怎么使用的呢 也就是说 设备树的使能?
    • hello

      @melo:都不知道你想要问什么问题。
  • seajiajia

    你好,请问下嵌入式arm用dts这种机制,那arm server呢?似乎没有找到对应的dts文件? 换句而言,一个server有多少内存资源,需要在device tree里边注明吗?不能动态改变?
    • wowo

      @seajiajia:没有看过server的平台,还真不知道。 至于内存,linux mm中有hotplug机制,不知道server会不会用。
  • 悟空不念经

    写的真好!
  • 风露

    蜗窝大师,再请教一个问题: dt结构中,哪些node的名字是不能改变的(soc 、chosen不可以?),哪些是可以改变的?
    • hello-world

      @风露:这位大哥,你的问题太细了,而且又很泛,别人没有办法回答啊。
    • tmmdh

      @风露:我想你是想问设备树中的保留关键字吧?分析一下dtc源码就知道了
  • linux2607 主页

    郭大侠,请问device tree中,16位的整数怎么表示?用什么符号括起来?
  • BeDook

    作为菜鸟,刚接触设备树,看到博主们和其他前辈的讨论和详细的解释,收获颇多,尤其是博主的讲解很有条理,对Linux内核方面可以说是庖丁解牛,功力深厚,这得多少年的积淀啊,向博主们学习,立马转成蜗窝粉!
  • billxiang

    感觉和qemu的配置很像,哈哈哈