linux-2.6.38 input子系统(简析)
阅读原文时间:2023年07月16日阅读:1

一、输入子系统简介

  引入输入子系统这种机制可以对不同的输入设备进行管理。各种输入设备如:鼠标、键盘、触摸屏等有一套相同的处理机制,输入子系统将其共性提取出来,

对于驱动开发人员只用实现其差异即可,实现其差异性即是完成各种设备的设备驱动程序。

  整个输入子系统有:设备驱动层、输入核心层、事件处理层三部分组成。这里借用别人的一张图 https://www.cnblogs.com/libra13179/p/10325058.html 来说明这三层的关系。

 驱动层:将硬件输入转化为统一的事件形式,向输入核心Input core 汇报

 输入核心层:为驱动层提供设备注册于操作函数,如:input_register_device;并通知事件处理层对事件进行处理;并在proc下产生相应的设备信息

 事件处理层:主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,

这些操作在输入子系统中由事件处理层完成)。

二、输入子系统代码分析

2.1  输入子系统核心层代码:

  2.1.1 输入子系统的入口函数:

static int __init input_init(void)
{
int err;

  err = class\_register(&input\_class);                        // 1. 注册一个类  /sys/class/input  
  if (err) {  
      pr\_err("unable to register input\_dev class\\n");  
      return err;  
  }

 err = input\_proc\_init();                                   // 2. 在proc目录下创建bus/input/devices 和handlers  
 if (err)  
     goto fail1;

 err = register\_chrdev(INPUT\_MAJOR, "input", &input\_fops);  // 3. 注册字符设备,主设备号是13 ,文件相关操作结构体input\_fops  
 if (err) {  
     pr\_err("unable to register char major %d", INPUT\_MAJOR);  
     goto fail2;  
 }

 return ;  

fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}

  由输入子系统的入口函数看,输入子系统其实也是一个字符设备。通过15行的字符设备注册函数可以看出输入设备的主设备号是13,输入设备文件操作的结构体是input_fops

  这就有点让人感到奇怪了为什么输入设备文件相关主要操作只有一个open,而没有read、write、poll、fasync 等操作呢? 只有一个open函数,那么当我打开随便打开一个输入设备的时候,肯定会调用这个open函数

static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
const struct file_operations *old_fops, *new_fops = NULL;
int err;

err = mutex\_lock\_interruptible(&input\_mutex);  
if (err)  
    return err;

/\* No load-on-demand here? \*/  
handler = input\_table\[iminor(inode) >> \];    // inminor(inode) >> 5 : 获取子设备号, 然后将子设备号除以32, 然后在input\_table 中找到相应的事件处理结构体input\_handler  
if (handler)  

                             // input_handler 中有关于文件操作的结构体fops, 得到input_handler中文件操作结构体
new_fops = fops_get(handler->fops);
mutex_unlock(&input_mutex);
if (!new_fops || !new_fops->open) {
fops_put(new_fops);
err = -ENODEV;
goto out;
}
old_fops = file->f_op;
file->f_op = new_fops; // 把当前文件操作的结构体换成input_handler中的文件操作结构体
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
out:
return err;
}

  从input_open_file函数中可以看出来,原来输入设备的文件操作结构体不是统一的input_fops,而是不同的输入设备有属于自己的文件操作方式,这种对应方式就是:

设备文件的子设备号--->input_handler结构体--->新的fops结构体--->替换旧的fops

这也就解决了为什么输入设备文件相关主要操作只有一个open,而没有read、write、poll、fasync 等操作,这些其他操作都有可能在一个对应的input_handler 中的一个fops结构体中。

