嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(2)
接前一篇文章:嵌入式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结构体,这样驱动就可以用统一的方式来处理不同类型的资源了。
更多内容请看下回。
