input子系统介绍
- 在linux系统中,输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备
- 其一般的工作机制是:
- 用户在按键、触摸等动作发生时产生一个中断
- 然后cpu读取键值、坐标等数据,再放一个缓冲区
- 字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据。
- 在Linux中,输入子系统是由:
- 输入子系统设备驱动层
- 输入子系统核心层(Input Core)
-
输入子系统事件处理层(Event Handler)组成。
- 其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;
- 而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;
- 而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。
- 所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。
-
在 Linux中,输入子系统作为一个模块存在,向上,为用户层提供接口函数,向下,为驱动层程序提供统一的接口函数。其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。这样,就能够使输入设备的事件通过输入子系统发送给用户层应用程序,用户层应用程序也可以通过输入子系统通知驱动程序完成某项功能。
input子系统框架分析
linux-3.5内核
事件层
-
核心代码在文件drivers/input/input.c
-
子系统的初始化函数是input_init()
- 该初始化函数最重要的部分就是注册一个字符设备:
- INPUT_MAJOR 宏的值为 13
- 驱动函数集合是input_fops
-
驱动操作函数集合中只有一个open方法
-
open方法的代码简化如下:
- 以次设备号为索引,找到一个handler
- 将handler->fops拷贝给file->f_op
- input_table[ ]数组分析
-
input_table[ ]是一个数组指针,指向一个struct input_handler结构;
-
struct input_handler结构在input.h中定义如下:
-
-
input_table[]数组添加成员的函数:
- 在分析完上面的框架之后,那么谁去调用了inoput_register_handler()往input_table数组添加成员呢?
-
evdev.c、joydev.c 、mousedev.c这些文件都在模块入口函数中调用input_register_handler()函数注册一个handler到数组input_table[]中
-
事件层小结
- 上面的分析基本上将事件层分析完毕
- 事件层,基本上将输入设备的事件归纳为joykey, evdev, mouse, keyboard等的事件;
- 同一类的事件在休眠、唤醒的代码基本相同的如;
-
handler->fops->read()
函数中休眠; -
handler->event()
函数中唤醒进程数据到达;
-
- 也就是说,要向应用层发送什么事件,只需要调用相应的 handler->event()函数即可;
- 也就是说,我们的驱动只需要注册一个中断处理函数,在中断处理函数里面调用handler->event()函数就可以了!
核心层
- 在input.c定义了两个链表,先来分析这两个链表
input_dev_list
input_handler_list
-
input_dev_list
链表中节点的类型是:
struct input_dev
表示每个硬件设备 -
input_handler_list
链表中的节点类型是:
struct input_handler
表示事件类型 -
对链表input_handler_list的操作函数:
将参数handler添加到链表中,并且对于链表input_handler_list中的每个成员都调用input_attach_handler函数
-
对链表input_dev_list的操作函数:
将参数dev添加到链表中,并且对于链表input_dev_list中的每个成员都调用input_attach_handler函数
-
input_attach_handler函数分析
匹配handler与dev的ID,如果匹配调用handler->connect()方法
-
框架图
- 发送事件函数
- 系统中,内核已经封装了发送事件的函数
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
- 跟踪这个函数如下:
- 系统中,内核已经封装了发送事件的函数
input_event()
input_handle_event()
input_pass_event()
handler->event()
通过input_dev找到与之关联的handler,调用handler的event函数
关键数据结构
- struct input_handler 事件处理结构体,定义怎么处理事件的逻辑
- struct input_handle 用来创建input_dev和input_handler之间关系的结构体
- struct input_dev 物理输入设备的基本数据结构,包含设备相关的一些信息
- input_handler是输入设备的事件处理接口,为处理事件提供一个统一的函数模板
- 以evdev.c中的evdev_handler为例:
static struct input_handler evdev_handler = {
.event = evdev_event, //向系统报告input事件,唤醒进程
.connect = evdev_connect, //和input_dev匹配,调用connect构建
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //event设备文件的操作方法
.minor = EVDEV_MINOR_BASE, //次设备号基准值
.name = "evdev",
.id_table = evdev_ids, //匹配规则
};
-
input_handle的主要功能是用来连接input_dev和input_handler
整体框架
input driver编写要点
- 分配、注册、注销input设备
struct input_dev *input_allocate_device(void)
int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
- 设置input设备支持的事件类型、事件码、事件值的范围、input_id等信息
一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件码。比如:EV_KEY事件,需要定义其支持哪些按键事件码。 - 如果需要,设置input设备的打开、关闭、写入数据时的处理方法
- 在发生输入事件时,向子系统报告事件
- 注册输入设备的函数为:
int input_register_device(struct input_dev *dev)
- 注销输入设备的函数为:
void input_unregister_device(struct input_dev *dev)
- 设备驱动通过set_bit()告诉input子系统它支持哪些事件,
- 如下所示:
set_bit(EV_KEY, button_dev.evbit)
Struct iput_dev中有两个成员,一个是evbit;一个是keybit。
- 分别用来表示设备所支持的事件类型和按键类型。
- 事件类型:
EV_RST Reset
EV_KEY 按键
EV_REL 相对坐标
EV_ABS 绝对坐标
EV_MSC 其它
EV_LED LED
EV_SND 声音
EV_REP Repeat
EV_FF 力反馈
- 用于报告EV_KEY、EV_REL和EV_ABS事件的函数分别为:
void input_report_key(struct input_dev *dev,unsigned int code, int value)
void input_report_rel(struct input_dev *dev,unsigned int code, int value)
void input_report_abs(struct input_dev *dev,unsigned int code, int value)
- code:
事件的代码。如果事件的类型是EV_KEY,该代码code为设备键盘代码。代码值0127为键盘上的按键代码,0x1100x116 为鼠标上按键代码,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键。其它代码含义请参看include/linux/input.h文件 - value:
事件的值。如果事件的类型是EV_KEY,当按键按下时值为1,松开时值为0。 -
input_sync()
用于事件同步,它告知事件的接收者:驱动已经发出了一个完整的报告。
例如,在触摸屏设备驱动中,一次坐标及按下状态的整个报告过程如下:
input_report_abs(input_dev, ABS_X, x); //X坐标
input_report_abs(input_dev, ABS_Y, y); //Y坐标
input_report_abs(input_dev, ABS_PRESSURE, pres); //压力
input_sync(input_dev); //同步
设备描述
-
Linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。驱动报告的事件经过InputCore和Eventhandler最终到达用户空间。
- code:
事件的代码。如果事件的类型是EV_KEY,该代码code为设备键盘代码。代码值0~127为键盘上的按键代码,0x110~0x116 为鼠标上按键代码,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键。其它代码含义请参看include/linux/input.h文件
evbit 事件类型
#define EV_SYN 0x00 /*表示设备支持所有的事件*/
#define EV_KEY 0x01 /*键盘或者按键,表示一个键码*/
#define EV_REL 0x02 /*鼠标设备,表示一个相对的光标位置结果*/
#define EV_ABS 0x03 /*手写板产生的值,其是一个绝对整数值*/
#define EV_MSC 0x04 /*其他类型*/
#define EV_LED 0x11 /*LED灯设备*/
#define EV_SND 0x12 /*蜂鸣器,输入声音*/
#define EV_REP 0x14 /*允许重复按键类型*/
#define EV_PWR 0x16 /*电源管理事件*/
具体可以参考<linux/input.h>
实战代码
tiny4412btn_input.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/gpio.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
/* name gpio irqnum keyvalue
k1 EXYNOS4_GPX3(2) ? 11
k2 EXYNOS4_GPX3(3) 12
k3 EXYNOS4_GPX3(4) 13
k4 EXYNOS4_GPX3(5) 14
*/
struct key_t {
char *name;
unsigned int gpio;
unsigned int irqnum;
unsigned int keyvalue;
};
struct key_t keys[]={
{"K1",EXYNOS4_GPX3(2),0,KEY_L},
{"K2",EXYNOS4_GPX3(3),0,KEY_S},
{"K3",EXYNOS4_GPX3(4),0,KEY_BACK},
{"K4",EXYNOS4_GPX3(5),0,KEY_ENTER},
};
static struct input_dev *btn_dev;
static struct timer_list btn_timer;
static struct key_t *keyp;
static irqreturn_t btn_handler(int irq, void *data)
{
btn_timer.data = (unsigned long)data;
mod_timer(&btn_timer,jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void btn_timer_function(unsigned long data)
{
struct key_t *p = (struct key_t *)data;
unsigned int pinval = 0;
pinval = gpio_get_value(p->gpio);
if(pinval == 1){
input_event(btn_dev,EV_KEY, p->keyvalue, 0);
input_sync(btn_dev);
}
else {
input_event(btn_dev,EV_KEY, p->keyvalue, 1);
input_sync(btn_dev);
}
}
static int __init tiny4412btn_init(void)
{
btn_dev = input_allocate_device();
set_bit(EV_KEY,btn_dev->evbit);
set_bit(EV_REP,btn_dev->evbit);
int i;
for(i=0;i<4;i++)
set_bit(keys[i].keyvalue,btn_dev->keybit);
input_register_device(btn_dev);
init_timer(&btn_timer);
btn_timer.function = btn_timer_function;
add_timer(&btn_timer);
/* gpio to irqnum*/
for(i=0;i<4;i++){
keys[i].irqnum= gpio_to_irq(keys[i].gpio);
request_irq(keys[i].irqnum,btn_handler,
IRQ_TYPE_EDGE_BOTH,
"tiny4412btn", &keys[i]);
}
return 0;
}
static void __exit tiny4412btn_exit(void)
{
int i;
for(i=0;i<4;i++){
free_irq(keys[i].irqnum, &keys[i]);
}
del_timer(&btn_timer);
input_unregister_device(btn_dev);
input_free_device(btn_dev);
}
module_init(tiny4412btn_init);
module_exit(tiny4412btn_exit);
MODULE_LICENSE("GPL");