INPUT子系统
INPUT子系统是什么?
输入子系统是一个包含硬件驱动、核心中转和事件处理的完整体系。其中,input_dev 是该体系中定义的逻辑设备对象。input_dev 本身并不具备主动检测物理电平的能力 ,它必须依赖于同属于输入子系统的底层硬件驱动程序 (如 GPIO 驱动的中断里、I2C 驱动等)去捕获硬件状态,并调用子系统接口将事件'同步'给它。input_dev 的作用是作为硬件驱动与用户空间之间的一座标准化桥梁。
输入子系统框架

- INPUT子系统管理输入设备的注册、事件的生成和处理 ,并将这些事件传递给用户空间的应用程序或内核中的其他模块。
- INPUT子系统是设备和驱动中的一个中间层,它统一了接口 。这个中间层被细分为三层:
- 驱动层 (Input Driver):负责与硬件通信,并将输入事件转化为标准的 Input 事件。(如在按键按下触发的中断程序中,上报这个input事件)
- 核心层 (Input Core):它负责管理设备注册,并匹配驱动和处理程序。
- 事件处理层 (Event Handler) :负责把事件传给用户,比如
evdev(通用事件驱动,即你看到的/dev/input/eventX)、mousedev、joydev等。- 查看/dev/input/eventX的方法
- Linux中:`hexdump /dev/input/eventX
- Hexadecimal Dump:
- Dump的原意是"倾倒"
- 它的作用是将二进制文件或数据流以十六进制的形式显示出来。因为二进制的0,1人类很难阅读。
- Hexadecimal Dump:
- 安卓中:
getevent -l /dev/input/eventX- Get Event
-l:帮你将16进制数转化为,具体的事件类型,事件码,事件值。
- Linux中:`hexdump /dev/input/eventX
- 查看/dev/input/eventX的方法
input_event
c
include/linux/input.h
struct input_dev {
const char *name; // 设备名称字符串
const char *phys; // 物理路径字符串
const char *uniq; // 唯一标识字符串
struct input_id 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)]; // 声音位图数组
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; // 力反馈位图数组
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; // 开关位图数组
struct input_absinfo *absinfo; // 绝对轴信息数组指针
unsigned long key[BITS_TO_LONGS(KEY_CNT)]; // 按键状态位图
unsigned long led[BITS_TO_LONGS(LED_CNT)]; // LED状态位图
unsigned long snd[BITS_TO_LONGS(SND_CNT)]; // 声音状态位图
unsigned long sw[BITS_TO_LONGS(SW_CNT)]; // 开关状态位图
............
}
输入事件,被分为如下三层
在文件include/uapi/linux/input-event-codes.h中被定义
-
事件类型(Event Types) :描述输入事件的类别,例如键盘事件、鼠标事件或触摸屏事件。对应结构体中evbit 。
- 常见的事件类型有:
EV_SYN: 同步事件,所有的input系统都需要同步事件。没有 EV_SYN,用户空间 不知道一帧事件什么时候结束。EV_KEY:按键事件,如键盘上的按键。EV_REL:相对位置事件,如鼠标移动。EV_ABS:绝对位置事件,如触摸屏触摸。
#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 / 压力状态事件 */ - 常见的事件类型有:
-
事件码(Event Codes) :描述具体的输入事件,例如键盘上的哪个键被按下或鼠标的哪个按钮被按下。对应结构体中
keybit,relbit,absbit- 例如:
KEY_A:键盘上的 A 键。BTN_LEFT:鼠标的左键。ABS_X:触摸屏的 X 轴位置。
- 例如:
-
事件值(Event Values) :描述输入事件的具体值。
- 对应结构体中:
struct input_absinfo *absinfo;(针对绝对位移值)unsigned long key[...],led[...](针对当前状态值)- 例如:
- 对于按键事件,值为 1 表示按下,0 表示松开。
- 对于相对位置事件,值表示移动的距离。
- 对于绝对位置事件,值表示触摸的位置。
PROR(设备属性与特殊行为)
GPIO Key / 普通按键设备通常不需要设置PROP,其默认会为0x00。
c
/*
* 设备属性与特殊行为
*/
#define INPUT_PROP_POINTER 0x00 /* 指针类设备,需要通过指针进行操作 */
#define INPUT_PROP_DIRECT 0x01 /* 直接输入设备,输入位置与显示位置直接对应(如触摸屏) */
#define INPUT_PROP_BUTTONPAD 0x02 /* 触控板下方带有按键的设备(如 ClickPad) */
#define INPUT_PROP_SEMI_MT 0x03 /* 半多点触控设备,只能上报触摸区域矩形信息 */
#define INPUT_PROP_TOPBUTTONPAD 0x04 /* 顶部带软按键区域的触控板 */
#define INPUT_PROP_POINTING_STICK 0x05 /* 指点杆设备(如 ThinkPad 小红点) */
#define INPUT_PROP_ACCELEROMETER 0x06 /* 带有加速度传感器的输入设备 */
#define INPUT_PROP_MAX 0x1f /* 输入设备属性的最大编号 */
#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) /* 输入设备属性的总数量 */
input驱动编写流程
注册input_dev
- 申请一个input_dev,[[#input相关函数或结构体详解]]
c
struct input_dev *devm_input_allocate_device(struct device *dev)
参数:struct device *dev
返回值:申请到的 input_dev。
//现代推荐用devm_input_allocate_device,其会对资源生命周期的自动化管理。接管释放工作,在驱动卸载或设备移除时会自动释放资源,减少手动释放的负担。其不需要手动input_free_device和input_unregister_device
struct input_dev *input_allocate_device(void)
参数:无。
返回值:申请到的 input_dev。
- 初始化input_dev 的事件类型以及事件值,详见下面实例
- 使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev
c
int input_register_device(struct input_dev *dev)
dev:要注册的 input_dev 。
返回值:0,input_dev 注册成功;负值,input_dev 注册失败。
- input_dev不需要你分配设备号,创建类,创建设备节点的过程。其会自动完成,
- NPUT子系统中所用主设备号为13,
#define INPUT_MAJOR 13。通过次设备号区分设备,最终为/dev/input/xxx。 - NPUT子系统的类为固定的
/sys/class/input。
- 卸载input驱动的时候需要先使用input_unregister_device 函数注销掉注册的input_dev
- 然后使用 input_free_device 函数释放掉前面申请的 input_dev
input_dev创建和初始化实例:
c
struct input_dev *inputdev; /* input 结构体变量 */
2
3 /* 驱动入口函数 */
4 static int __init xxx_init(void)
5 {
6 ......
7 inputdev = input_allocate_device(); /* 申请 input_dev */
8 inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */
9
10 /*********第一种设置事件和事件值的方法***********/
11 __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
12 __set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
13 __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
14 /************************************************/
15
16 /*********第二种设置事件和事件值的方法***********/
17 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
18 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
BIT_MASK(KEY_0);
19 /************************************************/
20
21 /*********第三种设置事件和事件值的方法***********/
22 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
23 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
24 /************************************************/
25
26 /* 注册 input_dev */
27 input_register_device(inputdev);
28 ......
29 return 0;
30 }
32 /* 驱动出口函数 */
33 static void __exit xxx_exit(void)
{
35 input_unregister_device(inputdev); /* 注销 input_dev */
36 input_free_device(inputdev); /* 删除 input_dev */
37 }
上报输入事件
经过注册input_dev,INPUT系统里已经有一个抽象的输入设备。但他还无法从外界获得具体值,我们需要在合适地方选择上报事件。
位置:针对按键,可在中断处理函数,或者消抖定时器中断函数中完成以下两步
- 上报指定的事件以及对应的值
c
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_sync 函数来告诉 Linux 内核 input 子系统上报结束,input_sync 函数本质是上报一个同步事件。
c
void input_sync(struct input_dev *dev)
dev:需要上报同步事件的 input_dev。
返回值:无。
特定的上报事件与函数
对与确定的上报事件,推荐使用指定的上报函数。
c
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);
............
input_event结构体详解
Linux 内核使用 input_event 这个结构体来表示所有的输入事件信息。
c
//结构体定义
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /*microseconds */
};
typedef __kernel_long_t __kernel_time_t;
typedef __kernel_long_t __kernel_clock_t;
typedef long __kernel_long_t;
用hexdump查看/dev/input/xxx,可知该文件实际上就是input_event形式。
里面的数字依次是
c
/* 编号 */
/* tv_sec */ 秒
/* tv_usec */ 毫秒
/* type */
/* code */
/* value */
input相关函数或结构体详解
devm_input_allocate_device与input_allocate_device
c
/**
* input_allocate_device - allocate memory for new input device
*
* Returns prepared struct input_dev or %NULL.
*
* NOTE: Use input_free_device() to free devices that have not been
* registered; input_unregister_device() should be used for already
* registered devices.
*/
struct input_dev *input_allocate_device(void)
{
static atomic_t input_no = ATOMIC_INIT(-1);
struct input_dev *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev) {
dev->dev.type = &input_dev_type;
dev->dev.class = &input_class;
device_initialize(&dev->dev);
mutex_init(&dev->mutex);
spin_lock_init(&dev->event_lock);
timer_setup(&dev->timer, NULL, 0);
INIT_LIST_HEAD(&dev->h_list);
INIT_LIST_HEAD(&dev->node);
dev_set_name(&dev->dev, "input%lu",
(unsigned long)atomic_inc_return(&input_no));
__module_get(THIS_MODULE);
}
return dev;
}
/**
* devm_input_allocate_device - allocate managed input device
* @dev: device owning the input device being created
*
* Returns prepared struct input_dev or %NULL.
*
* Managed input devices do not need to be explicitly unregistered or
* freed as it will be done automatically when owner device unbinds from
* its driver (or binding fails). Once managed input device is allocated,
* it is ready to be set up and registered in the same fashion as regular
* input device. There are no special devm_input_device_[un]register()
* variants, regular ones work with both managed and unmanaged devices,
* should you need them. In most cases however, managed input device need
* not be explicitly unregistered or freed.
*
* NOTE: the owner device is set up as parent of input device and users
* should not override it.
*/
struct input_dev *devm_input_allocate_device(struct device *dev)
{
struct input_dev *input;
struct input_devres *devres;
devres = devres_alloc(devm_input_device_release,
sizeof(*devres), GFP_KERNEL);
if (!devres)
return NULL;
input = input_allocate_device();
if (!input) {
devres_free(devres);
return NULL;
}
input->dev.parent = dev;
input->devres_managed = true;
devres->input = input;
devres_add(dev, devres);
return input;
}
devm_input_allocate_device使用方法
将platform_device.pdev.dev给该函数。
c
static int my_probe(struct platform_device *pdev) {
struct input_dev *input;
// 一步到位,自动关联 pdev
input = devm_input_allocate_device(&pdev->dev);
if (!input) return -ENOMEM;
return input_register_device(input);
// 如果 register 失败,内核会自动释放 input 内存
}
// 甚至不需要写 .remove 函数来处理 input 设备!
Linux自带的按键驱动程序
是什么?
Linux 内核自带的 KEY 驱动文件为drivers/input/keyboard/gpio_keys.c,其涉及到了[[#Pinctrl/GPIO子系统]],[[#设备树]],[[#Linux platform总线驱动框架]],[[#Linux中断]],[[#INPUT子系统]]。
什么用?
- 使能Linux内核配置KEY驱动,一般已默认配置,可进行检查。
- 驱动文件路径为
drivers/input/keyboard/gpio_keys.c。下对该驱动/文件和设备树进行分析,已解释该如何处理Linux自带的按键驱动程序。
源码分析
- 由模块的入口和出口函数得知,其采用platform驱动框架
c
static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}
static void __exit gpio_keys_exit(void)
{
platform_driver_unregister(&gpio_keys_device_driver);
}
- 由以下platform_driver的赋值,你应该知道设备树的匹配属性为
compatible = "gpio-keys"
c
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.shutdown = gpio_keys_shutdown,
.driver = {
.name = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = gpio_keys_of_match,
.dev_groups = gpio_keys_groups,
}
};
static const struct of_device_id gpio_keys_of_match[] = {
{ .compatible = "gpio-keys", },
{ },
};
- 以下为
gpio_keys_probe匹配函数节选:
c
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
struct fwnode_handle *child = NULL;
struct gpio_keys_drvdata *ddata;
struct input_dev *input;
int i, error;
int wakeup = 0;
pdata = gpio_keys_get_devtree_pdata(dev);//从设备树获得到 KEY 相关的设备节点信息
input = devm_input_allocate_device(dev);//申请input_dev
/*初始化input_dev*/
input->name = pdata->name ? : pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = dev;
input->open = gpio_keys_open;
input->close = gpio_keys_close;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
input->keycode = ddata->keymap;
input->keycodesize = sizeof(ddata->keymap[0]);
input->keycodemax = pdata->nbuttons;
/*此函数会设置事件类型和事件码*/
error = gpio_keys_setup_key(pdev, input, ddata,
button, i, child);
error = input_register_device(input); //注册input设备
device_init_wakeup(dev, wakeup);
return 0;
}
- 按键按下,然后向 Linux 内核上报事件,事件上报是在 gpio_keys_irq_isr 函数中完成的,此函数内容如下。Linux自带的gpio_keys实质上也是在中断上报,input事件。
c
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
struct input_dev *input = bdata->input;
unsigned long flags;
BUG_ON(irq != bdata->irq);
spin_lock_irqsave(&bdata->lock, flags);
if (!bdata->key_pressed) {
if (bdata->button->wakeup)
pm_wakeup_event(bdata->input->dev.parent, 0);
input_event(input, EV_KEY, *bdata->code, 1);
input_sync(input);
if (!bdata->release_delay) {
input_event(input, EV_KEY, *bdata->code, 0);
input_sync(input);
goto out;
}
bdata->key_pressed = true;
}
if (bdata->release_delay)
hrtimer_start(&bdata->release_timer,
ms_to_ktime(bdata->release_delay),
HRTIMER_MODE_REL_HARD);
out:
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED;
}
设备树分析
c
&soc {
gpio_keys {
compatible = "gpio-keys"; //匹配官方的`gpio_keys` 驱动
//描述性字段,不影响功能可在/sys/class/input/inputX/name查看
label = "gpio-keys";
//表明这些按键的电源或功能依赖于 `pm6125`(高通电源管理芯片)的 GPIO 控制器。
depends-on-supply= <&pm6125_gpios>;
pinctrl-names = "default";
pinctrl-0 = <&key_vol_up_default &key_vol_down_default &gpio_key_active >;
//按键
vol_up {
label = "volume_up";
//&tlmm:高通的GPIO控制器,GPIO编号96,低电平触发
gpios = <&tlmm 96 GPIO_ACTIVE_LOW>;
linux,input-type = <1>; //指定事件类型为 `EV_KEY`(按键事件)
//指定事件码为KEY_VOLUMEUP,该事件码在include/uapi/linux/input-event-codes.h定义
linux,code = <KEY_VOLUMEUP>;
//该按键可以被软件禁用,表现为/sys/class/input/inputX/device/disabled
linux,can-disable;
debounce-interval = <15>; //去抖时间(毫秒
gpio-key,wakeup; //该按键可以作为唤醒源
};
}
}