LV12-05-input子系统

摘要:

  这篇笔记主要关于 input 子系统驱动模型的相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。

【说明】

(1)使用的工具及版本如下:

Windows windows11
Ubuntu Ubuntu16.04的64位版本
Linux开发板 华清远见 底板: FS4412_DEV_V5 核心板: FS4412 V2
u-boot 2013.01
linux内核 linux-3.14

(2)下边命令中有 fs4412 # 的是在 Linux 开发板使用的 SecureCRT 终端输入的命令,没有任何标识的是在 ubuntu 中执行。

(3)本篇使用的 linux 源码是已经移植过的源码。

点击查看本文更新记录

2022-9-1 更新内容

创建笔记。

点击查看本文参考资料
参考方向参考原文
驱动开发指南i.MX6ULL Linux阿尔法开发板资料
华清远见课程华清远见课程
野火驱动开发指南输入子系统–按键输入实验——基于i.MX6ULL系列 文档
点击查看所需文件及下载地址
文件下载链接
暂无暂无

  学习 platform 总线的时候就说过, platform 是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于 I2C 而言,不需要虚拟出一条总线,直接使用 I2C 总线即可。

一、 input 子系统

1. input 子系统简介

   input 子系统就是管理输入的子系统,和 pinctrl 、 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点。

image-20220907113048950

2.各个分层

  接下来我们详细了解一下各个层都有什么用,先来重新看一张图片:

image-20220907115125337
  • 事件处理层

  接收来自核心层上报的事件,并选择对应的 handler (事件处理器 struct input_handler )去处理,与用户空间进行交互。内核维护着多个事件处理器对象,每个 input_handler 对象专门处理一类事件,所有产生同类事件的设备驱动共用同一个 handler 。

   linux 中在用户空间将所有的设备都当初文件来处理,在一般的驱动程序中需要提供 fops 接口,我的理解就是操作函数集,以及在 /dev 下生成相应的设备文件 nod ,这些操作在输入子系统中由事件处理层完成。

  • 核心层

  负责连接设备驱动层和事件处理层,为设备驱动层提供输入设备驱动的接口( struct input_dev )以及输入设备驱动的注册函数( input_register_device ),为事件处理层提供输入事件驱动的接口,通知事件处理层对事件进行处理。会在 /proc 下产生相应的设备信息。

  • 设备驱动层

  主要实现获取硬件设备的数据信息(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),并转换为核心层定义的规范事件后提交给核心层,该层每个设备对应一个 struct input_dev 对象。

二、基本数据结构

1. struct input_dev

  在使用 input 子系统的时候我们只需要注册一个 input 设备即可, input_dev 结构体表示 input 设备,这个结构体定义在 linux 内核源码的下边这个文件中:

1
include/linux/input.h 

我们打开这个文件,找到 struct input_dev 结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
// ... ...
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 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)];
// ... ...
};

【成员说明】

  • evbit : unsigned long 类型数组,这个我们用的比较多,它表示输入事件的类型。
点击查看可选事件类型

  可选的事件类型定义在 linux 内核源码的这个文件中:

1
include/uapi/linux/input.h 

我们打开这个文件,可以看到常见的事件类型定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* Event types
*/

#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 /* 压力状态事件 */
#define EV_MAX 0x1f /* 事件类型最大个数和提供位掩码支持 */
#define EV_CNT (EV_MAX+1)
  • keybit : unsigned long 类型数组,主要是一些记录支持的按键值的位图。
点击查看可选键值定义

  按键键值定义在 linux 内核源码的这个文件中:

1
include/uapi/linux/input.h 

我们打开这个文件,可以看到常见的键值定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* Keys and buttons
*
* Most of the keys/buttons are modeled after USB HUT 1.12
* (see http://www.usb.org/developers/hidpage).
* Abbreviations in the comments:
* AC - Application Control
* AL - Application Launch Button
* SC - System Control
*/

#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
// ... ...

2. struct input_event

   Linux 内核使用 input_event 这个结构体来表示所有的输入事件 ,这个结构体定义在 linux 内核源码的下边这个文件中:

1
include/uapi/linux/input.h 

我们打开这个文件,找到 struct input_event 结构体如下:

1
2
3
4
5
6
7
8
9
10
/*
* The event structure itself
*/

struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};

【成员介绍】

  • time : struct timeval 类型,表示此事件发生的时间。
点击查看 struct timeval

  这个结构体定义在 linux 内核源码的这个目录下:

1
include/uapi/linux/time.h

我们打开这个文件,可以看到结构体定义如下:

1
2
3
4
5
6
7
8
9
10
/* include/uapi/asm-generic/posix_types.h */
typedef long __kernel_long_t;
typedef __kernel_long_t __kernel_time_t;
typedef __kernel_long_t __kernel_suseconds_t;

