Linux Input子系统浅析(一)-- MTK6735M(Android6.0)平台内核实现

原创 2016年08月29日 15:35:50
 Linux输入设备总类繁杂,常见的包括有按键、键盘、触摸屏、鼠标、摇杆等等,他们本身就是字符设备,
而linux内核将这些设备的共同性抽象出来,简化驱动开发建立了一个input子系统。  驱动层和硬件相关,
直接捕捉和获取硬件设备的数据信息等(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),然
后将数据信息报告到核心层。核心层负责连接驱动层和事件处理层,设备驱动(device driver)和处理程序
(handler)的注册需要通过核心层来完成,核心层接收来自驱动层的数据信息,并将数据信息选择对应的
handler去处理,最终handler将数据复制到用户空间。

1.先了解三个定义在/linux/input.h下重要的结构体input_dev、input_handler、input_handle
file:kernel-3.18/include/linux/input.h
struct input_dev {
	const char *name;  //input 设备名称
	...
	struct input_id id;  //与input_handler匹配用的id




	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 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;
	
	...
	
	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);


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


	struct device dev;


	struct list_head	h_list;   //该链表头用于链接该设备所关联的input_handle
	struct list_head	node;      //该链表头用于将设备链接到input_dev_list


	...
};
struct input_handler {


	...
	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 (*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);




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


	struct list_head	h_list;      //用于链接和该handler相关的handle   
	struct list_head	node;        //用于将该handler链入input_handler_list
	...
};
struct input_handle {




	int open;
	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链表
};
三个结构体可以这样理解,input_dev可以作为输入设备的描述,input_handler可作为事件的处理函数(类似如中断处理)
,input_handle作为 input_dev和input_handler挂接的桥梁。因为一个device可以对应多个handler,而一个handler也可
处理多个input_device。就如一个触摸屏设备可以对应event handler也可以对应tseve handler,所以input_handle必不可
少。
2. input_dev和input_handler的匹配
file:kernel-3.18/drivers/input/input.c
int input_register_device(struct input_dev *dev)
{
	struct input_devres *devres = NULL;
	struct input_handler *handler;




	/* Every input device generates EV_SYN/SYN_REPORT events. */
	__set_bit(EV_SYN, dev->evbit);       //设置支持同步事件,input设备全部默认支持同步事件


	error = device_add(&dev->dev);
	...


	list_add_tail(&dev->node, &input_dev_list);        //将设备添加到input_dev_list中


	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);


	input_wakeup_procfs_readers();


	...
}


static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	...
	id = input_match_device(handler, dev);


	error = handler->connect(handler, dev, id);
	...
}


static const struct input_device_id *input_match_device(struct input_handler *handler,
							struct input_dev *dev)
{
	const struct input_device_id *id;


	for (id = handler->id_table; id->flags || id->driver_info; id++) {


		if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
			if (id->bustype != dev->id.bustype)
				continue;


		if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
			if (id->vendor != dev->id.vendor)
				continue;


		if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
			if (id->product != dev->id.product)
				continue;


		if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
			if (id->version != dev->id.version)
				continue;


		if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
			continue;


		if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
			continue;


		if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
			continue;


		if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
			continue;


		if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
			continue;


		if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
			continue;


		if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
			continue;


		if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
			continue;


		if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
			continue;
		//将设备id与handler的id进行匹配,成功直接返回ID
		if (!handler->match || handler->match(handler, dev))  
			return id;  
	}


	return NULL;
}
file:kernel-3.18/drivers/input/evdev.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",
	.id_table	= evdev_ids,
};
从上面input_handler的初始化可以看出handler->match为null,所以上面的input_match_device如果前面的条件
都满足的话直接返回ID。接下来在观察handler->connect即evdev_connect函数:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;    //定义一个evdev结构体指针


	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
	
	INIT_LIST_HEAD(&evdev->client_list);
	dev_no = minor;
	
	/* Normalize device number if it falls into legacy range */
	if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
		dev_no -= EVDEV_MINOR_BASE;
	dev_set_name(&evdev->dev, "event%d", dev_no);  //设置设备名为eventxx


	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	//这步最关键将handler赋值为evdev->handle.handler,完成input_dev和hander的挂接
	evdev->handle.handler = handler;  
	evdev->handle.private = evdev;


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


	error = input_register_handle(&evdev->handle);


	cdev_init(&evdev->cdev, &evdev_fops); //绑定file操作函数
	evdev->cdev.kobj.parent = &evdev->dev.kobj;
	error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);


	error = device_add(&evdev->dev);//注册一个设备到内核
	
	...
	return 0;
}
3.上报键值的实现
   在input子系统中上报键值一般使用input_report_xxx接口,先观察一个常用的input_report_key
