Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统

时间:2023-01-29 17:55:43

目录

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

前言

在有了字符设备驱动开发基础之后,一个月前踏上Linux驱动学习开发之路,首次接触的是Input子系统,看了很多书籍和博客,发现大多数一上来就讨论了Input子系统设备驱动层的开发步骤,而缺乏了对如何使用input子系统以及四层之间是怎么样联系起来的叙述,毕竟任何一项新的事物来说,先学会使用它,使用熟练了之后再去深究它的原理。这样会比较符合我们学习已经存在的事物的思维。所以这篇博客有两个目的:第一,是对阶段学习做一个笔记。第二:想给类似于一个月前的我作为只接触了字符设备驱动的小白们一些建议。也给大家分享学习任何新框架的核心思想先学应用,再深入原理。文章会结合每部分的内核源代码和一些思维方法来深入剖析input子系统。。末尾附上了我自己手写的文章导图 (字写得不好,见谅-.-)

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@1:Input子系统定义:内核用来管理”输入类”设备的”框架

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@2 :Input 在linux系统中的定位,以此引出怎样学习一个新的框架:

1.定位
Created with Raphaël 2.1.0 Linux OS Linux内核 Linux设备驱动 Input子系统
2.图解
input子系统隶属于linux设备驱动之一,设备驱动又隶属于内核七大子系统之一,内核又是linuxOS的一部分。打开驱动源代码目录 kernel/driver/ 笔者数了一下,可以看到有121个文件夹,如果说一个文件夹对应了一个驱动子系统,也就意味着有121个框架需要我们在未来的时间里去逐步深究,再往上想,linux内核这么庞大的体系,势必会有无穷多个框架需要我们去深入研究。此时,学习的方法就显得尤为重要。
3.框架学习的方法—先学会应用,再去深究原理
拿一个很典型的框架来分析
Created with Raphaël 2.1.0 往上:熟练使用API后,加上自己的创造力写应用----应用开发者 @1:用户层 @2:内核提供给用户层的API:系统调用 往下:熟练使用API后,深入底层去探测实现原理----驱动开发者 @3:kernel
可以看到,一开始学习linux系统框架的时候我们一定是先站在用户层去学习,学习内核提供的一些系统调用,譬如: open,read,ioctl.....等,当你把这些API掌握得能够熟练应用了之后,此时:你的未来方向就可以有两条不同的道路:第一,往上走,你可以使用API再加上你自己的创造力去开发出无限好玩儿的APP。第二,往下走,你就可以深入到内核去探测API的实现原理,譬如咱们将要探索的(kernel/driver/input)input子系统一样。再举一个通俗的例子,如果现在你买了一辆车,你首先的做法应该是先学会如何开车,当你开熟练之后,你可以发挥车的用途去旅游等等,如果你对车的原理感兴趣,那你也可以去深入探测车的组成原理。所以说:咱们学框架,一开始上来就应该先学习怎么使用框架,会使用了之后你明白它都能干些什么事,有些什么功能,而不是一上来就去研究设备驱动层怎么写,即使你看懂了怎么写,你心里也没谱,毕竟我就是这么走过来的。

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@3:什么是输入,输入的方式有哪些?

1.输入:一般是相对于操作系统而言的输入,是人对系统的输入。
2.常见输入的方式有四种

- 命令行输入:如,打开终端输入ls,则操作系统就会列出当前目录元素,此类响应比较单一
- GUI输入:如,windows桌面,当你点击鼠标时,除了要考虑你点击的是左键还是右键之外,还需要考虑你鼠标点击的位置,譬如你点击qq,则会打开qq,点击空白处,则不响应。此类响应给我们带来了便利,但对系统来说处理就更复杂
- 体感识别:如微软出的X-BOX游戏机,他会扫描你的动作,把此动作模拟进去控制里面人物做出相应动作
- 语音输入:而今对于嵌入式行业来说,语音的交互也变得越来越重要

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@4:Input子系统源码架构分析,接下来深入剖析四层

1. ls kernel/driver/input

Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统

2. 分析input子系统源码架构

  • 输入核心层代码:input.c对应了input_core
  • 事件驱动层代码:evdev.c对应eventX_handler,joydev.c对应了joystick_handler,mousedev.c对应了mouse_handler
  • 设备驱动层代码:其余的文件夹对应了具体的设备驱动

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@5:Input子系统的框架分层图

Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统
1.明确这张图
宏观上分两层:驱动层和应用层
驱动局部分三层:输入核心,设备驱动层(注意到设备驱动可以写很多个),事件处理层(而这里只有四个Handler)
接下来对这四层进行深入分析

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@6:Application–应用层分析

本着先应用再原理的学习思路,先学会怎么使用:以ubuntu 系统的鼠标为例(该驱动采用了input子系统框架)
首先需要知道一个数据结构(input_event),一个系统调用(read()),以及一个设备节点文件(eventX);

1. 数据结构(定义在input.h里)

/*描述一次事件的数据结构,如点击一次鼠标,产生一次鼠标事件*/
struct input_event{
time;//事件发生的时间
type;//事件的类型:按键(EV_KEY),还是(EV_REL)。。。事件类型定义在input.h里
code;//如果是按键,则为按键对应的按键码
value;//按下还是抬起,1按下,0抬起
}
该数据结构的作用:当你点击一次鼠标时,就产生了一次鼠标事件。此时,驱动层会封装出这样一个结构体发送到鼠标对应的设备节点文件

2. 设备节点文件:
**1.**linux系统下一切皆文件,每一个设备都对应一个字符设备文件:ls /dev/input 可以看到
Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统
2.依次cat /event0 ~ event15 ,并每次移动鼠标,如果屏幕上出现乱码,则说明该设备节点文件为你所使用系统下的鼠标设备文件
Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统
经测试我的鼠标设备文件为event5,那么为什么会出现乱码,是因为发送上来的是一个结构体,我们直接读结构体打印肯定乱码,因此需要对他进行解析。有了上面这些知识,我们就可以写一个自己的应用程序代码,如下是我的代码
Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统

3.编译运行.,移动鼠标出现如下效果。。。。
Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统

4.到这里,咱们就学会了如何使用input子系统编写一个应用程序,这时候的你并不知道驱动层干了什么事,但是你也依然会使用它。
接下来正式进入驱动层的分析,分析它到底是怎样封装出这个结构体的以及对应的API

Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@7:Input_core—输入核心层分析

核心层没干具体的活,就进行一些协调工作,那为什么叫做“核心”却不用干具体的活呢,你去思考一下任何一个集体的核心每天干不干具体的工作,譬如国家*,他不会具体去山西某个煤矿去挖煤,他一般都是去做一些管理协调的工作
所以这里的input_core干两件事情
一:协调device_driver 和handlers
二:给这两层提供相应的API

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@8:Device_driver–设备驱动层分析(我在11小结将会写出具体的编程步骤和接口函数)

功能:驱动层主要处理不同接口的设备驱动,usb则usb方式来处理,进行具体的硬件操作,这由驱动开发者具体做,驱动分两拨人:一波为内核开发者,写框架,另一波就是具体的硬件驱动工程师实现。这一层难点就是对硬件的操作,把具体的硬件信息提取并上报到input_core,再由input_core上报到事件驱动层

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@9:Handlers–事件处理层分析

1.功能:当handlers接收到input_core传递上来的事件信息之后,他会创建底层设备对应的字符设备节点文件(如上面应用程序里鼠标对应的event5 文件),初始化file_operations ,并封装出input_event结构体,最后选择一条通道(图中一个handler对应一个通道,下面会详细讲解)来把这个结构体发送给用户空间的设备节点文件里去。

2.通道:刚刚提到了通道的概念,框架图中event_handler一共有四个handler,即四个通道,这四个通道反映到用户空间之后就变成了/dev/input/ joyX eventX mouseX keboardX 等这些文件
Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统
图中的以event开头的文件eventX系列文件表明你底层的一个设备最终走的是event_handler这条通道来向用户空间发送数据的,譬如之前测试的event5,则表明鼠标设备最终走event_handler通道来创建该文件,并且来向上汇报数据

3.四部分分析:事件处理层分为了四个部分,那么这四部分之间有什么差异呢?,首先你得明白,到了事件处理层,它处理的基本单位就变成了事件而不是设备驱动层中的设备,这也正是这一层最多只有四个的原因,而你看设备驱动层却有无数多个。
顾明思议
**3.1:**joystick_handler:摇杆事件处理器,游戏机的摇杆
**3.2:**mouse_handler:鼠标事件处理器,处理所有鼠标
**3.3:**keyboard_handler:键盘事件处理器,处理所有键盘
**3.4:**event_handler:通用事件处理器,处理大多数的事件,譬如上面的鼠标事件也可以走这个通道来处理,键盘事件也可以走这个通道来处理。即联想到一个鼠标就可能会对应两个通道,反应到用户空间就是两个设备节点文件,而事实也确实如此,你可以去测试一下mouseX 一定有一个文件也对应了鼠标设备,如下测试为mouse0
Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@10:两个方面来剖析四层之间如何联系起来

一:当一次鼠标点击事件发生之后把input子系统四层联系起来
1.四层联系框图

Created with Raphaël 2.1.0 鼠标点击一次 鼠标点击一次 设备驱动层 设备驱动层 输入核心 输入核心 事件处理层 事件处理层 用户空间 用户空间 1.硬件上:某管脚变低电平 2.检测到之后:把按键信息type,code,value用API上报给core 3.传递给该设备匹配上的通道:按键对应keyboard/event 4.code,value,type封装成结构体发送到设备节点文件供用户读取

二:设备和通道的匹配过程把驱动局部三层联系起来
1.明确三个链表:
内核管理设备驱动层(设备)用一个链表管理list1表示
内核管理事件处理层(通道)用一个链表管理list2表示
当设备和通道匹配上之后内核用handle链表来记录list3表示
2.三层联系框图

Created with Raphaël 2.1.0 鼠标设备 鼠标设备 设备驱动(设备)list1 设备驱动(设备)list1 记录列表list3 记录列表list3 事件处理(通道)list2 事件处理(通道)list2 1。input_device_register()后就会有一个设备加入list1 2。依次调用match函数匹配出合适的通道 3。把匹配上的通道记录到handle列表里

3.对三层联系非分析
当设备驱动层调用input_device_register()函数之后,一个新的设备被加入到list1中
list2中始终维护着四个handlers,且每一个handler下都会有两个函数(match()函数,connect()函数);
每当有一个设备注册到内核时,即加入到list1,那么此时list2中的四个handler会依次调用match()函数来和设备匹配,如果匹配上,则会在用户空间创建主设备号为13,此设备号依次从64开始往后增加的设备节点文件,且文件名的开头部分即为通道名,如 event0,匹配上之后会调用connect()函数将二者绑定,并加入到handle链表中
Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@11:一个真实按键驱动的开发步骤,及底层上报的信息如何在四层之间传递

其实到这里,只要你上面的框架理论分析都搞明白了,再来看这一部分就显得很轻松了,无非就是调用input_core给设备驱动层提供的几个API接口函数嘛

真实的按键驱动开发步骤;在驱动源代码里有一个文档写得很详细了
Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统


