android底层驱动学习之linux输入子系统的理解

本文介绍了Linux内核输入子系统的架构,包括驱动层、系统核心层和事件处理层。主要关注设备驱动程序的注册过程,如通过input_allocate_device和input_register_device函数进行设备初始化和注册。输入事件通过input driver -> input core -> event handler -> user space传递到用户空间。文章以触屏驱动为例,详细解析了输入设备驱动的注册流程,并探讨了input_match_device函数在匹配事件处理层中的作用。最后,讨论了输入设备驱动与事件处理层如何关联,为后续的事件接受和处理奠定基础。

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);


至此,input drive就完成了。

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()用于注册一个输入设备。那么注册过程是怎样的呢?这是一个重点,我们在下面的代码中进行注释分析:

[cpp]  view plain  copy
  1. int input_register_device(struct input_dev *dev)  
  2. {  
  3.     /* 用于记录输入设备名称的索引值 */  
  4.     static atomic_t input_no = ATOMIC_INIT(0);  
  5.     /* 输入事件的处理接口指针,用于和设备的事件类型进行匹配 */  
  6.     struct input_handler *handler;  
  7.     const char *path;  
  8.     int error;  
  9.   
  10.     /* 默认所有的输入设备都支持EV_SYN同步事件 */  
  11.     set_bit(EV_SYN, dev->evbit);  
  12.   
  13.     /* 
  14.      * 如果设备驱动没有指定重复按键(连击),系统默认提供以下的支持 
  15.      * 其中init_timer为连击产生的定时器,时间到调用input_repeat_key函数 
  16.      * 上报,REP_DELAY用于设置重复按键的键值,REP_PERIOD设置延时时间 
  17.      */  
  18.     init_timer(&dev->timer);  
  19.     if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {  
  20.         dev->timer.data = (long) dev;  
  21.         dev->timer.function = input_repeat_key;  
  22.         dev->rep[REP_DELAY] = 250;  
  23.         dev->rep[REP_PERIOD] = 33;  
  24.     }  
  25.   
  26.     /* 如果设备驱动没有设置自己的获取键值的函数,系统默认 */  
  27.     if (!dev->getkeycode)  
  28.         dev->getkeycode = input_default_getkeycode;  
  29.   
  30.     /* 如果设备驱动没有指定按键重置函数,系统默认 */  
  31.     if (!dev->setkeycode)  
  32.         dev->setkeycode = input_default_setkeycode;  
  33.   
  34.     /* 重要,把设备挂到全局的input子系统设备链表input_dev_list上 */  
  35.     list_add_tail(&dev->node, &input_dev_list);  
  36.   
  37.     /* 动态获取input设备的ID号,名称为input*,其中后面的“*”动态获得,唯一的 */  
  38.     snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),  
  39.          "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);  
  40.   
  41.     /* 如果这个值没有设置,系统把输入设备挂入设备链表 */  
  42.     if (!dev->cdev.dev)  
  43.         dev->cdev.dev = dev->dev.parent;  
  44.   
  45.     /* 在/sys目录下创建设备目录和文件 */  
  46.     error = class_device_add(&dev->cdev);  
  47.     if (error)  
  48.         return error;  
  49.   
  50.     /* 获取并打印设备的绝对路径名称 */  
  51.     path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);  
  52.     printk(KERN_INFO "input: %s as %s\n",  
  53.         dev->name ? dev->name : "Unspecified device", path ? path : "N/A");  
  54.     kfree(path);  

  55.   
  56.     /* 核心重点,input设备在增加到input_dev_list链表上之后,会查找 
  57.      * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 
  58.      * 方式与设备模型的device和driver匹配过程很相似,所有的input 
  59.      * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list 
  60.      * 上,进行“匹配相亲”*/  
  61.     list_for_each_entry(handler, &input_handler_list, node)  
  62.         input_attach_handler(dev, handler);  
  63.   
  64.     input_wakeup_procfs_readers();  
  65.   
  66.     return 0;  
  67. }  

上面的代码主要的功能有以下几个功能,也是设备驱动注册为输入设备委托内核做的事情:

  • 进一步初始化输入设备,例如连击事件;
  • 注册输入设备到input类中;
  • 把输入设备挂到输入设备链表input_dev_list中;
  • 查找并匹配输入设备对应的事件处理层,通过input_handler_list链
    1. input设备在增加到input_dev_list链表上之后,会查找 
    2.      * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 
    3.      * 方式与设备模型的device和driver匹配过程很相似,所有的input 
    4.      * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list 
    5.      * 上,进行“匹配相亲


