输入子系统—— 按键驱动

看到网上有很多的关于输入子系统的文章,我知道自己的文章还有很多的漏洞和不足。但我坚持每学完一些东西都要进行一些总结。所以写下这篇文章,如有相同或巧合敬请原谅。同时,本文章是基于韦东山老师的视频和开发版所写的,如果有不对的地方敬请指正。

下面进入正文:输入子系统,我们通常将输入子系统分为三个部分,即

  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);
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值