【Linux驱动开发】Linux设备树详解

目录

一、设备树基础

1、概念

 2、文件格式

3、编译工具

二、DTS语法

1、.dtsi 头文件

2、 设备节点

3、标准属性

4、compatible 属性详解

5、修改设备树文件,增加或修改节点 

三、设备树在系统中的体现

四、Linux 内核解析 DTB 文件流程

五、绑定信息文档

六、设备树常用 OF 操作函数

(1)查找节点的 OF 函数

(2)查找父/子节点的 OF 函数

(3)提取属性值的 OF 函数

(4)其他常用的 OF 函数


一、设备树基础

1、概念

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如 CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等。如图所示。

树的主干就是系统总线,IIC 控制器、GPIO 控制器、SPI 控制器等都是接 到系统主线上的分支。IIC 控制器有分为 IIC1 IIC2 两种,其中 IIC1 上接了 FT5206 AT24C02 这两个 IIC 设备,IIC2 上只接了 MPU6050 这个设备。DTS 文件的主要功能就是按图所示的结构来描述板子上的设备信息。

 2、文件格式

DTS(.dts)设备树源文件(描述板级信息:开发板上有哪些 IIC 设备、SPI 设备等
DTB(.dtb)设备树编译文件
.dtsi设备树头文件(描述SOC级信息:CPU 架构、主频、外设寄存器地址范围等)

设备树相关文件均在 arch/arm/boot/dts/ 文件夹,如图: 

3、编译工具

DTC将.dts 编译为.dtb

DTC 工具源码在 Linux 内核的 scripts/dtc 目录下。

DTC 工具依赖于 dtc.c、flattree.cfstree.c 等文件,最终编译并链接出 DTC 这个主机文件。

在 arch/arm/boot/dts/Makefile 中新增需要编译的DTS文件。

如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命令:

make all

编译 Linux 源码中的所有东西,包括 zImage.ko 驱动模块以及设备 树

make dtbs

编译设备树

二、DTS语法

1、.dtsi 头文件

.dts 设备树文件中,可以通过“#include ”来引用 .h .dtsi .dts 文件。
.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART IIC 等等。

2、 设备节点

设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是 键值对。每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。

imx6ull.dtsi 文件节选设备树文件内容:

/ { 
    aliases { 
        can0 = &flexcan1; 
    };

    cpus { 
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
        compatible = "arm,cortex-a7";
        device_type = "cpu";
        reg = <0>;
        };
    };

    intc: interrupt-controller@00a01000 {
        compatible = "arm,cortex-a7-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0x00a01000 0x1000>,
        <0x00a02000 0x100>;
    };
}

/”是根节点,每个设备树文件只有一个根节点。

(1)设备树中节点命名格式

节点标签:节点名@设备的地址或寄存器首地址

label: node-name@unit-address ​​​

例如:cpu0:cpu@0 

(2)设备树源码中常用的几种数据形式如下所示:

①字符串

compatible ="arm,cortex-a7";

上述代码设置 compatible 属性的值为字符串“arm,cortex-a7”。

②32 位无符号整数

reg =<0>;

上述代码设置 reg 属性的值为 0reg 的值也可以设置为一组值,比如: reg =<0 0x123456 100>;

③字符串列表

属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:

compatible ="fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";

上述代码设置属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。

3、标准属性

节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux 下的很多外设驱动都会使用这些标准属性。

compatible 属性
compatible 属性也叫做“兼容性”属性。 用于将设备和驱动绑定起来。
值是一个字符串列表,用于选择设备所要使用的驱动程序。
model 属性
值是字符串,一般描述设备模块信息,例如名字。
status 属性
值是字符串,设备的状态信息。
#address - cells #size - cells 属性

值是无符号 32 位整形。可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。

#address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 ),
#size-cells 属性值决定了子节点 reg 属性中长度信息所占的 字长 (32 )。
reg 属性
值一般是(addresslength)对。用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息
ranges 属性
ranges 是一个地址映射/ 转换表, ranges 属性每个项目由子地址、父地址和地址空间长度 这三部分组成。
如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换。
name 属性
值是字符串,name 属性用于记录节点名字。 name 属性已经被弃用,不推荐使用 name 属性,一些老的设备树文件可能会使用此属性。
device_type 属性
值是字符串,IEEE 1275 会用到此属性,用于描述设备的 FCode ,但是设 备树没有 FCode ,所以此属性也被抛弃了。此属性只能用于 cpu 节点或者 memory 节点。

4、compatible 属性详解

(1)根节点“/”也有 compatible 属性。
  imx6ull-iot-emmc.dts 文件中根节点的 compatible 属性如图: 

