linux input子系统
为什么要用INPUT子系统
在不采用input子系统,而是自己实现的按键字符驱动中,会自己注册驱动,提供file_operations接口,并在读接口中,读取按键的电平值上传给应用。在linux系统中(linux4.9.88),构建了input子系统,所有采用input子系统的设备,在有输入事件后都会主动上报输入事件。
在输入设备中会有以下几个问题:
a. 何时上报?是在输入设备输入事件中断产生时上报。
b. 如何上报?输入设备在中断函数中调用input提供的input_report_key函数。
INPUT子系统初始化
input.c
subsys_initcall(input_init);
module_exit(input_exit);
由上可知,input子系统作为一个模块,在kernel启动的时候就运行了 input_init()
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; } ...... }
先注册一个 input 类型的 class
static int __init input_proc_init(void) { struct proc_dir_entry *entry; proc_bus_input_dir = proc_mkdir("bus/input", NULL); if (!proc_bus_input_dir) return -ENOMEM; entry = proc_create("devices", 0, proc_bus_input_dir, &input_devices_proc_ops); if (!entry) goto fail1; entry = proc_create("handlers", 0, proc_bus_input_dir, &input_handlers_proc_ops); ...... }
然后在 /proc/input 下创建两个文件,用于向应用层提供 input 类型的设备信息
最后申请了主设备号 INPUT_MAJOR,给 INPUT 类型设备使用。
evdev.c
evdev作为一个模块,在kernel启动的时候执行
module_init(evdev_init);
module_exit(evdev_exit);
evdev_init()为链表 input_handler_list 填充了一个条目evdev_handler,给 input_register_device() 使用,input_register_device() --->...--->evdev_connect() 自动创建字符设备
static struct input_handler evdev_handler = { .event = evdev_event, .events = evdev_events, .connect = evdev_connect, .disconnect = evdev_disconnect, .legacy_minors = true, .minor = EVDEV_MINOR_BASE, .name = "evdev", .id_table = evdev_ids, };
用户层
应用层代码会通过如下结构体读取事件信息
struct input_event { struct timeval time; unsigned short type; unsigned short code; unsigned int value; };
tv_sec / tv_usec:事件产生的时间
type:事件类型,如果是按键就选 EV_KEY,如下图:
#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)
EV_CNT 决定了事件类型的最大数量
code:哪个设备产生的事件,对于键盘按键,code 的取值小于 BTN_MISC,每一个code值对应键盘的一个按键,如下图:
value:对于按键,可以设置产生事件value等于1、没产生事件value等于0
应用代码实例:
static struct input_event inputevent; err = read(fd, &inputevent, sizeof(inputevent));
驱动层
注册 input_dev 实例到内核
input 设备类型结构体
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)]; // 1比特对应一个事件类型,EV_CNT为32,即最大32个事件类型,只需要一个 unsigned long 类型变量,所以 BITS_TO_LONGS(EV_CNT) == 1
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
......
ktime_t timestamp[INPUT_CLK_MAX];
bool inhibited;
};
1、动态申请了一个 struct input_dev 类型变量dev,并初始化dev->dev.kobj.name = "input0",这个就是设备节点的名字;dev->dev的class name为 "input"
struct input_dev *input_allocate_device(void);
2、初始化 input_dev 的事件类型和事件值
__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */ __set_bit(EV_REP, inputdev->evbit); /* 重复事件 */ __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
3、向内核注册输入设备
int __must_check input_register_device(struct input_dev *);
从 input_register_device()--->device_add() 可知,如果 input_dev.dev没有设置parent,/sys/devices/virtual/input 作为 input_dev.dev的parent。如果 input_dev.dev 没有设置 init_name,inpu_dev.dev的名字默认为 inputX(X表示数字,每添加一个,数字加1)
上报输入事件
当我们向 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);
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_sync 函数来告诉 Linux 内核 input 子系统上报结束,input_sync 函数本质是上报一个同步事件,此函数原型如下所示:
void input_sync(struct input_dev *dev)
相关文章
- Linux crontab 命令格式与详细例子
- Linux查看用户密码修改时间
- linux - mysql:注意事项
- [linux]Linux下的log
- linux常用命令
- 转摘--如何利用多核CPU来加速你的Linux命令 — awk, sed, bzip2, grep, wc等
- 【Linux基础】VI替换命令详解
- Linux基础之linux常用命令之文本替换
- linux shell 脚本攻略学习17--正则表达式入门
- 【学习总结】快速上手Linux玩转典型应用-第2章-linux简介
- Linux开机自动启动ORACLE设置
- Linux系统调优详解(七)——网络状态查看命令nethogs
- Linux文件类型与文件权限详解(三)
- 【Linux 内核】调度器 ② ( sched_class 调度类结构体源码 | 源码路径 linux-5.6.18kernelschedsched.h )
- Linux修改SSH连接数 重启SSH服务
- 【Linux】linux经常使用基本命令
- L56.linux命令每日一练 -- 第八章 Linux磁盘与文件系统管理命令 -- swapoff和sync
- L50.linux命令每日一练 -- 第八章 Linux磁盘与文件系统管理命令 -- tune2fs和parted
- L47.linux命令每日一练 -- 第七章 Linux用户管理及用户信息查询命令 -- users和whoami
- L44.linux命令每日一练 -- 第七章 Linux用户管理及用户信息查询命令 -- su和visudo
- L43.linux命令每日一练 -- 第七章 Linux用户管理及用户信息查询命令 -- chage和chpasswd
- 随笔:Linux下查看声卡基本信息
- 嵌入式linux开发,启用用户登录功能
- 02 从头开始atac项目 ubuntu20 install r4.2 Linux系统环境配置 服务器版本的rstudio r install in linux /ubuntu/centos
- 周立功arm linux教程,极速搭建周立功IMX287A ARM Linux开发环境
- Linux复习资料(二)、Linux基本操作
- Linux好用命令之curl劫持请求的三种方式
- 【看表情包学Linux】环境安装 | 安装XShell | 基本的账号管理