【Linux驱动】Input子系统

输入子系统(Input子系统)

1. 什么是输入子系统?

  • 什么是输入设备?
    常见的摄入设备有键盘、鼠标、触摸屏等等,用户通过这些输入设备与Linux系统进行数据交换。
  • 什么是输入系统?
    输入设备的种类繁多,能否统一它们的接口?既在驱动层面统一,也在应用程序层面统一?Linux系统为了统一管理这些输入设备,实现了一套能兼容所有输入设备的框架——输入子系统。驱动开发人员基于这套框架开发出程序,应用开发人员可以使用统一的API去使用设备。

2. 输入子系统架构

输入子系统由驱动层(input_driver)、核心层(input_core)以及事件层(input_event_handle)组成,如下图所示:
【Linux驱动】Input子系统_第1张图片

  • 驱动层 :输入设备的具体驱动程序,将硬件输入转化为统一的事件形式,向input核心层汇报。
  • 核心层:承上启下,为驱动层提供规范的接口,驱动层只要关心如何驱动硬件并获得硬件数据,然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。
  • 事件层:为不同硬件类型提供了用户访问和处理接口,将硬件驱动层传来的事件报告给用户程序。

Note:Input子系统中所有输入设备的主设备号都是13,核心层通过次设备号来将输入设备分类,例如如0-31是游戏杆,32-63是鼠标(对应Mouse Handler)、64-95是事件设备(如触摸屏,对应Event Handler)。在使用input系统时,不需要去注册字符设备,只需要向系统申请一个input_device即可

3. 相关结构体

3.1 Input_dev

在Linux内核中,使用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.2 Input_handler

input_handler结构体属于事件层,代表一种输入事件的解决方案,如键盘,鼠标、游戏手柄等等,该结构体定义在include/linux/input.h中,如下:

/* 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 Input_handle

input_handle结构体属于核心层代表一对已建立连接的input_dev和input_handler,该结构体定义在include/linux/input.h中,如下:

/* 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链表 */
};

Note:以上三个结构体的关系如下:
【Linux驱动】Input子系统_第2张图片

3.4 Input_event

在input子系统中,使用input_event来表示一个输入事件,该结构体被定义在文件/include/uapi/linux/input.h中,如下:

/* include/uapi/linux/input.h */
struct input_event
{
	struct timeval time;		/* 事件发生的时间 */
	__u16 type;					/* 事件类型 */
	__u16 code;					/* 事件码 */
	__s32 value;				/* 事件值 */
}

/* include/uapi/linux/time.h */
struct timeval
{
	__kernel_time_t tv_sec;			/* 秒 */
	__kernel_suseconds_t tv_usec;	/* 微秒 */
}

4. input子系统关键流程

4.1 input_dev注册流程

【Linux驱动】Input子系统_第3张图片
设备驱动层,注册一个input_dev需要三个步骤,如下:

1. 分配一个input_dev实例(内存)。

	my_dev = input_allocate_device();

2. 初始化input_dev实例(配置该输入设备支持的事件类型等)。

	my_dev->name = XXX_NAME;				/* 设备名称 */
	my_dev->phys = XXX_PHYS;				/* 设备在系统中的路径 */
	my_dev->id.vendor = XXX_VENDOR_ID;		/* 设备ID */
	my_dev->id.product = XXX_PRODUCT_ID;
	my_dev->id.version = XXX_CTL_VERSION;

	set_bit(EV_KEY, my_dev->evbit);			/* 设备支持的事件类型:EV_KEY(按键) */
  	set_bit(EV_REP, my_dev->evbit);			/* 设备支持的事件类型:EV_REP(允许重复按键) */

	my_dev->keycode = xxx_key_tab;					/* 存储按键值的数组的首地址 */
	my_dev->keycodesize = sizeof(int);				/* 每个键值字节数 */
	my_dev->keycodemax = ARRAY_SIZE(xxx_key_tab);	/* 支持的最大按键数 */

3. 向核心层注册input_dev。

	input_register_device(my_dev);

Note:函数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 (error)
	     goto err_free_vals;

	path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
	pr_info("%s as %s\n", dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
	kfree(path);

	error = mutex_lock_interruptible(&input_mutex);
	if (error)
	     goto err_device_del;
	
	/* 将新的dev放入链表input_dev_list尾部 */
	list_add_tail(&dev->node, &input_dev_list);
	/* 遍历链表input_handler_list中所有input_handler,是否支持这个input_dev,若支持,则将两者连接 */
	list_for_each_entry(handler, &input_handler_list, node)		
	input_attach_handler(dev, handler); 

	input_wakeup_procfs_readers();

	mutex_unlock(&input_mutex);

	if (dev->devres_managed) 
	{
		dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n", __func__, dev_name(&dev->dev));
		devres_add(dev->dev.parent, devres);
	}
	return 0;

err_device_del:
	 device_del(&dev->dev);
err_free_vals:
	kfree(dev->vals);
	dev->vals = NULL;
err_devres_free:
	devres_free(devres);
	return error;
}
EXPORT_SYMBOL(input_register_device);

