Linux Input子系统之第一篇(input_dev/input_handle/input_handler)

本文深入探讨Linux Input子系统的核心组件与交互关系,详细解析input_dev、input_handler与input_handle之间的多对多关系,并以evdev_handler为例展示系统如何通过Observer模式实现事件监听与数据处理的高效联动。文章旨在揭示Input子系统在设备驱动与数据处理方面的巧妙设计,为开发者提供深入理解与实践指导。
摘要由CSDN通过智能技术生成

原文:Linux Input子系统之第一篇(input_dev/input_handle/input_handler)   作者:force_with_you

Input子系统是linux kernel中与部分外围器件驱动联系比较紧密的模块,常用于Sensor,TP(touch panel),power key等器件的驱动。这类模块有个共同特点:字符设备,且数据量都不大,比如sensor一般最多只有xyz三个维度的数据。
      整体来看,Input子系统有一个主线,那就是题目中这三个结构体的关系(下面简称为三方关系),input_dev对应于实际的device端,input_handler从名字也可以猜出来是对device的处理。“处理”这个词语不单单指的是对device数据的处理,比如report等;它其实可以包括系统在该device事件发生时想做的任何动作。至于input_handle,它是连接input_dev与input_handler的,该设计后面也会详细分析。在这里请记住,我们最终的目的是,通过input_dev,可以遍历所有与它有关的input_handler;通过input_handler,也可以遍历所有与它有关的input_dev。
       为了更加透彻地讲述Input子系统,本博文将分两篇介绍,第一篇就来分析上面这个主线,第二篇分析Input子系统的A/B两个协议(B协议又称为Slot Protocol)。下面从input_device说起。
      驱动端通过input_allocate_device来allocate对应的input_dev结构体,之后持有该指针,并完成对应的初始化(name, set_bit等等)。

点击(此处)折叠或打开

  1. struct input_dev *input_allocate_device(void)
  2. {
  3.     struct input_dev *dev;
  4.     dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
  5.     if (dev) {
  6.         ...//I deleted many lines here.
  7.         INIT_LIST_HEAD(&dev->h_list);//this is the head of the list which consists of related input_handles.
  8.         ...
  9.     }
  10.     return dev;
  11. }
        结合这段代码,看下input_dev结构体(这里只关心主线相关的结构体成员)

点击(此处)折叠或打开

  1. struct input_dev {
  2.     ...//detailed info of this device
  3.     struct list_head         h_list;//这是与input_dev相关联的input_handle的链表的表头
  4.     struct list_head node;//链入全局链表
  5. };
        驱动完成初始化后,调用input_register_device来注册已经初始化的input_dev,这个函数是三方关系的核心,它调用了input_attach_handler,而恰恰是在input_attach_handler这个函数内dev, handler和handle这三者确定了关系。看下这个函数究竟做了哪些关键的事情,

点击(此处)折叠或打开

  1. int input_register_device(struct input_dev *dev)
  2. {
  3.     static atomic_t input_no = ATOMIC_INIT(0);
  4.     struct input_handler *handler;
  5.     ...//此次省略一千行O(∩_∩)O~
  6.     dev_set_name(&dev->dev, "input%ld",
  7.          (unsigned long) atomic_inc_return(&input_no) - 1);//set the dev name here: input0,input1,...    
  8.     list_add_tail(&dev->node, &input_dev_list);//input_dev_list is a global list! So every input_dev will be listed.
  9.     list_for_each_entry(handler, &input_handler_list, node)
  10.         input_attach_handler(dev, handler);//dev and handler, we fould it.
  11.     ...//同上    
  12.     return 0;
  13. }
        上面的代码,对dev做进一步的设置,同时也将dev链接到全局链表中;但它最重要的功能还是体现在11、12行。
        到了这里不得介绍下input_handler_list与input_handler,它是系统中所有handler挂载的链表的表头,自定义的handler必须挂载到该链表才有可能被系统所用(调用input_register_handler)。input_handler的作用上面简单提了,定义如下

点击(此处)折叠或打开

  1. struct input_handler {
  2.     ...
  3.     void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//important
  4.     bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
  5.     bool (*match)(struct input_handler *handler, struct input_dev *dev);
  6.     int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
  7.     void (*disconnect)(struct input_handle *handle);
  8.     void (*start)(struct input_handle *handle);
  9.     ...
  10.     const struct input_device_id *id_table;
  11.     struct list_head    h_list;//这是与input_handler相关联的input_handle的链表的表头
  12.     struct list_head    node;//链入全局链表   h_list node~是否注意到这与input_dev的最后两个一模一样呢,事实上他们名字与作用都一样
  13. };
        完成了必要的背景介绍,继续input_attach_handler,这个函数的逻辑与具体的handler是强相关的,下面就以input子系统默认的evdev_handler为例进行分析。

