原文:《韦东山Linux视频驱动第2期》学习总结之第13课(输入子系统)
内核的输入子系统是对分散的,多种不同类别的输入设备(如键盘,鼠标,跟踪球,操纵杆,触摸屏,加速计和手写板)等字符设备进行统一处理的一层抽象,就是在字符设备驱动上抽象出的一层。
输入子系统包括两类驱动程序:事件驱动程序和设备驱动程序。事件驱动程序负责和应用程序的接口,而设备驱动程序负责和底层输入设备的通信。鼠标事件生成文件mousedev属于事件驱动程序,而PS/2鼠标驱动程序是设备驱动程序。事件驱动程序是标准的,对所有的输入类都是可用的,所以要实现的是设备驱动程序而不是事件驱动程序。设备驱动程序可以利用一个已经存在的,合适的事件驱动程序通过输入核心和用户应用程序通信。
input子系统分三层,最上一层是event handler,中间是intput core,底层是input driver。input driver把event report到input core层。input core对event进行分发,传到event handler,相应的event handler层把event放到event buffer中,等待用户进程来取。它们的关系图:
注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用input_handler的connect函数建立"连接"
怎么建立连接?
1. 分配一个input_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;
input_dev,input_handler,input_handle 是 input子系统的3个基本的数据结构。一类handler可以和多个硬件设备相关联,一个硬件设备可以和多个handler相关联。例如:一个触摸屏设备可以作为一个event设备,作为一个鼠标设备,也可以作为一个触摸设备,所以一个设备需要与多个平台驱动进行连接。而一个平台驱动也不只为一个设备服务,一个触摸平台驱动可能要为A,B,C 3个触摸设备提供上层驱动,所以需要这样一对多的连接。
总结来说,输入子系统分上下两层。最上层是核心层input.c,里面有register_chrdev。这个注册的字符设备的fops里面只有一个open函数。该open函数根据打开的设备节点的次设备号,找到一个input_handler。把打开的文件的里面的fop指向这个handler里面的fops,并且打开这个handler里面的open函数。以后要读写的时候就调用这个handler里面的读写函数,如下代码片段1。input_handler通过input_register_handler向核心层注册一个input_handler结构体,并且在input_table数组第i个值指向这个注册的input_handler(“i”是根据handler里面的次设备号向右移5位算出的值)。这个input_table[i]就可在上诉open函数里根据打开的设备节点的次设备号找到相应input_handler。
input_dev通过input_register_device向核心层注册一个input_dev结构体。
当注册一个input_dev设备或者注册一个input_handler时,input_dev与input_handler会通过调用input_attach_handler函数搜索是否有合适的input_handler或者input_dev与之匹配。如果存在inputh_handler与注册的input_dev匹配或者存在input_dev与注册的inputh_handler匹配,则会调用input_handler里的connect函数。匹配的依据就是input_handler中的id_table与input_dev中的id里的信息是否相同。
connect函数会建立一个input_handle结构体,这个结构体里面的handler指向匹配的input_handler,dev指向input_dev。并且这个input_handle会被放到input_handler和input_dev的h_list链表里面来。以后就可以从input_dev里面的h_list找到handle,再从handle里面的handler找到input_handler。反之亦然。
对于输入设备,我们知道一般需要读取其数据,那么经过上述的一些列动作后又是如何读取到数据的呢?如果没有数据可读并且是NONBLOCK的话,就立刻返回。如果是没有数据可读但是BLOCK的话,就进入睡眠。既然有睡眠,那何时被唤醒呢?在evdev_read函数中唤醒read函数。evdev_event是input_handler中的成员,当有数据可读(如触摸屏被按下,按键被按下)时,event函数会被调用。
而event函数是怎么被调用到的?这就得看设备层了,设备层的驱动做了如下工作:
<1>在中断函数中确定判断发生了什么事件,并且相应的值是多少;<2>通过input_event()函数上报事件;
<3>通过input_sync()函数表明上报结束。
分析input_event函数我们就可以知道input_handler中的event函数是如何被调用到的了。
在input_event中,调用了input_handle_event函数,在input_handle_event函数中调用了input_pass_event函数。 在input_pass_event函数中,将input_handle从input_dev的h_list中一个个拿出来。如果找到一个input_handle被打开,则input_handle->input_handler即为input_dev所匹配的handler。
输入子系统之编写驱动程序
怎么写符合输入子系统框架的驱动程序?1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件
下面开始编写代码:
1. 从入口函数开始执行
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static int buttons_init(void)
{
int i;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();;
/* 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);
/* 3. 注册 ,把我们的input_dev结构放入设备链表;最终会在某个数据里面找到 input_handler结构,找到它的 .fops,调用.fops的read()、write()方法。*/
input_register_device(buttons_dev);
/* 4. 硬件相关的操作,防抖动 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
module_init(buttons_init);
struct input_dev的结构中的几个成员:
struct input_dev {
unsigned long evbit[NBITS(EV_MAX)]; // 表示能产生哪类事件
unsigned long keybit[NBITS(KEY_MAX)]; // 表示能产生哪些按键
unsigned long relbit[NBITS(REL_MAX)]; // 表示能产生哪些相对位移事件, x,y,滚轮
unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y
};
2. 编写 定时器事件和中断处理事件
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev); //上报同步事件,表示事件已上报完成
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}
以前要唤醒应用程序并发信号 ,input中只要上报事件就可以了。
3. 出口函数
static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_exit(buttons_exit)
因为视频中没有说测试程序怎么写,所以在网上找了下,下面列出来,但不是这个驱动的 只能当学习了解一下:测试程序 - 关于Linux设备驱动中input子系统的介绍
测试程序:
#include<stdio.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<linux/input.h>
int main(void )
{
int fd;
int key_value,i=0,count;
struct input_event ev_key;
fd=open("/dev/input/event0",0666);
if(fd<0){
perror("open device");
exit(1);
}
while(1){
count=read(fd,&ev_key,sizeof(struct input_event));
for(i=0;i<(int)count/sizeof(struct input_event);i++)
{
if(EV_KEY==ev_key.type)
{
int num=ev_key.code%10-1;
printf("type:%d,code:%d ,value:%d\n key%d pressed!\n",ev_key.type,ev_key.code,ev_key.value,num);
}
if(EV_SYN==ev_key.type)
printf("syn event\n");
}
}
close(fd);
return 0;
}
小知识:
根据我们输入或删除显示字符的是 -sh 进程,执行:#ps 可以看查看到它的PID 号是770,可以通过命令:# ls -l /proc/770/fd 查看它打开了哪些文件。