前言

这次主要会学习Linux中对于输入设备统一封装的框架,在计算机组成原理中,我们可以知道计算机的组成主要分为五个部分:控制器运算器存储器输入输出。可见,输入作为其中的一个子系统,但是对于众多的设备来说,需要一套统一的规范。所以,在嵌入式系统中的外设,鼠标、键盘、按键、G-Sensor等等都可以注册为Input设备。Linux在用户层提供了相应的接口读取数据,这里我暂时只介绍在上一篇文章的基础上,如何编写一个Input驱动

框架

首先还是先看一下Input子系统的整体框架,具体如下图所示;

如何实现input device 设备驱动?

如何编写input驱动的sample?在内核文档中已经有详细的介绍,虽然是英文的,但是相当规范,简单,详细,本文就是在此基础上展开,具体可以参考内核input文档

在这里,我们需要在代码中做哪些工作呢?下面将会重点罗列成每一个步骤进行讲解。

头文件

首先需要包含头文件#include <linux/input.h>,这个头文件包含了input设备的各种定义,结构体以及方法。

注册input_dev设备

首先定义一个用来注册input device的变量,这里会用到input_dev这个结构体。目前的情况是,我把需要的各种变量都封装到gpio_demo_device这个结构体中,但是,实际上这里暂时这关心input_demo就可以了。

struct gpio_demo_device {

	struct platform_device *pdev;
struct device *dev;
struct gpio_platform_data *pdata;
struct workqueue_struct *gpio_monitor_wq;
struct delayed_work gpio_delay_work ;
struct input_dev *input_demo; };

在这里需要为我们创建的gpio_demo_device结构体定义一个变量priv,并且使用devm_kzalloc为其分配相应的内存。

struct gpio_demo_device *priv;
priv = devm_kzalloc(dev, sizeof(*priv) , GFP_KERNEL);

然后,我们再为input_demo分配内存;

priv->input_demo = devm_input_allocate_device(priv->dev);

初始化input_demo

// kernel/include/uapi/linux/input-event-codes.h
priv->input_demo->name = "input-demo";
priv->input_demo->dev.parent = priv->dev;
priv->input_demo->evbit[0] = BIT_MASK(EV_KEY); //事件类型注册为EV_KEY
priv->input_demo->keybit[BIT_WORD(KEY_VOLUMEDOWN)] = BIT_MASK(KEY_VOLUMEDOWN); //按键值

name:当前初始化的input device的名字;

evbit[0]:配置为BIT_MASK(EV_KEY),将事件类型注册为EV_KEY类型,具体有哪些类型如下所示;

所有定义都在kernel/include/uapi/linux/input-event-codes.h文件中。

除了EV_KEY之外,还有两种基本事件类型:EV_RELEV_ABS。它们用于设备提供的相对值和绝对值。相对值可以是例如X轴上的鼠标移动。鼠标将其报告为与上一个位置的相对差异,因为它没有任何绝对坐标系可供使用。绝对事件即操纵杆和数字化仪 - 在绝对坐标系中工作的设备。

/*

Event types

*/

#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

#define EV_SND 0x12

#define EV_REP 0x14

#define EV_FF 0x15

#define EV_PWR 0x16

#define EV_FF_STATUS 0x17

#define EV_MAX 0x1f

#define EV_CNT (EV_MAX+1)

keybit[BIT_WORD(KEY_VOLUMEDOWN)]:按键值设置为BIT_MASK(KEY_VOLUMEDOWN),就是音量减的按键;

BITS_TO_LONGS(), BIT_WORD(), BIT_MASK()

These three macros from bitops.h help some bitfield computations:

BITS_TO_LONGS(x) - returns the length of a bitfield array in longs for x bits

BIT_WORD(x) - returns the index in the array in longs for bit x

BIT_MASK(x) - returns the index in a long for bit x

完成初始化之后,然后注册input_demo

ret = input_register_device(priv->input_demo);

上报按键值

当用户发生了向系统输入一定信息的操作之后,input device需要将一些列信息上传,本文需要上传按键值。在这里要可以通过中断触发,或者轮询去检测用户的动作,前面已经有提及,这里不再赘述,

本文代码已经在附录中,已经涵盖这两种方式。

关于上报数据可以通过input_report_keyinput_sync去完成。

dev->open()和dev->close()

如果驱动程序必须重复轮询设备,因为它没有来自它的中断,并且轮询太昂贵而无法一直进行,或者如果设备中断资源比较稀缺,可以使用open和close回调来通知设备何时可以停止轮询或释放中断以及何时必须重新开始轮询或再次获取中断。为此,我们将以下代码添加到示例驱动程序:

