设备树入门

1、设备树基本知识

1.1 什么是设备树

设备树就是用来描述硬件资源的文本文件。该文件的组织结构(语法结构)类似于一棵倒树形结构。

1.2 为什么会出现设备树

Linux内核中大量的驱动程序使用总线驱动模型,即总线、设备、设备驱动。设备(device)专门用来描述设备所占有的资源信息, 设备驱动(driver)专门用于描述驱动逻辑,驱动逻辑中需要使用资源信息时动态去device中取,从而实现了软硬件的分离。

一般device 部分的代码会放在内核源码arch/arm/plat-xxx和arch/arm/mach-xxx等板级支持包文件中。但是随着Linux内核支持的硬件(开发板)越来越多,内核中用于描述硬件的代码也就越来越多,这使得内核维护者在发布一个新内核时有大量的工作要做,而这些代码对内核本身又没有帮助(垃圾代码)。这就有了Linus Torvalds的 “this whole ARM thing is a f*cking pain in the ass”。 为了解决这个问题,设备树被引入进来用于描述硬件信息,替换掉device部分的代码。设备树文件被编译成二级制文件,系统启动时通过bootloader传递给内核。

1.3 dts文件、dtb文件是什么

名词全称说明
DTDevice Tree设备树
FDTFlattened Device Tree开发设备树,起源于OpenFirmware(OF)
dtsdevice tree source设备树源码
dtsidevice tree source include通用设备树源码
dtbdevice tree blob编译设备树源码得到的文件
dtcdevice tree compiler设备树编译器

1683267988544

dts源码: arch/arm/boot/dts/

DTC源码: scripts/dtc/

2、设备树语法及编译设备树

2.1 如何编译设备树

语法格式:

dtc -I dts -O dtb  -o xxx.dtb xxx.dts  #编译设备树
dtc -I dtb -O dts  -o xxx.dts xxx.dtb  #反编译设备树

举例:

scripts/dtc/dtc -I dts -O dtb -o my.dtb  arch/arm/boot/dts/zynq-zed.dts
scripts/dtc/dtc -I dtb -O dts -o my.dts   my.dtb

make dtbs

2.2 设备树的语法

官方文档: https://www.devicetree.org/releases/

1683272268370

2.2.1 根节点

根节点是设备树中必须包含的节点。根节点的名字是 / 。

/dts-v1/;    //表示dts文件的版本
/{  //根节点

};

2.2.2 子节点

语法格式:

[label:] node-name[@unit-address]{
	[properties definitions];
	[child nodes];
};

例如:

/dts-v1/;
/{
	node1{
		child_node1{
		};
	};
	node2{
	};
};

注意:同级节点下节点名称不能相同。

节点名称

在对节点命名时一般要体现设备的类型,比如网卡一般命名为ethernet,串口命名为uart,一般遵循下面的命名格式:

[标签:] <名称>[@设备地址]
/dts-v1/;

/{
	node1{
		child_node1{
		};
	};
	node2{
	};
	uart:serial@20230505{
	
	};
};

2.2.3 节点基本属性

reg属性

reg属性可以用来描述地址信息。比如寄存器地址。语法格式如下:

reg = <address1  length1  address2 length2 ....>

#address-cells和size-cells属性

#address-cells和size-cells用来描述子节点中的reg信息中的地址和长度信息

node1{
	#address-cells = <1>;
	#size-cells = <0>;
	node1-child{
		reg = <0>;   //0为地址
	};
};

model属性

model属性的值是一个字符串,一般model描述一些信息。比如设备的名称等。

model = "my board";
/dts-v1/;

/{
	model = "xxx board device tree";
	node1{
		child_node1{
		};
	};
	node2{
	};
	uart:serial@20230505{
	
	};
};

status属性

status属性和设备的状态有关系,status属性的值是字符串,有以下几个可选状态:

属性值描述
okay设备是可用状态
disable设备是不可用状态
fail设备是不可用状态并且设备检测到了错误
fail-sss设备是不可用状态并且设备检测到了错误,sss是错误信息

compatible属性

compatible用于和driver进行匹配,匹配成功会调用driver中的probe函数。

compatible="mybuttons", "buttons"; //匹配时先用第一个进行匹配,不成功再使用第二个值进行匹配
/dts-v1/;

/{
	model = "xxx board device tree";
	#address-cells = <2>;
	#size-cells = <2>;
	node1{
		#address-cells = <1>;
		#size-cells = <1>;
		child_node1{
			reg = <0 0>;
		};
	};
	node2{
	};
	uart:serial@20230505{
		reg = <20230505 1
			   20220808 8>;
		status = "okay";
		compatible = "uart", "serial";
	};
};

2.2.4 特殊节点

特殊节点aliases:

特殊节点aliases用来定义别名。定义别名的目的就是为了方便引用节点。当然,除了使用aliases来命名别名,还可以在对节点命名时添加标签来命名别名。

aliases{
	mmc0 = &sdmmc0;
	mmc1 = &sdmmc1;
	mmc2 = &sdhci;
	serial0 = "/serial@20230505";
};
/dts-v1/;

