Linux input 子系统详解

Posted 一口Linux

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux input 子系统详解相关的知识,希望对你有一定的参考价值。

1. 模块概述

1.1.相关资料和代码研究

drivers/input/
include/uapi/linux/input-event-codes.h

2. 模块功能

linux核心的输入框架

3. 模块学习

3.1.概述

Linux输入设备种类繁杂,常见的包括触摸屏、键盘、鼠标、摇杆等;这些输入设备属于字符设备,而linux将这些设备的共同特性抽象出来,Linux input 子系统就产生了。

3.2.软件架构

输入子系统是由设备驱动层(input driver)、输入核心层(input core)、输入事件处理层(input event handle)组成,具体架构如图4.1所示:

  • (1)input设备驱动层:负责具体的硬件设备,将底层的硬件输入转化为统一的事件形式,向input核心层和汇报;
    *(2)input核心层:连接input设备驱动层与input事件处理层,向下提供驱动层的接口,向上提供事件处理层的接口;
    *(3)input事件处理层:为不同硬件类型提供了用户访问以及处理接口,将硬件驱动层传来的事件报告给用户程序。

在input子系统中,每个事件的发生都使用事件(type)->子事件(code)->值(value)
所有的输入设备的主设备号都是13,input-core通过次设备来将输入设备进行分类,如0-31是游戏杆,32-63是鼠标(对应Mouse Handler)、64-95是事件设备(如触摸屏,对应Event Handler)。

Linux输入子系统支持的数据类型

时间类型编码含义
EV_SYN0x00同步事件
EV_KEY0x01按键事件(鼠标,键盘等)
EV_REL0x02相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS0x03绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC0x04其它
EV_SW0x05开关
EV_LED0x11按键/设备灯
EV_SND0x12声音/警报
EV_REP0x14重复
EV_FF0x15力反馈
EV_PWR0x16电源
EV_FF_STATUS0x17力反馈状态
EV_MAX0x1f事件类型最大个数和提供位掩码支持

定义的按键值

#define KEY_RESERVED           0
#define KEY_ESC                 1
#define KEY_1                   2
#define KEY_2                   3
#define KEY_3                   4
#define KEY_4                   5
#define KEY_5                   6
#define KEY_6                   7
#define KEY_7                   8
#define KEY_8                   9
#define KEY_9                   10
#define KEY_0                   11
...

3.3.数据结构

三个数据结构input_dev,input_handle,input_handler之间的关系如图4.2、4.3所示

input_dev:是硬件驱动层,代表一个input设备。
input_handler:是事件处理层,代表一个事件处理器。
input_handle:属于核心层,代表一个配对的input设备与input事件处理器。
input_dev 通过全局的input_dev_list链接在一起,设备注册的时候完成这个操作。

input_handler 通过全局的input_handler_list链接在一起。事件处理器注册的时候实现了这个操作(事件处理器一般内核自带,不需要我们来写)

input_hande 没有一个全局的链表,它注册的时候将自己分别挂在了input_dev 和 input_handler 的h_list上了。通过input_dev 和input_handler就可以找到input_handle在设备注册和事件处理器,注册的时候都要进行配对工作,配对后就会实现链接。通过input_handle也可以找到input_dev和input_handler。

我们可以看到,input_device和input_handler中都有一个h_list,而input_handle拥有指向input_dev和input_handler的指针,也就是说input_handle是用来关联input_dev和input_handler的。

那么为什么一个input_device和input_handler中拥有的是h_list而不是一个handle呢?
因为一个device可能对应多个handler,而一个handler也不能只处理一个device,比如说一个鼠标,它可以对应even handler,也可以对应mouse handler,因此当其注册时与系统中的handler进行匹配,就有可能产生两个实例,一个是evdev,另一个是mousedev,而任何一个实例中都只有一个handle。
至于以何种方式来传递事件,就由用户程序打开哪个实例来决定。后面一个情况很容易理解,一个事件驱动不能只为一个甚至一种设备服务,系统中可能有多种设备都能使用这类handler,比如event handler就可以匹配所有的设备。在input子系统中,有8种事件驱动,每种事件驱动最多可以对应32个设备,因此dev实例总数最多可以达到256个