file:kernel-3.18/drivers/input/input.h
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_KEY, code, !!value);
}
file:kernel-3.18/drivers/input/input.c
void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value)
{
	input_handle_event(dev, type, code, value);
}
static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
	int disposition;


	disposition = input_get_disposition(dev, type, code, &value);


	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);


	if (!dev->vals)
		return;


	if (disposition & INPUT_PASS_TO_HANDLERS) {
		struct input_value *v;


		if (disposition & INPUT_SLOT) {
			v = &dev->vals[dev->num_vals++];
			v->type = EV_ABS;
			v->code = ABS_MT_SLOT;
			v->value = dev->mt->slot;
		}


		v = &dev->vals[dev->num_vals++];
		v->type = type;
		v->code = code;
		v->value = value;
	}


	if (disposition & INPUT_FLUSH) {
		if (dev->num_vals >= 2)
			input_pass_values(dev, dev->vals, dev->num_vals);
		dev->num_vals = 0;
	} else if (dev->num_vals >= dev->max_vals - 2) {
		dev->vals[dev->num_vals++] = input_value_sync;
		input_pass_values(dev, dev->vals, dev->num_vals);
		dev->num_vals = 0;
	}


}
static void input_pass_values(struct input_dev *dev,
			      struct input_value *vals, unsigned int count)
{
	struct input_handle *handle;
	struct input_value *v;


	if (!count)
		return;


	rcu_read_lock();


	handle = rcu_dereference(dev->grab);
	if (handle) {
		count = input_to_handler(handle, vals, count);
	} else {
		list_for_each_entry_rcu(handle, &dev->h_list, d_node)
			if (handle->open)
				count = input_to_handler(handle, vals, count);
	}


	rcu_read_unlock();


	add_input_randomness(vals->type, vals->code, vals->value);


	/* trigger auto repeat for key events */
	for (v = vals; v != vals + count; v++) {
		if (v->type == EV_KEY && v->value != 2) {
			if (v->value)
				input_start_autorepeat(dev, v->code);
			else
				input_stop_autorepeat(dev);
		}
	}
}
static unsigned int input_to_handler(struct input_handle *handle,
			struct input_value *vals, unsigned int count)
{
	struct input_handler *handler = handle->handler;
	struct input_value *end = vals;
	struct input_value *v;


	for (v = vals; v != vals + count; v++) {
		if (handler->filter &&
		    handler->filter(handle, v->type, v->code, v->value))
			continue;
		if (end != v)
			*end = *v;
		end++;
	}


	count = end - vals;
	if (!count)
		return 0;


	if (handler->events)
		handler->events(handle, vals, count);
	else if (handler->event)
		for (v = vals; v != end; v++)
			handler->event(handle, v->type, v->code, v->value);


	return count;
}
file:kernel-3.18/drivers/input/evdev.c
static void evdev_event(struct input_handle *handle,
			unsigned int type, unsigned int code, int value)
{
	struct input_value vals[] = { { type, code, value } };


	evdev_events(handle, vals, 1);
}
static void evdev_pass_values(struct evdev_client *client,
			const struct input_value *vals, unsigned int count,
			ktime_t mono, ktime_t real)
{
	struct evdev *evdev = client->evdev;
	const struct input_value *v;
	struct input_event event;
	bool wakeup = false;


	if (client->revoked)
		return;


	event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ?
				      mono : real);


	/* Interrupts are disabled, just acquire the lock. */
	spin_lock(&client->buffer_lock);


	for (v = vals; v != vals + count; v++) {
		event.type = v->type;
		event.code = v->code;
		event.value = v->value;
		__pass_event(client, &event);
		if (v->type == EV_SYN && v->code == SYN_REPORT)
			wakeup = true;
	}


	spin_unlock(&client->buffer_lock);


	if (wakeup)
		wake_up_interruptible(&evdev->wait);
}
static void __pass_event(struct evdev_client *client,
			 const struct input_event *event)
{
	client->buffer[client->head++] = *event;
	client->head &= client->bufsize - 1;


	if (unlikely(client->head == client->tail)) {
		/*
		 * This effectively "drops" all unconsumed events, leaving
		 * EV_SYN/SYN_DROPPED plus the newest event in the queue.
		 */
		client->tail = (client->head - 2) & (client->bufsize - 1);
		
		//将需要上报的数据存放在buffer中
		client->buffer[client->tail].time = event->time;
		client->buffer[client->tail].type = EV_SYN;
		client->buffer[client->tail].code = SYN_DROPPED;
		client->buffer[client->tail].value = 0;


		client->packet_head = client->tail;
		if (client->use_wake_lock)
			wake_unlock(&client->wake_lock);
	}


	if (event->type == EV_SYN && event->code == SYN_REPORT) {
		client->packet_head = client->head;
		if (client->use_wake_lock)
			wake_lock(&client->wake_lock);
		kill_fasync(&client->fasync, SIGIO, POLL_IN);
	}
}
4.读取键值的实现
当上层进行read后,调用该设备的read函数:
static ssize_t evdev_read(struct file *file, char __user *buffer,
			  size_t count, loff_t *ppos)
{
	struct evdev_client *client = file->private_data;
	struct evdev *evdev = client->evdev;
	struct input_event event;
	size_t read = 0;
	int error;


	if (count != 0 && count < input_event_size())
		return -EINVAL;


	for (;;) {
		if (!evdev->exist || client->revoked)
			return -ENODEV;


		if (client->packet_head == client->tail &&
		    (file->f_flags & O_NONBLOCK))
			return -EAGAIN;


		/*
		 * count == 0 is special - no IO is done but we check
		 * for error conditions (see above).
		 */
		if (count == 0)
			break;


		while (read + input_event_size() <= count &&
		       evdev_fetch_next_event(client, &event)) {


			if (input_event_to_user(buffer + read, &event))
				return -EFAULT;


			read += input_event_size();
		}


		if (read)
			break;


		if (!(file->f_flags & O_NONBLOCK)) {
			error = wait_event_interruptible(evdev->wait,
					client->packet_head != client->tail ||
					!evdev->exist || client->revoked);
			if (error)
				return error;
		}
	}


	return read;
}
file:kernel-3.18/drivers/input/input-compat.c
int input_event_to_user(char __user *buffer,
			const struct input_event *event)
{
	if (INPUT_COMPAT_TEST && !COMPAT_USE_64BIT_TIME) {
		struct input_event_compat compat_event;


		compat_event.time.tv_sec = event->time.tv_sec;
		compat_event.time.tv_usec = event->time.tv_usec;
		compat_event.type = event->type;
		compat_event.code = event->code;
		compat_event.value = event->value;


		if (copy_to_user(buffer, &compat_event,
				 sizeof(struct input_event_compat)))
			return -EFAULT;


	} else {
		//最终使用copy_to_user将数据上传到用户空间
		if (copy_to_user(buffer, event, sizeof(struct input_event)))
			return -EFAULT;
	}


	return 0;
}
5.小结
通过上面4点分析,我们大致可以看出,输入子系统的本质就是利用input_dev、input_handler、input_handle
三个数据结构进行数据的转换和传递,其中input_dev用于数据的描述,input_handler用户事件的处理,input_handle
用于沟通input_dev、input_handler,当input_dev与input_handler挂接上了后,会创建相应的字符设备,并绑定相应的
读写方法,当底层有数据后,上报的数据存放在buffer数组中,当用户空间调用open 打开/dev/input/eventxx 然后调用
read 读取键值时,底层调用evdev_read并最终利用copy_to_user将内核空间数据传递到用户空间。












版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Android 6.0(M) BatteryService浅析及充电呼吸灯流程浅析 MTK

Android 6.0(M) BatteryService浅析及充电呼吸灯流程浅析

Elephone G9 4G Smartphone 4.5 inch Screen MTK6735M Quad Core

Elephone G9 4G Smartphone 4.5 inch Screen MTK6735M Quad Core 64bit Android 5.1 ...

MTK6735平台编译笔记:

切换到用户目录:cd / 切换到编译目录:cd home/yz/src/W1 修改用户权限:sudo su 设置编译环境:source build/envsetup.sh 选择编译模式:...

嵌入式Linux驱动学习笔记(六)------浅析input输入子系统框架

你好!这里是风筝的博客, 欢迎和我一起多多交流。 基于设备驱动分层的思想,其实理解了platform总线,输入子系统也是可以很好理解的。 以kernel  4.8.17为例: input...

浅析Linux Input 子系统

在android中,定时alarm功能是很常用的,现在来分析下怎么实现的,这里将采用自下而上的方式讲解。 输入子系统又叫input子系统。其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备...

Linux驱动之input子系统浅析

Linux驱动之input子系统浅析 分类: linux驱动 一,前言: 1, 近日要写一个很特殊的键盘驱动,故对linux的input子系统分析了一番,写下分析笔记,以防不日即...

Linux驱动之input子系统浅析(转载)

一,前言: 1, 近日要写一个很特殊的键盘驱动,故对linux的input子系统分析了一番,写下分析笔记,以防不日即忘。 2, 使用input子系统的一般流程为:input_allocate_devi...

linux内核input子系统解析

Android、X windows、qt等众多应用对于linux系统中键盘、鼠标、触摸屏等输入设备的支持都通过、或越来越倾向于标准的input输入子系统。     因为input子系统已经完...

linux内核input子系统解析

linux内核input子系统解析 Android、X windows、qt等众多应用对于linux系统中键盘、鼠标、触摸屏等输入设备的支持都通过、或越来越倾向于标准的input输入子系统。 因为...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)