简介 input 就是输入的意思,因此 input 子系统就是管理输入的子系统。像按键、键盘、鼠标、触摸屏等等就是输入设备。按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。input 子系统分为 驱动层、核心层、事件处理层 ,最终再给用户空间提供可访问的设备节点,input 子系统框架如下图所示:
图 1 input子系统
图中,左边是最底层的具体设备,比如按键、USB键盘/鼠标等等,中间部分属于 Linux 内核空间,分为驱动层、核心层和事件层,最右边就是用户空间,所有的输入设备以文件形式提供给用户程序使用。可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们在写驱动的时候只需要关注中间的驱动层、核心层和事件层。这三个层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。 核心层:承上启下,为驱动层提供输入设备注册和操作接口,通知事件层对输入事件进行处理。 事件层:主要和用户空间进行交互。
input 核心层会向 Linux 内核注册一个字符设备,我们找到 drivers/input/input.c 这个文件,它就是 input 输入子系统的核心层,这个文件中有如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 struct class input_class = { .name = "input" , .devnode = input_devnode, }; EXPORT_SYMBOL_GPL(input_class); ····· static int __init input_init (void ) { int err; err = class_register(&input_class); if (err) { pr_err("unable to register input_dev class\n" ); return err; } err = input_proc_init(); if (err) goto fail1; err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0 ), INPUT_MAX_CHAR_DEVICES, "input" ); if (err) { pr_err("unable to register char major %d" , INPUT_MAJOR); goto fail2; } return 0 ; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err; }
err = class_register(&input_class);这一句注册了一个 input 类,这样系统启动以后就会在 /sys/class 目录下有一个 input 子目录,register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, “input”);这一句注册了一个字符设备,主设备号为 INPUT_MAJOR,其定义在 include/uapi/linux/major.h 文件中,具体值为 13。因此,input 所有设备的主设备号都为 13,我们在使用 input 子系统时就不需要注册字符设备了,我们只需要向系统注册一个 input_device 即可。
1、注册 input_dev 在使用 input 子系统的时候我们只需要注册一个 input 设备即可,input_dev 结构体表示 input 设备,此结构体定义在 include/linux/input.h 文件中,定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 struct input_dev { const char *name; const char *phys; const char *uniq; struct input_id id ; unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; unsigned int hint_events_per_packet; unsigned int keycodemax; unsigned int keycodesize; void *keycode; int (*setkeycode)(struct input_dev *dev, const struct input_keymap_entry *ke, unsigned int *old_keycode); int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke); struct ff_device *ff ; struct input_dev_poller *poller ; unsigned int repeat_key; struct timer_list timer ; int rep[REP_CNT]; struct input_mt *mt ; struct input_absinfo *absinfo ; unsigned long key[BITS_TO_LONGS(KEY_CNT)]; unsigned long led[BITS_TO_LONGS(LED_CNT)]; unsigned long snd[BITS_TO_LONGS(SND_CNT)]; unsigned long sw[BITS_TO_LONGS(SW_CNT)]; int (*open)(struct input_dev *dev); void (*close)(struct input_dev *dev); int (*flush)(struct input_dev *dev, struct file *file); int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle __rcu *grab ; spinlock_t event_lock; struct mutex mutex ; unsigned int users; bool going_away; struct device dev ; struct list_head h_list ; struct list_head node ; unsigned int num_vals; unsigned int max_vals; struct input_value *vals ; bool devres_managed; ktime_t timestamp[INPUT_CLK_MAX]; bool inhibited; };
其中,evbit 表示输入事件类型,可选的事件类型定义在 include/dt-bindings/input/linux-event-codes.h,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define EV_SYN 0x00 #define EV_KEY 0x01 #define EV_REL 0x02 #define EV_ABS 0x03 #define EV_MSC 0x04 #define EV_SW 0x05 #define EV_LED 0x11 #define EV_SND 0x12 #define EV_REP 0x14 #define EV_FF 0x15 #define EV_PWR 0x16 #define EV_FF_STATUS 0x17 #define EV_MAX 0x1f #define EV_CNT (EV_MAX+1)
比如本次我们要使用按键,就要注册 EC_KEY 事件,如果要使用连按功能的话还需要注册 EV_REP 事件。
继续回到结构体的定义中,evbit、keybit、relbit 等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到 keybit,keybit 就是按键事件使用的位图,Linux 内核中定义了许多按键值,这些按键值定义在 include/dt-bindings/input/linux-event-codes.h 中,如下:
1 2 3 4 5 6 7 8 9 10 11 12 #define KEY_RESERVED 0 #define KEY_ESC 1 #define KEY_1 2 #define KEY_2 3 #define KEY_3 4 #define KEY_4 5 #define KEY_5 6 #define KEY_6 7 #define KEY_7 8 #define KEY_8 9 #define KEY_9 10 #define KEY_0 11
我们可以将开发板上的按键值设置为上述的任意一个,比如我们本次就把KEY按键设置为 KEY_0。 在编写 input 设备驱动的时候我们需要先申请一个 input_dev 结构体变量,使用 input_allocate_device 函数来申请一个 input_dev,此函数原型如下所示:
struct inputdev *input_allocate_device(void)
其中,返回值表示申请到的 input_dev。 如果要注销掉 input 设备的话需要使用 input_free_device 函数来释放掉前面申请到的 input_dev,input_free_device 函数原型如下:
void input_free_device(struct input_dev *dev)
其中参数 dev 表示需要释放的 input_dev。 申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类型和事件值这两种。input_dev 初始化完成以后就需要向 Linux 内核注册 input_dev 了,需要用到 input_register_device 函数,此函数原型如下:
int input_register_device(struct input_dev *dev)
其中,dev 表示要注册的 input_dev,返回值为0则注册成功,负值注册失败。 同样的,注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销,其原型如下:
void input_unregister_device(struct input_dev *dev)
其中,dev就是要注销的 input_dev。 综上所述,input_dev 注册过程如下: ①、使用 input_allocate_device 函数申请一个 input_dev。 ②、初始化 input_dev 的事件类型以及事件值。 ③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev。 ④、卸载 input 驱动的时候需要先使用 input_unregister_device 函数注销掉 input_dev,然后使用 input_free_device 函数释放掉前面申请的 input_dev。 input_dev 注册过程示例代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct input_dev *inputdev ;static int __init xxx_init (void ) { ······ inputdev = input_allocate_device(); inputdev->name = "test_inputdev" __set_bit(EV_KEY, inputdev->evbit); __set_bit(EV_KEY, inputdev->evbit); __set_bit(KEY_0, inputdev->keybit); input_register_device(inputdev); ······ return 0 ; } static void __exit xxx_exit (void ) { input_register_device(inputdev); input_free_device(inputdev); }
2、上报输入事件 当我们向 Linux 内核注册号 input_dev 以后还不能直接使用 input 设备,input 设备都具有输入功能,但是具体是什么样的输入值 Linux 内核是不知道的,我们需要先获取输入事件,然后再将事件上报给 Linux 内核。比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中把按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。不同的事件,上报的 API 函数也不一样。接下来我们依次看一下常用的事件 API 上报函数。 首先是 input_event 函数,此函数用于上报指定的事件以及对应的值,函数原型如下:
1 2 3 4 5 6 7 8 9 void input_event (struct input_dev *dev, unsigned int type, unsigned int code, int value) ;
input_event 函数可以上报所有的事件类型和事件值,Linux 内核也提供了其他的针对具体时间的上报函数,这些函数都用到了 input_event 函数。比如上报按键所使用的 input_report_key 函数,此函数内容如下:
1 2 3 4 static inline void input_report_key (struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_KEY, code, !!value); }
可以看处,input_report_key 的函数本质就是 input_event 函数,不过如果要上报按键事件的话还是推荐使用 input_report_key 函数。 同样的还有一些其他的事件上报函数,如下所示:
1 2 3 4 5 void input_report_rel (struct input_dev *dev, unsigned int code, int value) void input_report_abs (struct input_dev *dev, unsigned int code, int value) void input_report_ff_status (struct input_dev *dev, unsigned int code, int value) void input_report_switch (struct input_dev *dev, unsigned int code, int value) void input_mt_sync (struct input_dev *dev)
我们上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束,input_sync 函数本质上是上报一个同步事件,其函数原型如下:
void input_sync(struct input_dev *dev)
dev 表示需要上报同步事件的 input_dev。 综上所述,按键的上报事件的参考代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 void timer_function (unsigned long arg) { unsigned char value; value = gpio_get_value(keydesc->gpio); if (value == 0 ) { input_report_key(inputdev, KEY_0, 1 ); input_sync(inputdev); } ······ }
Linux 内核使用 input_event 这个结构体来表示所有的输入事件,input_event 结构体定义在 include/uapi/linux/input.h 中,结构体内容如下:‘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct input_event {#if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__) struct timeval time ; #define input_event_sec time.tv_sec #define input_event_usec time.tv_usec #else __kernel_ulong_t __sec; #if defined(__sparc__) && defined(__arch64__) unsigned int __usec; unsigned int __pad; #else __kernel_ulong_t __usec; #endif #define input_event_sec __sec #define input_event_usec __usec #endif __u16 type; __u16 code; __s32 value; };
其中,time就是此事件发生的事件,为 timeval 结构体类型,timeval 定义如下:
1 2 3 4 5 struct timeval { __time_t tv_sec; __suseconds_t tv_usec; };
可以看出,tv_sec 和 tv_usec 这两个成员的变量都为 long 类型,也就是 32 位。 回到 input_event,type 表示事件类型,比如 EV_KEY,表示此次事件位按键事件,此成员变量为 16 位。code 表示事件码,比如在 EV_KEY 事件中 code 就表示具体的按键事件,如 KEY_0、KEY_1 等,此成员变量为 16 位。value 在 EV_KEY 事件中表示按键值,如果为 1 的话表示按键按下,如果为 0 的话就表示没有按下。 input_event 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或者相关的值,比如按键值等等。
编写代码 设备树在之前的学习过程中已经修改过,直接使用即可。新建文件夹,创建 vscode 工程,创建新的 C 文件,写入以下内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/irq.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/fs.h> #include <linux/fcntl.h> #include <linux/file.h> #include <linux/input.h> #include <linux/irqnr.h> #include <linux/platform_device.h> #include <linux/device.h> #include <linux/of_address.h> #include <linux/of_device.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/of_irq.h> #define KEYINPUT_CNT 1 #define KEYINPUT_NAME "keyinput" #define KEY0VALUE 0x01 #define INVAKEY 0xFF #define KEY_NUM 1 struct irq_keydesc { int gpio; int irqnum; unsigned char value; char name[10 ]; irqreturn_t (*handler)(int ,void *); }; struct keyinput_dev { dev_t devid; struct cdev cdev ; struct class *class ; struct device *device ; struct device_node *nd ; struct timer_list timer ; struct irq_keydesc irqkeydesc [KEY_NUM ]; unsigned char curkeyname; struct input_dev *inputdev ; }; struct keyinput_dev keyinputdev ;void timer_function (struct timer_list *t) { unsigned char value; unsigned char num; struct irq_keydesc *keydesc ; struct keyinput_dev *dev = from_timer(dev, t, timer); num = dev->curkeyname; keydesc = &dev->irqkeydesc[num]; value = gpio_get_value(keydesc->gpio); if (value == 0 ) { input_report_key(dev->inputdev,keydesc->value, 1 ); input_sync(dev->inputdev); } else { input_report_key(dev->inputdev,keydesc->value, 0 ); input_sync(dev->inputdev); } } static irqreturn_t key0_handler (int irq, void *dev_id) { struct keyinput_dev *dev = (struct keyinput_dev *)dev_id; dev->curkeyname = 0 ; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500 )); return IRQ_RETVAL(IRQ_HANDLED); } static int keyio_init (void ) { unsigned char i = 0 ; char name[10 ]; int ret = 0 ; keyinputdev.nd = of_find_node_by_path("/key" ); if (keyinputdev.nd == NULL ) { printk("key node not found" ); return -EFAULT; } for (i = 0 ;i < KEY_NUM;i ++) { keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd, "key-gpio" , i); if (keyinputdev.irqkeydesc[i].gpio < 0 ) { printk("cant get key%d\r\n" ,i); } } for (i = 0 ; i < KEY_NUM ; i ++) { memset (keyinputdev.irqkeydesc[i].name , 0 , sizeof (name)); sprintf (keyinputdev.irqkeydesc[i].name, "KEY%d" ,i); gpio_request(keyinputdev.irqkeydesc[i].gpio, keyinputdev.irqkeydesc[i].name); gpio_direction_input(keyinputdev.irqkeydesc[i].gpio); keyinputdev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keyinputdev.nd, i); } keyinputdev.irqkeydesc[0 ].handler = key0_handler; keyinputdev.irqkeydesc[0 ].value = KEY_0; for (int i = 0 ;i < KEY_NUM;i++) { ret = request_irq(keyinputdev.irqkeydesc[i].irqnum, keyinputdev.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, keyinputdev.irqkeydesc[i].name, &keyinputdev); if (ret < 0 ) { printk("irq %d request failed\r\n" ,keyinputdev.irqkeydesc[i].irqnum); return -EFAULT; } } timer_setup(&keyinputdev.timer, timer_function, 0 ); keyinputdev.inputdev = input_allocate_device(); keyinputdev.inputdev->name = KEYINPUT_NAME; __set_bit(EV_KEY, keyinputdev.inputdev->evbit); __set_bit(EV_REP, keyinputdev.inputdev->evbit); __set_bit(KEY_0, keyinputdev.inputdev->keybit); ret = input_register_device(keyinputdev.inputdev); if (ret) { printk("register input device failed\r\n" ); return ret; } return 0 ; } static int __init keyinput_init (void ) { keyio_init(); return 0 ; } static void __exit keyinput_exit (void ) { unsigned int i = 0 ; del_timer_sync(&keyinputdev.timer); for (i = 0 ; i < KEY_NUM; i++) { free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev); } for (i = 0 ;i < KEY_NUM; i ++) { gpio_free(keyinputdev.irqkeydesc[i].gpio); } input_unregister_device(keyinputdev.inputdev); input_free_device(keyinputdev.inputdev); return ; } module_init(keyinput_init); module_exit(keyinput_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("hcw" );
编译,装载模块,运行测试程序,完美。