input子系统的架构分析及应用

input子系统的架构

以前我们写的裸板程序中,驱动程序一般分为以下几步:

(1)构造我们自己的file_operations

(2) 注册设备字符register_chrdev,将我们的file_operations告诉内核

(3)然后就可以通过入口函数module_init(),调用上面创建的字符设备

       上面是一个简单的字符设备的书写框架,但是要做到通用,肯定必须抽象出一套通用的东西出来,这就是内核的input子系统为我们做的事情,下面通过分析代码流程来了解一下input子系统的架构(注意:贴出来的代码中的,“...”表示省略的代码)。

input子系统架构图

核心层分析 

上图中有一个核心层的文件就是input.c,先看一下里面做了什么:

/*
 * drivers/input/input.c
 */
static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};

static int __init input_init(void)
{
	int err;

	err = class_register(&input_class);
	...
	err = input_proc_init();
	if (err)
		goto fail1;

	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
	...

}

       从代码中可以看出来,也是我们熟悉的步骤,构造file_operations、注册字符设备register_chrdev等。但是有个贴点就是,file_operations只提供了open函数,所以我们猜测input_open_file函数中肯定做了什么操作,使得和以前的字符驱动设备一样,有read、write等等的函数。

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;

	/* No load-on-demand here? */
	if (!handler || !(new_fops = fops_get(handler->fops)))
		return -ENODEV;

	...

	old_fops = file->f_op;
	file->f_op = new_fops;

	err = new_fops->open(inode, file);

	...
	return err;
}

       上面可以看出来,通过次设备号在一个input_table数组里面找到相应的handler后,再通过它获得了新的file_operations,然后再调用handler中的open函数,也就是我们一开始想调用的字符设备,比如键盘或者遥控器等等。

设备驱动层

       由文章开头的图可以看出来,input_table数组中的每一项,就是下层(百度了别人的文章,称之为驱动驱动层)的各种设备通过input_register_handler函数将自己注册进去的。下面我们以evdev为例,讲解一下handler的由来。

注册input_handler

/*
 * drivers/input/evdev.c
 */
static struct input_handler evdev_handler = {
	.event =	evdev_event,
	.connect =	evdev_connect,
	.disconnect =	evdev_disconnect,
	.fops =		&evdev_fops,
	.minor =	EVDEV_MINOR_BASE,
	.name =		"evdev",
	.id_table =	evdev_ids,
};

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

从上面可以看出来,在evdev驱动初始化的时候就把自己向input核心层注册。

int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;

	INIT_LIST_HEAD(&handler->h_list);

	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5])
			return -EBUSY;

		input_table[handler->minor >> 5] = handler; //这里就是将handler注册进input_table数组
	}

	list_add_tail(&handler->node, &input_handler_list); //handler并将自己挂到input_handler_list链表中

	list_for_each_entry(dev, &input_dev_list, node) //遍历input_dev_list链表,这里后面会讲到,其实就是我们编写的驱动程序的硬件部分,比如按键或者遥控器等
		input_attach_handler(dev, handler); //会根据input_handler的id_table去找到其匹配的dev设备,并通过input_handler的connect函数建立连接,其实也就是为设备创建设备节点

	input_wakeup_procfs_readers();
	return 0;
}

注册input_device

       同理,在我们注册自己的硬件设备时,也有类似上面的步骤。从文章开头的图可以看出来,有个input_register_device的函数可以将我们的硬件设备程序注册进核心层。下面就以kernel代码中的一个已经写好的程序为例(后面我们自己的设备驱动程序也可以模仿这么写):

/*
 * drivers\input\keyboard\gpio_keys.c
 */
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
	struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
	struct input_dev *input;
	int i, error;

	input = input_allocate_device(); //分配一个input_dev结构体的大小
	if (!input)
		return -ENOMEM;

	...

	error = input_register_device(input); //将自己向上层注册
	...
}

       其实上面的步骤,如果用来注册我们自己的设备驱动程序就够了,但是秉承着知其然还要知其所以然的精神,我们继续跟踪到底怎么注册设备的,而且又是怎么和前面的input_handler联系起来的。

/*
 * drivers/input/input.c
 */
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;

	...

	list_add_tail(&dev->node, &input_dev_list); //这里又是建立一个input_dev的链表,也就是和前面input_handler匹配过程中的input_dev_list

	...

	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler); //这里也有一个匹配的过程,动态将自己挂载到对应的handler链表中

	input_wakeup_procfs_readers();

	return 0;
}

总结一下前面的两个注册流程:

       注册input_dev或input_handler时,会两两比较input_dev和input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用input_handler的connect函数建立连接。

调用流程分析

       前面的内容都是驱动程序的注册过程,那么假如上层调用read函数捞读取键值,这又是什么样的流程呢?

static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
	struct evdev_client *client = file->private_data;
	struct evdev *evdev = client->evdev;
	int retval;

	...

	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);  //否则在没有事件发生时,进入休眠等待中断
	...

}

上面的程序就是和我们之前裸板驱动程序一样的。但是休眠后的进程是谁来唤醒呢?

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
	struct evdev *evdev = handle->private;
	struct evdev_client *client;

	if (evdev->grab) {
		...
	} else
		list_for_each_entry(client, &evdev->client_list, node) {
			...

			kill_fasync(&client->fasync, SIGIO, POLL_IN); //驱动程序发送信号去通知上层的应用程序去做相应处理
		}

	wake_up_interruptible(&evdev->wait); //并唤醒休眠的进程
}

有一个evdev_event函数是用来唤醒休眠的进程的。那么又是谁来调用这个函数呢?很明显,肯定是我们的硬件设备程序调用的:

