Linux驱动学习(四) input 子系统实验
前言
- 按键、鼠标、键盘、触摸屏等都属于 input 设备,内核专门设计了 input 子系统的框架来管理这些输入设备;
- 输入设备本质上还是字符设备,只是在字符设备的基础上又套了 input 框架;
- 用户只需上报输入事件(比如按键值、坐标等信息),由 input 核心层负责处理这些事件;
input 子系统讲解
主要代码路径
- 主要代码路径:
include\linux\input.h
include\linux\gpio_keys.h
include\uapi\linux\input.h
include\uapi\linux\major.h
drivers\input\input.c
drivers\input\keyboard\gpio_keys.c
简介
- input 子系统与 pinctl、gpio 子系统处于同一地位,都是 Linux 内核针对某一类设备而设计的框架;顾名思义,input 子系统是管理输入设备的;
- 不同输入设备的事件信息含义不同,例如按键和键盘表示的是按键信息,鼠标和触摸屏表示的是坐标信息,因此在应用层的处理就不同;
- 我们做驱动的,暂时不用操心应用层的事情,只需要按照要求上报输入设备的输入事件即可;
- input 子系统框架又分为如下3层:
- input 驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容
- input 核心层:承上启下,为驱动层提供设备的注册和操作接口;通知事件层对输入事件进行
处理; - input 事件处理层:主要和用户空间进行交互
- 通过编写输入设备的驱动,给用户空间提供可访问的设备节点,input 子系统框架结构图如下图所示:
- 左边(硬件输入设备)就是最底层的具体设备,比如按键、USB 键盘/鼠标等;
- 中间部分属于 Linux 内核空间(分为驱动层、核心层、事件处理层);
- 最右边的就是用户空间,所有输入设备都会对应一个设备访问节点,用户以访问文件的方式访问设备节点,进而做一些数据处理;
- 我们编写驱动只需要关注驱动层即可(核心层和事件处理层虽然不用修改,但也要理解)
驱动编写流程
- input 核心层会向内核注册一个字符设备;
- input 核心层源码:
drivers/input/input.c
// input 子系统的所有设备主设备号都为 13, 已经固定,不需要我们再去设置
#define INPUT_MAJOR 13 /* include/uapi/linux/major.h */
/**
* @brief 定义一个 input 类
* @param name 名称
* @param devnode 设备节点名
*/
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
EXPORT_SYMBOL_GPL(input_class);
/**
* @brief input 类初始化
* @note
*/
static int __init input_init(void)
{
int err;
/* 注册 input 类 */
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;
/* 注册一个字符设备,主设备号为 INPUT_MAJOR, 定义在 include/uapi/linux/major.h */
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;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
- 如上代码,注册一个 input 类,这样系统启动以后就会在
/sys/class
目录下有一个 input 子目录; - 注册一个字符设备,主设备号为 INPUT_MAJOR,固定值13,所以 input 子系统注册的所有设备,其主设备号都是13,所以我们不再需要去注册字符设备,只需注册 input_device 即可;
注册 input_dev
- 在使用 input 子系统框架时,我们只需要注册一个 input device,非常省事儿;
- input_dev 定义如下,省略不关心的代码:
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)]; /* 事件类型的位图 */
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 int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
};
#define to_input_dev(d) container_of(d, struct input_dev, dev)
- 其中 evbit 表示输入事件类型,可选的事件类型定义在 include/uapi/linux/input.h 文件
中,事件类型如下:
/*
* 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)
- 比如我们使用的是按键,就注册 EV_KEY 事件,如果使用连按功能还需要注册 EV_REP 事件;
- evbit、keybit、relbit 等都是存放不同事件对应的值,比如我们现在要使用按键事件,所以会用到 keybit,即按键事件使用的位图;
- Linux 内核定义了很多按键值,它们定义在 include/uapi/linux/input.h ,按键值如下所示:
/*
* 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
.......
#define BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7
- 我们可以将按键值设置为以上中的任意一个,比如设置为 KEY_0
- 编写 input 设备驱动时,首先需要定义 input_dev 结构体变量,然后分配内存(同理,使用完后,需要释放内存资源),函数接口如下:
struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
- 申请好 input_dev 之后,需要对其进行初始化,初始化内容主要有 事件类型(evbit)和事件值(keybit)这两项;
- 初始化 input_dev 之后,需要将其注册到内核中(同理,不使用的时候也要注销),函数接口如下:
/**
* @brief 注册 input_dev
* @param dev 指向要进行注册的 input_dev
* @return 0,input_dev 注册成功;负值,input_dev 注册失败
*/
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
- 综上所述,input_dev 注册过程如下:
①、使用 input_allocate_device 函数申请一个 input_dev。
②、初始化 input_dev 的事件类型以及事件值。
③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev。
④、卸载input驱动的时候需要先使用 input_unregister_device 函数注销掉注册的 input_dev,
然后使用 input_free_device 函数释放掉前面申请的 input_dev。input_dev 注册过程示例代码如下所示:
struct input_dev *inputdev; /* input 结构体变量 */
/* 驱动入口函数 */
static int __init xxx_init(void)
{
......;
inputdev = input_allocate_device(); /* 申请 input_dev */
inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */
/*********第一种设置事件和事件值的方法***********/
__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
/************************************************/
/*********第二种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | T_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= T_MASK(KEY_0);
/************************************************/
/*********第三种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | T_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/************************************************/
/* 注册 input_dev */
input_register_device(inputdev);
......;
return 0;
}
static void __exit xxx_exit(void)
{
input_unregister_device(inputdev); /* 注销 input_dev */
input_free_device(inputdev); /* 释放input_dev */
}
上报输入事件
- 注册完 input_dev 后,还不能使用,因为 input 设备是具有输入功能的,输入值是什么还不确定;
- 我们需要获取到具体的输入值(或输入事件),然后将输入事件上报给内核,比如按键,我们需要在按键中断处理函数(或消抖定时器中断函数)中,将按键值上报给内核,这样内核才能获取到正确的输入值;
- 不同事件,其上报事件的 API 函数不同,以下是一些常用的事件上报API函数:
/**
* input_event() - report new input event
* @dev: device that generated the event -- 产生事件的设备
* @type: type of the event -- 事件类型,比如 EV_KEY
* @code: event code -- 事件码,也就是我们注册的按键值,比如 KEY_0
* @value: value of the event -- 事件值,比如 1 表示按键按下,0 表示按键松开
*
* This function should be used by drivers implementing various input
* devices to report input events. See also input_inject_event().
*
* NOTE: input_event() may be safely used right after input device was
* allocated with input_allocate_device(), even before it is registered
* with input_register_device(), but the event will not reach any of the
* input handlers. Such early invocation of input_event() may be used
* to 'seed' initial state of a switch or initial position of absolute
* axis, etc.
*/
void input_event(struct input_dev *dev,
unsigned int type,
unsigned int code,
int value);
- input_event 函数可以上报所有的事件类型和事件值;
- 内核也提供了其他针对具体事件的上报函数,比如针对按键事件的上报函数(本质上还是使用 input_event ):
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!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 () 函数高速内核 input 子系统上报结束,其本质上是一个同步事件,函数原型:
void input_sync(struct input_dev *dev);
- 综上所述,按键上报按键值得参考代码如下:
/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
unsigned char value;
value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
if (value == 0) /* 按下按键 */
{
/* 上报按键值 */
input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1 , 按下 */
input_sync(inputdev); /* 同步事件 */
}
else /* 按键松开 */
{
input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0 , 松开 */
input_sync(inputdev); /* 同步事件 */
}
}
input_event 结构体
- Linux 内核使用 input_event 这个结构体来表示所有的输入事件,定义在 include/uapi/linux/input.h 文件;
- tv_sec 和 tv_usec 这两个成员变量都为 long 类型,也就是 32
位,这个一定要记住,后面我们分析 event 事件上报数据的时候要用到;
typedef long __kernel_long_t;
typedef __kernel_long_t __kernel_time_t;
typedef __kernel_long_t __kernel_suseconds_t;
struct timeval
{
__kernel_time_t tv_sec; /* 秒 */
__kernel_suseconds_t tv_usec; /* 微秒 */
};
struct input_event
{
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
- input_evetn 这个结构体非常重要,所有输入设备都是按照 input_event 结构体格式呈现给用户的;用户通过 input_event 来获取具体的输入事件和对应的事件值(比如按键事件的按键值);
实验
待更新。。。