点击(此处)折叠或打开

  1. static struct input_handler evdev_handler = {
  2.     .event        = evdev_event,
  3.     .connect    = evdev_connect,
  4.     .disconnect    = evdev_disconnect,
  5.     .fops        = &evdev_fops,
  6.     .minor        = EVDEV_MINOR_BASE,
  7.     .name        = "evdev",
  8.     .id_table    = evdev_ids,
  9. };

点击(此处)折叠或打开

  1. static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
  2. {
  3.     const struct input_device_id *id;
  4.     int error;
  5.     id = input_match_device(handler, dev);//判断handler是否与dev match.通过handler的id_table、match等实现,比较简单,不做展开。
  6.     if (!id)
  7.         return -ENODEV;
  8.     error = handler->connect(handler, dev, id);//这之前dev与handler还是彼此独立的,connect直接产生我们关注的三方关系
  9.     if (error && error != -ENODEV)
  10.         pr_err("failed to attach handler %s to device %s, error: %d\n",
  11.                handler->name, kobject_name(&dev->dev.kobj), error);
  12.     return error;
  13. }
        看看evdev_handler的connect究竟为这个三方关系做了什么吧,

点击(此处)折叠或打开

  1. static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
  2.              const struct input_device_id *id)
  3. {
  4.     struct evdev *evdev;
  5.     ...
  6.     evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
  7.     if (!evdev)
  8.         return -ENOMEM;
  9.     ...
  10.     evdev->handle.dev = input_get_device(dev);//这里的handle就是input_handle,它的dev成员指向input_dev
  11.     evdev->handle.name = dev_name(&evdev->dev);
  12.     evdev->handle.handler = handler;//它的handler成员指向input_handler
  13.     evdev->handle.private = evdev;
  14.     ...
  15.     error = input_register_handle(&evdev->handle);//10、12行完成了handle到dev和handler,这个函数完善了所有的三方关系
  16.     return 0;
  17.     ...//error handling
  18. }

点击(此处)折叠或打开

  1. struct input_handle {
  2.     ...
  3.     struct input_dev *dev;//上段代码的第10行
  4.     struct input_handler *handler;//上段代码的第12行
  5.     struct list_head    d_node;//链入input_dev的h_list代表的链表
  6.     struct list_head    h_node;//链入input_handler的h_list代表的链表
  7. };

点击(此处)折叠或打开

  1. int input_register_handle(struct input_handle *handle)
  2. {
  3.     struct input_handler *handler = handle->handler;
  4.     struct input_dev *dev = handle->dev;
  5.     ...
  6.     if (handler->filter)
  7.         list_add_rcu(&handle->d_node, &dev->h_list);//上段代码的第5行的注释
  8.     else
  9.         list_add_tail_rcu(&handle->d_node, &dev->h_list);
  10.     ...
  11.     list_add_tail_rcu(&handle->h_node, &handler->h_list);//上段代码的第6行的注释
  12.     ...
  13.     return 0;
  14. }
        至此,三方关系形成完毕。我们实现了最终的目的,通过input_dev,可以遍历所有与它有关的input_handler;通过input_handler,也可以遍历所有与它有关的input_dev。
        图解如下:图中单向箭头表示指针,双向箭头表示list_head。可以看出,从任何一个双向箭头出发,通过handle的过度,完全实现了我们的最终目标。掌握了这点,再看input_report那些流程的时候就非常容易了,dev想要report数据的时候无非是调用了handler的event函数指针指向的函数,我们可以在这个函数里定义任何想让系统去做的任务,比如cpu调频等,而不仅限于数据上报。熟悉面向对象编程的人可能想到了,其实这个设计运用了面向对象的observer设计模式。
 
        至此,本文主要内容完结。掌握了这些知识固然重要,但还需要简单分析下这个三方关系的设计思路。
        从本质上讲,input_dev与input_handler是一个多对多的关系,一个dev可以对应多个handler,一个handler也可以对应多个dev(参考上图)。
       针对这种多对多的关系,也许有人会想,为什么不将input_handle的dnode,hnode 分别内嵌到dev和handler内,这样也可以节省空间;实际上,内嵌的方式最终实现的是混乱的一对多的关系,因为指针的指向是唯一的,所以当两个不同的dev有一个共同的handler的时候,两个链表相交,那么后注册的dev会改变前一个dev的链表,导致混乱。
        实际上,input_handle可以拆成两部分,dnode一部分,hnode一部分,dnode来表达一个dev可以对应多个handler,hnode来表达一个handler也可以对应多个dev。这两部分独立存在也一样可以实现input子系统的功能,而且理解起来更加简单;将他们合并起来节省了空间。
        希望本文能让看了它的人,遇到多对多模型的类似问题时,能够记起这个三方关系。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值