资讯动态

理解Linux input子系统

发布时间:2026/10/16 9:26:55 来源:尧图企业网站定制
输入设备input包括按键、鼠标、键盘、触摸屏本质上还是字符设备。input子系统是管理输入的子系统和pinctrl、gpio子系统一样都是Linux内核针对某一类设备而创建的框架。不同的输入设备所代表的含义不同按键和键盘代表按键信息鼠标和触摸屏代表坐标信息所以在应用层的处理不同驱动开发只需要按照要求上报这些输入信息。input子系统分为input驱动层、input核心层、input事件处理层最终给用户空间提供可访问的设备节点。驱动层输入设备的具体驱动程序如按键驱动程序向内核层报告输入内容。核心层承上启下为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。事件层主要和用户空间进行交互。注册input_devinput核心层文件drivers/input/input.c主要完成注册一个input类注册一个字符设备input子系统的所有设备主设备号都是13编写驱动程序不需要去注册字符设备只需向系统注册一个input_dvice即可。input.c主要代码struct class input_class { .name input .devnode input_devnode, }; static int __init input_init(void) { int err; err class_register(input_class); ... err register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, input“); return 0 }注册一个input类系统启动后就会在/sys/class目录下有一个input子目录。如何注册input_devinput_dev结构体表示input设备结构体定义在include/linux/input.h中。struct input_dev { const char *name; const char *phys; ... unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */ unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */ unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */ ... };申请一个input_dev结构体变量使用如下函数申请struct input_dev *input_allocate_device(void)注销input设备需要使用input_free_device函数来释放void input_free_device(struct input_dev *dev)申请完input_dev以后需要初始化input_dev初始化内容主要是事件类evbit和事件值keybitinput_dev初始化完成后需要向Linux内核注册input_devint input_register_device(struct input_dev *dev)注销input驱动的时候也需要使用input_unregister_device函数来注销input_devvoid input_unregister_device(struct input_dev *dev)input_dev的注册流程1、使用input_allocate_device函数申请一个input_dev;2、初始化input_dev的事件类型及事件值3、使用input_register_device函数向Linux系统注册前面初始化好的input_dev4、卸载input驱动时需要先使用input_unregister_device函数注销掉注册的input_dev然后使用input_free_device函数释放掉前面申请的input_dev。上报输入事件input_event函数用于上报指定的事件以及对应的值void input_event(struct input_dev *dev, /* 需要上报的input_dev */ unsigned int type, /* 上报事件类型比如EV_KEY */ unsigend int code, /* 事件码就是注册的按键值 */ int value) /* 事件值*/Linux内核还有其他具体事件的上报函数比如void input_report_key(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_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子系统上报结束void input_sync(struct input_dev *dev)input_event结构体这个结构体表示所有的输入事件该结构体定义在include/uapi/linux/input.h文件中。struct input_event { struct timeval time; /* 事件发生的时间 */ __u16 type; /* 事件类型比如EV_KEY按键事件 */ __u16 code; /* 事件码事件对应的按键码 */ __s32 value; /* 对应事件类型的值比如EV_KEY事件中value时按键值 */ };有了上面的input输入设备理论基础再参考一下正点原子教程第五十一章的内容就可以自己编写一个按键输入的驱动程序了。驱动完成后运行如下能够在/dev/input/下找到event1但运行./inputApp /dev/input/event1不能显示反映出按键的状态。排除程序发现在void timer_function(unsigned long arg)函数中没有上报输入事件需要加上如下代码:input_report_key(inputdev, KEY_0, 1); input_sync(inputdev);修改后的完整代码如下#include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.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/platform_device.h #include linux/input.h #include asm/mach/map.h #include asm/uaccess.h #include asm/io.h /*************************************************************** Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : leddriver.c 作者 : 左忠凯 版本 : V1.0 描述 : platform驱动 其他 : 无 论坛 : www.openedv.com 日志 : 初版V1.0 2019/8/13 左忠凯创建 ***************************************************************/ #define IMX6UIRQ_CNT 1 #define IMX6UIRQ_NAME imx6uirq #define KEY0VALUE 0X02 #define INVAKEY 0XFF #define KEY_NUM 1 /* 中断IO描述结构体 */ struct irq_keydesc { int gpio; int irqnum; unsigned char value; char name[10]; irqreturn_t (*handler)(int, void *); }; /* imx6uirq设备结构体 */ struct imx6uirq_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ struct device_node *nd; /* 设备节点 */ atomic_t keyvalue; atomic_t releasekey; struct timer_list timer; /* 对应一个定时器实例 */ struct irq_keydesc irqkeydesc[KEY_NUM]; unsigned char curkeynum; }; struct imx6uirq_dev imx6uirq; /* key设备 */ struct input_dev *inputdev; static irqreturn_t key0_handler(int irq, void *dev_id) { struct imx6uirq_dev *dev (struct imx6uirq_dev *)dev_id; dev-timer.data (volatile long)dev_id; mod_timer(dev-timer, jiffies msecs_to_jiffies(10)); return IRQ_RETVAL(IRQ_HANDLED); } void timer_function(unsigned long arg) { unsigned char value; struct irq_keydesc *keydesc; unsigned char num; struct imx6uirq_dev *dev (struct imx6uirq_dev *)arg; num dev-curkeynum; keydesc dev-irqkeydesc[num]; value gpio_get_value(keydesc-gpio); if (value 0) { input_report_key(inputdev, KEY_0, 1); input_sync(inputdev); } else { input_report_key(inputdev, KEY_0, 0); input_sync(inputdev); } } static int keyio_init(void) { unsigned char i 0; int ret 0; imx6uirq.nd of_find_node_by_path(/key); if (imx6uirq.nd NULL) { printk(key node can not found!\r\n); return -EINVAL; } else { printk(key node has been found!\r\n); } for (i 0; i KEY_NUM; i) { imx6uirq.irqkeydesc[i].gpio of_get_named_gpio(imx6uirq.nd, key-gpio, 0); if (imx6uirq.irqkeydesc[i].gpio 0) { printk(cant get key-gpio); return -EINVAL; } else { printk(key-gpio num %d\r\n, imx6uirq.irqkeydesc[i].gpio); } } for (i 0; i KEY_NUM; i) { memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); sprintf(imx6uirq.irqkeydesc[i].name, KEY%d, i); gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name); gpio_direction_input(imx6uirq.irqkeydesc[i].gpio); #if 0 imx6uirq.irqkeydesc[i].irqnum irq_of_parse_and_map(imx6uirq.nd, i); #endif #if 1 imx6uirq.irqkeydesc[i].irqnum gpio_to_irq(imx6uirq.irqkeydesc[i].gpio); #endif printk(key%d:gpio%d, irqnum%d\r\n, i, imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].irqnum); } imx6uirq.irqkeydesc[0].handler key0_handler; imx6uirq.irqkeydesc[0].value KEY0VALUE; for (i 0; i KEY_NUM; i) { ret request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, imx6uirq); if (ret 0) { printk(irq %d request failed!\r\n,imx6uirq.irqkeydesc[i].irqnum); return -EFAULT; } } init_timer(imx6uirq.timer); imx6uirq.timer.function timer_function; return 0; } /* * description : 打开设备 * param - inode : 传递给驱动的inode * param - filp : 设备文件file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * return : 0 成功;其他 失败 */ static int imx6uirq_open(struct inode *inode, struct file *filp) { filp-private_data imx6uirq; /* 设置私有数据 */ return 0; } /* * description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符 * param - buf : 要写给设备写入的数据 * param - cnt : 要写入的数据长度 * param - offt : 相对于文件首地址的偏移 * return : 写入的字节数如果为负值表示写入失败 */ static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret 0; unsigned char keyvalue 0; unsigned char releasekey 0; struct imx6uirq_dev *dev (struct imx6uirq_dev *)filp-private_data; keyvalue atomic_read(dev-keyvalue); releasekey atomic_read(dev-releasekey); if (releasekey) { if (keyvalue 0x80) { keyvalue ~0x80; ret copy_to_user(buf, keyvalue, sizeof(keyvalue)); } else { goto data_error; } atomic_set(dev-releasekey, 0); } else { goto data_error; } return 0; data_error: return -EINVAL; } /* 设备操作函数 */ static struct file_operations imx6uirq_fops { .owner THIS_MODULE, .open imx6uirq_open, .read imx6uirq_read, }; /* * description : 驱动模块加载函数 * param : 无 * return : 无 */ static int __init inputdriver_init(void) { inputdev input_allocate_device(); inputdev-name test_inputdev; __set_bit(EV_KEY, inputdev-evbit); __set_bit(EV_REP, inputdev-evbit); __set_bit(KEY_0, inputdev-keybit); input_register_device(inputdev); keyio_init(); return 0; } /* * description : 驱动模块卸载函数 * param : 无 * return : 无 */ static void __exit inputdriver_exit(void) { input_unregister_device(inputdev); input_free_device(inputdev); } module_init(inputdriver_init); module_exit(inputdriver_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(bobluo);应用程序按照教程来的。#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 /*************************************************************** Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : ledApp.c 作者 : 左忠凯 版本 : V1.0 描述 : platform驱动驱测试APP。 其他 : 无 使用方法 ./ledApp /dev/platled 0 关闭LED ./ledApp /dev/platled 1 打开LED 论坛 : www.openedv.com 日志 : 初版V1.0 2019/8/16 左忠凯创建 ***************************************************************/ static struct input_event inputevent; /* * description : main主程序 * param - argc : argv数组元素个数 * param - argv : 具体参数 * return : 0 成功;其他 失败 */ int main(int argc, char *argv[]) { int fd, retvalue; char *filename; unsigned char keyvalue; if(argc ! 2){ printf(Error Usage!\r\n); return -1; } filename argv[1]; /* 打开key驱动 */ fd open(filename, O_RDWR); if(fd 0){ printf(file %s open failed!\r\n, argv[1]); return -1; } while (1) { retvalue read(fd, inputevent, sizeof(inputevent)); if (retvalue 0) { printf(read data fail !\r\n); } else { switch(inputevent.type ) { case EV_KEY: if (inputevent.code BTN_MISC) { printf(key %d %s\r\n, inputevent.code, inputevent.value?press:release); } else { printf(button %d %s\r\n, inputevent.code, inputevent.value?press:release); } break; case EV_REL: break; case EV_ABS: break; case EV_MSC: break; case EV_SW: break; } } } retvalue close(fd); /* 关闭文件 */ if(retvalue 0){ printf(file %s close failed!\r\n, argv[1]); return -1; } return 0; }重新编译完成后运行按下开发板上的按键可以显示key 11 press或key 11 release了。在Linux内核中KEY0的按键值是11可以在include/uapi/linux/input.h文件中查到。直接使用hexdump命令查看/dev/input/event1文件内容参考资料1.I.MX6Uq嵌入式Linux驱动开发指南V2.0.1正点原子2. Linux设备驱动开发 详解基于最新的Linux 4.0内核

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