3.3设备树

设备树介绍

总线、设备、驱动框架中实现了设备和驱动的分离,但是依然需要使用C代码来描述设备,而且还体现不出设备之间的层次关系。为了解决这个问题提,Linux引入了设备树,设备树是一种描述设备信息的数据结构(是一种树状的数据结构),它可以被bootloader传递到内核,从而让内核获得相应的硬件信息,在内核的sys/firmware/devicetree/base目录中可以查看设备树的节点信息(包括设备树中的各个节点)

DTS、DTB、DTC之间的关系

设备树源文件后缀为.dts(头文件为.dtsi,.dts文件可以通过 #include 包含.dtsi、.dts、.h文件)、编译后的设备树二进制文件后缀为.dtb、设备树编译器为DTC(内核源码树中一般包含设备树编译器的源码),可以通过命令 make ARCH=arm -j8 CROSS_COMPILE=arm-linux-gnueabihf- dtbs 编译设备树(其本质是通过makefile编译出DTC,然后调用DTC编译设备树),通过命令dtc -I dtb -o dts nameA.dtb > nameB.dts可以对设备树 进行反编译(nameA编译后的设备树二进制文件,nameB反编译后输出的设备树源文件,DTC工具一般位于内核代码的 scripts/dtc/目录中)。

内核对设备树的处理

uboot在启动内核的时候会将编译好的设备树二进制文件传递给Linux内核,内核会把每一个节点都转换为一个device_node对象,内核程序流程如下所示:
在这里插入图片描述

设备树绑定文档

设备树是用来描述板子上的设备信息的,不同的设备其信息不同,反映到设备树中就是属性不同。设备树绑定文档描述了如何在设备树中添加、修改相应设备的设备树节点(相当于设备树的说明文档,在使用这些设备驱动时可以根据绑定文档的描述添加、修改对应的设备树节点),路径为“Linux 源码目/Documentation/devicetree/bindings”。

设备树节点

设备树节点的格式如下:

//在设备树文件中可以使用"//"注释,也可以使用"/**/"注释
//标签:是为了方便在别处引用此节点
//名字:描述设备树节点的名称,一般根据设备取名,跟节点的名字为"/",每个.dts文件必须要有一个跟节点,当多个dts文件相互包含是编译器会将多个跟节点合并。
//地址:一般表示设备地址或寄存器首地址。
//属性:实际上是一组键值对,其格式为键=值。
[标签:] 名字[@地址] { //中括号中的内容可以省略
	//各种属性
};

设备树节点可以嵌套,形成父子节点以及兄弟节点。

设备树中采用的数据类型

  1. 字符串;用双引号括起来的ASCII码,如compatible = "arm,cortex-a7"中的"arm,cortex-a7"就是字符串(其中compatible
    是属性),可以将多个字符串组合成一个字符串列表,如compatible = “st,stm32mp157d-atk”, “st,stm32mp157”。
  2. 32位无符号整数;32位无符号整数采用符号<>表示,如reg = <0>中的0是一个32位无符号整数,可以将多个32位无符号整数组合成一个数组,如reg = <0 0x123456 100>,中间用空格隔开。
  3. 节点引用;一个元素可以引用其他节点,并传递引用参数,如quote = <&quote_node 1 2 3>中quote引用了quote_node并传递了3个参数,一个元素还可以引用多个节点,如quote = <&quote_node1 1 2 3 &quote_node1 1 2 3>

常用的设备树属性

  1. compatible;compatible 属性的值是一个字符串列表,用于匹配设备和驱动。一般驱动程序都包含一个设备树匹配表(struct device_driver 对象的 of_match_table 成员),当 compatible 属性中的某一个字符串与设备树匹配表中某个元素的compatible 成员所指字符串一样则匹配成功(根节点的compatible则是用于平台匹配)。
  2. model;model 属性是一个字符串,一般用于描述设备信息。
  3. status;status 属性是一个字符串,用于表示设备的状态信息。有 “okay”(设备是可操作的)、 “disabled”(设备当前是不可操作,但是在可以变为可操作)、 “fail”(设备不可操作,也不大可能变得可操作)、 “fail-sss”(与 “fail” 相同,sss表示 检测到的错误内容)。
  4. reg;reg属性的值一般是
    对,常用于描述设备地址空间信息。一般格式为reg = <address1 length1 address2 length2 address3 length3 ……>
  5. #address-cells 和 #size-cells;#address-cells 表示 reg 属性中的 address 信息所占字长(32位为一个字,如 #address-cells = 2 则表示每个address 信息由2个32位无符号整形组成), #size-cells 表示 reg 属性中的 length 信息所占字长,为0则表示没有 length 信息
  6. ranges;ranges 属性是按照<子节点地址, 父节点地址, 长度>格式编写的矩阵,它常用于描述子节点地址空间和父节点地址空间的对应关系,可以简化子节点中reg属性的编写,其中子节点地址和父节点地址的字长由父节点的 #address-cells 确定,长度由父节点的 #size-cells确定。
  7. name;name 属性值为字符串, name 属性用于记录节点名字, name 属性已经被弃用。
  8. device_type;device_type 属性值为字符串,用于描述设备的 FCode。

设备树中的特殊节点

  1. aliases节点;aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。
  2. chosen节点;chosen 节点主要引用向操作系统传递参数,其功能类似于 uboot 中的 bootargs,但是 bootargs 优先级更高。

在设备树节点中追加或修改节点内容

方法1:
直接在节点中追加或修改,但是如果此节点的文件被多个平台dts文件包含则相当于修改了多个平台的设备树。
方法2:
在跟节点外引用某个节点,然后通过引用进行追加或修改,如要在 i2c1:i2c@021a0000 节点中增加或修改可以采用如下方式:

&i2c1 {
	/* 要追加或修改的属性 */
};

常用的设备树函数

  1. 查找节点的函数
//通过节点名字查找设备树节点,from表示搜索from节点下的子节点,为NULL表示从跟节点开始搜索
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属性查找设备树节点,compatible属性一般是一个字符串列表,当列表中的某一项与形参compatible匹配成功即可;type表示device_type属性,为NULL表示忽略device_type属性的匹配
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
//通过of_device_id匹配表(设备树匹配表)来查找节点,matches是匹配列表,match返回成功匹配的匹配项
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)
  1. 提取父子节点的函数
