基于input子系统的按键驱动程序

1、input子系统框架介绍

2、编写按键驱动程序,通过input子系统将按键信息上发到应用层

1、input子系统框架介绍

  input子系统是内核专门针对输入类设备实现的管理框架,input子系统中已经事先定义了各类设备可能产生的各类事件,比如针对鼠标类设备,input子系统定义了左键按下、右键按下、移动等事件;驱动程序通过input子系统提供的专门接口将这些事件传递到input子系统中,input子系统将事件封装成一个结构体(struct input_event)放在一个缓存中;应用程序就可以从input子系统提供的设备文件中读取到事件结构体,根据结构体字段就可以解析出这是个什么事件。

基于input子系统的按键驱动程序

input子系统大致框架如上图所示,上图大致描述了

       1、input子系统大致由哪几部分组成

       2、具体设备驱动程序怎么注册到input子系统

       3、红色部分画出了硬件事件是怎么通过input子系统传到用户空间的

函数功能说明
input_allocate_device 申请struct input_dev结构体
input_set_capability 申明设备会产生哪些事件,比如,鼠标左键按下
input_register_device 添加input_dev到input核心层,并且会把这个input_dev和输入事件层注册的各个Handler进行匹配绑定,从而接通应用层
input_report_key 将事件传递给input子系统

 

如上图所示,input子系统大致可以分为输入设备驱动层、输入核心层以及输入事件驱动层;

       输入核心层是由内核开发者编写的,主要提供各类数据结构管理的功能;

       输入事件驱动层是和用户空间交互的层,该层会调用input_register_handler向input子系统注册一些句柄,这些句柄专门用于关联某一类设备。比如,Mouse Handler句柄,这个句柄可以处理鼠标产生的事件;Keyboard Handler句柄,这个句柄可以处理键盘产生的事件;Event Handler句柄,这是个特殊通用的句柄,可以处理所有输入类设备产生的事件。

       设备驱动程序调用input子系统提供的三个接口函数input_allocate_device、input_set_capability和input_register_device将自己关联注册到input子系统中,调用这三个函数后,会在input子系统中创建一个input_dev结构体表示这个设备,然后,input子系统会把这个input_dev结构体和输入事件驱动层注册的一个或多个句柄进行匹配绑定(当然,有匹配条件),和输入事件层的某个句柄绑定成功后,就会创建一个设备文件,以后设备产生事件后,就会传递到这个绑定的句柄,然后被组织成input_event结构体放在某个缓存,应用程序就可以通过对应的设备文件读取缓存中的input_event结构体数据。比如,如图所示,按键驱动程序注册到input子系统后,就会创建key:input_dev,这个结构体会和Event Handler句柄匹配绑定,从而创建设备文件/dev/input/eventxxx,按键按下后,应用程序就可以从设备文件/dev/input/eventxxx读取到按键信息;再比如,鼠标驱动程序,注册到input子系统后,就会创建Mouse:input_dev,这个结构体会和两个句柄(Event Handler和Mouse Handler)匹配绑定,那么就会创建两个设备文件/dev/input/eventxxx和/dev/input/mousexxx,鼠标产生事件后,应用程序从这两个设备文件都能读取到鼠标事件信息。

       综上所述,要将一个按键驱动程序关联到input子系统,我们只需要做如下工作:

1、调用input子系统提供的输入设备驱动层的3个注册接口函数,按键设备驱动注册到input子系统,input子系统会自动生成设备文件/dev/input/eventxxx

2、在按键中断函数中,调用input_report_key函数将按键产生的事件传递到input子系统

3、应用程序直接从/dev/input/eventxxx设备文件读取按键产生的事件

       如下为示例程序:

       程序采用平台总线的框架,按键信息在设备树中描述,在probe函数中完成中断注册后,调用devm_input_allocate_device、input_set_capability和input_register_device三个函数将驱动注册到input子系统

       在中断函数irq_test_irq_isr中调用input_report_key将按键事件传递到input子系统

示例驱动程序

linux内核版本:4.14.2

#include <linux/module.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/slab.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/gpio_keys.h>

#include <linux/workqueue.h>

#include <linux/gpio.h>

#include <linux/gpio/consumer.h>

#include <linux/of.h>

#include <linux/of_irq.h>

#include <linux/spinlock.h>

#include <linux/input.h>

static struct my_gpio_keys_button *button;

static int flag;

static struct input_dev *key_input;

struct my_gpio_keys_button {

    unsigned int code;

    int gpio;

    int active_low;

    const char *desc;

    unsigned int type;

    int wakeup;

    int debounce_interval;

    bool can_disable;

    int value;

    unsigned int irq;

    struct gpio_desc *gpiod;

};

static char *label[2];

static irqreturn_t irq_test_irq_isr(int irq, void *dev_id)

{

    printk(KERN_INFO "get irq --> irq_test_irq_isr. ");

       flag = gpiod_get_value(button->gpiod);

       if (flag) {

              input_report_key(key_input, KEY_HOME, 1);

       }

       else {

              input_report_key(key_input, KEY_HOME, 0);

       }

       input_sync(key_input);

       return IRQ_HANDLED;

}

