输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、触摸等动作发送时产生一个中断(或驱动通过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. }
参考:
- http://www.linuxidc.com/Linux/2011-09/43187.htm input子系统分析
- http://blog.csdn.net/zhangxizhicn/article/details/6642062 linux input子系统分析
- http://blog.csdn.net/21cnbao/article/details/5615493 linux设备驱动的分层设计思想
- linux设备驱动详解 宋宝华
- http://blog.csdn.net/luckywang1103/article/details/42324229 input子系统—架构、驱动、应用程序
- http://www.cnblogs.com/leaven/archive/2011/02/12/1952793.html linux input子系统io控制字段
- https://blog.csdn.net/jxgz_leo/article/details/51149561 driver: Linux设备模型之input子系统详解