Skip to content

LV150-设备树相关函数

一、内核操作设备树的函数

内核源码中 linux - include/linux 目录下有很多 of 开头的头文件, of 表示“open firmware”即开放固件。

image-20250225164508489

1. 内核中设备树相关的头文件介绍

前面我们已经了解过了,设备树的处理过程是: dtb -> device_node -> platform_device。

1.1 处理 DTB

  • of_fdt.h - include/linux/of_fdt.h:dtb 文件的相关操作函数, 我们一般用不到, 因为 dtb 文件在内核中已经被转换为 device_node 树(它更易于使用)

1.2 处理 device_node

1.3 处理 platform_device

比如 of_device_alloc(根据 device_node 分配设置 platform_device),of_find_device_by_node (根据 device_node 查找到 platform_device),of_platform_bus_probe (处理 device_node 及它的子节点)

2. OF 函数

设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。比如设备树使用 reg 属性描述了某个外设的寄存器地址为 0X02005482,长度为 0X400,我们在编写驱动的时候需要获取到 reg 属性的 0X02005482 和 0X400 这两个值,然后初始化外设。

Linux 内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_”,所以在很多资料里面也被叫做 OF 函数。这些 OF 函数原型一般都定义在 of.h - include/linux/of.h 文件中。在使用的时候要包含 #include <linux/of.h> 文件。

在内核中以 device_node 结构体来对设备树进行描述, 所以 of 操作函数实际上就是获取 device_node 结构体 。

2.1 获取设备树节点

2.1.1 of_find_node_by_name()
c
struct device_node *of_find_node_by_name(struct device_node *from,
	const char *name)
{
	struct device_node *np;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	for_each_of_allnodes_from(from, np)
		if (np->name && (of_node_cmp(np->name, name) == 0)
		    && of_node_get(np))
			break;
	of_node_put(from);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return np;
}
EXPORT_SYMBOL(of_find_node_by_name);

该函数通过指定的节点名称在设备树中进行查找, 返回匹配的节点的 struct device_node 指针。但是在设备树的官方规范中不建议使用“ name”属性,所以这函数也不建议使用。

参数说明

  • from: 指定起始节点, 表示从哪个节点开始查找。 如果 from 参数为 NULL, 则从设备树的根节点开始查找。
  • name: 要查找的节点名称。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

2.1.2 of_find_node_by_path()
c
static inline struct device_node *of_find_node_by_path(const char *path)
{
	return of_find_node_opts_by_path(path, NULL);
}

该函数通过节点路径在设备树中进行查找。 路径是设备树节点从根节点到目标节点的完整路径。 可以通过指定正确的路径来准确地访问设备树中的特定节点。使用该函数时, 可以直接传递节点的完整路径作为 path 参数, 函数会在设备树中查找匹配的节点。 这对于已知节点路径的情况非常有用。

参数说明

  • path: 节点的路径, 以斜杠分隔的字符串表示。 路径格式为设备树节点的绝对路径, 例如 / topeet/myLed。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

2.1.3 of_find_node_by_type()
c
struct device_node *of_find_node_by_type(struct device_node *from,
	const char *type)
{
	struct device_node *np;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	for_each_of_allnodes_from(from, np)
		if (np->type && (of_node_cmp(np->type, type) == 0)
		    && of_node_get(np))
			break;
	of_node_put(from);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return np;
}
EXPORT_SYMBOL(of_find_node_by_type);

该函数根据类型找到节点,节点如果定义了 device_type 属性,那我们可以根据类型找到它。但是在设备树的官方规范中不建议使用“ device_type”属性,所以这函数也不建议使用。

参数说明

  • from: 指定起始节点, 表示从哪个节点开始查找。 如果 from 参数为 NULL, 则从设备树的根节点开始查找。
  • type: 要查找的节点的 device_type 属性。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

