linux 设备树学习

是什么


设备树由一系列被命名的节点(node)和属性(property)组成,节点本身包含子节点,属性是成对出现的 < name, value > 。

1.1 帮助文件

Documentation/devicetree/binding/arm/gic.text

1.2 后缀

  • dts,设备树的源码文件,是ASCII文本格式,位置在内核源码目录下的 arch/arm/boot/dts
  • dtsi,设备树中公共部分放到了.dtsi文件中,类似c的头文件 dtb,二进制文件,板子要用的文件,编译后的文件。
  • dtc,编译设备树的编译器,在linux内核源码的 scripts\dtc 下。生成这个工具的方法是在内核配置的时候配置上 CONFIG_OF

1.3 单独编译设备树

内核源码目录下 make dtbs
使用 dtc 工具
./dtc -I dts -O dtb -o xxx.dtb xxx.dts
也可以用 dtc 工具把 dtb 文件编译成 dts 文件
./dtc -I dtb -O dts -o xxx.dts xxx.dtb

dtc [-I input-format] [-O output-format][-o output-filename
    ] [-V output_version] input_filename

参数说明
input-format:
- “dtb”: “blob” format
- “dts”: “source” format.
- “fs” format.
output-format:
- “dtb”: “blob” format
- “dts”: “source” format
-asm: assembly language file

output_version:
定义”blob”的版本,在dtb文件的字段中有表示,支持1 2 316,默认是3,16版本上有许多特性改变

##1.4 设备树在启动过程中的使用
bootloader在引导内核时,会预先读取.dtb到内存,并将设备树在内存中的地址传给内核。在ARM中通过 bootm 或 bootz 命令来进行传递。
bootm [kernel_addr] [initrd_address] [dtb_address]

其中kernel_addr为内核镜像的地址,
initrd为initrd的地址,若initrd_address为空,则用“-”来代替。
dtb_address为dtb所在的地址。

2. 语法


一个 dts 文件有且只有一个root节点 “/” ,root 节点下又可以有一系列的子节点,称root为“node1”和“node2”的parent节点,除了root节点外,每个节点有且仅有一个parent;其中子节点node1下还存在子节点“child-nodel1”和“child-node2”。

注意

dtsi中有一个根节点,dts在包含的话会不会有两个了?
编译器 DTC 在编译 dts 时,会对 node 进行合并操作。最终结果是dtb中有一个根节点。
/{
	node1{
		child-node1{
		};
		child-node2{
		};
	};
	node2{
	};    
}; 
{} 中是描述该节点的属性,它可以是:
1. string
2. u32
3. 二进制数据
4. 也可以是空

2.1 skeleton
首先从 “骨架” 开始 skeleton.dtsi
每个平台的 dtsi 文件都包含这个骨架
/include/ “skeleton.dtsi”
骨架中的内容

/ {
    // 以“/”根节点为parent的子节点中,reg的每个属性中存在一个 address 值
	#address-cells = <1>;
     // ... reg属性中存在一个 size 值. 则 reg 的组织形式是 reg = <addr1 size1 [addr2 size2] ... ]>
	#size-cells = <1>;
	chosen { };
	aliases { };
	memory { device_type = "memory"; reg = <0 0>; };
};

2.1.1 chosen 节点

主要用来描述由系统指定的 runtime parameter,它不用来描述任何硬件设备节点信息。原先通过 tag list 传递的一些 linux kernel 运行的参数,可以通过 chosen 来传递,如 uboot 阶段的命令行中的 bootargs 传递的参数,可以用这个节点来传递。它的父节点必须是 根节点。
例子

chosen {
    bootargs = "tegraid=40.0.0.00.00 vmalloc=256M video=tegrafb console=ttyS0,115200n8 earlyprintk";
}

2.1.2 aliases 节点

aliases 用来定义别名,类似 C++ 的引用。在 dtsi 文件中定义了别名后,在 dts 中,使用别名的效果和引用体是一样的。
例子
在 at91sam9g45.dtsi 文件中,根节点下有
在这里插入图片描述

aliases {
		serial0 = &dbgu;
		serial1 = &usart0;
	...
	};

dtsi 文件中使用的是 dbgu、usart0 …
dts 文件中使用 serial0、serial1 这些别名。

2.1.3 memory 节点

memory { device_type = "memory"; reg = <0 0>; };

device_type 必须是 memory, reg 指定 memory 的起点和大小
例子
usb_a9g20.dts 文件

memory {
	reg = <0x20000000 0x4000000>;
};

memory 的起点是 0x20000000,大小是 0x4000000.

一般而言,在 .dts 中不对 memory 进行描述,而是通过 bootargs 中类似 521M@0x00000000 的方式传递给内核。

2.2 通用属性

设备节点的名称格式: 节点名称@单元地址
节点名称必须唯一,
单元地址可以不描述,具体格式和设备挂载在哪个 bus 上相关。

2.2.1 compatible 属性

这个属性为 string list,用来跟驱动 匹配。优先级从左到右。
/ 节点也有这个属性,用来匹配 machine type。
compatible = "厂商,外设名字"

2.2.2 interrupts 属性

设备节点通过 interrupt-parent 来指定它所依附的中断控制器,当节点没有指定 interrupt-parent 时,则从 parent 节点中继承。
如果子节点使用到中断,则需用 interrupt 属性指定,该属性的数值长度受中断控制器中 #interrupt-cells 指定。当 #interrupt-cells >= 2 时,每个数字表示的意思由驱动决定。
例子
at91sam9g20.dtsi

