Linux驱动开发——设备树随记

Linux驱动开发——设备树随记

前言

在嵌入式Linux这块,对设备树一直都没怎么去了解,一直是模模糊糊的。所以最近也是被老大赶鸭子上架,快速跟着正点原子的驱动开发的课程学了一下。感觉对设备树的认识也是更清晰了一点。同样借着此篇博客记录了一下我的理解。起一个备忘的作用也希望能帮到其他人。

正文

其实类比理解的话DTS相当于.c源文件,文件描述板级设备信息。一个平台或机器对应一个.dts源文件。

  • DTI相当于c语言的头文件。

  • DTC相当于于gcc,可以将dts文件编译生成dtb文件

  • DTB相当于二进制.o文件,由DTC将DTS编译生成。

严格来讲,DTI是描述芯片以及芯片周围的一些外设(片上外设)的,比如:CPU的一些参数、总线、总线上的中断控制器、时钟、GPIO的参数、UART控制器、I2C控制器、SPI控制器等等。这些东西都是和芯片强绑定的。只要是你用IMX6ULL这颗芯片,那么它的片上外设就是这些。不存在不同。

而DTS则会描述具体的片外外设的一些参数信息。比如这个外设接在哪个GPIO口?这个外设要设置什么样的GPIO属性?等等。片外外设是围绕着IMX6ULL这颗芯片来设计不同的板载。比如利用IMX6ULL芯片设计出一个路由器、摄像头、交换机等。因为共用一个芯片。所以它们一定会使用同一个DTI。

一个节点名(node name)命名形如name@unit_addr,从命名上可以分成两个部分:@前面代表name(可重复)、@后面代表该节点外设在内存当中对应的首地址。特别的,如下所示name之前有个冒号和简称。冒号前面的称为标签(也可以理解为别名,不可重复),可以代替节点名来访问改节点。当节点代表一个设备时,比如一个I2C设备,@后面的数字代表设备的从机地址。

/{intc: interrupt-controller@00a01000 {/* ... */};
}

使用&符号可以向标签所代表的节点当中添加一些所需要设置的属性。比如

在开发板启动后,可以在文件系统当中,看到设备树的一些信息。在目录/proc/device-tree下,使用文件树的方式构建了设备树(节点作为目录、属性作为文件)。

两个特殊的节点:aliases和chosen

对于aliases节点其实翻译过来就是别名,以imx6ull.dtsi文件为例:

/ {aliases {gpio0 = &gpio1;gpio1 = &gpio2;i2c0 = &i2c1;i2c1 = &i2c2;serial0 = &uart1;serial1 = &uart2;serial2 = &uart3;serial7 = &uart8;/* ... */};soc {aips1 {gpio1: gpio@0209c000 {/* ... */};gpio2: gpio@020a0000 {/* ... */};/* ... */}}
}

可以看到,其实就是为各个标签起了一个别名。但是这就有一个疑问:标签和别名之间的区别是什么?

根据其他人提供的线索去查阅文档:https://elinux.org/Device_Tree_Mysteries#Label_vs_aliases_node_property

其中关键的一段话是:The aliases are not used directly in the device tree source, but are instead dereferenced by the Linux kernel. When a path is provided to of_find_node_by_path() or of_find_node_opts_by_path(), if the path does not begin with a “/” then the first element of the path must be a property name in the “/aliases” node. That element is replaced with the full path from the alias.

简单来讲,DTS、DTI文件无法使用别名,只能使用标签。标签最终会被解释为节点的绝对路径。而别名是被内核所使用的,当内核调用of_find_node_by_pathof_find_node_opts_by_path函数时,如果提供的的节点的路径不是绝对路径的话,就会把它视作在aliases节点下定义的别名,通过别名来获得节点的绝对路径。

对于chosen节点,查阅正点原子的IMX6ULL驱动开发指南得43.6.2小结得知,Uboot在启动内核前会向chosen添加一个bootargs属性,其内容为Uboot环境变量当中的bootargs的值。同时,bootargs也会作为内核启动的cmdline参数。

标准属性

compatible属性

compatible属性会维护一个形如:manufacture,model的驱动兼容列表,驱动程序会根据该列表判断是否与设备兼容。

例如现在有一个设备节点compatible属性值如下:

compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";

根据正点原子手册描述得知:上述compatible属性值有两个,分别为“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字。sound这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查。