3.3.1. Input_dev

输入设备

/* include/linux/input.h */
struct input_dev {
	    const char *name;  /* 设备名称 */
	    const char *phys;  /* 设备在系统中的路径 */
	    const char *uniq;  /* 设备唯一id */
	    struct input_id id;  /* input设备id号 */

	    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];  /* 设备支持的事件类型,主要有EV_SYNC,EV_KEY,EV_KEY,EV_REL,EV_ABS等*/	
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)];  /* 支持led事件 */
	    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 *mt;  /* 多点触控区域 */

	    struct input_absinfo *absinfo;  /* 存放绝对值坐标的相关参数数组 */

	    unsigned long key[BITS_TO_LONGS(KEY_CNT)];  /* 反应设备当前的案件状态 */
	    unsigned long led[BITS_TO_LONGS(LED_CNT)];  /* 反应设备当前的led状态 */
	    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); /* 用于处理传递设备的事件 */
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code,          int value);  /* 事件处理函数,主要是接收用户下发的命令,如点亮led */

	    struct input_handle __rcu *grab;  /* 当前占有设备的input_handle */

	    spinlock_t event_lock;  /* 事件锁 */
	    struct mutex mutex;  /* 互斥体 */

	    unsigned int users;  /* 打开该设备的用户数量(input_handle) */
	    bool going_away;  /* 标记正在销毁的设备 */

	    struct device dev;  /* 一般设备 */

	    struct list_head	h_list;  /* 设备所支持的input handle */
	    struct list_head	node;  /* 用于将此input_dev连接到input_dev_list */

	    unsigned int num_vals;  /* 当前帧中排队的值数 */
	    unsigned int max_vals;  /*  队列最大的帧数*/
	    struct input_value *vals;  /*  当前帧中排队的数组*/

	    bool devres_managed; /* 表示设备被devres 框架管理,不需要明确取消和释放*/
};

3.3.2. Input_handler

处理具体的输入事件的具体函数

/* include/linux/input.h */
struct input_handler {

	    void *private;  /* 存放handle数据 */

void (*event)(struct input_handle *handle, unsigned int type, unsigned  int code, int value);
	    void (*events)(struct input_handle *handle,
		           const struct input_value *vals, unsigned int count);
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	    bool (*match)(struct input_handler *handler, struct input_dev *dev);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const  struct input_device_id *id);
	    void (*disconnect)(struct input_handle *handle);
	    void (*start)(struct input_handle *handle);

	    bool legacy_minors;
	    int minor;
	    const char *name;  /* 名字 */

	    const struct input_device_id *id_table;  /* input_dev匹配用的id */

struct list_head	h_list; /* 用于链接和handler相关的handle,input_dev与input_handler配对之后就会生成一个input_handle结构 */
	    struct list_head	node;  /* 用于将该handler链入input_handler_list,链接所有注册到内核的所有注册到内核的事件处理器 */
};

3.3.3. Input_handle

连接输入设备和处理函数

/* include/linux/input.h */
struct input_handle {
	    void *private;  /* 数据指针 */

	    int open;  /* 打开标志,每个input_handle 打开后才能操作 */
	    const char *name;  /* 设备名称 */

	    struct input_dev *dev;  /* 指向所属的input_dev */
	    struct input_handler *handler;  /* 指向所属的input_handler */

	    struct list_head	d_node;  /* 用于链入所指向的input_dev的handle链表 */
	    struct list_head	h_node;  /* 用于链入所指向的input_handler的handle链表 */
};

3.3.4. Evdev

字符设备事件

/* drivers/input/evdev.c */
struct evdev {
	    int open;    /* 设备被打开的计数 */
	    struct input_handle handle;  /* 关联的input_handle */ 
	    wait_queue_head_t wait;  /* 等待队列,当前进程读取设备,没有事件产生时,
进程就会sleep */
	    struct evdev_client __rcu *grab;  /* event响应 */
struct list_head client_list;  /* evdev_client链表,说明evdev设备可以处理多个 evdev _client,可以有多个进程访问evdev设备 */
	    spinlock_t client_lock;
	    struct mutex mutex;
	    struct device dev;
	    struct cdev cdev;
	    bool exist;   /* 设备存在判断 */
};