/{
	model = "xxx board device tree";
	#address-cells = <2>;
	#size-cells = <2>;
	aliases{
		serial0="/node1";
		serial1=&uart;

	};
	node1{
		#address-cells = <1>;
		#size-cells = <1>;
		child_node1{
			reg = <0 0>;
		};
	};
	node2{
	};
	uart:serial@20230505{
		reg = <20230505 1
			   20220808 8>;
		status = "okay";
		compatible = "uart", "serial";
	};
};

chosen节点:

指定uboot给内核传递的参数。注意,chosen节点必须是根节点的子节点。

chosen{
	bootargs = "root=/dev/nfs rw nfsroot=192.168.1.3 console=ttyS0, 115200";
};

2.2.5特殊属性

device_type属性

device_type属性的值是字符串,只能用于对cpu或者memory节点的描述。

/dts-v1/;

/{
	model = "xxx board device tree";
	#address-cells = <1>;
	#size-cells = <1>;
	aliases{
		serial0="/node1";
		serial1=&uart;

	};
	memory@300000{
		device_type="memory";
		reg =<300000 100000>;
	};
	cpu1:cpu@1{
		device_type = "cpu1";
		compatible = "arm, cortex-a53";
	};
	chosen{
		bootargs = "root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs";
	};
	node1{
		#address-cells = <1>;
		#size-cells = <1>;
		child_node1{
			reg = <0 0>;
		};
	};
	node2{
	};
	uart:serial@20230505{
		reg = <20230505 1>;
		status = "okay";
		compatible = "uart", "serial";
	};
};

2.2.6自定义属性

允许自定义属性。

自定义一个管脚编号的属性pinnum;

pinnum = <0 1 2 3 4>;

/dts-v1/;

/{
	model = "xxx board device tree";
	#address-cells = <1>;
	#size-cells = <1>;
	aliases{
		serial0="/node1";
		serial1=&uart;

	};
	memory@300000{
		device_type="memory";
		reg =<300000 100000>;
	};
	cpu1:cpu@1{
		device_type = "cpu1";
		compatible = "arm, cortex-a53";
	};
	chosen{
		bootargs = "root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs";
	};
	node1{
		#address-cells = <1>;
		#size-cells = <1>;
		child_node1{
			reg = <0 0>;
		};
	};
	node2{
		pinnum=<0 1 2 3 4>;
	};
	uart:serial@20230505{
		reg = <20230505 1>;
		status = "okay";
		compatible = "uart", "serial";
	};
};

3、实例分析

3.1 中断

3.2 时钟

3.3 CPU

3.4 GPIO

4、设备树DTB格式及内核的使用

/dts-v1/
/{
	model1 = "this is my devicetree";
	#address-cells = <1>;
	#size-cells = <1>;
	chosen{
		bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
	};
	cpu1:cpu@1{
		device_type = "cpu";
		compatible = "arm,cortex-a35", "arm,armv8";
		reg = <0x0 0x01>;
	};
	aliases{
		led1 = "/gpio@22030101";
	};
	node1{
		#address-cells = <1>;
		#size-cells = <1>;
		gpio@22030102{
			reg = <0x20220102 0x40>;
		};
	};
	node2{
		node1-child{
			pinnum = <0 1 2 3 4>;
		};
	};
	gpio@22030101{
		compatible = "led";
		reg =<0x20230101 0x40>;
		status = "okay";
	};
};
dtc -I dts -O dtb -o test.dtb test.dts
hexdump test.dtb -C | less

4.1 设备树DTB文件的格式

DTB文件格式主要分为四个部分:small header(头部),memory reservation block(内存预留块),structure block(结构块),strings block(字符串块)。free space(自由空间)不一定存在。

1683598754767

4.1.1 header头部

1683600865666

注意: 所有成员类型均为u32, 大端模式。

totalsize

表示dtb文件的大小

off_dt_struct

表示struct block(结构块)在dtb文件中的偏移地址。

size_dt_struct

表示struct block(结构块)的大小。

off_dt_strings

表示string block(字符串块)在dtb文件中的偏移地址。

size_dt_strings

表示string block(字符串块)的大小

off_mem_rsvmap

表示memory reservation block(内存预留块)在dtb文件中的偏移

version

版本号

last_comp_version

表示向后兼容的版本。

4.1.2 内存保留块

如果在dts文件中使用memreserve描述保留的内存,保留的内存大小就会在这部分保存。

1683617062440

4.1.3 结构块

结构块描述的是设备树的结构,也就是设备树的节点。使用0x00000001表示节点的开始,然后跟上节点的名字(根节点的名字用0表示),使用0x00000003表示 一个属性的开始,属性的名字和值使用结构体表示。

1683617475335

len表示属性值的长度,nameoff表示属性名字在字符串块中的偏移。使用0x00000002表示节点的结束。使用0x00000009表示根节点的结束。也就是整个结构块的结束。

4.1.4 字符串块

字符串块用来存放属性的名字,比如compatible,reg等。通过分析dtb的头部,我们已经知道了字符串块的位置。

4.2 uboot如何把设备树传递给内核

