输入子系统框架详解

输入子系统框架详解

(1)为什么要使用输入子系统框架

       我们在刚开始学习字符设备驱动程序的编写时,都会用到这样一个框架:

      首先分配一个主设备号( alloc_chrdev_region(),推荐用这个函数 ),接着填充file_opreation 结构体(这是字符设备驱动程序编写的关键点),再接着在入口函数里面调用字符设备注册函数等,最后编写出口函数,不要忘记修饰入口、出口函数,最后加上关于模块信息的系列函数。

      基于这种架构写出来的字符设备驱动程序有一个问题,就是只有编写它的人知道怎么去调用它,其他人是不知到的,为了使得做应用程序开发相关人员的工作更轻松,需要提供一个统一的接口,输入子系统就在这样的需求下产生了。

(2)输入子系统的框架(基于Linux 2.6.22 内核)

      输入子系统的核心层是 input.c ,我们先进入该文件看一下,能不能发现什么线索,

      我们在源码文件中搜索 “init” 关键字,找到了  input_init(void)  函数,我们大胆假设该函数就是输入子系统的入口函数,我们需要在该函数体中找到证据,该函数的源码如下:

static int __init input_init(void)
{
	int err;
	err = class_register(&input_class);                                 //创建设备类
	if (err) {
		printk(KERN_ERR "input: unable to register input_dev class\n");  
		return err;
	}
	err = input_proc_init();
	if (err)
		goto fail1;
	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;
}

       由上面的两条注释应该就可以看出这就是输入子系统的入口函数,可是,疑问又来了,创建设备类了,可是并没有在该类下创建文件呀?这个问题先留着,接下来的分析你将会找到答案。

      err = register_chrdev(INPUT_MAJOR, "input", &input_fops);  看到这一行时,我们似乎对 传入的 input_fops  参数比较感兴趣,我们先找到它的原型,看看它的这是面目是啥?

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

      奇怪了,平时我们我们填充 file_operations 结构体时要传入一大堆函数指针,怎么现在这里就一个 input_open_f 的函数指针呀,输入子系统一定是在这个函数中做了相关工作,不然,应用程序系统调用都找不到底层的相关函数。我们先看看input_open_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;
}

       首先它定义了一个 input_hand 结构体类型的指针,并使这个指针指向 input_table 数组的第(iminor(inode) >> 5)项(次设备号除以32),接着就把这个指针指向的结构体中的 fops 成员赋值给new_fops,再调用new_fops,从而调用了我们编写的驱动程序的open函数。明显,我们事先要把input_table这个数组填充好,那么问题又来了,有谁来填充数组?如何去填充?

        我们不妨先看看input_handle结构体,看看里面有啥成员,说不定就能发现蛛丝马迹,找到线索。

struct input_handler {
	void *private;
	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);
	const struct file_operations *fops;
	int minor;
	const char *name;
	const struct input_device_id *id_table;
	const struct input_device_id *blacklist;
	struct list_head	h_list;
	struct list_head	node;
};

       我们可以猜一下,平时一般的字符设备驱动程序调用注册函数,就把设备相关联的结构体存储在一个数组中,这里会不会也是某个注册函数来填充数组了,事实上,确实如此。

       数组的填充由input_register_handler() 函数来完成

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;
	}
	list_add_tail(&handler->node, &input_handler_list);
	list_for_each_entry(dev, &input_dev_list, node)           // (1)
		input_attach_handler(dev, handler);   
	input_wakeup_procfs_readers();
	return 0;
}

      注释(1):由这两行代码,我们可以知道  input_register_handler() 函数还建立了和设备之间的联系。
      既然有 input_register_handle()函数,那么肯定有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;
	set_bit(EV_SYN, dev->evbit);
	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;
	list_add_tail(&dev->node, &input_dev_list);              //放入链表
	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;
	error = class_device_add(&dev->cdev);                    //在input设备类下创建设备节点
	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_handler,都调用input_attach_handler, 根据input_handler的 id_table判断能否支持这个input_dev
 */

	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, 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函数建立"连接".

         问题又来了,怎么样建立连接?
        1. 分配一个input_handle结构体(注意是handle)

        2.  input_handle.dev = input_dev;  // 指向左边的input_dev

            input_handle.handler = input_handler;  // 指向右边的input_handler

        3. 注册,使得device和handler能够互相找到对方

            input_handler->h_list = &input_handle;

            inpu_dev->h_list      = &input_handle;

(3)举例说明

    3.1  evdev_connect() 函数:

        // 分配一个input_handle

             evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

       // 设置

             evdev->handle.dev = dev;  // 指向左边的input_dev

             evdev->handle.name = evdev->name;

             evdev->handle.handler = handler;  // 指向右边的input_handler

             evdev->handle.private = evdev;
       // 注册

             error = input_register_handle(&evdev->handle);

    3.2   怎么读按键?

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

   3.3   谁来唤醒?

      evdev_event

              wake_up_interruptible(&evdev->wait);

 

     3.4  evdev_event被谁调用?

       猜:应该是硬件相关的代码,input_dev那层调用的

              在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数

       gpio_keys_isr(举例)

           // 上报事件

           input_event(input, type, button->code, !!state);

           input_sync(input);

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

4 怎么写符合输入子系统框架的驱动程序

      1. 分配一个input_dev结构体

      2. 设置

      3. 注册

      4. 硬件相关的代码,比如在中断服务程序里上报事件

5 程序实例

#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 <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>

struct pin_desc{
	int irq;
	char *name;
	unsigned int pin;
	unsigned int key_val;
};

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 input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	/* 10ms后启动定时器 */
	irq_pd = (struct pin_desc *)dev_id;
	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 i;
	
	/* 1. 分配一个input_dev结构体 */
	buttons_dev = input_allocate_device();;

	/* 2. 设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, buttons_dev->evbit);
	set_bit(EV_REP, buttons_dev->evbit);
	
	/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
	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. 注册 */
	input_register_device(buttons_dev);
	
	/* 4. 硬件相关的操作 */
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	add_timer(&buttons_timer);
	
	for (i = 0; i < 4; i++)
	{
		request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
	}
	
	return 0;
}

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_unregister_device(buttons_dev);
	input_free_device(buttons_dev);	
}

module_init(buttons_init);

module_exit(buttons_exit);

MODULE_LICENSE("GPL");

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值