使用input子系统上报按键值,按键驱动使用platform driver形式。
1.input子系统
按键、鼠标、键盘及触摸屏等都属于输入(input)设备,Linux内核为此类设备设计了input子系统框架,专门用来处理输入事件。输入设备本质上是字符设备,只是在这基础之上添加了input框架,用户只需负责上报输入事件,比如按键值、坐标等信息,input核心层负责处理这些事件。
input子系统分为3层,三层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向核心层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口,通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交流。
input核心层的代码在drivers/input/input.c
文件中。
1.1.注册input设备
input子系统的所有设备主设备号都为13,我们在使用input子系统处理输入设备的时候不需要注册字符设备,只需要向系统注册一个input设备即可。struct input_dev
表示input设备。
include <linux/input.h>
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)]; // 开关状态的位图
int (*setkeycode)(struct input_dev *dev, const struct input_keymap_entry *ke, unsigned int*old_keycode);
int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke);
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
};
evbit
表示输入事件类型,可选的事件类型如下:
include <uapi/linux/input.h>
#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 // 压力状态事件
vbit
、keybit
、relbit
等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到keybit
,Linux内核定义了很多按键值,按键值如下:
include <uapi/linux/input.h>
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
#define KEY_MINUS 12
#define KEY_EQUAL 13
#define KEY_BACKSPACE 14
#define KEY_TAB 15
#define KEY_Q 16
#define KEY_W 17
#define KEY_E 18
#define KEY_R 19
#define KEY_T 20
#define KEY_Y 21
#define KEY_U 22
#define KEY_I 23
#define KEY_O 24
#define KEY_P 25
在编写input设备驱动的时候需要先申请一个input_dev
结构体。使用input_allocate_device
分配input_dev
结构体。
include <linux/input.h>
struct input_dev __must_check *input_allocate_device(void);
// devm_input_allocate_device分配的结构体不需要显示的释放和unregister
struct input_dev __must_check *devm_input_allocate_device(struct device *);
使用input_free_device
释放分配的input_dev
结构体。
include <linux/input.h>
void input_free_device(struct input_dev *dev);
使用input_register_device
向内核注册input设备。
include <linux/input.h>
int input_register_device(struct input_dev *dev)
使用input_unregister_device
向内核注册input设备。
include <linux/input.h>
void input_unregister_device(struct input_dev *dev)
1.2.上报输入事件
Linux内核使用input_event
结构体表示所有输入事件。
include <uapi/linux/input.h>
struct input_event {
struct timeval time; // 上报事件发生的事件
__u16 type; // 事件类型
__u16 code; // 事件码,对于按键表示具体的按键码
__s32 value; // 值,对于按键表示具体的按键值,如按下表示1,未按下或者松开表示0
};
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
注册完input设备后,还需要告诉内核上报的什么内容。比如按键,需要在按键中断处理函数或者消抖定时器中断函数中将按键值上报,这样内核才能获得正确的输入值。对于不同的事件,上报的API函数不同。
input_event
函数用于上报指定的事件以及对应的值。dev
为注册的input_dev
结构体指针,type
为上报事件的配型,如EV_KEY
,code
表示事件码,如注册的按键值,比如KEY_0
、KEY_1
等等,value
表示事件值,比如1表示按键按下,0表示按键松开。input_event
函数可以上报所有的事件类型和事件值,是一种通用的上报事件的API。
include <linux/input.h>
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
input_report_key
函数用于上报按键事件,其内部调用的是input_event
函数。
include <linux/input.h>
void input_report_key(struct input_dev *dev, unsigned int code, int value)
同样还有其他的上报函数,如下所示。
include <linux/input.h>
// 上报相对坐标事件
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)
上报完事件,还需要使用input_sync
函数通知内核上报结束,input_sync
函数本质上上报的是一个同步事件。
include <linux/input.h>
void input_sync(struct input_dev *dev)
2.在设备树中指定中断
设备中中断控制器组成了一个树状结构,my_key
使用gpio5_1
中断,interrupt-parent
属性指定其父中断控制器为gpio5
,gpio5
有interrupt-controller
属性,说明gpio5
是一个中断控制器,其#interrupt-cells
属性值为2,说明以gpio5
作为父中断控制器的节点需要使用2个32为的整数描述中断信息,my_key
节点interrupts
属性就使用了2个32位整数描述中断信息。gpio5
的interrupt-parent
属性继承自soc
节点,其父中断控制器为gpc
,gpc
的父中断控制器为intc
,intc
为arm的GIC中断控制器,其负责将所有中断汇总,然后分发给CPU,是最顶层的中断控制器。
/ { // GIC中断控制器,最顶端的中断控制器
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
// 按键设备节点
my_key {
#address-cells = <1>;
#size-cells = <1>;
pinctrl-names = "default";
compatible = "my-key";
/* 设置pinctrl */
pinctrl-0 = <&pinctrl_gpio_keys>;
key = <&gpio5 1 GPIO_ACTIVE_HIGH>;
/* 设置父中断控制器 */
interrupt-parent = <&gpio5>; // gpio5作为中断控制器
interrupts = <1 IRQ_TYPE_EDGE_BOTH>; // 按键使用gpio5_1中断,中断触发类型为双边沿触发
// 可使用interrupts-extended属性代替interrupt-parent和interrupts属性
// interrupts-extended = <&gpio5 1 IRQ_TYPE_EDGE_BOTH>;
status = "okay";
code = <KEY_0>;
};
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
gpio5: gpio@020ac000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x020ac000 0x4000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller; // 表示中断控制器
#interrupt-cells = <2>; // 子节点使用几个32位整数描述中断
};
aips1: aips-bus@02000000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x100000>;
ranges;
gpc: gpc@020dc000 {
compatible = "fsl,imx6ul-gpc", "fsl,imx6q-gpc";
reg = <0x020dc000 0x4000>;
interrupt-controller;
#interrupt-cells = <3>;
interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&intc>;
fsl,mf-mix-wakeup-irq = <0xfc00000 0x7d00 0x0 0x1400640>;
};
};
};
};
3.获取中断号的函数
include <linux/gpio.h>
// 使用gpio编号获取中断号
int gpio_to_irq(unsigned int gpio)
include <linux/of_irq.h>
/ 使用节点指针和索引号获取中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
int of_irq_get(struct device_node *dev, int index)
// 使用节点指针和名称获取中断号
int of_irq_get_byname(struct device_node *dev, const char *name);
// 如设备树节点转换为platform_device,可使用平台设备相关函数
include <linux/platform_device.h>
// 使用platform_get_resource函数获取中断号,此时type应为IORESOURCE_IRQ
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
// 使用platform_get_irq直接获取中断号,num为中断索引号
int platform_get_irq(struct platform_device *dev, unsigned int num)
4.字符设备驱动源码
/*===========================my_key_input.h================================*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/input.h>
#define KEY_DT_PATH "/my_key"
#define NAME "my_key"
struct irq_key_desc
{
unsigned int irq; // 虚拟中断号
unsigned int gpio; // gpio编号
unsigned int code; // 按键码
volatile unsigned int key_push_cnt; // 按键按下的次数
volatile unsigned int key_release_cnt; // 按键松开的次数
};
// 设备结构体
struct my_key_dev {
struct class* my_key_class;
struct device* my_key_device;
struct timer_list timer; // 定时器
struct input_dev* key_input_dev;
struct irq_key_desc key_desc;
};
/*===========================my_key_input.c================================*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/sysfs.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/poll.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include "my_key_input.h"
static struct my_key_dev* my_key = NULL;
/*********************按键中断服务函数,在中断的上半段执行***********************/
irqreturn_t my_key_irq_handler(int irq, void* dev)
{
struct my_key_dev* key = (struct my_key_dev*)dev;
mod_timer(&key->timer, jiffies + msecs_to_jiffies(10)); // 定时器延时10毫秒
return IRQ_RETVAL(IRQ_HANDLED);
}
/******************************定时器到期执行函数****************************/
void timer_fun(unsigned long data)
{
int val = 0;
struct my_key_dev* dev = (struct my_key_dev*)data;
val = gpio_get_value(dev->key_desc.gpio);
if (val == 1) { // 按键按下
input_report_key(dev->key_input_dev, dev->key_desc.code, val);
input_sync(dev->key_input_dev);
dev->key_desc.key_push_cnt++;
}
else { // 按键松开
input_report_key(dev->key_input_dev, dev->key_desc.code, val);
input_sync(dev->key_input_dev);
dev->key_desc.key_release_cnt++;
}
}
static int key_gpio_init(struct my_key_dev* dev, struct platform_device* pdev)
{
int ret = 0;
struct device_node* n;
n = of_find_node_by_path(KEY_DT_PATH); // 获取按键设备树节点
if (NULL == n) {
printk(KERN_ERR "find key node error by %s\n", KEY_DT_PATH);
return -EINVAL;
}
dev->key_desc.gpio = of_get_named_gpio(n, "key",0); // 获取gpio编号
if (dev->key_desc.gpio < 0) {
printk(KERN_ERR "can't get key number\n");
return -EINVAL;
}
ret = gpio_request(dev->key_desc.gpio, NAME); // 申请gpio
if (ret < 0) {
printk(KERN_ERR "request key gpio error\n");
return -EINVAL;
}
gpio_direction_input(dev->key_desc.gpio); // gpio设置为输入
dev->key_desc.irq = irq_of_parse_and_map(n, 0); // 获取虚拟中断号
if (0 == dev->key_desc.irq) {
printk(KERN_ERR "can't parse and map irq\n");
ret = -1;
goto free_gpio;
}
// 读取code的属性值
ret = of_property_read_u32_index(n, "code", 0, &dev->key_desc.code);
if (ret < 0) {
printk(KERN_ERR "can't parse and map irq\n");
goto free_gpio;
}
ret = request_irq(dev->key_desc.irq, my_key_irq_handler,
IRQ_TYPE_EDGE_BOTH, NAME, dev);
if (ret < 0) {
printk(KERN_ERR "request %s irq failed\n", NAME);
goto free_gpio;
}
return 0;
free_gpio:
gpio_free(dev->key_desc.gpio);
return ret;
}
/***************************模块初始化**************************************/
static int my_key_probe(struct platform_device* dev)
{
int ret = 0;
// 分配设备结构体内存并将分配的内存清0
my_key = kzalloc(sizeof(struct my_key_dev), GFP_KERNEL);
if (NULL == my_key) {
printk(KERN_ERR "kzalloc failed\n");
return -ENOMEM;
}
init_timer(&my_key->timer); // 初始化定时器
my_key->timer.function = timer_fun;
my_key->timer.data = (unsigned long)my_key;
add_timer(&my_key->timer); // 注册定时器
ret = key_gpio_init(my_key, dev);
if (ret < 0) goto timer_del;
my_key->key_input_dev = input_allocate_device(); // 分配input_dev结构体
if (NULL == my_key->key_input_dev) {
printk(KERN_ERR "allocate input device failed\n");
ret = -ENOMEM;
goto timer_del;
}
my_key->key_input_dev->name = NAME;
set_bit(EV_KEY, my_key->key_input_dev->evbit); // 产生按键事件
set_bit(EV_REP, my_key->key_input_dev->evbit); // 产生重复事件
// 产生KEY_0按键
set_bit(my_key->key_desc.code, my_key->key_input_dev->keybit);
ret = input_register_device(my_key->key_input_dev);
if (ret < 0) {
printk(KERN_ERR "input device register failed\n");
goto free_input_device;
}
printk(KERN_INFO "my_key module init OK\n");
return 0;
free_input_device:
input_free_device(my_key->key_input_dev);
timer_del:
del_timer(&my_key->timer); // 删除定时器
kfree(my_key);
my_key = NULL;
return ret;
}
/********************模块注销************************/
static int my_key_remove(struct platform_device* dev)
{
input_unregister_device(my_key->key_input_dev);
input_free_device(my_key->key_input_dev);
gpio_free(my_key->key_desc.gpio);
free_irq(my_key->key_desc.irq, my_key);
del_timer(&my_key->timer); // 删除定时器
kfree(my_key);
my_key = NULL;
printk(KERN_INFO "my_key module exit\n");
return 0;
}
// 定义设备和驱动匹配需要的of_device_id匹配表变量,
static const struct of_device_id my_key_of_match[] = {
{ .compatible = "my-key" }, // 兼容属性,需要与设备树中的兼容属性一致
{ /* Sentinel */ } // 最后一个元素一定为空
};
MODULE_DEVICE_TABLE(of, my_key_of_match);
// 定义注册驱动时需要的platform_driver结构体变量
static struct platform_driver my_key_platform_driver = {
.driver = {
.name = "imx6ull-my-led", // 设置驱动的名称
// 驱动的设备的匹配数组信息,一个驱动可以和多个设备匹配
.of_match_table = my_key_of_match,
},
.probe = my_key_probe, // 设置初始化probe函数,驱动和设备匹配成功后此函数会执行
.remove = my_key_remove, // 设置退出remove函数
};
/**********************模块初始化****************************/
static int __init my_key_init(void)
{
int ret = 0;
ret = platform_driver_register(&my_key_platform_driver);
if (ret < 0) printk(KERN_ERR "my_key register platform driver error\n");
return ret;
}
/***********************模块注销***************************/
static void __exit my_key_exit(void)
{
platform_driver_unregister(&my_key_platform_driver);
}
module_init(my_key_init);
module_exit(my_key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liyang.plus@foxmail.com");
MODULE_VERSION("v1.00");
5.测试程序源码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/select.h>
#include <signal.h>
#include <linux/input.h>
#define PATH "/dev/my_key"
int main(int argc, char* argv[])
{
int len, ret;
struct input_event inputevent = {0};
int fd, push_cnt = 0, release_cnt = 0;
char* path = argv[1];
char push[] = "push";
char release[] = "release";
if (argc != 2) {
printf("path error\n");
return -1;
}
fd = open(path, O_RDONLY); // 阻塞打开
if (fd < 0){
printf("my_key open error\n");
return -1;
}
while (1) {
ret = read(fd, &inputevent, sizeof(inputevent));
if (ret > 0) {
switch (inputevent.type)
{
case EV_KEY:
printf("key %d %s %d\n",inputevent.code,
inputevent.value ? push : release,
inputevent.value ? push_cnt : release_cnt);
inputevent.value ? push_cnt++ : release_cnt++;
break;
default:
break;
}
} else printf("read error\n");
}
close(fd);
return 0;
}