//获取父节点
struct device_node *of_get_parent(const struct device_node *node)
//获取子节点
struct device_node *of_get_next_child(const struct device_node *node,  struct device_node *prev);
  1. 提取属性值的函数
//查找指定名字的属性
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp)
//获取数组属性中元素个数,如获取reg数组的元素个数
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);
int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
int of_property_read_u64_index(const struct device_node *np, const char *propname, u32 index, u64 *out_value);
//获取数组属性的值
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)
//获取整形属性的值
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);
//获取字符串属性的值
int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
//获取 #address-cells 属性的值
int of_n_addr_cells(struct device_node *np);
//获取 #size-cells 属性的值
int of_n_size_cells(struct device_node *np);
/* 获取属性引用的节点及传递的值
 17. np 节点
 18. list_name 属性名称
 19. stem_name 被引用的节点中描述在引用此节点时需传递的参数个数的前缀,如gpio节点中的#gpio-cells表示在引用
 20.           gpio节点时需传递的参数个数,这里则应该传递gpio
 21. index 索引
 22. out_args 返回属性引用的节点及传递的参数
 **/
int of_parse_phandle_with_args_map(const struct device_node *np,  const char *list_name, const char *stem_name, int index, struct of_phandle_args *out_args)
  1. 其他常用的函数
//将设备树描述的寄存器资源转换为resource结构体类型
int of_address_to_resource(struct device_node *node, int index, struct resource *r)
//将设备树描述的中断资源转换为resource结构体类型
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)

设备树操作示例

修改设备树

  1. 在顶层设备树根节点下中加入如下节点
//编写一个test_node节点
test_node:dts_test_node@0 {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "atkalpha-test-node";
	status = "disable";
	reg = <0X020C406C 0X04>;
};
//编写一个quote_node
quote_node:quote_node_test@0 {
	#args-cells = <3>;
	compatible = "atkalpha-quote-node";
	status = "disable";
};
  1. 在根节点外修改dts_test_node节点
//修改test_node节点,增加test-quote属性,并引用quote_node节点,修改status 属性的值
&test_node {
	test-quote = <&quote_node 1 2 3 &quote_node 4 5 6>;
	status = "okay";
};

设备树操作程序

下面是一段查找设备树节点,然后读取其属性的内核代码

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>

int __init dts_init(void)
{
	uint32_t i;
	struct device_node *dn;
	struct property *pp;
	const char *str;
	uint32_t reg[2];
	struct resource r;
	struct of_phandle_args phandle_args;
//	struct of_device_id matches[] = [] = {
//		{ .compatible = "atkalpha-test-node" },
//		{ /* 最后一项必须为空 */ }
//	};

//	//通过设备树匹配表查找设备树匹配表
//	dn = of_find_matching_node_and_match(NULL, matches, NULL)
//	//通过节点名称查找设备树节点
//	dn = of_find_node_by_name(NULL, "dts_test_node");
	//通过compatible属性查找设备树节点
	dn = of_find_compatible_node (NULL, NULL, "atkalpha-test-node");
	if(dn == NULL)
	{
		printk("no find device node\r\n");
		return -EIO;
	}

	//获取#address-cells属性的值
	printk("#address-cells = %d\r\n", of_n_addr_cells(dn));
	//获取#size-cells属性的值
	printk("#size-cells = %d\r\n", of_n_size_cells(dn));

	//查找compatible属性
	pp = of_find_property(dn, "compatible", NULL);
	if(pp)
	{
		printk("compatible name = %s\r\n", pp->name);
		printk("compatible length = %d\r\n", pp->length);
		printk("compatible value = %s\r\n", (char*)pp->value);
	}

	//获取属性中字符串的值
	if(of_property_read_string(dn, "status", &str) == 0)
		printk("status = %s\r\n", str);

	//获取数组中元素个数
	printk("reg elems size = %d\r\n", of_property_count_elems_of_size(dn, "reg", sizeof(uint32_t)));

	//获取属性中的第0个u32数据
	if(of_property_read_u32_index(dn, "reg", 0, reg) == 0)
		printk("reg[0] = 0x%x\r\n", reg[0]);

	//获取属性中的u32数组
	if(of_property_read_u32_array(dn, "reg", reg, 2) == 0)
		printk("reg[0] = 0x%x, reg[1] = 0x%x\r\n", reg[0], reg[1]);
	
	//将reg属性转换为resource结构体类型
	if(of_address_to_resource(dn, 0, &r) == 0)
		printk("start = 0x%x, size = 0x%x\r\n", r.start, resource_size(&r));
	
	//获取quote属性所引用的节点
	//args对应引用节点中的#args-cells属性,#args-cells属性表示引用节点时传入的参数个数
	if(of_parse_phandle_with_args_map(dn, "test-quote", "args", 0, &phandle_args) == 0)
	{
		if(of_property_read_string(phandle_args.np, "compatible", &str) == 0)
			printk("quote compatible = %s\r\n", str);
		printk("args_count = %d\r\n", phandle_args.args_count);
		for(i=0; i<phandle_args.args_count; i++)
			printk("args[%d]= %d\r\n", i, phandle_args.args[i]);
	}

	return 0;
}

void __exit dts_exit(void)
{

}

module_init(dts_init);
module_exit(dts_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_ALIAS("dtest");
MODULE_DESCRIPTION("dts test");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值