现在看一下这个input_handler 到底是什么?

  这个结构体和input_dev 是一对成对的结构体。输入子系统分为:设备驱动层、核心层、事件处理层。我想这里input_handler对应的是事件处理层的结构体,input_dev 对应的是设备驱动层的结构体。

  2.1.2   input_register_handler 函数分析

   在2.1.1中说了是在nput_table[]数组中找到input_handler 结构体的,那么问题来了这个结构体是什么时候被放到input_table中的?

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 >> ]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> ] = handler; // 这里就是把一个input_handler 放到input_table中的,
}
list_add_tail(&handler->node, &input_handler_list); // 这里是链表操作,把handler 这个结构体挂在input_handler_list 这个连表上(input_handler_list应该是一个input_hanler 链表)
list_for_each_entry(dev, &input_dev_list, node) // 遍历input_dev_list这个链表,进行input_handler 和 input_device 的 attach 操作
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
out:
mutex_unlock(&input_mutex);
return retval;
}

  可以看到在注册一个input_handler结构体的时候,将这个input_handler放入到input_table中,并将这个input_handler结构体挂在input_handler_list 上,方便遍历操作。

同时这里还遍历了input_dev_list 链表里的每一个input_dev结构体,进行input_handler 和 input_dev 的attach 操作。

    input_attach_handler 函数分析

  input_handler 和 input_dev 的  attach 操作到底进行了什么操作?我们可以看一下 input_attach_handler这个函数。

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
id = input_match_device(handler, dev); // 进行匹配操作, 匹配成功返回一个id
if (!id)
return -ENODEV;

error = handler->connect(handler, dev, id); // 然后连接这个input\_handler 和 input\_dev  
if (error && error != -ENODEV)  
    pr\_err("failed to attach handler %s to device %s, error: %d\\n",  
           handler->name, kobject\_name(&dev->dev.kobj), error);

return error;  

}

  先看第一步:input_match_device(handler, dev); id匹配操作

static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
int i;

for (id = handler->id\_table; id->flags || id->driver\_info; id++) {  
            //  这四个if 判断的是id中bustype、vendor、product、version这四个标识匹配否, 注意 若是 id->flags =0  那么 下面的4 的判断就不同执行了  
            ......  
            //  接下来的match\_bit是看hander与 dev  id中的关于事件操作的一些位匹配   这些位代表什么含义在后序解释  
            ......  
            //  接下来执行handler->match 函数  
         if (!handler->match || handler->match(handler, dev))  
        return id;  
}

return NULL;  

}

   在看第二步:在执行为完 input_match_device之后,在input_attach_handler中执行一个重要的函数handler->connect ,这个函数建立了input_dev 和 input_handler 这两个结构体之间的桥梁,即把事件处理和设备驱动联系起来了。

要看handler->connect 这个函数具体干了什么,必须借助一个具体的handler,在evdev.c中中有一个事件处理器结构体:evdev_handler

可以看到在evdev_handler中函数evdev_connect,在第三节借助evdev.c 这个文件来分析这些涉及到事件处理的函数。

2.1.3  input_register_device 函数分析

  前边分析了注册事件处理器input_handler的函数,这里分析注册设备驱动的函数,这两个函数可以说是一对函数

int input_register_device(struct input_dev *dev)
{
…..// 这里分析重点函数

list\_add\_tail(&dev->node, &input\_dev\_list);  // 把input\_dev 挂到input\_dev\_list 链表中,方便遍历

list\_for\_each\_entry(handler, &input\_handler\_list, node)  
    input\_attach\_handler(dev, handler);    // 遍历input\_handler\_list 链表进行dev和handler的attach操作

.....  

}

  input_register_device 和 input_register_handler是非常对称的一对函数,都有链表操作,都有遍历链表attach的操作。这说明了,不管是先注册handler 还是先注册 device 都会进行input_attach_handler操作,在里边进行匹配、连接操作。

  2.4 input_event 函数

  该函数的分析需结合下一节《用输入子系统实现按键操作》来分析。