这里简单了解到这就可以了,时间了;


那注册完是不是就要开始上报:

触摸屏驱动程序上报事件的函数为:

[cpp]  view plain  copy
  1. input_report_abs(dev,ABS_X, s3c2440_ts->tc.xp);  
  2. input_report_abs(dev,ABS_Y, s3c2440_ts->tc.yp);  
  3. input_report_abs(dev,ABS_PRESSURE, s3c2440_ts->tc.pressure);  
  4. input_report_key(dev,BTN_TOUCH, s3c2440_ts->pendown);  
  5. input_sync(dev);  


如果还想深入了解match的过程,可以看一下内容:


们需要再分析下这个匹配的过程,“相亲”这种事情还是很有意思的,但是需要注意的是下面分析的代码是我们暂时无法分析的,因为那样会使得情况变得更加复杂,当我们从应用层往下分析的时候一切都会明白。input_attach_handler匹配过程如下:

[cpp]  view plain  copy
  1. const struct input_device_id *id;  
  2.     int error;  
  3.   
  4. /* 如果handler的blacklist被赋值了并且则优先匹配 */  
  5.     if (handler->blacklist && input_match_device(handler->blacklist, dev))  
  6.         return -ENODEV;  
  7.   
  8.     /* 否则利用handler->id_table和dev进行匹配,后面讲述匹配什么和过程 */  
  9.     id = input_match_device(handler->id_table, dev);  
  10.     if (!id)  
  11.         return -ENODEV;  
  12.   
  13.     /* 这是一根“红线”,虽然你可能觉的是黑色的,但不可否认,他们真的匹配上了 
  14.          * 调用handler->connnect函数进行匹配,匹配详细过程后面讲述 
  15.         */  
  16.     error = handler->connect(handler, dev, id);  
  17.     if (error && error != -ENODEV)  
  18.         printk(KERN_ERR  
  19.             "input: failed to attach handler %s to device %s, "  
  20.             "error: %d\n",  
  21.             handler->name, kobject_name(&dev->cdev.kobj), error);  
  22.   
  23.     return error;  

我们先来看下input_match_device()函数,看一下这个匹配的条件是什么,如何匹配的过程是怎样的,匹配的结果会是什么?

[cpp]  view plain  copy
  1. /* 事件处理层中的对应flags如果设置或者driver_info被设置则进行匹配 */  
  2.     for (; id->flags || id->driver_info; id++) {  
  3.         /* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID 
  4.           如果没有匹配上将进行MATCH_BIT匹配 */  
  5.         if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)  
  6.             if (id->bustype != dev->id.bustype)  
  7.                 continue;  
  8.   
  9.         if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)  
  10.             if (id->vendor != dev->id.vendor)  
  11.                 continue;  
  12.   
  13.         if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)  
  14.             if (id->product != dev->id.product)  
  15.                 continue;  
  16.   
  17.         if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)  
  18.             if (id->version != dev->id.version)  
  19.                 continue;  
  20.           
  21.         /* MATCH_BIT用于匹配设备驱动中是否设置了这些为,MATCH_BIT的宏 
  22.         * 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的 
  23.         * 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL 
  24.         */  
  25.         MATCH_BIT(evbit,  EV_MAX);  
  26.         MATCH_BIT(keybit, KEY_MAX);  
  27.         MATCH_BIT(relbit, REL_MAX);  
  28.         MATCH_BIT(absbit, ABS_MAX);  
  29.         MATCH_BIT(mscbit, MSC_MAX);  
  30.         MATCH_BIT(ledbit, LED_MAX);  
  31.         MATCH_BIT(sndbit, SND_MAX);  
  32.         MATCH_BIT(ffbit,  FF_MAX);  
  33.         MATCH_BIT(swbit,  SW_MAX);  
  34.   
  35.         return id;  
  36.     }  
  37.   
  38.     return NULL;  

既然证明是合适的,接下来就应该登记注册,并公证了。还记得handler->connect(handler, dev, id)函数吧,当input_match_device()找到最合适的事件处理层驱动时,便执行handler->connect函数进行公证了,看下面这部分代码(假如说找到了evdev类型的驱动,在input/evdev.c中):