(2)arch/arm/mach-imx/mach-imx6ul.c 文件最后有设备兼容属性:
static const char *imx6ul_dt_compat[] __initconst = {
	"fsl,imx6ul",
	"fsl,imx6ull",
	NULL,
};

DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
	.map_io		= imx6ul_map_io,
	.init_irq	= imx6ul_init_irq,
	.init_machine	= imx6ul_init_machine,
	.init_late	= imx6ul_init_late,
	.dt_compat	= imx6ul_dt_compat,
MACHINE_END
设备 ( 板子 ) 根节点“ / ”的 compatible 属性值与 imx6ul_dt_compat 表中的任何一个值相等,那么就表示 Linux 内核支持此设备。

例如:imx6ull-iot-emmc.dts 文件根节点的 compatible 属性值如下:

compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

有匹配的节点属性“fsl,imx6ull”,则Linux内核支持此设备,可正常启动。

如果匹配不到对应属性,Linux 内核找不到对应的设备,无法启动。在 uboot 输出就卡在 Starting kernel…

(3)Linux 内核通过根节点 compatible 属性找到对应的设备的函数调用过程:

5、修改设备树文件,增加或修改节点 

例如 arch/arm/boot/dts/imx6ull-iot-emmc.dts 文件i2c1节点:

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
	};

	fxls8471@1e {
		compatible = "fsl,fxls8471";
		reg = <0x1e>;
		position = <0>;
		interrupt-parent = <&gpio5>;
		interrupts = <0 8>;
	};
};
  • &i2c1 表示要访问 i2c1 这个 label 所对应的节点。
  • “clock-frequency”为新添加的属性,表示 i2c1 时钟为 100KHz。
  • 将 status 属性的值由原来的 disabled 改为 okay
  • i2c1 子节点 mag3110NXP 官方开发板在 I2C1 上接了一个磁力计芯片 mag3110。
  • i2c1 子节点 fxls8471NXP 官方开发板在 I2C1 上接了 fxls8471六轴芯片。

三、设备树在系统中的体现

Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的 /proc/device-tree 目录下根据节点名字创建不同文件夹

/proc/device-tree 目录就是设备树在根文件系统中的体现。 

(1)输入以下命令,会进入/sys/firmware/devicetree/base : 

cd proc/device-tree/

如图,为根节点“/”的所有属性和子节点: 

根节点的属性#address-cells、#size-cells、compatible、model、name
根节点的子节点
aliases、 backlight chosen clocks...

(2)cat 命令来查看 model 和 compatible 这两个文件的内容:

(3)查看soc节点
soc 节点的所有子节点:

(4) 特殊节点

  • aliases 子节点:主要功能就是定义别名,定义别名的目的就是为了方便访问节点。
  • chosen 子节点:主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。
进入  /proc/device-tree/chosen 目录查看:

bootargs 环境变量的值是在uboot 中设置的,而 uboot 中的 fdt_chosen 函数在设备树的 chosen 节点中加入了 bootargs 属性,并且还设置了 bootargs 属性值。

bootz 80800000 – 83000000

输入以上命令并执行以后,do_bootz 函数就会执行 。

调用关系如下:

四、Linux 内核解析 DTB 文件流程

Linux 内核在启动的时候会解析 DTB 文件,然后在/proc/device-tree 目录下生成相应的设备树节点文件。

解析流程如下:

五、绑定信息文档

Linux 内核源码中有详细的 .txt 文档描述了如何添加节点,这些 .txt 文档叫做绑定文档。
路径在Linux 源码目录: /Documentation/devicetree/bindings

六、设备树常用 OF 操作函数

Linux 内核给我们提供了一系列函数来获取设备树中的节点或者属性信息,这一系列函数都有统一的前缀“of_ ”,也叫做 OF 函数。
OF 函数原型都定义在 include/linux/of.h 文件。

(1)查找节点的 OF 函数

设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。Linux 内核使用 device_node 结构体来描述一个节点,此结构体定义在文件 include/linux/of.h 中。
通过节点名字查找指定的节点
struct device_node * of_find_node_by_name (struct device_node *from, const char *name)
from :开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
name :要查找的节点名字。
返回值 :找到的节点,如果为 NULL 表示查找失败。
通过 device_type 属性
查找指定的节点
struct device_node * of_find_node_by_type (struct device_node *from, const char *type)
from :开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
type :要查找的节点对应的 type 字符串,也就是 device_type 属性值。
返回值 :找到的节点,如果为 NULL 表示查找失败。
根据 device_type compatible 两个属性查找指定的节点
struct device_node * of_find_compatible_node (struct device_node *from,
const char *type, const char *compatible)
from :开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
type :要查找的节点对应的 device_type 属性值,可以为 NULL ,表示忽略 device_type 属性。
compatible 要查找的节点所对应的 compatible 属性列表。
返回值 :找到的节点,如果为 NULL 表示查找失败
通过 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 表示查找失败
通过路径来查找指定的节点
inline struct device_node * of_find_node_by_path (const char *path)
path :全路径的节点名,可以使用节点的别名,比如“ /backlight ”就是 backlight 这个
节点的全路径。
返回值 :找到的节点,如果为 NULL 表示查找失败

