Linux输入子系统分析一

Linux输入子系统分析一

为什么要引入input子系统?

输入设备分散不堪,用input子系统可以对分散的,不同类别的输入设备进行统一驱动。
好处:
1. 统一了物理形态各异的输入设备相似的设备处理,例如各种鼠标,键盘,触摸屏。
2. 提供了用于分发输入报告给用户应用程序的简单事件接口。你的驱动不必创建管理/dev节点以及相关的访问方法。因此他能够很方便的调用输入API以发送鼠标移动,键盘按键或者触摸事件给用户空间。
3. 抽取出了输入驱动的通用部分,简化了驱动,并提供一致性。例如:输入子系统提供了一个底层驱动的集合,支持对串口和键盘控制器等硬件输入的访问。

input子系统框架结构

这里写图片描述
* 从上层到底层开始分析:

从上层到底层开始分析:

设备节点:

/dev/input、目录下面显示的是已经注册在内核中的设备编程接口。用户通过open这些设备文件,来打开不同的输入设备进行硬件操作。

handler事件处理层(纯软件层,不涉及硬件)
  1. .描述:与用户空间交互。为不同硬件类型提供了用户访问以及处理接口。例如:当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件的处理方法。

  2. 主要功能:
    包含不同的解决方案,例如键盘,鼠标,触摸屏等。对于不同的解决方案,都有一个input_handler的结构体。主要成员如下:
    .id_table: 一个存放该handler所支持的设备id的表(其实内部存放的是EV_xxx事件,用于判断device是否支持该事件)
    .fops: 该handler的file_operation
    .connect: 连接该handler和所支持的device的函数
    .disconnect: 断开该连接
    .event: 事件处理函数,让device调用
    h_list: 一个链表,该链表保存着handler到所支持的所有device的中间站:即handler结构体的指针

input core核心层

  1. 描述:承上启下。为驱动层提供输入设备的注册和操作接口。由内核代码drivers/input/input.c构成,他的存在屏蔽了用户到驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的界面。

  2. 主要功能:

    • 注册设备号
    • 对于swi进入的open函数进行第一层处理,并通过次设备号选择handler进入第二层open,也就是真正的open所在的file_operation,并返回file_operation的fd
    • 提供input_register_device和input_register_handler函数分别用于注册device和handler

device驱动层(纯硬件)

  1. 描述:将底层硬件的输入转化为统一事件形式,向核心层汇报。纯硬件操作,包含不同的硬件接口处理,如gpio等

  2. 主要功能:对于不同的具体硬件操作,都对应着一个不同的input_device结构体
    也包含一个h_list链表

handler和device

对于handler和device,分别用链表input_handler_list和input_device_list进行维护。
当handler或者device减少的时候,分别往这两个链表进行增加或删除节点。

input子系统调用过程分析

  1. 当外部应用程序需要调用子系统的open函数时,会先通过主设备号进入到核心层,然后通过次设备号进入handler层,再调用.fops内的open函数返回fd
  2. 当外部应用程序需要调用子系统的read函数时,会通过返回的fd调用.fops内的read函数,然后休眠,等待被.event函数唤醒
  3. 当外部中断到达的时候,会先确定中断事件,然后调用input_event上报事件,再通过h_list里面的所有handle调用对应的handler中的.event函数,对read进行唤醒,然后在read中返回,也就是当device有多个对应的handler的时候,input_event会向所有的handler上报事件
  4. 当需要加入新的handler时,需要先构建handler结构体,然后调用注册函数input_register_handler对该handler进行注册
    input_register_handler内部实现:往input_handler_list加入新增的handler结点,然后对input_device_list的所有结点,也就是对所有的device进行遍历。通过.id_table查看该device是否支持该handler。对于支持该handler的device就调用.connecet逐一构建input_handler结构体,连接handler和device
  5. 当需要加入新的device时,需要先构建input_dev结构体,然后调用注册函数input_register_device对该input_dev进行注册
    input_register_device内部实现:往input_device_list加入新增的device结点,然后对input_handler_list的所有结点,也就是对所有的handler进行遍历,通过handler的.id_table查看该handler是否支持device。对于支持该device的handler就调用.connect逐一构建input_handler结构体,连接handler和device

