【Linux驱动】设备树模型的LED驱动 | 查询方式的按键驱动

🐱作者:一只大喵咪1201
🐱专栏:《Linux驱动》
🔥格言:你只管努力,剩下的交给时间!
图

目录

  • 🍮设备树模型的LED驱动
    • 🍩设备树文件
    • 🍩驱动程序
  • 🍮应用层读取按键值
    • 🍩查询方式
    • 🍩休眠唤醒方式
    • 🍩poll方式
    • 🍩异步通知方式
  • 🍮查询方式实现按键驱动
      • 编程
  • 🍮总结

🍮设备树模型的LED驱动

目前有三种方式来写LED驱动程序:

  • 最简单的驱动模型——硬件操作绑定在驱动函数中。
  • 总线驱动模型。
  • 设备树驱动模型。

下面设备树驱动模型来实现一下LED驱动程序,该模型主要分为两部分,设备树文件和驱动程序。

🍩设备树文件

图
如上图所示设备树文件,在设备树中增加Big-Miaomi-LED@0Big-Miaomi-LED@1两个设备节点:

  • compatible属性:属性值都是BigMiaomi,LED_Driver

  • pin属性:属性值是各自节点所用GPIO组和引脚编号组成的32位整数。

  • 如果在设备树节点里使用reg属性,内核在生成对应的platform_device时,reg属性会被转换成IORESOURCE_MEM类型的资源。

  • 如果在设备树节点里使用interrputs属性,内核在生成对应的platform_device时,interrupts属性会被转换成IORESOURCE_IRQ类型的资源。

但是本喵写的Big-Miaomi-LED节点中,属性名是pin,该属性名是本喵自己定义的,不在内核自动转换资源类型的的命名范围内。

所以就不能从转换后的platform_device结构体中的resources数组中获得引脚资源了,具体获取方式编程时候再说。

图
如上图,然后在内核目录中使用make dtbs指令编译设备树文件,转换为内核认识的dtb文件。

🍩驱动程序

驱动程序在总线驱动模型的基础上进行修改,驱动层的上层不用动,只需要改变下层中的部分代码:

tu
如上图所示,由于现在支持了设备树,所以需要初始化platform_deiver结构体中driver成员里的of_match_table成员,这是一个struct of_device_id类型的数组。

所以需要定义一个struct of_device_id类型的数组,名为BigMiaomi_LEDs

  • 只用platform_deviceplatform_driver匹配规则中优先级最高的compatible属性来匹配。
  • 只支持LED设备,所以compatible属性只有一个值。

compatible属性的值,必须和设备树中要支持节点的compatible属性值相同,才能匹配成功

然后就是在匹配成功以后,会自动调用paltform_driver中的probe函数,在该函数中,原本是从paltform_deviceresources数组中获取硬件资源,但是此时不能这样干了:

tu
如上图所示probe函数,在该函数中首先要获取pin资源:

  • 设备树中的pin属性没有被转换到resources数组中,但是在第一次转换为device_node里的properties中是有该属性的。
    • 从匹配成功的platform_device中得到当前节点的device_node结构体指针of_node
  • 使用of_property_read_32函数,从np指向的当前节点deivce_node中的properties里找到pin属性,并且以32位整数的方式读取该属性的value值。
    • 将表示引脚资源的32位属性值放入到记录引脚资源的全局数组g_ledpins中。

获取到引脚资源后的其他操作和总线模型中相同,也是要使用led_class_create_device/dev目录下创建设备节点。

  • 设备树文件中的设备节点,内核加载后并不会在/dev目录下创建相应的文件,它不属于文件字符设备文件系统。

图
如上图代码所示,既然probe中的获取引脚资源的方式变了,那么在remove中获取引脚资源的方式和其他处理也要做出相应变化:

  • 从要移除设备节点的device_node中获取引脚资源led_pin
  • 遍历存放引脚资源的全局数组找到要移除的节点,移除后将对应的值修改为-1。
  • 最后判断一下是否该类型的设备节点全部移除了,如果存放引脚资源的全局数组中,所有值都成了-1,则说明全部移除了。

此时整个驱动程序就修改完毕了,相比于总线驱动模型,只是在获取引脚资源的方式上做了改变。

图
如上图所示Makfile文件,只需要编译驱动层上层led_drv.c和下层chip_led_opr.c即可,board_A.c不用再参与编译了。

  • 因为引脚资源不再由board_A.c中的platform_device结构体提供了。
  • 引脚资源由设备树文件提供,由内核将设备节点转换为platform_device结构体。

图
如上图所示,将在Linux服务器中编译好的dtb设备树文件和led_drv.kochip_led_opr.ko驱动文件,还有led_drv_test测试文件拷贝到网络根文件系统中。

在开发板上将dtb设备树文件拷贝到/boot目录下,然后重启开发板.

图

如上图所示,在/sys/firmware/devicetree/base/路径下,存在Big-Miaomi-LED@0Big-Miaomi-LED@1两个设备节点,这是我们在设备树文件中添加的两个节点,此时加载到了内核中。然后使用insmod led_drv.koinsmod chip_led_opr.ko安装驱动程序。

图

如上图所示,此时执行测试程序,在命令行中输入./led_drv_test /dev/BigMiaomi_LED0 on,内核打印信息现实操作了GPIO3_1

🍮应用层读取按键值

应用层读取按键值有4种方式:

  • 查询方式
  • 休眠-唤醒方式
  • poll方式
  • 异步通知方式

无论使用哪个方式都需要有按键驱动程序,通过这四种方式可以掌握一些驱动的基本技能:中断、休眠、唤醒、poll等机制

这些基本技能是驱动开发的基础,其他大型驱动复杂的地方是它的框架及设计思想,但是基本技能就只有这些。

🍩查询方式

图
如上图所示查询方式的驱动模型,这种方式最简单,这里并不考虑驱动层中的架构,只看驱动层所做的工作。

在驱动程序中构造并注册一个file_operations结构体,里面提供对应的drv_opendrv_read函数,当应用层调用open系统调用时,在驱动层的drv_open函数中配置相应的引脚为输入引脚。

当应用层调用read系统调用时,在驱动层的drv_read函数中读取该GPIO引脚的寄存器,把引脚的状态返回给应用层。

  • 读取引脚状态时,直接返回寄存器中的值,没有其他多余的动作。

🍩休眠唤醒方式

图

如上图所示休眠唤醒方式的驱动模型,在驱动层中的drv_open函数中,除了要把GPIO设置为输入引脚,还有注册GPIO的中断处理函数

当应用层调用read系统调用时,在驱动层的drv_read驱动函数中:

  1. 如果有按键数据,则直接返回给应用层。
  2. 如果没有按键数据,则应用层的APP在内核态休眠。

当用户按下按键时,GPIO中断被触发,导致drv_open中注册的中断服务程序被执行,在中断服务程序中:

  1. 记录按键数据。
  2. 唤醒休眠中的应用层APP。

应用层的APP被唤醒以后,继续在内核态运行,即执行驱动层代码,把中断服务程序中记录的按键数据返回给应用层的APP。

  • 没有读取到数据时,就会休眠,直到有按键数据到来才被唤醒。

🍩poll方式

上面的休眠-唤醒方式存在一个缺点:如果用户一直没有按下按键,那么应用层的APP就永远休眠阻塞不再执行了,所以可以给APP定个闹钟,这就是poll方式:

图

如上图所示poll驱动模型,poll是应用层实现多路转接的系统调用接口,在驱动层的file_operations结构体中,同样有一个poll函数指针:

tu
如上图所示file_operations结构体的定义,所以当应用层的APP调用poll系统调用时,会调用到驱动层该结构体中poll函数指针指向的函数。

所以需要我们在驱动层去定义poll函数指针指向的函数,使得整个驱动层符合poll驱动模型。驱动层总体步骤为:

  • 注册file_operations结构体,里面提供openreadpoll等驱动层的函数。
  • 应用层APP调用open时,驱动层的drv_open会将GPIO设置为输入引脚,并且注册中断处理函数。
  • 应用层APP调用poll/select时,意图是查询按键数据是否就绪,并且可以指定一个超时时间:
    • 当按键数据就绪时,驱动层的poll向应用层返回就绪状态,APP继续使用read读取按键数据。
    • 当按键数据没有就绪时,驱动层的poll就会在内核态休眠一段时间。

当APP被唤醒时,有两种情况:

  1. 在休眠期间,硬件按键被按下,按键数据就绪。
  2. 超时时间到了,硬件按键仍然没有按下,按键数据没有就绪。

被唤醒后进行判断,如果是数据就绪被唤醒,则调用read从按键的寄存器中读取按键数据,如果是超时被唤醒,则不调用read去读取了。

  • poll/select起到监视事件就绪的作用,驱动层的drv_poll都会告诉应用层APP所监视事件的状态。
  • APP根据驱动层告知的事件状态进行下一步动作。

🍩异步通知方式

图

如上图所示异步通知方式,在该模型中,应用层在打开要操作的设备时,要调用fcntl设置其fdFASYNC标志,此时会调用驱动层的drv_fasync函数:

tu
如上图所示,在file_operations结构体中也有一个fsync函数指针,在该模型中,该指针指向的函数只需要记录当前进程的PID

除了设置给fd设置FASYNC表示异步通知外,还需要使用signal系统调用注册信号处理函数my_func

此时该模型的处理步骤为:

  • APP调用open配置GPIO引脚为输入方式,并注册中断服务函数。
  • APP调用fcntl设置fd指向的文件为异步通知方式,并且注册信号处理函数.
  • 当硬件按键被按下时,中断服务程序会给记录下来的进程PID表示的进程发送信号,信号递达后执行注册的my_func信号处理函数。
  • 在信号处理函数中,调用read来读取按键数据,此时必然是有按键数据的。
  • 在没有按键按下时,APP正常执行,当按键按下后立刻去读取按键数据,使得应用层实现了中断的处理方式。

我们的驱动程序可以实现上述 4 种提供按键驱动的方法,但是驱动程序不应该限制APP使用哪种方法。

  • 这就是驱动设计的一个原则:只提供能力,不提供策略

就是说,APP想用哪种方法都行,驱动程序都可以提供;但是驱动程序不能限制APP使用哪种方法。

🍮查询方式实现按键驱动

前面介绍了按键的四种驱动模型,但是由于后面三种都涉及到中断方面的知识,而到目前为止本喵还没有介绍驱动程序中的中断,所以这里先仅用查询方式实现一下按键驱动程序:

tu

如上图所示,采用简单的驱动层分层模型来实现查询方式的按键驱动层数:

  • 应用层open/read系统调用和驱动层的drv_open/drv_read通过file_operations结构体来建立联系。
  • 驱动层上层的drv_open/drv_read和驱动层下层的board_button_init/board_button_read通过button_opr结构体连建立联系。

驱动层下层的board_button_init/board_button_read由具体的单板提供:

  • 驱动层下层的board_button_init根据设备号确定哪个按键,并将GPIO配置为输入引脚。
  • 驱动层下层的board_button_read根据设备号确定哪个按键,并读取对应寄存器中的值返回引脚电平。

驱动层上层:

tu
如上图所示,在button_operations.h中定义button_operations结构体:

  • count:表示按键设备个数
  • init:驱动层下层提供的初始化按键设备方法。
  • read:驱动层下层提供的读取按键状态方法。

tu
如上图所示,在button_drv.c中,创建file_operations结构体,并且用drv_opendrv_read初始化openread函数指针:

  • drv_open函数中,使用p_button_opr结构体中的init,根据次设备号进行初始化。
  • drv_read函数中,使用p_button_opr结构体中的read,根据次设备号读取按键状态。
    • 将读取到的按键状态level拷贝到用户层缓冲区。

图
如上图所示,在入口函数button_init中使用register_chrdev向内核中注册file_operations结构体,并且获得主设备号。还要创建button_class设备类来提供设备信息。

在出口函数button_exit中,销毁设备类button_class,并且使用unregister_chrdev函数从内核中将前面注册的file_operations移除。

图
如上图,由于p_button_opr结构体指针是由驱动层下层提供的,所以驱动层上层要提供一个register_button_operations函数给下层,让下层向上层注册p_button_opr结构体。

  • 在注册时,还要将所有按键设备使用device_create在文件系统中创建设备节点文件。
  • 在卸载时,使用device_destroy将文件系统中的所有按键设备文件移除掉。

由于下层在使用这两个函数时会用到上层的button_class类,所以这两个函数需要使用EXPORT_SYMBOL导出给下层,供下层先使用。

图
如上图,最后完善一下设备信息,告诉内核哪个是入口函数,哪个是出口函数,并且声明该驱动程序使用GPL协议。

驱动层下层:

图
如上图所示,在驱动层下层的board_button.c文件中,创建button_operations结构体,并进行初始化:

  • count:按键设备有两个。
  • init:初始化按键设备的函数board_button_init
  • read:读取按键设备状态的函数board_button_read

在入口函数中,使用驱动层上层提供的register_button_operations函数将下层创建的my_button_oprs结构体对象注册到上层,供上层使用下层提供的初始化和读取数据的方法。

在出口函数汇中,使用上层提供的unregister_button_operations函数移除在文件系统中创建的设备节点文件。

最后完善一下设备信息。

对于驱动层下层,重点在于初始化和读取数据函数的实现:

图
如上图所示IMX6ULL按键的电路原理图:

  • KEY1:与GPIO5_1相连,按键按下时是低电平(0),未按下时是高电平(1)。
  • KEY2:与GPIO4_IO14相连,按键按下时是低电平(0),未按下时是高电平(1)。
  1. 使能GPIO

TU
如上图所示使能GPIO的寄存器:

  • CCM_CCGR1:物理地址是0x020C406C,其中的[31,30]控制GPIO5的使能,但是这里保留了,GPIO5默认使能,
  • CCM_CCGR3:物理地址是0x020C4074,其中的[13,12]控制GPIO的使能,当这两个比特位为11时,GPIO4使能。
  1. 选择GPIO模式

tu
如上图所示IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1寄存器:

  • 物理地址是0x0229000C
  • MUX_MODE:这四个bit为101时,表示GPIO5_IO01引脚用作通用GPIO。

图
如上图所示IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B寄存器:

  • 物理地址是0x020E01B0
  • MUX_MODE:这四个bit为0101时,表示GPIO4_IO14引脚用作通用GPIO。
  1. 设置GPIO方向

TU
如上图所示内存映射表:

  • GPIO5:该组寄存器的基地址是0x020AC000
  • GPIO4:该组寄存器的基地址是0x020A8000

图
如上图所示GPIO所有寄存器的内存映射表,以GPIO4为例:

  • 一共8个寄存器,每组GPIO都是这样。
  • DR寄存器开始,到EDGE_SEL寄存器结束,地址从低到高,每个寄存器所占4个字节。

所以定义一个结构体来描述GPIO组中的所有寄存器:

tu
如上图所示结构体,用该结构体创建gpio5gpio4结构体对象来操作相应的GPIO。

图
如上图所示GDIR寄存器:

  • 对于GPIO5_1:将gpio5->gdirbit1设置为0,表示输入。
  • 对于GPIO4_14:将gpio4->gdirbit14设置为0,表示输入。
  1. 读取按键状态:

tu
如上图所示PSR寄存器:

  • 对于GPIO5_1gpio5->psrbit1为0,表示按键按下,为低电平,为1,表示按键没有按下,为高电平。
  • 对于GPIO4_14gpio4->gdirbit14为0,表示按键按下,为低电平,为1,表示按键没有按下,为高电平。

编程

tu

如上图所示,在驱动层下层board_button.c中,将用到的寄存器全部定义出来,并且创建gpio4gpio5两个结构体变量来表示GPIO。

board_button_init:

图

如上图所示初始化函数中:

  • 将所有涉及到的寄存器都在内存中映射相应的虚拟地址,只映射一次。
    • 其中GPIO组进行整体映射,大小为struct imx6ull_gpio结构体的大小。
  • 根据次设备号对GPIO口进行初始化,控制相关寄存器。
    • 使能GPIO组,设置引脚模式为通用GPIO,设置方向为输入。

board_button_read:

图
如上图所示读取按键数据的函数,根据次设备号确定读取gpio5还是gpio4中的psr寄存器,然后返回该寄存器中的值。

应用层测试函数:

tu
如上图应用层测试函数,在测试的时候,命令行中输入./button_test /dev/BigMiaomi_button0或者./button_test /dev/BigMiaomi_button1,在mian函数中会使用read系统调用去获取按键状态,最终会调用驱动层下层的board_button_read函数。

  • 如果打印1,表示按键没有按下。
  • 如果打印0,表示按键按下。

图
如上图所示Makefile文件中,make以后:

  • 会生成button_test可执行程序,用来测试。
  • 会生成button_drv.koboard_button.ko两个模块文件,用来安装驱动程序。

图
如上图所示,在开发板上安装两个按键的驱动程序,可以看到在./dev目录下有BigMiaomi_button0BigMiaomi_button1两个设备节点。

TU

如上图所示,在开发板上执行测试程序:

  • 未在开发板上按下KEY1KEY2两个按键时,打印出的值是1,表示高电平,和电路逻辑相符。
  • 按下开发板上按下KEY1KEY2两个按键时,打印出的值是0,表示低电平,和电路逻辑相符。

🍮总结

要会使用设备树向内核中注册设备节点,并且会对驱动程序做相应的修改。除此之外,要知道APP读取按键的四种方式,以及实现简单的APP按键驱动程序编程。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】树的遍历

树的遍历 前序遍历 前序遍历是按照根节点->左子树->右子树的顺序进行遍历 图片来源维基百科深度优先遍历(前序遍历): F, B, A, D, C, E, G, I, H. 代码实现 递归 # class TreeNode: # def __init__(self, x): # self.val x # …

[笔记] GICv3/v4 ITS 与 LPI

0. 写在前面 由于移植一个 pcie 设备驱动时,需要处理该 pcie 设备的 msi 中断(message signaled interrup)。 在 ARM 中, ARM 建议 msi 中断实现方式为: pcie 设备往 cpu 的一段特殊内存(寄存器)写某一个值&#xff0…

浅析xxl-obj分布式任务调度平台RCE漏洞

文章目录 前言本地环境搭建1、初始化数据库2、搭建调度中心3、搭建出执行器 XXL-JOB漏洞1、后台弱口令->RCE2、未授权API->RCE3、默认accessToken4、CVE-2022-361575、SSRF漏洞->RCE 总结 前言 在日常开发中,经常会用定时任务执行某些不紧急又非常重要的事…

c# 捕获全部线程的异常 试验

1.概要 捕获全部线程的异常 试验,最终结果task的异常没有找到捕获方法 2.代码 2.1.试验1 2.1.1 试验结果 2.2 代码 2.2.1主程序代码 using NLog; using System; using System.Threading; using System.Windows.Forms;namespace 异常监控 {static class Program…

【C#】知识点实践序列之Lock简单解决并发引起数据重复问题

欢迎来到《小5讲堂之知识点实践序列》文章,大家好,我是全栈小5。 这是2023年第3篇文章,此篇文章是C#知识点实践序列文章,博主能力有限,理解水平有限,若有不对之处望指正! 本篇在Lock锁定代码块基…

主浏览器优化之路2——Edge浏览器的卸载与旧版本的重新安装

Edge浏览器的卸载与旧版本的重新安装 引言开整寻找最年轻的她开始卸载原本的Edge工具下载后新版本的安装 结尾 引言 (这个前奏有点长,但是其中有一些我的思考顿悟与标题的由来,望耐心) 我在思考这个系列的时候 最让我陷入困得是…

python 深度学习 记录遇到的报错问题10

本篇继python 深度学习 解决遇到的报错问题9_module d2l.torch has no attribute train_ch3-CSDN博客 一、CUDA error: no kernel image is available for execution on the device CUDA kernel errors might be asynchronously reported at some other API call,so the stackt…

架构设计的核心:从多个维度理论分析

文章目录 一、如何实现高内聚低耦合的架构1、确定边界2、内聚的分类3、耦合的分类4、如何实现高内聚低耦合(1)耦合关注点(2)低耦合原则(3)高内聚原则 二、如何实现可扩展性的架构1、扩展性:核心…

机器人活动区域 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 现有一个机器人,可放置于 M x N 的网格中任意位置,每个网格包含一个非负整数编号,当相邻网格的数字编号差值的绝对值小于等于 1 时机器人可以在网格间移动。 问题: 求机器人可活动的最大范围对应的网格点数目。 说明: 网格…

G-LAB|2024年1月份最新的开班计划

1🈷最新的开班计划 👇👇👇 思科华为HCIA、HCIP、红帽RHCE 可预约免费试听

C#/.NET/.NET Core推荐学习书籍(23年12月更新)

前言 古人云:“书中自有黄金屋,书中自有颜如玉”,说明了书籍的重要性。作为程序员,我们需要不断学习以提升自己的核心竞争力。以下是一些优秀的C#/.NET/.NET Core相关学习书籍,值得.NET开发者们学习和专研。书籍已分类…

微服务智慧工地信息化解决方案(IOT云平台源码)

智慧工地是指应用智能技术和互联网手段对施工现场进行管理和监控的一种工地管理模式。它利用传感器、监控摄像头、人工智能、大数据等技术,实现对施工现场的实时监测、数据分析和智能决策,以提高工地的安全性、效率和质量。 智慧工地平台是一种智慧型、系…

Linux 详解:最完整的入门指南

Linux 是当今最流行的操作系统,仅次于 Windows 和 MacOS。这个开源系统是免费的,在可靠性、安全性和灵活性方面有着悠久的历史。 由于Linux存在于许多设备中并带来了许多优势,因此了解它是什么以及它如何影响计算机行业是至关重要的。 本文…

一呼百应API实时获取商品详情的实现

一、引言 随着电子商务的飞速发展,快速准确地获取商品详情变得尤为重要。一呼百应作为一家知名的B2B采购平台,提供了丰富的商品信息和交易数据。通过一呼百应的API接口,开发者可以实时获取商品详情,为业务决策和数据分析提供有力…

D4145 为什么是交流电源插座接地故障中断器的低功率控制器,有什么作用?

D4145 是。 在发生有 害或致命冲击前,这些器件检测是否有危险的接地情况,比如设备( 与 AC 线路反相连接) 与水以及与裸露电线接触。内含一个 26V 齐纳并联稳压 器、 一个运算放大器和一个 SCR 驱动器。 D4145 新增了两个感测变压 器、一个整流桥、一个 S…

哨兵1号回波数据(L0级)FDBAQ压缩算法详解

本专栏目录: 全球SAR卫星大盘点与回波数据处理专栏目录-CSDN博客 1. 全球SAR卫星回波数据压缩算法统计 各国的SAR卫星的压缩算法按照时间轴排列如下: 可以看出传统的分块BAQ压缩算法(上图粉色)仍然是主流,哨兵1号其实也有传统的BAQ压缩模式。 本文介绍哨兵1号用的FDBAQ算…

如何使用Plex在Windows系统搭建个人媒体站点公网可访问

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频,已经算是生活中稀松平常的场景了,特别是各…

cocos creator + vscode debug

安装插件 安装插件:JavaScript Debugger 配置 7456 为本地cocos creator的启动端口 启动debug调试 选择对应的启动方式

Sift 图片匹配

1. 模式匹配结果 2. 结果的可视化 3. 基于我们找到的匹配猜测仿射变换 4. 调整findHomo的参数,寻找最好的一堆参数 5. 带着新的仿射变换的信息,筛选我们的匹配

Linux之下载安装

rpm包管理 rpm介绍 rpm用于互联网下载包的打包及安装工具,他包含在某些linux分发版本中。他生成具有.rpm扩展名的文件。RPM是RedHat Package Manager(RedHat软件包管理工具)的缩写,类似windows的steup.exe。 rpm包的查询指令 查询已经安装…
最新文章