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灯点亮等
  • 相关阅读:
    Roce ofed 环境搭建与测试
    Ubuntu 1804 搭建NFS服务器
    Redhat 8.0.0 安装与网络配置
    Centos 8.1 安装与网络配置
    SUSE 15.1 系统安装
    VSpare ESXi 7.0 基本使用(模板、iso、SRIOV)
    VSpare ESXi 7.0 服务器安装
    open SUSE leap 15.1 安装图解
    KVM虚拟机网卡连接网桥
    GitHub Action一键部署配置,值得拥有
  • 原文地址:https://www.cnblogs.com/smartjourneys/p/6763400.html
Copyright © 2011-2022 走看看