**<1>定义input_dev**//自己仔细分析下这个结构体
**<2>input_allocate_device();**//这里面使用了申请动态内存(因为input_dev结构体太大了 所以动态申请)并初始化一些必要的东西,和具体的硬件初始化无关
**<3>input_set_capability();**//这个函数就可以用来对具体的硬件设备进行初始化,设置该硬件设备具有向上层上报什么事件的能力,注意每有一个能力就需要调用一次,比如左键调用一次,右键调用一次。如果不上报上层就为你没有这个能力,你将来报一个没有设置过的将会上报不上去。
Input_set_capability(dev,EV_KEY,BTN_LEFT);//设置鼠标左键,类型,按键码
Input_set_capability(dev,EV_KEY,BTN_LEFT);//右键...
Input_set_capability(dev,EV_KEY,BTN_MIDDLE);//中键...
//code type等都在linux/input.h下有世界统一使用的标准定义



**<4>再注册进去 input_register_device(struct input_dev *dev);**传递一个已经填充好的设备结构体
**<5>利用中断机制在中断处理函数中**
//当检测到按键对应的硬件管脚电平变低时掉如下两个函数上报
input_report_key();//或者是input_event()函数都是上报所用
input_sync();//这里表示你的事件上报完成了
**<6>应用层读取到这些数据之后再进行处理**,这样就ok了
有了前面的分析,你只需要自己找一个驱动代码去研究下别人怎么使用这些API的就可以了

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@12:思考Input子系统为什么会被分为四层

1.分为四层就会有三个分界线:
2.分界图

Created with Raphaël 2.1.0 device_driver device_driver input_core input_core Handles Handles userspace userspace 分界2 分界3 分界1

3.分析为什么
分界1:用户和驱动分离开
分界2:驱动具体操作硬件,驱动工程师来写,input_core 和 Handlers由内核人员来写,与硬件无关
分界3:input_core 和 Handles既然和硬件无关,为什么还有分开,我就干脆合在一起不久得了呗,大:是因为Handles中,譬如你mouse通道能兼容鼠标就兼容不了键盘,故此必须分开。但event通用通道最终一定会取代其他三个通道,更通用的事物会逐渐取代同类型其他事物

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@13:思考应用程序open(“xxx”,O_RDWR);时,xxx文件何时被创建出来以及当应用程序要操作按键时应该打开哪一个设备文件

**这个问题已经在分析Handles时解答**

@14:总结Input子系统的好处,前提是要接触过字符设备开发才能体会到

**第一:提高了代码可移植性**
因为用户在操作设备时一定会先open("xxx",O_RDWR),xxx为设备节点文件名;
传统的字符设备开发中有一个步骤是创建一个设备文件,这就有个弊端,如果你创建一个名叫zhangsan,那么你如果想把你的驱动程序移植给别的公司人员使用时,别人就得事先知道你得文件名,这样的话如果有一百个公司都要使用你得驱动代码,难不成你还要给它一一的说?而使用input子系统之后这个文件就不需要你去创建了,应用程序只需要去检测/dev/input/eventX就可以了

**第二:规范化**
传统字符设备开发时,向用户层发送什么样的驱动数据也是你自己来决定,譬如:我向发送“张三”就发送一个张三,向发送“李四”,就发送“李四”,这就导致应用程序没法儿知道你要发什么数据,就显得很混乱。而使用了input_event 结构体作为事件发送的数据结构之后,就变得规范化,我不管你底层怎么写,只要鼠标点击,你就一定会发送这样一个结构体上来,我应用程序只需要解析就可以知道你底层发生了什么事情....

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

@15:谈一谈要想看懂芯片厂家的驱动源代码还需要具备哪些知识

一:内核3.0以后新增了dts机制
二:驱动代码一律采用platform机制
**三:如何直接从dts中解析得到硬件的具体信息(基地址,gpio编号等等),而不是去看芯片手册里的每一个寄存器
这些我后续都会逐步写上来**

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

——–附上一张我手写的文章导图 [-. -]

Linux设备驱动之Input子系统学习日记--笔者将分享学习一个全新的框架的方法并结合源码深入分析input子系统**

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××