static int key_input_probe(struct platform_device *pdev)

{

    /* 获取节点信息,注册中断 */

    struct device *dev = &pdev->dev;

    struct fwnode_handle *child = NULL;

    int nbuttons;

    int irq, error;

    irq_handler_t isr;

    unsigned long irqflags;

    nbuttons = device_get_child_node_count(dev);

    if (nbuttons == 0) {

        printk(KERN_INFO "no child exist, return ");

        return ERR_PTR(-ENODEV);

    }

    printk(KERN_INFO "child num is %d. ", nbuttons);

    button = devm_kzalloc(dev, sizeof(struct my_gpio_keys_button) * nbuttons, GFP_KERNEL);

    /* 获取lable参数,父节点没有lable属性 */

    device_property_read_string(dev, "label", label[0]);

    printk(KERN_INFO "parent lable %s ", label[0]);

    /* 扫描处理每个子节点 */

    device_for_each_child_node(dev, child) {

        /* 获取虚拟中断号virq ??? */

        if (is_of_node(child)) {

            button->irq = irq_of_parse_and_map(to_of_node(child), 0);

                     printk(KERN_INFO "get irq from irq_of_parse_and_map successful ");

        }

        fwnode_property_read_string(child, "label", &button->desc);

        /* 获取gpio描述符gpiod */

        button->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL,

                                child,

                                GPIOD_IN,

                                button->desc);

        if (IS_ERR(button->gpiod)) {

            printk(KERN_INFO "get gpiod failed, return. ");

            return -ENOENT;

        }

        /* 检查虚拟中断号,可不使用 */

        if (!button->irq) {

            irq = gpiod_to_irq(button->gpiod);

            if (irq < 0) {

                error = irq;

                dev_err(dev,

                    "Unable to get irq number for GPIO %d, error %d ",

                    button->gpio, error);

                return error;

            }

            button->irq = irq;

        }

        printk(KERN_INFO "get virq %d for key. ", button->irq);

        isr = irq_test_irq_isr;

        irqflags = 0;

        irqflags |= IRQF_SHARED;

              /* 设置引脚为输入模式 */

              gpiod_set_value(button->gpiod, 1);

              gpiod_direction_input(button->gpiod);

             

              /* 注册中断 */

        /* 最后一个参数是传给中断函数的参数 */

        error = devm_request_any_context_irq(dev, button->irq, isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,

                         button->desc, NULL);

        if (error < 0) {

            dev_err(dev, "Unable to claim irq %d; error %d ",

                button->irq, error);

            return error;

        }

    }

    /* input子系统相关初始化 */

       key_input = devm_input_allocate_device(dev);

       if (!key_input) {

              dev_err(dev, "failed to allocate key_input device ");

              return -ENOMEM;

       }

       /* 添加input子系统响应的事件 */

       input_set_capability(key_input, EV_KEY, KEY_HOME);

    /* 注册input子系统 */

    error = input_register_device(key_input);

       if (error) {

              dev_err(dev, "Unable to register key_input device, error: %d ",

                     error);

              return error;

       }

    return 0;

}

static const struct of_device_id key_input_of_match[] = {

    { .compatible = "irq-keys", },

    { },

};

MODULE_DEVICE_TABLE(of, key_input_of_match);

static struct platform_driver key_input_device_driver = {

    .probe      = key_input_probe,

    .driver     = {

        .name   = "irqtest_keys",

        .of_match_table = key_input_of_match,

    }

};

static int __init key_input_init(void)

{

    return platform_driver_register(&key_input_device_driver);

}

static void __exit key_input_exit(void)

{

    platform_driver_unregister(&key_input_device_driver);

}

module_init(key_input_init);

module_exit(key_input_exit);

MODULE_LICENSE("GPL");

 

示例应用程序

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <linux/input.h>

#include <string.h>

#define DEVICE_KEY    "/dev/input/event0"

#define DEVICE_MOUSE  "/dev/input/event0"

int main(void)

{

    int fd = -1;

    int ret = -1;

    int i;

    struct input_event ev;

    char *p;

   

    // 第一步:打开设备文件

    fd = open(DEVICE_KEY, O_RDONLY);

    if (fd < 0) {

        perror("open");

        return -1;

    }

   

    while (1) {

        // 第二步:读取一个event事件包

        memset(&ev, 0, sizeof(ev));

        ret = read(fd, &ev, sizeof(ev));

        if (ret != sizeof(ev)) {

            perror("read");

            close(fd);

            return -1;

        }

        

        // 第三步:解析event包,才知道发生了什么样的输入事件

              printf("--------------------- ");

              printf("type: %hd ", ev.type);

              printf("code: %hd ", ev.code);

              printf("value: %d ", ev.value);

        printf(" ");

    }

   

    // 第四步:关闭设备

    close(fd);

   

}

 


参考资料

  朱有鹏老师驱动视频教程:http://t.elecfans.com/c278.html