(Linux驱动学习 - 11).Input 子系统

一.Input 子系统的定义

input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统

一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等

等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,

鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心

应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动

层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点。

可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:

驱动层 :输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。

核心层 :承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行

处理。

事件层:主要和用户空间进行交互。

二.注册 Input 设备有关函数

1.input_dev 结构体

cpp 复制代码
struct input_dev 
{
    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];         /* 事件类型的位图 */
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];       /* 按键值的位图 */
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];       /* 相对坐标的位图 */
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];       /* 绝对坐标的位图 */
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];       /* 杂项事件的位图 */
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];       /*LED 相关的位图 */
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];       /* sound 有关的位图 */
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];         /* 压力反馈的位图 */
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];         /*开关状态的位图 */

    ......

    bool devres_managed;
};

其中 evbit 表示输入事件类型,可选择以下事件

cpp 复制代码
#define EV_SYN 0x00         /* 同步事件 */
#define EV_KEY 0x01         /* 按键事件 */
#define EV_REL 0x02         /* 相对坐标事件 */
#define EV_ABS 0x03         /* 绝对坐标事件 */
#define EV_MSC 0x04         /* 杂项(其他)事件 */
#define EV_SW 0x05          /* 开关事件 */
#define EV_LED 0x11         /* LED */
#define EV_SND 0x12         /* sound(声音) */
#define EV_REP 0x14         /* 重复事件 */
#define EV_FF 0x15          /* 压力事件 */
#define EV_PWR 0x16         /* 电源事件 */
#define EV_FF_STATUS 0x17   /* 压力状态事件 */

2.申请一个 Input 结构体变量 - input_allocate_device

cpp 复制代码
/**
 * @description:        申请一个 input_dev 结构体变量
 * @param -         :   无
 * @return          :   申请到的 input_dev
 */
struct input_dev *input_allocate_device(void)

3.释放 input_dev 结构体变量 - input_free_device

cpp 复制代码
/**
 * @description:        释放 input_dev 结构体变量
 * @param - dev     :   要释放的 input_dev 
 * @return          :   无
 */
void input_free_device(struct input_dev *dev)

4.向 Linux 内核注册一个 input_dev - input_register_device

cpp 复制代码
/**
 * @description:        向 Linux 注册 input_dev
 * @param - dev     :   要注册的 input_dev
 * @return          :   注册成功返回(0),失败返回(负值)
 */
int input_register_device(struct input_dev *dev)

5.注销 Input 驱动 - input_unregister_device

cpp 复制代码
/**
 * @description:        注销 input_dev
 * @param - dev     :   要注销的 input_dev
 * @return          :   无
 */
void input_unregister_device(struct input_dev *dev)

三.上报事件相关函数

1.上报指定的事件以及对应的值 - input_event

cpp 复制代码
/**
 * @description:            上报指定的事件以及对应的值
 * @param - dev     :       需要上报的 input_dev
 * @param - type    :       上报的事件类型,比如 EV_KEY
 * @param - code    :       事件码,比如注册的按键值,KEY_0 , KEY_1 等
 * @param - value   :       事件值,比如 1 表示按下 , 0 表示按键松开
 */
void input_events(struct input_dev *dev,unsigned int type,unsigned int code,int value)

其他上报事件的函数,都是内部调用了 input_events 函数

cpp 复制代码
/**
 * @description:            上报按键事件
 */
static inline void input_report_key(struct input_dev *dev,unsigned int code,int value)
{
    input_event(dev,EV_KEY,code,!!value);
}



void input_report_rel(struct input_dev *dev,unsigned int code,int value)


void input_report_abs(struct input_dev *dev,unsigned int code,int value)


void input_report_ff_status(struct input_dev *dev,unsigned int code,int value)


void input_report_switch(struct input_dev *dev,unsigned int code,int value)


void input_mt_sync(struct input_dev *dev)

2.告诉 Linux 内核 input 子系统上报结束 - input_sync

cpp 复制代码
/**
 * @description:            告诉 Linux 内核 input 子系统上报结束
 * @param - dev     :       需要上报同步事件的 input_dev
 * @return          :       无
 */
void input_sync(struct input_dev *dev)

3.input_event 结构体

cpp 复制代码
struct input_event
{
    struct timeval time;            //事件发生的时间
    __u16 type;                     //事件类型
    __u16 code;                     //事件码
    __s32 value;                    //值,比如 EV_KEY 事件中, value 就是按键值
};

四. Input 子系统下的按键输入事件代码

1.设备树

(1).流程图

(2).代码部分

2.驱动部分

(1).流程图

(2).代码部分

