linux input输入子系统应用分析

输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、触摸等动作发送时产生一个中断(或驱动通过timer定时查询),然后CPU通过SPI、I2 C或外部存储器总线读取键值、坐标等数据,放入1个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据。

显然,在这些工作中,只是中断、读值是设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的。基于此,内核设计了输入子系统,由核心层处理公共的工作。硬件驱动层处理中断、读值相关(中断中上报读值)。

input子系统分层设计,共三层:硬件驱动层、子系统核心层、事件处理层。

驱动层由驱动程序员完成,主要处理中断(上报事件-读值)。

子系统核心层是链接其他两个层之间的纽带和桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。

事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。

设备驱动并不创建文件节点,它只负责将采集到数据通过input.c中的函数input_event() 向上一层汇报而各个事件驱动则分别将他们各自感兴趣的事件信息提取出来,通过文件节点,提供给用户。在这个过程中,input 子系统的核心负责这两层的交互工作,并管理和维护着记录了他们各自信息的链表。

驱动程序中上报的事件,input子系统已规定好,在linux/input.h中。支持的事件类型(Event types)有EV_CNT个(EV_SYN同步事件,EV_KEY按键事件,EV_REL相对坐标事件,EV_ABS绝对坐标事件,EV_MSC零散事件,EV_SW开关事件,EV_LED LED事件,EV_SND,EV_REP重复事件,EV_FF,EV_PWR,EV_FF_STATUS,EV_MAX,EV_CNE)(后几项用于服务事件类型,不代表实际事件),在代码中用“type”表示。每种事件类型都有多种属性(或选项,或编码),代码中用“code”表示,每种属性所对应的值就是要上报的数据(上面所说的“读值”)。“事件-属性-属性值”(type-code-value)唯一表征一个上报事件,“type-code”输入子系统有完整的规定,代码中通过“位掩码”表示支持该事件,该属性。举个简单例子,触摸设备支持绝对坐标事件EV_ABS,EV_ABS有ABS_CNT个code,其中ABS_X代表X坐标,ABS_Y代表Y坐标。
input_dev结构体自带事件相关数组,如下:
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
所有输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event。
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
----------------------------------------------------------------------------------------------------
设备支持的事件在驱动模块加载函数中设置,一般设置方法为:
button_dev->evbit[0] = BIT_MASK(EV_KEY); //直接位赋值(兼容性差)
static inline void __set_bit(int nr, volatile unsigned long *addr); // 函数设置,非原子
static inline void set_bit(int nr, volatile unsigned long *addr); //函数设置,原子,asm/bitops/atomic.h
中断上报事件用input_event(),此外还有3个变体函数分别用于报告EV_KEY、EV_REL、EV_ABS事件。
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_sync()用于事件同步,告知事件接收者驱动已发出一个完整的报告。
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
在宋宝华的《linux设备驱动开发详解》中,介绍了通过input子系统增加输入设备的驱动工作。
>>在模块加载函数中告知input子系统设备可以报告的事情。(set_bit())
>>在模块加载函数中注册输入设备。(int input_register_device(struct input_dev *dev);)
>>在键被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时通过input_report_xxx()报告发生的事情及对应的键值/坐标等状态。
>>在模块卸载函数中注销输入设备。(void input_unregister_device(struct input_dev *dev);)
---------------------------------------------------------------------------------------------------
事件处理层与用户程序和输入子系统核心打交道,是两层的桥梁。一般内核有好几个事件处理器,像evdev,mousedev,joydev。evdev事件处理器可以处理所有的事情。
evdev用户端结构:

1.    struct evdev_client {  
2.        struct input_event buffer[EVDEV_BUFFER_SIZE];    
3.            //这个是一个input_event数据结构的数组,input_event代表一个事件,基本成员:类型(type),编码(code),值(value)   
4.        int head;              //针对buffer数组的索引   
5.        int tail;              //针对buffer数组的索引,当head与tail相等的时候,说明没有事件   
6.        spinlock_t buffer_lock; /* protects access to buffer, head and tail */  
7.        struct fasync_struct *fasync;  //异步通知函数   
8.        struct evdev *evdev;           //evdev设备   
9.        struct list_head node;         // evdev_client 链表项   
10.    }; 


这个结构在进程打开event0设备的时候调用evdev的open方法,在open中创建这个结构,并初始化。在关闭设备文件的时候释放这个结构。在read时复制到用户空间。
用户空间的应用程序如何访问设备呢,设备文件是?

1.    struct evdev {  
2.        int exist;  
3.        int open;           //打开标志   
4.        int minor;          //次设备号   
5.        struct input_handle handle;  //关联的input_handle   
6.        wait_queue_head_t wait;      //等待队列,当进程读取设备,而没有事件产生的时候,进程就会睡在其上面   
7.        struct evdev_client *grab;   //强制绑定的evdev_client结构,这个结构后面再分析   
8.        struct list_head client_list;  //evdev_client 链表,这说明一个evdev设备可以处理多个evdev_client,可以有多个进程访问evdev设备   
9.        spinlock_t client_lock; /* protects client_list */  
10.        struct mutex mutex;  
11.        struct device dev;       //device结构,说明这是一个设备结构   
12.    };  


evdev结构体在配对成功的时候生成,由handler->connect生成,对应设备文件为/dev/input/event(n),如触摸屏驱动的event0,这个设备是用户空间要访问的设备,可以理解它是一个虚拟设备,因为没有对应的硬件,但是通过handle->dev 就可以找到input_dev结构,而它对应着触摸屏,设备文件为/dev/input/input0。这个设备结构生成之后保存在 evdev_table中,索引值是minor。设备文件通过设备号关联。
在evdev_connect()中,有设备文件初始化过程:

