input子系统分析:源码1. kernel-3.18\drivers\input\input.c

本文详细分析了Linux内核input子系统的注册流程,包括input核心层、input handler层和input驱动层。input_init()初始化input类并在/proc下创建目录,注册主设备号。input_register_handler()将input handler添加到全局链表,并遍历input_dev_list匹配设备。input_register_device()将input设备加入链表,然后遍历input_handler_list进行匹配。以evdev为例,它能匹配任何input device。文章还介绍了等待队列的实现及应用程序如何调用open()操作input设备。
摘要由CSDN通过智能技术生成
/*
	input子系统分析:

	input.c
	向下对驱动层提供的接口有:
		input_allocate_device();			// 分配一个input device
		input_event()						// input_report_abs()内部调用input_event()
		input_register_device(tpd->dev);
		input_unregister_device(tpd->dev);

	向上对事件处理层提供的接口:
		input_register_handler()			// 注册一个input handler
		input_register_handle()				// 注册一个input_handle结构(包含匹配后的dev与handler匹配)

=============================================
input 核心层: input.c 【注册流程】

static LIST_HEAD(input_dev_list);					// 定义全局链表 : input device
static LIST_HEAD(input_handler_list);				// 定义全局链表 : input handler

input_init()									// subsys_initcall()
	|
	class_register(&input_class)				// 将class注册到内核中,同时创建/sys/class/下节点,类似class_create()
	input_proc_init()							// 没啥用
		|
		proc_bus_input_dir = proc_mkdir("bus/input", NULL);								// 创建文件夹"/proc/bus/input"
		entry = proc_create("devices", 0, proc_bus_input_dir, &input_devices_fileops);	// 创建节点"/proc/bus/input/devices"
		entry = proc_create("handlers", 0, proc_bus_input_dir, &input_handlers_fileops);// 创建节点"/proc/bus/input/handlers"
	register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input");		// 根据要求申请主设备号,主设备号INPUT_MAJOR == 13

=============================================
input handler层: evdev.c 【注册流程】

evdev_init()												// module_init()
	|
	input_register_handler(&evdev_handler)
		|
		INIT_LIST_HEAD(&handler->h_list);

		list_add_tail(&handler->node, &input_handler_list);	// 重要,把input handler挂到全局的链表input_handler_list上
		list_for_each_entry(dev, &input_dev_list, node) 	// 核心重点,遍历input_dev_list链表,链表中每一个input device均尝试与当前的input handler(evdev)匹配
			input_attach_handler(dev, handler);				// evdev可以与任何input device匹配,因为evdev的id_table[]为空
				|
				id = input_match_device(handler, dev);		// 根据evdev的id_table[]进行匹配 - 满足id_table[]中的全部条件才能匹配成功
				error = handler->connect(handler, dev, id);	// 匹配成功后调用handler中connect() -- .connect	= evdev_connect,
		input_wakeup_procfs_readers();						// 将当前的handler加入到/proc/bus/input/handlers文件中

总结:
	1. 注册了evdev_handler
	2. 遍历input_dev_list,进行行匹配,匹配成功,调用handler中connect方法--- evdev_connect()
	3. 内核有好几个input handler: evdev、mousedev、joydev、evbug等
	4. 其中 evdev 可以处理所有的事件,触摸屏驱动,sensor就是用的这个。				    

=============================================
input 驱动层: accel.c 【注册流程】

acc_input_init(struct acc_context *cxt)
{
	struct input_dev *dev;
	int err = 0;

	dev = input_allocate_device();
	dev->name = ACC_INPUTDEV_NAME;

	input_set_capability(dev, EV_ABS, EVENT_TYPE_ACCEL_X);
	input_set_capability(dev, EV_ABS, EVENT_TYPE_ACCEL_Y);
	input_set_capability(dev, EV_ABS, EVENT_TYPE_ACCEL_Z);
	input_set_capability(dev, EV_ABS, EVENT_TYPE_ACCEL_STATUS);
	input_set_capability(dev, EV_REL, EVENT_TYPE_ACCEL_UPDATE);
	input_set_capability(dev, EV_REL, EVENT_TYPE_ACCEL_TIMESTAMP_HI);
	input_set_capability(dev, EV_REL, EVENT_TYPE_ACCEL_TIMESTAMP_LO);

	input_set_abs_params(dev, EVENT_TYPE_ACCEL_X, ACC_VALUE_MIN, ACC_VALUE_MAX, 0, 0);
	input_set_abs_params(dev, EVENT_TYPE_ACCEL_Y, ACC_VALUE_MIN, ACC_VALUE_MAX, 0, 0);
	input_set_abs_params(dev, EVENT_TYPE_ACCEL_Z, ACC_VALUE_MIN, ACC_VALUE_MAX, 0, 0);
	input_set_abs_params(dev, EVENT_TYPE_ACCEL_STATUS, ACC_STATUS_MIN, ACC_STATUS_MAX, 0, 0);
	input_set_drvdata(dev, cxt);

	input_register_device(dev);
}

input_register_device(dev)
	|
	list_add_tail(&dev->node, &input_dev_list);				// 重要,把input device挂到全局的链表input_dev_list上 
	list_for_each_entry(handler, &input_handler_list, node) // 核心重点,遍历input_handler_list链表,链表中每一个input handler均尝试与当前的input device匹配
		input_attach_handler(dev, handler);					// evdev可以与任何input device匹配,因为evdev的id_table[]为空
			|
			id = input_match_device(handler, dev);			// 根据evdev的id_table[]进行匹配 - 满足id_table[]中的全部条件才能匹配成功
			error = handler->connect(handler, dev, id);		// 匹配成功后调用handler中connect() -- .connect	= evdev_connect,

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,
};

====
等待队列的实现:
1 wait_queue_head_t mywq_head;	
2 init_waitqueue_head(&mywq_head);
3 wait_event_interruptible( mywq_head,  fs210_btn_device->btn_state); 	// 条件不满足,就把调用进程挂起
4 wake_up_interruptible(&mywq_head); 									// 唤醒等待队列 - 等待队列需要显式唤醒
====
evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
	|
	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);	// 分配次设备号,找到一个尚未被使用的最小次设备号,从64开始,65,66
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);					// 实例化一个evdev对象
	INIT_LIST_HEAD(&evdev->client_list);		// 多个应用打开同一个input device时,每次open都生成一个clinet,挂载到client_list,数据上报时遍历链表,copy到所有成员的buffer中
	init_waitqueue_head(&evdev->wait);			// 等待队列用于完成阻塞,read()的时候,没数据(缓存队列头等于尾)就睡眠,唤醒条件为有数据(缓存队列头不等于尾),input_sync()显示唤醒
	
	dev_set_name(&evdev->dev, "event%d", dev_no);	// 创建设备文件/dev/input/event0/1/2  以下代码与device_create()一样
	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);

	evdev->handle.dev = input_get_device(dev);	// 利用handle记录input device和input handler(经过匹配后的)
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;				// 后面evdev_events(struct input_handle *handle,)根据handle 拿到evdev

	input_register_handle(&evdev->handle);
		|
		list_add_tail_rcu(&handle->d_node, &dev->h_list);		// 将handle与input device关联,互相可以找到
		list_add_tail_rcu(&handle->h_node, &handler->h_list);	// 将handle与input handler关联,互相可以找到

	cdev_init(&evdev->cdev, &evdev_fops);		// 初始化并注册字符设备cdev,完成fops,为用户提供文件io接口
	cdev_add(&evdev->cdev, evdev->dev.devt, 1);

总结:
	1. 分配evdev,并初始化,记录handle 与 input device、input handler的关系
	2. 创建设备节点/dev/input/event0/1/2
	3. 注册cdev,并实现fops
	4. 关系:
		多个input device可以对应一个input handler
		一个input device对应一个evdev,对应一个设备节点:/dev/input/event0/1/2
		一个input device可以被多个应用打开,每次打开生成一个clinet,挂载到evdev中的client_list链表
	5. 所有设备节点被调用open(),read(),write()文件io的时候
		实际上都是调用cdev中fops的各个接口:
		static const struct file_operations evdev_fops = {
			.owner		= THIS_MODULE,
			.read		= evdev_read,
			.write		= evdev_write,
			.poll		= evdev_poll,
			.open		= evdev_open,
			...
		};
	
	device_create()
		device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
			device_create_groups_vargs(class, parent, devt, drvdata, NULL, fmt, args);
				struct device *dev = NULL;
				dev = kzalloc(sizeof(*dev), GFP_KERNEL);
				device_initialize(dev);
				dev->devt = devt;
				dev->class = class;
				dev->parent = parent;
				dev->groups = groups;
				dev->release = device_create_release;
				dev_set_drvdata(dev, drvdata);
				retval = kobject_set_name_vargs(&dev->kobj, fmt, args);		// 设置名字
				retval = device_add(dev);	// 注册到系统 

==============================================
1. 应用程序调用open() 


以androidM Gsensor为例
hal层中acceleration.cpp中
FindDataFd()
	fd = open("/sys/class/misc/m_acc_misc/accdevnum", O_RDONLY);
	len = read(fd, buf, sizeof(buf)-1);
	buf[len] = '\0';            
	sscanf(buf, "%d\n", &num);
	sprintf(buf_s, "/dev/input/event%d", num);
	fd = open(buf_s, O_RDONLY);
	
readEvents()
	mInputReader.fill(mdata_fd)
	mInputReader.readEvent(&event)

即open("/dev/input/event%d", O_RDONLY);
-----------------------------------------
vfs
	sys_open();		// 系统调用
		struct file file->f_ops = cdev->ops;
		file->f_ops->open();
-----------------------------------------
input handler层: evdev.c
	cdev
	xxx_ops = {
		.open = xxx_open,
		.read = xxx_read,
	}

	
evdev_connect()	
	cdev_init(&evdev->cdev, &evdev_fops);


static const struct file_operations evdev_fops = {
	.owner		= THIS_MODULE,
	.read		= evdev_read,
	.write		= evdev_write,
	.poll		= evdev_poll,
	.open		= evdev_open,
};
实际上最终调用了evdev_open()

evdev_open(struct inode *inode, struct file *file)
	|
	struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);	// 以小博大,inode->i_cdev就是connect()中住的的cdev
	unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);	// 通过handle找到 input device,根据input device 获取缓冲区的大小(几个input event),但是我们驱动中未给定缓冲区大小,系统会自动给定一个
	unsigned int size = sizeof(struct evdev_client) + 						// size包含了很多个input event
						bufsize * sizeof(struct input_event);
	struct evdev_client *client;

	client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);	// 分配一个client对象,用来描述一个缓冲队列,存放的就是input_event

	client->bufsize = bufsize;			// client中有一个缓冲区
	spin_lock_init(&client->buffer_lock);
	
	client->evdev = evdev;				// evdev_client中记录evdev
	evdev_attach_client(evdev, client);	// 将client 加入到evdev中的一个小链表中
		|
		list_add_tail_rcu(&client->node, &evdev->client_list);

	file->private_data = client;		// evdev_client记录到file中,方便其他接口调用(这里是open(),其他接口还有read()、write())

总结:
	1. 为输入设备分配一个缓冲区evdev_client,用于存放input device层上报的数据
	2. evdev_client中记录evdev
	3. evdev_client记录到file中,方便其他read() write() 等接口使用

==============================================
2.应用程序调用read() 

read(fd, &event, sizeof(struct input_event));
-----------------------------------------
vfs
	sys_read();						// 系统调用
		file->f_ops->read();		// fd就是file数组的下表,通过传入的fd找到file,其中的f_ops在open()的时候已经获取并保存
-----------------------------------------

static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
	|
	struct evdev_client *client = file->private_data;	// 获取open() 分配的缓冲区
	struct evdev *evdev = client->evdev;			// 获取到evdev
	struct input_event event;						// 表示一个数据包,要给用户

	for (;;) {
													// 实现非阻塞 -- 队列为空,且为非阻塞模式,直接返回again
		if (client->packet_head == client->tail &&	// 队列的头跟尾位置一样 == 队列为空
		    (file->f_flags & O_NONBLOCK))			// 非阻塞
			return -EAGAIN;
			
															// while每循环一次取一个input event数据,read加1
		while (read + input_event_size() <= count &&		// 这里判断要取的数据个数是否已取ok,count是要取得数据个数
		       evdev_fetch_next_event(client, &event)) {	// 1. 从client的缓冲区取数据,放到event中
					|
					*event = client->buffer[client->tail++];// 将client->buffer[]队列的尾巴给*event
			   	
			if (input_event_to_user(buffer + read, &event))	// 2. 把数据给用户空间
					|
					copy_from_user(event, buffer, sizeof(struct input_event))

			read += input_event_size();						// 3. 统计上报多少数据
		}

		if (!(file->f_flags & O_NONBLOCK)) {				// 如果当前不是非阻塞模式,即阻塞模式
			error = wait_event_interruptible(evdev->wait,	// 休眠 - 条件不满足就睡眠:
					client->packet_head != client->tail ||	// 队列头不等于尾 -> 有数据
					!evdev->exist || client->revoked);
总结:
	1. 如果没数据,就休眠等待
	2. 如果有数据,就会从缓冲区client->buffer[client->tail++]拿数据,通过copy_to_user上报给用户

疑问:
	1. 数据到底是如何存放在缓冲区的
	2. 等待队列是谁唤醒的
==============================================
3. 上报流程: 

input_report_abs(gt811_dev->input, ABS_MT_POSITION_X, x);
input_report_abs(gt811_dev->input, ABS_MT_POSITION_Y, y);
input_mt_sync(gt811_dev->input);

input_report_abs(struct input_dev *dev, unsigned int code, int value)
	|
	input_event(dev, EV_ABS, code, value);
		|
		input_handle_event(dev, type, code, value);
			|
			if (disposition & INPUT_PASS_TO_HANDLERS) { // input device数据交给input handler处理
				struct input_value *v;
				v = &dev->vals[dev->num_vals++];		// 将input device获取到的数据暂存到dev->vals
				v->type = type;
				v->code = code;
				v->value = value;
				input_pass_values(dev, dev->vals, dev->num_vals);
					|
					list_for_each_entry_rcu(handle, &dev->h_list, d_node)		// 通过inpit device中与handle建立连接的 h_list 成员找到 handle
					if (handle->open)
						input_to_handler(handle, vals, count);
							|
							struct input_handler *handler = handle->handler;	// 通过出入的handle找到input handler(这里是evdev)
							if (handler->events)								// 首选events(), 没有才调用event()
								handler->events(handle, vals, count);			// 调用events() 
							else if (handler->event)
								for (v = vals; v != end; v++)
									handler->event(handle, v->type, v->code, v->value);

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,
};

static void evdev_events(struct input_handle *handle, const struct input_value *vals, unsigned int count)
	|
	struct evdev *evdev = handle->private;	// 从handle中拿到evdev -- connect()中保存了:evdev->handle.private = evdev;
	struct evdev_client *client;

		如果多个应用进程打开了同一个input device, 每次open()都会生成一个evdev_client
		evdev_client挂载到evdev的client_list链表中
		input_report_abs()时,handler会把数据copy到client_list所有的evdev_client的buffer中
		input_mt_sync(),逐一唤醒
	list_for_each_entry_rcu(client, &evdev->client_list, node)
		evdev_pass_values(client, vals, count, time_mono, time_real);
			|
			struct evdev *evdev = client->evdev;		// 通过client 获取到 evdev
			const struct input_value *v;
			struct input_event event;					// 数据包

			event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ? mono : real);	// 填充数据包中的时间戳
			for (v = vals; v != vals + count; v++) {	// 将input device上报的数据封装成 input_event对象
				event.type = v->type;
				event.code = v->code;
				event.value = v->value;
				__pass_event(client, &event);			// 将input event数据放在缓冲区的头部 -- 读的时候从尾巴开始读
					|
					client->buffer[client->head++] = *event;	// 将input event数据放入缓冲区
					client->head &= client->bufsize - 1;

				if (v->type == EV_SYN && v->code == SYN_REPORT)	// 唤醒等待队列 -- 如果调用了input_sync() --  input_event(dev, EV_SYN, SYN_REPORT, 0);
					wakeup = true;
			}
			if (wakeup)									// 唤醒等待队列
				wake_up_interruptible(&evdev->wait);

struct input_event {
	struct timeval time;
	__u16 type;		// 如:EV_ABS
	__u16 code;		// 如:ABS_MT_POSITION_X
	__s32 value;	// 如:x (具体的数值,这里是tp横坐标)
};

总结:
	1. 数据到底是如何存放在缓冲区的
		input_report_abs()将数据交给handler,调用events(),将数据放入缓冲区client->buffer[client->head++] = *event;
	2. 等待队列是谁唤醒的
		input_mt_sync() 显式唤醒等待队列 wake_up_interruptible(&evdev->wait);
*/


/*
	字符设备注册流程
	
    register_chrdev_region(MKDEV(major, 0), 1, "hello");  
    cdev_init(&hello_cdev, &hello_fops);  
    cdev_add(&hello_cdev, devid, 1);  

    cls = class_create(THIS_MODULE, "hello");  	// 等同class_register()
    dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "hello")
*/

#define pr_fmt(fmt) KBUILD_BASENAME ": " fmt

#include <linux/init.h>
#include <linux/types.h>
#include <linux/idr.h>
#include <linux/input/mt.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/major.h>
#
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值