前面几章我们写的按键驱动程序虽然已经足够完善,但是这个驱动只有知道/dev/key设备节点和write()格式的人才能使用,不具有适应性
故本节引入标准的输入子系统,来编写通用的输入类设备。输入子系统是对所有的标准输入类设备的统一的管理系统,使用这个模型可以跨平台的处理所有的输入类设备
一、输入子系统分层
输入子系统将一个输入设备的输入过程分成了设备驱动(input device driver)和事件驱动(input event driver)两层。设备驱动负责从底层硬件采集数据,事件驱动负责给用户程序提供接口。通过分层设计,将不同的设备统一到几种驱动接口上。同一种事件驱动可以用来处理多个同类设备;同一个设备也可以和多种事件驱动相衔接。而事件驱动和设备驱动则由输入核心层进行连接,匹配。分层结构如下图:
输入子系统核心层定义在drivers/input/input.c中
由于输入子系统也是字符设备驱动程序,因此它一定也会有创建类、注册字符设备的过程,而且会有file_operations等结构体,我们可以从此进行分析
二、input.c分析
在input_init()函数中所做的和按键驱动程序中所做的大致相同,如创建类、注册名为input的字符设备
static int __init input_init(void) { ... err = class_register(&input_class); ... err = register_chrdev(INPUT_MAJOR, "input", &input_fops); ... }
其file_operations结构体定义如下:
static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, .llseek = noop_llseek, };
当我们应用程序open()时,会调用file_operations input_fops对应的open()函数
static int input_open_file(struct inode *inode, struct file *file) { ... handler = input_table[iminor(inode) >> 5]; if (handler) new_fops = fops_get(handler->fops); ... old_fops = file->f_op; file->f_op = new_fops; err = new_fops->open(inode, file); ... }
由上述代码可知:
1. input_table[]根据次设备号存储handler
2. open()函数使用新的fops(设备驱动中的fops)代替了旧的fops,这个操作也就解释了为什么file_operations结构体中没有读写函数
3. open()函数在替换之后,调用了新的fops的open()函数
handler是我们之前没有分析的,它定义为:
static struct input_handler *input_table[8];
至于数组大小为什么是8,这是因为目前常用的handler只有三种:evdev,mousedev,joydev。而且evdev是通用的handler,定义8个应该够用了
handler结构体定义为:
/** * struct input_handler - implements one of interfaces for input devices * ... */ struct input_handler { void *private; void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value); bool (*match)(struct input_handler *handler, struct input_dev *dev); int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); void (*disconnect)(struct input_handle *handle); void (*start)(struct input_handle *handle); const struct file_operations *fops; int minor; const char *name; const struct input_device_id *id_table; struct list_head h_list; struct list_head node; };
根据注释信息,handler应该就是输入事件驱动程序的结构体
在input_handler结构体中使用了input_handle结构体,其定义如下:
/** * struct input_handle - links input device with an input handler * ... */ struct input_handle { void *private; int open; const char *name; struct input_dev *dev; struct input_handler *handler; struct list_head d_node; struct list_head h_node; };
根据注释信息,input_handle用于连接input_dev和input_handler,三者关系在下面分析
分析完了input.c文件,我们来一一分析input_handler、input_handle以及两者的连接过程
三、input_dev
input_dev使用方法遵循:分配、设置、注册
分配:
struct input_dev *input_allocate_device(void)
设置(首先设置事件类,然后设置具体事件):
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code) { switch (type) { case EV_KEY: // 按键事件类,可指定按键如KEY_1、KEY_Q、KEY_ENTER等 __set_bit(code, dev->keybit); break; case EV_REL: // 相对位移事件类,可指定相对位移如REL_X、REL_Y等 __set_bit(code, dev->relbit); break; case EV_ABS: // 绝对位移事件类,可指定绝对位移如ABS_X、ABS_Y等 __set_bit(code, dev->absbit); break; ... } __set_bit(type, dev->evbit); }
也可以使用set_bit()函数,在源代码中使用set_bit()
注册:
int input_register_device(struct input_dev *dev) { ... /* 通用的同步事件 */ __set_bit(EV_SYN, dev->evbit); ... /* 注册的设备名字为input0, 1, 2, ... */ dev_set_name(&dev->dev, "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); /* 添加device */ error = device_add(&dev->dev); ... /* 把dev结构放到链表里面 */ list_add_tail(&dev->node, &input_dev_list); /* 对每一个input_handler都调用input_attach_handler()函数 */ list_for_each_entry(handler, &input_handler_list, node) /* 匹配dev和handler */ input_attach_handler(dev, handler); ... }
其中的input_attach_handler(dev, handler);对应input_handle,因为之前说过input_handle用于连接input_dev和input_handler
在注册完成后,若input_dev获得数据,需要向核心层上报事件,上报事件使用如下函数:
// 上报事件 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) // 上报绝对坐标 void input_report_abs(struct input_dev *dev, unsigned int code, int value) // 上报相对坐标 void input_report_rel(struct input_dev *dev, unsigned int code, int value) // 上报键值 void input_report_key(struct input_dev *dev, unsigned int code, int value) // 上报同步事件 void input_sync(struct input_dev *dev)
代码中后四个函数均使用input_event()函数实现,input_event()函数调用过程如下:
input_event() -> input_handle_event() -> input_pass_event() -> handler->event(handle, type, code, value);
上报事件最终会调用handler->event()函数
之前说过evdev是通用的handler,在此我便以/drivers/input/evdev.c进行分析
evdev_read()会进行休眠,evdev_event()在上报事件被调用后会唤醒休眠进程,从而完成read()操作
注销:
// 注销 void input_unregister_device(struct input_dev *dev) // 释放 void input_free_device(struct input_dev *dev)
四、input_handler
注册:
int input_register_handler(struct input_handler *handler) { ... INIT_LIST_HEAD(&handler->h_list); /* 设置input_table */ if (handler->fops != NULL) { if (input_table[handler->minor >> 5]) { retval = -EBUSY; goto out; } input_table[handler->minor >> 5] = handler; } /* 把handler放入input_handler_list */ list_add_tail(&handler->node, &input_handler_list); /* 对每一个dev调用input_attach_handler匹配handler */ list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); ... }
注销:
void input_unregister_handler(struct input_handler *handler)
五、input_dev和input_handler的连接过程
两者匹配使用的是input_attach_handler()函数:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { ... id = input_match_device(handler, dev); ... error = handler->connect(handler, dev, id); ... }
input_match_device()函数:
static const struct input_device_id *input_match_device(struct input_handler *handler, struct input_dev *dev) { for (id = handler->id_table; id->flags || id->driver_info; id++) { // 默认的匹配过程,使用handler->id_table和dev->id进行匹配 if (!handler->match || handler->match(handler, dev)) return id; } }
在代码中,匹配成功退出调用handler的connect()函数,否则调用handler的match()函数
在此还是以evdev为例,其连接函数为evdev_connect()定义如下:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { ... /* 分配evdev */ evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); ... /* 设置handle */ evdev->handle.dev = input_get_device(dev); evdev->handle.name = dev_name(&evdev->dev); evdev->handle.handler = handler; evdev->handle.private = evdev; ... /* 注册handle */ error = input_register_handle(&evdev->handle); error = evdev_install_chrdev(evdev); error = device_add(&evdev->dev); ... }
input_dev、input_handler和input_handle三者关系如下:
六、总结
1. input_init()初始化输入子系统
1.1 调用register_chrdev(13, "input", &input_fops);
2. open()输入子系统文件:int input_open_file()
2.1 替换替换file_oprations
2.2 执行new_fops->open()函数
3. 注册input_handler:input_register_handler()
3.1 添加handler到input_table[]数组
3.2 添加handler到input_handler_list链表
3.3 调用input_attach_handler()
4. 注册input_dev:input_register_device()
4.1 添加dev到input_dev链表
4.2 调用input_attach_handler()
5. 匹配:input_attach_handler()
5.1 匹配dev->id和handler->id_table
5.2 成功,调用input_handler->connect()
6. 连接:input_handler->connect()
6.1 创建input_handle,三者连接
7. event发生(如按键中断),在中断函数中上报事件:input_event()
7.1 调用input_handler->event()
七、更改key.c为输入子系统
主要更改的函数有keys_init()和key_timer_func():
keys_init():
1 static int keys_init(void) 2 { 3 /* 1. 分配 */ 4 inputdev = input_allocate_device(); 5 6 /* 2. 设置 */ 7 /* 2.1 设置事件类 */ 8 set_bit(EV_KEY, inputdev->evbit); 9 set_bit(EV_REP, inputdev->evbit); /* 重复类事件 */ 10 11 /* 2.2 设置按键事件 */ 12 set_bit(KEY_L, inputdev->keybit); 13 set_bit(KEY_S, inputdev->keybit); 14 set_bit(KEY_ENTER, inputdev->keybit); 15 set_bit(KEY_LEFTSHIFT, inputdev->keybit); 16 17 /* 3. 注册 */ 18 input_register_device(inputdev); 19 ... 20 }
set_bit(EV_REP, inputdev->evbit);表示可产生重复类事件,也就是长按按键,就会产生多次按键效果
key_timer_func():
1 static void key_timer_func(unsigned long arg) 2 { 3 ... 4 if (pinval) /* 松开 */ { 5 /* 上传数据 */ 6 input_event(inputdev, EV_KEY, pindesc->val, 0); 7 input_sync(inputdev); 8 } 9 else /* 按下 */ { 10 input_event(inputdev, EV_KEY, pindesc->val, 1); 11 input_sync(inputdev); 12 } 13 }
input_sync(inputdev);表示已完成当前上报工作
key源代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <linux/module.h> 2 #include <linux/fs.h> 3 #include <linux/init.h> 4 #include <linux/cdev.h> 5 #include <linux/slab.h> 6 #include <linux/device.h> 7 #include <linux/irq.h> 8 #include <linux/interrupt.h> 9 #include <linux/wait.h> 10 #include <linux/timer.h> 11 #include <linux/gpio.h> 12 #include <linux/sched.h> 13 #include <linux/input.h> 14 15 #include <asm/uaccess.h> 16 #include <asm/irq.h> 17 #include <asm/io.h> 18 19 #include <mach/gpio.h> 20 21 #define KEY_MAJOR 255 22 23 struct pin_desc { 24 unsigned int gpio; 25 int val; 26 char *name; 27 }; 28 29 static struct timer_list key_timer; 30 static struct pin_desc* pindesc; 31 static struct input_dev* inputdev; 32 33 static struct pin_desc desc[4] = { 34 { EXYNOS4_GPX3(2), KEY_L, "KEY0" }, 35 { EXYNOS4_GPX3(3), KEY_S, "KEY1" }, 36 { EXYNOS4_GPX3(4), KEY_ENTER, "KEY2" }, 37 { EXYNOS4_GPX3(5), KEY_LEFTSHIFT, "KEY3" }, 38 }; 39 40 static void key_timer_func(unsigned long arg) 41 { 42 struct pin_desc *irq_pd = pindesc; 43 44 if (!irq_pd) 45 return ; 46 47 unsigned int pinval; 48 49 pinval = gpio_get_value(pindesc->gpio); 50 51 if (pinval) /* 松开 */ { 52 /* 上传数据 */ 53 input_event(inputdev, EV_KEY, pindesc->val, 0); 54 input_sync(inputdev); 55 } 56 else /* 按下 */ { 57 input_event(inputdev, EV_KEY, pindesc->val, 1); 58 input_sync(inputdev); 59 } 60 } 61 62 static irqreturn_t key_interrupt(int irq, void *dev_id) 63 { 64 pindesc = (struct pin_desc *)dev_id; 65 66 mod_timer(&key_timer, jiffies + HZ / 100); 67 68 return IRQ_HANDLED; 69 } 70 71 static int keys_init(void) 72 { 73 /* 1. 分配 */ 74 inputdev = input_allocate_device(); 75 76 /* 2. 设置 */ 77 /* 2.1 设置事件类 */ 78 set_bit(EV_KEY, inputdev->evbit); 79 set_bit(EV_REP, inputdev->evbit); /* 重复类事件 */ 80 81 /* 2.2 设置按键事件 */ 82 set_bit(KEY_L, inputdev->keybit); 83 set_bit(KEY_S, inputdev->keybit); 84 set_bit(KEY_ENTER, inputdev->keybit); 85 set_bit(KEY_LEFTSHIFT, inputdev->keybit); 86 87 /* 3. 注册 */ 88 input_register_device(inputdev); 89 90 /* 注册中断 */ 91 int irq, i; 92 for (i = 0; i < ARRAY_SIZE(desc); i++) { 93 irq = gpio_to_irq(desc[i].gpio); 94 request_irq(irq, key_interrupt, IRQ_TYPE_EDGE_BOTH, desc[i].name, (void *)&desc[i]); 95 } 96 97 init_timer(&key_timer); 98 key_timer.function = key_timer_func; 99 add_timer(&key_timer); 100 101 return 0; 102 } 103 104 static void keys_exit(void) 105 { 106 // 释放中断 107 int irq, i; 108 109 for (i = 0; i < ARRAY_SIZE(desc); i++) { 110 irq = gpio_to_irq(desc[i].gpio); 111 free_irq(irq, (void *)&desc[i]); 112 } 113 114 del_timer(&key_timer); 115 116 input_unregister_device(inputdev); 117 input_free_device(inputdev); 118 } 119 120 module_init(keys_init); 121 module_exit(keys_exit); 122 123 MODULE_LICENSE("GPL");
Makefile:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 KERN_DIR = /work/tiny4412/tools/linux-3.5 2 3 all: 4 make -C $(KERN_DIR) M=`pwd` modules 5 6 clean: 7 make -C $(KERN_DIR) M=`pwd` modules clean 8 rm -rf modules.order 9 10 obj-m += key.o
测试文件:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 #include <string.h> 7 8 int main(int argc, char** argv) 9 { 10 if (argc != 2) { 11 printf("Usage:\n"); 12 printf("%s <event1|event2>\n", argv[0]); 13 return 0; 14 } 15 16 char buf[100] = "/dev/"; 17 strcat(buf, argv[1]); 18 printf("You Will Open %s\n", buf); 19 20 int fd; 21 22 fd = open(buf, O_RDWR); 23 if (fd < 0) { 24 printf("can't open %s\n", buf); 25 return -1; 26 } 27 28 unsigned char key_val; 29 30 while (1) { 31 read(fd, &key_val, 1); 32 printf("key_val = 0x%x\n", key_val); 33 } 34 35 close(fd); 36 37 return 0; 38 }
测试:
在编译并在开发板上insmod后,会出现如下信息:
<6>input: Unspecified device as /devices/virtual/input/input3
这是由于未设置input_dev的名字所导致的,暂时不需要管
接下来执行:
# ps -ef
确定-/bin/sh的pid为108(不同开发板-/bin/sh的pid不同)
# ls -l /proc/108/fd
确定使用tty1
# exec 0</dev/tty1
接下来按键,效果如下图:
下一章 九、总线设备驱动模型