cpp 复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define KEYINPUT_CNT    1               /* 设备号个数 */
#define KEYINPUT_NAME   "keyinput"      /* 名字 */
#define KEY0VALUE       0X01            /* KEY0按键值 */
#define INVAKEY         0XFF            /* 无效的按键值 */
#define KEY_NUM         1               /* 按键数量 */


/* 中断 IO 描述结构体 */
struct irq_keydesc
{
    int gpio;                           /* gpio */
    int irq_num;                        /* 中断号 */
    unsigned char value;                /* 按键对应的值 */
    char name[10];                      /* 名字 */
    irqreturn_t (*handler)(int,void *); /* 中断服务函数 */
};



/* keyinput 设备结构体 */
struct keyinput_dev
{
    dev_t devid;                                /* 设备号 */
    struct cdev cdev;                           /* cdev */
    struct class *class;                        /* 类 */
    struct device *device;                      /* 设备 */
    struct device_node *nd;                     /* 设备结点 */
    struct timer_list timer;                    /* 定义一个定时器 */
    struct irq_keydesc irqkeydesc[KEY_NUM];     /* 按键描述数组 */
    unsigned char curkeynum;                    /* 当前的按键号 */

    struct input_dev *inputdev;                 /* input 结构体 */
};


/* key input 设备 */
struct keyinput_dev keyinputdev;     



/**
 * @description:            中断服务函数,开启定时器,延时 10ms 定时器用于按键消抖
 */
static irqreturn_t key0_handler(int irq,void *dev_id)
{
    struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}


/**
 * @description:            定时器服务函数,用于按键消抖,定时器到了以后再次读取按键值,如果按键还是处于按下状态就表示按键有效
 * @param - arg     :       设备结构变量
 * @return          :       无
 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct keyinput_dev *dev = (struct keyinput_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    value = gpio_get_value(keydesc->gpio);      //读取 IO 值

    if(0 == value)
    {
        /* 上报按键值 */
        input_report_key(dev->inputdev,keydesc->value,1);
        input_sync(dev->inputdev);
    }
    else
    {
        /* 上报按键值 */
        input_report_key(dev->inputdev,keydesc->value,0);
        input_sync(dev->inputdev);
    }
}




/**
 * @description:            按键 IO 初始化
 * @param           :       无
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    char name[10];
    int ret;

    keyinputdev.nd = of_find_node_by_path("/key");
    if(NULL == keyinputdev.nd)
    {
        printk("key node not find!\r\n");
        return -EINVAL;
    }


    /* 1.获取每一个 GPIO 的编号 */
    for(i = 0; i < KEY_NUM ; i++)
    {
        keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd,"key-gpio",i);
        if(0 > keyinputdev.irqkeydesc[i].gpio)
        {
            printk("can not get key %d \r\n",i);
        }
    }


    /* 2.初始化 key 所使用的 IO , 并且设置成中断模式 */
    for(i = 0;i < KEY_NUM;i++)
    {
        /* (1).申请 GPIO */
        memset(keyinputdev.irqkeydesc[i].name,0,sizeof(name));
        sprintf(keyinputdev.irqkeydesc[i].name,"KEY%d",i);
        gpio_request(keyinputdev.irqkeydesc[i].gpio,keyinputdev.irqkeydesc[i].name);

        /* (2).设置 GPIO 为输入 */
        gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);

        /* (3).获取中断号 */
        keyinputdev.irqkeydesc[i].irq_num = irq_of_parse_and_map(keyinputdev.nd,i);
    }

    /* 3.申请中断 */
    keyinputdev.irqkeydesc[0].handler = key0_handler;
    keyinputdev.irqkeydesc[0].value = KEY0VALUE;
    for(i = 0; i < KEY_NUM; i++)
    {
        ret = request_irq(keyinputdev.irqkeydesc[i].irq_num,keyinputdev.irqkeydesc[i].handler,
                          IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,keyinputdev.irqkeydesc[i].name,&keyinputdev);

        if(0 > ret)
        {
            printk("irq %d request failed!\r\n",keyinputdev.irqkeydesc[i].irq_num);
            return -EFAULT;
        }
    }


    /* 4.创建定时器 */
    init_timer(&keyinputdev.timer);
    keyinputdev.timer.function = timer_function;


    /* 5.申请 input_dev */
    //(1).申请 input 结构体变量
    keyinputdev.inputdev = input_allocate_device();
    //(2).初始化 input_dev ,设置产生哪些事件
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    input_set_capability(keyinputdev.inputdev,EV_KEY,KEY_0);


    /* 6.注册输入设备 */
    ret = input_register_device(keyinputdev.inputdev);
    if(ret)
    {
        printk("register input device failed!\r\n");
        return ret;
    }


    return 0;
}



