输入子系统学习笔记之源码分析3_流程分析

看一个驱动程序的时候一般是从入口函数开始看,输入子系统的核心实现函数是 input.c,入口函数是input_init(),因此要分析输入子系统那么就从input_init()函数开始,input_init()代码如下:

static int __init input_init(void)
{
	int err;
    //创建一个类input_class
	err = class_register(&input_class);
	if (err) {
		printk(KERN_ERR "input: unable to register input_dev class\n");
		return err;
	}
    //在/proc下创建入口项
	err = input_proc_init();
	if (err)
		goto fail1;
    // 注册设备号INPUT_MAJJOR的设备,记住input子系统的设备的主设备号是13,即INPUT_MAJOR
	//  为13,并与input_fops相关联
	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
	if (err) {
		printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
		goto fail2;
	}

	return 0;

 fail2:	input_proc_exit();
 fail1:	class_unregister(&input_class);
	return err;
}

在入口函数里面创建了一个input_class类,主要作用就是在sys目录里面创建一些节点,比如cd到/sys/class下面可以看到这一类的设备,与这个相关的就是一些kobjects。当然对于一个新设备,可以注册进一个class也可以不注册进去,如果存在对应class的话注册进去更好。另外在/proc创建了入口项,这样就可以/proc目录看到input的信息,然后就是设备驱动函数里面都需要的注册设备,可以看出输入子系统的主设备号是13,在这里并没有生成设备文件,从前面的分析可知,在输入子系统中事件处理层负责生成与应用程序的设备文件接口,因此生成设备文件的工作应该是位于事件处理层上。这里还要注意一点是主设备号与input_fops关联。下面看看输入子系统的file_operation,如下:

static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};

这里只有一个open函数input_open_file,当应用程序打开输入设备文件时将调用这个函数,但是这里没有实现读写函数,在input_open_file里面可能做了一些工作,那么可以继续跟踪input_open_file函数,代码如下:

static int input_open_file(struct inode *inode, struct file *file)
{
    /* 依据次设备号找到事件处理程序 handler句柄*/
	struct input_handler *handler = input_table[iminor(inode) >> 5];
	const struct file_operations *old_fops, *new_fops = NULL;
	int err;

	/* No load-on-demand here? */
	/* 得到handler 里面的file_operation替代当前的file_operation*/
	if (!handler || !(new_fops = fops_get(handler->fops)))
		return -ENODEV;

	/*
	 * That's _really_ odd. Usually NULL ->open means "nothing special",
	 * not "no device". Oh, well...
	 */
	if (!new_fops->open) {
		fops_put(new_fops);
		return -ENODEV;
	}
	old_fops = file->f_op;
	file->f_op = new_fops;
    /* 调用handler里面的open函数*/
	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;
}

static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};

     从input_open_file 函数里面就可知道,在里面做了一个很重要替换工作,那就是根据设备文件的次设备号找到相应的handler,然后将handler里面file_operation替换当前的file_operation,因此以后open,read,write等将调用handler里面的open,read,write。这是一个非常有用的编程思想,统一了入口,然后根据一些条件选择相应的分支。这里要知道的handler怎么来了,那么就得知道input_table是由谁构建的,搜索代码可知input_table是由input_register_handler函数调用,input_register_handler是一个向上注册函数,也就是向核心层注册handler,那么是由谁调用input_register_handler呢?搜索代码可知input_register_handler由evdev.c,mousedev.c等文件调用,而这些文件一个文件实现了一个handler,每一个文件都向核心层注册,这些handler就是事件处理层的一个驱动,因此这个层次关系就出来了。可以打开evdev.c看看这个向上注册函数,如下:

static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}