2.1.4 of_find_compatible_node()
c
struct device_node *of_find_compatible_node(struct device_node *from,
	const char *type, const char *compatible)
{
	struct device_node *np;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	for_each_of_allnodes_from(from, np)
		if (__of_device_is_compatible(np, compatible, type, NULL) &&
		    of_node_get(np))
			break;
	of_node_put(from);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return np;
}
EXPORT_SYMBOL(of_find_compatible_node);

该函数根据 compatible 找到节点,节点如果定义了 compatible 属性,那我们可以根据 compatible 属性找到它。

参数说明

  • from: 指定起始节点, 表示从哪个节点开始查找。 如果 from 参数为 NULL, 则从设备树的根节点开始查找。
  • type: 要匹配的设备类型字符串,用来指定 device_type 属性的值,可以传入 NULL,表示忽略掉 device_type 属性。
  • compatible: 要匹配的兼容性字符串, 通常是设备树节点的 compatible 属性中的值。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

使用 of_find_compatible_node() 函数时, 可以指定起始节点和需要匹配的设备类型字符串以及兼容性字符串。 函数会从起始节点开始遍历设备树, 查找与指定兼容性字符串匹配的节点,并返回匹配节点的指针。

2.1.5 of_find_node_by_phandle()
c
struct device_node *of_find_node_by_phandle(phandle handle)
{
	//......
}
EXPORT_SYMBOL(of_find_node_by_phandle);

该函数根据 phandle 找到节点。 dts 文件被编译为 dtb 文件时,每一个节点都有一个数字 ID,这些数字 ID 彼此不同。可以使用数字 ID 来找到 device_node。这些数字 ID 就是 phandle。

参数说明

  • phandle: 要查找节点的数字 id。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

2.1.6 of_find_matching_node_and_match()
c
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)
{
	//......
}

该函数通过 of_device_id 匹配表来查找指定的节点。

参数说明

  • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
  • matches: of_device_id 匹配表,也就是在此匹配表里面查找节点(该表包含要搜索的匹配项)。
  • match: 找到的匹配的 of_device_id

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

of_find_matching_node_and_match() 函数在设备树中遍历节点, 对每个节点使用 __of_match_node() 函数进行匹配。 如果找到匹配的节点, 将返回该节点的指针, 并将 match 指针更新为匹配到的 of_device_id 条目, 函数会自动增加匹配节点的引用计数。下面是一个简单示例:

c
#include <linux/of.h>
static const struct of_device_id my_match_table[] = {
    { .compatible = "vendor,device" },
    { /* sentinel */ }
};
const struct of_device_id *match;
struct device_node *np;
// 从根节点开始查找匹配的节点
np = of_find_matching_node_and_match(NULL, my_match_table, &match);

我们定义了一个 of_device_id 匹配表 my_match_table, 其中包含了一个兼容性字符串为 "vendor, device" 的匹配项。 然后, 我们使用 of_find_matching_node_and_match 函数从根节点开始查找匹配的节点。

2.2 查找父子节点

2.2.1 of_get_parent()
c
struct device_node *of_get_parent(const struct device_node *node)
{
	struct device_node *np;
	unsigned long flags;

	if (!node)
		return NULL;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	np = of_node_get(node->parent);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return np;
}

该函数接收一个指向设备树节点的指针 node, 并返回该节点的父节点(如果有父节点的话)的指针。

参数说明

  • node: 要获取父节点的设备树节点指针。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

使用 of_get_parent() 函数时, 可以将特定的设备树节点作为参数传递给函数, 然后它将返回该节点的父节点。 这对于在设备树中导航和访问节点之间的层次关系非常有用。

父节点在设备树中表示了节点之间的层次结构关系。 通过获取父节点, 我们可以访问上一级节点的属性和配置信息, 从而更好地理解设备树中的节点之间的关系。

2.2.2 of_get_next_parent()
c
struct device_node *of_get_next_parent(struct device_node *node)
{
	struct device_node *parent;
	unsigned long flags;

