Linux驱动之输入子系统
1.1 输入子系统简介
1.1.1 概念
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。
其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。
1.1.2 输入子系统的好处
(1)统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。
(2)提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。
(3)抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问。
1.1.3 输入子系统的接口
/dev/input或者/dev目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。
输入子系统的接口:/dev/input目录。
[root@XiaoLong /]# ls /dev/input/* -l crw-rw---- 1 root root 13, 64 May 16 01:44 /dev/input/event0 crw-rw---- 1 root root 13, 65 May 16 01:44 /dev/input/event1 crw-rw---- 1 root root 13, 63 May 16 01:44 /dev/input/mice crw-rw---- 1 root root 13, 32 May 16 01:44 /dev/input/mouse0 |
输入子系统由内核代码 drivers/input/input.c 构成,它的存在屏蔽了用户到设备驱动的交互
细节,为设备驱动层和事件处理层提供了相互通信的统一界面。
有的系统的输入子系统的节点在 /dev/目录下:
[root@XiaoLong /]# ls /dev/* -l crw-rw---- 1 root root 13, 64 May 16 01:44 /dev/event0 crw-rw---- 1 root root 13, 65 May 16 01:44 /dev/event1 crw-rw---- 1 root root 13, 63 May 16 01:44 /dev/mice crw-rw---- 1 root root 13, 32 May 16 01:44 /dev/mouse0 |
事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。
输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。
1.1.4 输入子系统的总体框架
图1-1
1.1.5 输入子系统的分层
Linux输入子系统包括三个层次,有上到下别是事件处理层(Event Handler)、核心层(Input Core)和驱动层(Input Driver)。
1.事件处理层:负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
2.核心层:是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
3.驱动层:负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,键盘、鼠标、触摸屏等字符设备驱动功能的实现工作主要在这层。
在Input子系统三层框架中对应3个结构体。
1.结构体input_dev表示底层硬件设备,是所有输入设备的抽象。
2.handle是手柄的意思,结构体input_handle表示连接杆,连接底层硬件和上层事件处理层。
3.结构体input_handler表示事件处理器,是对事件处理的抽象。
事件处理层代码:
drivers\Input\Evdev.c
1.2 输入子系统的核心结构
1.2.1 input_dev结构
在驱动层需要实现struct input_dev结构,实现输入子系统的注册与注销。
在input.h定义了如下结构。
struct input_dev { const char *name; //设备名字--比如:键盘的名字 const char *phys; //设备在系统中的路径。比如:input/key0 const char *uniq; //全球唯一ID号 struct input_id id; //用于匹配事件处理层handler unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //记录支持的事件 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//记录支持的按键值 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //记录izhic的相对坐标 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)]; unsigned int hint_events_per_packet; unsigned int keycodemax; //支持的按键值个数 unsigned int keycodesize;//每个键值的字节数 void *keycode; //存储按键值的数据首地址 int (*setkeycode)(struct input_dev *dev, const struct input_keymap_entry *ke, unsigned int *old_keycode); int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke); struct ff_device *ff; unsigned int repeat_key; //最近一次按键值,用于连击 struct timer_list timer; //自动连击计时器---选择了重复事件 int rep[REP_CNT]; struct input_mt_slot *mt; int mtsize; int slot; int trkid; struct input_absinfo *absinfo; unsigned long key[BITS_TO_LONGS(KEY_CNT)]; unsigned long led[BITS_TO_LONGS(LED_CNT)]; unsigned long snd[BITS_TO_LONGS(SND_CNT)]; unsigned long sw[BITS_TO_LONGS(SW_CNT)]; 打开函数---可以自己实现*/ int (*open)(struct input_dev *dev); /*关闭函数---可以自己实现*/ void (*close)(struct input_dev *dev); 断开连接时,清除数据--可以自己实现*/ int (*flush)(struct input_dev *dev, struct file *file); /*回调函数-主要是接收用户下发的命令,如点亮led*/ int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle __rcu *grab; spinlock_t event_lock; struct mutex mutex; unsigned int users; bool going_away; bool sync; //最后一次同步后没有新的事件置1 struct device dev; struct list_headh_list; //handle链表,用于与input_handler相联系 struct list_headnode; // input_dev链表 /* 设备向输入子系统(input subsystem)注册后,会将该链表添加到系统维护的一个链表中去,从而系统可以管理这个设备*/ |
1.2.2 input_event事件结构
struct input_event 结构一般在应用层定义使用,用来接收事件层上报的事件。
struct input_event { struct timeval time; //本次上报时间戳 __u16 type; //本次数据的事件类型 (按键事件、相对坐标、绝对坐标) __u16 code; //具体数值,如果是按键事件,则是键值 __s32 value; //和code相关标志,如果是按键,代表按下还是松开。 }; |
:事件的类型。(比如:按键事件EV_KEY ,绝对坐标EV_ABS)
:上报的按键值。
如果上报的是EV_KEY事件,code表示按键值。
如果上报的是EV_ABS事件,code表示坐标的类型(X或者Y)。
value :
如果上报的是EV_KEY事件,value就表示状态值。(0或者1或者重复值2)
如果上报的是EV_ABS事件,value就表示具体的坐标值。
1.2.3 时间结构体
struct timeval { __kernel_time_ttv_sec;/*秒 */ __kernel_suseconds_ttv_usec;/* 微秒 */ }; |
1.3 输入子系统API函数
1.3.1 动态分配input_dev结构体
函数原型 | struct input_dev *input_allocate_device(void) |
函数功能 | 该函数为struct input_dev结构体分配内存,并初始化该结构体的部分成员 |
函数参数 | 空 |
函数返回值 | 成功:struct input_dev结构体指针,指向分配的结构体 失败:NULL |
所在头文件 | include/linux/input.h |
函数定义文件 | drivers/input/input.c |
1.3.2 释放input_dev结构体
函数原型 | void input_free_device(struct input_dev *dev) |
函数功能 | 释放input_allocate_device函数分配的 input_dev结构体 |
函数参数 | struct input_dev结构体指针 |
函数返回值 | 无 |
所在头文件 | include/linux/input.h |
函数定义文件 | drivers/input/input.c |
1.3.3 注册输入子系统
函数原型 | int input_register_device(struct input_dev *dev) |
函数功能 | 该函数用于向输入子系统核心注册输入设备 |
函数参数 | struct input_dev结构体指针 |
函数返回值 | 成功:返回0 失败:一个负的错误码 |
所在头文件 | include/linux/input.h |
函数定义文件 | drivers/input/input.c |
1.3.4 注销输入子系统
函数原型 | void input_unregister_device(struct input_dev *) |
函数功能 | 该函数用于注销一个输入设备 |
函数参数 | struct input_dev结构体指针,指向要注销的设备对应的输入设备结构体 |
函数返回值 | 空 |
所在头文件 | include/linux/input.h |
函数定义文件 | drivers/input/input.c |
1.3.5 填充input_dev结构体
方法1:使用设置位的函数实现填充input_dev 结构体
static inline void __set_bit(int nr, volatile unsigned long *addr); //设置指定的位 static inline void __clear_bit(int nr, volatile unsigned long *addr); //清除指定的位 |
参数:
设置的值
设置的地址
示例:
__set_bit(EV_KEY, key_input->evbit); //设置支持按键事件
__set_bit(KEY_1,key_input->keybit); // 设置上报的按键值
方法2:通过input_set_capability函数
函数原型 | void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code) |
函数功能 | 设置输入子系统上报的事件类型和具体的按键值 |
函数参数 | struct input_dev :结构体指针,指向要注销的设备对应的输入设备结构体 unsigned int type :事件类型 unsigned int code :事件类型对应的具体值 |
函数返回值 | 空 |
所在头文件 | include/linux/input.h |
函数定义文件 | drivers/input/input.c |
示例:
input_set_capability(input_dev,EV_KEY,KEY_1); //设置的上报事件类型和具体的值
1.3.6 向应用层上报事件
函数原型 | void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) |
函数功能 | 该函数用于向应用层上报输入事件 |
函数参数 | *dev:input_dev设备结构体 Type:上报的事件类型 Code:具体上报的发生事件,根据Type的不同而含义不同。 例如: Type为EV_KEY时,code表示键盘code或者鼠标按键值。 Type为EV_REL时,code表示操作的是哪个坐标轴,如:REL_X,REL_Y。(因为鼠标有x,y两个轴向,所以一次鼠标移动,会产生两个input_event) Value:根据Type的不同而含义不同 例如: Type为EV_KEY时,value: 0表示按键抬起。1表示按键按下。(4表示持续按下等)。 Type为EV_REL时,value: 表明移动的值和方向(正负值)。 Type为EV_ABS时,value表示具体的坐标值。 |
函数返回值 | 空 |
所在头文件 | include/linux/input.h |
函数定义文件 | drivers/input/input.c |
1.3.7 同步上报事件
函数原型 | static inline void input_sync(struct input_dev *dev) |
函数功能 | 该函数用于同步发生的事件,告诉应用层本次事件已经完成(必须的) |
函数参数 | struct input_dev结构体指针,指向要同步的结构体 |
函数返回值 | 空 |
所在头文件 | include/linux/input.h |
1.4 编程框架
1.4.1 上报普通按键事件步骤
- 定义input_dev结构体指针
static struct input_dev *key_input; |
- 动态分配input_dev结构体
key_input=input_allocate_device(); |
- 设置事件类型和对应的值
__set_bit(EV_KEY, key_input->evbit); //设置事件类型 __set_bit(KEY_1,key_input->keybit); // 设置事件类型对应的具体值 可以设置重复上报事件 |
或者:
input_set_capability(key_input,EV_KEY,KEY_1); |
- 注册输入子系统
input_register_device(key_input); |
- 在具体的地方上报事件(比如中断)
input_event(key_input,EV_KEY,KEY_1,1); //表示按键按下 input_event(key_input,EV_KEY,KEY_1,0); //表示按键松开 |
- 同步事件
input_sync(key_input); //同步事件--必须同步否则应用层收不到数据 |
- 注销输入设备(一般在驱动出口调用)
input_unregister_device(key_input); |
- 释放input_dev结构体(一般在驱动出口调用)
input_free_device(key_input); |
1.4.2 上报触摸屏事件步骤
- 动态分配input_dev 结构体
static struct input_dev *touch_input = NULL; //输入子系统的结构体 |
- 设置事件类型
/*设置上报事件类型为触摸屏事件--->绝对值事件 EV_ABS*/ __set_bit(EV_ABS,touch_input->evbit); __set_bit(EV_KEY,touch_input->evbit); |
- 设置事件对应的值
__set_bit(ABS_X,touch_input->absbit); /*上报X坐标*/ __set_bit(ABS_Y,touch_input->absbit); /*上报Y坐标*/ __set_bit(ABS_PRESSURE,touch_input->absbit); /*置为压力事件*/ __set_bit(BTN_TOUCH,touch_input->keybit); |
- 设置上报的事件范围
input_set_abs_params(touch_input,ABS_PRESSURE,0,1,0,0); //压力分为两个级别 input_set_abs_params(touch_input,ABS_X,0,800,0,0); //x坐标范围为0~800 input_set_abs_params(touch_input,ABS_Y,0,480,0,0); //y坐标范围为0~480 |
- 注册输入子系统
input_register_device(touch_input); |
- 上报触摸屏坐标
input_event(touch_input,EV_ABS,ABS_X,x0); input_event(touch_input,EV_ABS,ABS_Y,y0); |
- 上报触摸屏状态
按下: input_event(touch_input,EV_ABS,ABS_PRESSURE,1); input_event(touch_input,EV_ABS,BTN_TOUCH,1); 松开: input_event(touch_input,EV_ABS,BTN_TOUCH,0); input_event(touch_input,EV_ABS,ABS_PRESSURE,0); |
1.4.2 重新定义终端的输入
exec 0</dev/tty1 //重定义标准输入
标准:
0:文件描述符0 -- 标准输入
1:文件描述符1 -- 标准输出
2:文件描述符2 -- 标准错误
重定义标准输入之后,在CRT终端就可以使用普通按键模拟标准键盘,实现敲打命令。
- 查看系统的标准文件描述符:
Unix/Linux/BSD 都有三个特别文件,分别
1)标准输入 即 STDIN , 在 /dev/stdin ,
一般指键盘输入, shell里代号是 0
2) 标准输出 STDOUT, 在 /dev/stdout,
一般指终端(terminal), 就是显示器, shell里代号是 1
3) 标准错误 STDERR, 在 /dev/stderr
也是指终端(terminal), 不同的是, 错误信息送到这里
里代号是 2
[root@wbyq test_20180702]# ls /dev/std* -l lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stderr -> /proc/self/fd/2 lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stdin -> /proc/self/fd/0 lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stdout -> /proc/self/fd/1 |
1.5 鼠标与键盘驱动
USB鼠标驱动代码路径:
drivers\hid\usbhid\usbmouse.c
usb键盘驱动的源代码目录:
drivers/usb/input/usbkbd.c
1.6 输入子系统核心代码分析
核心层:\drivers\input\input.c
1.6.1 输入子系统的注册
在input.c文件里调了input_init函数注册输入子系统,主设备号为13。
文件操作集合:
static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, .llseek = noop_llseek, }; |
这里只实现了一个open函数,其他的文件操作接口不在这里,在事件处理实现。
在这个input_open_file函数里实现了到事件处理层的接口转换。
用户层打开输入子系统事件节点的时候,会调用input_open_file函数,然后在input_open_file函数里改变了文件操作集合的指针指向,最终指向了事件处理层的文件操作集合。
input_open_file函数代码如下:
static int input_open_file(struct inode *inode, struct file *file) { struct input_handler *handler; const struct file_operations *old_fops, *new_fops = NULL; int err; err = mutex_lock_interruptible(&input_mutex); if (err) return err; /* No load-on-demand here? */ handler = input_table[iminor(inode) >> 5]; if (handler) new_fops = fops_get(handler->fops); //得到事件层的文件操作集合指针 mutex_unlock(&input_mutex); /* * That's _really_ odd. Usually NULL ->open means "nothing special", * not "no device". Oh, well... */ if (!new_fops || !new_fops->open) { fops_put(new_fops); err = -ENODEV; goto out; } old_fops = file->f_op; file->f_op = new_fops; /*将事件层实现的open函数赋值给文件指针*/ err = new_fops->open(inode, file); /*调用事件层的OPEN函数*/ if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops); out: return err; } |
1.6.2 事件处理层
源码:\drivers\input\evdev.c
图1-1 注册了输入事件处理程序
图1-2 事件处理层调用的文件操作集合
图1-3 事件处理层的文件操作结合
从文件操作集合可以看出,输入子系统支持多种方式读取按键值。
可以通过read函数直接读、poll机制、异步通知机制等。
1.6.3 应用层编程框架
1.6.3.1 通过poll机制
#include <poll.h> struct input_event ev; struct pollfd fds[1]; fds[0].fd = fb; fds[0].events = POLLIN; while(1) { 等待数据*/ 读取发生的事件 } |