3.3.5. evdev_client

字符设备事件响应

/* drivers/input/evdev.c */
struct evdev_client {
	    unsigned int head;  /* 动态索引,每加入一个event到buffer中,head++ */
	    unsigned int tail;  /* 动态索引,每取出一个buffer中到event,tail++ */
	    unsigned int packet_head;  /* 数据包头部 */
	    spinlock_t buffer_lock;  
	    struct fasync_struct *fasync;  /* 异步通知函数 */
	    struct evdev *evdev;  
	    struct list_head node;  /* evdev_client链表项 */
	    int clkid;
	    unsigned int bufsize;
	    struct input_event buffer[];  /* 用来存放input_dev事件缓冲区 */
};

3.3.6. Evdev_handler

evdev_handler事件处理函数

/* drivers/input/input.c */
static struct input_handler evdev_handler = {
	    .event		= evdev_event,   /* 事件处理函数, */  
	    .events	= evdev_events,  /* 事件处理函数, */
	    .connect	= evdev_connect, /* 连接函数,将事件处理和输入设备联系起来 */
	    .disconnect	= evdev_disconnect,  /* 断开该链接 */
	    .legacy_minors	= true,
	    .minor		= EVDEV_MINOR_BASE,
	    .name		= "evdev", /* handler名称 */
	    .id_table	= evdev_ids, /* 断开该链接 */
};

3.3.7. input_event

标准按键编码信息

/* drivers/input/evdev.c */
struct input_event {                                                            
    struct timeval time;   /* 事件发生的时间  */                                
    __u16 type;             /* 事件类型 */                                      
    __u16 code;             /* 事件码 */                                        
    __s32 value;            /* 事件值 */                                        
};   

3.3.8. input_id

和input输入设备相关的id信息

/* include/uapi/linux/input.h */
struct input_id {  
    __u16 bustype;  /* 总线类型 */  
    __u16 vendor;  /* 生产厂商 */  
    __u16 product;  /* 产品类型 */ 
    __u16 version;  /* 版本 */
 };

3.3.9. input_device_id

/* include/uapi/linux/input.h */
struct input_device_id {

	    kernel_ulong_t flags;

	    __u16 bustype;  /* 总线类型 */
	    __u16 vendor;  /* 生产厂商 */
	    __u16 product;  /* 产品类型 */
	    __u16 version;  /* 版本 */

	    kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];
	    kernel_ulong_t propbit[INPUT_DEVICE_ID_PROP_MAX / BITS_PER_LONG + 1];

	    kernel_ulong_t driver_info;
};

3.3.10. input_even

输入事件的传递已input_event为基本单位

struct input_event {
	struct timeval time; //时间戳
	    __u16 type; //事件总类型
	    __u16 code; //事件子类型
	    __s32 value; //事件值
};

3.4. Linux input 子系统关键流程

核心层,执行的时候会注册设备号,然后在handler层注册input_handler,也就是evdev_handler会注册到核心层维护的链表中。

然后进行硬件初始化获取数据,而且需要将设备注册到链表中。注册进来就就会遍历input_handler_list链表,找到对应的handler,匹配成功后会调用connect方法。connect分配evdev,evdev就记录了input_handler和input_device之间的关系,同时创建设备节点,还会注册cdev从而可以让应用调用。

当应用程序调用open,read等接口的时候就会调用input_handler层实现的xxx_open,那么open就会分配好evdev_client,最终在input_dev层上报数据的时候会自动调用input_handler,input_handler就会调用events填充上报的数据到缓冲区client,此时如果没有唤醒队列的话应用read的时候会阻塞,而唤醒队列后最终使用copy_to_user来给应用数据。
设备驱动程序上报事件的函数有:

input_report_key //上报按键事件
input_report_rel //上报相对坐标事件
input_report_abs //上报绝对坐标事件
input_report_ff_status
input_report_switch
input_sync //上报完成后需要调用这些函数来通知系统处理完整事件 
input_mt_sync //上报完成后需要调用这些函数来通知系统处理完整事件

