Linux驱动学习笔记----------input输入子系统(基本概念与流程)

很久没有没有动博客,这些日子想把之前学的一些干货分享一下。

首先关于输入子系统:
这里写图片描述

关于input输入子系统,流程图如下:

input 输入子系统流程图
在输入子系统中我们将其分为4部分(设备驱动层,核心层,事件层,用户空间)
从左往右依次就是从内核空间用户空间事件层提供了接口
所以并不需要我们去写,因为内核已经封装好啦。

现在我们进入核心层看看(drivers/input/input.c)

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

首先是入口初始化函数 这里做的就是创建设备类,申请注册字符设备,主要就是提供input_fops,通过这个 input_fops我们看到

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

这里只提供了一个操作函数open,这时我们就应该想到其它的操作不出意外应该就是在open 里完成的,所以抱着试试的心态进入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;
    /* No load-on-demand here? */
    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;

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

在open函数中struct input_handler *handler = input_table[iminor(inode) >> 5];// 放到链表具体的位置(右移5位相当于/32,找到该设备的位置),而这个input_table是由input_register_handler创建,后面会提到例如evdev.c中初始化就注册了它,从而使应用层成功的调用到evdev.c 的file_operantion

这里先简要介绍一下几个重要的函数和结构体:

         input_dev 代表底层设备,比如键盘,什么的。所有设备的对象都保存在一个全局的的input_dev队列中
         input_handler代表某类输入设备的处理方法。所有的input_handler放在input_handler的队列中
         input_dev可以有多个input_handler,同样的一个input_handler可以用于多种输入设备
          比如一个鼠标可以点击按键也可以轮滑,因此说可以对应多个input_handler. 同样道理,一个input_handler可以对应多个设备,像键盘,鼠标都可以对应同种这种处理方式。

这里写图片描述

接着if (!handler || !(new_fops = fops_get(handler->fops)))获取到该设备的操作函数。
然后获得它的new_fops->open函数,从而就实现了这个open函数。

到这里,我们产生了一个疑问,那就是是谁生成了input_table[ ],因为这关乎到使用谁的fops。
因此进行搜索后,我们可以发现在 input_register_handler 注册了这个input_table(input_table是一个全局的数组),我们来看下这个函数:

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)
        input_attach_handler(dev, handler);//**input_dev_list是一个全局的链表,对每一个设备进行接下来的匹配操作。一旦匹配成功就进行connect操作**
    input_wakeup_procfs_readers();
    return 0;
}

在这里我们看到了似曾相似的语句
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler;
这就把我们传进来的handler装进input_table当中,使得我们可以通过input_table 来对事件驱动层进行操作。 这就相当于和事件层产生了关系。

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    const struct input_device_id *id;
    int error;
    if (handler->blacklist && input_match_device(handler->blacklist, dev))
        return -ENODEV;
    id = input_match_device(handler->id_table, dev);
    if (!id)
        return -ENODEV;
    error = handler->connect(handler, dev, id);
    if (error && error != -ENODEV)
        printk(KERN_ERR
            "input: failed to attach handler %s to device %s, "
            "error: %d\n",
            handler->name, kobject_name(&dev->cdev.kobj), error);
    return error;
}

在这个匹配函数中,我们可以知道当匹配一旦成功,就进入handler当中的connect函数,connect函数将会把dev和handler 进行一个绑定。通过谁进行绑定呢?
通过handle进行绑定

注意:
我们这里是以evdev.c作为例子,在evdev.c中我们看到初始化函数的第一句话就是input_register_handler(&evdev_handler); 也就是将自己的handler进行注册,使得核心层知道该去寻找谁。 而我们所说的connect函数,调用的其实就是evdev_handler中的connect。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,
};

