Linux驱动开发快速入门——设备树随记

news2024/11/25 6:02:24

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.coloradmin.cn/o/2245063.html

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

相关文章

OpenTelemetry 赋能DevOps流程的可观测性革命

原作者&#xff1a;天颇 原出处&#xff1a;微信公众号 乘云数字DATABUFF 原文地址&#xff1a;https://mp.weixin.qq.com/s/D_f31EBtLu7Rr0gahuF-bw 引言 在当今快节奏的软件开发和运维环境中&#xff0c;DevOps 已经成为主流&#xff0c;它通过整合开发和运维流程&#xff0…

计算机视觉算法——基于深度学习的高精地图算法(MapTRv2 / PivotNet / BeMapNet)

计算机视觉算法——基于深度学习的高精地图算法&#xff08;MapTRv2 / PivotNet / BeMapNet&#xff09; 计算机视觉算法——基于深度学习的高精地图算法&#xff08;MapTRv2 / PivotNet / BeMapNet&#xff09;1. MapTRv21.1 网络结构及特点1.2 Decoupled Self-Attention1.3 O…

nodejs21: 快速构建自定义设计样式Tailwind CSS

Tailwind CSS 是一个功能强大的低级 CSS 框架&#xff0c;只需书写 HTML 代码&#xff0c;无需书写 CSS&#xff0c;即可快速构建美观的网站。 1. 安装 Tailwind CSS React 项目中安装 Tailwind CSS&#xff1a; 1.1 安装 Tailwind CSS 和相关依赖 安装 Tailwind CSS: npm…

《Python网络安全项目实战》项目6 编写密码工具程序

《Python网络安全项目实战》项目6 编写密码工具程序 项目6 编写密码工具程序任务6.1 猜数字游戏任务描述任务分析任务实施6.1.1 编写基本的猜数字程序 6.1.2 为猜数字程序加入连续猜数的功能6.1.3 测试并修改程序6.1.4 给程序增加注释 任务拓展 任务6.2 编写密码工具程序任务描…

MATLAB蒙特卡洛仿真计算投资组合的VaR(Value at Risk )

1. 计算VaR简介 VaR&#xff08;Value at Risk&#xff09;&#xff0c;一般被称为“风险价值”或“在险价值”&#xff0c;是指在一定的置信水平下&#xff0c;某一金融资产&#xff08;或证券组合&#xff09;在未来特定的一段时间内的最大可能损失。VaR提供了一个具体的数值…

【linux学习指南】VSCode部署Ubantu云服务器,与Xshell进行本地通信文件编写

文章目录 &#x1f4dd;前言&#x1f320; 步骤&#x1f309;测试同步 &#x1f6a9;总结 &#x1f4dd;前言 本文目的是讲使用Vscode连接Ubantu,与本地Xshell建立通信同步文件编写。 查看本机系统相关信息&#xff1a; cat /etc/lsb*DISTRIB_IDUbuntu: 表示这是 Ubuntu 发行…

stm32下的ADC转换(江科协 HAL版)

十二. ADC采样 文章目录 十二. ADC采样12.1 ADC的采样原理12.2 STM32的采样基本过程1.引脚与GPIO端口的对应关系2.ADC规则组的四种转换模式(**)2.2 关于转换模式与配置之间的关系 12.3 ADC的时钟12.4 代码实现(ADC单通道 & ADC多通道)1. 单通道采样2. 多通道采样 19.ADC模数…

DockerFile与容器构建技术

一、 Docker架构 二、容器镜像分类 操作系统类 CentOSUbuntu在dockerhub下载或自行制作 应用类 TomcatNginxMySQLRedis 三、容器镜像获取的方法 主要有以下几种&#xff1a; 1、在DockerHub直接下载 2、把操作系统中文件系统打包为容器镜像 3、把正在运行的容器打包为容器镜…

分布式数据库中间件可以用在哪些场景呢

