Linux驱动学习笔记:input子系统机制

嵌入式技术

1368人已加入

描述

一、分层

数据结构

驱动层:输入设备的具体驱动程序,向内核层报告输入内容

核心层:为驱动层提供输入设备的注册和操作接口,通知事件层对输入事件进行处理

事件层:和用户空间进行交互

input子系统所有的设备主设备号都是13,在使用input系统的时候不需要去注册字符设备,只需要向系统申请一个input_device即可

二、流程

2.1注册input_device

input设备由input_dev结构体表示,定义在include/linux/input.h中

①使用input_allocate_device函数申请一个input_dev

数据结构

返回值:申请到的input_dev

②初始化input_dev的事件类型和事件值

也就是初始化input_dev结构体

③使用input_register_device函数向系统注册input_dev

数据结构

dev:要注册的input_dev

返回值:0成功,负值失败

④注销注册的input_dev

数据结构

⑤释放申请的input_dev

数据结构

2.2上报输入事件

用于上报指定的事件和事件值

数据结构

dev:需要上报的input_dev

type:上报的事件类型,如EV_KEY

code:事件码,比如KEY_0

value:事件值,比如1表示按键按下

还有其他API函数:

数据结构

数据结构

2.3上报同步事件

数据结构

三、input_event结构体

表示所有的输入事件,定义在include/uapi/linux/input.h中,用户程序通过input_event获取到具体的事件和相关值

数据结构

time:事件发生的事件

type:事件类型

code:事件码

value:值

四、三种设置事件的方法

数据结构

五、调试

cat /proc/bus/input/devices能够查看输入设备的具体信息。

六、input子系统架构

input子系统分为三个部分:input设备驱动层,input 核心层,input 事件处理层。

input设备驱动层是接近硬件的一层,负责获取输入设备的输入数据和上报数据,对应的就是我们写的各种设备驱动,比如触摸屏。

input核心层是内核自带的一层,负责给input设备驱动层和input 事件处理层提供服务接口,是两者的桥梁。

input 事件处理层负责给提供一个个input_event形式的消息给应用层,代表的数据结构是input_handler,代表的文件是evdev.c。

input 核心层

在/driver/input/input.c中,使用subsys_initcall进行input核心层的注册。

数据结构

1.注册input_class设备类,表现形式是在/sys/class下创建目录”input”

2.初始化input相关的/proc结构,表现形式是在/proc中创建bus/input/devices和bus/input/devices

3.注册主设备号为13,次设备号从0开始的1024个设备。(13,0)-(13,1023),都使用同一套file_operations操作集。

input_dev注册

在设备驱动层,注册一个input设备驱动需要三个步骤。

1.使用input_allocate_device申请一个input_dev实例(内存)

2.设置input设备的能力,比如支持什么事件?支持这个事件下的什么功能?表征这个功能的状态?分别对应的就是input_event消息的type,code,value。

3.使用input_register_device注册一个input_dev.

下面看看注册input_dev做了什么,内核是如何管理的。

input_dev数据结构