这些函数其实是input_event函数的封装,调用的都是input_event函数,在输入设备驱动(input_dev)中,一般通过轮询或中断方式获取输入事件的原始值(raw value),经过处理后再使用input_event()函数上报;核心层将事件数据(type、code、value)打包、分发至事件处理器;调用关系为:input_event->input_handle_event->input_pass_values,这一函数都在input.c实现。

3.4.1. Input 设备注册流程

输入设备注册过程如图4.3所示

3.4.2. 连接设备流程

连接设备流程如图4.4所示

3.4.3. 事件上报流程

事件上报流程如图4.5所示

3.4.4. 数据读取流程

数据读取流程如图4.6所示

3.5.关键函数解析

3.5.1. input_init

input子系统使用subsys_initcall宏修饰input_init()函数在内核启动阶段被调用。input_init()函数在内核启动阶段被调用。input_init()函数的主要工作是:在sys文件系统下创建一个设备类(/sys/class/input),调用register_chrdev()函数注册input设备。

/* drivers/input/input.c */
static int __init input_init(void)
{
	    int err;

	    err = class_register(&input_class);  /* 注册类,放在sys/class下 */  
	    if (err) {
		        pr_err("unable to register input_dev class\\n");
		        return err;
	    }

	    err = input_proc_init();  /* 在proc目录下建立相关目录 */
	    if (err)
		        goto fail1;

	    err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
				    INPUT_MAX_CHAR_DEVICES, "input");  /* 注册字符设备编号,INPUT_MAJOR		 永远是13 */  
	    if (err) {
		        pr_err("unable to register char major %d", INPUT_MAJOR);
		        goto fail2;
	    }

	    return 0;

 fail2:	input_proc_exit();
 fail1:	class_unregister(&input_class);
	    return err;
}

3.5.2. Input_register_device

/* drivers/input/input.c */
int input_register_device(struct input_dev *dev)
{
    struct atomic_t  input_no = ATOMIC_INIT(0);
	    struct input_devres *devres = NULL;
	    struct input_handler *handler;
	    unsigned int packet_size;
	    const char *path;
	    int error;

	    if (dev->devres_managed) {
		        devres = devres_alloc(devm_input_device_unregister,
				              sizeof(*devres), GFP_KERNEL);
		    if (!devres)
			        return -ENOMEM;

		    devres->input = dev;
	}

	    /* 每个input_device都会产生EV_SYN/SYN_REPORT时间,所以就放在一起设置 */
	    __set_bit(EV_SYN, dev->evbit);

	    /* KEY_RESERVED is not supposed to be transmitted to userspace. */
	    __clear_bit(KEY_RESERVED, dev->keybit);

	    /* 没有设置的位,确保被清零 */
	    input_cleanse_bitmasks(dev);
	    /*  */
	    packet_size = input_estimate_events_per_packet(dev);
	    if (dev->hint_events_per_packet < packet_size)
		         dev->hint_events_per_packet = packet_size;

	    dev->max_vals = dev->hint_events_per_packet + 2;
	    dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
	    if (!dev->vals) {
		        error = -ENOMEM;
		        goto err_devres_free;
	    }

    /* 如果延时周期是程序预先设定的,那么是由驱动自动处理,主要是为了处理重复按键 */
    	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_PERIOD] = 33;
	     }

	    if (!dev->getkeycode)  /* 获取按键值 */
		        dev->getkeycode = input_default_getkeycode;

	    if (!dev->setkeycode)  /* 设置按键值 */
		        dev->setkeycode = input_default_setkeycode;

	    error = device_add(&dev->dev);  /* 将dev注册到sys */
	    if 以上是关于Linux input 子系统详解的主要内容,如果未能解决你的问题,请参考以下文章

Linux 输入设备调试详解(零基础开发)Rotary_Encoder旋转编码器模块(EC11)通用GPIO为例 挂载input输入子系统

DRV_01_Input子系统框架详解

input子系统详解

LINUX操作系统知识:进程与线程详解

input子系统详解2

长知识!详解Linux五大网络IO模型