static int button_open(struct input_dev *dev)
{
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
return -EBUSY;
}
return 0;
} static void button_close(struct input_dev *dev)
{
free_irq(IRQ_AMIGA_VERTB, button_interrupt);
} static int gpio_demo_probe(struct platform_device *pdev)t(void)
{
...
button_dev->open = button_open;
button_dev->close = button_close;
...
}

请注意,输入内核会跟踪设备的用户数,并确保仅在第一个用户连接到设备时调用dev->open(),并在最后一个用户断开连接时调用dev-> close() 。对两个回调的调用都是序列化的。

open()回调应该在成功的情况下返回0,或者在失败的情况下返回任何非零值。close()回调(无效)必须始终成功。

其他事件类型,处理输出事件

到目前为止的其他事件类型是:

EV_LED - 用于键盘LED。

EV_SND - 用于键盘蜂鸣声。

它们与按键事件非常相似,但它们朝另一个方向 - 从系统到输入设备驱动程序。如果您的输入设备驱动程序可以处理这些事件,则必须在evbit中设置相应的位, 以及回调例程:

button_dev->event = button_event; 

int button_event(struct input_dev * dev,unsigned int type,
unsigned int code,int value){
if(type == EV_SND && code == SND_BELL){
outb(value,BUTTON_BELL);
return 0;
}
return -1;
}

这个回调例程可以从一个中断或一个BH调用(虽然这不是一个规则),因此不能休眠,并且不能花太长时间才能完成。

查看input device信息

系统启动之后,可以通过cat /proc/bus/input/devices来查看当前系统中已经注册的input device信息,这里可以看到已经注册成功的input-demo

附录

