Linux INPUT子系统实验
一、基本概念
按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。本质属于字符设备。
input 子系统分为 input 驱动层、input 核心层、input 事件处理层,最终给用户空间提供可访问的设备节点。
(1)驱动层
输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
(2)核心层
a.承上启下,为驱动层提供输入设备注册和操作接口;
b.通知事件层对输入事件进行处理。
(3)事件层
主要和用户空间进行交互。
2. input 子系统的所有设备主设备号都为 13,在drivers/input/input.c文件(核心层)中可以看到, 在使用 input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个 input_device 即可。
1.注册 input_dev
input_dev 结构体表示 input设备,此结构体定义在 include/linux/input.h 文件中,结构体中包含了各种事件输入类型,如evbit[BITS_TO_LONGS(EV_CNT)]存放着不同事件对应的值,可选的输入事件类型定义在input/uapi/linux/input.h 文件中,比如常见的输入事件类型有同步事件、按键事件、重复事件等。
注册过程:
a.申请input_dev结构体变量 struct input_dev *input_allocate_device(void)
b.初始化input_dev的事件类型以及事件值。
c.向Linux系统注册input_dev设备 input_register_device(struct input_dev *dev)
d.卸载驱动的时候要注销该设备并释放前面申请的input_dev。
void input_event(
struct input_dev *dev, //需要上报的 input_dev
unsigned int type, //上报的事件类型,比如 EV_KEY
unsigned int code, //事件码,也就是我们注册的按键值,比如 KEY_0、KEY_1 等等
int value //事件值,比如 1 表示按键按下,0 表示按键松开
)
input_event 函数可以上报所有的事件类型和事件值,Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了 input_event 函数。
#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/gpio.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/timer.h> #include <linux/string.h> #include <linux/input.h> #define IMX6UIRQ_NAME "imx6uirq" #define IMX6UIRQ_CNT 1 #define KEY_NUM 1 #define KEY0_VALUE 0X01 /* KEY0 按键值 */ #define INVAKEY 0xff #define KEYINPUT_NAME "keyinput" /*cmd*/ /* _IO(type,nr) //没有参数的命令 _IOR(type,nr,size) //该命令是从驱动读取数据 _IOW(type,nr,size) //该命令是从驱动写入数据 _IOWR(type,nr,size) //双向数据传输 */ // #define CLOSE_CMD _IO(0xef, 1) // #define OPEN_CMD _IO(0xef, 2) // #define SETPERIOD_CMD _IOW(0xef, 3, int) struct keydevice_dev { int gpio; // IO char name[10]; // IO name int irqnum; //中断号 unsigned char value; /* 按键对应的键值 */ irqreturn_t (*handler)(int, void *); /* 中断服务函数 */ }; struct imx6uirq_dev { dev_t devid; /*设备号*/ struct cdev cdev; struct class *class; struct device *device; struct device_node *nd; int major; int minor; struct timer_list timer; // int timeperiod; /* 定时周期,单位为 ms */ spinlock_t lock; //自旋锁 struct keydevice_dev keydecs[KEY_NUM]; atomic_t key_value; /* 有效的按键键值 */ atomic_t release_key; /* 标记是否完成一次完成的按键*/ unsigned char current_keynum; /* 当前的按键号 */ struct input_dev *inputdev; /* input 结构体 */ }; struct imx6uirq_dev imx6uirq; /* * @description : 关闭/释放设备 * @param - filp : 要关闭的设备文件(文件描述符) * @return : 0 成功;其他 失败 */ void timer_function(unsigned long arg) { struct keydevice_dev *keydecs; struct imx6uirq_dev *dev =(struct imx6uirq_dev*)arg; int ret = 0; unsigned char num; unsigned char value; num = dev->current_keynum; keydecs = &dev->keydecs[num]; value = gpio_get_value(keydecs->gpio);/* 读取 IO 值 */ if(value == 0) /*按键按下*/ { // printk("KEY0_PUSH\r\n"); /*上报按键值*/ input_report_key(dev->inputdev,keydecs->value,1); input_sync(dev->inputdev); } else if(value==1)//释放 { // printk("KEY0_RELEASE\r\n"); /*上报按键值*/ input_report_key(dev->inputdev,keydecs->value,0); input_sync(dev->inputdev); } } /* @description : 中断服务函数,开启定时器,延时 10ms, * 定时器用于按键消抖。 * @param - irq : 中断号 * @param - dev_id : 设备结构。 * @return : 中断执行结果 */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id; dev->current_keynum = 0; dev->timer.data = (volatile long)dev_id; mod_timer(&imx6uirq.timer,jiffies+msecs_to_jiffies(10));//消抖 // printk("irq handler\r\n"); return IRQ_RETVAL(IRQ_HANDLED); } static int keyirq_init(void) { int ret = 0; int i = 0; imx6uirq.nd = of_find_node_by_path("/key"); if (imx6uirq.nd == NULL) { ret = -EINVAL; goto fail_findnd; printk("find node failed"); } /* 提取 GPIO */ for (i = 0; i < KEY_NUM; i++) { imx6uirq.keydecs[i].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpios", i); if (imx6uirq.keydecs[i].gpio < 0) { printk("get gpio %d failed\r\n", i); } printk("imx6uirq.keydecs[%d].gpio = %d",i,imx6uirq.keydecs[i].gpio); } /* 初始化 key 所使用的 IO,并且设置成中断模式 */ for (i = 0; i < KEY_NUM; i++) { memset(imx6uirq.keydecs[i].name, 0, sizeof(imx6uirq.keydecs[i].name)); //给数组清0,按字节赋值 sprintf(imx6uirq.keydecs[i].name, "KEY%d", i); //给数组赋值 gpio_request(imx6uirq.keydecs[i].gpio, imx6uirq.keydecs[i].name); //申请IO gpio_direction_input(imx6uirq.keydecs[i].gpio); //设置为输入模式 imx6uirq.keydecs[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); //获取中断号 printk("gpio %d irqnum=%d\r\n", imx6uirq.keydecs[i].gpio, imx6uirq.keydecs[i].irqnum); } /* 申请中断 */ imx6uirq.keydecs[0].handler = key0_handler; imx6uirq.keydecs[0].value = KEY_0; /*根据按键的个数申请中断*/ for (i = 0; i < KEY_NUM; i++) { ret = request_irq(imx6uirq.keydecs[i].irqnum, imx6uirq.keydecs[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, imx6uirq.keydecs[i].name, &imx6uirq); if (ret < 0) { printk("irq %d request failed", imx6uirq.keydecs[i].irqnum); ret = -EINVAL; goto fail_request_irq; } } /* 创建定时器 */ init_timer(&imx6uirq.timer); imx6uirq.timer.function = timer_function; /*申请input_dev*/ imx6uirq.inputdev = input_allocate_device(); imx6uirq.inputdev->name = KEYINPUT_NAME; __set_bit(EV_KEY,imx6uirq.inputdev->evbit);/*按键事件*/ __set_bit(EV_REP,imx6uirq.inputdev->evbit);/*重复事件*/ __set_bit(KEY_0,imx6uirq.inputdev->keybit); /* 初始化 input_dev,设置产生哪些按键 */ // imx6uirq.inputdev->evbit[0]=BIT_MASK(EV_KEY)|BIT_MASK(EV_REP); // input_set_capability(imx6uirq.inputdev,EV_KEY,KEY_0); /* 注册输入设备 */ ret = input_register_device(imx6uirq.inputdev); if(ret){ printk("register failed\r\n"); return ret; } return 0; fail_findnd: fail_request_irq: return ret; } /*驱动入口函数*/ static int __init imx6uirq_init(void) { keyirq_init(); return 0; } /*驱动出口函数*/ static void __exit imx6uirq_exit(void) { int i = 0; /* 删除定时器 */ del_timer_sync(&imx6uirq.timer); /* 释放中断 */ for (i = 0; i < KEY_NUM; i++) { free_irq(imx6uirq.keydecs[i].irqnum, &imx6uirq); } input_unregister_device(imx6uirq.inputdev); input_free_device(imx6uirq.inputdev); printk("imx6uirq_exit !!!\r\n"); } module_init(imx6uirq_init); module_exit(imx6uirq_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("dongdong");
1 #include "stdio.h" 2 #include "unistd.h" 3 #include "sys/types.h" 4 #include "sys/stat.h" 5 #include "sys/ioctl.h" 6 #include "fcntl.h" 7 #include "stdlib.h" 8 #include "string.h" 9 #include <poll.h> 10 #include <sys/select.h> 11 #include <signal.h> 12 #include <fcntl.h> 13 #include <linux/input.h> 14 15 /* 定义一个 input_event 变量,存放输入事件信息 */ 16 static struct input_event inputevent; 17 /* 18 * @description : main主程序 19 * @param - argc : argv数组元素个数 20 * @param - argv : 具体参数 21 * @use: ./timerAPP /dev/gpioled 22 * @return : 0 成功;其他 失败 23 */ 24 int main(int argc, char *argv[]) 25 { 26 27 char *filename; 28 int fd; 29 int ret = 0; 30 31 /*打开文件*/ 32 filename = argv[1]; 33 34 if (argc != 2) //检查输入参数个数 35 { 36 printf("useage error\r\n"); 37 return -1; 38 } 39 40 fd = open(filename, O_RDWR); 41 if (fd < 0) 42 { 43 printf("can't open file %s\r\n", filename); 44 return -1; 45 } 46 /* 循环读取按键值数据! */ 47 while (1) 48 { 49 ret = read(fd, &inputevent,sizeof(inputevent)); 50 if(ret<0) 51 { 52 printf("读取数据失败\r\n"); 53 } 54 else{ 55 switch(inputevent.type) 56 { 57 case EV_KEY: 58 if(inputevent.code < BTN_MISC) 59 { 60 printf("key press\r\n"); 61 printf("key %d %s\r\n",inputevent.code,inputevent.value ? "press":"release"); 62 } 63 else 64 { 65 printf("button %d %s\r\n",inputevent.code,inputevent.value?"press":"release"); 66 } 67 break; 68 /* 其他类型的事件,自行处理 */ 69 case EV_REL: 70 break; 71 case EV_ABS: 72 break; 73 case EV_MSC: 74 break; 75 case EV_SW: 76 break; 77 78 } 79 80 } 81 } 82 ret = close(fd); 83 if (ret < 0) 84 { 85 printf("file %s close failed!\r\n", argv[1]); 86 return -1; 87 } 88 return 0; 89 90 }
从上图实验结果可以看出inpu_dev结构体的成员变量的值,从左到右依次是:
此事件发生的时间(s、us,均为32位)、事件类型(16位)、事件编码(16位)、按键值(32位)
四、也可以用Linux自带的按键驱动
1.make menuconfig配置
2.修改设备树文件
可以参考Linux内核文档(Documentation/devicetree/bindings/input/gpio-keys.txt)
参考上述文件修改开发板按键为回车键为LCD实验作准备。
1 gpio-keys { 2 compatible = "gpio-keys"; 3 #address-cells = <1>; 4 #size-cells = <0>; 5 autorepeat; 6 key0 { 7 label = "GPIO Key Enter"; 8 linux,code = <KEY_ENTER>; 9 gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; 10 }; 11 };
最后,实验结果如下: