input子系统驱动

input子系统驱动

框架分析

核心层

文件为:/drivers/input/input.c;

首先找到入口函数为**static int __init input_init(void)**,在该函数中主要做了如下几件事:

  1. 创建了一个input_class类;
  2. 初始化/proc/input路径下的文件;
  3. 使用register_chrdev()注册了一个名为input_fops的file_operations结构体变量,且主设备号为13(INPUT_MAJOR)

通过注册的file_operations中可发现只有一个.open函数,指向的是input_open_file函数,进入该函数中可发现如下语句:

struct input_handler *handler = input_table[iminor(inode) >> 5];
new_fops = fops_get(handler->fops);
err = new_fops->open(inode, file);
  1. 根据子设备号,从数组input_table[]中拿到对应的一个数据类型为struct input_handler *的handler;
  2. 将这个handler中指向的fops赋值给new_fops;
  3. 打开new_fops中的open()函数;

上述分析可看出,在input.c中的input_fops结构中的.open只是一个“中转”的作用,最终还是会用到“input_table[]”;

问题:那么input_table[]数组是由谁构建的?

input_table[]数组的定义是“static struct input_handler *input_table[8];”,是一个文件内部使用的数组,搜索查看,可以找到如下两个函数:

int input_register_handler(struct input_handler *handler);
void input_unregister_handler(struct input_handler *handler);

在input_register_handler()中可以看到:

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);
/* 对每个input_dev调用input_attach_handler */
list_for_each_entry(dev, &input_dev_list, node)
    input_attach_handler(dev, handler);

搜索查看input_register_handler()是谁在调用,可以找到/drivers/input/evdev.c(以此为例分析)文件下:

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

在入口函数evdev_init()中调用了input_register_handler()接口进行注册,以上便是具体设备与核心层(input.c)的层次调用关系;

注册的evdev_handler是一个"struct input_handler"结构体,具体定义如下:

static struct input_handler evdev_handler = {
    .event =    evdev_event,
    .connect =  evdev_connect,
    .disconnect =   evdev_disconnect,
    .fops =     &evdev_fops,
    .minor =    EVDEV_MINOR_BASE, /* 子设备号为64 */
    .name =     "evdev",
    .id_table = evdev_ids, /* 表示这个evdev_handler可以支持哪些“输入设备” */
};

其中.fops指向的evdev_fops,里面指定了open、read、write等的操作函数,具体定义如下:

static const struct file_operations evdev_fops = {
    .owner =    THIS_MODULE,
    .read =     evdev_read,
    .write =    evdev_write,
    .poll =     evdev_poll,
    .open =     evdev_open,
    .release =  evdev_release,
    .unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = evdev_ioctl_compat,
#endif
    .fasync =   evdev_fasync,
    .flush =    evdev_flush
};

同时在input.c这个文件中可以找到这个函数:

int input_register_device(struct input_dev *dev);

并且有如下操作:

/* 放入链表中 */
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);

从上面分析可得出,不管是先注册加载“handler”,还是注册加载“device”,最后都会调用input_attach_handler();

分析input_attach_handler中的代码可以看出:在注册input_dev或input_handler时,会两两比较input_dev和input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果支持,则调用input_handler中的connect函数建立“连接“,因为是调用input_handler中的connect函数,所以不同的handler可能有自己不同的连接方式;

最后可以得出,input子系统整体框架中,一边是向核心层“input.c”注册handler,这一层代表“软件”,还有另一边向核心层“input.c”注册device,这一层代表“硬件”;

代码分析

下面的代码是使用input子系统框架实现了按键输入检测的驱动程序

/*
 * jz2440 buttons driver
**/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct input_dev *buttons_dev;
static struct timer_list buttons_timer; /* 该变量不能直接定义为指针变量,否则在init_timer()中传入使用时,因为传入的是空指针系统报错 */

static struct button_irq_desc{
    int irq;             /* 中断号 */
    unsigned long flags; /* 中断标志,用来定义中断的触发方式 */
    char *name;          /* 中断名称 */
    unsigned int pin;    /* 引脚 */
    unsigned int key_val;/* 键值 */
};

static struct button_irq_desc button_irqs[] = {
    {IRQ_EINT0,  IRQT_BOTHEDGE, "KEY1", S3C2410_GPF0,  KEY_L},         /* S2 */
    {IRQ_EINT2,  IRQT_BOTHEDGE, "KEY2", S3C2410_GPF2,  KEY_S},         /* S3 */
    {IRQ_EINT11, IRQT_BOTHEDGE, "KEY3", S3C2410_GPG3,  KEY_ENTER},     /* S4 */
    {IRQ_EINT19, IRQT_BOTHEDGE, "KEY4", S3C2410_GPG11, KEY_LEFTSHIFT}, /* S5 */
};