/*
 * drivers/input/keyboard/gpio_keys.c
 */
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
	...

	for (i = 0; i < pdata->nbuttons; i++) {
		...

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

	return IRQ_HANDLED;
}

       在这个例子里面,中断服务程序有一个input_event函数是上报事件的,这个函数里面就是调用event函数来唤醒睡眠的进程,如下的代码所示。

/*
 * drivers/input/input.c
 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	struct input_handle *handle;

	if (type > EV_MAX || !test_bit(type, dev->evbit))
		return;

	...

	if (dev->grab)
		dev->grab->handler->event(dev->grab, type, code, value); 
	else
		list_for_each_entry(handle, &dev->h_list, d_node)
			if (handle->open)
				handle->handler->event(handle, type, code, value); //这里就是调用了event函数来唤醒休眠的进程
}

我们的驱动程序编写

       前面概要的分析了一下input子系统的架构,相信已经对它有一定的认识了,但是如果要为我们的设备编写驱动程序,又该怎么做呢?

其实前面的分析过程中,应该能看出一定的端倪:

(1)分配一个input_dev结构体

(2)设置为相应的事件类型,我们这里是按键事件

(3)将我们的input_dev用input_register_device函数注册到上层

(4)在中断来到的时候,调用input_event函数来上报事件

下面马上给出我们的代码,其实大部分和我们的裸板程序很类似,可以对比一下:字符设备驱动之按键驱动(poll机制实现)

#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/gpio_keys.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/gpio.h>

struct input_dev *buttons_dev;
static struct timer_list buttons_timer;
struct pin_desc {
	int irq;
	char *name;
	unsigned int pin;
	unsigned int key_val;
};

//模拟四个键盘按键值:L S ENTER 左移
struct pin_desc pins_desc[4] = {
	{IRQ_EINT0,  "S2", S3C2410_GPF0,  KEY_L},	
	{IRQ_EINT2,  "S3", S3C2410_GPF2,  KEY_S},	
	{IRQ_EINT11, "S4", S3C2410_GPG3,  KEY_ENTER},		
	{IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct pin_desc *irq_pd;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	irq_pd = (struct pin_desc *)dev_id;
	//10ms(HZ/100)后启动定时器
	mod_timer(&buttons_timer, jiffies+HZ/100);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static void buttons_timer_function(unsigned long data)
{
	struct pin_desc *pindesc = irq_pd;
	unsigned int pinval;

	if (!pindesc)
		return;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin); //低电平表示按下
	if (pinval) {
		/*松开:最后一个参数: 0-松开 1-按下*/
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
		input_sync(buttons_dev); //表示事件上报完了
	}
	else {
		/*按下*/
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
		input_sync(buttons_dev);
	}

}


static int buttons_init(void)
{
	int error, i;
	
	/*1、分配一个input_dev结构体*/
	buttons_dev = input_allocate_device();
	if (!buttons_dev)
		return -ENOMEM;

	/*2、设置*/
	/*2.1、设置为哪一类的事件,这里是按键事件*/
	set_bit(EV_KEY, buttons_dev->evbit);
	set_bit(EV_REP, buttons_dev->evbit); //允许重复上报事件

	/*2.2、设置为哪一些按键,
	  *这里模拟键盘的输入按键:
	  *L, S, ENTER和LEFTSHIFT键
	  */
	set_bit(KEY_L, buttons_dev->keybit);	
	set_bit(KEY_S, buttons_dev->keybit);
	set_bit(KEY_ENTER, buttons_dev->keybit);
	set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);

	/*3、注册*/
	error = input_register_device(buttons_dev);
	if (error) {
		printk(KERN_ERR "Unable to register button input device\n");
		goto fail; 
	}
	
	/*4、硬件相关的操作:加定时器是为防抖动*/
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	add_timer(&buttons_timer);
	
	for (i = 0; i < 4; i++) {
		error = request_irq(pins_desc[i].irq,  buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]); //注册中断函数
		if (error) {
			printk(KERN_ERR "request_irq failed\n");
			goto fail;
		}
	}

	
	return 0;
fail:
	input_free_device(buttons_dev);
	input_unregister_device(buttons_dev);
}

static void buttons_exit(void)
{
	int i;
	for (i = 0; i < 4; i++) {
		free_irq(pins_desc[i].irq, &pins_desc[i]);
	}
	del_timer(&buttons_timer);
	input_free_device(buttons_dev);
	input_unregister_device(buttons_dev);
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");

如果熟悉字符设备驱动的书写框架的话,上面的代码应该不难读懂。这里解释一下input_dev这个结构体:

struct input_dev {

	void *private;

	const char *name;
	const char *phys;
	const char *uniq;
	struct input_id id;

	unsigned long evbit[NBITS(EV_MAX)];  //能产生哪类事件,如按键、相对位移和绝对位移事件等
	unsigned long keybit[NBITS(KEY_MAX)]; //表示能产生哪些按键
	unsigned long relbit[NBITS(REL_MAX)]; //能产生哪些相对位移事件,x,y,滚轮
	unsigned long absbit[NBITS(ABS_MAX)]; //表示能产生哪些绝对位移事件, x, y
	
	...
}


/*
 * Event types
 */

#define EV_SYN			0x00  //同步
#define EV_KEY			0x01  //按键类,如键盘等
#define EV_REL			0x02  //相对位移,如鼠标
#define EV_ABS			0x03  //绝对位移,触摸屏

猜你喜欢

转载自blog.csdn.net/lee_jimmy/article/details/83241550