Linux驱动开发—设备树开发详解
设备树开发详解
设备树概念
Device Tree是一种描述硬件的数据结构,以便于操作系统的内核可以管理和使用这些硬件,包括CPU或CPU,内存,总线和其他一些外设。
Linux内核从3.x版本之后开始支持使用设备树,可以实现驱动代码与设备的硬件信息相互的隔离,减少了代码中的耦合性
-
引入设备树之前:一些与硬件设备相关的具体信息都要写在驱动代码中,如果外设发生相应的变化,那么驱动代码就需要改动。
-
引入设备树之后:通过设备树对硬件信息的抽象,驱动代码只要负责处理逻辑,而关于设备的具体信息存放到设备树文件中。如果只是硬件接口信息的变化而没有驱动逻辑的变化,开发者只需要修改设备树文件信息,不需要改写驱动代码。
一、DTS、DTB和DTC
- DTS
- 设备树源码文件,硬件的相应信息都会写在.dts为后缀的文件中,每一款硬件可以单独写一份xxxx.dts
- DTSI
- 对于一些相同的dts配置可以抽象到dtsi文件中,然后可以用include的方式到dts文件中
- 同一芯片可以做一个dtsi,不同的板子不同的dts,然后include同一dtsi
- 对于同一个节点的设置情况,dts中的配置会覆盖dtsi中的配置
- DTC
- dtc是编译dts的工具
- DTB
- dts经过dtc编译之后会得到dtb文件,设备树的二进制执行文件
- dtb通过Bootloader引导程序加载到内核。
二、设备树框架
1.根节点:\2.设备节点:nodex①节点名称:node②节点地址:node@0, @后面即为地址3.属性:属性名称(Property name)和属性值(Property value)4.标签
- “/”是根节点,每个设备树文件只有一个根节点。在设备树文件中会发现有的文件下也有“/”根节点,这两个**“/”根节点的内容会合并成一个根节点。**
- Linux 内核启动的时会解析设备树中各个节点的信息,并且在根文件系统的/proc/devicetree 目录下根据节点名字创建不同文件夹
三、DTS语法
3.1 dtsi头文件
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
设备树也支持头文件,设备树的头文件扩展名为.dtsi。在.dts 设备树文件中,还可以通过“#include”来引用.h、 .dtsi 和.dts 文件。
3.2 设备节点
-
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,
-
每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。
label: node-name@unit-address label:节点标签,方便访问节点:通过&label访问节点,追加节点信息 node-name:节点名字,为字符串,描述节点功能 unit-address:设备的地址或寄存器首地址,若某个节点没有地址或者寄存器,可以省略
-
设备树源码中常用的几种数据形式
1.字符串: compatible = "arm,cortex-a7";设置 compatible 属性的值为字符串“arm,cortex-a7” 2.32位无符号整数:reg = <0>; 设置reg属性的值为0 3.字符串列表:字符串和字符串之间采用“,”隔开 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand"; 设置属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。
3.3 属性
-
compatible属性(兼容属性)
"manufacturer,model" manufacturer:厂商名称 model:模块对应的驱动名字
例:
imx6ull-alientekemmc.dts 中 sound 节点是 音频设备节点,采用的欧胜(WOLFSON)出品的 WM8960, sound 节点的 compatible 属性值如下:compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
-
属性值有两个,分别为“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字。
-
sound这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查。
-
一般驱动程序文件会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
-
在根节点来说,Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。如果不支持的话那么这个设备就没法启动 Linux 内核。
-
-
model属性
model 属性值是一个字符串,一般 model 属性描述设备模块信息。 -
status属性
status 属性和设备状态有关的, status 属性值是字符串,描述设备的状态信息。
-
#address-cells 和#size-cells 属性
用于描述子节点的地址信息,reg属性的address 和 length的字长。
- #address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位),
- #size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。
- 子节点的地址信息描述来自于父节点的#address-cells 和#size-cells的值,而不是该节点本身的值(当前节点的信息是描述子节点的,自己的信息在父节点里)
//每个“address length”组合表示一个地址范围, //其中 address 是起始地址, length 是地址长度, //#address-cells 表明 address 这个数据所占用的字长, // #size-cells 表明 length 这个数据所占用的字长. reg = <address1 length1 address2 length2 address3 length3……>
-
reg属性
reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息, reg 属性的值一般是(address, length)对.例
uart1: serial@02020000 {compatible = "fsl,imx6ul-uart","fsl,imx6q-uart", "fsl,imx21-uart";reg = <0x02020000 0x4000>;interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_UART1_IPG>,<&clks IMX6UL_CLK_UART1_SERIAL>;clock-names = "ipg", "per";status = "disabled"; };
uart1 的父节点 aips1: aips-bus@02000000 设置了#address-cells = <1>、 #sizecells = <1>,因此 reg 属性中 address=0x02020000, length=0x4000。都是字长为1.
-
ranges属性
-
ranges属性值可以为空或者按照( child-bus-address , parent-bus-address , length )格式编写的数字
-
ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成。
-
如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换。
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长 parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长 length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长
-
-
特殊节点
在根节点“/”中有两个特殊的子节点: aliases 和 chosen
-
aliases
aliases {can0 = &flexcan1;can1 = &flexcan2;...usbphy0 = &usbphy1;usbphy1 = &usbphy2; };
aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。
但是,一般会在节点命名的时候会加上 label,然后通过&label来访问节点。
-
chosen
chosen 不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据(bootargs 参数)。
-
四、OF操作函数
Linux 内核提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_” (称为OF 函数)
4.1 查找节点
Linux 内核使用 device_node 结构体来描述一个节点:
struct device_node {const char *name; /* 节点名字 */const char *type; /* 设备类型 */phandle phandle;const char *full_name; /* 节点全名 */struct fwnode_handle fwnode;struct property *properties; /* 属性 */struct property *deadprops; /* removed 属性 */struct device_node *parent; /* 父节点 */struct device_node *child; /* 子节点...
}
-
通过节点名字查找指定的节点:of_find_node_by_name
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
name:要查找的节点名字。
返回值: 找到的节点,如果为 NULL 表示查找失败。 -
通过 device_type 属性查找指定的节点:of_find_node_by_type
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
type:要查找的节点对应的 type 字符串, device_type 属性值。
返回值: 找到的节点,如果为 NULL 表示查找失败 -
通过device_type 和 compatible两个属性查找指定的节点:of_find_compatible_node
struct device_node *of_find_compatible_node(struct device_node *from,const char *type,const char *compatible)
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
type:要查找的节点对应的 type 字符串,device_type 属性值,可以为 NULL
compatible: 要查找的节点所对应的 compatible 属性列表。
返回值: 找到的节点,如果为 NULL 表示查找失败 -
通过 of_device_id 匹配表来查找指定的节点:of_find_matching_node_and_match
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)
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
matches: of_device_id 匹配表,在此匹配表里面查找节点。
match: 找到的匹配的 of_device_id。
返回值: 找到的节点,如果为 NULL 表示查找失败 -
通过路径来查找指定的节点:of_find_node_by_path
inline struct device_node *of_find_node_by_path(const char *path)
path:设备树节点中绝对路径的节点名,可以使用节点的别名
返回值: 找到的节点,如果为 NULL 表示查找失败
4.2 获取属性值
Linux 内核中使用结构体 property 表示属性
struct property {char *name; /* 属性名字 */int length; /* 属性长度 */void *value; /* 属性值 */struct property *next; /* 下一个属性 */unsigned long _flags;unsigned int unique_id;struct bin_attribute attr;
}
-
查找指定的属性:of_find_property
property *of_find_property(const struct device_node *np,const char *name,int *lenp)
np:设备节点。
name: 属性名字。
lenp:属性值的字节数,一般为NULL
返回值: 找到的属性。 -
获取属性中元素的数量(数组):of_property_count_elems_of_size
int of_property_count_elems_of_size(const struct device_node *np,const char *propnameint elem_size)
np:设备节点。
proname: 需要统计元素数量的属性名字。
elem_size:元素长度。
返回值: 得到的属性元素数量 -
从属性中获取指定标号的 u32 类型数据值:of_property_read_u32_index
int of_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 表示属性值列表太小 -
读取属性中 u8、 u16、 u32 和 u64 类型的数组数据
of_property_read_u8_array of_property_read_u16_array of_property_read_u32_array of_property_read_u64_array int of_property_read_u8_array(const struct device_node *np,const char *propname,u8 *out_values,size_t sz)
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值,分别为 u8、 u16、 u32 和 u64。
sz: 要读取的数组元素数量。
返回值: 0:读取成功;
负值: 读取失败
-EINVAL 表示属性不存在
-ENODATA 表示没有要读取的数据
-EOVERFLOW 表示属性值列表太小 -
读取属性中字符串值:of_property_read_string
int of_property_read_string(struct device_node *np,const char *propname,const char **out_string)
np:设备节点。
proname: 要读取的属性名字。
out_string:读取到的字符串值。
返回值: 0,读取成功,负值,读取失败 -
获取 #address-cells 属性值:of_n_addr_cells ,获取 #size-cells 属性值:of_size_cells 。
int of_n_addr_cells(struct device_node *np) int of_n_size_cells(struct device_node *np)
np:设备节点。
返回值: 获取到的#address-cells 属性值。
返回值: 获取到的#size-cells 属性值。 -
内存映射
of_iomap 函数用于直接内存映射,前面通过 ioremap 函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址。这样就不用再去先获取reg属性值,再用属性值映射内存。of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段, of_iomap 函数原型如下:
void __iomem *of_iomap(struct device_node *np, int index)
np:设备节点。
index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
返回值: 经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。例
#if 1/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);GPIO1_DR = ioremap(regdata[6], regdata[7]);GPIO1_GDIR = ioremap(regdata[8], regdata[9]); #else //第一对:起始地址+大小 -->映射 这样就不用获取reg的值IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0); SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);GPIO1_DR = of_iomap(dtsled.nd, 3);GPIO1_GDIR = of_iomap(dtsled.nd, 4); #endif
相关文章:

