1.input子系统概述
输入设备(如键盘,鼠标,跟踪球,操纵杆,触摸屏,加速计和手写板,等)是典型的字符设备,其一般的工作机制是低层在按键,触摸等动作发生时产生一个中断(或驱动通过timer定时查询),然后cpu通过SPI,I2C或者外部存储器总线读取键值,坐标等数据,放一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值,坐标等数据。
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。
2. 输入子系统设备驱动层实现原理
在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
1).在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
2).将Input设备注册到input子系统中;
3).在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
3.软件设计流程
4.与软件设计有关的API函数
41.分配一个输入设备
struct input_dev *input_allocate_device*(void);
4.2.注册一个输入设备
Int input_register_device(struct input_dev *dev);
4.3.驱动实现-事件支持
set_bit告诉inout子系统它支持哪些事件
set_bit(EV_KEY,button_dev.evbit)
struct input_dev中有两个成员,一个是evbit;一个是keybit.分别用来表示设备所支持的事件类型和按键类型。
4.3.1事件类型
Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):
EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标
EV_ABS 0x03 绝对坐标
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 声音
EV_REP 0x14 Repeat
EV_FF 0x15 力反馈
4.4.驱动实现-报告事件
void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);//报告指定type,code的输入事件
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);//报告绝对坐标
void input_sync(struct input_dev *dev);//报告同步事件
在触摸屏驱动设计中,一次坐标及按下状态的整个报告过程如下:
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(struct input_dev *dev);//同步
4.5释放与注销设备
void input_free_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *);
5.输入设备驱动的简单案例
在Linux内核文档的documentation/input下,有一个input-programming.txt文件,讲解了编写输入设备驱动程序的核心步骤。
Here comes a very simple example of an input device driver. The device has just one button and the button is accessible at i/o port BUTTON_PORT. When pressed or released a BUTTON_IRQ happens. The driver could look like:
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/io.h>
/*输入设备结构体 */
static struct input_dev *button_dev;
/*中断处理函数*/
static irqreturn_t button_interrupt(int irq, void *dummy)
{
/*想输入子系统报告产生按键时间*/
input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
/*通知接收者,一个报告发送完毕*/
input_sync(button_dev);
return IRQ_HANDLED;
}
/*加载函数*/
static int __init button_init(void)
{
int error;
/*申请中断处理函数*/
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
return -EBUSY;
}
/*分配一个设备结构体*/
button_dev = input_allocate_device();
if (!button_dev) {
printk(KERN_ERR "button.c: Not enough memory\n");
error = -ENOMEM;
goto err_free_irq;
}
/*设置按键信息*/
button_dev->evbit[0] = BIT_MASK(EV_KEY);
button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
/*注册一个输入设备*/
error = input_register_device(button_dev);
if (error) {
printk(KERN_ERR "button.c: Failed to register device\n");
goto err_free_dev;
}
return 0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(BUTTON_IRQ, button_interrupt);
return error;
}
/*卸载函数*/
static void __exit button_exit(void)
{
/*注销按键设备*/
input_unregister_device(button_dev);
free_irq(BUTTON_IRQ, button_interrupt);
}
module_init(button_init);
module_exit(button_exit);
程序在初始化加载函数button_init()中注册了一个中断处理函数,然后调用input_allocate_device()函数分配了一个input_dev结构体,并调用input_registe_device()函数对其进行了注册。在中断处理函数button_interrupt()中,实例将接收到的按键信息上报给input子系统。从而通过input子系统,向用户态程序提供按键输入信息。
5.1 实例中包含的input子系统中的重要函数分析
input_allocate_device()函数在内存中为输入设备结构体分分配一个空间,并对其主要的成员进行了初始化。
该函数返回一个指向input_dev类型的指针,该结构体是一个输入设备结构体,包含了输入设备的一些相关信息,如设备支持的按键码、设备的名称、设备支持的事件等。input_dev 这是input设备基本的设备结构,任何驱动设备如果想标明自己是输入设备,都应该通过初始化这样的结构体,并且调用input_allocate_device()函数进行注册。
了解这一过程,需要先看一下structinput_dev结构体的内容:
void *private; //输入设备私有指针,一般指向用于描述设备驱动层的设备结构
const char *name; //提供给用户的输入设备的名称
const char *phys; //提供给编程者的设备节点的名称
const char *uniq; //指定唯一的ID号,就像MAC地址一样
struct input_id id; //输入设备标识ID,用于和事件处理层进行匹配
unsigned long evbit[NBITS(EV_MAX)]; //位图,记录设备支持的事件类型
unsigned long keybit[NBITS(KEY_MAX)]; //位图,记录设备支持的按键类型
unsigned long relbit[NBITS(REL_MAX)]; //位图,记录设备支持的相对坐标
unsigned long absbit[NBITS(ABS_MAX)]; //位图,记录设备支持的绝对坐标
unsigned long mscbit[NBITS(MSC_MAX)]; //位图,记录设备支持的其他功能
unsigned long ledbit[NBITS(LED_MAX)]; //位图,记录设备支持的指示灯
unsigned long sndbit[NBITS(SND_MAX)]; //位图,记录设备支持的声音或警报
unsigned long ffbit[NBITS(FF_MAX)]; //位图,记录设备支持的作用力功能
unsigned long swbit[NBITS(SW_MAX)]; //位图,记录设备支持的开关功能
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说明事件同步完成
int abs[ABS_MAX + 1]; //记录坐标的值
int rep[REP_MAX + 1]; //记录重复按键的参数值
unsigned long key[NBITS(KEY_MAX)]; //位图,按键的状态
unsigned long led[NBITS(LED_MAX)]; //位图,led的状态
unsigned long snd[NBITS(SND_MAX)]; //位图,声音的状态
unsigned long sw[NBITS(SW_MAX)]; //位图,开关的状态
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); //输入设备打开函数
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 *grab; //类似私有指针,可以直接访问到事件处理接口event
struct mutex mutex; //用于open、close函数的连续访问互斥
unsigned int users; //设备使用计数
struct class_device cdev; //输入设备的类信息
union { //设备结构体
struct device *parent;
} dev;
struct list_head h_list; //handle链表
struct list_head node; //input_dev链表
};
我们在写输入设备驱动时会定义这样一个输入设备结构体,并调用input_allocate_device()函数,这个函数的功能是为新添加的输入设备分配内存,如果成功,将返回input_dev *的指针结构,因此在写驱动的时候应该接受返回值,作为驱动层获得了一个新的输入设备操作的接口。
那么input_allocate_device()函数做了什么呢?打开函数看一下(input.c中实现):
struct input_dev *input_allocate_device(void)
{
struct input_dev *dev; /*分配一个input_dev结构体,并初始化为0*/
//动态申请内存,使用GFP_KERNEL方式,注意GFP_KERNEL可能导致睡眠,不能在中断中调用这个函数
dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
if (dev) {
dev->dev.type = &input_dev_type; /*初始化设备的类型*/
dev->dev.class = &input_class; /*设置为输入设备类*/
device_initialize(&dev->dev);/*初始化device结构体*/
mutex_init(&dev->mutex);/*初始化互斥锁*/
spin_lock_init(&dev->event_lock);/*初始化时间自旋锁*/
INIT_LIST_HEAD(&dev->h_list);/*初始化链表*/
INIT_LIST_HEAD(&dev->node);/*初始化链表*/
__module_get(THIS_MODULE); /*模块引用计数加1*/
}
return dev;
}
该函数返回一个指向input_dev类型的指针
// input_dev->evbit表示设备支持的事件类型,可以是下列值的组合
#define EV_SYN 0x00 //同步事件
#define EV_KEY 0x01 //绝对二进制值,如键盘或按钮
#define EV_REL 0x02 //绝对结果,如鼠标设备
#define EV_ABS 0x03 //绝对整数值,如操纵杆或书写板
#define EV_MSC 0x04 //其它类
#define EV_SW 0x05 //开关事件
#define EV_LED 0x11 //LED或其它指示设备
#define EV_SND 0x12 //声音输出,如蜂鸣器
#define EV_REP 0x14 //允许按键自重复
#define EV_FF 0x15 //力反馈
#define EV_PWR 0x16 //电源管理事件
5.1.1 注册函数input_register_device()
input_register_device()用于注册一个输入设备
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0);
//这个原子变量,代表总共注册的input设备,每注册一个加1,因为是静态变量,所以每次调用都不会清零的
struct input_handler *handler;
const char *path;
int error;
__set_bit(EV_SYN, dev->evbit); //设置input_dev所支持的事件类型
/*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
*/
// 这个内核定时器是为了重复按键而设置的
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
//如果没有定义有关重复按键的相关值,就用内核默认的
}
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
//以上设置的默认函数由input核心提供
dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1); //设置input_dev中device的名字,这个名字会在/class/input中出现
error = device_add(&dev->dev); //将device加入到linux设备模型中去
if (error)
return error;
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
printk(KERN_INFO "input: %s as %s\n",
dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);
//这个得到路径名称,并打印出来
error = mutex_lock_interruptible(&input_mutex);
if (error) {
device_del(&dev->dev);
return error;
}
list_add_tail(&dev->node, &input_dev_list); // 将新分配的input设备连接到input_dev_list链表上
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
//遍历input_handler_list链表,配对 input_dev 和 input_handler
//input_attach_handler 这个函数是配对的关键,下面将详细分析
input_wakeup_procfs_readers();
// 和proc文件系统有关,暂时不考虑
mutex_unlock(&input_mutex);
return 0;
}
input_register_device完成的主要功能就是:初始化一些默认的值,将自己的device结构添加到linux设备模型当中,将input_dev添加到input_dev_list链表中,然后寻找合适的handler与input_handler配对,配对的核心函数是input_attach_handler。
5.1.2 input_attach_handler函数
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id; /*输入设备的指针*/
int error;
if (handler->blacklist && input_match_device(handler->blacklist, dev))
return -ENODEV;
//blacklist是handler应该忽略的input设备类型,如果应该忽略的input设备也配对上了,那就出错了
id = input_match_device(handler->id_table, dev);
//这个是主要的配对函数,主要比较id中的各项,下面详细分析
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
//配对成功调用handler的connect函数,这个函数在事件处理器中定义,主要生成一个input_handle结构,并初始化,还生成一个事件处理器相关的设备结构,后面详细分析
if (error && error != -ENODEV)
printk(KERN_ERR
"input: failed to attach handler %s to device %s, "
"error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
//出错处理
return error;
}
此函数中,定义了一个input_device_id的指针。该结构体表示设备的标示,表示中存储了设备的信息,其定义如下:
struct input_device_id {
kernel_ulong_t flags; /*标志信息*/
__u16 bustype; /*总线类型*/
__u16 vendor; /*制造商ID*/
__u16 product; /*产品ID*/
__u16 version; /*版本号*/
kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];
kernel_ulong_t driver_info; /*额外的驱动信息*/
};
input_attach_handler的主要功能就是调用了两个函数,一个input_match_device进行配对,一个connect处理配对成功后续工作。
5.1.3 input_match_device函数
static const struct input_device_id *input_match_device(const struct input_device_id *id, struct input_dev *dev)
{
int i;
for (; id->flags || id->driver_info; id++) {
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) /*用来匹配总线类型*/
if (id->bustype != dev->id.bustype)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR) /*匹配设备厂商的信息*/
if (id->vendor != dev->id.vendor)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT) /*匹配设备号的信息*/
if (id->product != dev->id.product)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
//如果匹配成功进入下面的宏,否则进入下一个id
MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX);
return id;
}
return NULL;
}
MATCH_BIT宏的定义如下:
#define MATCH_BIT(bit, max) \
for (i = 0; i < BITS_TO_LONGS(max); i++) \
if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \
break; \
if (i != BITS_TO_LONGS(max)) \
continue;
从MATCH_BIT宏的定义可以看出。只有当input device和input handle的ID成员在evbit、keybit、...、swbit项相同才会匹配成功。只要有一项不同,就会循环到ID中的下一项进行比较。
简而言之,注册input device的过程就是为input device设置默认值,并将其挂以input_dev_list。
5.2 向子系统报告事件
在button_interrupt()函数中,调用input_report_key()函数向输入子系统报告发生的事件。在button_interrupt()函数中,不需要考虑重复按键的重复点击情况,input_report_key()函数会自动检查这个问题,并报告一次事件给输入子系统。
5.2.1 input_report_key()函数
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
该函数的第1个参数是产生事件的输入设备.第2个参数是产生的事件,第3个参数是事件的值。
在input_report_key()函数中真正起作用的函数是input_evnet()函数,该函数用来向输入子系统报告输入设备产生的事件。
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
if (is_event_supported(type, dev->evbit, EV_MAX)) { //判断是否支持此种事件类型和事件类型中的编码类型
spin_lock_irqsave(&dev->event_lock, flags);
add_input_randomness(type, code, value);
//对系统随机熵池有贡献,因为这个也是一个随机过程
input_handle_event(dev, type, code, value);
//这个函数是事件处理的关键函数,下面详细分析
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
该函数第1个参数是input_device设备,第2个参数是事件的类型,可以取EV_KEY、EV_REL、EV_ABS等值。第3、4和函数与input_report_key()函数的参数相同。
5.2.2 input_handle_event( )函数
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case SYN_REPORT:
if (!dev->sync) {
dev->sync = 1;
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case SYN_MT_REPORT:
dev->sync = 0;
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
break;
case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) {
if (value != 2) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code);
else
input_stop_autorepeat(dev);
}
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_SW:
if (is_event_supported(code, dev->swbit, SW_MAX) &&
!!test_bit(code, dev->sw) != value) {
__change_bit(code, dev->sw);
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX)) {
if (test_bit(code, input_abs_bypass)) {
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
value = input_defuzz_abs_event(value,
dev->abs[code], dev->absfuzz[code]);
if (dev->abs[code] != value) {
dev->abs[code] = value;
disposition = INPUT_PASS_TO_HANDLERS;
}
}
break;
case EV_REL:
if (is_event_supported(code, dev->relbit, REL_MAX) && value)
disposition = INPUT_PASS_TO_HANDLERS;
break;
case EV_MSC:
if (is_event_supported(code, dev->mscbit, MSC_MAX))
disposition = INPUT_PASS_TO_ALL;
break;
case EV_LED:
if (is_event_supported(code, dev->ledbit, LED_MAX) &&
!!test_bit(code, dev->led) != value) {
__change_bit(code, dev->led);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_SND:
if (is_event_supported(code, dev->sndbit, SND_MAX)) {
if (!!test_bit(code, dev->snd) != !!value)
__change_bit(code, dev->snd);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_REP:
if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) {
dev->rep[code] = value;
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_FF:
if (value >= 0)
disposition = INPUT_PASS_TO_ALL;
break;
case EV_PWR:
disposition = INPUT_PASS_TO_ALL;
break;
}
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
dev->sync = 0;
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
}
该函数想输入子系统传递事件信息。第1个参数是输入设备input_dev,第2个参数是事件的类型,第3个参数是键码,第4个传输是键值。
这个函数主要是根据事件类型的不同,做相应的处理。这里只关心EV_KEY类型,其他函数和事件传递关系不大,只要关心,disposition这个是事件处理的方式,默认的是INPUT_IGNORE_EVENT,忽略这个事件,如果是INPUT_PASS_TO_HANDLERS则是传递给事件处理器,如果是INPUT_PASS_TO_DEVICE,则是传递给设备处理。
5.2.3 input_pass_event( )函数
static void input_pass_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
rcu_read_lock();
handle = rcu_dereference(dev->grab); //如果是绑定的handle,则调用绑定的handler->event函数
if (handle)
handle->handler->event(handle, type, code, value);
else
//如果没有绑定,则遍历dev的h_list链表,寻找handle,如果handle已经打开,说明有进程读取设备关联的evdev。
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle,
type, code, value);
// 调用相关的事件处理器的event函数,进行事件的处理
rcu_read_unlock();
}