您的位置:首页 > 其它

input输入子系统框架分析

2015-04-21 10:09 423 查看
input子系统的搭建要点

核心层为事件驱动层和设备驱动层的注册提供API的实现、核心层为设备驱动层上报事件提供API的实现 、事件驱动层为应用层提供API的实现 。
(1)核心层:提供事件驱动层和设备驱动层所需的函数接口(为input dev和input handler建立联)

drivers/input/input.c:

##主要接口函数一览:##
为事件驱动层提供的:

注册API:

int input_register_handler(struct input_handler *handler);
void input_unregister_handler(struct input_handler *handler);
为设备驱动层提供的:

注册API:

struct
input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
设备驱动要用到的上报输入事件函数API:

void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);//下面是这个函数的特殊定义
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);
##上面这些接口函数的实现过程几乎就是input子系统的框架搭建过程:##
先来分析input_register_handler()接口函数实现注册input_handler:
input_register_handler(struct input_handler *handler)
{
......
// ①放入数组:根据次设备号就可以查找到,方便应用层快速调用
input_table[handler->minor >> 5] = handler;//把传进来的handler结构体根据他自身的minor次设备号放到input_table[]数组对应的位置

// ②链入链表:方便遍历
list_add_tail(&handler->node, &input_handler_list);

// ③在input_dev_list链表中主动找匹配的input_dev:
list_for_each_entry(dev, &input_dev_list, node) //for循环的宏定义
input_attach_handler(dev, handler);
id = input_match_device(handler->id_table, dev); // 根据input_handler的id_table判断能否支持这个input_dev
error = handler->connect(handler, dev, id); //match成功的话调用
......
}
完全可以想象只要有调用input_register_handler()就会根据minor来填充input_table[]对应的项,这点必须值得肯定!

在内核源码里搜一搜input_register_handler在哪里被调用:仅仅就下面这几个文件,only!

Evdev.c (drivers\input): return input_register_handler(&evdev_handler);

Joydev.c (drivers\input): return input_register_handler(&joydev_handler);

Keyboard.c (drivers\char): error = input_register_handler(&kbd_handler);

Mousedev.c (drivers\input): error = input_register_handler(&mousedev_handler);

这个函数的功绩:把input_handler结构体填入input_table[],同形成链表-->为下一步寻找他的天生一对“dev”做准备

现在input子系统就有了存放input_handler结构体数组,并且具备主动去寻找handler对应的input device的能力。
分析input_register_device()接口函数实现注册输入设备:
input_register_device(struct input_dev *dev)
{
......
// 链入链表
list_add_tail(&dev->node, &input_dev_list);

// 在input_handler_list链表中主动找匹配的input_handler:
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
id = input_match_device(handler->id_table, dev);// 根据input_handler的id_table判断能否支持这个input_dev
error = handler->connect(handler, dev, id); //如果能支持,则调用input_handler的connect函数建立"连接"
......
}
也在内核源码里边来搜一搜input_register_device函数在哪些地方被调用:

结果就不列出来了,实在太多文件中都有调用。从这个事实也可以察觉到:input_dev远远多于input_handler。因为很多输入设备的事件处理过程是一样的,这样input_handler的代码复用率就大大提高了!

这函数的功绩:让输入设备形成链表,新增加input_dev时能够主动去寻找匹配的input_handler。
③handler和dev是如何建立联系的?

容易发现input_register_handler()和input_register_device()如果match成功最后都是调用handler->connect函数,因此非常有必要来分析这个函数的实现过程,由于上面这两个函数都是通过connect函数指针来调用的,要继续分析下去就必须要分析这个指针具体指向的函数实体,毋庸置疑函数指针是搭建框架的利器!

connect函数指针是input_handler结构体的成员,可以猜测,每一类handler就会有他独有的connect函数实体!

既然这样就到evdev.c (drivers\input)文件中找找看:果不其然,找到了!
evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{
......
//1. 分配一个input_handle结构体
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
//2.设置这个结构体
evdev->minor = minor;
evdev->handle.dev = input_get_device(dev); // 指向input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; //指向input_handler
evdev->handle.private = evdev;
//3.把这个结构体注册到input_handler和input_dev结构体
error = input_register_handle(&evdev->handle);//下面进一步分析这个函数就知道究竟是怎样让handler和dev建立联系的!
......
}
input_register_handle函数实现:
input_register_handle();
{
......
list_add_tail_rcu(&handle->d_node, &dev->h_list);
list_add_tail(&handle->h_node, &handler->h_list);
......
}
小结一下这一点的内容:

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;

