零、几个问题
(1)为什么有输入子系统:
针对输入设备的多样性和输入事件的多样性,内核通过输入子系统来实现输入设备的驱动、输入事件的提交及对输入事件的读取,并有统一的命名规则。
(2)分层分离结构
input子系统是对不同类型的输入设备进行统一处理的驱动程序。一个输入事件,如按键,按照:
驱动层-->系统核心层-->事件处理层-->用户空间层
的顺序达到用户空间并传递给应用程序的。
input子系统组成
驱动层
核心层
事件处理层
(3)几个重要结构体
input_dev:物理输入设备结构体,包含设备信息。存在input_dev_list链表中
input_handler:事件处理结构体,实现事件处理逻辑。存在input_handler_list链表中
input_handle:建立input_dev与input_handler之间关系。input_handle与input_dev、input_handler之间关系的链表。
内核中,有很多写好的框架、例子等,理解这些框架的流程,把其头文件包含进来。调用系统框架的函数,来实现驱动等。以前自己写,只能是自己的人用,现在用内核现成的写好的,来修改。
(4)input子系统我们需要做什么?
系统核心层input、事件处理层handler已经被系统做好了,内核启动的时候会加载相关驱动。我们需要做的是注册设备层的驱动,注册过程被核心层知道后,会产生设备dev和handler处理者之间的连接,我们要做的就是按照input子系统的框架,写出对应的设备驱动。
(5)架构图
Linux输入子系统(Input Subsystem):
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。下面用图形来描述一下这三者的关系吧!
一、input子系统分析
1、以前我们写驱动
register_chrdev
file_operations
module_init
module_exit
缺点:不能用在现成的应用程序,别人的应用程序不可以用。别人可能打开tty,scanf,如何适应这种情况?
2、使用现成的驱动:输入子系统
输入子系统框架,也有以上的几点,只不过是别人写好的,我们只需要修改。
框架分析如下:Input.c (driversinput)核心层。
(1)看驱动程序从入口函数开始看:subsys_initcall(input_init);--》
static int __init input_init(void)
{
int err;
。。。
err = class_register(&input_class);
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
。。。
}
#define INPUT_MAJOR13 可以看出输入子系统的主设备号都是13
(2)查看file_operations 结构体
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
只有一个open函数, 这个open肯定做了某些特殊事情。
static int input_open_file(struct inode *inode, struct file *file)
{
。。。
//从input_table中获得一个input_handler结构体。input_handler结构体里面存有file_operations结构体,后面会介绍。
struct input_handler *handler = input_table[iminor(inode) >> 5];
//定义两个file_operations指针变量
const struct file_operations *old_fops, *new_fops = NULL;
if (!handler || !(new_fops =fops_get(handler->fops)))
return -ENODEV;
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);
。。。
}
这个特殊的open中,根据打开文件的次设备号得到一个input_handler,从input_handler中得到应用程序的file_operations,进行打开。
(3)input_table在哪呢?
在input_register_handler中构造了这个数组项。
int input_register_handler(struct input_handler *handler)
{。。
input_table[handler->minor >> 5] = handler;
}
(4)input_register_handler被谁调用呢?内核中搜索
(一些通用的设备函数调用,系统写好的处理函数)input_register_handler被input的下层调用。
因此是被下一层调用。
(5)以Evdev.c (driversinput)为例
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
调用input_register_handler向上核心层注册
(6)evdev_handler是一个input_handler结构体被存到input_table中。
static struct input_handler evdev_handler ={undefined
.event= evdev_event,
.connect= evdev_connect,
.disconnect= evdev_disconnect,
.fops= &evdev_fops,
.minor= EVDEV_MINOR_BASE,
.name= "evdev",
.id_table= evdev_ids,
};
input_register_handler(&evdev_handler);
.fops = &evdev_fops evdev_fops里面有各种函数 如open
evdev_fops中
.minor = EVDEV_MINOR_BASE,=64
因此input_register_handler中input_table[handler->minor >> 5] = handler; =input_table[2]=handler
evdev 64右移5放到input_table的第二项input_table[2]
evdev_handler是一个input_handler结构体。
假设应用程序要读:
(7)handler 是纯软件,事件处理层(EventHandler)
struct input_handler evdev_handler中有一个id_table,
.id_table = evdev_ids,表示这个hander能支持哪些设备。
(8)还有一层叫设备驱动层。
看一下这个hander
.id_table= evdev_ids, 表示这个hander能支持哪些设备。
能支持会调用.connect = evdev_connect,函数 ,建立一个连接.
(9)搜索下谁会调用 input_register_device,很多按键 鼠标 触摸屏等
注册输入设备:
看一下input_register_device做什么
list_add_tail(&dev->node,&input_dev_list);
会把一个结构体放入链表input_dev_list中
list_for_each_entry(handler,&input_handler_list, node)
input_attach_handler(dev,handler);
对链表里的每个input_handler(注册的时候也会放入链表),都调用input_attach_handler(dev, handler);来根据input_handler的id_table来判断能不能支持这个input_device输入设备。
(10)
注册input_register_handler的时候做什么?
放入数组
input_table[handler->minor >> 5] =handler;
放入链表
list_add_tail(&handler->node,&input_handler_list);
对每个input_dev调用input_attach_handler,来根据input_handler的id_table来判断能不能支持这个输入设备。
list_for_each_entry(dev,&input_dev_list, node)
input_attach_handler(dev,handler);
很对称 无论先加载右边Hander还是 左边的设备,都可以调用input_attach_handler。
(11)来看一下input_attach_handler做什么?
根据handler的id_table和输入设备dev看看能不能匹配,可以的话调用handler 的connect函数
id= input_match_device(handler->id_table, dev);
if(!id)
return-ENODEV;
error= handler->connect(handler, dev, id);
if(error && error != -ENODEV)
注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,
根据input_handler的id_table判断这个input_handler能否支持这个input_dev,
如果能支持,则调用input_handler的connect函数建立"连接",每个handler都有自己不同方式。
(12)怎么建立连接?
看一看evdev的connect函数
1. 分配一个input_handle结构体
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
error = input_register_handle(&evdev->handle);
2.
input_handle.dev= input_dev; // 指向左边的input_dev
input_handle.handler= input_handler; // 指向右边的input_handler
3. 注册:
input_handler->h_list = &input_handle;
inpu_dev->h_list =&input_handle;
evdev_connect
evdev= kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle
//设置
evdev->handle.dev= dev; // 指向左边的input_dev
evdev->handle.name= evdev->name;
evdev->handle.handler= handler; // 指向右边的input_handler
evdev->handle.private= evdev;
//注册
error= input_register_handle(&evdev->handle);
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
list_add_tail(&handle->d_node, &handle->dev->h_list);
list_add_tail(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
return 0;
}
(13)怎么读按键?
app: read
--------------------------
.......新的结构体的read函数
evdev_read
// 无数据并且是非阻塞方式打开,则立刻返回
if(client->head == client->tail && evdev->exist &&(file->f_flags & O_NONBLOCK))
return-EAGAIN;
//否则休眠(此时休眠,后面按键按下后由硬件唤醒)
retval= wait_event_interruptible(evdev->wait,
client->head!= client->tail || !evdev->exist);
谁来唤醒?
evdev_event
wake_up_interruptible(&evdev->wait);
evdev_event事件处理函数,唤醒应用程序。设备出发,hander处理。
evdev_event被谁调用?
猜:应该是硬件相关的代码,input_dev那层调用的
在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数
gpio_keys_isr
//上报事件
input_event(input,type, button->code, !!state);
input_sync(input);
input_event(struct input_dev *dev, unsignedint type, unsigned int code, int value)
structinput_handle *handle;
list_for_each_entry(handle,&dev->h_list, d_node)
if(handle->open)
handle->handler->event(handle,type, code, value);
二、按键input子系统代码实现
1.参考Gpio_keys.c (driversinputkeyboard)
(1)以前写驱动程序步骤:
应用程序:open、read、write。。。
驱动:drv_opne、drv_read、drv_write。。。
然后有一个结构体,涵盖了各个方法,注册、入口、出口
(2)input.c别人写的,也包括:
主设备号:13
file_opreations结构体:只有open函数(中转作用,找到某个hander 用里面的file_opreations)
注册函数:regist_chrdev(13,。。)
入口函数:
出口函数:
(3)怎么写符合输入子系统框架的驱动程序?
分配一个input_dev结构体
设置
注册
硬件相关的代码,比如在中断服务程序里上报事件
(4)参考Gpio_keys.c (driversinputkeyboard),拷贝里面的头文件
static int buttons_init(void)
static void buttons_exit(void)
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
(5)分配一个input_dev结构体
static struct input_dev *buttons_dev;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();
(6)设置结构体的属性参数
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
struct input_dev {
void *private;
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)]; //表示能产生哪类事件
设置可以产生:同步类、按键类事件、相对位移类、绝对位移事件(触摸屏)等事件
unsigned long keybit[NBITS(KEY_MAX)]; //表示能产生哪些按键L、S
unsigned long relbit[NBITS(REL_MAX)]; // 表示能产生哪些相对位移事件,x,y,滚轮
unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y
unsigned long mscbit[NBITS(MSC_MAX)];
unsigned long ledbit[NBITS(LED_MAX)];
unsigned long sndbit[NBITS(SND_MAX)];
上一篇:09-S3C2440驱动学习(三)嵌入式linux-platform平台总线驱动程序及分离分层构建驱动框架
下一篇:07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-查询+中断+引入poll机制的按键驱动程序
推荐阅读最新更新时间:2024-11-09 11:28
推荐帖子
- 2022得捷电子创新设计大赛+开箱贴
- 今天刚收到得捷电子发过来的货,发个开箱贴,多图准备,顺便测试下STM32F7508-DK开发板的使用情况。发货单上详细写了元器件型号等相关信息。主角STM32F508-DK。购置了各种各样的传感器,后续做一款拓展板全部集成在一起。然后就是测试下板子自带例程这里是VNC例程,连接网线后即可测试。这个里面带了两个图形GUI方案。使用下来还是很流畅的。看下背面,所带接口满足我本次开发需求了。最后视
- 流行科技 DigiKey得捷技术专区
- 简易直流电子负载(C题)
- 设计和制作一台恒流(CC)工作模式的简易直流电子负载。其原理示意图如图1所示。图1直流电子负载原理示意图二、要求1.基本要求(1)恒流(CC)工作模式的电流设置范围为100mA~1000mA,设置分辨率为10mA,设置精度为±1%。还要求CC工作模式具有
- 田健平 模拟电子
- 求助,晶丰明源的BP85928D应用,看不太懂
- 这个固定5V输出,有输出引脚吗?是如何做到输出的?求助,晶丰明源的BP85928D应用,看不太懂【这个固定5V输出,有输出引脚吗?是如何做到输出的?】看电原理图,像是Buck-Boost电路。输出是Vout两端,上为负,下为正,恐怕不是【固定5V输出】。 上面又写着输出电压纹波小,但是看手册,就没有能输出电压的PIN脚把,我都看懵了。 【但是看手册,就没有能输出电压的PIN脚把,我都看懵了】确实没有输出电压的引脚。输出的正负两端都与芯片不直接联接。
- tangbaoovo 电源技术
- 编译过程的警告
- 今天去编译Linux3.11,ARM1176,BCM2835.这个错误产生的原因大概有哪些?我用linux3.10.10的旧配置文件直接编译/编译过程的警告
- cl17726 Linux与安卓
- “全数控、积木式TI方案电源”分工,期待你加入!
- “全数控、积木式TI方案电源”的项目,现在需要大量人员参与开发,对每一部分的电路拓扑、元器件选择及定型作一下实验,需要第一手的数据。希望大家多协助,踊跃参加。现把整个项目分为几个部分,由不同人员分别调试,最后将调试数据(包括中间数据和最终数据)汇总整理。其划分如下:一、PFC部分。指标:输入AC160V~230V,输出DC390V+10V-20V;功率250W;频率100KHz~150KHz电路拓扑:受CP
- dontium DIY/开源硬件专区
- 【2024 DigiKey 创意大赛】”双光融合“智能热像仪 作品总结与提交
- 双光融合智能热像仪作者:JOEYCH 作品简介本项目为一款双光融合智能热像仪,双光融合指的是可见图像与热图像相融合。该设备硬件上基于高性能STM32H7微控制器,软件上基于使用micropython语言的OPENMV开源项目和TensorFlowLiteAI模型框架,实现了图像采集的高效率和手写数字分析的高精度。听取秦天大佬的建议,添加LCD触摸屏提供直观的用户交互体验,支持模式的快速切换和实
- JOEYCH DigiKey得捷技术专区
设计资源 培训 开发板 精华推荐
- 具有同步栅极驱动器的 RT7258 8A、24V、600kHz 降压转换器的典型应用,适用于 WDFN-14L 4x3 封装
- 【训练营】简单小型红外遥控-340465A
- LT6654AHS6-2.5、16 位 ADC 电压基准的典型应用
- NV47711PDAJGEVB:350 mA LDO 稳压器评估板
- LT1933ES6 3.3V 降压转换器的典型应用电路
- 电赛专用
- LT1021DCN8-5 由电流源驱动的负分流基准的典型应用
- QN8027电路设计
- 使用 NXP Semiconductors 的 NCX2200 的参考设计
- 使用 ON Semiconductor 的 NCP1605 的参考设计