一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的何一个值相等,那么就表示设备可以使用这个驱动。

model属性

代表设备名

status属性

表示设备可操作的状态:

含义
okay表示设备是可操作的
disable设备当前不可操作,但未来可能可操作,比如那些热插拔的设备
fail/fail-xxx设备出错了

reg、#address-cells和#size-cells 属性

#address-cells 和#size-cells 这两个属性可以用在任
何拥有子节点的设备中。一般和reg属性配合使用使用。reg属性一般格式如下:

reg = <address1 length1 address2 length2 address3 length3……>

当父节点定义了#address-cells和#size-cells 属性,子节点在定义reg属性时一个小单元就受到父节点定义的#address-cells和#size-cells 属性的约束,比如当父节点定义#address-cells为2、#size-cells为1时,就说明子节点reg属性当中一个单元由:两个地址 + 一个长度组成,当然典型的#address-cells和#size-cells 属性的值分别为1、1,这样reg值就和上面代码块所展示的一样。

这里还是一三个示例来说明一下:

对于#address-cells为1,#size-cells为0的情况:

spi4 {compatible = "spi-gpio";#address-cells = <1>;#size-cells = <0>;gpio_spi: gpio_spi@0 {compatible = "fairchild,74hc595";reg = <0>;};
};

表示gpio_spi当中的reg属性只有address值。

reg = <address1 address2 address3 ……>

对于#address-cells为1,#size-cells为1的情况:

spba-bus@02000000 {compatible = "fsl,spba-bus", "simple-bus";#address-cells = <1>;#size-cells = <1>;reg = <0x02000000 0x40000>;ranges;ecspi1: ecspi@02008000 {#address-cells = <1>;#size-cells = <0>;compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02008000 0x4000>;status = "disabled";};
}

表示ecspi1当中的reg属性一个单元组成是:address length。

reg = <address1 length1 address2 length2 address3 length3……>

对于#address-cells为2,#size-cells为1的情况:

external-bus {#address-cells = <2>#size-cells = <1>;...ethernet@0,0 {compatible = "smc,smc91c111";reg = <0 0 0x1000>;};i2c@1,0 {compatible = "acme,a1234-i2c-bus";#address-cells = <1>;#size-cells = <0>;reg = <1 0 0x1000>;};flash@2,0 {compatible = "samsung,k8f1315ebm", "cfi-flash";reg = <2 0 0x4000000>;};};    

表示i2c当中的reg属性一个单元组成是:address address length。

reg = <address1_1 address1_2 length1 address2_1 address2_2 length2 address3_1 address3_2 length3……>

特别注意的是#address-cells、#size-cells定义的都是子节点的reg规则,而不是本节点!!!

其他属性

对于range属性,IMX6ULL设备树当中是没有使用的(有,但都为空),它的值一般格式为:

<child-bus-address,parent-bus-address,length>

当父节点定义此属性时,代表将子节点从child-bus-address地址开始,映射到父节点起始地址parent-bus-address处,并映射length这么长的一个范围。

对于name属性:name 属性值为字符串,name 属性用于记录节点名字,name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性

device_type属性:属性值为字符串,IEEE 1275 会用到此属性,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。此属性只能用于 cpu 节点或者 memory 节点。imx6ull.dtsi 的 cpu0 节点用到了此属性,内容如下所示:

cpu0: cpu@0 {compatible = "arm,cortex-a7";device_type = "cpu";
}

对于根节点的compatible属性:。Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话才会启动 Linux 内核。

OF函数 —— 驱动和设备树交互的桥梁

OF函数定义的头文件:include/linux/of.h

查找节点的 OF 函数

  • struct device_node *of_find_node_by_name(struct device_node *from, const char *name)

    描述:通过节点名字查找指定的节点。

    • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    • name:要查找的节点名字。
    • 返回值:找到的节点,如果为 NULL 表示查找失败。
  • struct device_node *of_find_node_by_type(struct device_node *from, const char *type)

    描述:通过 device_type 属性查找指定的节点。(因为device_type用到很少,所以该函数用的也很少)

    • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    • type:要查找的节点对应的 type 字符串,也就是 device_type 属性值。
    • 返回值:找到的节点,如果为 NULL 表示查找失败。
  • struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)

    描述:根据 device_type 和 compatible 这两个属性查找指定的节点。

    • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    • type:要查找的节点对应的 type 字符串,也就是 device_type 属性值,可以为 NULL,表示忽略掉 device_type 属性。
    • compatible:要查找的节点所对应的 compatible 属性列表。
    • 返回值:找到的节点,如果为 NULL 表示查找失败
  • 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)

    描述:通过 of_device_id 匹配表来查找指定的节点。

    • from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
    • matches:of_device_id 匹配表,也就是在此匹配表里面查找节点。
    • match:找到的匹配的 of_device_id。
    • 返回值:找到的节点,如果为 NULL 表示查找失败
  • inline struct device_node *of_find_node_by_path(const char *path)

    描述:通过路径来查找指定的节点。

    • path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
    • 返回值:找到的节点,如果为 NULL 表示查找失败。
  • struct device_node *of_get_parent(const struct device_node *node)

    描述:用于获取指定节点的父节点(如果有父节点的话)。

    • node:要查找的父节点的节点。
    • 返回值:找到的父节点。
  • struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)

    描述:数用迭代的方式查找子节点。

    • node:父节点。
    • prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
    • 返回值:找到的下一个子节点。