这样一来connect之后,只要知道input_dev可以用他的h_list指针访问到对应的input_handle结构体的handler成员,从而也就找到了他对应的事件驱动handler,反过来也一样。
④设备驱动层上报事件input_event()接口函数分析:
input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{
input_handle_event(dev, type, code, value);
switch (type) {
case EV_SYN:
...
case EV_KEY:
...
case EV_SW:
...
case EV_ABS:
...
case EV_REL:
...
}
...
//分析到这里要小心了,一开始以为是这个if条件成立,因为第一感觉就是input_device结构体的这个成员会在哪里被初始化,
//然后就去直奔input_register_device函数里找啊找,半毛钱都没找到,原来本来就没有进行定义,所以dev->event为空,条件不成立!
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);

if (disposition & INPUT_PASS_TO_HANDLERS)//擦!从sourceInsi也可以发现INPUT_PASS_TO_HANDLERS这个宏是有定义的
input_pass_event(dev, type, code, value);//这个就是设备驱动层上报事件的关键工作!
}
input_pass_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
...
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle,type, code, value);//最终调用的是匹配的input_handler结构体的event成员
//这就好办了,handler是事件驱动层,我们就拿event.c文件里的例子来继续分析
}
void evdev_event(struct input_handle *handle,unsigned int type, unsigned int code, int value)
{
......
wake_up_interruptible(&evdev->wait);//唤醒工作
}
小结:

核心层为设备驱动层提供上报事件接口input_event()函数,实际上就是实现了让设备驱动能够重定向到匹配自己的事件驱动层的事件处理函数xxx_event(),到这里核心层的工作基本上就完成。了。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
(2)事件驱动层:为应用层提供API

这些API就是file_operation结构体成员的实现,这一层得以具体的某种handler来进行分析,因为这一层是一类事件处理方法,方法是具体的。

以evdev.c文件内容为例来进行分析:

①evdev_init()
input_register_handler(&evdev_handler)//通过核心层,可以和input_dev建立联系

②input_handler结构体的fops结构成员就是事件驱动层为应用层提供的API
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,
};
static const struct file_operations evdev_fops = {
.owner		= THIS_MODULE,
.read		= evdev_read, //以这个接口函数的实现进行分析
......
};
evdev_read()
{
.......
// 无数据并且是非阻塞方式打开,则立刻返回
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
// 否则进入休眠,上面刚刚提到:这里的休眠就是设备驱动层通过调用input_event()函数最终重定向到事件驱动层的evdev_event()函数来唤醒的
retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
.......
}
小结:

对于事件驱动层,Linux内核已经整理了几套常用的handler,分别定义在上面我们有说到的这几个文件里:

Evdev.c (drivers\input)、Joydev.c (drivers\input)、Keyboard.c (drivers\char)、Mousedev.c (drivers\input),也就是说,对于驱动人员来说,添加输入设备的驱动,基本所有的工作都落在设备驱动层里!

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
(3)设备驱动层:负责和硬件输入设备通信,然后把情况上报给核心层

主要工作:

1. 分配一个input_dev结构体

2. 设置:能产生哪类事件、能产生这类操作里的具体哪些事件

比如说,我申请的一个GPIO引脚中断能够产生按键事件,那究竟是什么按键事件,哦,比如可以是我按下这个按键时是26个字母中的具体哪一个

3. 注册:把这个input_dev结构体链入输入设备链表,并遍历input_handler_list看看是否有匹配的驱动

4. 硬件相关的代码,比如在中断服务程序里上报事件

软件设计流程如下所示:

分配一个输入设备 --> 设置输入设备 --> 注册输入设备 --> 上报输入事件 --> 注销输入设备 --> 释放输入设备

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

总体概括:
事件驱动层和核心层都是通用的,我们需要实现的是设备驱动层。输入设备驱动会把硬件产生的事件信息用统一的格式(struct input_event)上报给核心层,然后核心层进行分类后,再上报给相应的事件处理驱动程序,最后通过事件层传递到用户空间,应用程序可以通过设备节点来获取事件信息。比如底层报上来的是一个按键事件,核心层会报给evdev来处理;如果报的是一个鼠标事件,核心层会报给mousedev来处理。最后附上一张经典的输入子系统的框架图。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: