linux-3.0下input_dev模型按键驱动
该代码在FL2440开发板上测试通过,为方便教学,将驱动中的platform_device和platform_driver故意分为两个驱动模块。
[guowenxue@centos6 input_kbd]$ ls event_button.c kbd_device.c kbd_driver.c kbd_driver.h Makefile
驱动相关头文件 kbd_driver.h:
1 /******************************************************************************** 2 * Copyright: (C) 2016 Guo Wenxue<guowenxue@aliyun.com> 3 * All rights reserved. 4 * 5 * Filename: kbd_driver.h 6 * Description: This head file is for s3c keyboard driver 7 * 8 * Version: 1.0.0(07/26/2016) 9 * Author: Guo Wenxue <guowenxue@aliyun.com> 10 * ChangeLog: 1, Release initial version on "07/26/2016 06:54:47 PM" 11 * 12 ********************************************************************************/ 13 14 #ifndef _KBD_DRIVER_H_ 15 #define _KBD_DRIVER_H_ 16 17 /* keyboard hardware informtation structure definition */ 18 typedef struct s3c_kbd_info_s 19 { 20 int code; /* input device key code */ 21 int nIRQ; /* keyboard IRQ number*/ 22 unsigned int setting; /* keyboard IRQ Pin Setting*/ 23 unsigned int gpio; /* keyboard GPIO port */ 24 } s3c_kbd_info_t; 25 26 /* keyboard platform device private data structure */ 27 typedef struct s3c_kbd_platform_data_s 28 { 29 s3c_kbd_info_t *keys; 30 int nkeys; 31 } s3c_kbd_platform_data_t; 32 33 #endif /* ----- #ifndef _KBD_DRIVER_H_ ----- */
platform_device相关驱动文件 kbd_device.c:
1 /********************************************************************************* 2 * Copyright: (C) 2016 Guo Wenxue<guowenxue@aliyun.com> 3 * All rights reserved. 4 * 5 * Filename: kbd_device.c 6 * Description: This file 7 * 8 * Version: 1.0.0(07/26/2016) 9 * Author: Guo Wenxue <guowenxue@aliyun.com> 10 * ChangeLog: 1, Release initial version on "07/26/2016 05:01:25 PM" 11 * 12 ********************************************************************************/ 13 14 #include <linux/module.h> 15 #include <linux/init.h> 16 #include <linux/platform_device.h> 17 #include <linux/input.h> 18 #include <mach/hardware.h> 19 #include <asm/gpio.h> 20 #include <asm/irq.h> 21 #include <mach/regs-gpio.h> 22 #include "kbd_driver.h" 23 24 static s3c_kbd_info_t s3c_kbd_gpios[] = { 25 [0] = { 26 .code = KEY_1, 27 .nIRQ = IRQ_EINT0, 28 .gpio = S3C2410_GPF(0), 29 .setting = S3C2410_GPF0_EINT0, 30 }, 31 [1] = { 32 .code = KEY_2, 33 .nIRQ = IRQ_EINT2, 34 .gpio = S3C2410_GPF(2), 35 .setting = S3C2410_GPF2_EINT2, 36 }, 37 [2] = { 38 .code = KEY_3, 39 .nIRQ = IRQ_EINT3, 40 .gpio = S3C2410_GPF(3), 41 .setting = S3C2410_GPF3_EINT3, 42 }, 43 [3] = { 44 .code = KEY_4, 45 .nIRQ = IRQ_EINT4, 46 .gpio = S3C2410_GPF(4), 47 .setting = S3C2410_GPF4_EINT4, 48 }, 49 }; 50 51 /* keyboard platform device private data */ 52 static s3c_kbd_platform_data_t s3c_kbd_data = { 53 .keys = s3c_kbd_gpios, 54 .nkeys = ARRAY_SIZE(s3c_kbd_gpios), 55 }; 56 57 static void platform_kbd_release(struct device * dev) 58 { 59 return; 60 } 61 62 static struct platform_device s3c_keyboard_device = { 63 .name = "s3c_kbd", 64 .id = 1, 65 .dev = 66 { 67 .platform_data = &s3c_kbd_data, 68 .release = platform_kbd_release, 69 }, 70 }; 71 72 73 static int __init s3c_keyboard_dev_init(void) 74 { 75 int rv; 76 77 rv = platform_device_register(&s3c_keyboard_device); 78 if(rv) 79 { 80 printk("S3C keyboard platform device register failure\n"); 81 return rv; 82 } 83 84 printk("S3C keyboard platform device register ok\n"); 85 return 0; 86 } 87 88 static void __exit s3c_keyboard_dev_exit(void) 89 { 90 printk("S3C keyboard device exit\n"); 91 92 platform_device_unregister(&s3c_keyboard_device); 93 return ; 94 } 95 96 module_init(s3c_keyboard_dev_init); 97 module_exit(s3c_keyboard_dev_exit); 98 99 MODULE_DESCRIPTION("FL2440 board keyboard input driver platform_device"); 100 MODULE_AUTHOR("Guo Wenxue<guowenxue@gmail.com>"); 101 MODULE_LICENSE("GPL"); 102 MODULE_ALIAS("platform:FL2440 keyboard device");
platform_driver相关驱动文件 kbd_driver.c:
/********************************************************************************* * Copyright: (C) 2016 Guo Wenxue<guowenxue@aliyun.com> * All rights reserved. * * Filename: kbd_driver.c * Description: This file * * Version: 1.0.0(07/26/2016) * Author: Guo Wenxue <guowenxue@aliyun.com> * ChangeLog: 1, Release initial version on "07/26/2016 05:01:25 PM" * ********************************************************************************/ #include <linux/module.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/input.h> #include <linux/irq.h> #include <linux/interrupt.h> #include <mach/hardware.h> #include <asm/gpio.h> #include <asm/irq.h> #include <linux/slab.h> #include <mach/regs-gpio.h> #include "kbd_driver.h" /* 1HZ=100*jiffies 1*jiffies=10ms => 1HZ=100*10ms = 1s */ #define CANCEL_DITHERING_DELAY (HZ/50) /* Remove button push down dithering timer delay 20ms */ typedef struct s3c_kbd_s { struct timer_list *timers; /* every key get a cancel dithering timer */ struct input_dev *input_dev; s3c_kbd_platform_data_t *pdata; } s3c_kbd_t; /*--- end of struct s3c_kbd_s ---*/ s3c_kbd_t *s3c_kbd = NULL; static irqreturn_t s3c_kbd_intterupt(int irq, void *dev_id) { int i; int found = 0; struct platform_device *pdev = dev_id; s3c_kbd_t *s3c_kbd = NULL; s3c_kbd = platform_get_drvdata(pdev); for(i=0; i<s3c_kbd->pdata->nkeys; i++) { if(irq == s3c_kbd->pdata->keys[i].nIRQ) { found = 1; break; } } if(!found) /* An ERROR interrupt */ return IRQ_NONE; mod_timer(&s3c_kbd->timers[i], jiffies+CANCEL_DITHERING_DELAY); return IRQ_HANDLED; } static void cancel_dithering_timer_handler(unsigned long data) { int which =(int)data; unsigned int pinval; pinval = s3c2410_gpio_getpin(s3c_kbd->pdata->keys[which].gpio); if( pinval ) { //printk("s3c_kbd key[%d] code[%d] released\n", which, s3c_kbd->pdata->keys[which].code); input_event(s3c_kbd->input_dev, EV_KEY, s3c_kbd->pdata->keys[which].code, 0); } else { //printk("s3c_kbd key[%d] code[%d] pressed\n", which, s3c_kbd->pdata->keys[which].code); input_event(s3c_kbd->input_dev, EV_KEY, s3c_kbd->pdata->keys[which].code, 1); } input_sync(s3c_kbd->input_dev); } static int s3c_kbd_probe(struct platform_device *pdev) { int i = 0; int rv = -ENOMEM; struct input_dev *input_dev = NULL; s3c_kbd_platform_data_t *pdata = pdev->dev.platform_data; /* malloc s3c_kbd struct */ s3c_kbd = kmalloc(sizeof(s3c_kbd_t), GFP_KERNEL); if( !s3c_kbd ) { printk("error: s3c_kbd_probe kmalloc() for s3c_kbd failure\n"); goto fail; } memset(s3c_kbd, 0, sizeof(s3c_kbd_t)); /* malloc cancel dithering timer for every key */ s3c_kbd->timers = (struct timer_list *) kmalloc(pdata->nkeys*sizeof(struct timer_list), GFP_KERNEL); if( !s3c_kbd->timers ) { printk("error: s3c_kbd_probe kmalloc() for s3c_kbd timers failure\n"); goto fail; } memset(s3c_kbd->timers, 0, pdata->nkeys*sizeof(struct timer_list)); /* malloc input_dev for keyboard */ input_dev=input_allocate_device(); if( !input_dev ) { printk("error: s3c_kbd_probe input_allocate_device() failure\n"); goto fail; } /* setup input_dev */ input_dev->name = pdev->name; input_dev->dev.parent = &pdev->dev; input_dev->id.bustype = BUS_HOST; input_dev->id.vendor = 0x0001; input_dev->id.product = 0x0001; input_dev->id.version = 0x0100; set_bit(EV_KEY,input_dev->evbit); set_bit(EV_REP,input_dev->evbit); /* Initialize all the keys and interrupt */ for(i=0; i<pdata->nkeys; i++) { set_bit(pdata->keys[i].code, input_dev->keybit); s3c2410_gpio_cfgpin(pdata->keys[i].gpio, pdata->keys[i].setting); irq_set_irq_type(pdata->keys[i].nIRQ, IRQ_TYPE_EDGE_BOTH); rv = request_irq(pdata->keys[i].nIRQ, s3c_kbd_intterupt, IRQF_DISABLED, pdev->name, pdev); if( rv ) { printk("error: request IRQ[%d] for key<%d> failure\n", pdata->keys[i].nIRQ, i); rv = -EBUSY; goto fail; } //printk("s3c_kbd request IRQ[%d] for key<%d> ok\n", pdata->keys[i].nIRQ, i); /* Initialize all the keys cancel dithering timer */ setup_timer(&s3c_kbd->timers[i], cancel_dithering_timer_handler, i); } /* register input device */ rv = input_register_device(input_dev); if( rv ) { printk("error: s3c_kbd_probe input_register_device error!\n"); goto fail; } /* set s3c_kbd as private data in pdev */ s3c_kbd->input_dev = input_dev; s3c_kbd->pdata = pdata; platform_set_drvdata(pdev, s3c_kbd); printk("s3c_kbd_probe ok\n"); return 0; fail: while(i--) { disable_irq(pdata->keys[i].nIRQ); free_irq(pdata->keys[i].nIRQ, pdev); del_timer( &s3c_kbd->timers[i] ); } if(input_dev) { input_free_device(input_dev); } if(s3c_kbd && s3c_kbd->timers) { kfree(s3c_kbd->timers); } if(s3c_kbd) { kfree(s3c_kbd); } printk("s3c_kbd_probe failed\n"); return -ENODEV; } static int s3c_kbd_remove(struct platform_device *pdev) { int i = 0; s3c_kbd_t *s3c_kbd = platform_get_drvdata(pdev); for(i=0; i<s3c_kbd->pdata->nkeys; i++) { del_timer( &s3c_kbd->timers[i] ); disable_irq(s3c_kbd->pdata->keys[i].nIRQ); free_irq(s3c_kbd->pdata->keys[i].nIRQ, pdev); } input_unregister_device(s3c_kbd->input_dev); kfree(s3c_kbd->timers); kfree(s3c_kbd); printk("s3c_kbd_remove ok\n"); return 0; } static struct platform_driver s3c_keyboard_driver = { .probe = s3c_kbd_probe, .remove = s3c_kbd_remove, .driver = { .name = "s3c_kbd", .owner = THIS_MODULE, }, }; static int __init s3c_keyboard_drv_init(void) { int rv; rv = platform_driver_register(&s3c_keyboard_driver); if(rv) { printk("s3c keyboard platform driver register failure\n"); return rv; } printk("s3c keyboard platform driver register ok\n"); return 0; } static void __exit s3c_keyboard_drv_exit(void) { printk("s3c keyboard driver exit\n"); platform_driver_unregister(&s3c_keyboard_driver); return ; } module_init(s3c_keyboard_drv_init); module_exit(s3c_keyboard_drv_exit); MODULE_DESCRIPTION("FL2440 board keyboard input driver platform_driver"); MODULE_AUTHOR("Guo Wenxue<guowenxue@gmail.com>"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:FL2440 keyboard driver");
驱动测试文件event_button.c:
/********************************************************************************* * Copyright: (C) 2012 Guo Wenxue<Email:guowenxue@gmail.com QQ:281143292> * All rights reserved. * * Filename: event_button.c * Description: This file used to test GPIO button driver builtin Linux kernel on ARM board * * Version: 1.0.0(07/13/2012~) * Author: Guo Wenxue <guowenxue@gmail.com> * ChangeLog: 1, Release initial version on "07/13/2012 02:46:18 PM" * ********************************************************************************/ #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <libgen.h> #include <getopt.h> #include <sys/types.h> #include <sys/ioctl.h> #include <linux/input.h> #include <linux/kd.h> #include <linux/keyboard.h> #if 0 /* Just for comment here, Reference to linux-3.3/include/linux/input.h */ struct input_event { struct timeval time; __u16 type; /* 0x00:EV_SYN 0x01:EV_KEY 0x04:EV_MSC 0x11:EV_LED*/ __u16 code; /* key value, which key */ __s32 value; /* 1: Pressed 0:Not pressed 2:Always Pressed */ }; #endif #define TRUE 1 #define FALSE 0 #define EV_RELEASED 0 #define EV_PRESSED 1 #define EV_REPEAT 2 #define BUTTON_CNT 5 #define MODE_POLL 0x01 #define MODE_NORMAL 0x02 void usage(char *name); void display_button_event(struct input_event *ev, int cnt); int main(int argc, char **argv) { char *kbd_dev = NULL; char kbd_name[256] = "Unknown"; int kbd_fd = -1; int rv, opt; int mode = MODE_NORMAL; int size = sizeof (struct input_event); struct input_event ev[BUTTON_CNT]; struct option long_options[] = { {"device", required_argument, NULL, 'd'}, {"poll", no_argument, NULL, 'p'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; while ((opt = getopt_long(argc, argv, "d:ph", long_options, NULL)) != -1) { switch (opt) { case 'd': kbd_dev = optarg; break; case 'p': mode = MODE_POLL; break; case 'h': usage(argv[0]); return 0; default: break; } } if(NULL == kbd_dev) { usage(argv[0]); return -1; } if ((getuid ()) != 0) printf ("You are not root! This may not work...\n"); if ((kbd_fd = open(kbd_dev, O_RDONLY)) < 0) { printf("Open %s failure: %s", kbd_dev, strerror(errno)); return -1; } ioctl (kbd_fd, EVIOCGNAME (sizeof (kbd_name)), kbd_name); printf ("Monitor input device %s (%s) event with %s mode:\n", kbd_dev, kbd_name, MODE_POLL==mode?"poll":"infilit loop"); #if 0 /* Not implement in the Linux GPIO button driver */ unsigned char key_b[BUTTON_CNT/8 + 1]; memset(key_b, 0, sizeof(key_b)); if(ioctl(kbd_fd, EVIOCGKEY(sizeof(key_b)), key_b) < 0) { printf("EVIOCGKEY ioctl get error: %s\n", strerror(errno)); return -1; } #endif #if 0 /* Not implement in the Linux GPIO button driver */ /* rep[0]表示在按键é‡å¤å‡ºçŽ°ä¹‹å‰ delay的时间,rep[1]表示按键é‡å¤å‡ºçŽ°çš„时间间隔。 */ int rep[2] ={2500, 1000} ; if(ioctl(kbd_fd, EVIOCSREP, rep) < 0) { printf("EVIOCSREP ioctl get error: %s\n", strerror(errno)); return -1; } if(ioctl(kbd_fd, EVIOCGREP, rep) < 0) { printf("EVIOCGKEY ioctl get error: %s\n", strerror(errno)); return -1; } else { printf("repeate speed: [0]= %d, [1] = %d/n", rep[0], rep[1]); } #endif while (1) { if(MODE_POLL==mode) { fd_set rds; FD_ZERO(&rds); FD_SET(kbd_fd, &rds); rv = select(kbd_fd + 1, &rds, NULL, NULL, NULL); if (rv < 0) { printf("Select() system call failure: %s\n", strerror(errno)); goto CleanUp; } else if (FD_ISSET(kbd_fd, &rds)) { if ((rv = read (kbd_fd, ev, size*BUTTON_CNT )) < size) { printf("Reading data from kbd_fd failure: %s\n", strerror(errno)); break; } else { display_button_event(ev, rv/size); } } } else { if ((rv = read (kbd_fd, ev, size*BUTTON_CNT )) < size) { printf("Reading data from kbd_fd failure: %s\n", strerror(errno)); break; } else { display_button_event(ev, rv/size); } } } CleanUp: close(kbd_fd); return 0; } void usage(char *name) { char *progname = NULL; char *ptr = NULL; ptr = strdup(name); progname = basename(ptr); printf("Usage: %s [-p] -d <device>\n", progname); printf(" -d[device ] button device name\n"); printf(" -p[poll ] Use poll mode, or default use infinit loop.\n"); printf(" -h[help ] Display this help information\n"); free(ptr); return; } void display_button_event(struct input_event *ev, int cnt) { int i; struct timeval pressed_time, duration_time; for(i=0; i<cnt; i++) { //printf("type:%d code:%d value:%d\n", ev[i].type, ev[i].code, ev[i].value); if(EV_KEY==ev[i].type && EV_PRESSED==ev[i].value) { if(BTN_1 == ev[i].code) { pressed_time = ev[i].time; printf("S1 button key[%d] pressed time: %ld.%ld\n", ev[i].code, pressed_time.tv_sec, pressed_time.tv_usec); } else if(BTN_2 == ev[i].code) { pressed_time = ev[i].time; printf("S2 button key[%d] pressed time: %ld.%ld\n", ev[i].code, pressed_time.tv_sec, pressed_time.tv_usec); } else if(BTN_3 == ev[i].code) { pressed_time = ev[i].time; printf("S3 button key[%d] pressed time: %ld.%ld\n", ev[i].code, pressed_time.tv_sec, pressed_time.tv_usec); } else if(BTN_4 == ev[i].code) { pressed_time = ev[i].time; printf("S4 button key[%d] pressed time: %ld.%ld\n", ev[i].code, pressed_time.tv_sec, pressed_time.tv_usec); } else { pressed_time = ev[i].time; printf("button key[%d] pressed time: %ld.%ld\n", ev[i].code, pressed_time.tv_sec, pressed_time.tv_usec); } } if(EV_KEY==ev[i].type && EV_RELEASED==ev[i].value) { if(BTN_1 == ev[i].code) { timersub(&ev[i].time, &pressed_time, &duration_time); printf("S1 button key[%d] released time: %ld.%ld\n", ev[i].code, ev[i].time.tv_sec, ev[i].time.tv_usec); printf("S1 button key[%d] duration time: %ld.%ld\n", ev[i].code, duration_time.tv_sec, duration_time.tv_usec); } else if(BTN_2 == ev[i].code) { timersub(&ev[i].time, &pressed_time, &duration_time); printf("S2 button key[%d] released time: %ld.%ld\n", ev[i].code, ev[i].time.tv_sec, ev[i].time.tv_usec); printf("S2 button key[%d] duration time: %ld.%ld\n", ev[i].code, duration_time.tv_sec, duration_time.tv_usec); } else if(BTN_3 == ev[i].code) { timersub(&ev[i].time, &pressed_time, &duration_time); printf("S3 button key[%d] released time: %ld.%ld\n", ev[i].code, ev[i].time.tv_sec, ev[i].time.tv_usec); printf("S3 button key[%d] duration time: %ld.%ld\n", ev[i].code, duration_time.tv_sec, duration_time.tv_usec); } else if(BTN_4 == ev[i].code) { timersub(&ev[i].time, &pressed_time, &duration_time); printf("S4 button key[%d] released time: %ld.%ld\n", ev[i].code, ev[i].time.tv_sec, ev[i].time.tv_usec); printf("S4 button key[%d] duration time: %ld.%ld\n", ev[i].code, duration_time.tv_sec, duration_time.tv_usec); } else { timersub(&ev[i].time, &pressed_time, &duration_time); printf("button key[%d] released time: %ld.%ld\n", ev[i].code, ev[i].time.tv_sec, ev[i].time.tv_usec); printf("button key[%d] duration time: %ld.%ld\n", ev[i].code, duration_time.tv_sec, duration_time.tv_usec); } } } /* for(i=0; i<cnt; i++) */ }
驱动和测试程序编译Makefile文件
1 TEST_APP=event_button 2 3 KERNEL_VER = linux-3.0 4 LINUX_SRC ?= ../../linux/kernel/$(KERNEL_VER) 5 6 CROSS_COMPILE=/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux- 7 8 PWD := $(shell pwd) 9 10 obj-m += kbd_device.o 11 obj-m += kbd_driver.o 12 13 modules: 14 @make -C $(LINUX_SRC) M=$(PWD) modules 15 @make clear 16 @chmod a+x *.ko && cp *.ko /tftp 17 @make testapp 18 19 clear: 20 @rm -f *.o *.cmd *.mod.c 21 @rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f 22 @rm -f .*ko.cmd .*.o.cmd .*.o.d 23 24 clean: clear 25 @rm -f *.ko ${TEST_APP} 26 27 testapp: 28 ${CROSS_COMPILE}gcc ${TEST_APP}.c -o ${TEST_APP}
编译:
[guowenxue@centos6 input_kbd]$ make make[1]: Entering directory `/usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/linux/kernel/linux-3.0' CC [M] /usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd/kbd_device.o CC [M] /usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd/kbd_driver.o Building modules, stage 2. MODPOST 2 modules CC /usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd/kbd_device.mod.o LD [M] /usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd/kbd_device.ko CC /usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd/kbd_driver.mod.o LD [M] /usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd/kbd_driver.ko make[1]: Leaving directory `/usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/linux/kernel/linux-3.0' make[1]: Entering directory `/usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd' make[1]: Leaving directory `/usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd' make[1]: Entering directory `/usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd' /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc event_button.c -o event_button make[1]: Leaving directory `/usr/local/src/.guowenxue/code.taobao.org/fl2440/trunk/src/driver/input_kbd'
运行测试:
列出已经安装的驱动模块 ~ >: lsmod kbd_driver 2470 0 - Live 0xbf01c000 kbd_device 973 0 - Live 0xbf018000 ~ >: 删除驱动的platform_driver模块 ~ >: rmmod kbd_driver s3c keyboard driver exit s3c_kbd_remove ok ~ >: 删除驱动的paltform_device模块 ~ >: rmmod kbd_device S3C keyboard device exit ~ >: 安装驱动的paltform_device模块 ~ >: insmod kbd_device.ko S3C keyboard platform device register ok ~ >: 安装驱动的platform_driver模块 ~ >: insmod kbd_driver.ko input: s3c_kbd as /devices/platform/s3c_kbd.1/input/input6 s3c_kbd_probe ok s3c keyboard platform driver register ok ~ >: 使用测试程序event_button分别测试4个按键: ~ >: event_button -p -d /dev/input/event1 Monitor input device /dev/input/event1 (s3c_kbd) event with poll mode: button key[2] pressed time: 1469541794.755122 button key[2] released time: 1469541794.980104 button key[2] duration time: 1469418914.857224 button key[3] pressed time: 1469541798.625197 button key[3] released time: 1469541798.865105 button key[3] duration time: 1469418918.742225 button key[4] pressed time: 1469541801.260260 button key[4] released time: 1469541801.535216 button key[4] duration time: 1469418921.412336 button key[5] pressed time: 1469541803.805109 button key[5] released time: 1469541804.107 button key[5] duration time: 1469418923.877227