掌握设备树是 Linux驱动开发人员必备的技能!
1、了解设备树文件
在3.x版本以前的Linux内核源码中,存在大量的“arc/arm/mach-xxx”和“arc/arm/plat-xxx”文件夹,里面很多个“.c”和“.h”文件,它们用来描述设备信息。而现在的ARM架构是采用“设备树”来描述设备信息。“设备树”英文名字叫“Device Tree”。描述设备树的文件叫做“DTS”,是Device Tree Source的缩写。它主要用来描述板子上有哪些设备构成,如:I2C控制器、GPIO控制器、SPI控制器、UART控制器等。
设备树源文件扩展名为“.dts”,设备树头文件扩展名为“.dtsi”,它们经过DTC工具编译后生成的二进制文件,就是“.dtb”文件。
“.dts”设备树文件可以通过“#include”来引用“.h”、“.dtsi”和“.dts”文件。在编写设备树头文件时,我们最好选择“.dtsi”作为后缀。
一般“.dtsi”文件用于描述芯片的“内部外设信息”,比如CPU架构、主频、外设寄存器地址范围,如:UART、IIC、SPI、TIMER等等。
2、了解节点
设备树是描述板子上的“设备信息”的文件,均采用“树形结构”。每个设备树文件只有一个“根节点”,而“不同设备树文件的根节点”会合在一起形成一个根节点。每个设备都是一个节点,称之为“设备节点”,每个节点都采用“属性信息”来描述其“节点信息”。
认识“标准属性”、“节点”和“节点标签”,见下图:
标准属性
1)、compatiable属性,也叫兼容性属性。
如:compatible = "cirrus,cs42l51";
这个compatible只有一个属性值,就是“cirrus,cs42l51”,其中“cirrus”表示厂商,“cs42l51”表示驱动模块。
再如:compatible = "cirrus,cs42l51","cirrus,My_cs42l51";
这个compatible就有两个属性值,就是“cirrus,cs42l51”和"cirrus,My_cs42l51",其中“cirrus”表示厂商,“cs42l51”表示驱动模块,“My_cs42l51”表示驱动模块。
驱动文件的“OF匹配表”
const struct of_device_id cs42l51_of_match[] = { { .compatible = "cirrus,cs42l51", },{ }};
设备首先会使用第一个兼容值在Limux内核里面查找。看看能不能找到与之匹配的驱动文件,如果没有找到,就使用第二个兼容值查,以此类推,直到查找完 commpatible属性中的所有值。
compatible属性用于将设备和驱动绑定起来。驱动程序文件有一个“OF匹配表”,这个“OF匹配表”保存着一些compatible的值,如果设备节点的compatible属性值和“OF匹配表”中的任何一个值相等,那么这个设备就可以使用这个驱动。
2)、model属性
用于描述开发板的名字或设备模块的信息。
比如:model = "STMicroelectronics STM32MP157C-DK2 Discovery Board";
3)、status属性
status属性值 |
描述 |
“okay” |
表明设备是可操作的 |
“disabled” |
表明设备当前是不可操作的,但是在未来可以变为可操作的,比如:热插拔设备插入以后。至于disabled的具体含义还要看设备的绑定文档。 |
“fail” |
表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。 |
“fail-sss” |
含义和“fai1”相同,后面的“sss部分”是检测到的错误内容。 |
4)、reg属性
reg属性的值一般是(address,length)对。
reg 属性一般用于描述“设备地址空间资源信息”或者“设备地址信息”,比如某个外设的“寄存器地址范围信息”,或者I2C器件的设备地址等。
5)、ranges属性
ranges = <child-bus-address,parent-bus-address,length>;
child-bus-address表示“子总线地址空间的物理地址”;
parent-bus-address表示“父总线地址空间的物理地址”;
length表示“子总线地址空间的长度”;
若ranges属性值为空,则说明子地址空间和父地址空间完全相同;
6)、“#address-cells”和“#size-cells”属性
#address-cells的属性值为无符号32位整型,用于描述子节点的“reg和ranges”中的address所占的位数,1表示32位,2表示64位;
#size-cells的属性值为无符号32位整型,用于描述子节点的“reg和ranges”中的length所占的位数,0表示没有位数,1表示32位,2表示64位;
举例说明“reg、 #address-cells和#size-cells”属性:
cpus { #address-cells = <1>;//表示子节点cpu的reg和ranges的address占32个位
#size-cells = <0>; //表示子节点cpu的reg和ranges的length占0个位 cpu0: cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; //表示addres=0, 没有length,和前面定义一致
clocks = <&scmi0_clk CK_SCMI0_MPU>; clock-names = "cpu"; operating-points-v2 = <&cpu0_opp_table>; nvmem-cells = <&part_number_otp>; nvmem-cell-names = "part_number"; #cooling-cells = <2>; }; };
scmi_sram: sram@2ffff000 { compatible = "mmio-sram"; reg = <0x2ffff000 0x1000>; //表示address=0x2ffff000,length=0x1000
#address-cells = <1>;//表示scmi_shm的reg和ranges的address占32个位
#size-cells = <1>; //表示scmi_shm的reg和ranges的length占32个位
ranges = <0 0x2ffff000 0x1000>; //分别为32位,和前面定义一致
scmi0_shm: scmi_shm@0 { reg = <0 0x80>; //reg的address和length分别为32位,和前面定义一致
};
};
soc {
compatible = "simple-bus"; #address-cells = <1>; //表示sram的reg和ranges的addres长度为32个位 #size-cells = <1>; //表示sram的reg和ranges的length长度为32个位
interrupt-parent = <&intc>; ranges = <0 0x10000000 0x100000>;
sram: sram@10000000 { compatible = "mmio-sram"; reg = <0x0 0x60000>;//reg的address和length均为32位,和前面定义一致
#address-cells = <1>; #size-cells = <1>; ranges = <0 0x10000000 0x60000>; //均为32位,和前面定义一致
};};
以STM32MP157为例,创建设备树文件myfrst.dts ,要求在设备树里面的内容如下:
1)、芯片是由两个Cortex-A7 架构的32位CPU和Cortex-M4组成。
2)、STM32MP157内部sram,起始地址为 0x10000000,大小为384KB(0x60000)。
先搭建 “根节点框架”,如下:
/{ compatible = "st,stm32mp157d-atk", "st,stm32mp157"; };
接着添加cpus节点,如下:
/{ compatible = "st,stm32mp157d-atk", "st,stm32mp157"; cpus{
#address-cells = <1>;//表示子节点cpu的reg和ranges的address占32个位
#size-cells = <0>; //表示子节点cpu的reg和ranges的length占0个位
};
};
接着添加cpu0节点,如下:
/{ compatible = "st,stm32mp157d-atk", "st,stm32mp157"; cpus{
#address-cells = <1>;//表示子节点cpu的reg和ranges的address占32个位
#size-cells = <0>; //表示子节点cpu的reg和ranges的length占0个位
cpu0: cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; //表示addres=0, 没有length,和前面定义一致
};
};
};
接着添加cpu1节点,如下:
/{ compatible = "st,stm32mp157d-atk", "st,stm32mp157"; cpus{
#address-cells = <1>;//表示子节点cpu的reg和ranges的address占32个位
#size-cells = <0>; //表示子节点cpu的reg和ranges的length占0个位
cpu0: cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; //表示addres=0, 没有length,和前面定义一致
};
cpu1: cpu@1 { compatible = "arm,cortex-m4"; device_type = "cpu"; reg = <1>; //表示addres=1, 没有length,和前面定义一致
};
};
};
接着添加soc节点,如下:
/{ compatible = "st,stm32mp157d-atk", "st,stm32mp157"; cpus{
#address-cells = <1>;//表示子节点cpu的reg和ranges的address占32个位
#size-cells = <0>; //表示子节点cpu的reg和ranges的length占0个位
cpu0: cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; //表示addres=0, 没有length,和前面定义一致
};
cpu1: cpu@1 { compatible = "arm,cortex-m4"; device_type = "cpu"; reg = <1>; //表示addres=1, 没有length,和前面定义一致
};
};
soc {
compatible = "simple-bus"; #address-cells = <1>; //表示sram的reg和ranges的addres长度为32个位 #size-cells = <1>; //表示sram的reg和ranges的length长度为32个位
ranges; //说明子地址空间和父地址空间完全相同;
};
};
接着添加sram节点,如下:
/{ compatible = "st,stm32mp157d-atk", "st,stm32mp157"; cpus{
#address-cells = <1>;//表示子节点cpu的reg和ranges的address占32个位
#size-cells = <0>; //表示子节点cpu的reg和ranges的length占0个位
cpu0: cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; //表示addres=0, 没有length,和前面定义一致
};
cpu1: cpu@1 { compatible = "arm,cortex-m4"; device_type = "cpu"; reg = <1>; //表示addres=1, 没有length,和前面定义一致
};
};
soc {
compatible = "simple-bus"; #address-cells = <1>; //表示sram的reg和ranges的addres长度为32个位 #size-cells = <1>; //表示sram的reg和ranges的length长度为32个位
ranges; //说明子地址空间和父地址空间完全相同;
sram: sram@10000000 { compatible = "mmio-sram"; reg = <0x0 0x60000>;//address和length均为32位,和前面定义一致
ranges = <0 0x10000000 0x60000>; //均为32位,和前面定义一致
};
};
};
3、了解特殊节点
1)、aliases子节点
aliases {
serial0 = &uart4; //给&uart4起个别名叫“serial0”
};
2)、chosen子节点
chosen不是一个真实的设备,chosen节点主要是为了uboot向 Linux内核传递数据。
chosen {
stdout-path = "serial0:115200n8";
//设置“stdout-path”属性,表示标准输出使用“serial0”
};
4、学习“OF函数”
“OF函数原型”都定义在“include/linux/of.h”文件中。
1)、了解相关结构体
Linux内核使用device_node结构体来描述一个节点。
struct device_node {
const char *name; /*节点名字*/
phandle phandle;
const char *full_name; /*节点全名*/
struct fwnode_handle fwnode;
struct property *properties; /*属性*/
struct property *deadprops; /* removed properties */
struct device_node *parent; /*父节点*/
struct device_node *child; /*子节点*/
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
Linux内核中使用结构体property表示属性
struct property {
char *name; /*属性名字*/
int length; /*属性长度*/
void *value; /*属性值*/
struct property *next; /*下一个属性*/
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
}
resource结构体定义在文件 include/linux/ioport.h中。
struct resource {
resource_size_t start; /*开始地址,占32位*/
resource_size_t end; /*结束地址,占32位*/
const char *name; /*资源的名字*/
unsigned long flags; /*资源标志或资源类型*/
unsigned long desc;
struct resource *parent, *sibling, *child;
};
2)、通过节点的名字查找指定的节点:
struct device_node *of_find_node_by_name(struct device_node *from, const char *name)
from:开始查找的节点,如果为NULL,表示从根节点开始查找整个设备树。
name:要查找的节点名字。
返回值:返回找到的节点,如果为NULL,表示查找失败。
3)、通过device_type属性查找指定的节点
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
from:开始查找的节点,如果为NULL,表示从根节点开始查找整个设备树;
type:要查找的节点对应的type字符串,也就是 device_type属性值。
返回值:返回找到的节点,如果为NULL,表示查找失败。
4)、根据device_type属性和compatible属性查找指定的节点
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
from:开始查找的节点,如果为NULL,表示从根节点开始查找整个设备树;
type:要查找的节点对应的type字符串,也就是device_type属性值。若为NULL,则可忽略掉device_type属性值;
compatible:要查找的节点所对应的compatible 属性列表;
返回值:返回找到的节点,如果为NULL,表示查找失败。
5)、通过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,表示查找失败。
6)、通过路径来查找指定的节点
inline struct device_node *of_find_node_by_path(const char *path)
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
返回值:返回找到的节点,如果为NULL,表示查找失败。
7)、获取指定节点的父节点
struct device_node *of_get_parent(const struct device_node *node)
node:要查找的父节点的节点名称。返回值: 返回找到的父节点。
8)、采用迭代方式查找子节点,即查找“prev”的下一个节点。
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
node:父节点。
prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。若设置为NULL,则表示从第一个子节点开始。
返回值: 返回找到的下一个子节点。
9)、查找指定的属性
property *of_find_property(const struct device_node *np, const char *name, int *lenp)
np:设备节点。
name:属性名字。
lenp:属性值的字节数
返回值:返回找到的属性。
10)、获取属性中元素的数量
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
np:设备节点。
proname:需要统计元素数量的属性名字。
elem_size:元素长度。
返回值: 返回得到的属性元素数量。
11)、从属性中获取指定标号的数据值
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 表示属性值列表太小。
12)、读取属性中u8类型的数组数据
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
np:设备节点。proname:要读取的属性名字。out_value:读取到的数组值,数据类型为u8。
sz:要读取的数组元素数量。返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
13)、读取属性中u16类型的数组数据
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
np:设备节点。proname:要读取的属性名字。out_value:读取到的数组值,数据类型为u16。sz:要读取的数组元素数量。返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
14)、读取属性中u32类型的数组数据
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
np:设备节点。proname:要读取的属性名字。out_value:读取到的数组值,数据类型为u32。sz:要读取的数组元素数量。返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
15)、读取属性中u64类型的数组数据
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
np:设备节点。proname:要读取的属性名字。out_value:读取到的数组值,数据类型为u64。sz:要读取的数组元素数量。返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
16)、读取属性中只有一个u8类型的数据
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
np:设备节点。proname:要读取的属性名字。out_value:读取到的u8类型的数据值。返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
17)、读取属性中只有一个u16类型的数据
int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)
np:设备节点。proname:要读取的属性名字。out_value:读取到的u16类型的数据值。返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
18)、读取属性中只有一个u32类型的数据
int of_property_read_u16(const struct device_node *np, const char *propname, u32 *out_value)
np:设备节点。proname:要读取的属性名字。out_value:读取到的u32类型的数据值。返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
19)、读取属性中只有一个u64类型的数据
int of_property_read_u16(const struct device_node *np, const char *propname, u64 *out_value)
np:设备节点。proname:要读取的属性名字。out_value:读取到的u64类型的数据值。返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
20)、读取属性中的字符串值
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
np:设备节点。proname:要读取的属性名字。out_string:读取到的字符串值。返回值:0,读取成功,负值,读取失败。
21)、获取“#address-cells”属性值
int of_n_addr_cells(struct device_node *np)
np:设备节点。返回值:返回“#address-cells”的属性值。
22)、获取“#size-cells”属性值
int of_n_size_cells(struct device_node *np)
np:设备节点。返回值:返回“#size-cells”的属性值。
23)、査看“节点的compatible属性”是否有包含“compat指定的字符串”,也就是检查设备节点的兼容性
int of_device_is_compatible(const struct device_node *device, const char *compat)
device:设备节点;
compat:要查看的字符串。返回值:0,表示“节点的compatible属性”中不包含“compat指定的字符串”;正数,表示“节点的compatible属性”中包含“compat指定的字符串”。
24)、读取“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”等返回值:返回读取到的地址数据首地址,若返回值为NUIL,则表示读取失败。
25)、将从设备树读取到的地址addr转换为物理地址
u64 of_translate_address(struct device_node *dev, const __be32 *addr)
dev:设备节点。
addr:要转换的地址。
返回值:返回得到的物理地址,如果为 OF_BAD_ADDR,则表示转换失败。
26)、将reg属性值转换为resource结构体类型
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
dev:设备节点。
index:地址资源标号
r:得到的resource 类型的资源值。
返回值:0,成功:负值,失败。
27)、将“reg属性中地址信息”转换为“虚拟地址”,如果reg属性有多段的话,可以通过index参数指定要完成内存映射的是哪一段
void __iomem *of_iomap(struct device_node *np, int index)
np:设备节点。
index:reg属性中要完成内存映射的段,如果reg属性只有一段的话,则index=0。
返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败。文章来源:https://www.toymoban.com/news/detail-839039.html
注意:也可以使用ioremap()函数来完成物理地址到虚拟地址的内存映射。在采用设备树以后,大部分的驱动都使用of_iomap()函数来获取内存地址所对应的虚拟地址。文章来源地址https://www.toymoban.com/news/detail-839039.html
到了这里,关于Linux第73步_学习Linux设备树和“OF函数”的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!