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


    1、input子系统框架介绍

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

    1、input子系统框架介绍

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

     

  • 相关阅读:
    BZOJ 1050 旅行
    BZOJ 1040 骑士
    BZOJ 1038 瞭望塔
    BZOJ 1037 生日聚会
    BZOJ 1823 满汉全席
    BZOJ 3091 城市旅行
    CF702E Analysis of Pathes in Functional Graph
    Luogu 2154 [SDOI2009]虔诚的墓主人
    Luogu 1268 树的重量
    Luogu 4867 Gty的二逼妹子序列
  • 原文地址:https://www.cnblogs.com/lztutumo/p/13364166.html
Copyright © 2020-2023  润新知