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

news2025/1/13 3:15:23

一.Input 子系统的定义

        input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统
一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等
等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,
鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心
应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动
层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点。

        可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
        驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
        核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行
处理。
        事件层:主要和用户空间进行交互。

二.注册 Input 设备有关函数

1.input_dev 结构体

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 表示输入事件类型,可选择以下事件

#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

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


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

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

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

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

5.注销 Input 驱动 - input_unregister_device

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

三.上报事件相关函数

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

/**
 * @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 函数

/**
 * @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

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

3.input_event 结构体

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

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

1.设备树

(1).流程图

(2).代码部分

2.驱动部分

(1).流程图

(2).代码部分

#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).代码部分

#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自带的驱动如下:

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/ 下有没有新增节点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2215241.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊聊 Facebook Audience Network 绑定收款账号的问题

大家好&#xff0c;我是牢鹅&#xff01;本篇是Facebook开发者系列的第五篇&#xff0c;最近看见好多群友在群里问这个&#xff0c;说Facebook的变现账户在绑定国内的银行账户时&#xff08;有些用户反馈就算不是国内的卡也会出现该问题&#xff09;&#xff0c;显示“无法绑定…

05 django管理系统 - 部门管理 - 修改部门

04我们已经实现了新增部门的功能&#xff0c;下面开始修改部门模块的实现。 按道理来说&#xff0c;应该是做成弹框样式的&#xff0c;通过ajax悄咪咪的发数据&#xff0c;然后更新前端数据&#xff0c;但是考虑到实际情况&#xff0c;先用页面跳转的方式实现&#xff0c;后面…

【含文档】基于Springboot+Vue的旅游信息管理系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

QSettings 使用详解

QSettings 类是 Qt 框架中的一个重要类&#xff0c;用于存储和访问应用程序的设置和配置。它提供了一种简单的方法来读取和写入应用程序的配置数据&#xff0c;支持多种存储格式&#xff0c;包括 Windows 注册表、INI 文件和 XML 文件等。 主要功能 1. 存储设置&#xff1a;可…

PDF在线编辑器推荐!一站式解决PDF编辑难题!

当你要对PDF文件进行编辑时&#xff0c;一款PDF编辑器就十分重要。今天小编就为大家推荐几款PDF编辑器&#xff0c;有在线的&#xff0c;也有本地的&#xff0c;大家可以根据自己的需求体验选择&#xff01; Foxit PDF Edit 直达链接&#xff1a;editor.foxitsoftware.cn Fo…

大舍传媒-海外媒体发稿:为您打造全球品牌影响力

大舍传媒-海外媒体发稿&#xff1a;为您打造全球品牌影响力 在当今全球化的商业环境中&#xff0c;企业若想在激烈的市场竞争中脱颖而出&#xff0c;拓展全球市场&#xff0c;提升品牌影响力至关重要。大舍传媒的海外媒体发稿服务&#xff0c;正是您实现这一目标的得力助手。 …

某宝228滑块 请求头 bx_pp、bx_et、以及slide接口中参数n值。90%左右的成功率,轨迹不会爆,需要的联系

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关。 本文章未经许可禁止转载&#xff0c;禁止任何修改后二次传播&#xff0c;擅自使用本文讲解的技术而导致的任何意外&#xff…

PostgreSQL学习笔记:学习总结

一、架构 1. 常驻进程&#xff08;Postmaster&#xff09; 管理后端的常驻进程&#xff0c;默认监测UNIX Domain Socket和TCP/IP&#xff08;Windows等一部分平台只监测TCP/IP&#xff09;的5432端口&#xff0c;等待前端连接处理&#xff0c;监测的端口号可在设置文件postgre…

基于腾讯云的AI视频课程制作工具

1. 需求信息 1.1 需求背景 讲师们在制作视频的过程中&#xff0c;发现录制课程比较麻烦&#xff0c;要保证环境安静&#xff0c;保证录制过程不出错&#xff0c;很容易反复重复录制&#xff0c;为了解决重复录制的工作量&#xff0c;想通过 ai 课程制作工具&#xff0c;来解决…

注册安全分析报告:北外网校

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

【软考】设计模式之中介者模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 适用性6. 优点 1. 说明 1.用一个中介对象来封装一系列的对象交互。2.中介者使各对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。3.中介者模式&#xff08;Mediator Pattern&…

Qt-自定义控件鼠标事件键盘事件定时器绘图

1. 自定义控件 1.1 创建自定义控件 1.在项目目录上右键&#xff0c; 选择 "Add New" 2.选择 "Qt" --> "Qt 设计师界面类" 3.根据需求选择模板&#xff0c;此处选择空窗口 4.设置类名 和 相关文件名 使用设计师界面类会产生三个文件&…

媒界:插混VS增程:魏牌蓝山用天花板Hi4诠释都市家庭用车最优解

在新能源混动领域&#xff0c;关于插混、增程谁才是混动最优解&#xff0c;一直业内争论的焦点。正如路遥知马力、日久见人心。对于新能源动力系统的评判标准来说&#xff0c;最好的答案就是路上见。 近日&#xff0c;一位媒体博主驾驶着魏牌全新蓝山从阿拉善到武汉往返狂飙30…

Python面向对象编程:封装和私有属性④

文章目录 1. 引言2. 什么是封装&#xff1f;3. 公有属性和方法4. 私有属性和方法5. 属性访问器&#xff08;Getters 和 Setters&#xff09;6. 使用 property 函数7. 综合示例7.1 项目结构7.2 模块代码__init__.pystudent.pycourse.pymanager.py 7.3 主程序代码main.py 7.4 运行…

cmake模板-支持编译动态/静态文件

代码链接&#xff1a;代码仓库 git clone https://gitee.com/etsuyou/cmake-template.git模板 模板截图 如何使用 在src和inc中写代码 此处用我默认提供的代码 ./go.sh cmake 生成Makefile ./go.sh make 生成bin文件和.a以及.so ./go.sh run app 运行 ./go.sh clean 以…

Tomcat服务部署及优化

一、Tomcat的基本介绍 1. tomcat是什么&#xff1f; Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP程序的首选。一般来说&#xff0c;T…

QT QML 练习8-Simple Transformations

简单的转换&#xff08;Simple Transformations&#xff09; 转换操作改变了一个对象的几何状态。QML元素对象通常能够被平移&#xff0c;旋转&#xff0c;缩放。下面我们将讲解这些简单的操作和一些更高级的用法。 我们先从一个简单的转换开始。用下面的场景作为我们学习的开始…

Python学习100天第9天之面向对象进阶

1 前言 在前面的章节我们已经了解了面向对象的入门知识&#xff0c;知道了如何定义类&#xff0c;如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发&#xff0c;我们还需要对Python中的面向对象编程进行更为深入的了解。 2 property装饰器…

AVLTree 旋转笔记(根据平衡因子插入的公式,贼好理解)

平衡因子 avltree是一棵每个节点的左右子树的高度差不超过1的二叉树搜索树&#xff0c;对于avltree最重要的就是对平衡因子的控制。 对于旋转我们重点要注意的是三个节点&#xff0c;以左旋举例&#xff0c;需要注意的就是parent&#xff0c;subr&#xff0c;subrl。而旋转的方…

MYSQL架构、执行过程和顺序

MYSQL架构、执行过程和顺序 一、前言 1.1、说明 就MySQL的架构&#xff0c;以及执行过程、sql执行顺序&#xff0c;以及一些相关学习分享内容。 在参考文章的基础上&#xff0c;会增加自己的理解、看法&#xff0c;希望本文章能够在您的学习中提供帮助。 如有错误的地方&a…