一.Input 子系统的定义
input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统
一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等
等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,
鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心
应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动
层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点。
可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
驱动层 :输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层 :承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行
处理。
事件层:主要和用户空间进行交互。
二.注册 Input 设备有关函数
1.input_dev 结构体
cpp
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 表示输入事件类型,可选择以下事件
cpp
#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
cpp
/**
* @description: 申请一个 input_dev 结构体变量
* @param - : 无
* @return : 申请到的 input_dev
*/
struct input_dev *input_allocate_device(void)
3.释放 input_dev 结构体变量 - input_free_device
cpp
/**
* @description: 释放 input_dev 结构体变量
* @param - dev : 要释放的 input_dev
* @return : 无
*/
void input_free_device(struct input_dev *dev)
4.向 Linux 内核注册一个 input_dev - input_register_device
cpp
/**
* @description: 向 Linux 注册 input_dev
* @param - dev : 要注册的 input_dev
* @return : 注册成功返回(0),失败返回(负值)
*/
int input_register_device(struct input_dev *dev)
5.注销 Input 驱动 - input_unregister_device
cpp
/**
* @description: 注销 input_dev
* @param - dev : 要注销的 input_dev
* @return : 无
*/
void input_unregister_device(struct input_dev *dev)
三.上报事件相关函数
1.上报指定的事件以及对应的值 - input_event
cpp
/**
* @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 函数
cpp
/**
* @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
cpp
/**
* @description: 告诉 Linux 内核 input 子系统上报结束
* @param - dev : 需要上报同步事件的 input_dev
* @return : 无
*/
void input_sync(struct input_dev *dev)
3.input_event 结构体
cpp
struct input_event
{
struct timeval time; //事件发生的时间
__u16 type; //事件类型
__u16 code; //事件码
__s32 value; //值,比如 EV_KEY 事件中, value 就是按键值
};
四. Input 子系统下的按键输入事件代码
1.设备树
(1).流程图
(2).代码部分
2.驱动部分
(1).流程图
(2).代码部分
cpp
#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).代码部分
cpp
#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自带的驱动如下:
cpp
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/ 下有没有新增节点