1.什么叫输入子系统?
内核的输入子系统是对分散的,多种不同类别的输入设备(如键盘,鼠标,跟踪球,操纵杆,触摸屏,加速计和手写板)等字符设备进行统一处理的一层抽象,就是在字符设备驱动上抽象出的一层。输入子系统包括两类驱动程序:事件驱动程序和设备驱动程序。事件驱动程序负责和应用程序的接口,而设备驱动程序负责和底层输入设备的通信。鼠标事件生成文件mousedev属于事件驱动程序,而PS/2鼠标驱动程序是设备驱动程序。事件驱动程序是标准的,对所有的输入类都是可用的,所以要实现的是设备驱动程序而不是事件驱动程序。设备驱动程序可以利用一个已经存在的,合适的事件驱动程序通过输入核心和用户应用程序接口。
2.输入子系统的架构是怎么样的,分为几层,是怎么实现的?
输入子系统由驱动层、系统核心层(input core)、事件处理层(event handler)三部分组成,
一个输入事件(如点击触屏)通过input driver->input core->event handler->user space 到达用户空间传递给应用程序
在这里input core是核心,linux系统提供的内核接口,作为底层驱动的开发者来说,只需编辑input driver与硬件直接打交道的这一层。那event handler事件处理层是谁来做呢,还没弄懂。---以后要弄懂
3.那首先来看input driver层,我以触屏的来分析:
注册过程:
a.首先通过input_allocate_device来峙湟桓鰅nput_dev接口,并初始化一些基本的成员
b.然后在《》里面初始化一些参数。
c.最后通过input_register_device来注册注册一个input设备
static int fts_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
input_dev = input_allocate_device();//分配一个input_dev接口,并初始化一些基本的成员
.......
》 input_dev->name = "fts_ts";
input_dev->id.bustype = BUS_I2C;//总线类型是I2C
input_dev->dev.parent = &client->dev;//父设备
input_set_drvdata(input_dev, data);//保存data结构到錳nput_dev里面
i2c_set_clientdata(client, data);
__set_bit(EV_KEY, input_dev->evbit);//按键事件,每次触摸都有一个BTN_TOUCH的按键事件
__set_bit(EV_ABS, input_dev->evbit); //绝对坐标事件,触摸屏每次发送的坐标都是绝对坐标,不同于鼠标的相对坐标
__set_bit(BTN_TOUCH, input_dev->keybit);//touch类型按键
__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
input_mt_init_slots(input_dev, pdata->num_max_touches,0);
input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->x_min, pdata->x_max, 0, 0);//这个是设置ad转换的x坐标
》 input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->y_min, pdata->y_max, 0, 0);//这个是设置ad转换的y坐标
...........
err = input_register_device(input_dev);//注册一个input设备
.......
}
static struct i2c_driver fts_ts_driver = {
.probe = fts_ts_probe,
.remove = fts_ts_remove,
.driver = {
.name = "fts_ts",
.owner = THIS_MODULE,
.of_match_table = fts_match_table,
#ifdef CONFIG_PM
.pm = &fts_ts_pm_ops,
#endif
},
.id_table = fts_ts_id,
};
/*******************************************************************************
* Name: fts_ts_init
* Brief:
* Input:
* Output:
* Return:
*******************************************************************************/
static int __init fts_ts_init(void)
{
printk(" ### %s\n",__func__);
return i2c_add_driver(&fts_ts_driver);
}
/*******************************************************************************
* Name: fts_ts_exit
* Brief:
* Input:
* Output:
* Return:
*******************************************************************************/
static void __exit fts_ts_exit(void)
{
printk("%s\n",__func__);
i2c_del_driver(&fts_ts_driver);
}
module_init(fts_ts_init);
module_exit(fts_ts_exit);
4.那下面来看下上述两个重要的函数:input_allocate_device()和input_register_device()
a.input_allocate_device()
这个函数也就是做一些内存分配和一些基本的初始化
struct input_dev *input_allocate_device(void)
{
struct input_dev *dev;
dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);//分配一个 input_dev 结构体,并初始化为 0
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;
}b.
input_register_device()
input_register_device()用于注册一个输入设备。那么注册过程是怎样的呢?这是一个重点,我们在下面的代码中进行注释分析:
- 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类中;
- 把输入设备挂到输入设备链表input_dev_list中;
- 查找并匹配输入设备对应的事件处理层,通过input_handler_list链
- input设备在增加到input_dev_list链表上之后,会查找
- * input_handler_list事件处理链表上的handler进行匹配,这里的匹配
- * 方式与设备模型的device和driver匹配过程很相似,所有的input
- * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list
- * 上,进行“匹配相亲
这里简单了解到这就可以了,时间了;
那注册完是不是就要开始上报:
触摸屏驱动程序上报事件的函数为:
们需要再分析下这个匹配的过程,“相亲”这种事情还是很有意思的,但是需要注意的是下面分析的代码是我们暂时无法分析的,因为那样会使得情况变得更加复杂,当我们从应用层往下分析的时候一切都会明白。input_attach_handler匹配过程如下:
我们先来看下input_match_device()函数,看一下这个匹配的条件是什么,如何匹配的过程是怎样的,匹配的结果会是什么?
既然证明是合适的,接下来就应该登记注册,并公证了。还记得handler->connect(handler, dev, id)函数吧,当input_match_device()找到最合适的事件处理层驱动时,便执行handler->connect函数进行公证了,看下面这部分代码(假如说找到了evdev类型的驱动,在input/evdev.c中):
通过上述代码的执行,最终,输入设备在input_register_handle()的关联下与已经匹配上的handler结合,代码如下:
以上是输入设备驱动注册的全过程,牵涉的代码比较多,需要从宏观上理顺。纵观整个过程,输入设备驱动最终的目的就是能够与事件处理层的事件驱动相互匹配,但是在drivers/input目录下有evdev.c事件驱动、mousedev.c事件驱动、joydev.c事件驱动等等,我们的输入设备产生的事件应该最终上报给谁,然后让事件驱动再去处理呢?知道了这么个原因再看上面代码就会明白,其实evdev.c、mousedev.c等根据硬件输入设备的处理方式的不同抽象出了不同的事件处理接口帮助上层去调用,而我们写的设备驱动程序只不过是完成了硬件寄存器中数据的读写,但提交给用户的事件必须是经过事件处理层的封装和同步才能够完成的,事件处理层提供给用户一个统一的界面来操作。
由于以上的这些原因,才有了上述代码的关联过程,通过下图7,看一下整个关联注册的过程:

图7 input_dev与handler关联图
通过上图我们可以看到input输入设备匹配关联的关键过程以及涉及到的关键函数和数据。这一节主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联,下一节将从应用层的角度分析事件的接受过程和处理过程以及三层之间是如何配合处理输入事件的。
本文介绍了Linux内核输入子系统的架构,包括驱动层、系统核心层和事件处理层。主要关注设备驱动程序的注册过程,如通过input_allocate_device和input_register_device函数进行设备初始化和注册。输入事件通过input driver -> input core -> event handler -> user space传递到用户空间。文章以触屏驱动为例,详细解析了输入设备驱动的注册流程,并探讨了input_match_device函数在匹配事件处理层中的作用。最后,讨论了输入设备驱动与事件处理层如何关联,为后续的事件接受和处理奠定基础。

1366

被折叠的 条评论
为什么被折叠?