在里面初始化了一个handler结构体,查看这个结构体可知,里面有个evdev_fops结构体,里面已经实现了read,write,poll,fasync等函数,以前这个结构体需要我们自己实现,现在内核已经帮我们做好了,其中evdev.c 中minor = EVDEV_MINOR_BASE(64),有input_table[minor >> 5可知,evdev这个事件处理驱动位于input_table的第二项。现在假设应用程序去读这个设备,那么过程如下:

input_open_file  —>   fops  —>   handler—>fops   —>read

这边的handler是一个纯软件的概念,所谓的handler是处理者的意思,那么它处理谁呢? 那就是设备,设备代表硬件! handler 需要向上注册,dev同样也是向上注册,这样input核心层,才能管理设备和事件处理层,才能作为两者的桥梁!那么这里的hander 和 dev又是怎么建立连接的呢?也就是这个hander这个事件处理能不能支持这个设备呢? 在handler 结构体里面有一项是id_table,这个就是表示handler支持的设备,比如说evdev里面 id_table为evdev_ids,如下:

static const struct input_device_id evdev_ids[] = {
    { .driver_info = 1 },    /* Matches all devices */
    { },            /* Terminating zero entry */
};

这个表示evdev 这个handler支持所有的设备,也就是所有的输入设备它都可以处理。在handler结构体里面有一项connect,如果支持这个设备,就会调用这个函数。这个分析input_register_dev可知,input_register_dev所做的事如下:

注册输入设备:
input_register_device
	// 放入链表
	list_add_tail(&dev->node, &input_dev_list);
	
	// 对于每一个input_handler,都调用input_attach_handler
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev

同时可以看看handler的注册过程:

注册input_handler:
input_register_handler
	// 放入数组
	input_table[handler->minor >> 5] = handler;
	
	// 放入链表
	list_add_tail(&handler->node, &input_handler_list);

	// 对于每个input_dev,调用input_attach_handler
	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
	

input_attach_handler
	id = input_match_device(handler->id_table, dev);
	
	error = handler->connect(handler, dev, id);

由代码可知注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用input_handler的connect函数建立"连接"。那么怎么建立连接呢?不同的handler建立连接的过程可能不一样,那么以evdev.c里面的connect函数为例,说明下分析该函数,得知过程如下:
1. 分配一个input_handle结构体,需要注意的是input_handle没有r,然后对这个handle进行设置
2.
    input_handle.dev = input_dev;  // 指向左边的input_dev
    input_handle.handler = input_handler;  // 指向右边的input_handler
3. 注册:
   input_handler->h_list = &input_handle;
   inpu_dev->h_list      = &input_handle;
evdev_connect
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle 
    // 设置
    evdev->handle.dev = dev;  // 指向左边的input_dev
    evdev->handle.name = evdev->name;
    evdev->handle.handler = handler;  // 指向右边的input_handler
    evdev->handle.private = evdev;
   
    // 注册,注册的过程是把handle 放在dev和handler里面的h_list链表里面,这样的结果是dev可以通过

   // handle找到handler,handler可以通过handle找到dev
    error = input_register_handle(&evdev->handle);

这个过程的结果就是建立连接的过程,要实现的目地如下图所示:

image

 

输入子系统核心实现的功能不仅如此,现在假设应用程序去读read按键,那么怎么去读呢?

怎么读按键?
app: read
--------------------------
   .......  // 这个过程省略,前面也分析了
           evdev_read
               // 无数据并且是非阻塞方式打开,则立刻返回
            if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
                return -EAGAIN;
            // 否则休眠
            retval = wait_event_interruptible(evdev->wait,
                client->head != client->tail || !evdev->exist);

既然是读按键,那么必须要有硬件设备有按键按下才会返回按键值,这里还是处于事件处理层,应用程序在这里休眠,那么谁来唤醒呢? 很显然,有按键按下才去唤醒,因此这个工作就交给了设备驱动层,那么找到这个唤醒呢,直接去找不好找,那么可以直接搜索 evdev->wait,搜索结果可知evdev->wait在evdev_event()函数或者那个被唤醒。那么evdev_event()又是被谁调用呢?很显然是设备驱动层,现在看一个设备层例子,内核中有个按键的例子,gpiokey.c,这只是个例子不针对任何设备,在gpiokey.c 终端处理函数里面

gpio_keys_isr
	// 上报事件
	input_event(input, type, button->code, !!state);
	input_sync(input);

然后搜索input_event(),

input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
	struct input_handle *handle;

	list_for_each_entry(handle, &dev->h_list, d_node)
		if (handle->open)
			handle->handler->event(handle, type, code, value);

这里有dev –>h_list 找到handle,然后由handle找到handler,在调用handler里面的event,在event()里面唤醒evdev->wait,整个过程体现出来了。设备驱动层实际上通过上报事件与事件处理层交互信息,事件处理层提供统一的接口给应用层。因此屏蔽了底层设备的差异。

     输入子系统的流程分析在到这了,关于输入子系统的笔记也到此了,后面会写一个简单输入子系统的驱动,这个驱动当然是设备驱动层的,作为事件处理层,是一个标准的,适用于任何输入设备,因此要实现的也就是设备驱动层。对于输入子系统的理解应该还只是入门的阶段,这里只是记录自己这个时间的理解,随着对linux内核的慢慢深入,相信不久会有新的认识,到时再来补充! ^_^

posted @ 2013-03-22 16:38  lsx_007  阅读(295)  评论(0编辑  收藏  举报