Linux设备树的起源
arch/arm/plat-xxx和arch/arm/mach-xxx中存在大量的板级信息,注册platform_device,绑定resource等,随着时间的推移,越来越多的板级信息加入,影响维护,这些具体的硬件信息应该和内核代码解耦。有了Device Tree后,大量的板级信息不再需要,都可以通过Device Tree api来做处理。Device Tree相当于一个硬件配置文件,kernel读取这个配置文件做对应的驱动初始化相关工作
DTS (device tree source)
内核目录
arch/arm/boot/dts/
arch/arm64/boot/dts/
.dts文件是Device Tree源文件,相当于配置文件,一个.dts文件对应一个具体的板级信息,公用的一部分提炼为.dtsi, 类似于C语言的头文件.
/dts-v1/;
#include "rk3328.dtsi"
DTC (device tree compiler)
将.dts编译为.dtb的工具.
.dtb是.dts被DTC编译后的二进制格式的Device Tree描述,可由uboot和Linux内核解析。
Device Tree组成和结构
Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。
- Node节点。在DTS中使用一对花括号”node-name{}”来定义;
- Property属性。在Node中使用”property-name=value”字符串来定义;
Property 常见值类型:
int
u32
u64
string
phandle(int)
compatible
“compatible”属性用来device和driver的适配,
推荐的格式为”manufacturer,model”,
fsl,mpc8641为特指的设备, ns16550类型更广泛
compatible = "fsl,mpc8641", "ns16550";
phandle
“phandle”属性通用一个唯一的id来标识一个Node,在property可以使用这个id来引用Node
pic@10000000 {
// 将处理值定义为 1
phandle = <1>;
interrupt-controller;
};
another-device-node {
// 引用phandle值为1的pic节点
interrupt-parent = <1>;
};
定义一个“label:”来引用Node,在编译是系统会自动为node生成一个phandle属性。”backlight”是一个label,用来引用node”backlight”:
backlight: backlight {
compatible = "pwm-backlight";
enable-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_HIGH>;
pwms = <&pwm0 0 25000 0>;
}
使用”&”来引用“label”,即是引用phandle
edp-panel {
compatible = "boe,nv101wxmn51", "simple-panel";
backlight = <&backlight>;
power-supply = <&pp3300_disp>;
}
#address-cells 、 #size-cells
“#address-cells, #size-cells”属性用来定义当前node的子node中”reg”属性的解析格式。
- address-cells, 用几个数描述地址
- size-cells, 用几个数描述地址长度
soc {
// 1个值表示地址
#address-cells = <1>;
// 1个值表示长度
#size-cells = <1>;
serial {
compatible = "ns16550";
reg = <0x4600 0x100>;
clock-frequency = <0>;
interrupts = <0xA 0x8>;
interrupt-parent = <&ipic>;
};
};
reg
“reg”属性解析出”address,length”数字,解析格式依据父节点的”#address-cells、#size-cells”定义
soc {
// 1个值表示地址
#address-cells = <1>;
// 1个值表示长度
#size-cells = <1>;
serial {
compatible = "ns16550";
reg = <0x4600 0x100>;
clock-frequency = <0>;
interrupts = <0xA 0x8>;
interrupt-parent = <&ipic>;
};
};
0x4600表示对应的基地址,0x100表示地址对应的大小为0x100
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
};
};
address-cells = <1>;size-cells = <0>;58表示i2c对应从设备地址,映射长度为0
aliases 节点
节点的别名
aliases {
ethernet0 = &gmac;
i2c0 = &i2c0;
serial0 = &uart0;
spi0 = &spi0;
};
memory 节点
用来传递内存布局
memory {
device_type = "memory";
reg = <0x0 0x0 0x0 0x40000000>;
};
chosen 节点
- “bootargs”属性用来传递cmdline参数
- “stdout-path”属性用来指定标准输出设备
- “stdin-path”属性用来指定标准输入设备
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
常用OF API
判断设备结点的compatible属性是否包含compat指定的字符串
int of_device_is_compatible(const struct device_node *device,const char *compat);
根据compatible属性,获得设备结点。遍历Device Tree中所有的设备结点,看看哪个结点的类型、compatible属性与本函数的输入参数匹配,大多数情况下,from、type为NULL。
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compatible);
读取设备结点np的属性名为propname,类型为8、16、32、64位整型数组的属性
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(const struct device_node *np,
const char *propname, u64 *out_value);
读取字符串属性
int of_property_read_string(struct device_node *np, const char *propname,
const char **out_string);
读取字符串数组属性中的第index个字符串
int of_property_read_string_index(struct device_node *np,
const char *propname, int index, const char **output);
如果设备结点np含有propname属性,则返回true,否则返回false。一般用于检查空属性是否存在
static inline bool of_property_read_bool(const struct device_node *np,
const char *propname);
博客推荐
http://kernel.meizu.com/device-tree.html
https://blog.csdn.net/21cnbao/article/details/8457546
https://elinux.org/Device_Tree_Usage