uboot将dtb文件的地址放在了X0寄存器中传递给内核。

1683617991649

4.3 内核是如何把设备树展开成device_node

内核会将dtb形式的设备树展开为device_node。

struct device_node {
	const char *name;
	const char *type;
	phandle phandle;
	const char *full_name;

	struct	property *properties; /*节点属性 (链式)*/
	struct	property *deadprops;	/* removed properties */
	struct	device_node *parent;
	struct	device_node *child;
	struct	device_node *sibling; //兄弟节点
	struct	device_node *next;	/* next device of same type */
	struct	device_node *allnext;	/* next in list of all nodes */
	struct	proc_dir_entry *pde;	/* this node's proc directory */
	struct	kref kref;
	unsigned long _flags;
	void	*data;
};
struct property {
	char	*name;
	int	length;
	void	*value;
	struct property *next;
	unsigned long _flags;
	unsigned int unique_id;
};

1683618622496

源码分析:

asmlinkage void __init start_kernel(void){
    setup_arch(&command_line);{
        void __init setup_arch(char **cmdline_p){
            //__fdt_pointer ,dtb位于内存中的地址
            //在head.S中, mov	x21, x0	// x21=FDT
            setup_machine_fdt(__fdt_pointer);{
                
            }
            *cmdline_p = boot_command_line; //记录了uboot传递给内核的参数

        }
    }
}

1683622772071

4.4 device_node树是如何转换成platform_device

平台设备(platform_device)在驱动程序中用于描述硬件使用的资源信息,所以内核最终将内核认识的device_node树转换成了platform_device。可以通过/sys/bus/platform/devices查看。

注意,并不是所有的节点都会被转换为platform_device, 被转换的节点需要满足以下规则:

  • 根节点下包含compatible属性的子节点

  • 节点中compatible属性包含simple-bus, simple-mfd, isa其中之一的节点下包含compatible属性的子节点

    1683623440104

    ​ 上例中node1和gpio都会被转换为plaltform_device。

    • 如果节点compatible属性包含arm,primecell值,不会被转换为platform_device,而是转换为amba设备。

      1683623715525

int of_platform_populate(struct device_node *root,
			const struct of_device_id *matches,const struct of_dev_auxdata *lookup,
			struct device *parent){
    of_platform_bus_create(child, matches, lookup, parent, true){
        	if (of_device_is_compatible(bus, "arm,primecell")) {
                /*
                 * Don't return an error here to keep compatibility with older
                 * device tree files.
                 */
                of_amba_device_create(bus, bus_id, platform_data, parent);
                return 0;
            }
        	of_platform_device_create_pdata(bus, bus_id, platform_data, parent);{
                
            }
    }
}

注意:device和driver匹配时, 如果使用了设备树匹配函数为of_match_table

5、设备树中的of操作函数

Linux系统中使用device_node结构体来描述一个节点,定义在linux/include/of.h文件中。

5.1 获取节点

struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
    from,从哪个节点开始搜索
struct device_node *of_find_node_by_path(const char *path)
    path, 从根节点开始的全路径
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)
    prev,为NULL 则从第一个子节点开始查找
struct device_node *of_find_compatible_node(struct device_node *from,
									const char *type, const char *compatible)
    type, 要查找节点的device_type属性, 为NULL则忽略该属性
    compatible,要查找节点的compatible属性列表
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)
    

5.2 获取属性

Linux中使用呢property结构体来描述属性

struct property {
	char	*name;
	int	length;
	void	*value;
	struct property *next;
	unsigned long _flags;
	unsigned int unique_id;
};
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
    lenp,获取到的属性值的字节数
int of_property_read_u32_index(const struct device_node *np,
				       		const char *propname,
				       		u32 index, u32 *out_value)
int of_property_read_u32_array(const struct device_node *np,
			       const char *propname, u32 *out_values,
			       size_t sz)
int of_property_read_string(struct device_node *np, const char *propname,
				const char **out_string)

以上功能如果需要获取IORESOURCE_IRQ或者IORESOURCE_MEM类型资源时也可以考虑使用早期的

platform_get_resource

5.3 获取中断号

unsigned int irq_of_parse_and_map(struct device_node *node, int index)
static inline u32 irqd_get_trigger_type(struct irq_data *d)

注意:对于自定义属性一种方法是研究驱动源码

​ 另外一种方法就是内核源码Documentation/devicetree/bindings/目录下有相应的帮助文档

propname,
u32 index, u32 *out_value)
int of_property_read_u32_array(const struct device_node *np,
const char *propname, u32 *out_values,
size_t sz)
int of_property_read_string(struct device_node *np, const char *propname,
const char **out_string)


以上功能如果需要获取IORESOURCE_IRQ或者IORESOURCE_MEM类型资源时也可以考虑使用早期的

```c
platform_get_resource

5.3 获取中断号

unsigned int irq_of_parse_and_map(struct device_node *node, int index)
static inline u32 irqd_get_trigger_type(struct irq_data *d)

注意:对于自定义属性一种方法是研究驱动源码

​ 另外一种方法就是内核源码Documentation/devicetree/bindings/目录下有相应的帮助文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值