	if (!node)
		return NULL;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	parent = of_node_get(node->parent);
	of_node_put(node);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return parent;
}
EXPORT_SYMBOL(of_get_next_parent);

这个函数名比较奇怪,怎么可能有“ next parent”?它实际上也是找到 device_node 的父节点,跟 of_get_parent() 的返回结果是一样的。差别在于它多调用 of_node_put() 函数,把 node 节点的引用计数减少了 1。这意味着调用 of_get_next_parent() 之后,我们不再需要调用 of_node_put() 释放 node 节点

参数说明

  • node: 要获取父节点的设备树节点指针。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

2.2.3 of_get_next_child()
c
struct device_node *of_get_next_child(const struct device_node *node,
	struct device_node *prev)
{
	struct device_node *next;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	next = __of_get_next_child(node, prev);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return next;
}
EXPORT_SYMBOL(of_get_next_child);

该函数函数用于获取设备树节点的下一个子节点。

参数说明

  • node: 当前节点, 用于指定要获取子节点的起始节点。
  • prev: 上一个子节点, 用于指定从哪个子节点开始获取下一个子节点。 如果为 NULL, 则从起始节点的第一个子节点开始。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

使用 of_get_next_child() 函数时, 可以传递当前节点以及上一个子节点作为参数。 函数将从上一个子节点的下一个节点开始, 查找并返回下一个子节点。设备树中的子节点表示了节点之间的层次关系。 通过获取子节点, 可以遍历和访问当前节点的所有子节点, 以便进一步处理它们的属性和配置信息。

2.2.4 of_get_next_available_child()
c
struct device_node *of_get_next_available_child(const struct device_node *node,
	struct device_node *prev)
{
	//......
}

该函数取出下一个“可用”的子节点,有些节点的 status 是“disabled”,那就会跳过这些节点。

参数说明

  • node: 当前节点, 用于指定要获取子节点的起始节点。
  • prev: 上一个子节点, 用于指定从哪个子节点开始获取下一个子节点。 如果为 NULL, 则从起始节点的第一个子节点开始。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

2.2.5 of_get_child_by_name()
c
struct device_node *of_get_child_by_name(const struct device_node *node,
				const char *name)
{
	struct device_node *child;

	for_each_child_of_node(node, child)
		if (child->name && (of_node_cmp(child->name, name) == 0))
			break;
	return child;
}

该函数根据名字取出子节点。

参数说明

  • node: 当前节点, 用于指定要获取子节点的起始节点。
  • name: 表示子节点的名字。

返回值】如果找到匹配的节点, 则返回对应的 struct device_node 指针。如果未找到匹配的节点, 则返回 NULL。

2.3 获取属性

节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要, Linux 内核中使用结构体 struct property 表示属性:

c
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
};
2.3.1 of_find_property()
c
struct property *of_find_property(const struct device_node *np,
				  const char *name,
				  int *lenp)
{
	struct property *pp;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	pp = __of_find_property(np, name, lenp);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);

	return pp;
}

该函数用于在节点 np 下查找指定名称 name 的属性。

参数说明

  • np: 要查找的节点。
  • name: 要查找的属性的属性名。
  • lenp: 一个指向整数的指针, 用于接收属性值的字节数。

返回值】如果成功找到了指定名称的属性, 则返回对应的属性结构体指针 struct property * ; 如果未找到, 则返回 NULL。

示例:

在设备树中,节点大概是这样:

c
xxx_node {
	xxx_pp_name = “hello”;
};

上述节点中,“ xxx_pp_name”就是属性的名字,值的长度是 6。

2.3.2 of_property_count_elems_of_size()
c
int of_property_count_elems_of_size(const struct device_node *np,
				const char *propname, int elem_size)
{
	//......
}

该函数在设备树中的设备节点下查找指定名称的属性, 并获取该属性中元素的数量。 调用该函数可以用于获取设备树属性中某个属性的元素数量, 比如一个字符串列表的元素数量或一个整数数组的元素数量等,在比如我们常见的 reg 属性值就是一个数组,那么使用此函数可以获取到这个数组的大小 。

