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

news2024/11/23 22:44:10

🐱作者:一只大喵咪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.coloradmin.cn/o/1350659.html

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

相关文章

抽象类和接口的区别(小白版)

抽象类和接口的区别: 抽象类(Abstract Class): 抽象类是一种不能被实例化的类,它只能被用作其他类的父类(基类)。抽象类可以包含抽象方法和非抽象方法。抽象方法是没有具体实现的方法&#xf…

【JAVA】实验二 类与对象

实验名称 实验二 类与对象 实验目的 1. 深刻理解类的封装与继承; 2. 熟练掌握类的定义、包与路径、对象的创建、方法的调用、类的继承、方法的重写、运行时多态、访问权限修饰符的使用等; 3. 熟练运用JDK提供的常用类及API。 实验内容&…

测试自动创建设备节点的功能

一. 简介 上一篇文章在 新设备驱动框架代码的基础上,添加了自动创建设备节点的代码。文章地址如下: 自动创建设备节点代码的实现-CSDN博客 本文对自动创建设备节点的功能进行测试。 二. 自动创建设备节点代码的测试 1. 编译驱动,并拷贝…

Python 数据库(一):使用 mysql-connector-python 操作 MySQL 数据库

大家好,我是水滴~~ 当涉及到使用 Python 操作 MySQL 数据库时,mysql-connector-python 库是一个强大而常用的选择。该库提供了与 MySQL 数据库的交互功能,使您能够执行各种数据库操作,如连接数据库、执行查询和插入数据等。在本文…

2024年美赛数学建模ABCDEF题思路选题分析

文章目录 1 赛题思路2 美赛比赛日期和时间3 赛题类型4 美赛常见数模问题5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 美赛比赛日期和时间 比赛开始时间:北京时间2024年2月2日(周五&#xff…

[笔记] GICv3/v4 ITS 与 LPI

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

浅谈开关量信号隔离器在钢铁厂除鳞系统的应用-安科瑞 蒋静

摘要:在钢铁生产线中,轧制是其中一项重要的加工工艺。通过轧制将金属坯料进行延展和定型,满足不同行业的使用要求。在轧制前需要进行除鳞,除鳞系统是通过高压水形成扇形水束,喷射到钢坯表面将氧化铁层剥离。高压水由高…

精益生产敏捷实践手册:软件行业的精益转型之路——张驰咨询

精益生产培训的内容相当广泛,涵盖创立精益的理念、工具、执行策略和管理方法。下面将详细介绍各种培训内容以及它们的作用: 理念建设 精益生产基本概念:什么是精益,它的历史和核心理念等。 组织变革管理:怎样在组织…

数据库攻防学习之Redis

Redis 0x01 redis学习 在渗透测试面试或者网络安全面试中可能会常问redis未授权等一些知识,那么什么是redis?redis就是个数据库,常见端口为6379,常见漏洞为未授权访问。 0x02 环境搭建 这里可以自己搭建一个redis环境&#xf…

2024网络安全趋势—— “双刃剑”效应带来全新冲击和挑战

“生成式AI”正以前所未有的方式影响着人们的生活和工作方式。 在网络安全方面,这项技术也正深刻改变着对抗形态和攻防模式,其在打开人类认知世界新路径的同时,也成为黑客开展网络攻击的“利器”。随着生成式AI的深入发展,“双刃…

别划走!3分钟看懂 Git 底层工作原理

这是一篇能让你迅速了解 Git 工作原理的文章,实战案例解析,相信我,3 分钟,绝对能够有收获! Git 目录结构 Git 的本质是一个文件系统(很重要,记住这句话,理解这句话)&am…

oracle-undo

tips:串行化隔离级别:事务开始后,对一张表不会被别人影响,对于审计工作比较有用,避免了幻读。 undo表空间:自动生成段,自动生成区,自动维护的,不像一般的表空间&#xff…

【响应式编程-01】Lambda表达式初体验

一、简要描述 Lambda初体验Lambda表达式的语法格式Lambda表达式应用举例Lambda表达式底层实现 二、什么是Lambda表达式 Java8新特性,来源于数学中的λ[l:mdə]演算 是一套关于函数(f(x))定义、输入量、输出量的计算方案 Lambda表达式 -> 函数 使代码变得简洁…

智慧园区物联综合管理平台之架构简述

总体架构 系统总体划分为物联感知系统层、 核心平台层、 综合运营服务平台和展示层四部分。 物联感知系统层 物联感知系统主要是支撑园区智能化运行的各子系统, 包括门禁系统、 视频监控系统、 车辆管理系统等。 核心平台层 核心平台层包括: 园区物联综合管理平台和园区…

6.4 通过IO实现文件的读取与写入

6.4 通过IO实现文件的读取与写入 1. File类及常用方法2. 通过字节字符流实现文件读取与写入1. 流2. 字节输入流 InputStream 3.2. 1. File类及常用方法 package com.imooc.io;import java.io.File; import java.io.IOException;public class FileSample {public static void ma…

Premiere分屏特效图文内容幻灯片展示视频素材PR模板下载

Premiere Pro 模板,多屏幕内容展示PR幻灯片模板,分屏特效图文视频素材pr模板下载。 这是一个高质量、组织良好且易于自定义的视频剪辑模板。只需替换图像或视频,编辑文本,添加音频,微微调整即可! 来自PR模板…

Java经典框架之SpringBoot

SpringBoot Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台。 课程内容的介绍 1. SpringBoot基础 2. Spring…

GraalVM Native学习及使用

概述 在开发Spring Boot 应用或者其他JAVA程序的过程中,启动慢、内存占用大是比较头疼的问题,往往需要更多的资源去部署,成本大幅提高。为了优化上述问题,常常使用优化程序、使用更小消耗的JVM、使用容器等措施。 现在有一个叫做…

权限修饰符和代码块

权限修饰符:是用来控制一个成员能够被访问的范围的。、 可以修饰成员变量、方法、构造方法、内部类。 权限修饰符的范围 权限修饰符的使用规则: 实际开发中,一般只用private和public 成员变量私有 方法公开 特例:如果方法中的…

Debezium日常分享系列之:向 Debezium 连接器发送信号

Debezium日常分享系列之:向 Debezium 连接器发送信号 一、概述二、激活源信号通道三、信令数据集合的结构四、创建信令数据集合五、激活kafka信号通道六、数据格式七、激活JMX信号通道八、自定义信令通道九、Debezium 核心模块依赖项十、部署自定义信令通道十一、信…