这篇笔记的目的不是把input子系统应用起来,而是解答我自己在学习使用input子系统的时候心里的一些疑惑。供有相同疑惑的朋友参考。其中可能有错误或理解不正确之处,欢迎批评指正。
输入子系统核心层 driver/input/input.c 文件里做了如下工作:
入口函数 input_init()里面调用register_chardev(INPUT_MAJOR,"input",&input_fops) 注册了字符设备input,主设备号为INPUT_MAJOR=13,对应的file_operation 为input_fops,查看input_fops结构体的初始化,发现其仅仅填充了open()函数,这个open()函数是input_open_file(),即input_fops->open = input_open_file()。
至此还没有创建设备节点(设备节点的创建过程,下面再分析),应用程序还不能通过open()函数 调用到 驱动的 input_open_file().但我们现在可以知道最终应用程序通过open 打开主设备号为 13 的字符设备时,就会调用到 input_open_file(),我们先看一看static int input_open_file(struct inode *inode, struct file *file)里面做了什么事情:
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;
int err;
if (!handler || !(new_fops = fops_get(handler->fops)))
return -ENODEV;
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
抽出三行关键代码阅读:
struct input_handler *handler = input_table[iminor(inode) >> 5];
if (!handler || !(new_fops = fops_get(handler->fops)))
file->f_op = new_fops;
第一行:用iminor(inode)取出次设备号(主设备号是INPUT_MAJOR=13),再iminor(inode) >> 5,即次设备号右移 5 位,也即次设备号除以32,用这个结果值作为数组下标,取出对应的数组元素input_table[iminor(inode) >> 5],然后赋给handler: *handler = input_table[iminor(inode) >> 5];
这里有两点要说一说:
1:右移5位有什么含义?
最多可以有32个不同的次设备号取出的是同一个数组元素input_table[n]。或者说同一个数组元素input_table[n],或者同一个handler 最多可以有32个不同的输入设备与之对应。
2:数组input_table 的数据类型 与handler 一样,是struct input_handler 类型,他在哪里被谁构造好?
它是输入子系统事件处理层构造好的,用户无需关心。具体是在 driver/input 目录下的evdev.c keyboard.c mousedev.c 等文件里 通过调用 input_register_handler(evdev_handler), input_register_handler(keyboard_handler)等来构造input_table[]数组,就是把已经初始好了的 struct input_handler 类型结构体 evdev_handler,keyboard_handler等填充到input_table 数组。简单说就是 初始化input_handler 类型结构体,然后向输入子系统核心注册,这一过程输入子系统自动完成。
具体的初始化过程和注册过程,本人正在搞得头晕晕的(后面我也准备写篇笔记),但意义也不是很大,所以不建议大家花太多时间钻研它。(实际上 内核里写了 static struct input_handler *input_table[8];即最多8 个元素,我看了源码,好像这8个元素并没有填充完,并且元素 input_table[0] 被多个文件初始化,搞不懂这里。)
第二行和第三行代码:if (!handler || !(new_fops = fops_get(handler->fops)));file->f_op = new_fops;结果是file->f_op = fops_get(handler->fops),即我们打开的文件file,其f_op字段现在才被填充,被输入子系统事件处理层构造好的handler->fops 填充。到现在应该明白应用层对设备文件file的操作集就是这个系统构造好的handler->fops.不同的设备文件可能对应不同的handler,其fops也不同。但handler总数不超过8个(input_table[8]),因为输入设备发生的事件类型就那么几种。
画一下过程:
input_init()
register_chrdev(INPUT_MAJOR,"input",input_fops)
input_fops->open = input_open_file(struct inode *inode, struct file *file)
struct input_handler *handler = input_table[iminor(inode)>>5]
file->fops = fops_get(handler->fops)
现在总结一下,应用程序通过open 打开主设备号为 13 的字符设备时,就会调用到input_open_file(),然后对设备的读写等操作是通过 input_table[]数组某个元素的fops操作的。具体是哪一个元素的fops 与次设备号有关。相同类型的设备用的是相同的input_table[]数组里的元素,即相同的handler,也即相同的fops.但是,现在这个fops 还没有与具体的设备建立联系,还不能操作设备,并且到现在设备节点还没有创建。
具体设备与系统构建好的handler 建立联系的过程发生在注册输入设备的时候,其实就是发生在调用int input_register_device(struct input_dev *dev)函数的时候(这说法虽然片面,但实际应用中应该大多数是这样的)。传入的参数是一个输入设备结构体,这个结构体要我们分配和设置好一些必要的内容后再调用int input_register_device(struct input_dev *dev)向输入子系统注册。下面参考了网友对input_register_device的分析。http://blog.csdn.net/happyguys12345/article/details/53487921
int input_register_device(struct input_dev *dev)
{
/* 用于记录输入设备名称的索引值 */
static atomic_t input_no = ATOMIC_INIT(0);
/* 输入事件的处理接口指针,用于和设备的事件类型进行匹配 */
struct input_handler *handler;
const char *path;
int error;
/* 默认所有的输入设备都支持EV_SYN同步事件 */
set_bit(EV_SYN, dev->evbit);
/*
* 如果设备驱动没有指定重复按键(连击),系统默认提供以下的支持
* 其中init_timer为连击产生的定时器,时间到调用input_repeat_key函数
* 上报,REP_DELAY用于设置重复按键的键值,REP_PERIOD设置延时时间
*/
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子系统设备链表input_dev_list上 */
list_add_tail(&dev->node, &input_dev_list);
/* 动态获取input设备的ID号,名称为input*,其中后面的“*”动态获得,唯一的 */
snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
/* 如果这个值没有设置,系统把输入设备挂入设备链表 */
if (!dev->cdev.dev)
dev->cdev.dev = dev->dev.parent;
/* 在/sys目录下创建设备目录和文件 */
error = class_device_add(&dev->cdev);
if (error)
return error;
/* 获取并打印设备的绝对路径名称 */
path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);
printk(KERN_INFO "input: %s as %s\n",
dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);
/* 核心重点,input设备在增加到input_dev_list链表上之后,会查找
* input_handler_list事件处理链表上的handler进行匹配,这里的匹配
* 方式与设备模型的device和driver匹配过程很相似,所有的input
* 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list
* 上,进行“匹配相亲”*/
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
return 0;
}
经过 input_register_device(struct input_dev *dev)后,系统已经根据 struct input_dev *dev 提供的一些信息创建了设备节点,并且在经过最后三行代码
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
后,成功与handler 匹配。那么应用层就可以通过handler 操作设备了。
下一篇分析匹配过程,有兴趣的朋友可以参考看一看。