linux设备树-设备树常用OF操作函数
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
在Linux内核采用设备树之后,驱动程序需要获取设备树的属性。Linux内核为驱动程序提供了一系列API函数,用于获取设备树的属性值。在Linux内核中,以“of_”开头的函数是设备树API函数。
一、获取设备节点API
在内核中,设备以节点的形式附加到设备树上,因此要获得设备信息,必须先获取设备节点。
Linux内核使用device_node结构体来描述一个设备节点,此结构体定义在文件 include/linux/of.h 中,代码如下:
struct device_node { const char *name; /*节点的名字*/ phandle phandle; const char *full_name; /*节点的全名,node-name[@unit-address]*/ 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 };
上述数据结构是设备节点结构。让我们来看一下获取设备节点的几个常见函数。
1.1 of_find_node_by_name
of_find_node_by_name函数通过设备节点的名字获取设备节点,函数原型:
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
其中:
- from:指定要搜索设备节点的起始位置。若为NULL,则从根节点开始搜索;
- name:要查找的设备节点的名称;
成功返回设备节点结构,失败时返回NULL。
1.2 of_find_node_by_type
of_find_node_by_type函数通过设备节点类型获取设备节点,函数原型:
struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
其中:
- from:指定要搜索设备节点的起始位置。若为NULL,则从根节点开始搜索;
- type:要查找的设备节点的类型,device_type属性值;
成功返回设备节点结构,失败返回NULL。
1.3 of_find_compatible_node
of_find_compatible_node函数通过节点的compatible属性和type获取设备节点,函数原型:
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compat);
其中:
- from参数:指定要搜索设备节点的起始位置。若为NULL,则从根节点开始搜索;
- type参数:要查找的设备节点类型。如果为NULL,则忽略类型限制;
- compatible参数:要查找的设备节点的compatible属性名称;
成功返回设备节点结构,失败时返回NULL。
1.4 of_find_node_by_path
of_find_node_by_path函数通过设备节点路径名获取设备节点,函数定义:
static inline struct device_node *of_find_node_by_path(const char *path) { return of_find_node_opts_by_path(path, NULL); }
其中:
- path参数:设备节点的路径名;
成功返回设备节点结构,失败时返回NULL;
1.5 of_find_matching_node_and_match
of_find_matching_node_and_match 函数通过 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。
1.6 of_get_child_by_name
of_get_child_by_name函数用于查找指定设备节点的子节点,函数原型:
struct device_node *of_get_child_by_name(const struct device_node *node, const char *name);
其中:
- node:表示要查询子节点的父节点;
- name:表示要查找的子节点的名称;
函数的主要操作是从node节点的子节点列表中查找名为name的子节点。如果找到匹配的子节点,则将其返回;否则返回NULL。
二、获取父子设备节点API
2.1 of_get_parent
of_find_node_by_path函数用于获取某一节点的父节点,函数原型:
struct device_node *of_get_parent(const struct device_node *node);
其中:
- node参数:要查找父节点的节点;
成功返回父节点的设备节点结构,失败时返回NULL。
2.2 of_get_next_child
of_get_next_child函数可以遍历某一节点的子节点,函数原型:
device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);
其中:
- node参数:父节点;
- prev参数:上一个找到的子节点,即从哪个子节点开始搜索。如果为NULL,表示从第一个子节点开始搜索;
返回值是找到的下一个子节点的设备节点结构。
三、获取设备树属性API
在内核中,设备树中的属性以结构体的形式表示。 Linux 内核中使用结构体property表示属性,此结构体同样定义在文件include/linux/of.h中,内容如下:
struct property { char *name; /* 属性名 */ int length; /* 数据长度 */ void *value; /* 属性数据指针 */ struct property *next; /* 下一个属性 */ };
在该结构体中:
- name表示属性名;
- length表示属性数据的长度;
- value指向属性数据的指针;
- next指向下一个属性。
3.1 of_find_property
of_find_property函数可以在设备节点的属性列表中查找指定的属性,函数原型如下:
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp);
其中:
- np:设备节点;
- name:属性名称;
- lenp:属性长度,单位为字节;
比如下面一段代码,通过of_find_property函数获取设备的属性"linux,gpio-keymap"的值。
of_find_property(client->dev.of_node, "linux,gpio-keymap",&proplen)
3.2 of_property_read_u32_index
of_property_read_u32_index函数可以读取设备树中属性值为32位无符号整数的属性。函数原型:
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);
其中:
- np:设备节点;
- propname:属性名称;
- index:索引,指定要读取的属性值的编号;
- out_value:读取出的属性值;
函数返回值:
- 0:读取成功;
- -EINVAL:指定的属性不存在;
- -ENODATA:没有数据可读;
- -EOVERFLOW:属性值列表太小;
例如,以下代码段表示从设备节点nd的名为"my_property"的属性中读取第三个32位无符号整数值:
u32 val; int ret; ret = of_property_read_u32_index(np, "my_property", 2, &val); if (ret) pr_err("Failed to read my_property\n"); else pr_info("my_property[2] = %u\n", val);
3.3 of_property_read_u8/16/32/64_array
这四个是读取设备节点中包含多个无符号数据的属性值的函数。函数原型:
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)
其中:
- np:设备节点
- propname:属性名称;
- out_values:读出的属性值;
- sz:需要读取的属性值数量;
函数返回值:
- 0:读取成功;
- -EINVAL:指定的属性不存在;
- -ENODATA:没有数据可读;
- -EOVERFLOW:属性值列表太小;
例如,以下代码段表示从设备节点nd的名为"reg"的属性中读取前三个32位无符号整数值:
u32 val[3]; int ret; ret = of_property_read_u32_array(np, "reg", val, 3); if (ret) pr_err("Failed to read reg\n"); else pr_info("reg[0] = 0x%x, reg[1] = 0x%x, reg[2] = 0x%x\n", val[0], val[1], val[2]);
3.4 of_property_read_u8/16/32/64
这四个是读取设备节点中包含单个无符号数据的属性值的函数。函数原型:
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value) int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value) int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value) int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value
其中:
- np:设备节点
- propname:属性名称;
- out_value:读出的属性值;
函数返回值:
- 0:读取成功;
- -EINVAL:指定的属性不存在;
- -ENODATA:没有数据可读;
- -EOVERFLOW:属性值列表太小;
3.5 of_property_read_string
of_property_read_string函数用于读取设备节点属性中字符串值,函数原型:
of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
其中:
- np:设备节点;
- propname:属性名称;
- out_string:读出的字符串;
函数返回值:0表示读取成功,负数表示读取失败。
例如,以下代码段表示从设备节点nd的名为"compatible"的属性中读取一个字符串:
char buf[32]; int ret; ret = of_property_read_string(np, "compatible", buf, sizeof(buf)); if (ret) pr_err("Failed to read compatible\n"); else pr_info("compatible = %s\n", buf);
3.6 of_n_addr_cells
of_n_addr_cells 函数用于获取 #address-cells 属性值,函数原型:
int of_n_addr_cells(struct device_node *np)
其中:
- np:设备节点;
返回 获取到的#address-cells 属性值。
3.7 of_n_size_cells
of_size_cells 函数用于获取 #size-cells 属性值,函数原型:
int of_n_size_cells(struct device_node *np)
其中:
- np:设备节点;
返回 获取到的#size-cells 属性值。
3.8 of_property_count_elems_of_size
of_property_count_elems_of_size 函数用于获取属性中元素的数量,比如reg属性值是一个数组,那么使用此函数可以获取到这个数组的大小,此函数原型:
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);
其中:
- np:设备节点;
- propname:属性名称;
- elem_size:元素长度;
返回得到的属性元素数量。
3.9 of_property_read_u32_index
of_property_read_u32_index 函数用于从属性中获取指定标号的 u32 类型数据值,比如某个属性有多个u32类型的值,那么就可以使用此函数来获取指定标号的数据值,此函数原型:
of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value);
其中:
- np:设备节点;
- propname:属性名称;
- index:要读取的值标号;
- out_value:读取到的值;
函数返回值:
- 0:读取成功;
- -EINVAL:指定的属性不存在;
- -ENODATA:没有数据可读;
- -EOVERFLOW:属性值列表太小;
3.10 of_property_match_string
of_property_match_string函数用于查找字符串在指定属性值(字符串列表)中出现的索引,函数原型:
int of_property_match_string(const struct device_node *np, const char *propname, const char *string);
其中:
- np:设备节点;
- propname:属性名称;
- string:指向在字符串列表中查找的字符串的指针;
四、其他常用的API
Linux内核提供了大量的API来处理设备树,除了之前介绍过的API,还有大量的API在include/linux/of_xxx头文件中声明:
比如:
of.h // 提供设备树的一般处理函数, 比如 of_property_read_u32(读取某个属性的u32值), *of_get_child_count(获取某个device_node的子节点数) of_address.h // 地址相关的函数, 比如of_get_address(获得reg属性中的addr, size值) of_device.h // 设备相关的函数,比如of_match_device(从matches数组中取出与当前设备最匹配的一项) of_dma.h // 设备树中DMA相关属性的函数 of_gpio.h // GPIO相关的函数 of_graph.h // GPU相关驱动中用到的函数, 从设备树中获得GPU信息 of_iommu.h // 很少用到 of_irq.h // 中断相关的函数 of_mdio.h // MDIO (Ethernet PHY) API of_net.h // OF helpers for network devices. of_pci.h // PCI相关函数 of_pdt.h // 很少用到 of_reserved_mem.h // reserved_mem的相关函数 of_platform.h // 把device_node转换为platform_device时用到的函数
4.1 设备资源
像IIC、SPI 和GPIO等外设都有它们自己的寄存器地址,这些寄存器地址实际上是一组内存空间。Linux 内核提供了一些设备树API函数来获取这些寄存器地址。这些函数一般在include/linux/of_address.h头文件中声明;
Linux内核将寄存器、中断和其他信息描述为一组内存资源,并用resource结构体来表示。 resource 结构体定义在文件include/linux/ioport.h 中,定义如下:
struct resource { resource_size_t start; /*起始地址,对于32位soc,resource_size_t 的数据类型是u32*/ resource_size_t end; /*结束地址*/ const char *name; /*资源的名字*/ unsigned long flags; /*资源标志位,一般表示资源类型,可选的资源标志定义在文件 include/linux/ioport.h,如IORESOURCE_BITS、IORESOURCE_MEM、IORESOURCE_IRQ等 */ struct resource *parent, *sibling, *child; };
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 #define IORESOURCE_PREFETCH 0x00002000 /* No side effects */ #define IORESOURCE_READONLY 0x00004000 #define IORESOURCE_CACHEABLE 0x00008000 #define IORESOURCE_RANGELENGTH 0x00010000 #define IORESOURCE_SHADOWABLE 0x00020000 #define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */ #define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */ #define IORESOURCE_MEM_64 0x00100000 #define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */ #define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */ #define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */ #define IORESOURCE_DISABLED 0x10000000 #define IORESOURCE_UNSET 0x20000000 /* No address assigned yet */ #define IORESOURCE_AUTO 0x40000000 #define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */
4.1.1 of_address_to_resource
of_address_to_resource函数用于获取设备树节点的内存资源。函数原型如下:
int of_address_to_resource(const struct device_node *np, int index, struct resource *r);
其中:
- np:设备节点;
- index:所需获取的资源的编号;
- r:获取到的资源结构体;
返回值:如果成功则返回 0,否则返回负数。
该函数接受一个指向某个设备树节点的指针作为输入参数,然后返回一个包含该节点所描述的资源信息的struct resource 结构体。该结构体包含了资源的开始地址、结束地址、大小和标志等信息。
这个函数通常用于在驱动程序初始化阶段获取设备的资源信息,并将其映射到内存中以供使用。
4.1.2 of_iomap
of_iomap函数用于获取I/O内存资源,它会寻找设备节点中指定索引号的内存资源,并将其映射到内核虚拟地址空间中。比如可以查找/led节点reg属性指定的内存地址:
led{ compatible = "mini2440-led"; status = "okay"; reg = <0x56000010 0x04 /* GPBCON */ 0x56000014 0x04>; /* GPBDATA */ };
该函数接受一个指向设备节点的指针和资源的偏移量作为输入参数,然后返回一个指向虚拟地址空间中映射的地址的指针。
在以前没有设备树的时候,内存地址映射通常需要两个步骤;
- 首先使用 platform_get_resource函数获取资源,
- 然后使用 ioremap函数执行地址映射;
现在有了设备树,可以直接使用of_iomap函数同时进行资源获取和地址映射。以下是of_iomap函数定义:
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:资源在节点中的索引;
当调用成功时,该函数将返回一个指向映射地址的指针;如果调用失败,则返回 NULL 指针。
4.1.3 of_get_address
该函数用于获取地址相关属性,主要是 “reg” 或者 “assigned-address” 属性值;
/* Extract an address from a device, returns the region size and * the address space flags too. The PCI version uses a BAR number * instead of an absolute index */ const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags);
其中:
- dev:设备节点指针;
- index:设备节点地址索引;
- size:用于存储地址空间大小的无符号 64 位整数指针。如果该参数为 NULL,则不返回地址空间大小信息;
- flags:用于存储地址空间标志的无符号整数指针。如果该参数为 NULL,则不返回地址空间标志信息;
该函数用于从设备节点中提取地址信息,并将其作为一个指向类型为 _be32的常量指针返回。同时,它还可以获取当前地址空间的大小和标志信息。
注意:__be32是Linux 内核中定义的一个数据类型,用于表示 32 位无符号整数值,并且已经将其转换成了大端字节序(big-endian)。在网络编程中,传输的数据通常使用大端字节序来表示,以保证不同主机之间的数据交换正确性。因此,内核中很多与网络相关的数据结构和函数都会使用类似 __be32、__be16 等数据类型来进行字节序的处理。
需要注意的是,返回的地址信息和地址空间大小都已转换为大端字节序。如果获取失败,则返回 NULL 指针。
在使用该函数时,需要对返回的地址信息进行类型转换,并根据实际情况使用对应的访问方式进行访问。同时,也需要针对获取到的地址空间大小和标志信息进行相应的处理。
4.1.4 of_translate_address
该函数用于将设备树中的虚拟地址转换为物理地址,并返回转换后的 64 位物理地址。需要注意的是,该函数假设输入的地址信息已经被解释为大端字节序;
u64 of_translate_address(struct device_node *np, const __be32 *in_addr);
其中:
- np:设备节点指针;
- in_addr:指向类型为_be32的地址信息;
在使用该函数时,需要先从设备树中获取到需要转换的虚拟地址,然后将其作为in_addr参数传入函数中。函数会自动对其进行转换,并返回对应的物理地址。
4.2 platform_device相关
of_platform.h声明了将device_node转换为platform_device时用到的函数。
4.2.1 of_device_alloc
of_device_alloc函数根据device_node分配platform_device:
/* Platform drivers register/unregister */ struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent);
其中:
- np: 设备节点在设备树中对应的节点;
- bus_id: 设备所属的总线类型(如 "spi"、"i2c" 等),通常此参数被设为 NULL,表示依赖设备属性(device properties)标识符自动匹配设备总线类型;
- parent: 设备的父设备,通常设置为 NULL;
该函数返回值是新分配的platform_device结构体指针,如果分配失败则返回 NULL。
4.2.2 of_find_device_by_node
该函数在内核中查找与给定设备节点(np)相对应的平台设备。如果找到了匹配的平台设备,则返回这个平台设备的指针;否则,返回 NULL。
struct platform_device *of_find_device_by_node(struct device_node *np);
其中:
- np: 设备节点在设备树中对应的节点。
该函数常用于在设备树驱动程序中查找一个已知的平台设备,以便对其进行进一步的操作或数据访问。
在实际编写设备树驱动程序中,通常需要通过 of_find_device_by_node 函数查找与设备节点关联的设备,并使用dev_set_drvdata函数将自定义的设备数据结构(如设备驱动结构体)与平台设备绑定。
4.2.3 of_platform_device_create
该函数基于设备树节点节点(struct device_node)创建一个平台设备,并注册到内核中,该函数内部调用了of_device_alloc;
struct platform_device *of_platform_device_create(struct device_node *np,const char *bus_id,struct device *parent);
其中:
- np: 设备节点在设备树中对应的节点;
- bus_id: 设备所属的总线类型(如 "spi"、"i2c" 等),通常此参数被设为NULL,表示依赖设备属性(device properties)标识符自动匹配设备总线类型;
- parent: 设备的父设备,通常设置为NULL;
该函数返回值是新创建的平台设备(platform_device)结构体指针。如果创建失败,则返回 NULL。
在函数内部,of_platform_device_create 会使用 devm_kzalloc函数为设备结构体分配内存,并调用of_device_add函数将该设备注册到系统总线中。然后,将设备节点、总线类型和父设备作为参数传递给这个平台设备结构体,并返回新创建的平台设备结构体指针。
4.3 of_device_is_compatible
该函数用于检查给定设备节点是否与指定的设备兼容性字符串匹配。如果匹配,则返回 1,否则返回 0。此外,该函数还支持使用通配符来匹配设备兼容性字符串;
int of_device_is_compatible(const struct device_node *device, const char * compat);
其中:
- device:指向设备节点的指针;
- compat:设备兼容性字符串;
参考文章
[1]Linux设备树学习笔记(四、设备树常用 OF 操作函数)
[3]DeviceTree Kernel API — The Linux Kernel documentation
[4]Linux device tree API (programs.wiki)
[5]of.h - include/linux/of.h - Linux source code (v5.2.8) - Bootlin