#include <linux/module.h>
#include <linux/init.h> #include <linux/platform_device.h>
//API for libgpio
#include <linux/gpio.h>
//API for malloc
#include <linux/slab.h>
//API for device tree
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
//API for thread
#include <linux/kthread.h> #include <linux/delay.h>
#include <linux/mutex.h>
//API for delaywork
#include <linux/workqueue.h> #include <linux/interrupt.h>
#include <linux/irq.h> //API for input
#include <linux/input.h> #define TIMER_MS_COUNTS 1000 // default value of dts
#define DEFAULT_POLL_TIME 5
#define DEFAULT_MODE 1 struct gpio_platform_data {
int mode;
int count;
int gpio_index;
struct mutex mtx;
int poll_ms;
}; struct gpio_demo_device { struct platform_device *pdev;
struct device *dev;
struct gpio_platform_data *pdata;
struct workqueue_struct *gpio_monitor_wq;
struct delayed_work gpio_delay_work ;
struct input_dev *input_demo; int gpio_irq;
}; static int gpio_parse_data(struct gpio_demo_device *di){ int ret;
struct gpio_platform_data *pdata;
struct device *dev = di->dev;
struct device_node *np = di->dev->of_node; pdata = devm_kzalloc(di->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
return -ENOMEM;
}
di->pdata = pdata;
// set default value for platform data
pdata->mode = DEFAULT_MODE;
pdata->poll_ms = DEFAULT_POLL_TIME * 1000; dev_info(dev,"parse platform data\n"); ret = of_property_read_u32(np, "mode", &pdata->mode);
if (ret < 0) {
dev_err(dev, "can't get mode property\n");
}
ret = of_property_read_u32(np, "poll_time", &pdata->poll_ms);
if (ret < 0) {
dev_err(dev, "can't get poll_ms property\n");
} pdata->gpio_index = of_get_named_gpio(np,"input-gpio", 0);
if (pdata->gpio_index < 0) {
dev_err(dev, "can't get input gpio\n");
}
// debug parse device tree data
dev_info(dev, "Success:mode is %d\n", pdata->mode);
dev_info(dev, "Success:gpio index is %d\n", pdata->gpio_index);
return 0;
} static void gpio_demo_work(struct work_struct *work) { struct gpio_demo_device *di = container_of(work,
struct gpio_demo_device,
gpio_delay_work.work); struct gpio_platform_data *padta = di->pdata;
int gpio_index,value;
gpio_index = padta->gpio_index;
if (!gpio_is_valid(gpio_index) ) {
dev_err(di->dev, "gpio is not valid\n");
goto end;
} if ( (value = gpio_get_value(gpio_index) ) == 0) {
dev_info(di->dev,"get value is %d\n",value);
}else{
dev_info(di->dev,"get value is %d\n",value);
}
end:
queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
msecs_to_jiffies(di->pdata->poll_ms));
} static int gpio_demo_init_poll(struct gpio_demo_device *di) { dev_info(di->dev,"%s\n", __func__); di->gpio_monitor_wq = alloc_ordered_workqueue("%s",
WQ_MEM_RECLAIM | WQ_FREEZABLE, "gpio-demo-wq"); INIT_DELAYED_WORK(&di->gpio_delay_work, gpio_demo_work);
queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
msecs_to_jiffies(TIMER_MS_COUNTS * 5)); return 0;
} static irqreturn_t gpio_demo_isr(int irq, void *dev_id)
{
struct gpio_demo_device *di = (struct gpio_demo_device *)dev_id;
struct gpio_platform_data *pdata = di->pdata; BUG_ON(irq != gpio_to_irq(pdata->gpio_index)); input_report_key(di->input_demo, KEY_VOLUMEDOWN, 1);
input_sync(di->input_demo);
input_report_key(di->input_demo, KEY_VOLUMEDOWN, 0);
input_sync(di->input_demo); dev_info(di->dev, "%s\n", __func__);
//printk("%s\n",__func__);
return IRQ_HANDLED;
} static int gpio_demo_init_interrupt(struct gpio_demo_device *di) { int irq, ret;
int gpio_index = di->pdata->gpio_index;
dev_info(di->dev,"%s\n", __func__); if (!gpio_is_valid(gpio_index)){
return -1;
} irq = gpio_to_irq(gpio_index); if (irq < 0) {
dev_err(di->dev, "Unable to get irq number for GPIO %d, error %d\n",
gpio_index, irq);
gpio_free(gpio_index);
return -1;
}
ret = devm_request_irq(di->dev, irq, gpio_demo_isr,
IRQF_TRIGGER_FALLING,
"gpio-demo-isr",
di);
if (ret) {
dev_err(di->dev, "Unable to claim irq %d; error %d\n",
irq, ret);
gpio_free(gpio_index);
return -1;
} return 0;
} static int gpio_demo_probe(struct platform_device *pdev){ int ret;
struct gpio_demo_device *priv;
struct device *dev = &pdev->dev; priv = devm_kzalloc(dev, sizeof(*priv) , GFP_KERNEL); if (!priv) {
return -ENOMEM;
}
priv->dev = dev; //important ret = gpio_parse_data(priv);
if (ret){
dev_err(dev,"parse data failed\n");
} priv->input_demo = devm_input_allocate_device(priv->dev);
if(!priv->input_demo) {
dev_err(dev,"Can't allocate input device\n");
return -ENOMEM;
}
// kernel/include/uapi/linux/input-event-codes.h
priv->input_demo->name = "input-demo";
priv->input_demo->dev.parent = priv->dev;
priv->input_demo->evbit[0] = BIT_MASK(EV_KEY);
priv->input_demo->keybit[BIT_WORD(KEY_VOLUMEDOWN)] = BIT_MASK(KEY_VOLUMEDOWN); ret = input_register_device(priv->input_demo);
if(ret) {
dev_err(priv->dev, "register input device failed\n");
input_free_device(priv->input_demo);
return ret;
} //input_set_capability(priv->dev, EV_KEY, KEY_VOLUMEDOWN); platform_set_drvdata(pdev,priv); if (priv->pdata->mode == 0){
gpio_demo_init_poll(priv);
} else {
gpio_demo_init_interrupt(priv);
}
return 0;
}
#ifdef CONFIG_OF
static struct of_device_id gpio_demo_of_match[] = {
{ .compatible = "gpio-demo"},
{},
} MODULE_DEVICE_TABLE(of,gpio_demo_of_match);
#else
static struct of_device_id gpio_demo_of_match[] = {
{ },
}
#endif static struct platform_driver gpio_demo_driver = {
.probe = gpio_demo_probe,
.driver = {
.name = "gpio-demo-device",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_demo_of_match),
}
}; static int __init gpio_demo_init(void){
return platform_driver_register(&gpio_demo_driver);
} static void __exit gpio_demo_exit(void){
platform_driver_unregister(&gpio_demo_driver);
} late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Gpio demo Driver");
MODULE_ALIAS("platform:gpio-demo");