输入设备开发步骤

  1. 在驱动加载模块中,设置你的input设备支持的事件类型。
  2. 注册中断处理函数。例如:键盘设备需要编写键盘的抬起,放下。触摸屏设备需要编写按下,抬起,绝对移动。鼠标设备需要编写单击,抬起,相对移动,并且需要在必要的时候提交硬件数据。比如:键值,坐标,状态等。
  3. 将输入设备注册到输入子系统中。

数据结构分析

input_dev
struct input_dev
{
     void *private//输入设备私有指针,一般指向用于描述设备驱动层的设备结构
     const char *name;     //输入设备名称
     const char *phys;     //设备节点名称
     const char *uniq;     //指定唯一的ID号,就像MAC地址一样
     struct input_id id;     //用于匹配事件处理层handler,标识设备驱动特征。包含总线类型,生产厂商,产品类型,版本
     unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //用来记录支持事件类型的位图,设置方式:dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS)
     unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];     //用来记录支持的按键值的位图
     unsigned long relbit[BITS_TO_LONGS(REL_CNT)];     //用来记录支持的相对坐标的位图
     unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];     //用来记录支持的绝对坐标的位图(触摸屏),设置方法:input_set_abs_params
     unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   //其他功能
     unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   //led
     unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   //beep
     unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];   //作用力功能
     unsigned long swbit[BITS_TO_LONGS(SW_CNT)];   //开关功能
     unsigned int keycodemax;     //支持的按键值的个数
     unsigned int keycodesize;        //每个键值的字节数
     void *keycode;          //指向按键池,存储按键值的数组首地址
     int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);     //修改键值的函数,可选
     int (*getkeycode)(struct input_dev *dev, int scancode, int keycode);     //获取扫描码键值的函数,可选
     struct ff_device *ff;          //用于强制更新输入设备的部分内容
     unsigned int repeat_key;     //最近一次按键值,用于连击
     struct timer_list timer;     //自动连击计时器
     int state;     //设备状态
     int sync;     //最后一次同步后没有新的事件置1,如果是1说明事件同步完成
     int abs[ABS_MAX + 1];     //当前各个坐标的值
     int rep[REP_MAX + 1];     //自动连击的参数
     unsigned long key[BITS_TO_LONGS(KEY_CNT)];     //反应当前按键状态的位图
      unsigned long led[BITS_TO_LONGS(LED_CNT)];   //反应当前led状态的位图
      unsigned long snd[BITS_TO_LONGS(SND_CNT)];   //反应当前beep状态的位图
     unsigned long sw[BITS_TO_LONGS(SW_CNT)];  
      int absmax[ABS_MAX + 1];     //当前各个坐标的最大值
      int absmin[ABS_MAX + 1];     //当前各个坐标的最小值
      int absfuzz[ABS_MAX + 1];     //当前各个坐标的分辨率
      int absflat[ABS_MAX + 1];     //当前各个坐标的基准值
     int (*open)(struct input_dev *dev);     //打开函数
      int (*close)(struct input_dev *dev);     //关闭函数
      int (*flush)(struct input_dev *dev, struct file *file);     //断开连接时冲刷数据
      int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);     //事件处理,回调函数,可选
     struct input_handle *grab;     //类似私有指针,可以直接访问到事件处理接口event
     spinlock_t event_lock;
     struct mutex mutex;     //用于open,close函数的连续访问互斥
     unsigned int users;     //设备使用计数
     int going_away;
     struct device dev;     //设备结构体
     struct list_head h_list;     //handle链表
     struct list_head node;     //input_dev链表
};
input_handler:事件处理器的数据结构,代表一个事件处理器
  • 几个操作函数

    1. void(*event)(struct input_handler *handle, unsigned int type, unsigned int code, int value);//事件处理器接收到来自input设备的事件时调用,负责处理事件
    2. int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);//当一个input设备模块注册到内核的时候调用,将事件处理器与输入设备联系起来
    3. void (*disconnect)(struct input_handle *hanlde);//与connect相反
    4. void (*start)(struct input_handle *handle);
  • 两个ID

    1. const struct input_device_id *id_table;//事件处理器支持的input设备
    2. const struct input_device_id *blacklist;//事件处理器忽略的input设备
  • 两个链表
    struct list_head h_list;//用来链接他所支持的input_handle结构,input_dev和input_handler配对之后会生成一个input_handle结构
    struct list_head node;//链接了所有注册到内核的事件处理器,链接到input_handler_list