参数说明

  • np: 设备节点。
  • propname: 需要获取元素数量的属性名。
  • elem_size: 单个元素的尺寸。

返回值】如果成功获取了指定属性中元素的数量, 则返回该数量; 如果未找到属性或属性中没有元素, 则返回 0。

示例:

在设备树中,节点大概是这样:

c
xxx_node {
	xxx_pp_name = <0x50000000 1024> <0x60000000 2048>;
};
  • 调用 of_property_count_elems_of_size(np, “xxx_pp_name”, 8)时,返回值是 2;
  • 调用 of_property_count_elems_of_size(np, “xxx_pp_name”, 4)时,返回值是 4。
2.3.3 读数组 u8/u16/u32/u64

下面四个函数定义在:property.c - drivers/of/property.c

c
//从指定属性中读取变长的 u8 数组
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);
//从指定属性中读取变长的 u16 数组
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);
//从指定属性中读取变长的 u32 数组
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);
//从指定属性中读取变长的 u64 数组
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)

这几个函数用于从设备树中读取指定属性名的变长数组。 通过提供设备节点、 属性名和输出数组的指针, 可以将设备树中的数组数据读取到指定的内存区域中。 同时, 还需要指定数组的最小大小和最大大小, 以确保读取到的数组符合预期的大小范围。

参数说明

  • np: 设备节点。propname: 要读取的属性名。
  • out_values: 用于存储读取到的 u8/u16/u32/u64 数组的指针。
  • sz_min: 数组的最小大小。
  • sz_max: 数组的最大大小。

返回值】0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。

示例

在设备树中,节点大概是这样:

c
xxx_node {
	name2 = <0x50000012 0x60000034>;
};

调用 of_property_read_variable_u8_array (np, “name2”, out_values, 1, 10)时,out_values 中将会保存这 8 个字节: 0x12,0x00,0x00,0x50,0x34,0x00,0x00,0x60。

调用 of_property_read_variable_u16_array (np, “name2”, out_values, 1, 10)时,out_values 中将会保存这 4 个 16 位数值: 0x0012, 0x5000,0x0034,0x6000。

总之,这些函数要么能取到全部的数值,要么一个数值都取不到;如果值的长度在 sz_min 和 sz_max 之间,就返回全部的数值;否则一个数值都不返回。

衍生函数】从这几个函数衍生出下面一组函数:of_property_read_u8_array()of_property_read_u16_array()of_property_read_u32_array()of_property_read_u64_array()。这里的四个函数会调用 of_property_read_variable_uX_array ()。

2.3.4 读整数 u8/u16/u32/u64

下面的三个函数定义在这里:of.h - include/linux/of.h1162 - 1181

c
static inline int of_property_read_u8(const struct device_node *np,
				       const char *propname,
				       u8 *out_value)
{
	return of_property_read_u8_array(np, propname, out_value, 1);
}

static inline int of_property_read_u16(const struct device_node *np,
				       const char *propname,
				       u16 *out_value)
{
	return of_property_read_u16_array(np, propname, out_value, 1);
}

static inline 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);
}

读 u64 整数的函数与上面不太一样,定义在 property.c - drivers/of/property.c

c
int of_property_read_u64(const struct device_node *np, const char *propname,
			 u64 *out_value)
{
	const __be32 *val = of_find_property_value_of_size(np, propname,
						sizeof(*out_value),
						0,
						NULL);

	if (IS_ERR(val))
		return PTR_ERR(val);

	*out_value = of_read_number(val, 2);
	return 0;
}

示例

在设备树中,节点大概是这样:

c
xxx_node {
    name1 = <0x50000000>;
    name2 = <0x50000000 0x60000000>;
};

调用 of_property_read_u32 (np, “name1”, &val)时, val 将得到值 0x50000000;