/**
 * @description:            驱动入口函数
 * @param -         :       无
 * @return          :       无
 */
static int __init keyinput_init(void)
{
    keyio_init();

    return 0;
}



/**
 * @description:            驱动出口函数
 */
static void __exit keyinput_exit(void)
{
    unsigned int i = 0;

    /* 1.删除定时器 */
    del_timer_sync(&keyinputdev.timer);

    /* 2.释放中断 */
    for(i = 0; i < KEY_NUM; i++)
    {
        free_irq(keyinputdev.irqkeydesc[i].irq_num,&keyinputdev);
    }

    /* 3.释放 IO */
    for(i = 0;i < KEY_NUM;i++)
    {
       gpio_free(keyinputdev.irqkeydesc[i].gpio); 
    }


    /* 4.注销与释放 input_dev */
    input_unregister_device(keyinputdev.inputdev);
    input_free_device(keyinputdev.inputdev);
    
}


module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kaneki");

3.应用程序部分

(1).流程图

(2).代码部分

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>



/* 1.定义一个 input_event 变量,用于存放输入事件信息 */
static struct input_event inputevent;


int main(int argc, char  *argv[])
{
    int ret,fd;
    char *filename;

    if(argc != 2)
    {
        printf("Usage : ./%s <dev_path>",argv[0]);
        return -1;
    }

    filename = argv[1];


    fd = open(filename,O_RDWR);
    if(0 > fd)
    {
        perror("open dev error");
        return -1;
    }

    while(1)
    {
        ret = read(fd,&inputevent,sizeof(inputevent));
        /* 读取成功 */
        if(ret > 0)
        {   
            switch(inputevent.type)
            {
                case EV_KEY:
                    if(inputevent.code < BTN_MISC)      //键盘键值
                    {
                        printf("key %d %s \r\n",inputevent.code,inputevent.value ? "press" : "release");
                    }
                    else
                    {
                        printf("button %d %s\r\n",inputevent.code,inputevent.value ? "press" : "release");
                    }
                    ret = 0;
                    break;

                /* 其他事件...... */
            }
        }
        else
            printf("读取失败\n");
    }

    return 0;
}

五.使用Linux自带的按键驱动程序

1.Linux自带的 KEY 驱动文件

Linux 内核自带的 KEY 驱动文件为 drivers/input/keyboard/gpio_keys.c ,其中采用了 platform 框架,在 KEY 驱动上使用了 input 子系统实现。

所以,使用 Linux 自带的 KEY 驱动文件,只需在设备树中注册节点时,注意节点的 compatibel 要与 驱动中匹配列表的 compatible 一致。

Linux自带的驱动如下:

cpp 复制代码
static const struct of_device_id gpio_keys_of_match[] = 
{
    { .compatible = "gpio-keys", },
    { },
};

static struct platform_driver gpio_keys_device_driver = 
{
    .probe = gpio_keys_probe,
    .remove = gpio_keys_remove,
    .driver = 
    {
        .name = "gpio-keys",            //设备树中的 compatible 属性值要与此处一致
        .pm = &gpio_keys_pm_ops,
        .of_match_table = of_match_ptr(gpio_keys_of_match),
    }
};

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

2.设备树

要使用Linux自带的 KEY 驱动,就要使得 KEY 设备树节点中 compatible 属性要与 Linux 自带KEY 驱动中的一致。

(1).流程图

(2).设备树代码

(3).重新编译设备树

重新编译设备树,查看 /dev/input/ 下有没有新增节点

相关推荐
我言秋日胜春朝★20 分钟前
【Linux】进程地址空间
linux·运维·服务器
C-cat.41 分钟前
Linux|环境变量
linux·运维·服务器
yunfanleo1 小时前
docker run m3e 配置网络,自动重启,GPU等 配置渠道要点
linux·运维·docker
糖豆豆今天也要努力鸭1 小时前
torch.__version__的torch版本和conda list的torch版本不一致
linux·pytorch·python·深度学习·conda·torch
烦躁的大鼻嘎2 小时前
【Linux】深入理解GCC/G++编译流程及库文件管理
linux·运维·服务器
ac.char2 小时前
在 Ubuntu 上安装 Yarn 环境
linux·运维·服务器·ubuntu
敲上瘾2 小时前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
长弓聊编程2 小时前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
cherub.2 小时前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
梅见十柒3 小时前
wsl2中kali linux下的docker使用教程(教程总结)
linux·经验分享·docker·云原生