Linux输入子系统分析一
为什么要引入input子系统?
输入设备分散不堪,用input子系统可以对分散的,不同类别的输入设备进行统一驱动。
好处:
1. 统一了物理形态各异的输入设备相似的设备处理,例如各种鼠标,键盘,触摸屏。
2. 提供了用于分发输入报告给用户应用程序的简单事件接口。你的驱动不必创建管理/dev节点以及相关的访问方法。因此他能够很方便的调用输入API以发送鼠标移动,键盘按键或者触摸事件给用户空间。
3. 抽取出了输入驱动的通用部分,简化了驱动,并提供一致性。例如:输入子系统提供了一个底层驱动的集合,支持对串口和键盘控制器等硬件输入的访问。
input子系统框架结构
* 从上层到底层开始分析:
从上层到底层开始分析:
设备节点:
/dev/input、目录下面显示的是已经注册在内核中的设备编程接口。用户通过open这些设备文件,来打开不同的输入设备进行硬件操作。
handler事件处理层(纯软件层,不涉及硬件)
.描述:与用户空间交互。为不同硬件类型提供了用户访问以及处理接口。例如:当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件的处理方法。
主要功能:
包含不同的解决方案,例如键盘,鼠标,触摸屏等。对于不同的解决方案,都有一个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核心层
描述:承上启下。为驱动层提供输入设备的注册和操作接口。由内核代码drivers/input/input.c构成,他的存在屏蔽了用户到驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的界面。
主要功能:
- 注册设备号
- 对于swi进入的open函数进行第一层处理,并通过次设备号选择handler进入第二层open,也就是真正的open所在的file_operation,并返回file_operation的fd
- 提供input_register_device和input_register_handler函数分别用于注册device和handler
device驱动层(纯硬件)
描述:将底层硬件的输入转化为统一事件形式,向核心层汇报。纯硬件操作,包含不同的硬件接口处理,如gpio等
主要功能:对于不同的具体硬件操作,都对应着一个不同的input_device结构体
也包含一个h_list链表
handler和device
对于handler和device,分别用链表input_handler_list和input_device_list进行维护。
当handler或者device减少的时候,分别往这两个链表进行增加或删除节点。
input子系统调用过程分析
- 当外部应用程序需要调用子系统的open函数时,会先通过主设备号进入到核心层,然后通过次设备号进入handler层,再调用.fops内的open函数返回fd
- 当外部应用程序需要调用子系统的read函数时,会通过返回的fd调用.fops内的read函数,然后休眠,等待被.event函数唤醒
- 当外部中断到达的时候,会先确定中断事件,然后调用input_event上报事件,再通过h_list里面的所有handle调用对应的handler中的.event函数,对read进行唤醒,然后在read中返回,也就是当device有多个对应的handler的时候,input_event会向所有的handler上报事件
- 当需要加入新的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 - 当需要加入新的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
输入设备开发步骤
- 在驱动加载模块中,设置你的input设备支持的事件类型。
- 注册中断处理函数。例如:键盘设备需要编写键盘的抬起,放下。触摸屏设备需要编写按下,抬起,绝对移动。鼠标设备需要编写单击,抬起,相对移动,并且需要在必要的时候提交硬件数据。比如:键值,坐标,状态等。
- 将输入设备注册到输入子系统中。
数据结构分析
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:事件处理器的数据结构,代表一个事件处理器
几个操作函数
- void(*event)(struct input_handler *handle, unsigned int type, unsigned int code, int value);//事件处理器接收到来自input设备的事件时调用,负责处理事件
- int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);//当一个input设备模块注册到内核的时候调用,将事件处理器与输入设备联系起来
- void (*disconnect)(struct input_handle *hanlde);//与connect相反
- void (*start)(struct input_handle *handle);
两个ID
- const struct input_device_id *id_table;//事件处理器支持的input设备
- 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链表项
};