Linux驱动开发笔记(八)输入子系统

news2025/3/1 11:04:21

文章目录

  • 前言
  • 一、输入子系统
    • 1. 子系统的引入
    • 2. 组成部分
    • 3. 事件处理流程
    • 4. 相关数据结构
  • 二、程序编写
    • 1. 相关API函数
      • 1.1 input_allocate_device ( )
      • 1.2 input_free_device ( )
      • 1.3 input_register_device ( )
      • 1.4 input_unregister_device ( )
      • 1.5 input_event ( )
      • 1.6 input_report_key ( )和input_sync ( )
    • 2. probe函数的编写
    • 3. remove函数
    • 4. 按键消抖


前言

  Linux 的 input 子系统是一个用于处理和管理输入设备(例如键盘、鼠标、触摸屏、游戏控制器等)的框架。它的作用是将硬件输入设备产生的原始输入数据转换成系统可以识别和使用的输入事件,并将这些事件传递给用户空间的应用程序。


一、输入子系统

1. 子系统的引入

  引入子系统是操作系统设计中一个重要的实践,它有助于管理复杂性、提高可扩展性和维护性。子系统将操作系统的不同功能模块化,每个子系统专注于特定的功能。例如,input子系统专门处理各种输入设备的数据。这种模块化设计可以简化内核的复杂性,使得每个子系统更易于开发、测试和维护。
  子系统提供标准化的接口和框架,简化了驱动程序的开发。开发人员只需关注如何与具体设备交互,而不必关心数据如何传递到用户空间。
  这种模块化和标准化设计有助于增强系统的稳定性。每个子系统都是相对独立的模块,出问题时可以单独调试和修复,而不影响其他部分。例如,input子系统出问题时,主要影响输入设备,而不会波及网络、存储等其他子系统。

2. 组成部分

  linux为了统一各个输入设备,将输入子系统分为了Drivers(驱动层)、Input Core(输入子系统核心层)、Handlers(事件处理层)三部分:
在这里插入图片描述

  Drivers
  设备驱动程序负责与实际的硬件设备进行交互,将设备的原始输入数据传递给 input core。每种输入设备(如键盘、鼠标)通常都有相应的驱动程序。
  Input Core
  这是 input 子系统的核心部分,为Drivers提供了规范及接口并通知Handlers对事件进行处理。负责管理输入设备和事件,处理设备注册、事件分发等工作。
  Handlers
  事件处理程序负责接收来自 input core 的输入事件,并将其传递给用户空间的应用程序或其他内核子系统。常见的事件处理程序包括键盘事件处理器、鼠标事件处理器等。

3. 事件处理流程

  1. 驱动程序检测到输入事件:
    当输入设备(如键盘或鼠标)产生输入数据时,设备驱动程序会检测到这些事件。
  2. 驱动程序生成输入事件:
    驱动程序将这些原始数据转换成标准的 input 事件结构,并将其传递给 input core。
  3. Input core 分发事件:
    input core 接收到驱动程序传递的事件后,将其分发给相应的事件处理程序。
  4. 事件处理程序处理事件:
    事件处理程序(如键盘或鼠标事件处理程序)接收到事件后,将其转换成用户空间可以理解的格式,并通过 input 事件接口传递给用户空间的应用程序。
  5. 用户空间应用程序接收事件:
    用户空间的应用程序通过读取 /dev/input/eventX 设备文件来接收和处理这些输入事件。

4. 相关数据结构

  我们可以将所有输入设备的输入信息将被抽象成以下结构体:

//输入事件
struct input_event{
    struct timeval time;//事件产生的时间
    __u16 type;         //输入设备的类型,鼠标、键盘、触摸屏
    __u16 code;			//设备类型的不同其含义也不同,如其类型是按键表示为按键号
    __s16 value;		//设备类型的不同其含义也不同,如其类型是按键表示为按键值
}

  我们可以利用struct input_dev结构体来代表一个具体的输入设备, 后面将会根据具体的设备来初始化这个结构体。

struct input_dev {
    const char *name;   //提供给用户的输入设备的名称
    const char *phys;   //提供给编程者的设备节点的名称
    const char *uniq;   //指定唯一的ID号
    struct input_id 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)];
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
    /*----------以下结构体成员省略----------------*/
};
  • evbit:用于指定支持的事件类型,这要根据实际输入设备能够产生的事件来选择,可选选项如下所示。