获取属性值的 OF 函数

设备树的属性在内核当中以一个结构体的形式存在,它的定义如下:

struct property {char *name; /* 属性名字 */int length; /* 属性长度 */void *value; /* 属性值 */struct property *next; /* 下一个属性 */unsigned long _flags;unsigned int unique_id;struct bin_attribute attr;
};
  • property *of_find_property(const struct device_node *np, const char *name, int *lenp)

    描述:查找指定节点的属性名为name的属性值。

    • np:设备节点。
    • name: 属性名字。
    • lenp:属性值的字节数。
    • 返回值:找到的属性。
  • int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)

    描述:获取属性中元素的数量,比如 reg 属性值是一个数组,那么使用此函数可以获取到这个数组的大小。

    • np:设备节点。
    • proname: 需要统计元素数量的属性名字。
    • elem_size:元素长度。
    • 返回值:得到的属性元素数量。
  • int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)

    描述:从属性中获取指定标号的 u32 类型数据值(无符号 32位),比如某个属性有多个 u32 类型的值,那么就可以使用此函数来获取指定标号的数据值。

    • np:设备节点。
    • proname: 要读取的属性名字。
    • index:要读取的值标号。
    • out_value:读取到的值
      返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
  • int of_property_read_ux_array(const struct device_node *np, const char *propname, ux *out_values, size_t sz)(x = 8, 16, 32, 64)

    描述:可以读取属性中 u8、u16、u32 和 u64 类型的数组数据,比如大多数的 reg 属性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据。

    • np:设备节点。
    • proname: 要读取的属性名字。
    • out_value:读取到的数组值,分别为 u8、u16、u32 和 u64。
    • sz:要读取的数组元素数量。
    • 返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
  • int of_property_read_ux(const struct device_node *np, const char *propname, ux *out_value)(x = 8, 16, 32, 64)

    描述:是用于读取这种只有一个整形值的属性,可以读取 u8、u16、u32 和 u64 类型属性值。

    • np:设备节点。
    • proname: 要读取的属性名字。
    • out_value:读取到的数组值。
    • 返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
  • int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)

    描述:读取属性中字符串值。

    • np:设备节点。
    • proname: 要读取的属性名字。
    • out_string:读取到的字符串值。
    • 返回值:0,读取成功,负值,读取失败。
  • int of_n_addr_cells(struct device_node *np)int of_n_size_cells(struct device_node *np)

    描述:分别可以获取设备的#address-cells、#size-cells属性值。

    • np:设备节点。
    • 返回值:#address-cells、#size-cells属性值。
  • int of_device_is_compatible(const struct device_node *device, const char *compat)

    描述:查看节点的 compatible 属性是否有包含 compat 指定的字符串。

    • device:设备节点。
    • compat:要查看的字符串。
    • 返回值:0,节点的 compatible 属性中不包含 compat 指定的字符串;正数,节点的 compatible
    • 属性中包含 compat 指定的字符串。

