看到网上有很多的关于输入子系统的文章,我知道自己的文章还有很多的漏洞和不足。但我坚持每学完一些东西都要进行一些总结。所以写下这篇文章,如有相同或巧合敬请原谅。同时,本文章是基于韦东山老师的视频和开发版所写的,如果有不对的地方敬请指正。
下面进入正文:输入子系统,我们通常将输入子系统分为三个部分,即
1. input.c的核心层
2.以evdev.c为代表的input_handler层:这层为稳定层,代码不发生改动,主要是软件相关的代码
3. 用户自己编写的input_dev层:这层为用户自己编写变化量大,主要是硬件相关的代码
在说输入子系统时,我们先说一下我们自己写一个驱动程序时的框架即
1.设置主设备号:major
2.写file_operations结构体并在其下写出相应的open,read,write·····函数
3 使用register_chrdev注册一个上面file_operations定义的设备,这个设备的主设备号为major
4 写入口函数
5 写出口函数
而在输入子系统中,我们上面所提到的框架中所有的步骤,都有,只是被分成了三部分
下面我们开始分析输入子系统,我们从drivers/input/input.c函数的入口函数分析(分析一个驱动程序都是从入口函数开始分析):
static int __init input_init(void)
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
在入口函数中我看到有register_chrdev函数,而INPUT_MAJOR为主设备号:13,input_fops为file_operations结构体。所以,在子系统中一个基本的框架已经有了。但是当我们去看file_operations结构体时会发现他里面只有open函数,也就是说他只能打开设备,而其他的操作要有其他的file_operations来完成(其实这里的open函数也不是打开设备的作用,而是起一个中转的作用)。当我们打开open函数所对应的函数时有:
struct input_handler *handler = input_table[iminor(inode) >> 5]; //根据次设备号,将input_table中的input_handler结构体分配给handler
const struct file_operations *old_fops, *new_fops = NULL; //定义新旧两个file_operations结构体,
new_fops = fops_get(handler->fops) //将设备所对应的handler的file_operations结构体赋值为new_fops ,此时已经实现中转作用,也就是说从前 //的之后的操作中将只用现在的file_operations而不是用在入口函数中的file_operations。
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file); //下面代码就是使用new_fops的open函数去实现APP所指定的open功能。
上面只是说明了打开一个设备的过程,而我们说过输入子系统分为三部分,那这三部分是怎样联系,或者说他们是怎样实现其他的功能那?
下面我们需要对input_register_handler,input_register_device函数进行分析
首先是int input_register_handler(struct input_handler *handler)
input_table[handler->minor >> 5] = handler; //将这个input_handler结构体根据次设备号放入一个input_table数组中(这就是我们上面说的要用次设备号分 //配handler打下基础)
list_add_tail(&handler->node, &input_handler_list); //将这个handler添加到一个input_handler_list的链表中。
list_for_each_entry(dev, &input_dev_list, node) //
input_attach_handler(dev, handler); //根据input_handler的id_table判断能否支持这个input_dev设备
下面是int input_register_device(struct input_dev *dev)
list_add_tail(&dev->node, &input_dev_list); //将这个dev添加到一个input_dev_list的链表中。
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); /根据input_handler的id_table判断能否支持这个input_dev设备
看到上面的代码你会发现相似的地方吧,他们都要将对方的链表遍历一遍来确定是否存在一个dev与handler相匹配。如果匹配那又该怎么办那??那我就要在看看input_attach_handler函数了:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
id = input_match_device(handler->id_table, dev);
error = handler->connect(handler, dev, id); //从上面的代码可以看出通过handler的id_table和dev相比较,如果相同则调用handler的connect函数。
那他们比较的是什么那?
if (id->bustype != dev->id.bustype)
continue;
if (id->vendor != dev->id.vendor)
continue;
if (id->product != dev->id.product)
continue;
if (id->version != dev->id.version)
continue;
其实就是:
struct input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};
那么比较相同怎么调用handler的connect函数那???
我们知道不同的handler有不同的file_operations所以就有不同的connect函数,下面我们以evdev的connect函数为例:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //分配一个input_handle结构体
evdev->handle.dev = dev; //设置input_handle,使handle.dev 指向dev
evdev->handle.handler = handler; //设置input_handle,使handle.handler 指向 handler
error = input_register_handle(&evdev->handle); //注册handle
list_add_tail(&handle->d_node, &handle->dev->h_list); //将dev下的h_list指向handle
list_add_tail(&handle->h_node, &handler->h_list); //将handler下的h_list指向handle
通过上面的handle实现 handler <=> handle <=> dev 三者的互相联通。当dev可以通过h_list找到所对应的handle再找到所对应的handler,而handler可以通过h_list找到所对应的handle再找到所对应的dev。这样就可以实现通过在注册dev时找到所对应的handler了。同时可以调用handler中的file_operations中的对应open,read,write函数了。
说到这里,输入子系统的框架就大致说清楚了。但还有一些细节是要说明的。
在APP中当我们使用read函数时,最终会调用到handler中的read函数。我们这里以evdev为例,就会调用到evdev_read函数。在函数中有
retval = wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); //此函数使进程进入休眠,
那么谁去唤醒他那???
这就要说到事件上报和事件处理了。当有指定的事件产生时用上报事件函数
* input_event() - report new input event
* @dev: device that generated the event
* @type: type of the event
* @code: event code
* @value: value of the event
*
* This function should be used by drivers implementing various input devices
* See also input_inject_event()
而事件处理函数是对不同的事件做不同的处理,而这时候我们就要介绍一下常用到的一些事件:
/*
* Event types
*/
#define EV_SYN 0x00 //同步类事件
#define EV_KEY 0x01 //按键类事件
#define EV_REL 0x02 //相对位移类事件
#define EV_ABS 0x03 //绝对位移类事件
#define EV_MSC 0x04 //其他类事件
#define EV_SW 0x05 //开关类事件
下面通过写符合输入子系统框架的按键驱动程序对其进行说明:
下面是实现的步骤
1.写入口函数,并在其中实现对input_dev结构体的定义、分配,设置,注册,并进行相关硬件的设置:如注册中断和初始化定时器
static int input_drv_init(void)
{
int i;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();
/* 2. 设置input_dev结构体 */
/* 2.1 设置能产生哪类事件 : 按键事件*/
set_bit(EV_KEY,buttons_dev->evbit);
set_bit(EV_REP,buttons_dev->evbit);
/* 2.2 设置产生这类事件中的具体事件 :L,S,ENTER,LEFTSHIFT*/
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_register_device(buttons_dev);
/* 4. 硬件相关的操作 */
//注册中断
for(i=0;i<4;i++){
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name,&pins_desc[i]);
}
//设置定时器
init_timer(&button_timer);
button_timer.function = buttons_timer_function;
add_timer(&button_timer);
return 0;
}
2.既然有入口函数就会有出口函数,出口函数的作用与入口函数的作用相反:解除中断,删除定时器,注销设备
static void input_drv_exit(void)
{
int i;
for(i=0;i<4;i++){
free_irq(pins_desc[i].irq,&pins_desc[i]);
}
del_timer(&button_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
3.实现对中断的处理和事件的上报:
//中断处理函数
static int buttons_irq(int irq,void *dev_id)
{
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&button_timer,jiffies+HZ/100);
return 1;
}
//定时器处理函数
static void buttons_timer_function(unsigned long data)
{
struct pin_desc *pindesc = irq_pd;
if(!pindesc){
return;
}
unsigned int pinval;
//获得gpio的键值松开为1,按下为0
pinval = s3c2410_gpio_getpin(pindesc->pin);
if(pinval){
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);
}
}