设备树是为驱动服务的,驱动如何匹根据设备树的描述信息匹配驱动源码,及C语言如何获取设备树的描述属性,是驱动工程师关心的。编写驱动时,通常情况下都需要获取驱动对应的设备树描述属性,如GPIO属性、内存地址范围、中断地址等。linux提供了驱动和设备树交互的API接口,一般以“of_”开头,接口声明位于“kernel/include/linux”的头文件下。
1. 设备节点
设备树中的设备是以“节点”形式存在,因此要获取设备节点的属性信息,必须先获取到这个设备的节点(device_node),获取节点属性的函数接口第一个传入的参数就是“节点”地址。关于“device_node”的声明,位于“kernel/include/linux/of.h”中。
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; /* 链表节点 */
struct device_node *allnext;
struct proc_dir_entry *pde;
struct kref kref;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
2. 常用函数接口
2.1 驱动匹配
2.1.1 匹配属性兼容
of_match_ptr(int_demo_dt_ids)
#ifdef CONFIG_OF
#define of_match_ptr(_ptr) (_ptr)
#else
#define of_match_ptr(_ptr) (null)
#endif
#ifdef CONFIG_ACPI
#define ACPI_PTR(_ptr) (_ptr)
#else
#define of_match_ptr(_ptr) (null)
#endif
然而,阅读linux驱动目录下的源码,一部分驱动属性表并未通过“of_match_ptr”进行转换,也是能正常使用的。从该宏的原型可知,该宏只是针对不同类型驱动匹配的宏选择,不通过该宏处理,并不影响。保持良好的习惯,保证驱动兼容性,同时兼容dt和acpi匹配,建议增加“of_match_ptr”进行转换。
例子:
static struct of_device_id of_bmp180_ids[] = {
{.compatible = "bosch,bmp180"},
{ }
};
static struct i2c_driver bmp180_driver = {
.driver = {
.owner = THIS_MODULE,
.name = BMP180_DEV_NAME,
.of_match_table = of_match_ptr(of_bmp180_ids),
},
.id_table = bmp180_id,
.probe = bmp180_probe,
.remove = bmp180_remove,
};
2.2 获取节点
要获取一个设备节点的具体属性信息,必须先获取到这个设备的节点,可以通过多种方式获取设备节点。
2.2.1 通过节点名称获取
struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
from | 查找节点路径地址,传入NULL表示从根节点开始查找整个设备树 |
name | 节点名称 |
返回 | 成功返回节点首地址,失败返回NULL |
2.2.2 通过设备类型获取
struct device_node *of_find_node_by_type(struct device_node *from,const char *type);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
from | 查找节点路径地址,传入NULL表示从根节点开始查找整个设备树 |
type | 设备类型(device_type) |
返回 | 成功返回节点首地址,失败返回NULL |
2.2.3 通过“compatible”属性获取
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compat);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
from | 查找节点路径地址,传入NULL表示从根节点开始查找整个设备树 |
type | 查找节点类型(device_type),传入NULL表示忽略节点类型 |
compat | compatible属性(字符串) |
返回 | 成功返回节点首地址,失败返回NULL |
2.2.4 通过驱动匹配表属性获取
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);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
from | 查找节点路径地址,传入NULL表示从根节点开始查找 |
matches | 驱动匹配表,编写驱动时自定义 |
match | 返回匹配到的驱动表 |
返回 | 成功返回节点首地址,失败返回NULL |
注:
通过匹配表属性获取,本质也是通过“compatible”属性获取节点。匹配表一般定义如下:
static struct of_device_id of_bmp180_ids[] = { {.compatible = "bosch,bmp180"}, { } };
例子:
struct device_node *p;
p =of_find_matching_node(NULL, &of_bmp180_ids, &&pf);
2.2.5 通过“full_name”获取
struct device_node *of_find_node_by_path(const char *path);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
path | 节点带全路径的名称 |
返回 | 成功返回节点首地址,失败返回NULL |
例子:
struct device_node *p
p = of_find_node_by_path("/gpio-leds");
2.2.6 获取父子点
struct device_node *of_get_parent(const struct device_node *node);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
node | 待查找的父节点 |
返回 | 成功返回父节点首地址,失败返回NULL |
2.2.7 获取下一子节点
struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
node | 父节点 |
prev | 当前子节点,传入NULL表示从第一个子节点开始查找 |
返回 | 成功返回父节点首地址,失败返回NULL |
2.3 获取属性
一个节点(设备)属性包括了名称、长度、值等,这些都是驱动使用的必备属性,linux用一个结构体“strcut property”描述节点属性,位于“kernel/include/of.h”中。
struct property {
char *name;
int length;
void *value;
struct property *next;
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
2.3.1 获取指定属性
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
np | 设备节点 |
name | 属性名称 |
lenp | 属性值长度(字节) |
返回 | 成功返回属性首地址,失败返回NULL |
2.3.2 获取指定属性元素数量
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
np | 设备节点 |
propname | 属性名称 |
lenp | 属性值长度(字节) |
返回 | 成功返回属性元素数量,失败返回负数 |
例子:
设备树:
/ {
compatible = “rockchip,rk3399”;
interrupt-parent = <&gic>;
#address-cells = <2>;
#size-cells = <2>;
… … … …
spi4: spi@ff1f0000 {
compatible = “rockchip,rk3399-spi”, “rockchip,rk3066-spi”;
reg = <0x0 0xff1f0000 0x0 0x1000>;
clocks = <&cru SCLK_SPI4>, <&cru PCLK_SPI4>;
clock-names = “spiclk”, “apb_pclk”;
interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = “default”;
pinctrl-0 = <&spi4_clk &spi4_tx &spi4_rx &spi4_cs0>;
#address-cells = <1>;
#size-cells = <0>;
status = “disabled”;
};
… … … …
获取节点spi4的reg属性元素数量(伪代码)
struct device_node *p = NULL
int num = 0;
p = of_find_compatible_node(NULL, NULL, "rockchip,rk3399-spi");
num = of_property_count_elems_of_size(p, "reg", 8/*64位处理器*/); /* 执行成功num==4 */
2.3.3 获取指定属性指定标号的u32类型值
extern int of_property_read_u32_index(const struct device_node *np,
const char *propname, u32 index, u32 *out_value);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
np | 设备节点 |
propname | 属性名称 |
index | 读取标号 |
*out_value | 返回读取值 |
返回 | 成功返回0,失败返回负数 |
例子
假设2.3.2中的例子是一个32位机的CPU,地址字长为1(32位),则spi节点中的reg属性值为u32
/ {
compatible = “rockchip,rkxxx”;
interrupt-parent = <&gic>;
#address-cells = <1>;
#size-cells = <1>;
struct device_node *p = NULL
uint32 out= 0;
p = of_find_compatible_node(NULL, NULL, "rockchip,rk3399-spi");
of_property_read_u32_index(p, "reg", 1, &out);
2.3.4 获取数值
int of_property_read_variable_u8_array(const struct device_node *np,
const char *propname, u8 *out_values,
size_t sz_min, size_t sz_max);
int of_property_read_variable_u16_array(const struct device_node *np,
const char *propname, u16 *out_values,
size_t sz_min, size_t sz_max);
int of_property_read_variable_u32_array(const struct device_node *np,
const char *propname, u32 *out_values,
size_t sz_min, size_t sz_max);
int of_property_read_variable_u64_array(const struct device_node *np,
const char *propname,u64 *out_values,
size_t sz_min,size_t sz_max);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
np | 设备节点 |
propname | 属性名称 |
index | 读取标号 |
out_value | 返回读取值 |
sz_min | 读取数组元素最小数量 |
sz_max | 读取数组元素最大数量,可以为0 |
返回 | 成功返回0,失败返回负数 |
四个函数分别可以读取节点属性值为u8、u16、u32、u64的数组数据中的变化的数据。当“sz_max”为0时,表示节点属性值是固定的数组数据,此时linux内核单独封装出几个函数,“sz_min”则表示读取元素数目。
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_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
{
int ret = of_property_read_variable_u64_array(np, propname, out_values,
sz, 0);
if (ret >= 0)
return 0;
else
return ret;
}
2.3.2例子中,reg为u64类型数组,可以用"of_property_read_u64_array"一次性获取数组所有元素。
当获取数组元素数目“sz”为1时,可以表示获取单个u8、u16、u32、u64的值,因此进一步又封装理论获取数值的函数。
static inline int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value);
static inline int of_property_read_u16(const struct device_node *np,const char *propname,u8 *out_value);
static inline int of_property_read_u32(const struct device_node *np,const char *propname,u8 *out_value);
int of_property_read_u32(const struct device_node *np,const char *propname, u32 *out_value)
{
return of_property_read_u32_array(np, propname, out_value, 1);
}
2.3.5 获取string
int of_property_read_string_helper(struct device_node *np, const char *propname,
const char **out_strs, size_t sz, int index)
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
np | 设备节点 |
propname | 属性名称 |
out_strs | 返回读取值数值地址 |
sz | 读取数量 |
index | 读取标号 |
返回 | 成功返回0,失败返回负数 |
“of_property_read_string_helper”函数是获取节点属性为一组string数值的多个数据,而且可以指定标号开始获取。与2.3.4中获取数值函数类似,函数进一步封装为“获取string数组所有数据”和“获取属性为string的值(sz=1)”
int of_property_read_string_array(struct device_node *np,const char *propname, const char **out_strs,size_t sz)
{
return of_property_read_string_helper(np, propname, out_strs, sz, 0);
}
int of_property_read_string(struct device_node *np,const char *propname, const char **out_string);
2.3.6 获取地址
【1】获取地址和地址范围的字长
int of_n_addr_cells(struct device_node *np);
int of_n_size_cells(struct device_node *np);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of.h> |
np | 设备节点 |
返回 | 成功返回地址字长,失败返回负数 |
【2】获取地址
获取地址相关属性,一般是“reg”或者“assigned-address”的值。
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of_address.h> |
dev | 设备节点 |
index | 待读取地址标号 |
size | 待读取地址长度 |
flags | 读取标识,IORESOURCE_IO、IORESOURCE_MEM |
返回 | 成功返回首地址,失败返回NULL |
【3】IO地址转换为物理地址
u64 of_translate_address(struct device_node *np, const __be32 *addr)
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of_address.h> |
np | 设备节点 |
addr | 待转换地址 |
返回 | 成功返回物理地址,失败返回OF_BAD_ADDR |
【4】获取虚拟地址
void __iomem *of_iomap(struct device_node *device, int index);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of_address.h> |
np | 设备节点 |
index | 属性标号;如果reg属性只有一个单位地址,传入0 |
返回 | 成功返回虚拟地址首地址,失败返回NULL |
“of_iomap”用于把物理地址转为为虚拟地址。在linux未引入设备树前,采用的是“ioremap”函数实现物理地址转换虚拟地址的过程,引入设备树后,可以直接调用“of_iomap”函数获取虚拟地址。“of_iomap”本质也是将“reg”属性的的地址信息转换为虚拟地址。如果“reg”属性有多个单位地址,可以通过函数形参“index”指定某一段内存。
2.3.7 获取GPIO
int of_get_named_gpio(struct device_node *np, const char *propname, int index);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of_gpio.h> |
np | 设备节点 |
propname | GPIO属性名称 |
index | GPIO属性标号;只有1个GPIO,则传入0 |
返回 | 成功返回GPIO序号,失败返回负数 |
2.3.8 获取中断
【1】获取中断数量
int of_irq_count(struct device_node *dev);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of_irq.h> |
np | 设备节点 |
返回 | 返回中断数量,返回0表示无中断属性 |
【2】获取中断号
int of_irq_get(struct device_node *dev, int index); /*通过中断标号获取*/
int of_irq_get_byname(struct device_node *dev, const char *name);/*通过中断名称获取*/
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of_gpio.h> |
dev | 设备节点 |
index | 中断标号;只有1个中断,则传入0 |
name | 中断名称 |
返回 | 成功返回中断号,失败返回负数 |
2.3.9 提取资源空间
CPU的一系列外设,包括GPIO、Timer、I2C、SPI等都有自己的寄存器,也就是它们自己特有的“资源空间”。linux内核采用“struct resource”结构体来描述一个节点的资源空间。“struct resource”位于“linux/linux/ioport.h”
/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
resource_size_t start; /* 起始地址 */
resource_size_t end; /* 结束地址 */
const char *name; /* 资源名称 */
unsigned long flags; /* 资源类型 */
struct resource *parent, *sibling, *child;
};
关于资源类型“flags”,常用的资源类型,位于“linux/linux/ioport.h”中定义了常用的资源类型。我们见的是内存资源“IORESOURCE_MEM”、GPIO资源“IORESOURCE_IO”、寄存器资源“IORESOURCE_REG”、中断资源“IORESOURCE_IRQ”等。
/*
* IO resources have these defined flags.
*/
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
......
“of_address_to_resource”函数是将“reg”属性转换为“struct resource”类型。
int of_address_to_resource(struct device_node *dev, int index, struct resource *r);
参数 | 含义/说明 |
---|---|
引用 | #include<linux/of_address.h> |
dev | 设备节点 |
index | 地址资源标号 |
r | 资源返回 |
返回 | 成功返回0,失败返回负数 |
3. 参考
【1】https://blog.csdn.net/qq_33160790/article/details/77803873