~~struct input_dev {
 const char *name;
 const char *phys;
 const char *uniq;
 struct input_id id;

 unsigned long propbit[~~BITS_TO_LONGS(INPUT_PROP_CNT)];

 unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
 unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
 unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

 unsigned int hint_events_per_packet;

 unsigned int keycodemax;
 unsigned int keycodesize;
 void *keycode;

 int (*setkeycode)(struct input_dev *dev,
     const struct input_keymap_entry *ke,
     unsigned int *old_keycode);
 int (*getkeycode)(struct input_dev *dev,
     struct input_keymap_entry *ke);

 struct ff_device *ff;

 unsigned int repeat_key;
 struct timer_list timer;

 int rep[REP_CNT];

 struct input_mt *mt;

 struct input_absinfo *absinfo;

 unsigned long key[BITS_TO_LONGS(KEY_CNT)];
 unsigned long led[BITS_TO_LONGS(LED_CNT)];
 unsigned long snd[BITS_TO_LONGS(SND_CNT)];
 unsigned long sw[BITS_TO_LONGS(SW_CNT)];

 int (*open)(struct input_dev *dev);
 void (*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 __rcu *grab;

 spinlock_t event_lock;
 struct mutex mutex;

 unsigned int users;
 bool going_away;

 struct device dev;

 struct list_head h_list;
 struct list_head node;

 unsigned int num_vals;
 unsigned int max_vals;
 struct input_value *vals;

 bool devres_managed;
};

1.判断是否使用devres管理服务,由input→devres_managed指定

数据结构

2.给每个设备设置同步能力

数据结构

3.将注册的input_dev放入input核心层维护的全局链表input_dev_list的表尾,这个链表在input.c中定义,每个节点都是struct input_dev类型。

数据结构

4.遍历input_handler_list链表,每个节点都是struct input_handler类型。这个链表同样在input核心层维护,这个链表操作传入的是struct input_handler *handler空指针,会被赋值成每一个handler的首地址,每赋值一次代表遍历到了某一个handler节点,然后使用input_attach_handler来看input_dev和input_handler是否匹配。

数据结构

至此,input_dev的注册事情就做完了。注意的是input_handler_list早就在input_dev注册前就已经初始化好。下面用手画一张图表示。

数据结构

input_dev和input_handler匹配的方式,匹配后做什么?

数据结构

1.使用input_match_device匹配

2.匹配成功后调用handler的connect函数

这里需要看input_handler数据结构

数据结构

input_match_device是如何匹配呢?通过handler的id_table和input_dev的id下某些字段判断

数据结构

对于evdev.c来说,能与任何input设备匹配。原因是:

数据结构

匹配上了,调用connect函数做了什么?

不同的handler有不同的函数,这里使用evdev.c的connect看看

数据结构

在evdev.c中定义了一个handler 叫evdev_handler,里面定义了一些函数,先看我们要的connect函数

在evdev中,有一个evdev数据结构,里面管理了一个handle,注意handle并不是handler。handle是一个句柄,用来沟通匹配的input_dev和input_handler。

数据结构

1.获取一个次设备号,没深究怎么获取,最后在/sys/class/input下创建设备event%d

数据结构

2.申请一个evdev实例(内存),并且初始化其链表,链表自旋锁,互斥锁,等待队列,标志位exist等资源。

数据结构

3.初始化evdev的字段,最重要的是里面handle的初始化,使handle保存匹配的input_dev和input_handler地址

数据结构

4.注册handle

数据结构

5.注册字符设备添加逻辑设备,可见字符设备绑定了一个evdev的操作集

数据结构

handle注册

上面说的注册handle,如何表示注册?使用input_register_handle注册,此函数很可能应该是由handler的connect函数调用。

主要工作就是如下:如果handler定义了filter函数,那么就将handle自身的节点加入要input_dev维护的h_list链表头,否则加入到链表尾。h_list是handle的链表,在handler里面也有一条,因此也会把handle节点加入到handler的h_list节点。

数据结构

总结就是如下一幅图,这样能表示链表错综复杂的关系。有了handle,input_dev和input_handler都可以找到对方。

数据结构

数据结构

应用程序打开/dev/input/event%d流程

在handler的connect中,注册了一个字符设备,绑定了一个操作集。当我们应用层使用设备节点的时候就能调用到handler的操作集。

数据结构

evdev_open:

数据结构

当打开一个输入设备节点时候,会分配一个evdev_client结构体,一个实例表示有某个app打开了设备节点准备接收数据,意味着可以有多个app同时接收数据。分配缓冲区。分配好后,设置其buffer大小,指向evdev建立联系,然后最重要的是把evdev_client放入evdev维护的链表中。最后将file->private_data = client指向client,稍后read write的时候能直接找到client中的缓冲区

数据结构

evdev_read:

evdev_read是一个死循环,当没有设置无阻塞访问的时候就在evdev的等待队列中休眠

数据上报流程

容易理解,数据源头是在硬件,和我们紧密接触的就是input设备驱动层,也就是定义input_dev的我们编写的各种设备驱动。一般这样的驱动都是在获取到数据之后使用上报数据的API去上报数据。无论有多少上报的api,都是input_event()的变体。

数据结构

先初步判断要上传的类型和你设置的类型是否一致,一致则调用input_handle_event()进一步上传。调用链是:input_event→input_handle_event→input_pass_values→input_to_handler.

input_to_handler中最终调用到handler的三个上报函数,重要性:handler->filter > handler->events > handler->event .在evdev中没有定义filter,所以最后在handler->events上报数据。

数据结构

数据结构

数据结构

上图三个函数依次是调用链:最后的__pass_event就是把一个个的input_event放入各个client的缓冲区供各个app同时读取。最后在handler->events中会唤醒evdev中的等待队列的所有app。

总结

input核心层分别给input设备驱动层和input处理层提供注册的接口。

三个核心数据结构体:

  • input_dev属于设备驱动层,
  • input_handle属于核心层,不单独出现,会被包裹在一个处理层的一个处理器里面,如evdev。
  • input_handler属于处理层

input_dev注册的时候要和各个input_handler进行匹配,每匹配成功则产生一个handle用于沟通彼此,input_dev和input_handler中有h_list链表把input_handle组织起来,表示input_dev和input_handler是多对多的关系。handle和handler是1对1的关系,如果一个input_dev和两个handler匹配则产生两个handle。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分