输入子系统事件类型取值描述
EV_SYN0x00同步事件
EV_KEY0x01用于描述键盘、按钮或其他类似按键的设备
EV_REL0x02用于描述相对位置变化,例如鼠标移动
EV_ABS0x03用于描述绝对位置变化,例如触摸屏的触点坐标
EV_MSC0x04其他事件类型
EV_SW0x05用于描述二进制开关类型的设备,例如拨码开关
EV_LED0x11用于 LED 事件,表示 LED 灯的状态
EV_SND0x12用于声音事件,表示声音的播放相关事件
EV_REP0x14用于重复事件,表示键盘重复发送事件
EV_FF0x15用于力反馈事件,表示力反馈设备的输出事件
EV_PWR0x16用于电源事件,表示电源状态变化
EV_FF_STATUS0x17用于力反馈状态事件,表示力反馈设备的状态变化
EV_MAX0x1f输入事件类型的最大值
EV_CNT(EV_MAX+1)输入事件类型的数量
  • keybit:记录支持的键值,“键值”在程序中用于区分不同的按键,可选“键值”如下所示。
按键键值描述
KEY_RESERVED0
KEY_ESC1
KEY_12
KEY_23
KEY_34
KEY_45

二、程序编写

1. 相关API函数

1.1 input_allocate_device ( )

//申请input_dev结构体
struct input_dev *input_allocate_device(void);
  • 参数:无
  • 返回值:申请到的 input_dev。

1.2 input_free_device ( )

//释放掉申请到的input_dev
void input_free_device(struct input_dev *dev);
  • 参数
    • dev:需要释放的input_dev。
  • 返回值:无

1.3 input_register_device ( )

//初始化input_dev
int input_register_device(struct input_dev *dev);
  • 参数
    • dev:要注册的 input_dev 。
  • 返回值:0,input_dev 注册成功;负值,input_dev 注册失败

1.4 input_unregister_device ( )

//注销input_dev
void input_unregister_device(struct input_dev *dev);
  • 参数
    • dev:要注销的 input_dev
  • 返回值:无

1.5 input_event ( )

//上报事件
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
  • 参数:
    • dev:指定输设备(input_dev结构体)。
    • type:事件类型。
    • code:编码。
    • value:指定事件的值。
  • 返回值: 无

1.6 input_report_key ( )和input_sync ( )

//发送上报结束事件
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!value);
}
//上报按键事件
static inline void input_sync(struct input_dev *dev)
{
    input_event(dev, EV_SYN, SYN_REPORT, 0);
}

2. probe函数的编写

  本次实验采用平台设备的写法,考虑到在input子系统中,我们不需要进行设备号的申请和类的创建,这里便不进行module_init和module_exti的编写。代码如下(示例):

//这里还用到了上章讲到的tasklet和work软中断,在宏定义通过更改DEFER_TEST实现切换
static int button_probe(struct platform_device *pdev)
{
    struct button_data *priv;
    struct gpio_desc *gpiod;
    struct input_dev *i_dev;
    int ret;

    pr_info("button_probe\n");
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    i_dev = input_allocate_device();
    if (!i_dev) {
        devm_kfree(&pdev->dev, priv);
        return -ENOMEM;
    }

    i_dev->open = btn_open;
    i_dev->close = btn_close;
    i_dev->name = "key input";
    i_dev->dev.parent = &pdev->dev;
    priv->button_input_dev = i_dev;
    priv->pdev = pdev;

    set_bit(EV_KEY, i_dev->evbit);
    set_bit(KEY_1, i_dev->keybit);

    gpiod = gpiod_get(&pdev->dev, "button", GPIOD_IN);
    if (IS_ERR(gpiod)) {
        ret = PTR_ERR(gpiod);
        devm_kfree(&pdev->dev, priv);
        input_free_device(i_dev);
        return ret;
    }

    priv->irq = gpiod_to_irq(gpiod);
    priv->button_input_gpiod = gpiod;

    ret = input_register_device(priv->button_input_dev);
    if (ret) {
        pr_err("Failed to register input device\n");
        gpiod_put(priv->button_input_gpiod);
        devm_kfree(&pdev->dev, priv);
        input_free_device(i_dev);
        return ret;
    }

    platform_set_drvdata(pdev, priv);

    // 初始化去抖动工作
    INIT_DELAYED_WORK(&priv->debounce_work, button_debounce_work);

    ret = request_any_context_irq(priv->irq, button_input_irq_handler, IRQF_TRIGGER_FALLING, "input-button", priv);
    if (ret < 0) {
        dev_err(&pdev->dev, "Request GPIO IRQ failed\n");
        input_unregister_device(priv->button_input_dev);
        gpiod_put(priv->button_input_gpiod);
        devm_kfree(&pdev->dev, priv);
        return ret;
    }

#if (DEFER_TEST == 0)
    tasklet_init(&button_tasklet, button_tasklet_handler, 0);
#elif (DEFER_TEST==1)
    /*初始化button_work*/
    INIT_WORK(&button_work, button_work_hander);
#endif

    return 0;
}
}