调用 of_property_read_u64 (np, “name2”, &val)时, val 将得到值 0x6000000050000000。

2.3.5 读某个整数 u32

of_property_read_u32_index() 函数用于读取某个 u32 的值:

c
int of_property_read_u32_index(const struct device_node *np,
				       const char *propname,
				       u32 index, u32 *out_value)
{
	const u32 *val = of_find_property_value_of_size(np, propname,
					((index + 1) * sizeof(*out_value)),
					0,
					NULL);

	if (IS_ERR(val))
		return PTR_ERR(val);

	*out_value = be32_to_cpup(((__be32 *)val) + index);
	return 0;
}

该函数在设备树中的设备节点下查找指定名称的属性, 并获取该属性在给定索引位置处的 u32 类型的数据值。这个函数通常用于从设备树属性中读取特定索引位置的整数值。 通过指定属性名和索引,可以获取属性中指定位置的具体数值。

参数说明

  • np: 设备节点。
  • propname: 要读取的属性名。
  • index: 要读取的属性值在属性中的索引, 索引从 0 开始。
  • out_value: 用于存储读取到的值的指针。

返回值】0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。

示例

在设备树中,节点大概是这样:

c
xxx_node {
	name2 = <0x50000000 0x60000000>;
}

调用 of_property_read_u32 (np, “name2”, 1, &val)时, val 将得到值 0x60000000。

2.3.6 读某个整数 u64

of_property_read_u64_index() 用于读取某个 u64 的值:

c
int of_property_read_u64_index(const struct device_node *np,
				       const char *propname,
				       u32 index, u64 *out_value)
{
	const u64 *val = of_find_property_value_of_size(np, propname,
					((index + 1) * sizeof(*out_value)),
					0, NULL);

	if (IS_ERR(val))
		return PTR_ERR(val);

	*out_value = be64_to_cpup(((__be64 *)val) + index);
	return 0;
}

该函数在设备树中的设备节点下查找指定名称的属性, 并获取该属性在给定索引位置处的 u64 类型的数据值。这个函数通常用于从设备树属性中读取特定索引位置的 64 位整数值。 通过指定属性名和索引, 可以获取属性中指定位置的具体数值。

参数说明

  • np: 设备节点。
  • propname: 要读取的属性名。
  • index: 要读取的属性值在属性中的索引, 索引从 0 开始。
  • out_value: 用于存储读取到的值的指针。

返回值】0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。

2.3.7 读字符串

of_property_read_string() 函数用于读取属性中字符串值:

c
int of_property_read_string(const struct device_node *np, const char *propname,
				const char **out_string)
{
	const struct property *prop = of_find_property(np, propname, NULL);
	if (!prop)
		return -EINVAL;
	if (!prop->value)
		return -ENODATA;
	if (strnlen(prop->value, prop->length) >= prop->length)
		return -EILSEQ;
	*out_string = prop->value;
	return 0;
}

该函数在设备树中的设备节点下查找指定名称的属性, 并获取该属性的字符串值, 最后返回读取到的字符串的指针, 通常用于从设备树属性中读取字符串值。 通过指定属性名, 可以获取属性中的字符串数据。

参数说明

  • np: 设备节点。
  • propname: 要读取的属性名。
  • out_string: 用于存储读取到的字符串的指针。

返回值】如果成功读取到了指定属性的字符串, 则返回 0; 如果未找到属性或读取失败, 则返回相应的错误码。

2.3.8 读获取#address-cells

of_n_addr_cells() 函数用于读取#address-cells 属性值 :

c
int of_n_addr_cells(struct device_node *np)
{
	u32 cells;

	do {
		if (np->parent)
			np = np->parent;
		if (!of_property_read_u32(np, "#address-cells", &cells))
			return cells;
	} while (np->parent);
	/* No #address-cells property for the root node */
	return OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
}

参数说明

  • np: 要查找的节点。

