引言
本章概述linux kernel输入子系统框架,理解input各层的作用和实现的意义。
输入子系统框架
输入子系统是将所有输入设备进行分类,并将其驱动分为事件处理层和设备描述层。
其中,设备描述层,来描述本设备所能够产生的事件,而对应的事件处理层,对设备产生的事件进行处理。在内核中对应的结构体为input_handler和input_device,二者注册进内核,并通过id_table进行匹配。
当例如按键按下后,其通过input_device产生了一个事件,该事件进入input核心层,找到该事件对应的input_handler,如果上层进行了系统调用,则调用input_handler中fops的相应接口。
输入子系统分为:核心层、事件驱动层和设备(描述)层。
核心层
内核提供给输入子系统核心代码,它提供驱动层和设备层所需的一些接口。
核心层相关代码分析如下:
Input.c:
Input_init:
Register_chrdev(INPUT_MAJOR, "input", &input_fops);
注册输入子系统核心层的fops至内核链表,该fops定义为:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
该fops只有open,上层open时将进入该open函数
Input_open_file:
//该函数参数为设备号inode和file结构体
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *new_fops = fops_get(handler->fops);
//将从input_table中获得的handler结构体里面的fops,赋值给file的fops,并执行,这里相当于替换了输入子系统核心层的fops
file->f_op = new_fops;
err = new_fops->open(inode, file);
//综上,上层Open(Cat/hexdump/app)输入子系统设备,实际执行的是input_table中的handler,handler中的fops,fops中的open函数
input_table中的handler怎么来的?
input_table为静态全局变量,只在该input.c中被使用,查找可得赋值是在:
int input_register_handler(struct input_handler *handler):
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler;
}
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
通过input_register_handler将input_handler结构体插入input_tabel表中。循环读取input_dev_list中的dev成员,调用input_attach_handler判断handler中的id_table能否支持这个dev。
匹配过程如下:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
id = input_match_device(handler->id_table, dev);
error = handler->connect(handler, dev, id);
}
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;
MATCH_BIT(evbit, EV_MAX);
for (i = 0; i < BITS_TO_LONGS(EV_MAX); i++){
//如果 id 的支持项 > = dev MATCH_BIT 下一项,或者id==0 进行下一项
if ((id->evbit[i] & dev->evbit[i]) != id->evbit[i])
break;
}
if (i != BITS_TO_LONGS(EV_MAX))
continue;
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;
}
总结匹配过程即:将input_dev中的flag和evbit,同input_handler中的id_table进行比较。
如果input_handler中的id_table为NULL,例如evdev.c,那么由input_match_device可知,它可以和任何input_dev相匹配。
那么,假设有一个按键设备,该设备将同时和evdev以及keyboard的handler匹配上。即多个dev可以和一个handler匹配。
事件处理层(input_handler层)
针对input_dev中发起的事件作出相应。
以事件驱动输入子系统evdev.c为例:
//入口函数:
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
//evdev的handler结构体:
static struct input_handler evdev_handler = {
.event = evdev_event, //事件上报
.connect = evdev_connect, //连接dev和handler
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //文件操作接口
.minor = EVDEV_MINOR_BASE, //event0的次设备号
.name = "evdev",
.id_table = evdev_ids, //设备id
};
//设备id:
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
其中,struct input_device_id *id_table用于和dev中的xxbit进行比较,来判断是否匹配。
如此,在evdev_init中完成了evdev的handler的注册
=====================================
此外,一个设备dev可以被多个handler匹配,从而执行相应的kbd_event
keyboard.c:
static struct input_handler kbd_handler = {
.event = kbd_event,
.connect = kbd_connect,
.disconnect = kbd_disconnect,
.start = kbd_start,
.name = "kbd",
.id_table = kbd_ids,
};
static const struct input_device_id kbd_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT(EV_KEY) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT(EV_SND) },
},
{ }, /* Terminating entry */
};
input_dev层
一般情况下,这里是我们需要开发的代码。
linux内核支持的输入设备有:鼠标、按键、触摸屏、手柄等。这些设备对应的驱动已经存在在内核中了。
我们需要做的是写一个input_dev,该input_dev中设定了设备会向输入子系统中输入什么事件。
示例:
/* 参考drivers\input\keyboard\gpio_keys.c */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}
static int buttons_init(void)
{
int i;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();;
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
/* 3. 注册 */
input_register_device(buttons_dev);
/* 4. 硬件相关的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");