/* include/uapi/linux/time.h */
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
  • type : __u16 类型,表示事件类型,比如 EV_KEY ,表示此次事件为按键事件,此成员变量为 16 位。
  • code : __u16 类型,事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如: KEY_0 、 KEY_1
    等等这些按键,此成员变量为 16 位。
  • value : __s32 类型,表示值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。

   input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值,比如按键值等。

  每个事件的发生都使用事件(type)→子事件(code)→值(value)。

三、类、设备号和设备节点

  前边我们在编写驱动的时候都是需要自己创建类和设备节点或者是需要在程序中编程实现节点的自动创建,但是 input 子系统会自动创建相应的类和结点,我们的程序中就不再需要这部分的代码了。在这里边会有类和设备节点的创建相关代码。

1.类的定义

  我们打开 linux 内核源码中的 drivers/input/input.c 这个文件, 这个文件就是 input 子系统的核心层,里边有这样一个结构体变量:

1
2
3
4
5
6
/* linux3.14版本内核的话,应该在1758行 */
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
EXPORT_SYMBOL_GPL(input_class);

  这个就是定义的一个类的结构体变量,然后在下边有这样一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* linux3.14版本内核的话,应该在2401行 */
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 = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
// ... ...
}

  在该函数中,会注册一个 input 类,这样系统启动以后就会在 /sys/class 目录下有一个 input 子目录。

2.设备号

  在 linux 内核源码的 drivers/input/input.c 这个文件的 input_init 函数中有这么一条语句:

1
2
3
4
5
6
7
8
/* linux3.14版本内核的话,应该在2401行 */
static int __init input_init(void)
{
// ... ...
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
// ... ...
}

  这里会注册一个字符设备,主设备号为 INPUT_MAJOR ,这个宏定义在 linux 内核源码的这个文件中:

1
include/uapi/linux/major.h 

我们打开这个文件会发现:

1
#define INPUT_MAJOR 13

所以说, input 子系统的所有设备主设备号都为 13 。

3.设备节点

  关于这个设备节点问题,目前还没有找到,在哪里创建的,不过暂时不重要,知道怎么找我们加载的驱动的节点就好啦。

  在 input 子系统中,设备节点也会自动创建,我们加载驱动之后,会在在 /dev/input/ 目录下生成节点,之前测试用的 key 和 mpu6050 的例子得到的设备节点都是 event 开头的,如:

1
2
/dev/input/event1
/dev/input/event2

我们加载驱动前看一下这个目录,加载后看一下多了哪个节点,这个就是我们加载的驱动对应节点啦。

三、基本函数

1. input_dev 对象创建与销毁 

1.1 input_allocate_device()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_allocate_device -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
struct input_dev *input_allocate_device(void);

【函数说明】该函数用于创建一个 input_dev 对象 。

【函数参数】 none

【返回值】 struct input_dev * 类型,成功返回创建好的 input_dev 对象地址,失败返回 NULL 。

【使用格式】 none

【注意】 none

1.2 input_free_device()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_free_device -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
void input_free_device(struct input_dev *dev);

【函数说明】该函数用于销毁一个 input_dev 对象 。

【函数参数】

  • dev : struct input_dev * 类型,表示要销毁的 input_dev 对象。

【返回值】 none

【使用格式】 none

【注意】 none

2. input_dev 对象注册与注销 

  创建好 input_dev 对象后,还需要向内核注册这个对象。

2.1 input_register_device()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_register_device -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
int input_register_device(struct input_dev *dev);

【函数说明】该函数用于向内核注册一个 input_dev 对象 。

【函数参数】

  • dev : struct input_dev * 类型,表示要注册的 input_dev 对象。

【返回值】 int 类型,注册成功返回 0 ,失败返回一个负数。

【使用格式】 none

【注意】 none

2.2 input_unregister_device()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_unregister_device -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
void input_unregister_device(struct input_dev *dev);

【函数说明】该函数用于从内核注销一个 input_dev 对象

【函数参数】

dev : struct input_dev * 类型,表示要注销的 input_dev 对象。

【返回值】 none

【使用格式】 none

【注意】 none

3.设置事件和事件值 

  这一步是在创建好 input_dev 对象之后进行,可以看做是对 input_dev 对象的初始化。

3.1 __set_bit()

  我们使用以下命令查询一下函数所在头文件:

1
grep __set_bit -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 需包含的头文件 */
//函数定义在 include/asm-generic/bitops/non-atomic.h,应该是包含下边这个头文件就够了
#include <linux/input.h>