static struct button_irq_desc *pin_desc;

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

static void buttons_timer_function(unsigned long data)
{
    unsigned int pinval;

    if (!pin_desc)
        return;
    
    /* 获取对应引脚的值 */
    pinval = s3c2410_gpio_getpin(pin_desc->pin);

    if (pinval)
    {
        /* 松开 : 最后一个参数: 0-松开, 1-按下 */
        input_event(buttons_dev, EV_KEY, pin_desc->key_val, 0);
        input_sync(buttons_dev);
    }
    else
    {
        /* 按下 */
        input_event(buttons_dev, EV_KEY, pin_desc->key_val, 1);
        input_sync(buttons_dev);
    }
}

static int __init s3c2440_buttons_init(void)
{
    int error, i;
    
    /* 1. 分配一个input_dev结构体 */
    buttons_dev = input_allocate_device();
    if (!buttons_dev)
        return -ENOMEM;
    
    /* 2. 设置 */
    /* 设置能产生哪类事件 */
    set_bit(EV_KEY, buttons_dev->evbit); /* 可以产生按键类事件 */
    set_bit(EV_REP, buttons_dev->evbit); /* 可以产生重复类事件 */

    /* 设置能产生这类事件中的哪些事件: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 buttons input device\n");
        goto fail;
    }

    /* 4. 硬件相关的操作 */
    /* 注册一个定时器用于按键消抖 */
    init_timer(&buttons_timer);
    buttons_timer.function = buttons_timer_function;
    add_timer(&buttons_timer);

    /* 申请中断 */
    for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++){
        request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags, button_irqs[i].name, &button_irqs[i]);
    }
    
    return 0;
    
fail:
    input_free_device(buttons_dev);
    
    return error;
}

static void __exit s3c2440_buttons_exit(void)
{
    int i;

    /* 释放中断 */
    for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++){
        free_irq(button_irqs[i].irq, &button_irqs[i]);
    }

    /* 删除定时器 */
    del_timer(&buttons_timer);

    input_unregister_device(buttons_dev);

    input_free_device(buttons_dev);
}

module_init(s3c2440_buttons_init);
module_exit(s3c2440_buttons_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jason.tian");
MODULE_VERSION("0.0.1");
MODULE_DESCRIPTION("S3C2440 buttons Driver");

按照上述代码总结如何写一个符合input子系统驱动框架的驱动程序,步骤如下:

  1. 分配一个input_dev结构体(使用input_allocate_device()函数);

  2. 对input_dev结构体成员进行设置;

    • 设置能产生哪类事件,例如:

      set_bit(EV_KEY, buttons_dev->evbit); /* 可以产生按键类事件 */
      set_bit(EV_REP, buttons_dev->evbit); /* 可以产生重复类事件 */
    • 设置能产生这类事件中的哪些事件,例如:

      /* 设置能产生这类事件中的哪些事件: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. 注册(使用input_register_device()函数注册);

  4. 硬件相关的操作,比如在中断服务程序中使用input_event()函数进行上报事件,并使用input_sync()函数产生一个同步信号;

当装载按键驱动模块后,可以使用如下方法测试:

  1. cat /dev/tty1 或者 exec 0</dev/tty1,然后使用按键输入;

  2. hexdump /dev/event1 (具体查看是哪个event,ls -l /dev/event*);

    hexdump 是16进制显示open后的/dev/event1,显示的数据格式如下:

    struct input_event {
     struct timeval time;
     __u16 type;
     __u16 code;
     __s32 value;
    };
    struct timeval {
     time_t      tv_sec;     /* seconds */
     suseconds_t tv_usec;    /* microseconds */
    };
    /* 数据举例如下 */
                秒       微秒     类   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

使用内核中的gpio_key.c

代码位于:/drivers/input/keyboard/gpio_keys.c

该文件是内核中自带的一个基于input子系统架构并且符合“总线、设备、驱动”模型的通用GPIO按键驱动,所以需要自己编写一个具有设备信息的platform_device文件;

下面分别是driver文件和device文件,其中gpio_keys.c中的内容有部分优化,因为在原有代码实测中发现,按键的抖动对按键输入有影响,所以增加了一个定时器用于按键消抖,具体代码如下:

/* 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 <linux/gpio_keys.h>
#include <asm/gpio.h>

static struct platform_device *pdev_buttons;
static struct timer_list buttons_timer;
static int buttons_irq;

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
    pdev_buttons = dev_id;
    buttons_irq  = irq;

    /* 10ms后启动定时器 */
    mod_timer(&buttons_timer, jiffies+HZ/100);

    return IRQ_HANDLED;
}