dev_set_name(&evdev->dev, "event%d", minor);

evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);

也可通过/proc/bus/input/devices查看设备文件,PC虚拟机下测试如下:
cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button"
P: Phys=LNXPWRBN/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXPWRBN:00/input/input0
U: Uniq=
H: Handlers=kbd event0
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0011 Vendor=0002 Product=0005 Version=0000
N: Name="ImPS/2 Generic Wheel Mouse"
P: Phys=isa0060/serio1/input0
S: Sysfs=/devices/platform/i8042/serio1/input/input3
U: Uniq=
H: Handlers=mouse1 event3
B: PROP=0
B: EV=7
B: KEY=70000 0 0 0 0 0 0 0 0
B: REL=103
通过比较VID、PID来找到对应的usb mouse设备,然后找到对应的mouse1、event3
其实也可以不写应用程序,直接通过cat /dev/input/mouse1 | hexdump来获取鼠标数据。
一个网上示例如下:

1.    /* 
2.    20150101 
3.    just a simple input test code 
4.    lei_wang 
5.    */  
6.      
7.    #include <stdio.h>  
8.    #include <stdlib.h>  
9.    #include <unistd.h>  
10.    #include <fcntl.h>  
11.    #include <string.h>  
12.    #include <linux/input.h>  
13.      
14.    int main()  
15.    {  
16.        int fd;  
17.        int version;  
18.        int ret;  
19.        struct input_event ev;  
20.          
21.        fd = open("/dev/input/event1", O_RDONLY);  
22.        if (fd < 0) {  
23.            printf("open file failed\n");  
24.            exit(1);  
25.        }  
26.      
27.        ioctl(fd, EVIOCGVERSION, &version);  
28.        printf("evdev driver version is 0x%x: %d.%d.%d\n",  
29.                        version, version>>16, (version>>8) & 0xff, version & 0xff);  
30.      
31.        while (1) {  
32.            ret = read(fd, &ev, sizeof(struct input_event));  
33.            if (ret < 0) {  
34.                printf("read event error!\n");  
35.                exit(1);  
36.            }  
37.              
38.            if (ev.type == EV_KEY)  
39.                printf("type %d,code %d, value %d\n", ev.type, ev.code, ev.value);  
40.        }  
41.          
42.        return 0;  
43.    }  

连续按一个按键输出:

type: 0x1, code: 0x73, value: 0x1
type: 0x0, code: 0x0, value: 0x0
type: 0x1, code: 0x73, value: 0x2
type: 0x0, code: 0x0, value: 0x1
......
type: 0x1, code: 0x73, value: 0x2
type: 0x0, code: 0x0, value: 0x1
type: 0x1, code: 0x73, value: 0x0
type: 0x0, code: 0x0, value: 0x0

EV_KEY 0x1,键code:0x73,键按下0x1,键释放0x0。

EV_SYN 0x0,code:0x0,SYN_REPORT 0x0, SYN_CONFIG  0x1(value未特别说明),用于标识一个完整的报告。

按键按下后数据连续上报原因解析:

按键产生中断,在中断中向上报告键值,这里要注意两点:

1. input_event(input, type, button->code, !!state);  这个函数 如果第一次报告了 input_event(input, type, button->code, 1);  第二次又报告了 input_event(input, type, button->code, 1);  那么第二次是报告不上的,也就是说 只有键值变化了报告才有效。这也是按键驱动为什么都是双边延触发,就是为了产生按键按下 和 按键抬起 ,如果每次只报告一次按键按下,那么 驱动只会报告一次按键。
2. 如果设置了 __set_bit(EV_REP, input->evbit);  也就是重复报告,它的工作机制是这样的:
如果按键报告了input_event(input, type, button->code, 1);  之后,  在250ms (可以改)后,依然没有报告 input_event(input, type, button->code, 0); 则 input 会每隔 33ms 继续报告一次 input_event(input, type, button->code, 2);  直到 报告了  input_event(input, type, button->code, 0); 才停止 ,这就是我们按住一个按键不松开时,会一直打印键值的原因;
这段代码在 drivers/input/input.c 中
/*
     * If delay and period are pre-set by the driver, then autorepeating
     * is handled by the driver itself and we don't do it in input.c.
     */

    init_timer(&dev->timer);
    if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
        dev->timer.data = (long) dev;
        dev->timer.function = input_repeat_key;
        //dev->rep[REP_DELAY] = 250;
        dev->rep[REP_DELAY] = 2500;
        dev->rep[REP_PERIOD] = 33;
    }
这里要注意红色的说明文字,也就说如果我们自己的驱动里自己定义了 dev->rep[REP_DELAY] = 2500;  那么就不会使用input 的timer ,而要使用自己编写的timer 。

 

参考:

  1. http://www.linuxidc.com/Linux/2011-09/43187.htm   input子系统分析
  2. http://blog.csdn.net/zhangxizhicn/article/details/6642062  linux input子系统分析
  3. http://blog.csdn.net/21cnbao/article/details/5615493  linux设备驱动的分层设计思想
  4. linux设备驱动详解    宋宝华
  5. http://blog.csdn.net/luckywang1103/article/details/42324229   input子系统—架构、驱动、应用程序
  6. http://www.cnblogs.com/leaven/archive/2011/02/12/1952793.html   linux input子系统io控制字段 
  7. https://blog.csdn.net/jxgz_leo/article/details/51149561  driver: Linux设备模型之input子系统详解
posted @ 2016-10-23 17:36  yuxi_o  阅读(1158)  评论(0编辑  收藏  举报