前言

这次主要会学习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. Multiple Books多账薄

    有些公司因管理需要配置多本账薄,比如管理帐和PRC,那么在Epicor 10中如何实现呢? 1创建 new Book: 2 created a map: Financial Management -& ...

  2. React AntDesign 引入css

    React项目是用umi脚手架搭建的AntDesign,用到一个第三方表格组件Jexcel,npm install 之后组件的样式加载不上,犯了愁,翻阅各种资料,踏平两个小坑. 大家都知道,安装完成的 ...

  3. .NET中 kafka消息队列、环境搭建与使用

    前面几篇文章中讲了一些关于消息队列的知识,就每中消息队列中间件,我们并没有做详细的讲解,那么,今天我们就来详细的讲解一下消息队列之一kafka的一些基本的使用与操作. 一.kafka介绍 kafka: ...

  4. IN612 IN612L蓝牙5.0 SoC芯片替换NRF52832/NRF52840

    IN612L是美国公司INPLAY的SOC产品系列之一,具有多模协同2.4G无线协议栈,支持2.4G私有协议栈以及蓝牙5.0全协议栈的SOC芯片.如2mbps高数据速率模式,125kbps/500kb ...

  5. 一站式轻量级框架 Spring

    Spring 简介 Spring 是一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的.Spring 的核心是控制反转(IoC)和面向切面编程(AOP).简单来说,Spring ...

  6. 用Python打造电脑人脸屏幕解锁神器附带接头暗号!

    前言 最近突然有个奇妙的想法,就是当我对着电脑屏幕的时候,电脑会先识别屏幕上的人脸是否是本人,如果识别是本人的话需要回答电脑说的暗语,答对了才会解锁并且有三次机会.如果都没答对就会发送邮件给我,通知有 ...

  7. mysql 复制表结构和数据

    CREATE TABLE 新表名 SELECT 字段 as 新字段,字段 as 新字段.....from 旧表名:

  8. Qt5 escape spaces in path

    There are two possible ways. You can either use escaped quotes (inserting the string between quotes) ...

  9. Hard filters (by GATK)

    Filter Symbol T. Definition QualByDepth QD 2.0 The variant confidence (from the QUAL field) divided ...

  10. pfSense®2.4.4发布后,原pfSense 黄金会员的服务将免费使用!

    2018年7月16日,Doug McIntire 从即将发布的pfSense®2.4.4开始,之前在"pfSense Gold"下提供的所有服务都将继续,但所有pfSense用户都 ...