十:evdev设备结点的open()操作
对主设备号为INPUT_MAJOR的设备节点进行操作,会将操作集转换成handler的操作集.在evdev中,这个操作集就是evdev_fops.对应的open函数如下示:
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev;
struct evdev_client *client;
int i = iminor(inode) - EVDEV_MINOR_BASE;
int error;
if (i >= EVDEV_MINORS)
return -ENODEV;
error = mutex_lock_interruptible(&evdev_table_mutex);
if (error)
return error;
evdev = evdev_table[i];
if (evdev)
get_device(&evdev->dev);
mutex_unlock(&evdev_table_mutex);
if (!evdev)
return -ENODEV;
client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
}
spin_lock_init(&client->buffer_lock);
client->evdev = evdev;
evdev_attach_client(evdev, client);
error = evdev_open_device(evdev);
if (error)
goto err_free_client;
file->private_da
ta = client;
return 0;
err_free_client:
evdev_detach_client(evdev, client);
kfree(client);
err_put_evdev:
put_device(&evdev->dev);
return error;
}
iminor(inode) - EVDEV_MINOR_BASE就得到了在evdev_table[ ]中的序号.然后将数组中对应的evdev取出.递增devdev中device的引用计数.
分配并初始化一个client.并将它和evdev关联起来: client->evdev指向它所表示的evdev. 将client挂到evdev->client_list上. 将client赋为file的私有区.
对应handle的打开是在此evdev_open_device()中完成的.代码如下:
static int evdev_open_device(struct evdev *evdev)
{
int retval;
retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval;
if (!evdev->exist)
retval = -ENODEV;
else if (!evdev->open++) {
retval = input_open_device(&evdev->handle);
if (retval)
evdev->open--;
}
mutex_unlock(&evdev->mutex);
return retval;
}
如果evdev是第一次打开,就会调用input_open_device()打开evdev对应的handle.跟踪一下这个函数:
int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
int retval;
retval = mutex_lock_interruptible(&dev->mutex);
if (retval)
return retval;
if (dev->going_away) {
retval = -ENODEV;
goto out;
}
handle->open++;
if (!dev->users++ && dev->open)
retval = dev->open(dev);
if (retval) {
dev->users--;
if (!--handle->open) {
/*
* Make sure we are not delivering any more events
* through this handle
*/
synchronize_rcu();
}
}
out:
mutex_unlock(&dev->mutex);
return retval;
}
在这个函数中可以看到.递增handle的打开计数.如果是第一次打开.则调用input device的open()函数
(如果已经赋值的话,但是我在akm8976.c中没有看到对input device的open函数指针赋值的地方,
所以这里有个问题:open一般是用来干嘛的? ).
十一:evdev的事件处理
经过上面的分析.每当input device上报一个事件时,会将其交给和它匹配的handler的event函数处理.在evdev中.这个event函数对应的代码为:
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int co
de, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event;
do_gettimeofday(&event.time);
event.type = type;
event.co
de = co
de;
event.value = 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_pass_event(client, &event);
rcu_read_unlock();
wake_up_interruptible(&evdev->wait);
}
首先构造一个struct input_event结构.并设备它的type.co
de,value为处理事件的相关属性.如果该设备被强制设置了handle.则调用与之对应的client.
在evdev_open的时候.会初始化clinet并将其链入到evdev->client_list. 这样,就可以通过evdev->client_list找到这个client.
对于找到的每一个client都会调用evdev_pass_event( ).代码如下:
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);
client->buffer[client->head++] = *event;
client->head &= EVDEV_BUFFER_SIZE - 1;
spin_unlock(&client->buffer_lock);
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
这里的操作很简单.就是将event保存到client->buffer中.client->head就是当前的数据位置.注意这里是一个环形缓存区.写数据是从client->head写.而读数据则是从client->tail中读.
十二:设备节点的read处理
对于evdev设备节点的read操作都会由evdev_read()完成.它的代码如下:
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_da
ta;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval;
if (count < evdev_event_size())
return -EINVAL;
if (client->head == client->tail && evdev->exist &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
if (retval)
return retval;
if (!evdev->exist)
return -ENODEV;
while (retval + evdev_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (evdev_event_to_user(buffer + retval, &event))
return -EFAULT;
retval += evdev_event_size();
}
return retval;
}
首先,它判断缓存区大小是否足够.在读取数据的情况下,可能当前缓存区内没有数据可读.在这里先睡眠等待缓存区中有数据.如果在睡眠的时候,.条件满足.是不会进行睡眠状态而直接返回的.
然后根据read()提够的缓存区大小.将client中的数据写入到用户空间的缓存区中.
十三:设备节点的写操作
同样.对设备节点的写操作是由evdev_write()完成的.代码如下:
static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_da
ta;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval;
retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval;
if (!evdev->exist) {
retval = -ENODEV;
goto out;
}
while (retval < count) {
if (evdev_event_from_user(buffer + retval, &event)) {
retval = -EFAULT;
goto out;
}
input_inject_event(&evdev->handle,
event.type, event.co
de, event.value);
retval += evdev_event_size();
}
out:
mutex_unlock(&evdev->mutex);
return retval;
}
首先取得操作设备文件所对应的evdev.
实际上,这里写入设备文件的是一个event结构的数组.在之前分析过,这个结构里包含了事件的type.co
de和event.
将写入设备的event数组取出.然后对每一项调用event_inject_event().
这个函数的操作和input_event()差不多.就是将第一个参数handle转换为输入设备结构.然后这个设备再产生一个事件.
代码如下:
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int co
de, int value)
{
struct input_dev *dev = handle->dev;
struct input_handle *grab;
unsigned long flags;
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
rcu_read_lock();
grab = rcu_dereference(dev->grab);
if (!grab || grab == handle)
input_handle_event(dev, type, co
de, value);
rcu_read_unlock();
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
这里可以跟input_event()对比一下,这里设备可以产生任意事件,而不需要和设备所支持的事件类型相匹配.
由此可见.对于写操作而言.就是让与设备文件相关的输入设备产生一个特定的事件.
将上述设备文件的操作过程以图的方式表示如下:
十四:小结
在这一节点,分析了整个input子系统的架构,各个环节的流程.最后还以evdev为例.将各个流程贯穿在一起.以加深对input子系统的 理解.由此也可以看出.linux设备驱动采用了分层的模式.从最下层的设备模型到设备,驱动,总线再到input子系统最后到input device.这样的分层结构使得最上层的驱动不必关心下层是怎么实现的.而下层驱动又为多种型号同样功能的驱动提供了一个统一的接口.