返回值】获取到的#address-cells 属性值。

2.3.9 读取#size-cells

of_n_size_cells() 用于读取#size-cells 属性值:

c
int of_n_size_cells(struct device_node *np)
{
	u32 cells;

	do {
		if (np->parent)
			np = np->parent;
		if (!of_property_read_u32(np, "#size-cells", &cells))
			return cells;
	} while (np->parent);
	/* No #size-cells property for the root node */
	return OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
}

参数说明

  • np: 要查找的节点。

返回值】获取到的#size-cells 属性值。

2.4 其他函数

2.4.1 of_device_is_compatible()

of_device_is_compatible() 用于函数用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性 :

c
int of_device_is_compatible(const struct device_node *device,
		const char *compat)
{
	unsigned long flags;
	int res;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	res = __of_device_is_compatible(device, compat, NULL, NULL);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return res;
}

参数说明

  • device:设备节点。
  • compat:要查看的字符串。

返回值】0,节点的 compatible 属性中不包含 compat 指定的字符串; 正数,节点的 compatible 属性中包含 compat 指定的字符串。

2.4.2 of_get_address()

of_get_address() 用于函数用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值 :

c
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 的话表示读取失败。

2.4.3 of_translate_address()

of_translate_address() 用于函数负责将从设备树读取到的地址转换为物理地址:

c
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{
	struct device_node *host;
	u64 ret;

	ret = __of_translate_address(dev, in_addr, "ranges", &host);
	if (host) {
		of_node_put(host);
		return OF_BAD_ADDR;
	}

	return ret;
}

参数说明

  • dev:设备节点。
  • in_addr:要转换的地址。

返回值】得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。

2.4.4 of_address_to_resource()

of_address_to_resource() 函数看名字像是从设备树里面提取资源值,但是本质上就是将 reg 属性值,然后将其转换为 resource 结构体类型:

c
int of_address_to_resource(struct device_node *dev, int index,
			   struct resource *r)
{
	const __be32	*addrp;
	u64		size;
	unsigned int	flags;
	const char	*name = NULL;

	addrp = of_get_address(dev, index, &size, &flags);
	if (addrp == NULL)
		return -EINVAL;

	/* Get optional "reg-names" property to add a name to a resource */
	of_property_read_string_index(dev, "reg-names",	index, &name);

	return __of_address_to_resource(dev, addrp, size, flags, name, r);
}

参数说明

  • dev:设备节点。
  • index:地址资源标号。
  • r:得到的 resource 类型的资源值。

返回值】0,成功;负值,失败。

Tips:关于 resource:

IIC、 SPI、 GPIO 等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间, Linux 内核使用 resource 结构体来描述一段内存空间,“resource”翻译出来就是“资源”,因此用 resource 结构体描述的都是设备资源信息,resource 结构体定义在文件 ioport.h - include/linux/ioport.h 中,定义如下:

c
struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};

对于 32 位的 SOC 来说, resource_size_t 是 u32 类型的。其中 start 表示开始地址, end 表示结束地址, name 是这个资源的名字, flags 是资源标志位,一般表示资源类型,可选的资源标志定义在文件 ioport.h - include/linux/ioport.h 中,如下所示

c
#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
//......

一般最常见的资源标志就是 IORESOURCE_MEM 、 IORESOURCE_REG 和 IORESOURCE_IRQ 等。

2.4.5 of_iomap()

of_iomap() 函数用于直接内存映射,以前我们会通过 ioremap() 函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap() 函数来获取内存地址所对应的虚拟地址,不需要使用 ioremap() 函数了。当然了,也可以使用 ioremap() 函数来完成物理地址到虚拟地址的内存映射,只是在采用设备树以后,大部分的驱动都使用 of_iomap() 函数了。 of_iomap() 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段, of_iomap() 函数原型如下