/* 函数定义 */
/**
* __set_bit - Set a bit in memory
* @nr: the bit to set
* @addr: the address to start counting from
*
* Unlike set_bit(), this function is non-atomic and may be reordered.
* If it's called on the same region of memory simultaneously, the effect
* may be that only one operation succeeds.
*/
static inline void __set_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);

*p |= mask;
}

【函数说明】该函数用于设置会产生的事件类型 。

【函数参数】

  • nr : int 类型,表示事件的位,比如 EV_KEY , KEY_0 等。
  • addr : volatile unsigned long * 类型,表示 input_dev 对象的不同位图,例如 evbit 成员, keybit 成员。

【返回值】 none

【使用格式】

1
2
3
4
struct input_dev *pdev;

__set_bit(EV_KEY, pdev->evbit);
__set_bit(KEY_2, pdev->keybit);

【注意】 none

3.2 set_bit()

  我们使用以下命令查询一下函数所在头文件:

1
grep __set_bit -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
// 没有找到,不过用法和 __set_bit() 是一样的

【函数说明】该函数用于设置事件和事件值,格式和上边的 __set_bits 一样 。

【函数参数】

  • nr : int 类型,表示事件的位,比如 EV_KEY , KEY_0 等。
  • addr : volatile unsigned long * 类型,表示 input_dev 对象的不同位图,例如 evbit 成员, keybit 成员。

【返回值】 none

【使用格式】

1
2
3
4
struct input_dev *pdev;

set_bit(EV_KEY, pdev->evbit);
set_bit(KEY_2, pdev->keybit);

【注意】 none

3.3 input_set_abs_params()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_set_abs_params -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
void (struct input_dev *dev, unsigned int axis,
int min, int max, int fuzz, int flat);

【函数说明】该函数也是用于设置事件和事件值,不过还没有用过,以后用到了再在这里补充笔记。

【函数参数】

  • dev : struct input_dev * 类型,表示 input_dev 对象。
  • axis : unsigned int 类型。
  • min : int 类型。
  • max : int 类型。
  • flat : int 类型。

【返回值】 none

【使用格式】 none

【注意】 none

3.4 input_set_capability()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_set_capability -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code);

【函数说明】该函数也是用于设置事件和事件值,不过还没有用过,以后用到了再在这里补充笔记。

【函数参数】

  • dev : struct input_dev * 类型,表示 input_dev 对象,也就是要设置的输入设备。
  • type : unsigned int 类型,设置输入类型,可以看到,函数实现中根据 type 设置不同的 input_dev 结构体参数。例如 type =EV_KEY , 那么设置的是 input_dev->keybit ,也就是键值。
  • code : unsigned int 类型,不同类型的输入信号含义不同,如果是按键,则表示的是要设置的按键的键值。

【返回值】 none

【使用格式】 none

【注意】 none

4.上报事件

4.1 input_event()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_event -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

【函数说明】该函数用于向内核上报事件。

【函数参数】

  • dev : struct input_dev * 类型,表示指定的 input_dev 对象。
  • type : unsigned int 类型,表示事件类型。我们在根据实际输入设备配置 input_dev 结构体时会设置 input_dev-> evbit 参数, 用于设置输入设备能够产生的事件类型(可能是多个)。上报事件时要从“能够产生”的这些事件类型中选择。
  • code : unsigned int 类型,表示编码。以按键为例,按键的编码就是我们设置的按键键值。
  • value , int 类型,指定事件的值。

【返回值】 none

【使用格式】 none

【注意】 none

4.2 input_report_key()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_report_key -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);

【函数说明】该函数用于上报按键事件 。

【函数参数】

  • dev : struct input_dev * 类型,表示指定的 input_dev 对象。
  • code : unsigned int 类型,表示编码。以按键为例,按键的编码就是我们设置的按键键值。
  • value , int 类型,指定事件的值。

【返回值】 none

【使用格式】 none

【注意】 none

4.3 input_report_abs()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_report_abs -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);

【函数说明】该函数用于上报绝对坐标事件。

【函数参数】

  • dev : struct input_dev * 类型,表示指定的 input_dev 对象。
  • code : unsigned int 类型,表示编码。以按键为例,按键的编码就是我们设置的按键键值。
  • value , int 类型,指定事件的值。

【返回值】 none

【使用格式】 none

【注意】 none

4.4 input_sync()

  我们使用以下命令查询一下函数所在头文件:

1
grep input_sync -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/input.h>

/* 函数声明 */
static inline void input_sync(struct input_dev *dev);

【函数说明】该函数用于事件同步 。

【函数参数】

  • dev : struct input_dev * 类型,表示发生事件的 input_dev 对象。

【返回值】 none

【使用格式】 none

【注意】 none