Linux驱动开发—设备树开发详解
设备树开发详解 设备树概念 Device Tree是一种描述硬件的数据结构,以便于操作系统的内核可以管理和使用这些硬件,包括CPU或CPU,内存,总线和其他一些外设。 Linux内核从3.x版本之后开始支持使用设备树,可以实现驱动代…...

深入浅出C++ ——继承
文章目录一、继承的相关概念1. 继承的概念2. 继承格式3. 继承方式4. 访问限定符5. 继承基类成员访问方式的变化二、基类和派生类对象赋值转换三、继承中的作用域四、派生类的默认成员函数五、继承与友元六、继承与静态成员七、菱形继承及菱形虚拟继承1. 单继承2. 多继承3. 菱形…...

设计模式C++实现20: 桥接模式(Bridge)
部分内容参考大话设计模式第22章;本实验通过C语言实现。 一 基本原理 意图:将抽象部分和实现部分分离,使它们都可以独立变化。 上下文:某些类型由于自身的逻辑,具有两个或多个维度的变化。如何应对“多维度的变化”…...

Android中的Rxjava
要使用Rxjava首先要导入两个包,其中rxandroid是rxjava在android中的扩展 implementation io.reactivex:rxandroid:1.2.1implementation io.reactivex:rxjava:1.2.0observer 是一个观察者接口,泛型T为观察者观察数据的类型,里面只有三个方法&a…...