3  evdev.c 中相关的事件处理函数分析

  3.1  evdev_connect 函数

  这个函数是建立一个evdev_handler (事件处理器)和一个(input_dev)输入设备之间关系的桥梁。

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 = ; minor < EVDEV\_MINORS; minor++)    // EVDEV\_MINORS = 32 表示 在 evdev 事件下只能有 32 个输入设备和 evdev\_handler 建立连接,建立新的evdev设备  
      if (!evdev\_table\[minor\])                      // 找一个还没有用的次设备号  
         break;

 if (minor == EVDEV\_MINORS) {  
     pr\_err("no more free evdev devices\\n");  
     return -ENFILE;  
 }

 evdev = kzalloc(sizeof(struct evdev), GFP\_KERNEL); // 分配一个evdev 结构体  
 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);           // 设置evdev->dev 的名字  
 evdev->exist = true;  
 evdev->minor = minor;

 evdev->handle.dev = input\_get\_device(dev);            // evdev->handle 的初始化  
 evdev->handle.name = dev\_name(&evdev->dev);  
 evdev->handle.handler = handler;  
 evdev->handle.private = evdev;

 evdev->dev.devt = MKDEV(INPUT\_MAJOR, EVDEV\_MINOR\_BASE + minor);  //设置evdev的的主设备号和次设备号:主设备号13 表示一个输入设备、 次设备号64+minor  
 evdev->dev.class = &input\_class;  
 evdev->dev.parent = &dev->dev;  
 evdev->dev.release = evdev\_free;  
 device\_initialize(&evdev->dev);

 error = input\_register\_handle(&evdev->handle);                // 注册handle结构体   注意区分 input\_register\_handler  
 if (error)  
     goto err\_free\_evdev;

 error = evdev\_install\_chrdev(evdev);                          // 将新分配的evdev 放入到evdev\_table中  
 if (error)  
     goto err\_unregister\_handle;

 error = device\_add(&evdev->dev);                              // 添加设备  
 if (error)  
     goto err\_cleanup\_evdev;

 return ;

err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}

  (1)在每一次调用evdev_connect 函数的时候,都会创建一个新的evdev设备, 在第8~10 行就是找一个可以用的次设备号

  (2)input_register_handle函数分析:

int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list); // 把evdev->handle挂到input_dev->h_list链表中
else
list_add_tail_rcu(&handle->d_node, &dev->h_list);
mutex_unlock(&dev->mutex);
list_add_tail_rcu(&handle->h_node, &handler->h_list); // 把evdev->handle 挂到input_handler->h_list链表中
if (handler->start)
handler->start(handle);

return ;  

}

  这两个链表操作把要连接的input_dev和input_handler 通过 evdev->handle 串在了一起。

  3.2  evdev_read 函数分析

  在input_dev 和 evdev_handler建立了连接之后,若是要读取设备文件的数据,那么就会调用evdev_handler->fops.read 函数

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())  
    return -EINVAL;  
   // 在非阻塞情况下,环形队列中没有数据的时候,立即返回给应用程序-EAGAIN,提醒用户重新读取数据  
if (client->head == client->tail && evdev->exist &&  
    (file->f\_flags & O\_NONBLOCK))  
    return -EAGAIN;  
   //  若唤醒队列中没有数据或者evdev不存在时进入睡眠状态等待其他程序来唤醒  
retval = wait\_event\_interruptible(evdev->wait,  
    client->head != client->tail || !evdev->exist);  
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))  
        return -EFAULT;

    retval += input\_event\_size();  
}  
return retval;  

}

  这里是哪个程序来唤醒这个evdev_read函数的呢?有可能是中断,还有可能其他函数,这里可以看一下evdev_event函数, 这个函数可以唤醒evdev_read 函数。

  3.3 evdev_event 函数分析

  当设备驱动程序中上报事件时,若是该设备的input_dev和evdev_handler连接好了之后,就会调用evdev_event函数。这些东西在设备驱动编写过程中再分析调用流程。

static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event;

do\_gettimeofday(&event.time);  
event.type = type;  
event.code = code;  
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);      // 这里即可以唤醒evdev\_read函数  

}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章