Input子系统(二)【转】
转自:http://blog.chinaunix.net/uid-25047042-id-4192368.html
上一篇中粗略的分析了下input_dev,input_handle,input_handler这三者之间的关系,而在实际系统当中input子系统是如何工作的呢,当然我们知道,故事肯定是围绕着它们三个发生,下面我们来看看具体的input设备的工作流程。同样以触摸屏为例。
在触摸屏驱动中,当有触摸事件产生(手接触到触摸屏的时候),触摸屏相关IC会产生中断,在中断处理函数当中,kernel或者说tp driver会读取此次中断产生的数据(对于一个支持多点触摸的触摸屏来说就是每条触摸轨迹的坐标),driver将数据组织好,然后向input子系统report数据:
……
ret = gtp_i2c_read(ts->client, buf, 2 + 8 * (touch_num - 1));
memcpy(&point_data[12], &buf[2], 8 * (touch_num - 1));//从硬件读取数据
……
input_x = coor_data[pos + 1] | coor_data[pos + 2] << 8;
input_y = coor_data[pos + 3] | coor_data[pos + 4] << 8;
input_w = coor_data[pos + 5] | coor_data[pos + 6] << 8;//组织相关数据
……
input_mt_slot(ts->input_dev, id);
input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, id);
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);
input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w);
input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w);//向input子系统report数据
input_sync(ts->input_dev);//同步相关数据
……
Input子系统对于不同类型的事件有不同的处理方法,以多点触摸绝对坐标为例:
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
ts->input_dev是report数据的这个设备,在上一篇input设备初始化有提过,ABS_MT_POSITION_X表明report的这个事件是一个多点触摸的X轴绝对坐标事件,x就是从硬件中读取的某个触摸点的X轴也就是这次report的值了。
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
Input子系统用input_report_abs()封装了对绝对坐标类型事件,在input.h中我们可以看到,input子系统还封装了其他很多种类型事件接口,它们中间都调用了input_event接口。
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
if (is_event_supported(type, dev->evbit, EV_MAX)) {
/*
首先会判断该input设备是否具有report type类型事件的能力,只有设备支持report type类型事件,才会向input子系统report相关数据。而支持该种类型事件是在设备的初始化的时候做的,上一篇中也有提过,也就是:
ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;
在设备的probe当中会设置设备支持的处理的事件类型,在这里设备是支持处理坐标类型事件的能力的。
*/
spin_lock_irqsave(&dev->event_lock, flags);
add_input_randomness(type, code, value);
/*
将事件数据做为一个随机数产生熵,因为触摸事件可等同视为一个随机事件,人手触摸屏幕点是随机的,没有什么规定一定要总去触摸屏上某个位置
*/
input_handle_event(dev, type, code, value);//处理这次input事件
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
/*
在input_handle_event()当中,根据事件类型的不同,分别进行不同的处理,下面只选取了EV_ABS类型事件列出。
*/
{
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
……
case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX))
/*
这里同样还会做出一个判断,因为坐标类型事件同样分为很多种类型的坐标事件,我们这里要处理的是多点触摸的绝对坐标类型事件,如果设备不支持处理这种事件的话,那也不会向input子系统report事件。对于该类型事件是否支持的设置同样放在了设备初始化的时候:
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, ts->abs_x_max, 0, 0);
这里同样设置了该设备report的最大的x轴绝对坐标值。
*/
disposition = input_handle_abs_event(dev, code, &value);
/*
对于绝对坐标类型事件在很多时候是会非常频繁的产生,像触摸屏它的报点率一般会有50-60HZ,那么它产生的数据相对来说是非常大的,对于连续的重复的坐标,input子系统会进行过滤,只处理一次,以及可以过滤由于硬件noise产生的误数据。
*/
break;
……
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
dev->sync = false;
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);//事件处理成功,将事件传递给input_handler处理。
}
通过以上一系列接口的传递和处理,input event终于被传递给了input_handler。
static void input_pass_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
struct input_handler *handler;
struct input_handle *handle;
rcu_read_lock();
handle = rcu_dereference(dev->grab);
if (handle)
handle->handler->event(handle, type, code, value);
else {
bool filtered = false;
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
/*
在这里是不是看到了很眼熟的东西,在上一篇当中我们有说过,在注册Input_device的时候,会将input_handle通过d_node保存在input_dev的h_list上,在这里就是通过d_node从input_dev的h_list获取其对应的input_handle。
*/
if (!handle->open)
continue;
handler = handle->handler;//通过handle获取到对应的input_handler。
if (!handler->filter) {
if (filtered)
break;
handler->event(handle, type, code, value);//input_handler处理input事件。
} else if (handler->filter(handle, type, code, value))
filtered = true;
}
}
rcu_read_unlock();
}
到这里,我们就要进入input_handler了,input_handler调用其event接口对数据进行处理。
Input_handler
上一篇有说过,input_handler是数据的消耗者,负责kernel设备数据与上层应用的交接。Linux内核为我们提供了一个默认的input_handler,它支持处理所有的input设备,它就是evdev。相关代码在evdev.c当中。
在evdev.c当中为我们定义了一个input_handler:
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
其中event是事件处理接口,当input设备传递数据过来的时候,event负责处理。
Connect在上一篇有粗略分析过,当注册一个input_device或者input_handler的时候,它就会被调用,关联对应的input_handler或者input_device。
Disconnect断开input_device和input_handler时被调用。
Fops是input_handler提供的文件操作接口集合。在linux系统当中,所有的设备对上层来说都是文件,而这里定义了应用对设备文件所有的接口。
Minor是input_handler处理的设备的次设备号的起始设备号。
Name,很简单,该input_handler的name。
Id_table,定义了该input_handler支持处理的设备id,evdev支持所有类型的input_device设备类型。
Evdev.c模块加载的时候会注册当input_handler:
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
以下是注册input_handler内核的动作:
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval;
retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler;
}
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node) //在注册input_device的时候会将input_device挂载在input_dev_list链表上。
input_attach_handler(dev, handler);//关联input_dev和input_handler
input_wakeup_procfs_readers();
out:
mutex_unlock(&input_mutex);
return retval;
}
这里可以看到,当注册input_handler的时候,会关联内核当前已经存在的input_device,因此不用担心有注册在input_handler注册之前的input_device没有关联相关input_handler。
这里还需要介绍另外两个重要的数据结构:
struct evdev {
int open;
int minor;//次设备号
struct input_handle handle;//对应input_device关联的input_handle
wait_queue_head_t wait;//等待队列头
struct evdev_client __rcu *grab;
struct list_head client_list;
spinlock_t client_lock; /* protects client_list */
struct mutex mutex;
struct device dev;
bool exist;
};
Evdev是input_device eventn设备的定义,eventn设备是Input_device的一个子设备,在linux内核系统当中,/dev/input/目录下每个input_device都对应着一个eventn(n=0,1,2……)文件,而input_handler所处理的就是这个evdev设备。
struct evdev_client {
unsigned int head;
unsigned int tail;
unsigned int packet_head; /* [future] position of the first element of next packet */
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct wake_lock wake_lock;
char name[28];
struct fasync_struct *fasync;
struct evdev *evdev;//指向的evdev,该evdev_client处理的eventn对象
struct list_head node;
unsigned int bufsize;
struct input_event buffer[];//保存input_device传递过来的数据的buffer
};
Evdev_client是evdev提供处理数据的一个客户端,当有多个应用进程打开同一个eventn设备文件的时候,内核为每个应用进程提供一个event_client客户端,该event_client为对应进程负责数据处理传输。
我们知道在注册input_device的时候会匹配evdev,然后关联input_device和input_handler,即调用evdev的connect接口evdev_connect:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int error;
for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break;
/*
首先进行判断evdev_table数组是否还有剩余空间留给新的evdev,evdev_table数组当中的每一个元素都代表着一个evdev设备,系统最多只能存在32个evdev设备
*/
if (minor == EVDEV_MINORS) {
pr_err("no more free evdev devices\n");
return -ENFILE;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
dev_set_name(&evdev->dev, "event%d", minor);//通过次设备号设置设备文件名,可在/dev/input/目录下查看
evdev->exist = true;//设置evdev存在标志
evdev->minor = minor;//从evdev_table空闲空间中选取一个做为该evdev的次设备号。
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;//初始化evdev的input_handle结构
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);//通过input_device主设备号和evdev次设备号组合该设备的设备号
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;//将evdev设备父设备设置为input_device
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
error = evdev_install_chrdev(evdev);
/*
static int evdev_install_chrdev(struct evdev *evdev)
{
evdev_table[evdev->minor] = evdev;//将该evdev保存在evdev_table数组中,minor作为数组下标,因此,evdev_table的第minor个元素被占用了。
return 0;
}
*/
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev);//向系统添加一个设备
if (error)
goto err_cleanup_evdev;
return 0;
err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}
因此,当input_device和input_handler关联起来之后,便会在/dev/input/目录下生成一个evdev设备文件eventn(n=0,1,2,3……)。
而当上层应用需要获取input_device数据的时候就会打开eventn设备文件,就会调用static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev;
struct evdev_client *client;
int i = iminor(inode) - EVDEV_MINOR_BASE;//获取该设备文件的次设备号
unsigned int bufsize;
int error;
if (i >= EVDEV_MINORS)
return -ENODEV;
error = mutex_lock_interruptible(&evdev_table_mutex);
if (error)
return error;
evdev = evdev_table[i];//通过次设备号取得其evdev结构
if (evdev)
get_device(&evdev->dev);
mutex_unlock(&evdev_table_mutex);
if (!evdev)
return -ENODEV;
bufsize = evdev_compute_buffer_size(evdev->handle.dev);
client = kzalloc(sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event),
GFP_KERNEL);//分配一个evdev_client客户端
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
}
client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
snprintf(client->name, sizeof(client->name), "%s-%d",
dev_name(&evdev->dev), task_tgid_vnr(current));
wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
client->evdev = evdev;//初始化evdev_client相关结构
evdev_attach_client(evdev, client);//关联event_client和evdev,将event_client保存在evdev的client_list链表上。
error = evdev_open_device(evdev);//调用input_device open接口来做一些初始化工作。
if (error)
goto err_free_client;
file->private_data = client;//将evdev_client保存在file的私有域。
nonseekable_open(inode, file);
return 0;
err_free_client:
evdev_detach_client(evdev, client);
wake_lock_destroy(&client->wake_lock);
kfree(client);
err_put_evdev:
put_device(&evdev->dev);
return error;
}
以上准备工作做好了,再回到前面的input_device的数据被传递到input_handler,input_handler调用其event接口对数据处理,我们来看看evdev的event接口evdev_event:
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;//通过handle获取该input_device对应的evdev
struct evdev_client *client;
struct input_event event;
struct timespec ts;
ktime_get_ts(&ts);
event.time.tv_sec = ts.tv_sec;
event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
event.type = type;
event.code = code;
event.value = value;
/*
Evdev用一个input_event结构将传递过来的事件类型type,事件编码code,事件值value以及该事件产生的时间保存起来
*/
rcu_read_lock();
client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event);
else{
list_for_each_entry_rcu(client, &evdev->client_list, node)
/*
遍历evdev的client_list链表,将事件event传递给每一个evdev客户端去处理,这样所有打开该eventn设备文件的进程都将得到响应。
*/
evdev_pass_event(client, &event);
}
rcu_read_unlock();
if (type == EV_SYN && code == SYN_REPORT)
/*
接受到同步信号之后也就是input_sync(ts->input_dev)调用之后,系统唤醒等待队列,所有阻塞在该等待队列的进程得到响应,告诉它们现在有数据可以读取了。
进程通过poll系统调用,等待数据,直到input_device将数据传递过来且通过上面的evdev_pass_event将数据传递到第一个evdev客户端处理完之后:
static unsigned int evdev_poll(struct file *file, poll_table *wait)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
unsigned int mask;
poll_wait(file, &evdev->wait, wait);//进程阻塞,直到evdev_pass_event处理完之后被唤醒
mask = evdev->exist ? POLLOUT | POLLWRNORM : POLLHUP | POLLERR;
if (client->packet_head != client->tail)
mask |= POLLIN | POLLRDNORM;
return mask;
}
*/
wake_up_interruptible(&evdev->wait);//唤醒阻塞进程,进程返回后上层应用就能意识到数据已经准备好了,可以读取了。
}
static void evdev_pass_event(struct evdev_client *client,
struct input_event *event)
{
/* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock);
wake_lock_timeout(&client->wake_lock, 5 * HZ);
client->buffer[client->head++] = *event;//将event保存在evdev_client的buffer当中
client->head &= client->bufsize - 1;
if (unlikely(client->head == client->tail)) {
client->tail = (client->head - 2) & (client->bufsize - 1);
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 (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
kill_fasync(&client->fasync, SIGIO, POLL_IN);//接收到input_device的同步信号之后evdev_client发送同步信号。
}
spin_unlock(&client->buffer_lock);
}
return retval;
}
下面看看数据是怎么被读取的:
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;
int retval;
if (count < input_event_size())//如果要读取的数据量少于一个input_event的字长,说明读取参数有误
return -EINVAL;
if (client->packet_head == client->tail && evdev->exist &&
(file->f_flags & O_NONBLOCK))//如果客户端buffer没有input_event且evdev有效且以非阻塞方式读取数据,刚返回重新读取错误
return -EAGAIN;
retval = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail || !evdev->exist);//客户端evdev_client buffer有数据或者evdev无效,进程继续执行
if (retval)
return retval;
if (!evdev->exist)
return -ENODEV;
while (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + retval, &event))//每次向用户空间copy一个input_event,直到evdev_client buffer中所有input_event copy完毕
return -EFAULT;
retval += input_event_size();
}
return retval;
}
根据对evdev的分析,我们可知,当input_device report数据的时候,数据首先被组织成一个input_event事件,并记录该事件的详细时间,然后input_event会被传递给每一个打开该设备文件eventn的evdev_client客户端的buffer中保存,当input_device report type为EV_SYN且code为SYN_ERPORT数据的时候,说明在input_dev该次已全部完成数据的report,唤醒阻塞进程(应用进程通过调用poll()或者select()系统调用阻塞在evdev的evdev_poll()当中,检测是否存在数据可以读取),开始在evdev_read()中将各自event_client 的buffer中的input_event copy到用户空间,这样用户空间就能获取到input_device从硬件读取然后传递给input_handler的数据了。详细的上层应用是怎么读取input设备数据流程,且听下回分解。