【RocketMQ】源码详解:消息储存服务加载、文件恢复、异常恢复
消息储存服务加载 入口:org.apache.rocketmq.store.DefaultMessageStore#load 在创建brokerContriller时会调用初始化方法初始化brokerController,在初始化方法中会进行消息储存服务的加载 this.messageStore.load(); 加载方法主要是加载一些必要的参数和数据,如配…...

数字IC设计工程师是做什么的?
随着我国半导体产业的发展,近几年的新入行的从业人员,除了微电子相关专业的,还有就是物理、机械、数学、计算机等专业,很多人对这一高薪行业充满了好奇,那么数字IC设计工程师到底是做什么的? 首先来看看数…...

【040】134. 加油站[简单模拟 + 逻辑转化]
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组 gas 和 cost &am…...

Python用selenium实现自动登录和下单的脚本
前言 学python对selenium应该不陌生吧 Selenium 是最广泛使用的开源 Web UI(用户界面)自动化测试套件之一。Selenium 支持的语言包括C#,Java,Perl,PHP,Python 和 Ruby。目前,Selenium Web 驱动…...

(02)Cartographer源码无死角解析-(55) 2D后端优化→AppendNode()、class MapById、 PoseGraphData、
讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885 文末正下方中心提供了本…...

如何在jmeter中把响应中的数据提取出来并引用
jmeter做接口测试过程中,经常遇到请求需要用到token的时候,我们可以把返回token的接口用后置处理器提取出来,但是在这种情况下,只能适用于当前的线程组,其他线程组无法引用到提取的token变量值,所以必须要生…...

