前面幾章我們寫的按鍵驅動程序雖然已經足夠完善,但是這個驅動只有知道/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源代碼:

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:

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
測試文件:

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
接下來按鍵,效果如下圖:
下一章 9、總線設備驅動模型