Chinaunix首页 | 论坛 | 博客
  • 博客访问: 414971
  • 博文数量: 60
  • 博客积分: 442
  • 博客等级: 下士
  • 技术积分: 910
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-27 14:53
文章分类

全部博文(60)

文章存档

2021年(1)

2018年(1)

2017年(14)

2016年(31)

2015年(1)

2013年(3)

2012年(9)

我的朋友

分类: LINUX

2012-08-29 16:49:14

先看一篇网上摘的总结,写的很清晰:
http://blog.csdn.net/zhangxizhicn/article/details/6642062
 
                                                Linux  Input 设备驱动
 
1认识和使用 input 事件:
 
1.1Linux input 驱动分类
 
Input驱动程序是Linux输入设备的驱动程序,分成游戏杆(joystick)、鼠标(mouse和mice)和事件设备(Event queue)3种驱动程序。其中事件驱动程序是目前通用的驱动程序,可支持键盘、鼠标、触摸屏等多种输入设备。
 
Input驱动程序的主设备号是13,驱动程序的设备号分配如下所示。
 
  joystick游戏杆:0~31
 
  mouse鼠标:32~62   
 
  mice鼠标:63
 
 事件(Event)设备:64~95
 
  
 
1.2查看 input 驱动
 
  
 
cat /proc/bus/input/devices
 
  
 
例如显示以下信息:
 
I: Bus=0017 Vendor=0001 Product=0001 Version=0100
 
N: Name="Macintosh mouse button emulation"
 
P: Phys=
 
S: Sysfs=/devices/virtual/input/input0
 
U: Uniq=
 
H: Handlers=mouse0 event0
 
B: EV=7
 
B: KEY=70000 0 0 0 0 0 0 0 0
 
B: REL=3:
 
  
 
则说明 /dev/input/mouse0  和 /dev/input/event0 都对应着同一个鼠标设备。
 
  
 
  
 
2.理解Input 子系统的模型:
 
2.1  input 子系统概述
 
Linux input  子系统将一个输入设备的输入过程分成了设备驱动(input device driver)和事件驱动(input event driver)两个层。前者负责从底层硬件采集数据;后者负责与用户程序接口,将采集到的数据分发给不同的用户接口。通过这样的设计,将千差万别的设备统 一到了为数不多的几种驱动接口上。同一种事件驱动可以用来处理多个同类设备;同一个设备也可以和多种事件驱动相衔接。
 
设 备驱动并不创建文件节点,它只负责将采集到数据通过input.c中的函数input_event() 向上一层汇报;而各个事件驱动则分别将他们各自感兴趣的事件信息提取出来,通过文件节点,提供给用户。在这个过程中,input 子系统的核心负责着这两层的交互工作。并管理和维护着记录了他们各自信息的链表。具体过程如下图所示:
 
  
 
更进一步理解Input 子系统,需要理解以下4个对象:
 
Input_device , handler,  handle , client
 
Input_device:  代表着具体的输入设备,它直接从硬件中读取数据,并以事件的形式转发
 
Hanler:  代表接收某一类事件的上层接口,对应于一类事件设备文件
 
Handle : 用于将input_device 和  handler 连接起来的胶水,对应于某1个具体的设备文件。
 
Client:  对应于用户程序对文件的访问接口,每open一次事件驱动,就创建一个client.
 
  
 
Input 子系统维护者有两条重要的链表:
 
static LIST_HEAD(input_dev_list);
 
static LIST_HEAD(input_handler_list);
 
一 条记录着系统中所有的输入设备,另一条记录着所有的事件驱动。输入设备是通过input_dev 表示,而事件驱动是通过handler 表示。每当一个新的设备(如键盘,鼠标等)或者一个新的事件驱动被系统加载(调用input_register_device()或 input_register_driver()),都会扫描整个连标,并调用函数input_match_device(struct input_handler *handler, struct input_dev *dev)  尝试配对工作。Input_handler-->id_table   记录了需要匹配的特征。
 
比如eventX事件驱动它的匹配特征存在于
 
static const struct input_device_id evdev_ids[] = {
 
            { .driver_info = 1 },       /* Matches all devices */
 
            { },                                /* Terminating zero entry */
 
};
 
就 像代码中注释的一样,任何输入设备都可以和他匹配成功,也就是说任何输入设备都可以映射到/input/dev/eventX 下。 一旦匹配成功就会调用函数handler->connect(handler, dev, id);将具体的设备(input_dev)和事件驱动(handler)邦定到一起,形成一个handle,将handle 注册到它相关联的input_dev 设备的链表h_list中。设备编号在input_init() 的时候通过调用创建。
 
  
 
2.2  文件节点的建立
 
Input 子系统在初始化时,首先调用register_chrdev(INPUT_MAJOR, "input", &input_fops); 为自己注册了256个字符设备节点。(这也代表着系统中最多可以存在256个输入设备)这256个设备会被分为8类,分别对应于数组input_table[8]中存放的8个 handler.
 
static struct input_handler *input_table[8];  // kernel/input/input.c
 
其中数第1个句柄管理此设备号0-31,第2个句柄管理设备号32-63。。。
 
每一个句柄,都可以用来实现一类事件驱动(但每类事件驱动最多只能管理32个设备)。
 
例 如:/dev/input/eventX所表示的设备  和dev /input/mou*** 所表示的设备 就分别使用了最常见的两种事件驱动。以/dev/input/eventX  为例,他们都使用同一个event事件驱动,从对象的角度看,拥有同一个handler。而这个handler 所管理的设备,拥有次设备号64-95,每一个次设备号又可以对应到一个handle.
 
2.3 事件驱动注册
 
事件驱动的方法被抽象到特定的 handler结构体中,驱动在初始化时并通过调用input_register_handler()把handler 注册的系统中。
 
input_register_handler()具体完成3件事情:
 
1. 把handler 存放到input_table[]中
 
2. 把 handler 存放到input 核心的链表中
 
3. 扫描input核心链表上的device ,并试着匹配
 
  
 
2.4 设备驱动
 
具体设备是用input_dev 表示的,它通过函数input_register_device()注册。
 
input_register_device()主要做了两件事情:
 
1.  把 input_dev 存放到input 核心的链表中
 
2. 扫描input核心链表上的handler ,并试着匹配
 
2.5 匹配
 
Dev 和handler 是通过input_match_device(struct input_handler *handler, struct input_dev *dev)  尝试配的。每当查找到可以相互匹配的dev 和 handler 便回调handler->connect() ,生成一个handle结构体,并 通过handle  把某一个具体的dev 和另一个具体的handler被邦定到一起。最后handle 通过函数input _register_handle()注册到dev 的链表中。以后dev 上有数据要发送时,便可以在dev 中搜索这个链表,找到handle,再调用handle->handler->event() 把数据传到了event 驱动中。
 
2.6数据传送
 
当有数据上传时,数据通过input_pass_event () 函数向handle 发送, 这个函数的核心内容如下:
 
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
 
            handler = handle->handler;
 
            handler->event(handle, type, code, value);
 
  }
 
根据input_dev 设备的h_list 链表查找与他相关的handle,然后调用event 处理方法,将数据传递给上层 的client。
 
2.7 clinet
 
每一个事件驱动(handler),可能对应多个文件节点(handle),而每一个文件节点又可以有多个client, 底层的数据直接复制到上层的client中。对文件句柄的操作则实际上是对这些个client 中的数据进行操作。
 
以驱动evdev.c 为例,根据次设备号取值范围64-95。可以分别生成/input/event0 ,input/event1,一直到/input/event31总共32个设备文件。
 
每 一个设备文件又可以同时对应多个client,当有多个应用程序同时调用设备文件时,他们会从不同的client 中取数据。每当应用程序调用open 时,便会调用evdev_open()创建一个client,并被与设备节点邦定  file->private_data = client;
 
设备被应用层当作文件操作时,通过struct evdev_client *client = file->private_data; 再将client 找到。直接读/写 client 中的数据。而当底层有数据要传过来时则会通过handler->event() 把数据复制到client 中,如evdev_event()通过client 链便,对每一个client 进行evdev_pass_event()调用。而在evdev_pass_event()中实现数据向client 节点的复制。
 
 
 
 
下面是调用input_register_device后的一系列执行流程:
------------------------------------------------------------------------------------------
int input_register_device(struct input_dev *dev)
{
    ...
    list_add_tail(&dev->node, &input_dev_list);
    list_for_each_entry(handler, &input_handler_list, node)
            input_attach_handler(dev, handler);
    ...
}
 
当我们调用input_register_device时,会把input设备加入到input_dev_list链表,然后循环
input_handler_list链表,找到匹配的handler。
 
我们看下如何才算匹配的handler:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    const struct input_device_id *id;
    int error;
 
    id = input_match_device(handler, dev);
    if (!id)
        return -ENODEV;
 
    error = handler->connect(handler, dev, id);
    if (error && error != -ENODEV)
        pr_err("failed to attach handler %s to device %s, error: %dn",
               handler->name, kobject_name(&dev->dev.kobj), error);
 
    return error;
}
 
input_match_device可参见源码drivers/input/input.c。
struct input_handler结构体中有个id_table成员,主要匹配规则就是在此id_table中找出与传入的input_dev相同的
BUS类型、供应商信息、版本号、事件类型等。找到之后会调用handler->connect(handler, dev, id)。
 
如果你的input设备注册了自己的input_handler(drivers/input/mousedev.c),那么会调用你注册的connect;如果你
未注册自己的input_handler,那么会调用input子系统默认的connect函数,即evdev_connect(drivers/input/evdev.c此文件就是event驱动)。
evdev_connect主要工作就是初始化struct evdev *evdev,调用input_register_handle(&evdev->handle)。
 