/ {
	model = "Atmel AT91SAM9G20 family SoC";
	compatible = "atmel,at91sam9g20";
	interrupt-parent = <&aic>;
    ...
    aic: interrupt-controller@fffff000 {
				#interrupt-cells = <2>;
				compatible = "atmel,at91rm9200-aic";
				interrupt-controller;
				reg = <0xfffff000 0x200>;
	};
    usart0: serial@fffb0000 {
    	...
		interrupts = <6 4>;
	}

根节点指定中断控制器,
aic 中 interrupt-controller 指定了这个节点是中断控制器
中断控制器中指定了每个中断单元是 2 个元素。
usart0 中 interrupts 传递了一个中断 <6 4>
当两个中断 cell 时,一般第一个cell表示中断线的序号,第二个cell表示中断类型的 flag,对于不同的中断控制器,需要阅读对应的 binding document 来得知其说明符的格式。

2.2.3 ranges 属性

地址转换表,这在 pcie 中使用比较常见。它表明该设备在 parent 节点中所对应的地址映射关系。ranges格式长度受当前节点 #address-cell、parent 节点的 #address-cell 和 #size-cells 所控制。格式如下
ranges = <当前节点#address-cell, parent 节点的 #address-cell, 当前节点#size-cells>
例子

rwid-axi {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "simple-bus";
		ranges;

		ebi@50000000 {
			compatible = "simple-bus";
			#address-cells = <2>;
			#size-cells = <1>;
			ranges = <0 0 0x40000000 0x08000000
				  1 0 0x48000000 0x08000000
				  2 0 0x50000000 0x08000000
				  3 0 0x58000000 0x08000000>;
		};

可以看到 ebi 的父节点中指定了 1 个地址单元
ebi 的节点中是 2 个地址单元
ebi 中的 size 单元的个数是 1 个

0 0 0x40000000 0x08000000 表示
0 0 是该设备地址, 0x40000000 是对应 parent 节点上的地址,size 是
0x08000000

2.2.4 合并原则

dtc在编译设备树时会将相同名称的节点进行合并,以最后的属性为准。

3. 设备树(DTB)的组成


DTB(DT block) 由 3 部分组成:

头(header)结构块(device-tree structure)字符串块(string block)

3.1 头

struct boot_param_header {
	__be32	magic;			/* 设备树的幻数,固定为 0xd00dfeed */
	__be32	totalsize;		/* 整个DT block的大小 */
	__be32	off_dt_struct;		/* 结构块 在这个设备树中的偏移 */
	__be32	off_dt_strings;		/* 字符串块 的偏移 */
	__be32	off_mem_rsvmap;		/* 保留内存区的偏移,该区域不能被内核动态分配 */
	__be32	version;		/* 版本 */
	__be32	last_comp_version;	/* 向下兼容的版本号 */
	/* version 2 fields below */
	__be32	boot_cpuid_phys;	/* 正在运行的物理 CPU 的id */
	/* version 3 fields below */
	__be32	dt_strings_size;	/* 字符串块 大小 */
	/* version 17 fields below */
	__be32	dt_struct_size;		/* 结构块 大小 */
};

3.2 结构块

设备树的结构块是一个线性化的结构体,是设备树的主体。
一个节点的组成

OF_DT_BEGIN_NODE节点的开始的标志
节点的路径 / 节点的单元名(version<3以节点路径表示,version>=0x10以节点单元名表示)
四字节对齐
OF_DT_PROP节点属性的开始标志
属性的字节长度
属性名称在字符串中的偏移量
属性值+4字节对齐
子节点
OF_DT_END_NODE节点结束的标志

3.3 字符串块

不同节点的属性有相同的属性名称,因此,将属性名称提取出了一张表,而在结构块 的属性名字段保存名称在字符串块中的偏移

3.4 memory reserve map

在头中可以看到还有一个 内存保留的 map,这些内存区域被保留用于 ARM 和 DSP 进行信息交互 或做 他用,总之是不会进入内存管理系统。
这个区域包括了若干的reserve memory描述符。没一个描述符是由 address 和 size 组成的,他们的数据类型都是 u64.

4. 设备树相关过程


4.1 旧方式和新方式的比较

在以前的方式中,内核包含了对硬件的全部描述。
uboot 在加载一个二进制的内核镜像(uImage/zImage)时,会提供一些额外信息,成为 ATAGS,通过 r2 寄存器传给内核。ATAGS 中包含 内存大小和地址、命令行 等。
uboot 通过 r1 寄存器传递机器类型(machine type)。
uboot 的命令行中启动内核的命令: bootm <内核镜像的地址>

有了设备树后,内核和硬件描述分离。uboot 需要加载两个二进制文件,一个是内核镜像(uImage/zImage),一个是设备树文件。
uboot 通过 r2 寄存器来传递设备树的地址,r1 寄存器 不在用来存放机器类型信息,设备树中包含了。
uboot 的命令行中启动内核的命令:bootm <kernel img addr> <ramdisk addr> < dtb addr>
在这里插入图片描述
在这里插入图片描述

4.2 内核获取设备树中信息的过程

① kernel 入口处获取到 uboot 传过来的 .dtb 镜像的基地址
② 通过 early_init_dt_scan() 函数来获取 kernel 初始化时需要的 bootargs 和 cmd_line 等系统引导参数。
③ 调用 unflatten_device_tree 函数来解析dtb文件,构建一个由 device_node 结构连接而成的单向链表,并使用全局变量 of_allnodes 保存这个链表的头指针。
④ 内核调用OF的API接口,获取 of_allnodes 链表信息来初始化内核其他子系统、设备等。

5. 设备树相关 API

设备树有关 API 包含在的源文件所在位置: drivers/of/

1of_get_flat_dt_root(void)查找dtb中的根节点
2struct device_node *of_find_node_by_path(const char *path)根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node
3struct device_node *of_find_node_by_name(struct device_node *from,const char *name)若from=NULL,则在全局链表of_allnodes中根据name查找合适的device_node
4struct device_node *of_find_node_by_type(struct device_node *from,const char *type)根据设备类型查找相应的device_node
5struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)根据compatible字符串查找device_node
6struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)根据节点属性的name查找device_node
7struct device_node *of_find_node_by_phandle(phandle handle)根据phandle查找device_node
8int of_alias_get_id(struct device_node *np, const char *stem)根据alias的name获得设备id号
9struct device_node *of_node_get(struct device_node *node)void of_node_put(struct device_node *node) device node计数增加/减少
10struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)根据property结构的name参数,在指定的device node中查找合适的property
11const void *of_get_property(const struct device_node *np, const char *name,int *lenp)根据property结构的name参数,返回该属性的属性值
12int of_device_is_compatible(const struct device_node *device,const char *compat)根据compat参数与device node的compatible匹配,返回匹配度
13struct device_node *of_get_parent(const struct device_node *node)获得父节点的device node
14const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node)将matches数组中of_device_id结构的name和type与device node的compatible和type匹配,返回匹配度最高的of_device_id结构
15int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value)根据属性名propname,读出属性值中的第index个u32数值给out_value
16int 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)根据属性名propname,读出该属性的数组中sz个属性值给out_values
17int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value)根据属性名propname,读出该属性的u64属性值
18int of_property_read_string(struct device_node *np, const char *propname,const char **out_string)根据属性名propname,读出该属性的字符串属性值
19int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output)根据属性名propname,读出该字符串属性值数组中的第index个字符串
20int of_property_count_strings(struct device_node *np, const char *propname)读取属性名propname中,字符串属性值的个数
21unsigned int irq_of_parse_and_map(struct device_node *dev, int index)读取该设备的第index个irq号
22int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)读取该设备的第index个irq号,并填充一个irq资源结构体
23int of_irq_count(struct device_node *dev)获取该设备的irq个数
24int of_address_to_resource(struct device_node *dev, int index,struct resource *r)const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags)获取设备寄存器地址,并填充寄存器资源结构体
25void __iomem *of_iomap(struct device_node *np, int index)获取经过映射的寄存器虚拟地址
26struct platform_device *of_find_device_by_node(struct device_node *np)根据device_node查找返回该设备对应的platform_device结构
27struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id, void *platform_data,struct device *parent)根据device node,bus id以及父节点创建该设备的platform_device结构
28int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent)遍历of_allnodes中的节点挂接到of_platform_bus_type总线上,由于此时of_platform_bus_type总线上还没有驱动,所以此时不进行匹配
设备树(device tree)机制是Linux内核linux-3.x版本开始引进的一种机制,目的是解决内核源码的arch/arm目录下代码混乱的问题:随着ARM生态的快速发展,在内核源码的arch/arm目录下,存放着几十种arm芯片和几百个开发板相关的源文件,很多开发板和处理器的中断、寄存器等相关硬件资源都在这个目录下以.c或.h的文件格式定义。而对于内核来说,与这些硬件耦合,会导致内核代码混乱不堪,每个开发板上运行的内核镜像都必须单独编译配置,无法通用。什么时候Linux内核能像Windows镜像那样,无论你的电脑什么配置,一个Windows安装包,都可以直接下载安装运行呢?设备树机制,实现了Linux内核和硬件平台的解耦:每个硬件平台的硬件资源使用一个设备树文件(xxx.dts)来描述,而不是在arch/arm下以.c 或 .h 文件来定义。Linux内核是一个通用的内核,在启动过程中,在通过解析设备树中的硬件资源来初始化某个具体的平台。 引入设备树后,很多和内核驱动开发的工作也发生了变化:以往驱动工程师关注的头文件宏定义、寄存器定义,现在这些基本上不用关注,关注的重点则转向了如何根据硬件平台去配置和修改设备树文件。很多驱动的编程接口也发生了变化,开始慢慢使用device tree提供的编程接口去开发驱动。本期课程主要面向嵌入式开发人员,分享Linux下驱动开发所需要的设备树知识和必备技能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值