c
void __iomem *of_iomap(struct device_node *np, int index)
{
	struct resource res;

	if (of_address_to_resource(np, index, &res))
		return NULL;

	return ioremap(res.start, resource_size(&res));
}

参数说明

  • np:设备节点。
  • index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话, index 就设置为 0。

返回值】经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。

3. platform_device 相关的函数

我们知道,设备树的节点有一些会转化成 platform_device,针对这种类型的节点,也有几个相关的函数。

3.1 of_find_device_by_node()

c
struct platform_device *of_find_device_by_node(struct device_node *np)
{
	struct device *dev;

	dev = bus_find_device(&platform_bus_type, NULL, np, of_dev_node_match);
	return dev ? to_platform_device(dev) : NULL;
}
EXPORT_SYMBOL(of_find_device_by_node);

设备树中的每一个节点,在内核里都有一个 device_node;我们可以使用 device_node 去找到对应的 platform_device。

3.2 platform_get_resource()

这个函数跟设备树没什么关系,但是设备树中的节点被转换为 platform_device 后,设备树中的 reg 属性、 interrupts 属性也会被转换为“ resource”,这个时候我们可以通过这个函数获取这些资源:

c
struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num)
{
	int i;

	for (i = 0; i < dev->num_resources; i++) {
		struct resource *r = &dev->resource[i];

		if (type == resource_type(r) && num-- == 0)
			return r;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);

参数说明

  • dev:struct platform_device 类型指针,包含了设备树节点的相关信息。
  • type:资源的类型,IORESOURCE_MEM、 IORESOURCE_REG、IORESOURCE_IRQ 等
  • num:这类资源中的哪一个。

对于设备树节点中的 reg 属性,它对应 IORESOURCE_MEM 类型的资源;对于设备树节点中的 interrupts 属性,它对应 IORESOURCE_IRQ 类型的资源。

二、of 函数使用实例

1. 设备树节点准备

这里可以在设备树中添加这样一个节点:

c
    alpha {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";

        sdev_led {
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "led";
            status = "okay";
            reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
                    0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
                    0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
                    0X0209C000 0X04 /* GPIO1_DR_BASE */
                    0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
        };
    };

为了避免#address-cells = <1>; 和 #size-cells = <1>; 这两个属性改变根节点其他的节点的属性, 所以在这里创建了一个 alpha 节点。 在这个示例中, #address-cells 设置为 1 表示地址使用一个 32 位的单元, #size-cells 也设置为 1 表示大小使用一个 32 位的单元。

第 4 行: 将 compatible 属性设置为 "simple-bus" 用于表示 alpha 节点的兼容性, 指明它是一个简单总线设备, 在转换 platform_device 的过程中, 会继续查找该节点的子节点。

第 9 行: sdev_led 节点下的 compatible 属性为 "led", 表明该节点将会被转换为 platform_device。

第 11 行: 这个属性用于描述 sdev_led 节点的寄存器信息。可以看到有 5 个寄存器 。

这个节点在编译的时候报警告,但是并没有什么太大影响,这里可以先不管:

image-20250309161117557

我们之前用的是从 tftp 下载设备树,所以我们直接替换服务器中的设备树文件即可,然后重启设备,看一下节点情况:

image-20250309161625812

2. of 获取设备节点实例

2.1 demo 源码

源码可以看这里:11_device_tree/02_of_get_node/drivers_demo/sdriver_demo.c 中的函数 of_get_node_test(void)

2.2 开发板测试

我们加载驱动,会看到如下打印信息:

image-20250309162949432

3. of 获取设备节点属性实例

3.1 demo 源码

源码可以看这里:11_device_tree/02_of_get_node/drivers_demo/sdriver_demo.c 中的函数 of_get_node_property_test(void)

3.2 开发板测试

我们加载驱动,会看到如下打印信息:

image-20250309164449464

4. led 实例

上面已经可以从设备树获取对应的节点和属性了,那么我们来写一个 led 灯的 demo,可以看这里:11_device_tree/03_dts_led