Linux内核驱动学习(十)Input子系统详解的更多相关文章

  1. Linux内核驱动学习(八)GPIO驱动模拟输出PWM

    文章目录 前言 原理图 IO模拟输出PWM 设备树 驱动端 调试信息 实验结果 附录 前言 上一篇的学习中介绍了如何在用户空间直接操作GPIO,并写了一个脚本可以产生PWM.本篇的学习会将写一个驱动操 ...

  2. Linux内核驱动学习(六)GPIO之概览

    文章目录 前言 功能 如何使用 设备树 API 总结 前言 GPIO(General Purpose Input/Output)通用输入/输出接口,是十分灵活软件可编程的接口,功能强大,十分常用,SO ...

  3. linux内核驱动学习指南

    1. 参考链接 小白的博客 ONE_Tech 你为什么看不懂Linux内核驱动源码? 求教怎么学习linux内核驱动

  4. driver: Linux设备模型之input子系统详解

    本节从整体上讲解了输入子系统的框架结构.有助于读者从整体上认识linux的输入子系统.在陷入代码分析的过程中,通过本节的知识能够找准方向,明白原理. 本节重点: 输入子系统的框架结构 各层对应内核中的 ...

  5. linux input子系统详解

    首先,什么是linux的子系统: 输入子系统由驱动层.输入子系统核心.事件处理层三部分组成.一个输入事件,如鼠标移动通过Driver->Input core->Event handler- ...

  6. Linux内核驱动学习(三)字符型设备驱动之初体验

    Linux字符型设备驱动之初体验 文章目录 Linux字符型设备驱动之初体验 前言 框架 字符型设备 程序实现 cdev kobj owner file_operations dev_t 设备注册过程 ...

  7. Linux内核驱动学习(二)添加自定义菜单到内核源码menuconfig

    文章目录 目标 drivers/Kconfig demo下的Kconfig 和 Makefile Kconfig Makefile demo_gpio.c 目标 Kernel:Linux 4.4 我编 ...

  8. input子系统详解

    一.初识linux输入子系统 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(Inpu ...

  9. input子系统详解2

    上一节大概了解了输入子系统的流程 这一节认真追踪一下代码 input.c: input_init(void)函数 static int __init input_init(void) { int er ...

随机推荐

  1. G - Messy codeforces1262C

    题目大意: 输入n和m,n是n个字符,m是m个前缀.对前缀的规定可以配对的括号.比如(),,((()))等等.在输入n个括号字符,对这个n个字符,通过交换使其满足m个前缀.交换次数不限,规则想当与re ...

  2. Python之疑难杂症包安装

    ansible 直接用pip install 安装一直失败 1.下载ansible压缩包 https://files.pythonhosted.org/packages/ec/ee/1494474b5 ...

  3. Python算法题:金字塔

    代码如下: #Python金字塔练习 """ 最大层数:max_level 当前层数:current_level 金字塔正序时: 每层的空格=最大层数-当前层数 每层的星 ...

  4. Java数组模拟环形队列

    2.环形队列 (上一篇队列:https://www.cnblogs.com/yxm2020/p/12676323.html) 百度百科 1.假溢出 ​ 系统作为队列用的存储区还没有满,但队列却发生了溢 ...

  5. LDA概率主题模型

    目录 LDA 主题模型 几个重要分布 模型 Unigram model Mixture of unigrams model PLSA模型 LDA 怎么确定LDA的topic个数? 如何用主题模型解决推 ...

  6. BATJ高级Java面试题分享:JVM+Redis+Kafka +数据库+设计模式

    话不多说,直接上面试题,来看一下你还欠缺多少? Mysql 与 Oracle 相比, Mysql 有什么优势? 简洁描述 Mysql 中 InnoDB 支持的四种事务隔离级别名称,以及逐级之间的区别? ...

  7. C语言字符数组超细讲解

    看到标题,有不少朋友会想:字符数组不也是数组吗?为什么要单独拿出来讲哩?莫非它是朵奇葩? 哈哈,确实,一起来认识一下这朵数组界的奇葩吧! 一.字符数组的定义.引用.初始化 大家好!我是字符数组,看我的 ...

  8. Python模块---制作新冠疫情世界地图()

    目录 pyecharts模块 简介 安装pyecharts 测试pyecharts模块 pyecharts实战:绘制新冠肺炎疫情地图 需求分析 请求数据 提取数据 处理数据 制作可视化地图 设置可视化 ...

  9. 第八次-非确定的自动机NFA确定化为DFA

     提交作业 NFA 确定化为 DFA 子集法: f(q,a)={q1,q2,…,qn},状态集的子集 将{q1,q2,…,qn}看做一个状态A,去记录NFA读入输入符号之后可能达到的所有状态的集合. ...

  10. 怎么将swagger API导出为HTML或者PDF

    文章目录 将swagger API导出为HTML或者PDF 什么是Asciidoc swagger2markup-maven-plugin asciidoctor-maven-plugin 使用命令行 ...