在数字化转型的浪潮中&#xff0c;企业面临着海量数据的存储、管理和分析挑战。华为云分布式数据库中间件&#xff08;DDM&#xff09;作为一款高效的数据管理解决方案&#xff0c;致力于帮助企业在多个场景中实现数据的高效管理和应用&#xff0c;提升业务效率和用户体验。九河…

jmeter常用配置元件介绍总结之断言

系列文章目录 1.windows、linux安装jmeter及设置中文显示 2.jmeter常用配置元件介绍总结之安装插件 3.jmeter常用配置元件介绍总结之线程组 4.jmeter常用配置元件介绍总结之函数助手 5.jmeter常用配置元件介绍总结之取样器 6.jmeter常用配置元件介绍总结之jsr223执行pytho…

项目技术栈-解决方案-web3去中心化

web3去中心化 Web3 DApp区块链:钱包:智能合约:UI:ETH系开发技能树DeFi应用 去中心化金融P2P 去中心化网络参考Web3 DApp 区块链: 以以太坊(Ethereum)为主流,也包括Solana、Aptos等其他非EVM链。 区块链本身是软件,需要运行在一系列节点上,这些节点组成P2P网络或者半…

多目标优化算法:多目标蛇鹫优化算法(MOSBOA)求解DTLZ1-DTLZ9,提供完整MATLAB代码

一、蛇鹫优化算法 蛇鹫优化算法&#xff08;Secretary Bird Optimization Algorithm&#xff0c;简称SBOA&#xff09;由Youfa Fu等人于2024年4月发表在《Artificial Intelligence Review》期刊上的一种新型的元启发式算法。该算法旨在解决复杂工程优化问题&#xff0c;特别是…

数据集-目标检测系列- 花卉 鸡蛋花 检测数据集 frangipani >> DataBall

数据集-目标检测系列- 花卉 鸡蛋花 检测数据集 frangipani >> DataBall DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 贵在坚持&#xff01; 数据样例项目地址&#xff1a; * 相关项目 1&#xff09;数据集…

【Pikachu】SSRF(Server-Side Request Forgery)服务器端请求伪造实战

尽人事以听天命 1.Server-Side Request Forgery服务器端请求伪造学习 SSRF&#xff08;服务器端请求伪造&#xff09;攻击的详细解析与防范 SSRF&#xff08;Server-Side Request Forgery&#xff0c;服务器端请求伪造&#xff09; 是一种安全漏洞&#xff0c;它允许攻击者通…

Element Plus

快速入门: 然后我在src下创建了一个Button.vue文件,再去Element-plus官网查找组件的源码 常用组件 表格: <script lang"ts" setup> import {Delete,Edit, } from element-plus/icons-vueconst tableData [{title: 标题1,category: 时事,time: 2000-…

界面控件DevExpress WPF中文教程:网格视图数据布局的列和卡片字段

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

实现两个表格的数据传递(类似于穿梭框)

类似于element的 第一个表格信息以及按钮&#xff1a; <div style"height: 80%"><el-table :data"tableData1" border :cell-style"{text-align:center}" style"width: 100%;"ref"multipleTable1"selection-chang…

NPOI 实现Excel模板导出

记录一下使用NPOI实现定制的Excel导出模板&#xff0c;已下实现需求及主要逻辑 所需Json数据 对应参数 List<PurQuoteExportDataCrInput> listData [{"ItemName": "电缆VV3*162*10","Spec": "电缆VV3*162*10","Uom":…

凸函数与深度学习调参

问题1&#xff1a;如何区分凸问题和凹问题&#xff1f; 问题2&#xff1a;深度学习如何区分调参&#xff1f;

DBeaver MACOS 安装 并连接到docker安装的mysql

官网下载&#xff1a;Download | DBeaver Community 网盘下载&#xff1a;链接: https://pan.baidu.com/s/15fAhbflHO-AGc-uAnc3Rjw?pwdbrz9 提取码: brz9 下载驱动 连接测试 报错 null, message from server: "Host 172.17.0.1 is not allowed to connect to this M…