接前一篇文章:嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(1)

核心数据结构:device_node、property和resource

在讲具体的API之前,我们需要先了解一下内核是用什么数据结构来表示设备树的。毕竟API只是操作这些数据结构的工具,如果不了解数据结构本身,用起API来也是一头雾水。

struct device_node:节点的内核表示

struct device_node是内核对设备树节点的描述。每个设备树节点在内核里都对应一个device_node结构体。这个结构体的定义在include/linux/of.h里,我们挑重点字段看:

struct device_node {
    const char *name;        /* 节点名字,比如 "gpio" */
    const char *type;        /* 设备类型,取自 device_type 属性 */
    phandle phandle;         /* 节点的 phandle 值 */
    const char *full_name;   /* 节点的全路径名 */
    struct fwnode_handle fwnode;

    struct property *properties;  /* 属性链表头 */
    struct property *deadprops;   /* 已删除的属性 */

    struct device_node *parent;   /* 父节点 */
    struct device_node *child;    /* 子节点 */
    struct device_node *sibling;  /* 兄弟节点 */

    struct kobject kobj;
    unsigned long _flags;
    void *data;
    /* ... 更多平台特定字段 ... */
};

这个结构体设计得很巧妙。它不仅记录了节点的名字和类型,还通过parent、child和sibling三个指针把整棵树串了起来。这意味着你可以从任意一个节点出发,往上找父节点、往下找子节点、往旁边找兄弟节点 —— 就像在真的树上爬一样(其实更像查族谱)。

struct device_node中的properties字段指向一个属性链表,所有的property结构体都挂在这个链表上。我们接下来看property结构体。

struct property:属性的内核表示

struct property {
    char *name;            /* 属性名字,比如 "reg" */
    int length;            /* 属性值的字节长度 */
    void *value;           /* 属性值,可以是任意数据 */
    struct property *next; /* 指向下一个属性 */
    unsigned long _flags;
    unsigned int unique_id;
    struct bin_attribute attr;
};

这里最关键的是value字段。它是一个void *,可以指向任意类型的数据。这是因为设备树里的属性值可以是各种类型:可能是单个整数、可能是字符串、可能是整数数组、甚至可能是任意字节序列。

那么内核怎么知道value里存的是什么类型呢?答案是:不知道。内核只知道这是一坨字节,具体怎么解释,要看属性的名字和上下文。比如status属性通常被解释为字符串,reg属性被解释为整数数组,而compatible属性被解释为字符串数组。

所以当我们用API读取属性时,需要明确告诉内核我们想要什么类型的数据。这就是为什么有of_property_read_u32()、of_property_read_string()这样不同的函数。

struct resource:资源的统一描述

Linux 内核用struct resource来统一描述各种资源 —— 不仅仅是内存映射IO,还包括中断、DMA 通道等。这个结构体定义在include/linux/ioport.h中:

struct resource {
    resource_size_t start;  /* 资源起始地址/号 */
    resource_size_t end;    /* 资源结束地址/号 */
    const char *name;       /* 资源名称 */
    unsigned long flags;    /* 资源类型标志 */
    struct resource *parent, *sibling, *child;
};

flags字段说明这是什么类型的资源:

  • IORESOURCE_MEM:内存映射IO。
  • IORESOURCE_IRQ:中断资源。
  • IORESOURCE_IO:端口IO(x86特有)。
  • IORESOURCE_DMA:DMA通道。

设备树里的reg属性可以通过of_address_to_resource()函数转换成resource结构体,这样驱动就可以用统一的方式来处理不同类型的资源了。

更多内容请看下回。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