(2)查找父/子节点的 OF 函数

用于获取指定节点的父节点
struct device_node * of_get_parent (const struct device_node *node)
node :要查找的父节点的节点。
返回值 :找到的父节点。
用迭代的查找子节点
struct device_node * of_get_next_child (const struct device_node *node,
struct device_node *prev)
node :父节点。
prev :前一个子节点,从此开始迭代的查找下一个子节点。NULL,表示从第一个子节点开始。
返回值 :找到的下一个子节点。

(3)提取属性值的 OF 函数

用于查找指定的属性
property * of_find_property (const struct device_node *np, const char *name, int *lenp)
np :设备节点;
name : 属性名字;
lenp :属性值的字节数;
返回值:找到的属性。
用于获取属性中元素的数量
(获取到属性数组的大小)
int of_property_count_elems_of_size (const struct device_node *np, const char *propname,int elem_size)
np :设备节点;
proname: 属性名;
elem_size:元素长度;
返回值 :得到的属性元素数量。
用于从属性中获取指定标号的 u32 类型数据值
int of_property_read_u32_index (const struct device_node *np, const char *propname, u32 index, u32 *out_value)
np :设备节点。
proname : 要读取的属性名字。
index :要读取的值标号。
out_value :读取到的值
返回值 :0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
读取属性中 u8 u16 u32 u64 类型的数组数据
int of_property_read_u8_array (const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
of_property_read_u16_array
of_property_read_u32_array
of_property_read_u64_array
np :设备节点。
proname : 要读取的属性名字。
out_value :读取到的数组值,分别为 u8 u16 u32 u64。
sz :要读取的数组元素数量。
用于读取这种只有一个整形值的属性
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
of_property_read_u16
of_property_read_u32
of_property_read_u64
np :设备节点。
proname : 要读取的属性名字。
out_value :读取到的数组值。
返回值 :0 :读取成功,负值:读取失败, -EINVAL :属性不存在, -ENODATA:没
有要读取的数据,-EOVERFLOW:属性值列表太小。
用于读取属性中字符串值
int of_property_read_string(struct device_node *np, const char *propname,const char **out_string)
np :设备节点。
proname : 要读取的属性名字。
out_string :读取到的字符串值。
返回值 :0:读取成功,负值:读取失败。
用于获取 #address-cells 属性值
int of_n_addr_cells(struct device_node *np)
np :设备节点。
返回值 :获取到的 #address-cells 属性值。
用于获取 #size-cells 属性值
int of_n_size_cells (struct device_node *np)
np :设备节点。
返回值 :获取到的 #size-cells 属性值。

(4)其他常用的 OF 函数

用于查看节点的 compatible 属性是否有包含 compat 指定的字
符串,也就是检查设备节点的兼容性
int of_device_is_compatible (const struct device_node *device, const char *compat)
device :设备节点。
compat :要查看的字符串。
返回值 :0 :节点的 compatible 属性中不包含 compat 指定的字符串;
正数:节点的 compatible 属性中包含 compat 指定的字符串。
用于获取地址相关属性,
主要是“ reg ”或者
“assigned-addresses”属性值
const __be32 * of_get_address (struct device_node *dev, int index, u64 *size, unsigned int *flags)
dev :设备节点。
index :要读取的地址标号。
size :地址长度。
flags :参数,比如 IORESOURCE_IO IORESOURCE_MEM 等
返回值 :读取到的地址数据首地址,为 NULL 的话表示读取失败。
将从设备树读取到的地址转换为物理地址
u64 of_translate_address (struct device_node *dev, const __be32 *in_addr)
dev :设备节点。
in_addr :要转换的地址。
返回值 :得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。
从设备树里面提取资源值,
将 reg 属性值转换为 resource 结构体类型
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
dev :设备节点。
index :地址资源标号。
r :得到的 resource 类型的资源值。
返回值 :0,成功;负值,失败。
reg 属性中地址信息转换为
虚拟地址
void __iomem * of_iomap (struct device_node *np, int index)
np :设备节点。
index reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
返回值 :经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。
  • 6
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值