[置顶] 输入子系统
驱动框架
核心层input.c
1.输入子系统核心对应与/drivers/input/input.c文件,这个也是作为一个模块注册到内核的。所以首先分析模块初始化函数
点击(此处)折叠或打开
- input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
-
- static const struct file_operations input_fops = {
- .owner = THIS_MODULE,
- .open = input_open_file,
- };
app: read > ... > file->f_op->read
进入input_open_file
定义一个input_handler指针
点击(此处)折叠或打开
-
struct input_handler *handler = input_table[iminor(inode) >> 5];
- file->f_op = new_fops;
点击(此处)折叠或打开
-
if (handler)
-
new_fops = fops_get(handler->fops);//从input_handler中提取出new_fops
-
file->f_op = new_fops;//将new_fops赋值给当前文件的file_operations
- err = new_fops->open(inode, file);
经过上面的操作之后,当应用程序再来调用read,open,write等函数的时候,最终都会调用到file->f_op中的read,write,open函数。
那么input_open_file函数中的input_table 又是从何而来的呢?
可见在input.c的input_register_handler函数中构造了input_table
点击(此处)折叠或打开
-
int input_register_handler(struct input_handler *handler)
-
{
-
struct input_dev *dev;
-
-
INIT_LIST_HEAD(&handler->h_list);
-
-
if (handler->fops != NULL) {
-
if (input_table[handler->minor >> 5])
-
return -EBUSY;
-
-
input_table[handler->minor >> 5] = handler;
-
}
-
-
list_add_tail(&handler->node, &input_handler_list);
-
-
list_for_each_entry(dev, &input_dev_list, node)
-
input_attach_handler(dev, handler);
-
-
input_wakeup_procfs_readers();
-
return 0;
- }
又是谁在调用input_register_handler呢?
搜索内核代码可知:evdev.c,tsdev.c,joydev.c,keyboard.c,mousedev.c等文件调用了 input_register_handler,我们以evdev.c为例
在drivers/input/evdev.c中:
进入模块入口函数evdev_init:
点击(此处)折叠或打开
-
static int __init evdev_init(void)
-
{
-
return input_register_handler(&evdev_handler);
- }
evdev_handler如下:
点击(此处)折叠或打开
-
static struct input_handler evdev_handler = {
-
.event = evdev_event,
-
.connect = evdev_connect,
-
.disconnect = evdev_disconnect,
-
.fops = &evdev_fops,
-
.minor = EVDEV_MINOR_BASE,
-
.name = "evdev",
-
.id_table = evdev_ids,
- };
这里需要先学习下输入子系统的架构
当通过input_register_device注册一个input_dev设备或者通过input_register_handler注册一个input_handler时,input_dev与input_handler会进行匹配,如何匹配?
答:在input_handler中有一个id_table,里面包含的就是这个input_handler能处理的input_dev,当input_handler与input_dev匹配成功的话,则会调用input_handler里的connect函数来建立连接。
当通过input_register_device注册一个input_dev设备或者通过input_register_handler注册一个input_handler时,input_dev与input_handler会进行匹配,如何匹配?
答:在input_handler中有一个id_table,里面包含的就是这个input_handler能处理的input_dev,当input_handler与input_dev匹配成功的话,则会调用input_handler里的connect函数来建立连接。
那么他们怎样建立连接呢?
下面我们来分析下input_register_device和input_register_handler分别做了什么:
input_register_device:
1.将刚注册的input_dev放入input_dev_list链表
点击(此处)折叠或打开
- list_add_tail(&dev->node, &input_dev_list);
点击(此处)折叠或打开
-
/*对 input_handler_list中的每一个input_handler都调用 */
-
*input_attach_handler(dev, handler);这个函数,来查看是否有input_handler *合适该input_dev
-
*/
-
list_for_each_entry(handler, &input_handler_list, node)
-
input_attach_handler(dev, handler);
-
input_register_handler:
-
//将刚注册的input_handler放入input_table中
-
input_table[handler->minor >> 5] = handler;
-
//将刚注册的input_handler放入 input_handler_list链表中
-
list_add_tail(&handler->node, &input_handler_list);
-
/*对 input_dev_list 中的每一个input_dev都调用
-
*input_attach_handler(dev, handler);这个函数,来查看是否有input_dev合适该input_handler
-
*/
-
list_for_each_entry(dev, &input_dev_list, node)
- input_attach_handler(dev, handler);
由上面可以看出,无论是先注册input_handler还是先注册input_dev最终都会调用来input_attach_handler(dev, handler),来进行两两匹配。现在我们来看看input_attach_handler是如何将input_handler和input_dev进行匹配的。
在input_attach_handler函数中:
/*
在input_attach_handler函数中:
/*
**先进行匹配,匹配的依据就是input_handler中的id_table与 *input_dev中的id里的信息是否相同*/
id = input_match_device(handler, dev);
/*再调用input_handler中的connect函数完成连接,具体如何连接,需要分析connect函数*/
error = handler->connect(handler, dev, id);
我们以evdev.c中的input_handler结构中的connect函数为例,分析connect函数做了些什么。
id = input_match_device(handler, dev);
/*再调用input_handler中的connect函数完成连接,具体如何连接,需要分析connect函数*/
error = handler->connect(handler, dev, id);
我们以evdev.c中的input_handler结构中的connect函数为例,分析connect函数做了些什么。
在evdev_connect函数中:
点击(此处)折叠或打开
-
/*分配一个evdev结构体,该结构体中含一个input_handle结构
-
*注意:不同于input_handler结构
-
*/
-
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
-
-
/*设置evdev结构中的input_handle
-
*@input_handle的dev变量指向input_dev结构
-
*@input_handle的handler变量指向input_handler结构
-
*/
-
evdev->handle.dev = input_get_device(dev);
-
evdev->handle.name = dev_name(&evdev->dev);
-
evdev->handle.handler = handler;
-
evdev->handle.private = evdev;
-
-
/*向内核注册input_handle*/
- error = input_register_handle(&evdev->handle);
下面我们来看注册 input_register_handle做了些什么:
复制代码
/*将input_handle添加到input_dev中的h_list链表中
*以后当input_dev需要使用对应的input_handler时就可以通过自身的*h_list链表找到input_handle,从而找到匹配的input_handler。
*/
list_add_tail_rcu(&handle->d_node, &dev->h_list);
/*将input_handle添加到input_handler中的h_list链表中,
*以后当input_handler需要使用对应的input_dev时就可以通过自身的
*h_list链表找到input_handle,从而找到匹配的input_dev
*/
list_add_tail_rcu(&handle->h_node, &handler->h_list);
对于输入设备,我们知道一般需要读取其数据,那么经过上述的一些列动作 后又是如何读取到数据的呢?我们来看下evdev.c中的input_handler中的fops 中的read函数
在evdev_read函数中:
//如果没有数据且nonblock的话,则EAGAIN
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);
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是input_handler中的成员,当有数据可读(如触摸屏被按下,按键被按下)时,event函数会被调用。而event函数是怎么被调用到的?
在evdev_event函数中: //唤醒
wake_up_interruptible(&evdev->wait);
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函数中:
<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函数中:
struct input_handler *handler;
/*从注册的input_dev的h_list中将input_handle一个个拿出来*/
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
if (!handle->open)
continue;
//如果该input_handle被打开,则该input_handle->input_handler即为可 //处理该input_dev的handler
handler = handle->handler;
if (!handler->filter) {
if (filtered)
break;
/*最终调用到event函数*/
handler->event(handle, type, code, value);
} else if (handler->filter(handle, type, code, value))
filtered = true;
}
/*从注册的input_dev的h_list中将input_handle一个个拿出来*/
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
if (!handle->open)
continue;
//如果该input_handle被打开,则该input_handle->input_handler即为可 //处理该input_dev的handler
handler = handle->handler;
if (!handler->filter) {
if (filtered)
break;
/*最终调用到event函数*/
handler->event(handle, type, code, value);
} else if (handler->filter(handle, type, code, value))
filtered = true;
}
最后,回归到如何写符合输入子系统框架的驱动程序,四个步骤:
<1>分配一个input_dev结构体
<2>设置input_dev
<3>注册input_dev
<4>在中断函数中上报事件
<1>分配一个input_dev结构体
<2>设置input_dev
<3>注册input_dev
<4>在中断函数中上报事件
接下来就是编写一个符合输入子系统的驱动程序了
安装上面的方法:
<1>分配一个input_dev结构体
<2>设置input_dev
<3>注册input_dev
<4>在中断函数中硬件相关的代码
<2>设置input_dev
<3>注册input_dev
<4>在中断函数中硬件相关的代码
那么怎样来写这个符合要求的驱动程序呢?
A.先参考gpio_keys.c添加头文件
点击(此处)折叠或打开
-
/* 参考drivers\input\keyboard\gpio_keys.c */
-
-
#include <linux/module.h>
-
#include <linux/version.h>
-
-
#include <linux/init.h>
-
#include <linux/fs.h>
-
#include <linux/interrupt.h>
-
#include <linux/irq.h>
-
#include <linux/sched.h>
-
#include <linux/pm.h>
-
#include <linux/sysctl.h>
-
#include <linux/proc_fs.h>
-
#include <linux/delay.h>
-
#include <linux/platform_device.h>
-
#include <linux/input.h>
-
#include <linux/irq.h>
-
-
#include <asm/gpio.h>
-
#include <asm/io.h>
- #include <asm/arch/regs-gpio.h>
B.入口函数,出口函数
module_init(buttons_init);
点击(此处)折叠或打开
-
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_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_exit(buttons_exit);
以下是全部的代码:
点击(此处)折叠或打开
-
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);
- }
中断处理函数:
点击(此处)折叠或打开
-
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);
- }
以下是全部的代码:
点击(此处)折叠或打开
-
/* 参考drivers\input\keyboard\gpio_keys.c */
-
-
#include <linux/module.h>
-
#include <linux/version.h>
-
-
#include <linux/init.h>
-
#include <linux/fs.h>
-
#include <linux/interrupt.h>
-
#include <linux/irq.h>
-
#include <linux/sched.h>
-
#include <linux/pm.h>
-
#include <linux/sysctl.h>
-
#include <linux/proc_fs.h>
-
#include <linux/delay.h>
-
#include <linux/platform_device.h>
-
#include <linux/input.h>
-
#include <linux/irq.h>
-
-
#include <asm/gpio.h>
-
#include <asm/io.h>
-
#include <asm/arch/regs-gpio.h>
-
-
struct pin_desc{
-
int irq;
-
char *name;
-
unsigned int pin;
-
unsigned int key_val;
-
};
-
-
struct pin_desc pins_desc[4] = {
-
{IRQ_EINT1, "K1", S3C2410_GPF1, KEY_L},
-
{IRQ_EINT4, "K2", S3C2410_GPF4, KEY_S},
-
{IRQ_EINT2, "K3", S3C2410_GPF2, KEY_ENTER},
-
{IRQ_EINT0, "K4", S3C2410_GPF0, KEY_LEFTSHIFT},
-
};
-
-
static struct input_dev *buttons_dev;
-
static struct pin_desc *irq_pd;
-
static struct timer_list buttons_timer;
-
-
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);
-
}
-
}
-
-
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_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;
-
}
-
-
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_init(buttons_init);
-
-
module_exit(buttons_exit);
-
- MODULE_LICENSE("GPL");
然后编译驱动程序:
放到网络文件系统中,然后再终端输入查看信息
cat /dev/tty1(按回车)
cat /etc/inittab
转自:http://blog.chinaunix.net/uid-26859745-id-3190337.html