Android、qt等众多应用对于linux系统中键盘、鼠标、触摸屏等输入设备的支持都通过、或越来越倾向于标准的input输入子系统。因为input子系统已经完成了字符驱动的文件操作接口,所以编写驱动的核心工作是完成input系统留出的接口,工作量不大。
1 Input子系统框架
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(InputCore)和输入子系统事件处理层(EventHandler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件的状态变化转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口以及对驱动层提交来的事件进行处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。下图是input输入子系统框架,一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler ->userspace 到达用户空间传给应用程序。
2 框架分析
2.1 Input core提供Driver接口
在Linux中,Input设备用input_dev结构体描述,定义在input.h中。
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_slot *mt;
int mtsize;
int slot;
int trkid;
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;
bool sync;
struct device dev;
struct list_head h_list;
struct list_head node;
};
设备的驱动只需按照如下步骤就可实现了。
1).在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
2).将Input设备注册到input子系统中;
3).在Input设备发生输入操作时(如:键盘被按下/抬起、检测到gsensor状态、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
软件的设计流程如下图:
Input core 为Input driver提供的主要接口如下:
(1)申请分配Input_dev结构体
struct input_dev *input_allocate_device(void) |
(2)向Input子系统注册新的设备
int input_register_device(struct input_dev *dev) |
(3)注销一个input设备
void input_unregister_device(struct input_dev *dev) |
(4) 向Input子系统报告新事件
用于报告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) 上报坐标值结束的同步信号: static inline void input_sync(struct input_dev *dev) |
2.2 初始化 Input core
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
.llseek = noop_llseek,
};
static int __init input_init(void)
{
err = class_register(&input_class);
……
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
……
}
static void __exit input_exit(void)
{
……
unregister_chrdev(INPUT_MAJOR, "input");
class_unregister(&input_class);
……
}
subsys_initcall(input_init);
module_exit(input_exit);
上面是标准的linux 驱动程序编写,首先静态初始化file_operations结构体,然后在input_init()中完成驱动程序的注册,创建了主设备好为 INPUT_MAJOR=13,设备节点名称为“Input”,代码看起来非常的简单。
2.3初始化 Event Handler
这里我们那evdev.c作为例子,代码如下:
static const struct file_operations evdev_fops = { //向用户空间提供的接口
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
static struct input_handler evdev_handler = {
.event = evdev_event, //向系统报告input事件,系统通过read方法读取
.connect = evdev_connect, //和input_dev匹配后调用connect构建
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //event设备文件的操作方法
.minor = EVDEV_MINOR_BASE,
.name = "evdev", //次设备号基准值
.id_table = evdev_ids, //匹配规则
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler);
}
module_init(evdev_init);
module_exit(evdev_exit);
这里跟Input Core的初始化有点类似,首先也需要静态初始化file_operations结构体,里面定义的跳转接口是提供给用户空间调用的,其次是静态初始化input_handler结构体,用于向Input Core注册的,当内核加载该模块的时候,调用evdev_init()方法,然后调用input.c的方法int input_register_handler(structinput_handler *handler)向input core注册一个新的input Handler。代码如下:
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval;
……
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler;
}
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
}
其实完成的是,将input_handler添加到input_handler_list列表中。那么这个列表什么时候被用到的呢?就是在input driver注册的时候,也会将input_dev添加到列表input_dev_list中,然后会完成一个input_dev与input_handler之间的一个匹配。
2.4 Driver与Event Handler匹配
每个Driver都有一个Event Handler与之对应,在2.3点的时候,我们讲到,在Event Handler初始化的时候向input core注册,将input_handler加入到input_handler_list列表中,在driver注册的时候,需要做一个匹配。
int input_register_device(struct input_dev *dev)
{ ……
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
…….
error = device_add(&dev->dev);
……
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
……
return 0;
}
将input device 挂到input_dev_list链表上.然后,对每一个挂在input_handler_list的handler调用input_attach_handler()进行匹配。在这里的情况有好比设备模型中的device和driver的匹配。所有的input device都挂在input_dev_list链上。所有的handle都挂在input_handler_list上。
看一下这个匹配的详细过程。匹配是在input_attach_handler()中完成的。代码如下:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
…….
}
其实需要匹配的handle->id和dev->id中的数据。如果匹配成功,则调用handler->connect()。
2.5 创建新的evdev设备(以evdev为例子)
Input_dev和input_handler匹配后调用input_handler的connect。以evdev_handler为例:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
for (minor = 0; minor < EVDEV_MINORS; minor++) //
if (!evdev_table[minor])
break;
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//为每个匹配的nput_hanlder的设备创建一个evdev
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
dev_set_name(&evdev->dev, "event%d", minor);
evdev->exist = true;
evdev->minor = minor;
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev);//创建设备节点
if (error)
goto err_cleanup_evdev;
return 0;
}
参考:
http://www.cnblogs.com/cute/archive/2011/08/30/2159305.html
http://www.360doc.com/content/07/0430/10/7821_474910.shtml