设备树
一般 .dts描述板级信息 (也就是开发板上有哪些IIC设备、SPI设备等 ),.dtsi描述SOC级信息 (也就是 SOC有几个 CPU、主频是多少、各个外设控制器信息等 )。其他的 .dts文件可以直接引用 .dtsi文件,类似于 C语言中的头文件。
DTS是设备树源码文件, DTB是将DTS编译以后得到的二进制文件,编译工具是DTC。DTC工具源码在 Linux内核的 scripts/dtc目录下。
DTS语法
dtsi头文件
和C语言一样,设备树也支持头文件,设备树的头文件扩展名为 .dtsi。
在 .dts设备树文件中,可以通过“#include”来引用 .h、 .dtsi和 .dts文件。
设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设
备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键值对。
设备的命名格式有以下两种:
node-name@unit-address
label: node-name@unit-address
label:节点标签,引入 label的目的就是为了方便访问节点,可以直接通过 &label来访问这个节点;
node-name:节点名字,为 ASCII字符串,节点名字应该能够清晰的描述出节点的功能。
unit-address:一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话可以不要。
示例代码如下:
/ {
aliases {
...............
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0>;
clock-latency = <61036>; /* two CLK32 periods */
operating-points = <
/* kHz uV */
996000 1275000
792000 1225000
/*696000 1225000*/
528000 1175000
396000 1025000
198000 950000
>;
fsl,soc-operating-points = <
/* KHz uV */
996000 1175000
792000 1175000
/*696000 1175000*/
528000 1175000
396000 1175000
198000 1175000
>;
fsl,low-power-run;
clocks = <&clks IMX6UL_CLK_ARM>,
<&clks IMX6UL_CLK_PLL2_BUS>,
<&clks IMX6UL_CLK_PLL2_PFD2>,
<&clks IMX6UL_CA7_SECONDARY_SEL>,
<&clks IMX6UL_CLK_STEP>,
<&clks IMX6UL_CLK_PLL1_SW>,
<&clks IMX6UL_CLK_PLL1_SYS>,
<&clks IMX6UL_PLL1_BYPASS>,
<&clks IMX6UL_CLK_PLL1>,
<&clks IMX6UL_PLL1_BYPASS_SRC>,
<&clks IMX6UL_CLK_OSC>;
clock-names = "arm", "pll2_bus", "pll2_pfd2_396m", "secondary_sel", "step",
"pll1_sw", "pll1_sys", "pll1_bypass", "pll1", "pll1_bypass_src", "osc";
};
};
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
第一行的“/”是根节点。
在设备树源码中,常见的数据形式有字符串、字符串列表(通过“,”隔开)和32位无符号整数。
标准属性
- compatible属性:“兼容性”属性,其值是一个字符串列表, compatible属性用于将设备和驱动绑定起来。
- model属性:属性值也是一个字符串,一般 model属性描述设备模块信息,比如名字。
- status属性:属性值也是一个字符串,与设备的状态有关。
- #address-cells和#size-cells属性:属性值是无符号32位整型,可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells属性值决定子节点reg属性中地址信息所占用的字长(一个字长是32位),#size-cells属性值决定子节点reg属性中长度信息所占用的字长(一个字长是32位)。
- reg属性:值一般是(address, length)对,一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息:起始地址和地址长度。reg属性的格式一般为:
每个“ address length”组合表示一个地址范围,其中 address是起始地址, length是地址长度。示例如下:reg = <address1 length1 address2 length2 address3 length3…………>
clocks { #address-cells = <1>; #size-cells = <0>; ...................... ipp_di0: clock@2 { compatible = "fixed-clock"; reg = <2>; #clock-cells = <0>; clock-frequency = <0>; clock-output-names = "ipp_di0"; }; ...................... }; soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; interrupt-parent = <&gpc>; ranges; ......................... ocrams: sram@00900000 { compatible = "fsl,lpm-sram"; reg = <0x00900000 0x4000>; }; ............................... };
- ranges属性:属性值可以为空或者按照 (child-bus-address,parent-bus-address,length)格式编写的数字矩阵, ranges是一个地址映射 /转换表, ranges属性每个项目由子地址、父地址和地址空间长度这三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的 #address-cells确定此物理地址
所占用的字长。
parent-bus-address 父总线地址空间的物理地址,同样由父节点的 #address-cells确定此物
理地址所占用的字长。
length:子地址空间的长度,由父节点的 #size-cells确定此地址长度所占用的字长。
如果 ranges属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换。 - device_type属性:属性值为字符串,只用于cpu节点或者memory节点。
根节点compatible属性
Linux内核会通过根节点的 compoatible属性查看是否支持此设备,如果支持的话设备就会启动 Linux内核。
当 Linux内核引入设备树以后,使用 DT_MACHINE_START进行匹配。 DT_MACHINE_START也定义在文件 arch/arm/include/asm/mach/arch.h 里面,定义如下:
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
在DT_MACHINE_START里面直接将.nr设置为 ~0。说明引入设备树以后不会再根据machine id来检查Linux内核是否支持某个设备了。
在文件 arch/arm/mach-imx/mach-imx6ul.c中有以下内容:
static const char *imx6ul_dt_compat[] __initconst = {
"fsl,imx6ul",
"fsl,imx6ull",
NULL,
};
DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
MACHINE_END
// 展开后如下所示
static const struct machine_desc __mach_desc_IMX6UL \
__used \
__attribute__((__section__(".arch.info.init"))) = { \ // 变量__mach_desc_IMX6UL存放在.arch.info.init段中
.nr = ~0, \
.name = "Freescale i.MX6 Ultralite (Device Tree)",
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
};
machine_desc结构体中dt_compat成员变量保存着本设备兼容属性,与设备树中根节点的compatible属性对应,匹配说明支持此设备。
Linux内核调用start_kernel函数来启动内核,具体的匹配流程如下:
向节点追加或修改内容
通过 &label来访问节点,然后直接在里面编写要追加或者修改的内容。示例如下:
&cpu0 {
arm-supply = <®_arm>;
soc-supply = <®_soc>;
dc-supply = <®_gpio_dvfs>;
};
设备树在系统中的体现
Linux内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的 /proc/device-tree目录下根据节点名字创建不同文件夹,通过以下命令可以查看相关内容:
cd /proc/device-tree
特殊节点
根节点下有两个特殊节点:aliases和chosen
aliases节点
节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。作用与节点命名中的label一样。
chosen节点
chosen并不是一个真实的设备, chosen节点主要是为了 uboot向 Linux内核传递数据,重点是bootargs参数。
stdout-path属性表示标准输出设备。另外,bootargs属性由uboot设置。具体流程如下:
Linux内核解析DTB文件
其他及设备树操作函数
在Linux源码目录/Documentation/devicetree/bindings内有详细的.txt 文档描述了如何添加节点。
Linux内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“ of_”。这些 OF函数原型都定义在 include/linux/of.h文件中。使用结构体device_node描述设备节点。
查找设备节点操作函数
// 通过节点name查找
struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
// 通过device_type属性查找
struct device_node *of_find_node_by_type(struct device_node *from,
const char *type);
// 通过compatible属性查找, type可以为NULL
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type,
const char *compat);
// 通过of_device_id进行查找
struct device_node *of_find_matching_node_and_match(
struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match);
// 通过带有节点名称的路径查找
static inline struct device_node *of_find_node_by_path(const char *path)
{
return of_find_node_opts_by_path(path, NULL);
}
属性提取操作函数
struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
其他操作函数
int of_device_is_compatible(const struct device_node *device,
const char *);
//
static const __be32 *of_get_address(struct device_node *dev, int index,
u64 *size, unsigned int *flags);
//
u64 of_translate_address(struct device_node *np, const __be32 *addr);
//
int of_address_to_resource(struct device_node *dev, int index,
struct resource *r);
void __iomem *of_iomap(struct device_node *node, int index);