zoukankan      html  css  js  c++  java
  • Linux input子系统简介

    1.前言

    本文主要对Linux下的input子系统进行介绍

    2. 软件架构

    图 input子系统结构图

    input子系统主要包括三个部分:设备驱动层、核心层和事件层。我们可以分别理解为:具体的输入设备、过度设备和逻辑设备。对于用户空间来说与之直接交互的只有逻辑设备也就是事件层。

    Input子系统主要包含两条路径(主要讲述第一条路径):

    • 第一条路径

    设备驱动层捕获事件并向核心层报告->核心层将事件交由事件层处理->用户空间读取事件层处理的数据

    • 第二条路径

    用户空间写入事件数据->事件层生成事件->调用核心层传递事件信息给驱动层->设备驱动层驱动硬件设备

     3.系统组件之间的关联

    图 input_dev  input_handle  input_handler之间的关联

    通常input_dev、 input_handle、input_handler三者的关系如上图所示。

    • input_dev

    input_dev通过node链接进全局链表input_dev_list中;通过h_list将与其关联的所有input_handle链接起来,这说明每个input_dev可能会有多个handle与其连接

    • input_handler

    input_handler通过node节点链接进全局链表input_handler_list中;通过h_list将与其关联的所有input_handle链接起来,这说明每个input_handler可能会有多个handle与其连接

     4. 系统组件

    4.1 input_handle

    Elemete Name input_handle
    Path include/linux/input.h
    Responsiblities

    用于连接input_dev和input_handler,由input_handler创建

    Attributions
    • private:void *类型,存放input handle的特有数据
    • open:open用来计数,指示这个handle是否被打开了
    • name:const char *类型,handler的名字,由handler在创建handle的时候指定
    • dev:struct input_dev *类型,与此handle关联的input_dev
    • handler:struct input_handler *类型,handler通过handle与上面的input_dev一起work
    • d_node:struct list_head类型,可以把此节点链到input_dev的h_list链表上,这说明一个input_dev可以与多个handle链接
    • h_node:struct list_head类型,可以将此节点链接到input_handler的h_list链表上,这说明一个input_handler可以与多个handle链接
    Operations

    4.2 input_dev

    Elemete Name input_dev
    Path include/linux/input.h
    Responsiblities

    代表一个input设备,如按键设备、触摸屏设备等

    Attributions
    • name:const char *类型,input设备的名字
    • phys:const char *类型,/sys/目录下的文件节点名,如/sys/class/input/event0
    • uniq:const char *类型,设备的唯一标识码,需要设备支持
    • id:struct input_id类型,设备的ID,包括生产商,产品好,版本号
    • propbit:
    • evbit:设备支持哪些事件
    • keybit:这个设备支持哪些按键和button
    • relbit:设备的相对坐标,针对鼠标设备?
    • absbit:针对触摸屏设备?
    • mscbit:设备支持的杂项事件
    • ledbit:设备上的LED
    • sndbit:设备的声音支持相关,如蜂鸣器等
    • ffbit:设备所支持的强反馈事件,如马达等
    • swbit:设备是否支持开关,如iphone上的静音开关
    • hint_events_per_packet:设备打包事件暗含的数目,一般发生在EV_SYN和SYN_REPORT之间的事件数目,input_handler需要据此预估buffer大小来存放事件
    • keycodemax:按键码表的大小,用来存放按键码,实际的按键码个数可能小于此
    • keycodesize:实际上存放在按键码表中的按键码数目
    • keycode:void *类型,扫描码到按键码的映射,对ADC按键扫描码可以理解为某个按键的ADC值,按键码有音量加,音量减等
    • setkeycode:函数指针
    • getkeycode:函数指针
    •  ff:struct ff_device *类型
    • repeat_key:保存了上次按下的按键,用于软件自动重发
    • timer:struct timer_list 类型,软件重发的定时器,包括按键按下多长时间启动自动重发功能,每隔多长时间重发一次
    • rep:数组类型,用于自动重发的参数值保存
    • mt:struct input_mt *类型
    • absinfo: struct input_absinfo *类型
    • key:数组,反应当前key/button设备的状态,如按下还是抬起
    • led:数组,反映当前led的状态
    • snd:数组,反映snd的状态
    • sw:数组,反映开关的状态
    • open:函数指针,这个方法由第一个用户在调用input_open_device时被调用,驱动中此时需要已经开启 poll线程,并可以上报事件,其它用户调用时将只把user计数加1
    • close:函数指针,最后一个用户调用input_close_device时将调用此函数,驱动需要实现做一个善后工作
    • flush:函数指针
    • event:函数指针,主要用于input_handler接收用户层的操作事件,对input_dev进行操作,如对led和ff设备的操作
    • grab:struct input_handle __rcu *类型,一旦grab不为空,则说明此input_handle将成为此input_dev的唯一事件处理者
    • event_lock:spinlock_t类型,用于input core接收或处理此input_dev的事件
    • mutex:struct mutex 类型,用于保证open、close、flush方法的串行执行
    • users:input_handler执行input_open_device的次数
    • going_away:标示input_dev在执行unregister的过程中,此时执行input_open_device将失败
    • dev:struct device类型,设备驱动模型device
    • h_list:struct list_head类型,用于链接此input_dev的所有input_handle
    • node:struct list_head类型,用于将此input_dev加入到全局input_dev_list中
    • num_vals:当前队列中有多少个frame在排队,每个frame可以理解成EV_SYN和SYN_REPORT之间的事件
    • max_vals:一个frame中可以包含的最大事件个数???
    • vals:struct input_value *类型,入队到当前队列的数组????
    • devres_managed:指示设备资源是否由设备资源框架进行管理
    Operations

    4.3 input_handler

    Elemete Name input_handler
    Path include/linux/input.h
    Responsiblities

    input_dev的逻辑表示,用于和用户空间交互,为input_dev实现一堆接口

    Attributions
    • private:void *类型,私有数据
    • event:函数指针,event handler,事件处理主函数。会持有dev->event_lock spinlock,并关中断,因此不能进休眠
    • events:函数指针,事件顺序处理,同上不能睡眠
    • filter:函数指针
    • match:函数指针,对input_dev->id与input_handler->id_table进行匹配
    • connect:函数指针,当需要将一个input_device与一个input_handler进行连接的时候调用,调用的过程中会创建input_handle
    • disconnect:从一个input_dev上断开与一个input_handler的连接
    • start:对于给定的handle start handlers,这个函数一般在connect被成功执行后进行调用
    • legacy_minors:是否使用遗留的minors范围
    • minor:遗留的minors范围的开始次设备号
    • name:const char *指针,handler的名字,在/proc/bus/input/handlers显示
    • id_table:const struct input_device_id *类型,id_table保存input_dev ids,指示这个handler可以处理哪些input_devices
    • h_list:struct list_head类型,与此input_handler关联的input_handle将挂到h_list链表
    • node:struct list_head类型,通过node节点将此input_handler连接到全局input_handler_list链表
    Operations

    4.4 evdev

    Elemete Name evdev
    Path drivers/input/evdev.c
    Responsiblities

    Evdev是最上层代表 input设备的结构体,它代表一个字符设备,用户可以open ,close等等fop操作

    Attributions
    • open:设备被打开的次数
    • handle:struct input_handle类型,此handle用于联系此handler和input device
    • wait:wait_queue_head_t类型,等待队列,用于处理上报事件
    • grab:struct evdev_client __rcu *类型
    • client_list:struct list_head类型 
    • client_lock:spinlock_t类型,protects client_list 
    • mutex:struct mutex类型
    • dev:struct device 类型,evdev是代表设备,因此需要注册进设备驱动模型
    • cdev:struct cdev类型,此处表示input dev是一个字符设备,用户可以通过fops对其进行操作
    • exist:
    Operations

    4.5 evdev_client

    Elemete Name evdev_client
    Path drivers/input/evdev.c
    Responsiblities

    Evdev_client代表打开evdev设备的用户,用来管理input event buffer

    Attributions
    • head:
    • tail:
    • packet_head:[future] position of the first element of next packet
    • buffer_lock:spinlock_t类型,protects access to buffer, head and tail
    • wake_lock:struct wake_lock类型
    • use_wake_lock:是否使用wake_lock
    • name[28]:
    • fasync:struct fasync_struct *类型
    • evdev:struct evdev *类型
    • node: struct list_head类型,用于与evdev链接,一个evdev可以有多个evdev_client
    • clkid:
    • bufsize:
    • buffer[]:struct input_event 类型
    Operations

    5.关键流程

    5.1 input_init

    input_init->

           class_register(&input_class)

           input_proc_init()

           register_chrdev_region->

                  __register_chrdev_region

    主要完成了input子系统的注册(通过注册subsys->kobject),同时创建字符设备char_device_struct,并将指针插入到全局哈希表chrdevs中

    • class_register
    class_register实际上是分配struct subsys_private并放到全局的kobject层级结构中。
    我们知道内核中任何一个设备或类的实体的元模型就是kobject,任何一个设备或类都将其kobject放入到全局kobject层级结构中。
    而kset可以理解为是kobject的容器,一个子系统与一个kset是对应的。
    subsys_private是对kset的进一步封装,其中的成员变量struct kset subsys就代表子系统,
    class_register实际完成的工作就是将subsys->kobject放入到全局的kobject层级结构中,从而完成了子系统的注册。
    •   input_proc_init
    创建/proc/bus/input/devices和/proc/bus/input/handlers两个目录。
    
      cat /proc/bus/input/devices可以看到当前系统注册了哪些input设备;
    
      cat /proc/bus/input/handlers可以看到当前系统注册了哪些handlers
    • register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input")
    主要用来分配次设备号。从主设备号INPUT_MAJOR(13),次设备号0开始,注册INPUT_MAX_CHAR_DEVICES(1024)个设备,所有设备有一个共同的名字”input”。
    核心是调用__register_chrdev_region,后者会对每一个字符设备分配一个char_device_struct结构体,并将结构体指针插入到全局chrdevs哈希表中。

     5.2  input_register_device

    input_register_device->

           devres_alloc

           __set_bit(EV_SYN, dev->evbit)

           __clear_bit(KEY_RESERVED, dev->keybit)

           input_cleanse_bitmasks(dev)

           input_estimate_events_per_packet

           init_timer(&dev->timer)

           dev_set_name

           device_add

           kobject_get_path

           list_add_tail

           input_attach_handler->

                 input_match_device

           input_wakeup_procfs_readers

    input_register_device首先设置了相关的属性支持事件等,然后调用device_add将input_dev注册进设备驱动模型,同时连接进全局链表input_dev_list中,注册过程中会匹配handler,调用handler->connect,实际上handle的创建就是在connect 中完成的,后文会提到。

    • devres_alloc
    如果采用设备资源框架来分配则调用此函数分配input_dev,用此种方法分配的input_dev无需ungegister 或 free;
    • __set_bit(EV_SYN, dev->evbit)
    每个设备都支持EV_SYN事件;
    • __clear_bit(KEY_RESERVED, dev->keybit)
    KEY_RESERVED不支持发送给用户空间;
    • input_cleanse_bitmasks(dev)
    确保除了设置显示支持的事件以外,其它事件都被清空;
    • input_estimate_events_per_packet
    估算每个packet包含的事件长度,主要鼠标和触摸屏用到;

    dev->max_vals = max(dev->hint_events_per_packet, packet_size) + 2;

    dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);

    • init_timer(&dev->timer)
    初始化自动重复发送定时器,定时器处理函数为input_repeat_key,默认按下超过250ms开启自动开启重复发送按键,每33ms发送一次;
    • dev_set_name(&dev->dev, "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
    设置input设备的名字
    • device_add
    这个是 input设备注册的核心语句,完成向设备驱动模型的注册。
    • kobject_get_path(&dev->dev.kobj, GFP_KERNEL)
    获取sysfs中设备的节点路径
    • list_add_tail(&dev->node, &input_dev_list)
    向全局input_dev_list加入此节点
    • list_for_each_entry(handler, &input_handler_list, node)
    • input_attach_handler(dev, handler);
    从全局的input_handler_list遍历每个handler执行input_attach_handler,完成input_device和input_handler的连接,一旦匹配成功,将调用handler->connect函数
    • wait_wakeup_procfs_readers

     5.3 input_register_handler

    input_register_handler->

      list_add_tail

      input_attach_handler->

        evdev_connect->

          kzalloc(sizeof(struct evdev), GFP_KERNEL)

          init_waitqueue_head

          dev_set_name(&evdev->dev, "event%d", dev_no)

          device_initialize(&evdev->dev)

          input_register_handle(&evdev->handle)

          cdev_init

          cdev_add

          device_add

    以evdev handler为例,在注册input_handler过程中一旦与input_dev匹配将通过handler->connect创建handle并注册之,同时connect将完成evdev的初始化,并为整个input子系统添加一个字符设备,evdev作为一个device还将通过devcie_add注册进设备驱动模型

    • list_add_tail
    将handler加入到全局input_handler_list链表中,便于遍历;
    • list_for_each_entry(dev, &input_dev_list, node)
      •   input_attach_handler(dev, handler);
    对input_dev_list全局input_dev链表遍历,建立input_dev与input_handler的关联。实际上input_match_device匹配成功,会handler->connect来创建对应的handle
    • evdev_connect
    1)kzalloc(sizeof(struct evdev), GFP_KERNEL)分配一个evdev结构体;
    
    (2)init_waitqueue_head(&evdev->wait);初始化等待队列;
    
    (3)dev_set_name(&evdev->dev, "event%d", dev_no);设置evdev的名字
    
    (4)初始化evdev->handle成员变量;
    
    (5)device_initialize(&evdev->dev),evdev是一个device,需要加入设备驱动模型,此处初始化device;
    
    (6)input_register_handle(&evdev->handle)注册input_handle,见下文
    
    (7)cdev_init(&evdev->cdev, &evdev_fops)初始化evdev->cdev字符设备的fos为evdev_fops
    
    (8)cdev_add(&evdev->cdev, evdev->dev.devt, 1)将cdev设备注册进系统,此处可以看出整个input子系统只注册一个字符设备。

    5.4   input_handle_register

    input_handle注册,主要将handle->d_node连入对应的input_dev->h_list链表;主要将handle->d_node连入对应的input_handler->h_list链表;

    5.5 evdev_open

    evdev_open->

           evdev_compute_buffer_size

           kzalloc(size, GFP_KERNEL | __GFP_NOWARN)

           evdev_attach_client

           evdev_open_device

    • evdev_compute_buffer_size
    计算需要分配的存放事件的buffer大小
    • kzalloc(size, GFP_KERNEL | __GFP_NOWARN)
    创建evdev_client代表有一个用户打开了此设备。
    • evdev_attach_client
    建立evdev_client与evdev的关联 
    • evdev_open_device(evdev)
    增加open的引用计数,调用input_open_device (&evdev->handle),后者最终会调用input_dev->open函数

    5.6 evdev_read

    evdev_read->

           evdev_fetch_next_event

           input_event_to_user

           wait_event_interruptible

    •  evdev_fetch_next_event
    for循环中通过此函数来从evdev_client->buffer取出一个input_event 
    • input_event_to_user
    将input event解析完后发送给user buffer; 
    • wait_event_interruptible
    如果file->f_flags定义为非O_NONBLOCK则代表是阻塞,如果buffer里面没有数据则会在此处阻塞等待,直到有数据将其唤醒(可参照后面的解释)

    5.7 input_event

    input_event->

           input_handle_event->

                  input_pass_values->

                         input_to_handler->

                                handler->event, evdev_event->

                                       evdev_events->

                                              evdev_pass_values->

                                                     __pass_event

                                                     wake_up_interruptible

    input_event会在input_report_key等被调用,不同的事件会进行不同的封装。此函数最终将input event上报存放在evdev_client的buffer里面供用户层取用,如果定义为阻塞,则此处需要唤醒阻塞的读。

    5.8.    evdev_write

    evdev_write->

           input_event_from_user

                  input_inject_event->

                         input_handle_event

    • input_event_from_user
    将用户空间数据拷贝到evdev_client->buffer中; 
    • input_inject_event
    将根据事件方向调用input_handle_event进行写入,应用场景可以是震动马达,LED灯点亮等
  • 相关阅读:
    grad-cam 、cam 和热力图,基于keras的实现
    高斯过程(转)
    Keras中使用LSTM层时设置的units参数是什么
    Real Time Credit Card Fraud Detection with Apache Spark and Event Streaming
    NodeJs+http+fs+request+cheerio 采集,保存数据,并在网页上展示(构建web服务器)
    NodeJs+Request+Cheerio 采集数据
    数组与对象的深浅复制
    Git(进击学习:远程仓库操作)-V3.0
    牛逼的css3:动态过渡与图形变换
    Git(远程仓库:git@oschina)-V2.0
  • 原文地址:https://www.cnblogs.com/smartjourneys/p/6763400.html
Copyright © 2011-2022 走看看