1. 设备树概念
1.1.设备树感性认识
设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做DTS(Device Tree Source),这个DTS 文件采用树形结构描述板级设备,比如CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等。设备树是树形数据结构,具有描述系统中设备的节点。每个节点都有描述所代表设备特征的键值对。每个节点只有一个父节点,而根节点则没有父节点。
1.2.DTS、DTB、DTC
DTS:设备树源码文件;DTB:将DTS编译后得到的二进制文件;DTC:DTS的编译工具,其源码在内核的scriptsdtc目录下。基于同样arm架构的CPU有很多,同一个CPU会制作很多配置不一的板子,如何正确的编译所选的板子的DTS文件呢?在内核的arch/arm/boot/dts/Makefile中:
dtb-$(CONFIG_ARCH_XXX) += xxx.dtb
dtb-$(CONFIG_ARCH_XXX) += xxx-sip.dtb
dtb-$(CONFIG_ARCH_XXX) += xxx.dtb
dtb-$(CONFIG_ARCH_XXX) += xxx.dtb
例如xxxx的开发板,只要设置CONFIG_ARCH_xxx=y,所有用到这颗SOC的DTS都会编译成DTB。如果后续还用到了这颗SOC设计的开发板,只要新建一个DTS文件,并将对应名称的DTB文件名加到dtb-$(CONFIG_ARCH_xxx)中,在编译设备树时就会将DTS编译为二进制的DTB文件。
1.3.Device Tree语法
以下语法分析均以xxx.dts为例。
1.3.1. dtsi头文件
设备树的头文件扩展名为 .dtsi。以xxx.dts为例,其包含以下头文件。
#include "skeleton.dtsi"
#include xxx.h"
#include "xxx-clocks.dtsi"
#include "xxx-pinctrl.dtsi"
#include "xxx-camera.dtsi"
需要注意的是.dts文件不但可以引用.dtsi文件,还可以引用.h文件和其他的.dts文件。Q1:每一个.dtsi和.dts都有自己的根节点,但是一个设备树文件只允许有一个根节点,DTC如何处理?将根节点合并,保留最后一级的根节点。包含的头文件内容会被展开,展开的位置在/memory和/cpus之间。(存疑,只用xxx.dts编译过)Q2:如果包含过程中有重复的compatible,DTC怎么处理?编译时不会报错,会生成两个compatible属性一样的节点。
1.3.2. 设备节点
设备树中的每一个节点都按照以下格式命名:
node-name@unit-address
node-name表示节点名称,它的长度范围应该是1~31个字符,可以由以下的字符组成:
字符说明0~9数字a-z小写字母A-Z大写字母,逗号(英文).句号(英文)_下划线(英文)+加号-减号
表 2-1节点名称的有效字符
节点名称应以较低或大写字符开头,并应描述设备的一般类别。节点的单位地址特定于节点所在的总线类型。它由表2-1中字符集中的一个或多个ASCII字符组成。单位地址必须与节点的reg属性中指定的第一个地址匹配。如果节点没有reg属性,则必须省略@unit-address,并且单独使用节点名称将节点与树中相同级别的其他节点区分开来。对于reg格式和单位地址,特定总线的绑定可能会指定附加更具体的要求。根节点没有节点名称或单位地址。它由正斜杠(/)标识。
图 2-1节点名称示例
在图2-1中,节点名称为cpu的两个节点通过uint-address 0和1区分;节点名称为ethernet的两个节点通过uint-address fe002000和fe003000区分。在设备树中经常会看到以下设备名称:
watchdog: watchdog@04009800
冒号前的是节点标签(label),冒号后是节点名称。引入label的目的是方便访问节点,可以直接通过&label来访问这个节点。比如上述节点就可以使用&watchdog来访问。
1.3.2.1. 通用名称建议
节点的名称应该有些通用,反映设备的功能,而不是其精确的编程模型。如适用,名称应为以下选择之一:
? adc ? accelerometer
? atm ? audio-codec
? audio-controller ? backlight:
? bluetooth ? bus
? cache-controller ? camera
? can ? charger
? clock: ? clock-controller
? compact-flash ? cpu
? cpus ? crypto
? disk ? display
? dma-controller ? dsp
? eeprom ? efuse:
? mdio ? memory
? memory-controller ? mmc
? mmc-slot ? mouse
? nand-controller ? nvram
? oscillator ? parallel
? pc-card ? pci
? pcie ? phy
? pinctrl ? pmic
? pmu ? port
? ports ? pwm
1.3.2.2. 路径名称
通过指定从根节点到所需节点的完整路径(通过所有子节点),可以唯一识别devicetree中的节点。指定设备路径的约定是:
/node-name-1/node-name-2/.../node-name-N
例如,在图2-1中,到cpu#1的设备路径为:
/cpus/cpu@1
/为根节点,在保证完整路径明确的前提下,可以省略uint-address。
1.3.3. 属性
设备树中的每个节点都有描述节点特性的属性。属性由名称和值组成。
1.3.3.1. 属性名称
属性名称的长度范围应该是1~31个字符,可以由以下的字符组成:
字符说明0~9数字a-z小写字母A-Z大写字母,逗号(英文).句号(英文)_下划线(英文)+加号?问号(英文)##号(hash)
非标准属性名称应指定唯一的字符串前缀,例如股票代号,用于标识定义该属性的公司或组织的名称。示例:
xxx,pin-function = <6>;
fsl,channel-fifo-len
linux,network-index
ibm,ppc-interrupt-server#s
1.3.3.2. 属性值
属性值是包含与属性关联的信息的零或多个字节的数组。
数值描述、值为空,用于描述bool信息。个人理解类似于flag参数、32位整数,采用big-endian格式。示例:32位值0x11223344将在内存中表示为:地址 11 地址 + 1 22 地址 + 2 33 地址 + 3 44、表示采用big-endian格式的64位整数。包括两个值,其中第一个值包含整数的最有效位,第二个值包含最小有效位。例如:64位值0x1122334455667788将表示为两个单元格:<0x11223344 0x55667788>。、格式特定于属性,参见属性定义。、字符串可打印且以空值()结尾。、一个值。phandle值是引用设备树中另一个节点的方法。可被引用的任何节点都用唯一的定义了phandle属性数值。该数字用于带有phandle值类型的属性值。、串联在一起的值列表。
big-endian和little-endian(大小端):big-endian:是指低地址端存放高位字节;little-endian:是指高地址端存放低位字节;
1.3.3.3. 标准属性Compatible(兼容)属性名称兼容值值类型<stringlist>描述兼容属性值由定义设备特定编程模型的一个或多个字符串组成。客户端程序应使用此字符串列表选择设备驱动程序。该属性值包含一个从最特定到最通用的null终止字符串的串联列表。它们允许设备表达其与一系列类似设备的兼容性,可能允许单个设备驱动器与几个设备匹配。推荐的格式是“制造商,型号”,其中制造商是描述制造商名称的字符串(如股票代号)。
示例:
compatible =“fsl,mpc8641”,“ns16550”;
在此示例中,操作系统将首先尝试查找支持fsl,mpc8641-uartmpc8641的设备驱动程序。如果找不到驱动程序,然后,它将尝试定位受支持的更通用的ns16550设备类型驱动程序 。
一般驱动程序文件都会有个OF匹配表,此匹配表保存着一些compatible值,如果设备节点的 compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这驱动。比如在文件drvier/misc/memctrl.c中:
static struct of_device_id_xxx_memctrl_of_match[] = {
{ .compatible = "xxxx,memctrl", },
{},
};
对应的,在arch/arm/boot/dts/xxx.dts中有:
memctrl: memctrl {
compatible = "xxxx,memctrl";
reg = <0x0121B000 0x1044>;
clocks = <&sdram_bandw_clk>, <&mem_axi_clk>;
clock-names = "sdram_bandwidth_clk", "mem_axi_clk";
interrupts = <GIC_SPI INT_SDRAM IRQ_TYPE_LEVEL_HIGH>;
interrupt-controller;
#interrupt-cells = <1>;
};
Model(型号)属性名称模型值值类型
描述指定设备的制造商型号。推荐的格式为:“制造商,型号”,其中制造商是描述制造商名称的字符串(如股票代号)。
示例:
model =“fsl,MPC8349EMITX”;
Phandle(pointer handle)属性名称pointer handle值值类型
描述phandle属性指定设备树中唯一节点的数字标识符。phandle属性值被需要引用与该属性关联的节点的其他节点使用。
示例:
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
定义了1的phandle值。另一个设备节点可以引用phandle值为1的pic节点:
another-device-node {
interrupt-parent = <1>;
};
Status属性名称状态值值类型<string>描述状态属性指示设备的操作状态,其有效值如下:"okay":指示设备可运行。"disabled":表明设备目前尚未运行,但未来可能会运行(例如,未插入或关闭某物)。"fail":表示设备无法运行,在设备中检测到严重错误,如果不进行维修就不太可能运行"fail-sss":表示设备无法运行,在设备中检测到严重错误,如果不进行维修就不太可能运行,sss部分特定于设备,并指示检测到的错误情况。#address-cells and #size-cells属性名称#address-cells,#size-cells值类型<u32>描述#address-cells和#size-cells属性可用于设备树层次结构中包含子节点并描述如何解决子设备节点的任何设备节点。#address-cells属性定义用于编码子节点的reg属性中地址字段的个单元格的数量。#size-cells属性定义用于编码子节点的reg属性中大小字段的个单元格的数量。#address-cells = <1>;
#size-cells = <0>;
表示reg属性中有一个u32表示address,没有表示reg大小的数据,所以:reg = <0x0>; 即reg的起始地址为0x0,不描述其大小
#address-cells = <1>;
#size-cells = <1>;
表示reg属性中有一个u32表示address,有一个u32表示size,所以:reg = <0x00000000 0x00040000>; 即reg的起始地址为0x00000000,大小是0x00040000
Reg属性名称reg值类型<prop-encoded-array> 编码为任意数量(地址、长度)对描述reg属性描述设备资源在其父总线定义的地址空间内的地址。这通常意味着内存映射IO寄存器块的偏移和长度,但在某些总线类型上可能有不同的含义。根节点定义的地址空间中的地址为cpu真实地址。该值是一个,由任意数量的地址和长度对组成,<地址长度>。指定地址和长度所需的单元格的数量是总线特定的,由设备节点父级中的#address-cells和#size-cells属性指定。如果父节点为#size-cells单元格指定值0,则应忽略reg值中的长度字段。
示例:假设系统芯片中的设备包含两个寄存器块,SOC中偏移0x3000的32字节块和偏移0xFE00的256字节块。reg属性的编码如下(假设#address-cells和#size-cells值为1):
reg=<0x3000 0x20 0xFE00 0x100>;
virtual-reg属性名称virtual-reg值类型<u32>描述virtual-reg属性指定一个有效地址,该地址映射到设备节点的reg属性中指定的第一个物理地址。此属性使引导程序能够为客户端程序提供已设置的虚拟到物理映射。Ranges属性名称range值类型<empty>或编码为任意数量的(子总线地址、父总线地址、长度)三联体描述range属性提供了一种在总线地址空间(子地址空间)和总线节点父地址空间(父地址空间)之间定义映射或转换的方法。range属性值的格式是任意数量的三联体(子总线地址、父总线地址、长度):1.子总线地址是子总线地址空间内的物理地址。表示地址的单元格数取决于总线,可以通过此节点(出现range属性的节点)的#address-cells确定。2. 父总线地址是父总线地址空间中的物理地址。表示父地址的单元格数取决于总线,可以通过定义父地址空间的节点的#address-cells属性确定。3. 长度指定子地址空间中范围的大小。表示大小的单元格数可以根据该节点(出现range属性的节点)的#size-cells确定。如果属性用值定义,则它指定父地址和子地址空间相同,并且不需要地址转换。如果总线节点中不存在该属性,则假设节点的子节点和父地址空间之间不存在映射。
示例:
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0xe0000000 0x00100000>;
serial {
device_type = "serial";
compatible = "ns16550";
reg = <0x4600 0x100>;
clock-frequency = <0>;
interrupts = <0xA 0x8>;
interrupt-parent = <&ipic>;
};
};