程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

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 操作函数)

[2]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

[6]drivers/of/platform.c - Linux Kernel Source Code

posted @ 2023-04-20 20:37  大奥特曼打小怪兽  阅读(437)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步