3. remove函数

static int button_remove(struct platform_device *pdev)
{
    struct button_data *priv = platform_get_drvdata(pdev);
    
#if (DEFER_TEST == 0)
    tasklet_kill(&button_tasklet);
#endif

    cancel_delayed_work_sync(&priv->debounce_work);
    free_irq(priv->irq, priv);
    input_unregister_device(priv->button_input_dev);
    gpiod_put(priv->button_input_gpiod);
    devm_kfree(&pdev->dev, priv);

    return 0;
}

4. 按键消抖

//按键消抖
static void button_debounce_work(struct work_struct *work)
{
    struct button_data *priv = container_of(work, struct button_data, debounce_work.work);
    int button_status;

    button_status = gpiod_get_value(priv->button_input_gpiod);
    input_report_key(priv->button_input_dev, KEY_1, button_status);
    input_sync(priv->button_input_dev);
}

//中断服务函数
static irqreturn_t button_input_irq_handler(int irq, void *dev_id)
{
    struct button_data *priv = dev_id;
     // 调试信息:记录去抖动工作被执行
    pr_info("Debounce work executed\n");

    // 调度去抖动工作
    schedule_delayed_work(&priv->debounce_work, msecs_to_jiffies(DEBOUNCE_DELAY_MS));
    
#if (DEFER_TEST==0)
	tasklet_schedule(&button_tasklet);
#elif (DEFER_TEST==1)
	schedule_work(&button_work);  //触发工作
#endif	

    return IRQ_HANDLED;
}

免责声明:本文参考了野火的部分资料,仅供学习参考使用,若有侵权或勘误请联系笔者。

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

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

相关文章

ModbusRTU协议之3.5T

RTU 模式每个字节 ( 11 位 ) 的格式为 : 1 起始位&#xff0c; 8 数据位&#xff0c; 1 校验位&#xff08;也可以无校验&#xff09;&#xff0c; 1 停止位&#xff0c; 校验位使用偶校验、奇校验、无校验都可以&#xff0c;但 使用无校验时必须配置 2 个停止位。 所以无…

阿里云的视频超分辨率服务将视频从25M视频增强至469M,画质提升了不少

效果还是不错的&#xff1a; tom&#xff1a; 阿里云文档&#xff1a; 视频超分辨率SuperResolveVideo的语法及示例_视觉智能开放平台(VIAPI)-阿里云帮助中心 请求参数&#xff1a; 还比较简单&#xff0c;就三个参数&#xff0c;其中一个还是固定的&#xff0c;我本次的BitRat…

【SQLAlChemy】表之间的关系,外键如何使用?

表之间的关系 数据库表之间的关系分为三种&#xff1a; 一对一关系&#xff08;One-to-One&#xff09;&#xff1a;在这种关系中&#xff0c;表A的每一行都与表B的一行关联&#xff0c;反之亦然。例如&#xff0c;每个人都有一个唯一的社保号&#xff0c;每个社保号也只属于…

深度学习(六)——神经网络的基本骨架:nn.Module的使用

一、torch.nn简介 官网地址&#xff1a; torch.nn — PyTorch 2.0 documentation 1. torch.nn中的函数简介 Containers&#xff1a;神经网络的骨架 Convolution Layers&#xff1a;卷积层 Pooling layers&#xff1a;池化层 Padding Layers&#xff1a;Padding Non-linear …

android 播放视频

播放视频文件 新建一个activity_main.xml文件&#xff0c;文件中放置了3个按钮&#xff0c;分别用于控制视频的播放、暂停和重新播放。另外在按钮的下面又放置了一个VideoView&#xff0c;稍后的视频就将在这里显示。 <LinearLayout xmlns:android"http://schemas.an…

Android Studio新增功能:Device Streaming

今天将Android Studio升级到2023.3.1 Patch2。发现新增了Device Streaming功能。支持远程使用Google的物理设备调试程序。这样可以方便地在真实设备上测试自己的APP。这对于手头没有Google设备的开发者而言&#xff0c;确实方便很多。该功能目前处于测试阶段&#xff0c;在2025…

【机器学习300问】117、序列模型中的符号表示方法?以命名实体识别(NER)任务为例。

在序列模型中&#xff0c;特别是在命名实体识别(NER)任务中&#xff0c;我们通常会用一系列符号来表示输入序列、目标标签以及模型的结构和操作。本文列出一些常见的符号表示方法&#xff0c;结合NER任务进行解释。 一、什么是命名实体识别任务&#xff1f; &#xff08;1&am…

06 SpringBoot 配置文件详解-application.yaml

