专题5-触摸屏驱动程序设计-输入子系统模型解析

章节一.输入子系统模型解析
一.为何需要输入子系统
1.回顾之前按键驱动的代码,总结其大致流程如下
(1)注册字符设备驱动
(2)实现系统调用相关的的驱动函数
(3)实现硬件相关的函数,以便获取硬件数据并传送到用户空间
2.输入设备
诸如键盘,鼠标,按键之类的给PC输入数据的外设,都称之为输入设备。
3.在Linux系统中,对着写输入设备的操作,势必都会包含(1)注册驱动(2)实现系统调用。
不同的是具体硬件的操作。所以为了简化编程以及管理系统的驱动架构,将共性提取出来,单独作为一个系统模块(输入子系统)。也就是说输入子系统是包含了注册驱动以及实现系统调用等的共性的驱动框架的集合体。
在此基础上我们只关心不同硬件的具体的操作,以便实现不同的中断处理函数来获取用户空间所需要的数据。
二.输入子系统模型
1.总体框架

图片:1.png[删除]

2.主要的三大部分:
--》Input Device Drivers(设备驱动层):需要我们程序员自己实现。将底层的事件输入转换为统一事件形式,向核心汇报。
--》Input Core(核心层):内核已经实现。为设备驱动层提供输入设备注册与操作接口。通知事件处理层对事件进行处理。
--》Input Event Drivers(事件驱动层):内核已经实现。主要作用是和用户空间交互,提供read,open等设备方法,实现共性部分的操作,自动创建设备文等。
3.大致工作流程
(1)硬件发生某个事件,比如中断;
(2)Input Device Drivers检测到事件发生,并将它上报到Input Core
(3)Input Core经过相关处理流程以后,将事件的处理权交给Input Event Drivers
(4)Input Event Drivers首先要实现一个字符设备驱动,从而就会涉及到相关系统调用的接口实现,比如说read。
(5)read系统调用对应的接口实现函数里面会组织好数据传送到用户空间的程序。
三.案例分析
1./*分配输入型设备结构*/    button_dev = input_allocate_device();
前提是要先声明这个结构体变量
struct input_dev *button_dev;
以及包含这个头文件
#include <linux/input.h>
2./*申明所支持的事件类型*/
    set_bit(EV_KEY,button_dev->evbit);
一般Linux支持如下事件
事件类型:
EV_RST Reset           
EV_KEY 按键
EV_REL 相对坐标
EV_ABS 绝对坐标
EV_MSC 其它
EV_LED LED
EV_SND 声音
EV_REP Repeat
EV_FF 力反馈
3./*申明可能上报的键编号*/
    set_bit(KEY_3,button_dev->keybit);
    set_bit(KEY_4,button_dev->keybit);
当事件类型为EV_KEY时,还需指明按键类型:
BTN_ LEFT:鼠标左键 BTN_0:数字0键
BTN_RIGHT:鼠标右键
BTN_1:数字1键
4./*注册输入型设备*/    input_register_device(button_dev);
5.上报事件
input_report_key(button_dev,KEY_4,1);
具体代码部分如下:
key_val = readw(gpio_data)&0x1;
    if (key_val == 0)
    {
       key_num = 4;
       input_report_key(button_dev,KEY_4,1);
    }
6.给出上报完成消息
input_sync(button_dev);
不再需要唤醒操作了。
7.具体思维导图

图片:2.png[删除]

8.注意注销函数也要变
input_unregister_device(button_dev);    
9.应用程序打开的文件名是/dev/event_1
10.要先声明一个结构体
struct input_event ev_key;
用来表示事件。里面有类型,编号,事件值。
ev_key.type,ev_key.code,ev_key.value

 

章节二.输入子系统原理分析
一.子系统核心框架
1.总的框图

图片:3.png[删除]

2.两个“猪脚”
(1)input_dev(输入设备):实际和硬件打交道的驱动。具有“异性”。
(2)handler:主要完成和用户程序的交互以及收集来自硬件的数据。具有“共性”。可以称之为“事件驱动”。也是一个结构体。
二.输入设备注册
1.用输入设备的ID去匹配handler的ID,一旦匹配上就表明这个handler可以处理我这个设备的事件
(1)打开source insght,找到input.c文件里面的
int input_register_device(struct input_dev *dev)函数。开始追踪函数调用关系。
(2)遍历input_handler_list链表,从而找到匹配input——dev的handler。因为这个链表包含了所有设备的handler。
list_for_each_entry(handler, &input_handler_list, node)
     input_attach_handler(dev, handler);
(3)进入input_attach_handler函数。发现利用这句话
id = input_match_device(handler->id_table, dev);进行设备和handler的匹配。
(4)找到struct input_dev订阅跌结构体,发现里面有struct input_id id;成员。

图片:4.png[删除]

再到struct input_handler结构体,发现有const struct input_device_id *id_table;成员。

图片:5.png[删除]

