OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2003|回复: 0

《DFZU2EG_4EV MPSoC开发板之嵌入式Linux 驱动开发指南》第二十章 Linux设备树(下)

[复制链接]

1117

主题

1128

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4666
金钱
4666
注册时间
2019-5-8
在线时间
1224 小时
发表于 2023-6-5 11:30:35 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 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

155537c2odj87vz1z9vj6l.jpg
155537nfqovl2gg9faaol9.png

20.4 驱动与设备节点的匹配
这部分内容已经在前面跟大家讲过了,具体请看20.3.4小节中的第一个小点compatible属性介绍。

20.5 内核启动过程中解析设备树
Linux内核在启动的时候会解析内核DTB文件,然后在根文件系统的/proc/device-tree(后面给大家演示)目录下生成相应的设备树节点文件。接下来我们简单分析一下Linux内核是如何解析DTB文件的,流程如下图所示:                              
image002.png
图 20.5.1 设备树中节点解析流程

从上图中可以看出,在start_kernel函数中完成了设备树节点解析的工作,最终实际工作的函数为unflatten_dt_node。那么具体如何进行设备树解析的这里就不给大家进行一一分析了,如果大家有时间可以自个去研究研究!

20.6 设备树在系统中的体现
Linux内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree目录下根据节点名字创建不同文件夹,如下图所示:
image003.png
图 20.6.1 根节点的属性以及子节点

上图列出来就是/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这两个文件的内容,结果如下图所示:
image005.png
图 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节点的所有子节点,如所示:
image007.png
图 20.6.3 amba节点的所有属性和子节点

和根节点“/”一样,图 20.6.3中的所有文件分别为amba节点的属性文件和子节点文件夹。大家可以自行查看一下这些属性文件的内容是否和我们使用的设备树中amba节点的属性值相同。

20.7 绑定信息文档
设备树是用来描述板子上的硬件设备信息的,不同的设备其信息不同,反映到设备树中就是属性不同。那么我们在设备树中添加一个硬件对应的节点的时候从哪里查阅相关的说明呢?在Linux内核源码中有详细的.txt文档描述了如何添加节点,这些.txt文档叫做绑定文档,路径为:Linux源码目录/Documentation/devicetree/bindings,如所示:
image009.png
图 20.7.1 绑定文档

这些文档详细的描述了如何在设备树中添加设备节点,有时候使用的一些芯片在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中,定义如下:
  1. 示例代码20.8.1.1 device_node节点
  2. 49 structdevice_node {
  3. 50     constchar *name;                /*节点名字 */
  4. 51     constchar *type;                /*设备类型 */
  5. 52     phandle phandle;
  6. 53     constchar *full_name;             /*节点全名 */
  7. 54     structfwnode_handle fwnode;
  8. 55
  9. 56     struct  property *properties;     /* 属性 */
  10. 57     struct  property *deadprops;       /*removed属性 */
  11. 58     struct  device_node *parent;       /* 父节点 */
  12. 59     struct  device_node *child;        /*子节点 */
  13. 60     struct  device_node *sibling;
  14. 61     struct  kobject kobj;
  15. 62     unsignedlong _flags;
  16. 63     void    *data;
  17. 64 #ifdefined(CONFIG_SPARC)
  18. 65     constchar *path_component_name;
  19. 66     unsignedint unique_id;
  20. 67     structof_irq_controller *irq_trans;
  21. 68 #endif
  22. 69 };
复制代码
与查找节点有关的OF函数有5个,我们依次来看一下。

1、of_find_node_by_name函数
of_find_node_by_name函数通过节点名字查找指定的节点,函数原型如下:
  1. structdevice_node *of_find_node_by_name(struct device_node        *from,
  2. const char                    *name);
复制代码
函数参数和返回值含义如下:
from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
name:要查找的节点名字。
返回值:找到的节点,如果为NULL表示查找失败。

2、of_find_node_by_type函数
of_find_node_by_type函数通过device_type属性查找指定的节点,函数原型如下:
  1. 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这两个属性查找指定的节点,函数原型如下:
  1. structdevice_node *of_find_compatible_node(struct device_node     *from,
  2.                                    const char                    *type,
  3.                                                                 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匹配表来查找指定的节点,函数原型如下:
  1. structdevice_node *of_find_matching_node_and_match(struct device_node           *from,
  2.                                                                       const struct of_device_id   *matches,
  3.                                                                                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函数通过节点路径来查找指定的节点,函数原型如下:
  1. 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函数用于获取指定节点的父节点(如果有父节点的话),函数原型如下:
  1. structdevice_node *of_get_parent(const struct device_node *node)
复制代码
函数参数和返回值含义如下:
node:要查找的父节点的节点。
返回值:找到的父节点。

2、of_get_next_child函数
of_get_next_child函数用迭代的查找子节点,函数原型如下:
  1. structdevice_node *of_get_next_child(const struct device_node       *node,
  2.                                                     struct device_node                 *prev)
复制代码
函数参数和返回值含义如下:
node:父节点。
prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
返回值:找到的下一个子节点。

20.8.3 提取属性值的OF函数
设备树节点的属性保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux内核中使用结构体property表示属性,此结构体同样定义在文件include/linux/of.h中,内容如下:
  1. 示例代码20.8.3.1 property结构体
  2. 35 structproperty {
  3. 36     char    *name;                /*属性名字 */
  4. 37     int    length;               /*属性长度 */
  5. 38      void    *value;              /*属性值 */
  6. 39     structproperty *next;     /* 下一个属性 */
  7. 40     unsignedlong _flags;
  8. 41     unsignedint unique_id;
  9. 42     structbin_attribute attr;
  10. 43 };
复制代码
Linux内核也提供了提取属性值的OF函数,我们依次来看一下。

1、of_find_property函数
of_find_property函数用于查找指定的属性,函数原型如下:
  1. property*of_find_property(const struct device_node  *np,
  2.                                     const char                        *name,
  3.                                     int                                  *lenp)
复制代码
函数参数和返回值含义如下:
np:设备节点。
name: 属性名字。
lenp:属性值的字节数
返回值:找到的属性。

2、of_property_count_elems_of_size函数
of_property_count_elems_of_size函数用于获取属性中元素的数量,比如reg属性值是一个数组,那么使用此函数可以获取到这个数组的大小,此函数原型如下:
  1. intof_property_count_elems_of_size(const struct device_node  *np,
  2.                                                    const char                        *propname,
  3.                                                   int                                  elem_size)
复制代码
函数参数和返回值含义如下:
np:设备节点。
proname: 需要统计元素数量的属性名字。
elem_size:元素长度。
返回值:得到的属性元素数量。

3、of_property_read_u32_index函数
of_property_read_u32_index函数用于从属性中获取指定下标(属性值是一个u32数据组成的数组)的u32类型数据值(无符号32位),比如某个属性有多个u32类型的值,那么就可以使用此函数来获取指定下标的数据值,此函数原型如下:
  1. intof_property_read_u32_index(const struct device_node  *np,
  2.                                       const char                        *propname,
  3.                                       u32                               index,
  4.                                             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属性中的所有数据。这四个函数的原型如下:
  1. intof_property_read_u8_array(const struct device_node     *np,
  2.                                            const char                         *propname,
  3.                                            u8                                    *out_values,
  4.                                            size_t                               sz)
  5. intof_property_read_u16_array(const struct device_node   *np,
  6.                                             const char                        *propname,
  7.                                             u16                                 *out_values,
  8.                                             size_t                              sz)
  9. intof_property_read_u32_array(const struct device_node   *np,
  10.                                     const char                        *propname,
  11.                                             u32                                 *out_values,
  12.                                     size_t                              sz)
  13. intof_property_read_u64_array(const struct device_node   *np,
  14.                                     const char                        *propname,
  15.                                             u64                                 *out_values,
  16.                                    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类型属性值,函数原型如下:
  1. intof_property_read_u8(const struct device_node       *np,
  2.                                     const char                         *propname,
  3.                                    u8                                    *out_value)
  4. intof_property_read_u16(const struct device_node     *np,
  5.                                     const char                         *propname,
  6.                                    u16                                  *out_value)
  7. intof_property_read_u32(const struct device_node     *np,
  8.                                     const char                         *propname,
  9.                                    u32                                  *out_value)
  10. intof_property_read_u64(const struct device_node     *np,
  11.                                     const char                         *propname,
  12.                                    u64                                  *out_value)
复制代码
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值。
返回值:0,读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小。

6、of_property_read_string函数
of_property_read_string函数用于读取属性中字符串值,函数原型如下:
  1. intof_property_read_string(struct device_node   *np,
  2.                                    const char                 *propname,
  3.                                      const char                 **out_string)
复制代码
函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_string:读取到的字符串值。
返回值:0,读取成功,负值,读取失败。

7、of_n_addr_cells函数
of_n_addr_cells函数用于获取#address-cells属性值,函数原型如下:
  1. intof_n_addr_cells(struct device_node *np)
复制代码
函数参数和返回值含义如下:
np:设备节点。
返回值:获取到的#address-cells属性值。

8、of_n_size_cells函数
of_size_cells函数用于获取#size-cells属性值,函数原型如下:
  1. 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指定的字符串,也就是检查设备节点的兼容性,函数原型如下:
  1. intof_device_is_compatible(const struct device_node        *device,
  2.                                      const char                               *compat)
复制代码
函数参数和返回值含义如下:
device:设备节点。
compat:要查看的字符串。
返回值:0,节点的compatible属性中不包含compat指定的字符串;正数,节点的compatible属性中包含compat指定的字符串。

2、of_get_address函数
of_get_address函数用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值,函数属性如下:
  1. const__be32 *of_get_address(struct device_node       *dev,
  2.                                         int                            index,
  3.                                         u64                           *size,
  4.                                      unsigned int               *flags)
复制代码
函数参数和返回值含义如下:
dev:设备节点。
index:要读取的地址标号。
size:地址长度。
flags:参数,比如IORESOURCE_IO、IORESOURCE_MEM等
返回值:读取到的地址数据首地址,为NULL的话表示读取失败。

3、of_translate_address函数
of_translate_address函数负责将从设备树读取到的地址转换为物理地址,函数原型如下:
  1. u64of_translate_address(struct device_node       *dev,
  2. 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中,定义如下:
  1. 示例代码20.8.4.1 resource结构体
  2. 18 structresource {
  3. 19     resource_size_t start;
  4. 20     resource_size_t end;
  5. 21     constchar *name;
  6. 22     unsignedlong flags;
  7. 23     unsigned longdesc;
  8. 24     structresource *parent,*sibling,*child;
  9. 25 };
复制代码
对于32位的SOC来说,resource_size_t是u32类型的。其中start表示开始地址,end表示结束地址,name是这个资源的名字,flags是资源标志位,一般表示资源类型,可选的资源标志定义在文件include/linux/ioport.h中,如下所示:
  1. 示例代码20.8.4.2 资源标志
  2. 1 #define IORESOURCE_BITS            0x000000ff  
  3. 2 #define IORESOURCE_TYPE_BITS      0x00001f00  
  4. 3 #define IORESOURCE_IO               0x00000100  
  5. 4 #define IORESOURCE_MEM              0x00000200
  6. 5 #define IORESOURCE_REG              0x00000300  
  7. 6 #define IORESOURCE_IRQ              0x00000400
  8. 7 #define IORESOURCE_DMA              0x00000800
  9. 8 #define IORESOURCE_BUS              0x00001000
  10. 9 #define IORESOURCE_PREFETCH        0x00002000  
  11. 10 #define IORESOURCE_READONLY       0x00004000
  12. 11 #define IORESOURCE_CACHEABLE       0x00008000
  13. 12 #define IORESOURCE_RANGELENGTH    0x00010000
  14. 13 #define IORESOURCE_SHADOWABLE     0x00020000
  15. 14 #define IORESOURCE_SIZEALIGN       0x00040000  
  16. 15 #define IORESOURCE_STARTALIGN     0x00080000  
  17. 16 #define IORESOURCE_MEM_64          0x00100000
  18. 17 #define IORESOURCE_WINDOW          0x00200000  
  19. 18 #define IORESOURCE_MUXED          0x00400000  
  20. 19 #define IORESOURCE_EXCLUSIVE       0x08000000  
  21. 20 #define IORESOURCE_DISABLED        0x10000000
  22. 21 #define IORESOURCE_UNSET          0x20000000
  23. 22 #define IORESOURCE_AUTO            0x40000000
  24. 23 #define IORESOURCE_BUSY            0x80000000
复制代码
大家一般最常见的资源标志就是IORESOURCE_MEM、IORESOURCE_REG和IORESOURCE_IRQ等。接下来我们回到of_address_to_resource函数,此函数看名字像是从设备树里面提取资源值,但是本质上就是将reg属性值,然后将其转换为resource结构体类型,函数原型如下所示
  1. intof_address_to_resource(struct device_node     *dev,
  2.                                      int                           index,
  3.                                    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函数原型如下:
  1. void__iomem *of_iomap(struct device_node      *np,
  2.                                     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驱动实验都将采用设备树,从最基本的点灯,到复杂的音频、网络或块设备等驱动。将会带领大家由简入深,深度剖析设备树,最终掌握基于设备树的驱动开发技能。

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 09:28

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表