Spring Boot 提供了大量的自动配置&#xff0c;极大地简化了spring 应用的开发过程&#xff0c;当用户创建了一个 Spring Boot 项目后&#xff0c;即使不进行任何配置&#xff0c;该项目也能顺利的运行起来。当然&#xff0c;用户也可以根据自身的需要使用配置文件修改 Spring …

L50--- 104. 二叉树的最大深度(深搜)---Java版

1.题目描述 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 2.思路 这个二叉树的结构如下&#xff1a; 根节点 1 左子节点 2 右子节点 3 左子节点 4 计算过程 从根节点 1 开始计算&#xff1a; 计算左子树的最大深度&#xff1a; 根节点 2&#xf…

QTcreator编译器路径错误,no valid kit found

重装系统后&#xff0c;整个QT的应用换了一个盘&#xff0c;但是创建QT工程时默认的编译器路径还是以前的路径且无法修改&#xff0c;创建工程时&#xff0c;出现no valid kit found 可见在option下的编译器相关路径是在E盘且无法更改 进入现在的QT盘进行文件的修改 F:\QT\Too…

UnityAPI学习之协程原理与作用

协程的原理与作用 Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 using System.Collections; using System.Collections.Generic; using UnityEngine;public class NO14_coroutine : MonoBehaviour {Animator animator;// Start is called before the first…

深入掌握SystemVerilog验证:《SystemVerilog验证 测试平台编写指南》(可下载)

在数字设计的世界中&#xff0c;验证是确保硬件设计满足预期功能和性能要求的关键步骤。SystemVerilog作为一种先进的硬件描述语言&#xff0c;以其强大的验证特性&#xff0c;成为了设计和验证工程师的首选工具。 1. SystemVerilog验证的重要性 在数字设计流程中&#xff0c…

javaWeb项目-ssm+vue在线购物系统功能介绍

本项目源码&#xff1a;java-ssmvue在线购物系统的设计与实现源码说明文档资料资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;sprin…

[Nacos]No spring.config.import property has been defined

在学习 Spring Cloud Alibaba &#xff0c;Nacos组件&#xff0c;创建一个cloudalibaba-config-nacos-client&#xff0c;加载多配置集时遇到问题 配置了 bootstrap.yml 后启动项目报错&#xff1a; 是因为在springcloud 2020.0.2版本中把bootstrap的相关依赖从spring-cloud-s…

Python 中国象棋游戏【含Python源码 MX_011期】

简介&#xff1a; 中国象棋是一种古老而深受喜爱的策略棋类游戏&#xff0c;也被称为中国的国粹之一。它在中国有着悠久的历史&#xff0c;起源可以追溯到几个世纪以前。Python 中国象棋游戏是一个用Python编程语言编写的软件程序&#xff0c;旨在模拟和提供中国象棋的游戏体验…

一键获取CPU详情:Python最强查询工具实战教程

要在 Python 中查询 CPU 信息,可以使用多个强大的工具和库.以下是一些最常用和强大的库,可以帮助你获取详细的 CPU 信息&#xff1a; 1.psutil 2.cpuinfo 3.platform 4.os 1. 使用 psutil 库 psutil 是一个跨平台库,提供了获取系统和进程信息的便捷方法.它不仅可以获取 C…

I/O Stream设计实验

实验要求和目的 深入理解java输入输出流相关类的基本用法&#xff0c;并且可以掌握Java程序的编写和调试。 实验环境 Java语言&#xff0c;PC或android平台 实验具体内容 设计和编写以下程序&#xff1a; 程序1&#xff1a; 从键盘读入多行字符串&#xff08;英文&#xf…

生产运作管理--第六版陈荣秋

第一章&#xff1a; 生产运作的分类有哪些&#xff1f; 答&#xff1a;可以分为两大类&#xff1a; 产品生产: 产品生产是通过物理或者化学作用将有形输入转化为有形输出的过程。 按照工艺过程的特点&#xff0c;可以分为&#xff1a; 连续性生产&#xff1a;物料均匀、连续的按…

右值引用和移动语义

什么是左值&#xff1f;什么是右值&#xff1f; 通俗来讲&#xff0c;可以出现在赋值语句左侧的&#xff0c;为左值&#xff1b;只能出现在赋值语句右侧的&#xff0c;为右值。 左值与右值的本质区别在于&#xff1a;左值能取地址&#xff0c;但右值不能。 本文主要通过三个场景…

Commons-io工具包与Hutool工具包

Commons-io Commons-io是apache开源基金组织提供的一组有关IO操作的开源工具包 作用:提高I0流的开发效率。 FileUtils类(文件/文件夹相关) static void copyFile(File srcFile,File destFile) 复制文件 static void copyDirectory(File srcDir,File destDir) 复…