一、语法
1.节点命名格式
- node-name@unit-address
node-name:节点名字,为ASCII字符串,节点名字应该能清晰描述节点的功能。
unit-address:一般表示设备的地址或寄存器的首地址,如果某个节点没有这些,那么unit-addree可以省略。
- label:node-name@unit-address
引入label是为了方便调用,可以使用&label来访问这个节点,而不需要输入完整的节点名。
2.属性的值数据类型
- 字符串
compatible = "str"
- 32位无符号整数
reg = <0 0x123456 100> #一组u32
- 字符串列表
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
3.标准属性
- compatible
compatible属性用于将设备和驱动绑定起来,其属性值是字符串列表类型。 特殊的是,根节点的该属性是用于
"manufacturer,model" #compatible格式
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960"; #实例
- model属性
model属性值用于描述设备模块,比如名字等等。
model = "wm8960-audio"
- status属性
"okay":设备可操作
"disabled":设备不可操作,但在未来可以变得可操作,如热插拔等。 "fail":设备不可操作,设备检测到一系列错误。
"fail-sss":同上,sss是检测到错误的内容。
- reg属性
reg属性用来描述设备的起始地址和长度
reg<address, length> #reg的一般格式,address占用字长则由下面的属性决定
- #address-cells 和#size-cells属性
这两个属性用在含子节点的设备中,用来描述子节点的reg属性。
#address-cells决定了子节点reg属性中地址信息所占用字长。
#size-cells决定了子节点reg属性中长度信息所占的字长。
# 例1
#address-cells = <1>;
#size-cells = <1>;
...
reg = <0x02280000 0x4000>; 地址0x02280000 长度0x4000
#例2
#address-cells = <2>;
#size-cells = <2>;
...
reg = <0x90000000 00000000 0x800000 00000000>; 地址0x90000000 00000000 长度0x800000 00000000
- ranges属性
ranges是一个地址映射表。
ranges = <child-bus-address,parent-bus-address,lenth>
child-bus-address: 父总线地址空间物理地址,由#address-cells确定此物理地址所占用的子长。
parent-bus-address: 子总线地址空间物理地址,由#address-cells确定此物理地址占用的字长。
lenth: 子地址空间的长度,由#size-cells确定此地址长度所占用的字长。
#实例1 父地址空间和子地址空间完全相同
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
......
}
#实例2 父地址空间和子地址空间不同
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0xe0000000 0x00100000>;#此属性值指定了一个1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为0x0,映射父地址空间的物理起始地址为 0xe0000000。
serial {
device_type = "serial";
compatible = "ns16550";
reg = <0x4600 0x100>; #serial是串口设备节点,reg属性定义了serial设备寄存器的起始地址为0x4600,寄存器长度为 0x100。经过地址转换,serial设备可以从0xe0004600开始进行读写操作,0xe0004600=0x4600+0xe0000000。
...
};
};
- name属性
name属性值为字符串,name 属性用于记录节点名字,name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。
- device_type属性
device_type属性值为字符串,只用于cpu节点和memory节点。
#实例
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0>;
......
};
二、创建小型设备树流程
1.要求
(1)I.MX6ULL 这个 Cortex-A7 架构的 32 位 CPU。
(2)I.MX6ULL 内部 ocram,起始地址 0x00900000,大小为 128KB(0x20000)。
(3)I.MX6ULL 内部 aips1 域下的 ecspi1 外设控制器,寄存器起始地址为 0x02008000,大小为 0x4000。
(4) I.MX6ULL 内部 aips2 域下的 usbotg1外设控制器,寄存器起始地址为0x02184000,大小为 0x4000。
(5)I.MX6ULL 内部 aips3 域下的 rngb 外设控制器,寄存器起始地址为 0x02284000,大小为 0x4000。
2.设备树实现
/{
compatible = "fsl,imx6ull-ht", "fsl,imx6ull";
#描述cpu
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0:cpu@0 {
compatible = "arm,cortex-a7"; #兼容架构
device_type = "cpu";
reg = <0>;
};
}
#描述SOC内部外设
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus"
ranges;
ocram: sram@00900000 {
compatible = "fsl,lpm-sram";
reg = <0x00900000 0x20000>;
};
aips1: aips-bus@02000000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x100000>;
ranges;
ecspi1: ecspi@0200800 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
reg = <0x02008000 0x4000>;
status = "disabled";
}
}
aips2: aips-bus@02100000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02100000 0x100000>;
ranges;
usbotg1: usb@02184000 {
compatible = "fsl,imx6ul-usb", "fsl,imx27-usb";
reg = <0x02184000 0x200>;
status = "disabled";
}
}
aips3: aips-bus@02200000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02200000 0x100000>;
ranges;
rngb: rngb@02284000 {
compatible = "fsl,imx6sl-rng", "fsl,imx-rng", "imxrng";
reg = <0x02284000 0x4000>;
}
}
}
}
3.设备树在根文件系统中的体现
/proc/device-tree/和/sys/firmware/devicetree/base/ 目录下体现了根节点的所有属性和子节点。
4.设备树下特殊节点aliases\chosen解析
- aliases
给其他节点起一个别名,用于访问。
- chosen
该节点主要是为了uboot向linux内核传递数据,重点是bootargs参数
三、设备树中常用的OF操作函数
1.查找节点的of函数
(1) 通过节点名字查找指定的节点
struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
/*
* from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
* name:要查找的节点名字。
* 返回值:找到的节点,如果为 NULL表示查找失败。
*/
(2) 通过device_type属性查找指定的节点
struct device_node *of_find_node_by_type(struct device_node *from,
const char *type)
/*
* from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
* type:要查找的节点对应的 type 字符串,也就是 device_type 属性值。
* 返回值:找到的节点,如果为 NULL表示查找失败。
*/
(3) 通过device_type和compatible这两个属性查找指定的节点
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type,
const char *compatible)
/*
* from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
* type:要查找的节点对应的 type 字符串,也就是 device_type 属性值,可以为 NULL,表示
* 忽略掉 device_type 属性。
* compatible: 要查找的节点所对应的 compatible 属性列表。
* 返回值: 找到的节点,如果为 NULL 表示查找失败
*/
(4) 通过 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)
/*
* from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
* matches:of_device_id 匹配表,也就是在此匹配表里面查找节点。
* match:找到的匹配的 of_device_id。
* 返回值:找到的节点,如果为 NULL 表示查找失败
*/
(5) 通过路径来查找指定的节点
inline struct device_node *of_find_node_by_path(const char *path)
/*
* path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是backlight这个节点的路径。
* 返回值:找到的节点如果为NULL表示查找失败。
*/
2.查找父/子节点的of函数
(1) 查找父节点
struct device_node *of_get_parent(const struct device_node *node)
/*
* node:要查找的父节点的节点。
* 返回值:找到的父节点。
*/
(2) 查子父节点
struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev)
/*
* node:父节点。
* prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
* 返回值: 找到的下一个子节点。
*/
3.提取属性值的of函数
(1) 根据节点属性名字查找
property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
(2) 获取属性中元素的数量
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname,
int elem_size)
(3) 从属性中获取指定标号的 u32 类型数据值
int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index,
u32 *out_value)
(4) 这4个函数分别是读取属性中 u8、 u16、 u32 和 u64 类型的数组数据
int of_property_read_u8_array(const struct device_node *np,
const char *propname,
u8 *out_values,
size_t sz)
int of_property_read_u16_array(const struct device_node *np,
const char *propname,
u16 *out_values,
size_t sz)
int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz)
int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values,
size_t sz)
(5) 有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性。
int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value)
int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value)
int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
int of_property_read_u64(const struct device_node *np,
const char *propname,
u64 *out_value)
(6) 读取属性中字符串值
int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string)
(7) 获取#address-cells属性值
int of_n_addr_cells(struct device_node *np)
(8) 获取获取#size-cells属性值
int of_n_size_cells(struct device_node *np)
4.其他常用的OF函数
(1)查看节点的compatible属性是否有包含compat指定的字符串,也就是检查设备节点的兼容性
int of_device_is_compatible(const struct device_node *device,
const char *compat)
(2)获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值
const __be32 *of_get_address(struct device_node *dev,
int index,
u64 *size,
unsigned int *flags)
(3) 将从设备树读取到的地址转换为物理地址
u64 of_translate_address(struct device_node *dev,
const __be32 *in_addr)
(4) inux内核使用 resource结构体来描述一段内存空间,本质上就是提取reg属性值,然后将其转换为 resource 结构体类型。
int of_address_to_resource(struct device_node *dev,
int index,
struct resource *r)
(5) of_iomap 函数用于直接内存映射
void __iomem *of_iomap(struct device_node *np,
int index)
四、设备树反编译及其在文件系统中的体现
1. 设备树的反汇编
#在linux内核源码中
./scripts/dtc/dtc -I dtb -O dts -o output.dts arch/arm64/boot/dts/xxx.dtb
生成的output.dts就是设备树
当然也可以重新编译成dtb
./scripts/dtc/dtc -I dts -O dtb -o xxx.dtb output.dts
2. 删除设备树属性的方式(不改变源文件)
1 / {
2 ... ...
3 demo1: demo1 {
4 compatible = "demo1";
5 property1 = <1>;
6 property2;
7 property3 = <2>;
8 property4;
9 };
10 };
11
12 &demo1 {
13 /delete-property/property2;
14 /delete-property/property3;
15 };
3. 设备树在文件系统的体现
(1) /sys/firmware/fdt:原始dtb文件
(2) /sys/firmware/devicetree:以目录结构程现的dtb文件,根节点对应base目录,每一个节点对应一个目录, 每一个属性对应一个文件。
(3) /sys/devices/platform:系统中所有的platform_device,有来自设备树的,也有来有.c文件中注册的对于来自设备树的platform_device,可以进入/sys/devices/platform/<设备名>/of_node查看它的设备树属性。
(4) /proc/device-tree 是链接文件,指向 /sys/firmware/devicetree/base。