Linux驱动(input子系统)
input子系统
- input 子系统简介
- input 驱动编写流程
- 注册 input_dev
- 上报输入事件
- input_event 结构体
- 案例
按键、鼠标、键盘、触摸屏等都属于输入(input)设备, Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了 input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息, input 核心层负责处理这些事件
input 子系统简介
input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点, input 子系统框架如图所示
左边就是最底层的具体设备,比如按键、 USB 键盘/鼠标等,中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理
事件层:主要和用户空间进行交互
input 驱动编写流程
input 核心层会向 Linux 内核注册一个字符设备,大家找到 drivers/input/input.c 这个文件,input.c 就是 input 输入子系统的核心层
struct class input_class = {.name = "input",.devnode = input_devnode,
};
............................................................
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 子目录
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, “input”);注册一个字符设备,主设备号为 INPUT_MAJOR, INPUT_MAJOR 定义在include/uapi/linux/major.h 文件中
#define INPUT_MAJOR 13
因此, input 子系统的所有设备主设备号都为 13,我们在使用 input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个 input_device 即可
注册 input_dev
在使用 input 子系统的时候我们只需要注册一个 input 设备即可, input_dev 结构体表示 input设备,此结构体定义在 include/linux/input.h 文件中
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)]; /*LED 相关的位图 */unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; /* sound 有关的位*/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];
};
evbit 表示输入事件类型,可选的事件类型定义在 include/uapi/linux/input.h 文件中
#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 /* LED */
#define EV_SND 0x12 /* sound(声音) */
#define EV_REP 0x14 /* 重复事件 */
#define EV_FF 0x15 /* 压力事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 压力状态事件 */
比如我们要使用到按键,那么就需要注册 EV_KEY 事件,如果要使用连按功能的话还需要注册 EV_REP 事件
evbit、 keybit、 relbit 等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到 keybit, keybit 就是按键事件使用的位图, Linux 内核定义了很多按键值,这些按键值定义在include/uapi/linux/input-event-codes.h 文件中,由于内容太多只展示部分,可以自行在内核文件查看
#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
.....................................................................
我们可以将开发板上的按键值设置为上述代码中的任意一个,比如将 STM32MP1 开发板上的 KEY0 按键值设置为 KEY_0
在编写 input 设备驱动的时候我们需要先申请一个 input_dev 结构体变量,使用input_allocate_device 函数来申请一个 input_dev
struct input_dev *input_allocate_device(void)
参数:无
返回值: 申请到的 input_dev
如果要注销的 input 设备的话需要使用 input_free_device 函数来释放掉前面申请到的input_dev
void input_free_device(struct input_dev *dev)
dev:需要释放的 input_dev
返回值: 无
申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。 input_dev 初始化完成以后就需要向 Linux 内核注册 input_dev了,需要用到 input_register_device 函数
int input_register_device(struct input_dev *dev)
dev:要注册的 input_dev
返回值: 0, input_dev 注册成功;负值, input_dev 注册失败
同样的,注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册的 input_dev
void input_unregister_device(struct input_dev *dev)
dev:要注销的 input_dev
返回值: 无
综上所述, input_dev 注册过程如下
1.使用 input_allocate_device 函数申请一个 input_dev
2.初始化 input_dev 的事件类型以及事件值
3.使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev
4.卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,然后使用 input_free_device 函数释放掉前面申请的 input_dev
input_dev 注册过程示例代码如下所示:
struct input_dev *inputdev; /* input 结构体变量 *//* 驱动入口函数 */
static int __init xxx_init(void)
{...inputdev = input_allocate_device(); /* 申请 input_dev */inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 *//********* 第一种设置事件和事件值的方法 ***********/__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */__set_bit(KEY_0, inputdev->keybit); /* 设置产生哪些按键值 *//************************************************//********* 第二种设置事件和事件值的方法 ***********/keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);/************************************************//********* 第三种设置事件和事件值的方法 ***********/keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);/************************************************//* 注册 input_dev */input_register_device(inputdev);...return 0;
}/* 驱动出口函数 */
static void __exit xxx_exit(void)
{input_unregister_device(inputdev); /* 注销 input_dev */input_free_device(inputdev); /* 删除 input_dev */
}
设置 input 设备事件和按键值,这里用了三种方法来设置事件和按键值,调用 input_register_device 函数向 Linux内核注册 inputdev
上报输入事件
向 Linux 内核注册好 input_dev 以后还不能高枕无忧的使用 input 设备, input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,我们需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给 Linux 内核。比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同,我们依次来看一下一些常用的事件上报 API 函数。
首先是 input_event 函数,此函数用于上报指定的事件以及对应的值
void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value)
dev:需要上报的 input_dev
type: 上报的事件类型,比如 EV_KEY
code: 事件码,也就是我们注册的按键值,比如 KEY_0、 KEY_1 等等
value:事件值,比如 1 表示按键按下, 0 表示按键松开
返回值: 无
input_event 函数可以上报所有的事件类型和事件值, Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了 input_event 函数。比如上报按键所使用的input_report_key 函数
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 函数
同样的还有一些其他的事件上报函数
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
返回值: 无
综上所述,按键的上报事件的参考代码如下所示
/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{unsigned char value;/* 读取按键IO值 */value = gpio_get_value(keydesc->gpio);/* 根据当前电平状态上报按键事件 */if (value == 0) {/* 按键按下:上报KEY_0按下事件 */input_report_key(inputdev, KEY_0, 1);input_sync(inputdev);} else {/* 按键松开:上报KEY_0松开事件 */input_report_key(inputdev, KEY_0, 0);input_sync(inputdev);}
}
如果按键值为 0 那么表示按键被按下了,如果按键按下的话就要使用input_report_key 函数向 Linux 系统上报按键值,比如向 Linux 系统通知 KEY_0 这个按键按下了
如果按键值为 1 的话就表示按键没有按下,是松开的。向 Linux 系统通知KEY_0 这个按键没有按下或松开了
input_event 结构体
Linux 内核使用 input_event 这个结构体来表示所有的输入事件, input_envent 结构体定义在include/uapi/linux/input.h 文件中
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;
};
我们依次来看一下 input_event 结构体中的各个成员变量:
time:时间,也就是此事件发生的时间,为 timeval 结构体类型
typedef long __kernel_long_t;
typedef __kernel_long_t __kernel_time_t;
typedef __kernel_long_t __kernel_suseconds_t;struct timeval {__kernel_time_t tv_sec; /* 秒 */__kernel_suseconds_t tv_usec; /* 微秒 */
};
tv_sec 和 tv_usec 这两个成员变量都为 long 类型,也就是 32位
type: 事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位
code: 事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如: KEY_0、 KEY_1等等这些按键。此成员变量为 16 位
value: 值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了
input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值,比如按键值等
案例
驱动程序
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>#define KEYINPUT_NAME "keyinput" /* 名字 *//* key 设备结构体 */
struct key_dev{struct input_dev *idev; /* 按键对应的 input_dev 指针*/struct timer_list timer; /* 消抖定时器 */int gpio_key; /* 按键对应的 GPIO 编号 */int irq_key; /* 按键对应的中断号 */
};static struct key_dev key; /* 按键设备 *//** @description : 按键中断服务函数* @param – irq : 触发该中断事件对应的中断号* @param – arg : arg参数可以在申请中断的时候进行配置* @return : 中断执行结果*/
static irqreturn_t key_interrupt(int irq, void *dev_id)
{if(key.irq_key != irq)return IRQ_NONE;/* 按键防抖处理,开启定时器延时15ms */disable_irq_nosync(irq); /* 禁止按键中断 */mod_timer(&key.timer, jiffies + msecs_to_jiffies(15));return IRQ_HANDLED;
}/** @description : 按键初始化函数* @param – nd : device_node设备指针* @return : 成功返回0,失败返回负数*/
static int key_gpio_init(struct device_node *nd)
{int ret;unsigned long irq_flags;/* 从设备树中获取GPIO */key.gpio_key = of_get_named_gpio(nd, "key-gpio", 0);if(!gpio_is_valid(key.gpio_key)) {printk("key:Failed to get key-gpio\n");return -EINVAL;}/* 申请使用GPIO */ret = gpio_request(key.gpio_key, "KEY0");if (ret) {printk(KERN_ERR "key: Failed to request key-gpio\n");return ret;} /* 将GPIO设置为输入模式 */gpio_direction_input(key.gpio_key);/* 获取GPIO对应的中断号 */key.irq_key = irq_of_parse_and_map(nd, 0);if(!key.irq_key){return -EINVAL;}/* 获取设备树中指定的中断触发类型 */irq_flags = irq_get_trigger_type(key.irq_key);if (IRQF_TRIGGER_NONE == irq_flags)irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;/* 申请中断 */ret = request_irq(key.irq_key, key_interrupt, irq_flags, "Key0_IRQ", NULL);if (ret) {gpio_free(key.gpio_key);return ret;}return 0;
}/** @description : 定时器服务函数,用于按键消抖,定时时间到了以后* 再读取按键值,根据按键的状态上报相应的事件* @param – arg : arg参数就是定时器的结构体* @return : 无*/
static void key_timer_function(struct timer_list *arg)
{int val;/* 读取按键值并上报按键事件 */val = gpio_get_value(key.gpio_key);input_report_key(key.idev, KEY_0, !val);input_sync(key.idev);enable_irq(key.irq_key);
}/** @description : platform驱动的probe函数,当驱动与设备* 匹配成功以后此函数会被执行* @param – pdev : platform设备指针* @return : 0,成功;其他负值,失败*/
static int atk_key_probe(struct platform_device *pdev)
{int ret;/* 初始化GPIO */ret = key_gpio_init(pdev->dev.of_node);if(ret < 0)return ret;/* 初始化定时器 */timer_setup(&key.timer, key_timer_function, 0);/* 申请input_dev */key.idev = input_allocate_device();key.idev->name = KEYINPUT_NAME;#if 0/* 初始化input_dev,设置产生哪些事件 */__set_bit(EV_KEY, key.idev->evbit); /* 设置产生按键事件 */__set_bit(EV_REP, key.idev->evbit); /* 重复事件,比如按下去不放开,就会一直输出信息 *//* 初始化input_dev,设置产生哪些按键 */__set_bit(KEY_0, key.idev->keybit);
#endif#if 0key.idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);key.idev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endifkey.idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);input_set_capability(key.idev, EV_KEY, KEY_0);/* 注册输入设备 */ret = input_register_device(key.idev);if (ret) {printk("register input device failed!\r\n");goto free_gpio;}return 0;
free_gpio:free_irq(key.irq_key,NULL);gpio_free(key.gpio_key);del_timer_sync(&key.timer);return -EIO;}/** @description : platform驱动的remove函数,当platform驱动模块* 卸载时此函数会被执行* @param – dev : platform设备指针* @return : 0,成功;其他负值,失败*/
static int atk_key_remove(struct platform_device *pdev)
{free_irq(key.irq_key,NULL); /* 释放中断号 */gpio_free(key.gpio_key); /* 释放GPIO */del_timer_sync(&key.timer); /* 删除timer */input_unregister_device(key.idev); /* 释放input_dev */return 0;
}static const struct of_device_id key_of_match[] = {{.compatible = "alientek,key"},{/* Sentinel */}
};static struct platform_driver atk_key_driver = {.driver = {.name = "stm32mp1-key",.of_match_table = key_of_match,},.probe = atk_key_probe,.remove = atk_key_remove,
};module_platform_driver(atk_key_driver);MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
应用程序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/input.h>/** @description : main主程序* @param – argc : argv数组元素个数* @param – argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, ret;struct input_event ev;if(2 != argc) {printf("Usage:\n""\t./keyinputApp /dev/input/eventX @ Open Key\n");return -1;}/* 打开设备 */fd = open(argv[1], O_RDWR);if(0 > fd) {printf("Error: file %s open failed!\r\n", argv[1]);return -1;}/* 读取按键数据 */for ( ; ; ) {ret = read(fd, &ev, sizeof(struct input_event));if (ret) {switch (ev.type) {case EV_KEY: // 按键事件if (KEY_0 == ev.code) { // 判断是不是KEY_0按键if (ev.value) // 按键按下printf("Key0 Press\n");else // 按键松开printf("Key0 Release\n");}break;/* 其他类型的事件,自行处理 */case EV_REL:break;case EV_ABS:break;case EV_MSC:break;case EV_SW:break;};}else {printf("Error: file %s read failed!\r\n", argv[1]);goto out;}}out:/* 关闭设备 */close(fd);return 0;
}
Linux 内核会使用 input_event 结构体来表示输入事件,所以我们要获取按键输入信息,那么必须借助于 input_event 结构体,上面定义了一个 input_event类型变量 ev
当我们向 Linux 内核成功注册 input_dev 设备以后,会在/dev/input 目录下生成一个名为“eventX(X=0….n)”的文件,这个/dev/input/eventX 就是对应的 input 设备文件。我们读取这个文件就可以获取到输入事件信息,比如按键值什么的,使用 read 函数读取输入设备,文件,也就是/dev/input/eventX,读取到的数据按照 input_event 结构体组织起来。获取到输入事件以后(input_event 结构体类型)使用 switch case 语句来判断事件类型,例程中我们设置的事件类型为 EV_KEY,因此只需要处理 EV_KEY 事件即可