id_table中有这个handler支持的设备的ID号,一旦设备的ID号struct input_id id在这个列表里,就表示匹配成功。
2.继续在input_attach_handler函数里调用匹配到的handler的connect函数通过id进行设备和handler的实际关联。
error = handler->connect(handler, dev, id);
(1)connect是handler的一个函数指针成员。
(2)以evdev_handler为例:
(在SI搜索evdev_handler)

图片:6.png[删除]

进入
我们可以发现

图片:7.png[删除]

connect函数就是evdev_connect
(3)进入evdev_connect函数,他在这里面自动创建了设备文件(节点),这就是为什么我们说事件层会自动创建设备文件的原因。
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
3.按说在创建设备文件之前,是要注册设备驱动的。针对输入子系统的话,是要先注册字符设备驱动的。但是不是在evdev_connect函数完成这项任务。而是在input.c的input_init函数完成的:
里面的
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);就是向内核注册了字符设备驱动。
也就是说多那个Linux系统上电启动的时候,会先调用输入子系统的初始化,与此同时就注册了字符设备驱动。
4.之前的按键输入没有指明ID号,也能正常工作的原因是,在没有显示指定设备ID的时候,默认输入设备(input_dev)的id是随机的,但是evdev_handler可以去和他匹配。原因是在evdev_handler上面一句注释:
static const struct input_device_id evdev_ids[] = {
    { .driver_info = 1 },    /* Matches all devices */
    { },            /* Terminating zero entry */
};
也就是说在初始化的时候evdev_handler将成员id_table初始化为evdev_ids,而这个evdev_ids是可以喝任意的设备id进行匹配的。
三.事件上报
1.内和空间数据的存储
(1)进入input.h,找到input_report_key函数,随即进入input_event函数;
(2)在input_event函数里面先对现在设备发生的事件进行判断,也就是说我们之前要先声明这个类型的事件,否则会被过滤掉。
一般使用set_bit函数声明。
(3)进入input_handle_event函数————》在进入input_pass_event函数。发现他是调用了之前匹配好的handler的event函数:
handle->handler->event(handle, type, code, value);
(4)还是以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,
};
发现确实有event成员,并且赋值为evdev_event。
(5)进入evdev_event函数,发现
if (client)
          evdev_pass_event(client, &event);
(6)进入evdev_pass_event函数发现
client->buffer[client->head++] = *event;
     client->head &= EVDEV_BUFFER_SIZE - 1;
表明将发生的事件存储在了buffer里面,等待用户空间的应用程序来读取。
至此,内核空间工作大致完毕。
*(7)唤醒相关中断
wake_up_interruptible(&evdev->wait);
2.用户空间对数据的访问
(1)app使用系统调用open打开设备文件
(2)内核会调用设备文件匹配的字符设备驱动对应的open函数。
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
注字符设备驱动对应的open函数:
static const struct file_operations input_fops = {
    .owner = THIS_MODULE,
    .open = input_open_file,
};
就是input_open_file。
(3)进入input_open_file函数:
发现有
if (!handler || !(new_fops = fops_get(handler->fops))) {
          err = -ENODEV;
          goto out;
     }
以及
     old_fops = file->f_op;
     file->f_op = new_fops;
     err = new_fops->open(inode, file);
这些操作都是将新的fops赋值给原来设备文件的fops。而新的fops实际上就是handler的fops(由new_fops = fops_get(handler->fops)这句话可以看出)。所以总的来说就是在旧的open函数里将字符设备驱动的fops偷龙转凤了,换成了设备匹配的handler的fops。于是当app使用read等系统调用的时候就可以调用到handler的read函数了。
(4)从evdev_handler结构体里面可以看到,他的fops成员是evdev_fops结构体。进入evdev_fops可以看到,read系统调用对应的函数是evdev_read。
(5)进入evdev_read,发现调用了evdev_fetch_next_event函数。
(6)在evdev_fetch_next_event函数里,通过
     have_event = client->head != client->tail;
     if (have_event) {
          *event = client->buffer[client->tail++];
          client->tail &= EVDEV_BUFFER_SIZE - 1;
     }
将buffer里的数据取出来赋值给event。
(7)最后通过evdev_read函数里的
if (input_event_to_user(buffer + retval, &event))
将event传递到了用户空间,至此完成了用户空间对内核空间的数据的读取。
3.总的来说,就是硬件发生事件,然后输入设备驱动将事件寸放到了与之匹配的handler的buffer,然后用户空间的app通过open系统调用打开文件的同时,将旧的字符设备驱动对应的fops替换成了输入设备匹配的handler的fops,下一步app使用read系统调用的时候就直接调用了handler的fops的read成员,去读取相应handler的buffer存放的事件,并反馈到用户空间。

 

图片:14.png[删除]

图片:15.png[删除]

图片:16.png[删除]

posted @ 2015-04-15 16:25  生活需要深度  阅读(282)  评论(0)    收藏  举报