2023环翠区编程挑战赛中学组题解
T1. 出栈序列 题目描述 栈是一种“先进后出”的数据结构,对于一个序列1,2,...,n1,2, ...,n1,2,...,n,其入栈顺序是1,2,...n1,2, ...n1,2,...n,但每个元素出栈的时机可以自由选择。 例如111入栈、111出栈,222入栈、333入栈、333…...

手撸一个Switch开关组件
一、前言 手撸系列又来了,这次咱们来撸一个Switch开关组件,废话不多说,咱们立刻发车。 二、使用效果 三、实现分析 首先我们先不想它的这个交互效果,我们就实现“不合格”时的一个静态页面,静态页面大致如下&#x…...

2023年1月冰箱品牌销量排行:销量环比增长26%,销售额36亿+
鲸参谋电商大数据2023年1月京东平台“冰箱”销售数据出炉! 根据鲸参谋平台电商数据显示,2023年1月份,在京东平台上,冰箱的销量将近130万件,环比增长26%,同比下滑8%;销售额达36亿,环比…...

DSP CCS 开发问题总结及解决办法
文章目录 问题汇总 1. CCS编译器的Project菜单栏工程导入选项丢失,怎么解决! 1.1启动CCS后发现导入工程菜单栏丢失,无法导入工程文件。 1.2方法一 工程选项的导入工程文件丢失,如果要重新获得相应的选项,就需要删除当前…...