那么是如何建立连接?
我们来看下connect是如何实现的:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
    struct evdev *evdev;//包含了input_handle
    struct class_device *cdev;
    dev_t devt;
    int minor;
    int error;

    for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
    if (minor == EVDEV_MINORS) {
        printk(KERN_ERR "evdev: no more free evdev devices\n");
        return -ENFILE;
    }

    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//申请了一个handle
    if (!evdev)
        return -ENOMEM;

    INIT_LIST_HEAD(&evdev->client_list);
    init_waitqueue_head(&evdev->wait);

    evdev->exist = 1;
    evdev->minor = minor;
    evdev->handle.dev = dev;//绑定dev
    evdev->handle.name = evdev->name;
    evdev->handle.handler = handler;//绑定handler
    evdev->handle.private = evdev;
    sprintf(evdev->name, "event%d", minor);

    evdev_table[minor] = evdev;

    devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),

    cdev = class_device_create(&input_class, &dev->cdev, devt,
                   dev->cdev.dev, evdev->name);
    if (IS_ERR(cdev)) {
        error = PTR_ERR(cdev);
        goto err_free_evdev;
    }

    /* temporary symlink to keep userspace happy */
    error = sysfs_create_link(&input_class.subsys.kobj,
                  &cdev->kobj, evdev->name);
    if (error)
        goto err_cdev_destroy;

    error = input_register_handle(&evdev->handle);//注册handle
    if (error)
        goto err_remove_link;

    return 0;

 err_remove_link:
    sysfs_remove_link(&input_class.subsys.kobj, evdev->name);
 err_cdev_destroy:
    class_device_destroy(&input_class, devt);
 err_free_evdev:
    kfree(evdev);
    evdev_table[minor] = NULL;
    return error;
}

这样就成功的将handler与dev进行了绑定,使得特定的设备可以由指定的处理函数进行处理。

如果我们想写 一个input的按键驱动,那我们应该怎么写?
1. 分配一个input_dev结构体 //input_allocate_device( );
2. 设置 //set_bit( )
2.1 设置能产生哪类事件
2.2 设置能产生哪些事件
3. 注册 // input_register_device( );
4. 硬件相关的代码,比如在中断服务程序里上报事件

下面的是基于讯为4412的按键代码
实现的是按键 L,S,Enter。


/* 参考drivers\input\keyboard\gpio_keys.c */

#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 <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/delay.h>

struct pin_desc{    //进行封装
     int irq;
     char *name;
     unsigned int pin;
     unsigned int key_val;
};

struct pin_desc pins_desc[2] = {
    {IRQ_EINT(9),"K1",EXYNOS4_GPX1(1),KEY_L},
    {IRQ_EINT(10),"K1",EXYNOS4_GPX1(2),KEY_S},
};
static struct input_dev *buttons_dev;   //申请一个输入设备指针
static struct timer_list buttons_timer; //申请一个定时器
static struct pin_desc *irq_pd;
/*中断处理函数---里面调用定时器,并且设置了启动时间,也就是10毫秒后*/
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)//刚开始进入到这里,返回了,因为irq_pd为空
        return;

    pinval = gpio_get_value(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 __init buttons_init(void){
    int i;
    int err;
    /* 1. 分配一个input_dev结构体 */
    buttons_dev = input_allocate_device();
    /* 2. 设置 */
    /* 2.1 能产生哪类事件 */
    set_bit(EV_KEY,buttons_dev->evbit); //将按键事件传给evbit进行设置
    set_bit(EV_REP,buttons_dev->evbit); //将循环事件传给evbit进行设置

    /* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
    set_bit(KEY_L,buttons_dev->keybit);
    set_bit(KEY_S,buttons_dev->keybit);

    /* 3. 注册 */
    err = input_register_device(buttons_dev);
        if(err){
            printk(KERN_EMERG "input_register_device failed!\n--%d\n",err); 
            goto exit;
        }
    /* 4. 硬件相关的操作 */
    init_timer(&buttons_timer);
    buttons_timer.function = buttons_timer_function;
    add_timer(&buttons_timer);//这里开始并没有设置超时时间   
    /*中断申请--这里不需要什么GPIO申请函数,在启动的时候就已经初始化好了,我们之前做的申请,用的也是宏定义好的*/
    /*类似 gpio_register(EXYNOS4_GPX1(1),"EINT9") ,这里其实使用的也是EXYNOS4_GPX1(1)宏*/
    for (i = 0; i < 2; i++)
    {
        err = request_irq(pins_desc[i].irq, buttons_irq, IRQ_TYPE_EDGE_BOTH, pins_desc[i].name, &pins_desc[i]);
            if(err){
                printk(KERN_EMERG "register_irq failed!\n--%d\n",err);  
                goto exit2;
            };
    }

exit2:
        del_timer(&buttons_timer);
        input_unregister_device(buttons_dev);
exit:
        return err;

}

static void __exit buttons_exit(void){
    int i;
    for (i = 0; i < 2; 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");

实验:
hexdump /dev/event1 (open(/dev/event1), read(), )
秒 微秒 类 code value
0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000
0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000
0000020 0bb2 0000 5815 000e 0001 0026 0000 0000
0000030 0bb2 0000 581f 000e 0000 0000 0000 0000

1按下—-0松开

define KEY_LEFTBRACE 26

从内核可以看出 刚好code对应的是26
证明了代码是正确的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值