1、input子系统框架介绍
2、编写按键驱动程序,通过input子系统将按键信息上发到应用层
1、input子系统框架介绍
input子系统是内核专门针对输入类设备实现的管理框架,input子系统中已经事先定义了各类设备可能产生的各类事件,比如针对鼠标类设备,input子系统定义了左键按下、右键按下、移动等事件;驱动程序通过input子系统提供的专门接口将这些事件传递到input子系统中,input子系统将事件封装成一个结构体(struct input_event)放在一个缓存中;应用程序就可以从input子系统提供的设备文件中读取到事件结构体,根据结构体字段就可以解析出这是个什么事件。
input子系统大致框架如上图所示,上图大致描述了
1、input子系统大致由哪几部分组成
2、具体设备驱动程序怎么注册到input子系统
3、红色部分画出了硬件事件是怎么通过input子系统传到用户空间的
函数功能说明 |
input_allocate_device 申请struct input_dev结构体 |
input_set_capability 申明设备会产生哪些事件,比如,鼠标左键按下 |
input_register_device 添加input_dev到input核心层,并且会把这个input_dev和输入事件层注册的各个Handler进行匹配绑定,从而接通应用层 |
input_report_key 将事件传递给input子系统 |
如上图所示,input子系统大致可以分为输入设备驱动层、输入核心层以及输入事件驱动层;
输入核心层是由内核开发者编写的,主要提供各类数据结构管理的功能;
输入事件驱动层是和用户空间交互的层,该层会调用input_register_handler向input子系统注册一些句柄,这些句柄专门用于关联某一类设备。比如,Mouse Handler句柄,这个句柄可以处理鼠标产生的事件;Keyboard Handler句柄,这个句柄可以处理键盘产生的事件;Event Handler句柄,这是个特殊通用的句柄,可以处理所有输入类设备产生的事件。
设备驱动程序调用input子系统提供的三个接口函数input_allocate_device、input_set_capability和input_register_device将自己关联注册到input子系统中,调用这三个函数后,会在input子系统中创建一个input_dev结构体表示这个设备,然后,input子系统会把这个input_dev结构体和输入事件驱动层注册的一个或多个句柄进行匹配绑定(当然,有匹配条件),和输入事件层的某个句柄绑定成功后,就会创建一个设备文件,以后设备产生事件后,就会传递到这个绑定的句柄,然后被组织成input_event结构体放在某个缓存,应用程序就可以通过对应的设备文件读取缓存中的input_event结构体数据。比如,如图所示,按键驱动程序注册到input子系统后,就会创建key:input_dev,这个结构体会和Event Handler句柄匹配绑定,从而创建设备文件/dev/input/eventxxx,按键按下后,应用程序就可以从设备文件/dev/input/eventxxx读取到按键信息;再比如,鼠标驱动程序,注册到input子系统后,就会创建Mouse:input_dev,这个结构体会和两个句柄(Event Handler和Mouse Handler)匹配绑定,那么就会创建两个设备文件/dev/input/eventxxx和/dev/input/mousexxx,鼠标产生事件后,应用程序从这两个设备文件都能读取到鼠标事件信息。
综上所述,要将一个按键驱动程序关联到input子系统,我们只需要做如下工作:
1、调用input子系统提供的输入设备驱动层的3个注册接口函数,按键设备驱动注册到input子系统,input子系统会自动生成设备文件/dev/input/eventxxx
2、在按键中断函数中,调用input_report_key函数将按键产生的事件传递到input子系统
3、应用程序直接从/dev/input/eventxxx设备文件读取按键产生的事件
如下为示例程序:
程序采用平台总线的框架,按键信息在设备树中描述,在probe函数中完成中断注册后,调用devm_input_allocate_device、input_set_capability和input_register_device三个函数将驱动注册到input子系统
在中断函数irq_test_irq_isr中调用input_report_key将按键事件传递到input子系统
示例驱动程序 linux内核版本:4.14.2 |
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/slab.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> #include <linux/gpio_keys.h> #include <linux/workqueue.h> #include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/spinlock.h> #include <linux/input.h> static struct my_gpio_keys_button *button; static int flag; static struct input_dev *key_input; struct my_gpio_keys_button { unsigned int code; int gpio; int active_low; const char *desc; unsigned int type; int wakeup; int debounce_interval; bool can_disable; int value; unsigned int irq; struct gpio_desc *gpiod; }; static char *label[2]; static irqreturn_t irq_test_irq_isr(int irq, void *dev_id) { printk(KERN_INFO "get irq --> irq_test_irq_isr. "); flag = gpiod_get_value(button->gpiod); if (flag) { input_report_key(key_input, KEY_HOME, 1); } else { input_report_key(key_input, KEY_HOME, 0); } input_sync(key_input); return IRQ_HANDLED; } static int key_input_probe(struct platform_device *pdev) { /* 获取节点信息,注册中断 */ struct device *dev = &pdev->dev; struct fwnode_handle *child = NULL; int nbuttons; int irq, error; irq_handler_t isr; unsigned long irqflags; nbuttons = device_get_child_node_count(dev); if (nbuttons == 0) { printk(KERN_INFO "no child exist, return "); return ERR_PTR(-ENODEV); } printk(KERN_INFO "child num is %d. ", nbuttons); button = devm_kzalloc(dev, sizeof(struct my_gpio_keys_button) * nbuttons, GFP_KERNEL); /* 获取lable参数,父节点没有lable属性 */ device_property_read_string(dev, "label", label[0]); printk(KERN_INFO "parent lable %s ", label[0]); /* 扫描处理每个子节点 */ device_for_each_child_node(dev, child) { /* 获取虚拟中断号virq ??? */ if (is_of_node(child)) { button->irq = irq_of_parse_and_map(to_of_node(child), 0); printk(KERN_INFO "get irq from irq_of_parse_and_map successful "); } fwnode_property_read_string(child, "label", &button->desc); /* 获取gpio描述符gpiod */ button->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child, GPIOD_IN, button->desc); if (IS_ERR(button->gpiod)) { printk(KERN_INFO "get gpiod failed, return. "); return -ENOENT; } /* 检查虚拟中断号,可不使用 */ if (!button->irq) { irq = gpiod_to_irq(button->gpiod); if (irq < 0) { error = irq; dev_err(dev, "Unable to get irq number for GPIO %d, error %d ", button->gpio, error); return error; } button->irq = irq; } printk(KERN_INFO "get virq %d for key. ", button->irq); isr = irq_test_irq_isr; irqflags = 0; irqflags |= IRQF_SHARED; /* 设置引脚为输入模式 */ gpiod_set_value(button->gpiod, 1); gpiod_direction_input(button->gpiod);
/* 注册中断 */ /* 最后一个参数是传给中断函数的参数 */ error = devm_request_any_context_irq(dev, button->irq, isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, button->desc, NULL); if (error < 0) { dev_err(dev, "Unable to claim irq %d; error %d ", button->irq, error); return error; } } /* input子系统相关初始化 */ key_input = devm_input_allocate_device(dev); if (!key_input) { dev_err(dev, "failed to allocate key_input device "); return -ENOMEM; } /* 添加input子系统响应的事件 */ input_set_capability(key_input, EV_KEY, KEY_HOME); /* 注册input子系统 */ error = input_register_device(key_input); if (error) { dev_err(dev, "Unable to register key_input device, error: %d ", error); return error; } return 0; } static const struct of_device_id key_input_of_match[] = { { .compatible = "irq-keys", }, { }, }; MODULE_DEVICE_TABLE(of, key_input_of_match); static struct platform_driver key_input_device_driver = { .probe = key_input_probe, .driver = { .name = "irqtest_keys", .of_match_table = key_input_of_match, } }; static int __init key_input_init(void) { return platform_driver_register(&key_input_device_driver); } static void __exit key_input_exit(void) { platform_driver_unregister(&key_input_device_driver); } module_init(key_input_init); module_exit(key_input_exit); MODULE_LICENSE("GPL"); |
示例应用程序 |
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h> #include <string.h> #define DEVICE_KEY "/dev/input/event0" #define DEVICE_MOUSE "/dev/input/event0" int main(void) { int fd = -1; int ret = -1; int i; struct input_event ev; char *p;
// 第一步:打开设备文件 fd = open(DEVICE_KEY, O_RDONLY); if (fd < 0) { perror("open"); return -1; }
while (1) { // 第二步:读取一个event事件包 memset(&ev, 0, sizeof(ev)); ret = read(fd, &ev, sizeof(ev)); if (ret != sizeof(ev)) { perror("read"); close(fd); return -1; }
// 第三步:解析event包,才知道发生了什么样的输入事件 printf("--------------------- "); printf("type: %hd ", ev.type); printf("code: %hd ", ev.code); printf("value: %d ", ev.value); printf(" "); }
// 第四步:关闭设备 close(fd); } |
参考资料
朱有鹏老师驱动视频教程:http://t.elecfans.com/c278.html