static void buttons_timer_function(unsigned long data)
{
    int i;

    struct gpio_keys_platform_data *pdata = pdev_buttons->dev.platform_data;
    struct input_dev *input = platform_get_drvdata(pdev_buttons);

    for (i = 0; i < pdata->nbuttons; i++) {
        struct gpio_keys_button *button = &pdata->buttons[i];
        int gpio = button->gpio;

        if (buttons_irq == gpio_to_irq(gpio)) {
            unsigned int type = button->type ?: EV_KEY;
            int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;

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

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();
    if (!input)
        return -ENOMEM;

    platform_set_drvdata(pdev, input);

    input->evbit[0] = BIT(EV_KEY);

    input->name = pdev->name;
    input->phys = "gpio-keys/input0";
    input->dev.parent = &pdev->dev;

    input->id.bustype = BUS_HOST;
    input->id.vendor = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;

    for (i = 0; i < pdata->nbuttons; i++) {
        struct gpio_keys_button *button = &pdata->buttons[i];
        int irq = gpio_to_irq(button->gpio);
        unsigned int type = button->type ?: EV_KEY;

        set_irq_type(irq, IRQ_TYPE_EDGE_BOTH);
        error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM,
                     button->desc ? button->desc : "gpio_keys",
                     pdev);
        if (error) {
            printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d\n",
                irq, error);
            goto fail;
        }

        input_set_capability(input, type, button->code);
    }

    error = input_register_device(input);
    if (error) {
        printk(KERN_ERR "Unable to register gpio-keys input device\n");
        goto fail;
    }

    /* 注册一个定时器用于按键消抖 */
    init_timer(&buttons_timer);
    buttons_timer.function = buttons_timer_function;
    add_timer(&buttons_timer);

    return 0;

 fail:
    for (i = i - 1; i >= 0; i--)
        free_irq(gpio_to_irq(pdata->buttons[i].gpio), pdev);

    input_free_device(input);

    return error;
}

static int __devexit gpio_keys_remove(struct platform_device *pdev)
{
    struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
    struct input_dev *input = platform_get_drvdata(pdev);
    int i;

    for (i = 0; i < pdata->nbuttons; i++) {
        int irq = gpio_to_irq(pdata->buttons[i].gpio);
        free_irq(irq, pdev);
    }

    input_unregister_device(input);

    return 0;
}

struct platform_driver gpio_keys_device_driver = {
    .probe      = gpio_keys_probe,
    .remove     = __devexit_p(gpio_keys_remove),
    .driver     = {
        .name   = "gpio-keys",
    }
};

static int __init gpio_keys_init(void)
{
    return platform_driver_register(&gpio_keys_device_driver);
}

static void __exit gpio_keys_exit(void)
{
    platform_driver_unregister(&gpio_keys_device_driver);
}

module_init(gpio_keys_init);
module_exit(gpio_keys_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>");
MODULE_DESCRIPTION("Keyboard driver for CPU GPIOs");
/* platform_button_device.c */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
#include <linux/gpio_keys.h>
#include <linux/input.h>
#include <asm/arch/regs-gpio.h>

static struct gpio_keys_button jz2440_buttons[] = {
    {
        .code = KEY_L,
        .gpio = S3C2410_GPF0, /* 最终会通过gpio转换为对应的中断号 */
        .active_low = 1,
        .desc = "l",
        .type = EV_KEY, /* 可以不用设置,最后在驱动代码中都强制设置为EV_KEY */
    },
    {
        .code = KEY_S,
        .gpio = S3C2410_GPF2,
        .active_low = 1,
        .desc = "s",
        .type = EV_KEY,
    },
    {
        .code = KEY_ENTER,
        .gpio = S3C2410_GPG3,
        .active_low = 1,
        .desc = "enter",
        .type = EV_KEY,
    },
    {
        .code = KEY_LEFTSHIFT,
        .gpio = S3C2410_GPG11,
        .active_low = 1,
        .desc = "left-shift",
        .type = EV_KEY,
    }
};

static struct gpio_keys_platform_data jz2440_buttons_data = {
    .buttons  = jz2440_buttons,
    .nbuttons = ARRAY_SIZE(jz2440_buttons),
};

static struct platform_device button_dev = {
    .name         = "gpio-keys",
    .id       = -1,
    .dev = { 
        .platform_data = &jz2440_buttons_data,
    },
};

static int button_dev_init(void)
{
    platform_device_register(&button_dev);
    return 0;
}

static void button_dev_exit(void)
{
    platform_device_unregister(&button_dev);
}

module_init(button_dev_init);
module_exit(button_dev_exit);
MODULE_LICENSE("GPL");

转载于:https://www.cnblogs.com/jasontian996/p/11443947.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值