了解完操作设备树的OF函数之后,其实我们就应该知道,所谓设备树的属性,除了常用的reg之外,在设备树文件当中,存在很多其他的一些厂商自定义的属性,这些属性专门为他们的芯片/设备服务的。我们也可以为一个节点自定义一个属性。最开始接触设备树的时候,因为没有任何单片机的基础,对于设备树文件当中出现的GPIO、I2C、SPI、PWM、UART等陌生的词汇没有任何概念,在有了一点单片机的基础后,再来看设备树这些概念,其实就很好理解了,对于某一个节点,为什么要有这些属性都大概能知道其原因。


本章完结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/59865.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Uni-APP+Vue3+鸿蒙 开发菜鸟流程

参考文档 文档中心 运行和发行 | uni-app官网 AppGallery Connect DCloud开发者中心 环境要求 Vue3jdk 17 Java Downloads | Oracle 中国 【鸿蒙开发工具内置jdk17&#xff0c;本地不使用17会报jdk版本不一致问题】 开发工具 HBuilderDevEco Studio【目前只下载这一个就…

[每日一氵] PySpark 的 log GC 部分是什么意思

2024-11-15T11:10:40.2920800: 2850.503: [GC (Allocation Failure) [PSYoungGen: 142705K->3472K(141312K)] 1403514K->1264289K(1543168K), 0.0170225 secs] [Times: user0.05 sys0.00, real0.01 secs] 这一行日志来自Java的垃圾收集器&#xff08;Garbage Collector, …

sslSocketFactory not supported on JDK 9+

clientBuilder.sslSocketFactory(SSLSocketFactory) not supported on JDK 9 at okhttp3.internal.platform.Jdk9Platform.trustManager(Jdk9Platform.kt:61) at okhttp3.OkHttpClient$Builder.sslSocketFactory(OkHttpClient.kt:751) at 1.升版本4.9.3以上 2、加个函数获取X…

「Mac玩转仓颉内测版8」入门篇8 - Cangjie函数与方法

本篇介绍Cangjie编程语言中的函数与方法&#xff0c;帮助理解如何通过函数封装重复操作&#xff0c;提升代码的复用性和可维护性。 关键词 Cangjie函数方法定义参数传递返回值模块化与复用性 一、什么是函数&#xff1f; 函数是一个代码块&#xff0c;用于接收参数、执行操作…

ubuntu 16.04 中 VS2019 跨平台开发环境配置

su 是 “switch user” 的缩写&#xff0c;表示从当前用户切换到另一个用户。 sudo 是 “superuser do” 的缩写&#xff0c;意为“以超级用户身份执行”。 apt 是 “Advanced Package Tool” 的缩写&#xff0c;Ubuntu中用于软件包管理的命令行工具。 1、为 root 用户设置密码…

git没有识别出大写字母改成小写重命名的文件目录

Git 默认不会跟踪大写字母和小写字母的区别&#xff0c;因为在大多数文件系统中&#xff0c;大写字母和小写字母被认为是相同的文件&#xff0c;只有在区分大小写的文件系统中&#xff08;如 macOS 的 HFS 或 Windows 的 NTFS&#xff09;&#xff0c;这才是一个问题。 如果重命…

Java集合ConcurrentHashMap——针对实习面试

目录 Java集合ConcurrentHashMapConcurrentHashMap的特性是什么&#xff1f;HashMap和ConcurrentHashMap的区别&#xff1f;说说ConcurrentHashMap的底层实现 Java集合ConcurrentHashMap ConcurrentHashMap的特性是什么&#xff1f; 线程安全性 多线程并发读写安全&#xff1a…

游戏引擎学习第16天

视频参考:https://www.bilibili.com/video/BV1mEUCY8EiC/ 这些字幕讨论了编译器警告的概念以及如何在编译过程中启用和处理警告。以下是字幕的内容摘要&#xff1a; 警告的定义&#xff1a;警告是编译器用来告诉你某些地方可能存在问题&#xff0c;尽管编译器不强制要求你修复…

数据库几道简答题

1。 什么是数据库? 答&#xff1a;数据库是长期存储在计算机内、有组织的、可共享的数据集合。数据库是按某种数据模型进行组织的、存放在外存储器上&#xff0c;且可被多个用户同时使用。因此,数据库具有较小的冗余度,较高的数据独立性和易扩展性. 2。 什么是数据库的数据独…

使用--log-file保存pytest的运行日志

前面使用了tee和重定向来保存pytest的运行日志&#xff0c;这次使用--log-file&#xff0c;因为它可以配置日志的级别、格式和每行日志的生成时间。 pytest -q -s -ra --count100 test_open_stream.py --alluredir./report/CXL --log-filepytest_log.txt 【pytest.ini】 使用…

【题目3】C++类的设计——07年复试笔试题

【题目】07年C复试笔试真题 定义一个处理日期的类TDate&#xff0c;它有3个私有数据成员&#xff1a;Month,Day,Year和若干共有成员函数&#xff0c;实现如下要求[附条件解读] ①构造函数重载→创建无参构造函数有参构造函数 ②成员函数设置缺省参数→与④一同可用set()在类中实…

【STL】set,multiset,map,multimap的介绍以及使用

关联式容器 在C的STL中包含序列式容器和关联式容器 1.关联式容器&#xff1a;它里面存储的是元素本身&#xff0c;其底层是线性序列的数据结构&#xff0c;比如&#xff1a;vector&#xff0c;list&#xff0c;deque&#xff0c;forward_list(C11)等 2.关联式容器里面储存的…

VUE+SPRINGBOOT实现邮箱注册、重置密码、登录功能

随着互联网的发展&#xff0c;网站用户的管理、触达、消息通知成为一个网站设计是否合理的重要标志。目前主流互联网公司都支持手机验证码注册、登录。但是手机短信作为服务端网站是需要付出运营商通信成本的&#xff0c;而邮箱的注册、登录、重置密码&#xff0c;无疑成为了这…

PyTorch——从入门到精通:PyTorch基础知识(张量)【PyTorch系统学习】

什么是张量&#xff08;Tensor&#xff09; ​ 张量在数学中是一个代数对象&#xff0c;描述了与矢量空间相关的代数对象集之间的多重线性映射。张量是向量和矩阵概念的推广&#xff0c;可以理解为多维数组。作为数学中的一个基本概念&#xff0c;张量有着多种类型&#xff0c;…

自动化生成边界测试和极端情况测试用例

在软件测试中&#xff0c;边界测试和极端情况测试是确保代码健壮性和容错能力的关键步骤。许多软件缺陷和错误往往发生在输入数据的边界值或极端情况下。手动生成这些测试用例不仅费时费力&#xff0c;而且容易遗漏。幸运的是&#xff0c;OpenAI的强大功能可以帮助软件测试工程…

ARM(安谋) China处理器

0 Preface/Foreword 0.1 参考博客 Cortex-M23/M33与STAR-MC1星辰处理器 ARM China&#xff0c;2018年4月established&#xff0c;独立运行。 1 处理器类型 1.1 周易AIPU 1.2 STAR-MC1&#xff08;星辰处理器&#xff09; STAT-MC1&#xff0c;主要为满足AIOT应用性能、功…

OpenCV:VideoWriter.write()导致内存不断增长(未解决)

以前某个应用&#xff0c;专门把opencv独立为进程&#xff0c;完成后自动释放。当时我还想优化一下&#xff0c;比如减少frame&#xff0c;结果一点用没用。 这次专门一下&#xff0c;结论就是&#xff1a;每次执行write()&#xff0c;内存必然增加。 输出版本号&#xff0c;是…

如何使用Django写个接口,然后postman中调用

好的&#xff0c;下面是一个详细的步骤&#xff0c;展示如何使用 Django 创建一个简单的 API 接口&#xff0c;并在 Postman 中进行调用。 1. 创建 Django 项目和应用 首先&#xff0c;确保你已经安装了 Django。如果还没有安装&#xff0c;可以使用以下命令安装&#xff1a;…

nfs服务器作业

1.NFS服务器可以让PC将网络中的NFS服务器共享的目录挂载到本地端的文件系统中&#xff0c;而在本地端的系统 中看来&#xff0c;那个远程主机的目录就好像是自己的一个磁盘分区。 2.RPC服务 &#xff1a; 由于当服务器在启动NFS时会随机 选取数个端口号&#xff0c;并主动向RP…

Android中perform和handle方法的区别——以handleLaunchActivity与performLaunchActivity为例

在Android系统中&#xff0c;perform和handle方法经常出现在关键流程中&#xff0c;分别承担不同的职责。这种命名约定反映了框架设计中的分层思想&#xff0c;帮助开发者区分任务的调度与实现。本文通过handleLaunchActivity和performLaunchActivity这两个典型方法的源码分析&…