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

文章目录

  • 前言
  • 一、输入子系统
    • 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; //输入设备标识IDunsigned 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);
#endifreturn 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);
#endifcancel_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.mzph.cn/diannao/27337.shtml

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

相关文章

Prometheus写入influxDB:中间件remote_storage_adapter

Prometheus写入influxDB&#xff1a;中间件remote_storage_adapter prometheus默认采用的是本地磁盘做数据存储&#xff0c;本地存储的优势就是运维简单但是缺点就是无法海量的metrics持久化和数据存在丢失的风险,数据写入可能造成wal文件损坏导致采集数据无法再写入的问题。 …

【嵌入式DIY实例】-Nokia 5110显示DS18B20传感器数据

Nokia 5110显示DS18B20传感器数据 文章目录 Nokia 5110显示DS18B20传感器数据1、硬件准备2、代码实现本文将介绍如何使用 ESP8266 NodeMCU 板和 DS18B20 数字温度传感器实现简单的温度测量站。 NodeMCU 微控制器 (ESP8266EX) 从 DS18B20 传感器读取温度值,并将其打印在诺基亚 …

Nginx部署Vue项目css文件能加载但是不生效

目录 问题描述问题解决 问题描述 Nginx部署打包后的Vue项目css文件能加载但是不生效&#xff0c; 问题解决 查看响应标头&#xff0c;发现不对劲&#xff0c; Content-Type: text/plain正确的应该是 Content-Type: text/css根本原因是nginx没有告诉浏览器正确的文件类型 所…

Postman接口测试/接口自动化实战教程

一、API 自动化测试 Postman 最基本的功能用来重放请求&#xff0c;并且配合良好的 response 格式化工具。 高级点的用法可以使用 Postman 生成各个语言的脚本&#xff0c;还可以抓包&#xff0c;认证&#xff0c;传输文件。 仅仅做到这些还不能够满足一个系统的开发&#x…

Python学习打卡:day05

day5 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day538、函数的初体验39、函数的基础定义语法函数的定义注意事项 40、函数的基础定义案例练习41、函数的传入参数42、函数的传入参数案例练习——升…

毕业年薪30W起!25届最近5年浙江大学自动化考研院校分析

浙江大学 目录 一、学校学院专业简介 二、考试科目指定教材 三、近5年考研分数情况 四、近5年招生录取情况 五、最新一年分数段图表 六、历年真题PDF 七、初试大纲复试大纲 八、学费&奖学金&就业方向 一、学校学院专业简介 二、考试科目指定教材 1、考试科目…

揭秘招生简章的制作方法

一年一度的招生季即将来临&#xff0c;各大院校纷纷摩拳擦掌&#xff0c;准备迎接新的学子。对于学校而言&#xff0c;招生简章是其对外宣传的重要窗口&#xff0c;它直接关系到学校的招生效果和声誉。那么&#xff0c;如何制作一份既吸引人又实用的招生简章呢&#xff1f;下面…

【leetcode--字母异位词分组】

class Solution:def groupAnagrams(self, strs: List[str]) -> List[List[str]]:np collections.defaultdict(list)for st in strs:name "".join(sorted(st))np[name].append(st)return list(np.values()) collections.defaultdict(list)创建字典类型&#xff…

Git代码冲突原理与三路合并算法

Git代码冲突原理 Git合并文件是以行为单位进行一行一行合并的&#xff0c;但是有些时候并不是两行内容不一样Git就会报冲突&#xff0c;这是因为Git会帮助我们进行分析得出哪个结果是我们所期望的最终结果。而这个分析依据就是三路合并算法。当然&#xff0c;三路合并算法并不…

Flowable-决策表设计器

✨✨✨ 最好用的Flowable决策表设计器 ✨✨✨ 最好用的Flowable流程设计器 本文中内容和案例出自贺波老师的书《深入Activiti流程引擎&#xff1a;核心原理与高阶实战》&#xff0c;书中的介绍更全面、详细&#xff0c;推荐给大家。 深入Activiti流程引擎

C++ 31 之 静态成员变量

#include <iostream> #include <string.h> using namespace std;// 特点: // 1.在编译阶段就分配了内存空间 // 2.类内声明&#xff0c;在类外进行初始化 // 3.所有对象共享一份静态成员数据 class Students01{ public:static int s_a; // 静态成员变量int s_b; };…

LSS 和 BEVDepth算法解读

前言 当前BEV的研究大都基于深度学习的方法&#xff0c;从组织BEV特征信息的方式来看&#xff0c;主流方法分属两类&#xff1a;自底向上方法和自顶向下方法。 自底向上方法比较早的代表工作是LSS&#xff0c;后来BEVDet、BEVDepth等也是基于LSS的框架来进行优化。自底向上方…

电子画册制作与传统画册相比,有哪些优势?

在当今数字化时代&#xff0c;电子画册作为一种新兴的媒体形式&#xff0c;其制作与传统画册相比具有显著的优势。以下是对这些优势的详细探讨。 首先&#xff0c;电子画册的制作过程通常更加便捷和经济。相较于传统画册需要经历的繁琐的印刷过程&#xff0c;电子画册的制作大多…

【C++课程学习】:类和对象(拷贝构造和运算符重载)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 ✍拷贝构造&#xff1a; &#x1f349;特点一&#xff1a; &#x1f349;特点二&#xff1a; &…

(三十)Flask之wtforms库【剖析源码上篇】

每篇前言&#xff1a; &#x1f3c6;&#x1f3c6;作者介绍&#xff1a;【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者 &#x1f525;&#x1f525;本文已收录于Flask框架从入门到实战专栏&#xff1a;《Flask框架从入…

45.Python-web框架-Django - 开始建立第一个项目

目录 1.django是什么&#xff1f; 2.Pycharm 社区版&#xff0c;还是专业版&#xff1f; 3.开始django&#xff0c;Pycharm专业版 创建一个Django项目 运行一个Django项目 运行方法一&#xff0c;命令行的方式 运行方法二&#xff0c;配置Django Server的方式 4.django尊…

gpio-0设置成输出失败的原因

原因 spi驱动先设置gpio-0成了输出&#xff1b; 但是后面其他驱动&#xff0c;使用of_property_read_u32获取设备树节点的属性时&#xff0c;没去判断返回值&#xff1b;而直接使用参数返回的0&#xff0c;导致gpio-0又被设置成了输入 实例 正常的如下&#xff0c;gpio显示…

入职3年-我如何做一名AI产品经理(文末福利)

引言 从2021年校招加入京东开始&#xff0c;我一直从事AI产品经理的工作&#xff0c;有幸见证了AI行业的热情从一台台服务器烧到了全世界各个角落&#xff0c;也见证了京东AI中台团队的影响力如何一步步的扩大。从21年的迷茫到24年的坚定&#xff0c;很庆幸我正走在适合自己的…

【C++】类的默认成员函数

类的默认成员函数 类的六个默认成员函数构造函数构造函数的概念构造函数的特性 析构函数析构函数的概念析构函数的特性 构造函数与析构函数的调用顺序拷贝构造拷贝构造的概念拷贝构造的特性赋值运算符重载运算符重载赋值运算符重载前置与后置重载输入输出流重载 const修饰成员实…

高精度减法

高精度减法 两个高精度整数的减法题目思路实现代码实现 两个任意符号的高精度加减法 两个高精度整数的减法 高精度指的是数字的大小非常非常大&#xff0c;最多能有10的5次方 的 位数。 本次计算的两个数均为 正数&#xff0c;如何求负数会在最后提到。 题目 给定两个正整数…