Vue3.x+Element Plus仿制Acro Design简洁模式分页器组件
Vue3.xElement Plus仿制Acro Design简洁模式分页器组件 开发中难免会遇到宽度很窄的列表需要使用分页器的情况,这时若使用Element Plus组件的分页器会导致分页器内容超出展示的区域,而Element Plus组件中目前没有Acro Design那样小巧的分页器(…...

经典文献阅读之--VoxelMap(体素激光里程计)
0. 简介 作为激光里程计,常用的方法一般是特征点法或者体素法,最近Mars实验室发表了一篇文章《Efficient and Probabilistic Adaptive Voxel Mapping for Accurate Online LiDAR Odometry》,同时还开源了代码在Github上。文中为雷达里程计提…...

.NET6中使用GRPC详细描述
Supported languages | gRPC,官网。至于原理就不说了,可以百度原理之后,然后再结合代码,事半功倍,就能很好理解GRPC了。 目录 一、简单使用 二、实际应用 一、简单使用 1.使用vs2022创建一个grpc程序,…...

ML@矩阵微积分基础
文章目录矩阵微积分Matrix calculus记法简单Jacobi Matrix分子记法分母记法一般形式的Jacobi MatrixTypes of matrix derivative向量求导向量对标量求导标量对向量求导向量对向量求导矩阵求导矩阵对标量求导(切矩阵)标量对矩阵求导记法向量求导 向量对标量求导标量对向量求导向…...

华为OD机试真题Python实现【优秀学员统计】真题+解题思路+代码(20222023)
优秀学员统计 题目 公司某部门软件教导团正在组织新员工每日打卡学习活动,他们开展这项学习活动已经一个月了,所以想统计下这个月优秀的打卡员工。每个员工会对应一个 id,每天的打卡记录记录当天打卡员工的 id 集合,一共 30 天。 请你实现代码帮助统计出打卡次数 top5 的…...

docsify在线文档支持pdf查看
目录 步骤一:添加插件 步骤二:添加pdf地址 步骤三:成果展示 docsify是一个在github上很好用的文档转换网页的工具,但是大部分情况我们都是使用的markdown文件。最近想把pdf文档也能支持在这上面展示,研究后总结一下…...

ES6中Set类型的基本使用
在ES6之前,存储数据的结构主要有两种:数组、对象。 在ES6中新增了另外两种数据结构(存放数据的方式):Set、Map,以及他们的另外形式WeakSet、WeakMap。 Set的基本使用 Set是一个新增的数据结构,…...

【VUE3.0_CSS功能】
CSS功能组件css作用域深度选择器(标签名空格:deep(标签名))插槽选择器(:soltted(标签名))全局选择器(:global(类名))动态CSS(v-bind)useCSSModule拓展知识:deep的写法组件…...

微机原理复习总结6:汇编语言程序设计
本篇博客主要分享几道汇编语言例题编写一完整的程序,从键盘输入一组字符,直到输入“0”为止,当输入是小写字母时,则修改为大写字母,输入的字符存放在string为首址的存储单元中。data segment ;数据段定义 st…...

计算机网络 部分原理和过程
下面是一台计算机 Ping 和不在同一 IP 网络上的另一台计算机的全过程: 该计算机首先确定要 Ping 的目标 IP 地址,并检查该 IP 地址是否与本地 IP 地址在同一 IP 网络上。如果目标 IP 地址与本地 IP 地址不在同一 IP 网络上,则需要通过路由器…...

C++实现链表
C实现链表 众所周知,C/C语言实现的链表是由一个一个的结点构成,每个结点分为数据域和指针域,指针域中存储了其后继结点的地址,通过地址来访问下一个结点。 链表是一系列节点串联形成的数据结构,链表存储有序的元素集合…...

MySQL索引篇
文章目录说明:索引篇一、索引常见面试题按数据结构按物理存储分类按字段特性分类按字段个数分类索引缺点:什么时候适用索引?什么时候不需要创建索引?常见优化索引的方法:发生索引失效的情况:二、从数据页角…...

Ardiuno-交通灯
LED交通灯实验实验器件:■ 红色LED灯:1 个■ 黄色LED灯:1 个■ 绿色LED灯:1 个■ 220欧电阻:3 个■ 面包板:1 个■ 多彩杜邦线:若干实验连线1.将3个发光二极管插入面包板,2.用杜邦线…...

Leetcode.1234 替换子串得到平衡字符串
题目链接 Leetcode.1234 替换子串得到平衡字符串 Rating : 1878 题目描述 有一个只含有 Q, W, E, R四种字符,且长度为 n 的字符串。 假如在该字符串中,这四个字符都恰好出现 n/4次,那么它就是一个「平衡字符串」。 给你一个这样…...

聚类算法之K-means算法详解
文章目录 什么是聚类k-means算法简介牧师-村民模型算法步骤伪代码流程描述手动实现优缺点优点缺点算法调优与改进数据预处理合理选择 K 值手肘法Gap Statistic(间隔统计量)轮廓系数法(Silhouette Coefficient)Canopy算法拍脑袋法采用核函数K-means++ISODATA参考文献<...

电话呼入/呼出CSFB流程介绍
MO CSFB 注册的LAI跟SYS_INFO不同会触发LU流程;LU流程结束后,判断LOCATION UPDATING ACCEPT消息中的"Follow-on proceed"参数状态。(1)如果IE消息中有"Follow-on proceed",终端直接发送CM Service Request; (2)如果IE消息中没有"Follow-on procee…...