input_handle:代表一个配对成功的input_dev和input_handler
struct input_handle
{
     void *private;//每个配对的事件处理器都会分配一个对应的设备结构,如evdev事件处理器evdev结构,注意这个结果和设备驱动层的input_dev不同。初始化handle时,保存到这里
     int open;//打开标志,每个input_handle打开后才能操作,一般通过事件处理器的open方法间接设置
     const char *name;
     struct input_dev *dev;//关联的input_dev结构
     struct input_handler *handler;//关联的input_handler结构
     struct list_head d_node;//input_handle通过d_node连接到input_dev的h_list链表
     struct list_head h_node;//input_handle通过h_node连接到input_handler的h_list链表
};

三个结构体之间的关系

  • input_dev是硬件驱动层,代表一个input设备。通过全局的input_dev_list连接到一起,设备注册的时候实现这个操作
  • input_handler是事件处理层,代表一个事件处理器。通过全局的input_handler_list连接到一起,事件处理器注册的时候实现这个操作(事件处理器不需要我们写)
  • input_handle属于核心层,代表配对的input设备和input事件处理器。没有全局链表,他注册的时候把自己分别挂在了input_dev和input_handler的h_list上
  • 小结:通过input_dev和input_handler就可以找到input_handle在设备注册和事件处理器。注册的时候都需要配对,配对之后就会实现链接。通过input_handle也可以找到input_dev和input_handler

补充结构体

  • evdev设备结构体
    在配对成功的时候生成,由handler->connect生成,对应设备文件是/class/input/event(n)
    例如触摸屏驱动的event0,这个设备是用户空间要访问的设备,因为没有对应的硬件可以理解为他是一个虚拟设备。但是通过handle->dev就可以找到input_dev结构,而他对应着触摸屏,设备文件为/class/input/input0.这个设备结构体生成之后保存在evdev_table中,索引值是minor
struct evdev
{
     int exist;
     int open;//打开标志
     int minor;//次设备号
     struct input_handle handle;//关联的input_handle
     wait_queue_head_t wait;//等待队列,当进程读取设备,而没有事件产生的时候,进程就会睡在上面
     struct evdev_client *grab;//强制绑定的evdev_client结构
     struct list_head client_list;//evdev_client链表,这说明一个evdev设备可以处理多个evdev_client,可以有多个进程访问evdev设备
     spinlock_t client_lock;
     struct mutex mutex;
     struct device dev;//这是一个设备结构
};
  • evdev用户端结构
    这个结构在进程打开evdev0设备的时候调用evdev的open方法,在open中创建这个结构,并初始化。在关闭设备文件的时候释放这个结构
struct evdev_client
{
     struct input_event buffer[EVDEV_BUFFER_SIZE];// input_event数据结构的数组, input_event代表一个事件,基本成员:类型type,编码code,值value
     int head;//针对buffer数组的索引
     int tail;//针对buffer数组的索引,当head和tail相等的时候,说明没有事件
     spinlock_t buffer_lock;//保护buffer的访问
     struct fasync_struct *fasync;//异步通知函数
     struct evdev *evdev;//evdev设备
     struct list_head node;//evdev_client链表项
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值