int input_register_handle(struct input_handle *handle)
{
    ...
    list_add_rcu(&handle->d_node, &dev->h_list);
    ...
    list_add_tail_rcu(&handle->h_node, &handler->h_list);
    ...
}
看一个注释(摘自对struct input_handle结构体成员变量d_node、h_node的说明):
 * @d_node: used to put the handle on device's list of attached handles
 * @h_node: used to put the handle on handler's list of handles from which
 *      it gets events
从注释中我们知道上面的代码是把handle分别加入到dev->h_list、handler->h_list链表中。
------------------------------------------------------------------------------------------
 
我们再看调用input_sync后的执行流程:
------------------------------------------------------------------------------------------
input_sync
    input_event
        input_handle_event
            input_pass_event
                handler->event(handle, type, code, value)
前面铺垫工作全是为后边处理事件做准备。handler的event在drivers/input/evdev.c文件中,即evdev_event。
 
static void evdev_event(...)
{
    ...
        client = rcu_dereference(evdev->grab);
        if (client)
                evdev_pass_event(client, &event);
        else
                list_for_each_entry_rcu(client, &evdev->client_list, node)
                        evdev_pass_event(client, &event);
 
    ...
}
 
static void evdev_pass_event(struct evdev_client *client,
                             struct input_event *event)
{
    ...
    client->buffer[client->head++] = *event;
    ...
}
从上面的代码可以看出对event的处理,即把报上来的input事件保存到了client的buffer中。
struct input_event {         
        struct timeval time; 
        __u16 type;          
        __u16 code;          
        __s32 value;
};
从这个结构体可以看出input子系统把事件值封装成了struct input_event结构体。
那么这个client是什么?
------------------------------------------------------------------------------------------
 
接下来分析完input_open_file就知道了:
------------------------------------------------------------------------------------------
上层调用open打开input设备文件时,会调用到input_open_file。
static int input_open_file(struct inode *inode, struct file *file)
{
    ...
        /* 由上面文摘的分析,input设备共有8类,每一类有32个设备,所以同类设备的次设备号的低5位是相同的 */
        handler = input_table[iminor(inode) >> 5];
        if (handler)
                new_fops = fops_get(handler->fops);
    ...
 
        err = new_fops->open(inode, file);
    ...
}
通过上面的代码我们看到,input_open_file调用了handler->fops的open函数。
也就是:
static struct input_handler evdev_handler = {
    ...
        .fops           = &evdev_fops,
    ...
};
 
static const struct file_operations evdev_fops = {
    ...
        .open           = evdev_open,
    ...
};
最终调用到了evdev_open。
static int evdev_open(struct inode *inode, struct file *file)
{
    ...
        evdev = evdev_table[i];
    ...
        client = kzalloc(sizeof(struct evdev_client) +
                                bufsize * sizeof(struct input_event),
                         GFP_KERNEL);
    ...
        evdev_attach_client(evdev, client);
 
        error = evdev_open_device(evdev);
    ...
        file->private_data = client;    //注意这里把client存到了这里,在后面read时会用到
    ...
}
这里的client就是上面处理event事件即函数evdev_event中用到的client。
 
static void evdev_attach_client(struct evdev *evdev,
                                struct evdev_client *client)
{
    ...
        list_add_tail_rcu(&client->node, &evdev->client_list);
    ...
}
evdev_attach_client把client加入到evdev的client_list链表中。
 
evdev_open_device-->input_open_device。
如果你的input设备定义了open接口,那么会调用它,如果未定义,input_open_device也并未做实质性的工作,只把计数器加1。
现在不知道打开一个设备最终要做的实质性工作是什么,也许什么都不用做?这和文件系统相关,以后再研究。open函数分析到此为止。
------------------------------------------------------------------------------------------
 
下面我们看下用户进程读取event是怎么执行的:
------------------------------------------------------------------------------------------
我们直接从evdev_read分析:
static ssize_t evdev_read(struct file *file, char __user *buffer,
                          size_t count, loff_t *ppos)
{
        /* 用上面存在file结构体的client,每个不同的input设备打开时都会有自己的client,
         * 然后保存到file结构体,以作不同设备间的区分。
         */
        struct evdev_client *client = file->private_data;
    ...
        while (retval + input_event_size() <= count &&
               evdev_fetch_next_event(client, &event)) {
         
                if (input_event_to_user(buffer + retval, &event))
                        return -EFAULT;
                 
                retval += input_event_size();
        }
    ...
}
 
static int evdev_fetch_next_event(struct evdev_client *client,
                                  struct input_event *event)
{
    ...
        *event = client->buffer[client->tail++];
    ...
}
 
int input_event_to_user(char __user *buffer,
                        const struct input_event *event)
{              
        if (copy_to_user(buffer, event, sizeof(struct input_event)))
                return -EFAULT;
                
        return 0;
}
input_event_to_user 把数据拷贝到了用户空间。至此,evdev_read分析完。
------------------------------------------------------------------------------------------
阅读(1454) | 评论(0) | 转发(1) |
0

上一篇:常用小算法

下一篇:电源管理

给主人留下些什么吧!~~