Linux设备模型之input子系统详解(三)

十: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.这样的分层结构使得最上层的驱动不必关心下层是怎么实现的.而下层驱动又为多种型号同样功能的驱动提供了一个统一的接口.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值