本次介绍Linux的输入子系统的驱动开发.
Linux 内核的输入子系统为鼠标、键盘、触摸屏、游戏杆等输入设备提供了驱动框架。 当程序员要为自己的输入设备编写驱动程序时,只需要实现从设备获取输入事件即可。至于 输入事件如何处理,用户接口如何实现,都由输入子系统完成。这大大减轻了输入驱动程序 的编码工作,也提高了驱动程序的稳健性。
同时输入子系统为所有输入设备都为应用层提供了标准的接口,这大大提高了驱动程序 的易用性。输入子系统的驱动代码在内核的<drivers/input/> 目录下。
输入子系统构成
输入子系统的实现需要满足以下需求:
(1) 输入子系统要为每个输入设备都在/dev/ 目录下生成一个设备文件,以方便应用程序 读取指定输入设备产生的事件;
(2) 对于每一个输入设备,在输入子系统只需要实现其事件获取即可,至于事件如何处理、如何到达设备文件则不需要考虑;
(3) 在Linux 输入设备的可以分为事件类(如USB 鼠标、USB 键盘、触摸屏等)、MOUSE 类(特指 PS/2 接口的输入设备)、游戏杆等类型,为这些输入设备而实现的设备文件的接口必须有所差别。因此输入子系统需要为不同类型的输入设备实现正确的设备文件接口。
本次将通过开发一个简单的输入子系统代码来了解输入子系统,本次结合平台总线 + 输入子系统的方式开发,以下是相关代码:
先定义一个头文件:
#ifndef __PLAT_INPUT_H__ #define __PLAT_INPUT_H__ struct key_info { char *name; int gpio; int code; int flags; }; struct key_platdata { struct key_info *key_desc; int num; }; #endif
输入子系统平台设备信息代码:
#include <linux/init.h> #include <linux/module.h> #include <linux/input.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include "key_info.h" struct key_info key_pdesc[4] = { [0] = { .name = "KEY_UP", .gpio = EXYNOS4_GPX3(2), .code = KEY_UP, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [1] = { .name = "KEY_DOWN", .gpio = EXYNOS4_GPX3(3), .code = KEY_DOWN, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [2] = { .name = "KEY_LEFT", .gpio = EXYNOS4_GPX3(4), .code = KEY_LEFT, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [3] = { .name = "KEY_RIGHT", .gpio = EXYNOS4_GPX3(5), .code = KEY_RIGHT, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, }; struct key_platdata key_pdev_info = { .key_desc = key_pdesc, .num = ARRAY_SIZE(key_pdesc), }; struct platform_device key_input_pdev = { .name = "EXYNOS4_KEY", .id = -1, .dev = { .platform_data = &key_pdev_info, }, }; static void __exit key_platdev_exit(void) { platform_device_unregister(&key_input_pdev); } static int __init key_platdev_init(void) { return platform_device_register(&key_input_pdev); } module_init(key_platdev_init); module_exit(key_platdev_exit); MODULE_LICENSE("Dual BSD/GPL");
输入子系统平台设备驱动代码:
#include <linux/init.h> #include <linux/module.h> #include <linux/input.h> #include <linux/gpio.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include "key_info.h" struct key_platdata *key_plat_info = NULL; struct input_dev *key_inputdev = NULL; irqreturn_t key_input_irq(int irqno, void *devid) { int i = 0; udelay(25); for(i = 0; i < key_plat_info->num; i++){ if(gpio_get_value(key_plat_info->key_desc[i].gpio)){ input_report_key(key_inputdev, key_plat_info->key_desc[i].code, 0); } else{ printk("<KERNEL> %s PRESSED\n", key_plat_info->key_desc[i].name); input_report_key(key_inputdev, key_plat_info->key_desc[i].code, 1); } } input_sync(key_inputdev); return IRQ_HANDLED; } int key_input_remove(struct platform_device *pdev) { int i; for(i = 0; i < key_plat_info->num; i++) free_irq(gpio_to_irq(key_plat_info->key_desc[i].gpio), &key_plat_info->key_desc[i]); input_unregister_device(key_inputdev); input_free_device(key_inputdev); } int key_input_probe(struct platform_device *pdev) { int ret = -1, i; // 0, 获取platform数据 key_plat_info = pdev->dev.platform_data; // 1, 构建一个input_devvice对象 key_inputdev = input_allocate_device(); if(NULL == key_inputdev){ printk("input alloc failed!\n"); return -EINVAL; } // 2, 初始化input_device对象 // 2.1, 设置能产生哪些类型的数据 __set_bit(EV_KEY, key_inputdev->evbit); // 2.2, 设置能产生哪些键值 __set_bit(KEY_UP, key_inputdev->keybit); __set_bit(KEY_DOWN, key_inputdev->keybit); __set_bit(KEY_LEFT, key_inputdev->keybit); __set_bit(KEY_RIGHT,key_inputdev->keybit); // 3, 注册input device对象 ret = input_register_device(key_inputdev); if(0 != ret){ printk("input register failed!\n"); goto err1; } // 4, 硬件初始化-->申请中断 for(i = 0; i < key_plat_info->num; i++){ ret = request_irq(gpio_to_irq(key_plat_info->key_desc[i].gpio), key_input_irq, key_plat_info->key_desc[i].flags, key_plat_info->key_desc[i].name, &key_plat_info->key_desc[i]); if(0 != ret){ printk("request irq failed!\n"); goto err2; } } return 0; err2: input_unregister_device(key_inputdev); err1: input_free_device(key_inputdev); return ret; } const struct platform_device_id key_id_table[] = { {"S5PV210_KEY", 0x5555}, {"EXYNOS4_KEY", 0x6665}, }; struct platform_driver key_input_pdrv = { .probe = key_input_probe, .remove = key_input_remove, .driver = { .name = "Samsung_KEY", }, .id_table = key_id_table, }; static void __exit key_platdrv_exit(void) { platform_driver_unregister(&key_input_pdrv); } static int __init key_platdrv_init(void) { return platform_driver_register(&key_input_pdrv); } module_init(key_platdrv_init); module_exit(key_platdrv_exit); MODULE_LICENSE("Dual BSD/GPL");
用户层测试代码:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h> int main(void) { int ret = -1, fd = -1; struct input_event event; fd = open("/dev/input/event2", O_RDWR); if(fd < 0){ perror("open failed!\n"); exit(1); } for(;;){ ret = read(fd, &event, sizeof(struct input_event)); if(ret < 0){ perror("read failed!\n"); exit(1); } if(EV_KEY == event.type){ switch(event.code) { case KEY_UP: if(event.value) printf("KEY_UP pressed!\n"); else printf("KEY_UP UP!\n"); break; case KEY_DOWN: if(event.value) printf("KEY_DOWN pressed!\n"); else printf("KEY_DOWN UP!\n"); break; case KEY_LEFT: if(event.value) printf("KEY_LEFT pressed!\n"); else printf("KEY_LEFT UP!\n"); break; case KEY_RIGHT: if(event.value) printf("KEY_RIGHT pressed!\n"); else printf("KEY_RIGHT UP!\n"); break; default: break; } } } if(close(fd) < 0){ perror("close"); exit(1); } return 0; }
最后是Makefile文件:
#Linux源代码路径 KERNEL_DIR = /home/george/1702/exynos/linux-3.5 #指定当前路径 CUR_DIR = $(shell pwd) MYAPP = key_test MODULE = plat_input_dev MODULE2 = plat_input_drv all: make -C $(KERNEL_DIR) M=$(CUR_DIR) modules arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c clean: make -C $(KERNEL_DIR) M=$(CUR_DIR) clean $(RM) $(MYAPP) install: cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702 #指定编译当前目录下哪个源文件 obj-m = $(MODULE).o obj-m += $(MODULE2).o就这么多吧.