本帖最后由 正点原子运营 于 2023-6-3 10:42 编辑
第二十章 Linux设备树
1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板
2) 章节摘自【正点原子】DFZU2EG_4EV MPSoC开发板之嵌入式Linux 驱动开发指南 V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=692450874670
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-MPSOC.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)Linux技术交流QQ群:299746173
20.4 驱动与设备节点的匹配这部分内容已经在前面跟大家讲过了,具体请看20.3.4小节中的第一个小点compatible属性介绍。
20.5 内核启动过程中解析设备树Linux内核在启动的时候会解析内核DTB文件,然后在根文件系统的/proc/device-tree(后面给大家演示)目录下生成相应的设备树节点文件。接下来我们简单分析一下Linux内核是如何解析DTB文件的,流程如下图所示: 从上图中可以看出,在start_kernel函数中完成了设备树节点解析的工作,最终实际工作的函数为unflatten_dt_node。那么具体如何进行设备树解析的这里就不给大家进行一一分析了,如果大家有时间可以自个去研究研究!
20.6 设备树在系统中的体现Linux内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree目录下根据节点名字创建不同文件夹,如下图所示: 上图列出来就是/proc/device-tree目录下的内容,/proc/device-tree目录下是根节点“/”的所有属性和子节点,我们依次来看一下这些属性和子节点。
1、根节点“/”各个属性 在图 20.6.1中,根节点下的属性表现为一个个的文件(大家可以用ls -l查看到文件的类型),比如图 20.6.1中的“#address-cells”、“#size-cells”、“compatible”、“model”和“name”这5个文件,它们在设备树中就是根节点的5个属性。既然是文件那么肯定可以查看其内容,输入cat命令来查看model和compatible这两个文件的内容,结果如下图所示: 图 20.6.2 model和compatible文件内容 从图 20.6.2可以看出,文件model的内容是“Alientek Zynq MpSocDevelopment Board”,文件compatible的内容为“xlnx,zynqmp”。这跟system-user.dtsi文件根节点的model属性值、以及zynqmp.dtsi文件根节点的compatible属性值是完全一样的。
2、根节点“/”各子节点 图 20.6.1中列出的各个文件夹就是根节点“/”的各个子节点,比如“aliases”、“cpus”、“chosen”和“amba”等等。大家可以查看我们用到的设备树文件,看看根节点的子节点都有哪些,看看是否和图 20.6.1中的一致。
/proc/device-tree目录就是设备树在根文件系统中的体现,同样是按照树形结构组织的,进入/proc/device-tree/amba目录中就可以看到amba节点的所有子节点,如所示: 和根节点“/”一样,图 20.6.3中的所有文件分别为amba节点的属性文件和子节点文件夹。大家可以自行查看一下这些属性文件的内容是否和我们使用的设备树中amba节点的属性值相同。
20.7 绑定信息文档设备树是用来描述板子上的硬件设备信息的,不同的设备其信息不同,反映到设备树中就是属性不同。那么我们在设备树中添加一个硬件对应的节点的时候从哪里查阅相关的说明呢?在Linux内核源码中有详细的.txt文档描述了如何添加节点,这些.txt文档叫做绑定文档,路径为:Linux源码目录/Documentation/devicetree/bindings,如所示: 这些文档详细的描述了如何在设备树中添加设备节点,有时候使用的一些芯片在Documentation/devicetree/bindings目录下找不到对应的文档,这个时候就要咨询芯片的提供商,让他们给你提供参考的设备树文件。
20.8 设备树常用of操作函数设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。比如设备树使用reg属性描述了某个外设的寄存器地址为0X02005482,长度为0X400,我们在编写驱动的时候需要获取到reg属性的0X02005482和0X400这两个值,然后初始化外设。Linux内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_”,所以在很多资料里面也被叫做OF函数。这些OF函数原型都定义在include/linux/of.h文件中。
20.8.1 查找节点的OF函数 设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的属性信息,必须先获取到这个设备的节点。Linux内核使用device_node结构体来描述一个节点,此结构体定义在文件include/linux/of.h中,定义如下: - 示例代码20.8.1.1 device_node节点
- 49 structdevice_node {
- 50 constchar *name; /*节点名字 */
- 51 constchar *type; /*设备类型 */
- 52 phandle phandle;
- 53 constchar *full_name; /*节点全名 */
- 54 structfwnode_handle fwnode;
- 55
- 56 struct property *properties; /* 属性 */
- 57 struct property *deadprops; /*removed属性 */
- 58 struct device_node *parent; /* 父节点 */
- 59 struct device_node *child; /*子节点 */
- 60 struct device_node *sibling;
- 61 struct kobject kobj;
- 62 unsignedlong _flags;
- 63 void *data;
- 64 #ifdefined(CONFIG_SPARC)
- 65 constchar *path_component_name;
- 66 unsignedint unique_id;
- 67 structof_irq_controller *irq_trans;
- 68 #endif
- 69 };
复制代码与查找节点有关的OF函数有5个,我们依次来看一下。
1、of_find_node_by_name函数 of_find_node_by_name函数通过节点名字查找指定的节点,函数原型如下: - structdevice_node *of_find_node_by_name(struct device_node *from,
- const char *name);
复制代码函数参数和返回值含义如下: from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。 name:要查找的节点名字。 返回值:找到的节点,如果为NULL表示查找失败。
2、of_find_node_by_type函数 of_find_node_by_type函数通过device_type属性查找指定的节点,函数原型如下: - structdevice_node *of_find_node_by_type(struct device_node *from, const char *type)
复制代码函数参数和返回值含义如下: from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。 type:要查找的节点对应的type字符串,也就是device_type属性值。 返回值:找到的节点,如果为NULL表示查找失败。
3、of_find_compatible_node函数 of_find_compatible_node函数根据device_type和compatible这两个属性查找指定的节点,函数原型如下: - structdevice_node *of_find_compatible_node(struct device_node *from,
- const char *type,
- const char *compatible)
复制代码函数参数和返回值含义如下: from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。 type:要查找的节点对应的type字符串,也就是device_type属性值,可以为NULL,表示忽略掉device_type属性。 compatible:要查找的节点所对应的compatible属性列表。 返回值:找到的节点,如果为NULL表示查找失败
4、of_find_matching_node_and_match函数 of_find_matching_node_and_match函数通过of_device_id匹配表来查找指定的节点,函数原型如下: - structdevice_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表示查找失败
5、of_find_node_by_path函数 of_find_node_by_path函数通过节点路径来查找指定的节点,函数原型如下: - inlinestruct device_node *of_find_node_by_path(const char *path)
复制代码函数参数和返回值含义如下: path:带有全路径的节点名,可以使用节点的别名(用aliens节点中定义的别名)。 返回值:找到的节点,如果为NULL表示查找失败
20.8.2 查找父/子节点的OF函数Linux内核提供了几个查找节点对应的父节点或子节点的OF函数,我们依次来看一下。
1、of_get_parent函数 of_get_parent函数用于获取指定节点的父节点(如果有父节点的话),函数原型如下: - structdevice_node *of_get_parent(const struct device_node *node)
复制代码函数参数和返回值含义如下: node:要查找的父节点的节点。 返回值:找到的父节点。
2、of_get_next_child函数 of_get_next_child函数用迭代的查找子节点,函数原型如下: - structdevice_node *of_get_next_child(const struct device_node *node,
- struct device_node *prev)
复制代码函数参数和返回值含义如下: node:父节点。 prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。 返回值:找到的下一个子节点。
20.8.3 提取属性值的OF函数设备树节点的属性保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux内核中使用结构体property表示属性,此结构体同样定义在文件include/linux/of.h中,内容如下: - 示例代码20.8.3.1 property结构体
- 35 structproperty {
- 36 char *name; /*属性名字 */
- 37 int length; /*属性长度 */
- 38 void *value; /*属性值 */
- 39 structproperty *next; /* 下一个属性 */
- 40 unsignedlong _flags;
- 41 unsignedint unique_id;
- 42 structbin_attribute attr;
- 43 };
复制代码Linux内核也提供了提取属性值的OF函数,我们依次来看一下。
1、of_find_property函数 of_find_property函数用于查找指定的属性,函数原型如下: - property*of_find_property(const struct device_node *np,
- const char *name,
- int *lenp)
复制代码函数参数和返回值含义如下: np:设备节点。 name: 属性名字。 lenp:属性值的字节数 返回值:找到的属性。
2、of_property_count_elems_of_size函数 of_property_count_elems_of_size函数用于获取属性中元素的数量,比如reg属性值是一个数组,那么使用此函数可以获取到这个数组的大小,此函数原型如下: - intof_property_count_elems_of_size(const struct device_node *np,
- const char *propname,
- int elem_size)
复制代码函数参数和返回值含义如下: np:设备节点。 proname: 需要统计元素数量的属性名字。 elem_size:元素长度。 返回值:得到的属性元素数量。
3、of_property_read_u32_index函数 of_property_read_u32_index函数用于从属性中获取指定下标(属性值是一个u32数据组成的数组)的u32类型数据值(无符号32位),比如某个属性有多个u32类型的值,那么就可以使用此函数来获取指定下标的数据值,此函数原型如下: - intof_property_read_u32_index(const struct device_node *np,
- const char *propname,
- u32 index,
- u32 *out_value)
复制代码函数参数和返回值含义如下: np:设备节点。 proname: 要读取的属性名字。 index:要读取的值的下标。 out_value:读取到的值 返回值:0读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小。
4、of_property_read_u8_array函数 of_property_read_u16_array函数 of_property_read_u32_array函数 of_property_read_u64_array函数 这4个函数分别是读取属性中u8、u16、u32和u64类型的数组数据,比如大多数的reg属性都是数组数据,可以使用这4个函数一次读取出reg属性中的所有数据。这四个函数的原型如下: - intof_property_read_u8_array(const struct device_node *np,
- const char *propname,
- u8 *out_values,
- size_t sz)
- intof_property_read_u16_array(const struct device_node *np,
- const char *propname,
- u16 *out_values,
- size_t sz)
- intof_property_read_u32_array(const struct device_node *np,
- const char *propname,
- u32 *out_values,
- size_t sz)
- intof_property_read_u64_array(const struct device_node *np,
- const char *propname,
- u64 *out_values,
- size_t sz)
复制代码函数参数和返回值含义如下: np:设备节点。 proname: 要读取的属性名字。 out_value:读取到的数组值,分别为u8、u16、u32和u64。 sz:要读取的数组元素数量。 返回值:0,读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小。
5、of_property_read_u8函数 of_property_read_u16函数 of_property_read_u32函数 of_property_read_u64函数 有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性,分别用于读取u8、u16、u32和u64类型属性值,函数原型如下: - intof_property_read_u8(const struct device_node *np,
- const char *propname,
- u8 *out_value)
- intof_property_read_u16(const struct device_node *np,
- const char *propname,
- u16 *out_value)
- intof_property_read_u32(const struct device_node *np,
- const char *propname,
- u32 *out_value)
- intof_property_read_u64(const struct device_node *np,
- const char *propname,
- u64 *out_value)
复制代码函数参数和返回值含义如下: np:设备节点。 proname: 要读取的属性名字。 out_value:读取到的数组值。 返回值:0,读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小。
6、of_property_read_string函数 of_property_read_string函数用于读取属性中字符串值,函数原型如下: - intof_property_read_string(struct device_node *np,
- const char *propname,
- const char **out_string)
复制代码函数参数和返回值含义如下: np:设备节点。 proname: 要读取的属性名字。 out_string:读取到的字符串值。 返回值:0,读取成功,负值,读取失败。
7、of_n_addr_cells函数 of_n_addr_cells函数用于获取#address-cells属性值,函数原型如下: - intof_n_addr_cells(struct device_node *np)
复制代码函数参数和返回值含义如下: np:设备节点。 返回值:获取到的#address-cells属性值。
8、of_n_size_cells函数 of_size_cells函数用于获取#size-cells属性值,函数原型如下: - intof_n_size_cells(struct device_node *np)
复制代码函数参数和返回值含义如下: np:设备节点。 返回值:获取到的#size-cells属性值。
20.8.4 其他常用的OF函数1、of_device_is_compatible函数 of_device_is_compatible函数用于查看节点的compatible属性是否有包含compat指定的字符串,也就是检查设备节点的兼容性,函数原型如下: - intof_device_is_compatible(const struct device_node *device,
- const char *compat)
复制代码函数参数和返回值含义如下: device:设备节点。 compat:要查看的字符串。 返回值:0,节点的compatible属性中不包含compat指定的字符串;正数,节点的compatible属性中包含compat指定的字符串。
2、of_get_address函数 of_get_address函数用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值,函数属性如下: - 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的话表示读取失败。
3、of_translate_address函数 of_translate_address函数负责将从设备树读取到的地址转换为物理地址,函数原型如下: - u64of_translate_address(struct device_node *dev,
- const __be32 *in_addr)
复制代码函数参数和返回值含义如下: dev:设备节点。 in_addr:要转换的地址。 返回值:得到的物理地址,如果为OF_BAD_ADDR的话表示转换失败。
4、of_address_to_resource函数 IIC、SPI、GPIO等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间,Linux内核使用resource结构体来描述一段内存空间,“resource”翻译出来就是“资源”,因此用resource结构体描述的都是设备资源信息,resource结构体定义在文件include/linux/ioport.h中,定义如下: - 示例代码20.8.4.1 resource结构体
- 18 structresource {
- 19 resource_size_t start;
- 20 resource_size_t end;
- 21 constchar *name;
- 22 unsignedlong flags;
- 23 unsigned longdesc;
- 24 structresource *parent,*sibling,*child;
- 25 };
复制代码对于32位的SOC来说,resource_size_t是u32类型的。其中start表示开始地址,end表示结束地址,name是这个资源的名字,flags是资源标志位,一般表示资源类型,可选的资源标志定义在文件include/linux/ioport.h中,如下所示: - 示例代码20.8.4.2 资源标志
- 1 #define IORESOURCE_BITS 0x000000ff
- 2 #define IORESOURCE_TYPE_BITS 0x00001f00
- 3 #define IORESOURCE_IO 0x00000100
- 4 #define IORESOURCE_MEM 0x00000200
- 5 #define IORESOURCE_REG 0x00000300
- 6 #define IORESOURCE_IRQ 0x00000400
- 7 #define IORESOURCE_DMA 0x00000800
- 8 #define IORESOURCE_BUS 0x00001000
- 9 #define IORESOURCE_PREFETCH 0x00002000
- 10 #define IORESOURCE_READONLY 0x00004000
- 11 #define IORESOURCE_CACHEABLE 0x00008000
- 12 #define IORESOURCE_RANGELENGTH 0x00010000
- 13 #define IORESOURCE_SHADOWABLE 0x00020000
- 14 #define IORESOURCE_SIZEALIGN 0x00040000
- 15 #define IORESOURCE_STARTALIGN 0x00080000
- 16 #define IORESOURCE_MEM_64 0x00100000
- 17 #define IORESOURCE_WINDOW 0x00200000
- 18 #define IORESOURCE_MUXED 0x00400000
- 19 #define IORESOURCE_EXCLUSIVE 0x08000000
- 20 #define IORESOURCE_DISABLED 0x10000000
- 21 #define IORESOURCE_UNSET 0x20000000
- 22 #define IORESOURCE_AUTO 0x40000000
- 23 #define IORESOURCE_BUSY 0x80000000
复制代码大家一般最常见的资源标志就是IORESOURCE_MEM、IORESOURCE_REG和IORESOURCE_IRQ等。接下来我们回到of_address_to_resource函数,此函数看名字像是从设备树里面提取资源值,但是本质上就是将reg属性值,然后将其转换为resource结构体类型,函数原型如下所示 - intof_address_to_resource(struct device_node *dev,
- int index,
- struct resource *r)
复制代码函数参数和返回值含义如下: dev:设备节点。 index:地址资源标号。 r:得到的resource类型的资源值。 返回值:0,成功;负值,失败。
5、of_iomap函数 of_iomap函数用于直接内存映射,以前我们会通过ioremap函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过of_iomap函数来获取内存地址所对应的虚拟地址,不需要使用ioremap函数了。当然了,你也可以使用ioremap函数来完成物理地址到虚拟地址的内存映射,只是在采用设备树以后,大部分的驱动都使用of_iomap函数了。of_iomap函数本质上也是将reg属性中地址信息转换为虚拟地址,如果reg属性有多段的话,可以通过index参数指定要完成内存映射的是哪一段,of_iomap函数原型如下: - void__iomem *of_iomap(struct device_node *np,
- int index)
复制代码函数参数和返回值含义如下: np:设备节点。 index:reg属性中要完成内存映射的段,如果reg属性只有一段的话index就设置为0。 返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话表示内存映射失败。
关于设备树常用的OF函数就先讲解到这里,Linux内核中关于设备树的OF函数不仅仅只有前面讲的这几个,还有很多OF函数我们并没有讲解,这些没有讲解的OF函数要结合具体的驱动,比如获取中断号的OF函数、获取GPIO的OF函数等等,这些OF函数我们在后面的驱动实验中再详细的讲解。
关于设备树就讲解到这里,关于设备树我们重点要了解一下几点内容: ①、DTS、DTB和DTC之间的区别,如何将.dts文件编译为.dtb文件。 ②、设备树语法,这个是重点,因为在实际工作中我们是需要修改设备树的。 ③、设备树的几个特殊子节点。 ④、关于设备树的OF操作函数,也是重点,因为设备树最终是被驱动文件所使用的,而驱动文件必须要读取设备树中的属性信息,比如内存信息、GPIO信息、中断信息等等。要想在驱动中读取设备树的属性值,那么就必须使用Linux内核提供的众多的OF函数。
从下一章开始所有的Linux驱动实验都将采用设备树,从最基本的点灯,到复杂的音频、网络或块设备等驱动。将会带领大家由简入深,深度剖析设备树,最终掌握基于设备树的驱动开发技能。
|