[cpp]  view plain  copy
  1. struct evdev *evdev;  
  2.     struct class_device *cdev;  
  3.     dev_t devt;  
  4.     int minor;  
  5.     int error;  
  6.   
  7.     /* EVDEV_MINORS为32,代表共能容纳32个evdev事件层设备,下面代码在找到空的地方,用于保存evdev事件层的数据,即上面定义的evdev */  
  8.     for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);  
  9.     /* 这说明内核已经没办法再分配这种类型的设备了 */  
  10.     if (minor == EVDEV_MINORS) {  
  11.         printk(KERN_ERR "evdev: no more free evdev devices\n");  
  12.         return -ENFILE;  
  13.     }  
  14.     /* 开始给evdev事件层驱动分配空间了 */  
  15.     evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);  
  16.     if (!evdev)  
  17.         return -ENOMEM;  
  18.   
  19.     /* 初始化client_list列表和evdev_wait队列,后面介绍 */  
  20.     INIT_LIST_HEAD(&evdev->client_list);  
  21.     init_waitqueue_head(&evdev->wait);  
  22.   
  23.     /* 初始化evdev结构体,其中handle为输入设备和事件处理的关联接口 */  
  24.     evdev->exist = 1;  
  25.     evdev->minor = minor;  
  26.     evdev->handle.dev = dev;  
  27.     evdev->handle.name = evdev->name;  
  28.     evdev->handle.handler = handler;  
  29.     evdev->handle.private = evdev;  
  30.     sprintf(evdev->name, "event%d", minor);  
  31.   
  32.     /* 重要,上层访问时通过次设备号找到事件处理的接口 */  
  33.     evdev_table[minor] = evdev;  
  34.   
  35.     /* evdev事件设备的此设备号的基准值INPUT_MAJOR, EVDEV_MINOR_BASE */  
  36.     devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),  
  37.   
  38.     /* 创建用户事件驱动层设备访问接口/dev/input/event* */  
  39.     cdev = class_device_create(&input_class, &dev->cdev, devt,  
  40.                    dev->cdev.dev, evdev->name);  
  41.     if (IS_ERR(cdev)) {  
  42.         error = PTR_ERR(cdev);  
  43.         goto err_free_evdev;  
  44.     }  
  45.   
  46.     /* 提供/sys目录的用户空间接口 */  
  47.     error = sysfs_create_link(&input_class.subsys.kobj,  
  48.                   &cdev->kobj, evdev->name);  
  49.     if (error)  
  50.         goto err_cdev_destroy;  
  51.   
  52.     /* input_dev设备驱动和handler事件处理层的关联,由handle完成 */  
  53.     error = input_register_handle(&evdev->handle);  

通过上述代码的执行,最终,输入设备在input_register_handle()的关联下与已经匹配上的handler结合,代码如下:

[cpp]  view plain  copy
  1. struct input_handler *handler = handle->handler;  
  2.     /* 将d_node链接到输入设备的h_list,h_node链接到事件层的h_list链表上 
  3.     * 因此,在handle中是输入设备和事件层的关联结构体,通过输入设备可以 
  4.     * 找到对应的事件处理层接口,通过事件处理层也可找到匹配的输入设备 
  5.     */  
  6.     list_add_tail(&handle->d_node, &handle->dev->h_list);  
  7.     list_add_tail(&handle->h_node, &handler->h_list);  
  8.   
  9.     /* 如果start函数有定义则调用,但是evdev结构体中并未初始化这个函数 */  
  10.     if (handler->start)  
  11.         handler->start(handle);  

以上是输入设备驱动注册的全过程,牵涉的代码比较多,需要从宏观上理顺。纵观整个过程,输入设备驱动最终的目的就是能够与事件处理层的事件驱动相互匹配,但是在drivers/input目录下有evdev.c事件驱动、mousedev.c事件驱动、joydev.c事件驱动等等,我们的输入设备产生的事件应该最终上报给谁,然后让事件驱动再去处理呢?知道了这么个原因再看上面代码就会明白,其实evdev.c、mousedev.c等根据硬件输入设备的处理方式的不同抽象出了不同的事件处理接口帮助上层去调用,而我们写的设备驱动程序只不过是完成了硬件寄存器中数据的读写,但提交给用户的事件必须是经过事件处理层的封装和同步才能够完成的,事件处理层提供给用户一个统一的界面来操作。

由于以上的这些原因,才有了上述代码的关联过程,通过下图7,看一下整个关联注册的过程:


图7  input_dev与handler关联图

通过上图我们可以看到input输入设备匹配关联的关键过程以及涉及到的关键函数和数据。这一节主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联,下一节将从应用层的角度分析事件的接受过程和处理过程以及三层之间是如何配合处理输入事件的。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值