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设备数据流程,且听下回分解。

上一篇:Linux:- comm命令的妙用


下一篇:Snort规则中的逻辑关系