其中,函数input_attach_handler解析如下:

	/* drivers/input/input.c */
	static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)  
	{  
    	const struct input_device_id *id;  
    	int error;  

	/* blacklist是handler该忽略input设备类型 */    
    if (handler->blacklist && input_match_device(handler->blacklist, dev))  
    	return -ENODEV;  
    
    /* 比较input_dev中的id和handler支持的id,存放在handler中的id_table中 */
    id = input_match_device(handler->id_table, dev);  
    if (!id)  
        return -ENODEV;  
  
  	/* 配对成功调用handler的connect函数,这个函数在事件处理器中定义,主要生成一个input_handle结构,并初始化,还生成一个事件处理器相关的设备结构 */
    error = handler->connect(handler, dev, id);  
    if (error && error != -ENODEV)  
        printk(KERN_ERR  
            "input: failed to attach handler %s to device %s, "  
            "error: %d\n",  
            handler->name, kobject_name(&dev->dev.kobj), error);  
        /* 出错处理 */  
    return error;  
 }

4.2 事件上报流程

在向核心层申请、注册完input_dev结构体后,input子系统知道了输入设备的存在,并可以找到该设备对应的事件解决方案input_handler,但此时还不能正常的使用input子系统,因为input设备是输入一些信息,但是linux内核还不清楚输入的信息表示什么意思,有什么作用,所以我们需要驱动硬件获取输入事件,然后将输入事件上报给linux内核。不同的输入设备获取的输入事件不同,不同事件的上报函数也不同,首先来看一下有哪些常用的API函数:

  • input_evnet函数
    input_event函数可以上报所有类型的事件事件值,linux内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了input_event函数。input_event函数的原型如下:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	unsigned long flags;
	/* 如果支持evbit类上报 */
	if (is_event_supported(type, dev->evbit, EV_MAX)) {

		spin_lock_irqsave(&dev->event_lock, flags); 	/* 获取自旋锁 */
		input_handle_event(dev, type, code, value); 	/* 上报事件 */
		spin_unlock_irqrestore(&dev->event_lock, flags);/* 释放锁 */
	}
}
  • 按键及其他事件上报函数
    input_report_key函数为按键事件的上报函数,该函数的本质上还是调用了input_event函数,原型如下:
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!value);
}

像input_report_key这样针对具体事件类型的事件上报函数在input子系统中还有很多,它们本质上都是都是调用了input_event函数,函数如下:

void input_report_rel(struct input_dev *dev, unsigned int code, int value)		/* 相对位移事件上报 */
void input_report_abs(struct input_dev *dev, unsigned int code, int value)		/* 绝对位移事件上报 */
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)	/* force-feedback(强制反馈)事件上报 */
void input_report_switch(struct input_dev *dev, unsigned int code, int value)	/* 开关事件上报 */
void input_mt_sync(struct input_dev *dev)										/* 同步多点触摸(multi-touch)事件 */
  • input_sync函数
    input_sysnc函数为事件同步函数,用来告诉input子系统事件上报结束,该函数本质上是上报一个同步类事件,函数声明如下:
void input_sync(struct input_dev *dev);

接下来,以按键设备为例,了解一下事件上报的流程,如下:

/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
	unsigned char value;
	value = gpio_get_value(keydesc->gpio); 		 /* 读取 IO 值 */
	if(value == 0)
	{   /* 按下按键 */
 		/* 上报按键值 */
  		input_report_key(inputdev, KEY_0, 1); 	 /* 最后一个参数 1,按下 */
 		input_sync(inputdev);     				 /* 同步事件 */
 	} 
 	else 
 	{        
 		/* 按键松开 */
  		input_report_key(inputdev, KEY_0, 0); 	 /* 最后一个参数 0,松开 */
  		input_sync(inputdev);      				 /* 同步事件 */
 	}
}

4.3 应用程序读取数据流程

上面讲到了输入事件上报的过程,那么驱动程序将事件上报给input子系统后,应用程序又如何拿到数据呢?过程如下:

  1. APP调用open函数打开dev/input/event0
    • 在驱动程序evdev_open里,创建一个evdev.client,表示一个“客户”
  2. APP调用read/poll读取、等待数据。
    • 没有数据时休眠:wait_event_interruptible(evdev->wait, ...)
  3. 点击、操作输入设备,产生中断
  4. 在中断服务程序里
    • 从硬件读取数据
    • 使用以下函数上报数据:
    void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
    static inline void input_sync(struct input_dev *dev); // 实质也是 input_event
    
  5. 最后再wake_up_interruptible(&evdev->wait);唤醒中断程序。

5. 总结,如何写一个输入设备(input_dev)的驱动?

【Linux驱动】Input子系统_第4张图片

你可能感兴趣的:(Linux驱动,linux)