文章目录
1 input子系统概述
一个标准化的计算机由五大硬件组成,分别是控制器(Control)、运算器(Datapath)、存储器(Memory)、输入设备(Input system)、输出设备(Output system)。对于计算机而言,输入设备种类繁多,常见的键盘、鼠标、触摸面板,嵌入式计算机中还有游戏手柄、触摸屏、按键、重力传感器、陀螺仪等等,这些设备类型不同,实现原理差异化,且输入信息和响应的对象也不一样。如果没有一个标准化程序框架,对于应用程序和驱动程序而言,会显得异常混乱,也不便于添加新的输入设备。
linux作为一个通用操作系统,为方便管理一系列的输入设备,引入了一个“input子系统”,屏蔽了输入设备的底层差异,提供一个标准的用户接口(input节点)访问。input子系统总体框架从下到上,可以分为input驱动层、input核心层、input事件处理层,最上层事件层由用户通过“设备节点”处理。
- input驱动层,即是具体输入设备的驱动程序,由驱动工程师根据input子系统框架实现驱动程序,如按键驱动、键鼠驱动、触摸驱动。
- input核心层,input子系统的核心,负责协调驱动层和事件层,捕捉驱动层的设备输入信息,转换为事件通知事件层,是一个”承上启下“的作用。
- input事件处理层,与用户空间的信息交互接口,每个事件处理函数属于平行关系,事件对应的设备在用户空间下以“节点”形式存在,节点路径为
“/dev/input/input_nameX”
,不同的输入设备节点名称不同,节点名称接着的是序号,如event0、event1、event2
。
1.1 input子系统意义
- 屏蔽输入设备底层差异,统一用户访问接口
- 输入设备转换为“事件方式”驱动
- 便于应用层链接各种输入设备
2 inuput子系统数据结构
该部分,我们主要了解input子系统的基本数据结构类型,这些编写input驱动程序时会使用到的。
2.1 input设备点
- input设备本质是一个字符驱动,input核心在字符驱动之上,进一步构建出input设备,可以通过
"ls /sys/class/input"
查看系统已注册的input设备。
- input设备在字符设备之上,其主设备号
INPUT_MAJOR
固定为13,主设备号宏INPUT_MAJOR
在常用主设备号定义文件“include/uapi/linux/major.h
中有定义。
/* include/uapi/linux/major.h */
#define INPUT_MAJOR 13
-
input次设备号分布范围。
joystick游戏杆:0~16
mouse鼠标: 32~62
mice鼠标: 63
事件设备: 64~95
-
input设备在注册时会调用标准字符驱动注册接口,因此,编写input设备驱动时,不需再注册字符驱动。
2.2 input设备数据结构
linux内核采用struct input_dev
结构描述一个input设备,位于“include/linux/input.h”
中。
struct input_dev {
const char *name; /* input 设备名称 */
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)]; /* 设备开关事件位图 */
unsigned int hint_events_per_packet;
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt *mt;
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)]; /* 开关设备当前状态 */
/* 设备打开、关闭、清除设备及写数据时回调方法(如果有)*/
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle __rcu *grab;
spinlock_t event_lock; /* 自旋锁 */
struct mutex mutex; /* 互斥锁 */
unsigned int users;
bool going_away;
struct device dev; /* linux 标准设备驱动 */
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
};
编写input驱动程序时需关注的几个成员参数。
【1】设备基本信息
name
,设备名称phys
,设备节点物理路径,如“key/key0”
;如果不指定路径则默认在"input"
目录下
【2】事件类型
事件类型evbit
是驱动设备注册时跟进物理设备来指定,如同步事件(EV_SYN
)、按键事件(EV_KEY
)、声音事件(EN_SND
)等。常规的事件类型,input子系统已经定义好,位于“include/uapi/linux/input-event-codes.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 /* 声音事件 */
#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) /* 事件总数 */
注:
【1】重复事件,一般用于支持重复事件的输入设备,如按键的连按、鼠标的双击。
【2】同步事件,每个input输入设备都会注册一个同步事件,用于于事件报告结束。
【3】事件键值
事件键值evbit、keybit、sndbit、swbit
则是具体input设备的输入事件,在注册input驱动设备是也是需要指定的。常用的事件键值(按键事件、坐标事件…)在"include/uapi/linux/input-event-codes.h"
定义,命名风格是以具体事件类型为前缀的,可读性高。
/*
* Keys and buttons
*/
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
......
/*
* Relative axes
*/
#define REL_X 0x00
#define REL_Y 0x01
#define REL_Z 0x02
#define REL_RX 0x03
......
/*
* Absolute axes
*/
#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_Z 0x02
#define ABS_RX 0x03
......
【4】回调方法
input输入设备特定需求时,可以在实现在操作设备时,实现相关回调方法(函数实例),分别是打开设备open
、关闭设备close
、清除设备flush
、写入事件event
。
2.3 input事件数据结构
input事件是由input子系统向input核心发送的,input核心向用户传输事件信息是一个strcut input_event
结构体,包含一个事件的各类属性,位于“include/uapi/linux/input.h”
中。
/*
* The event structure itself
*/
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time
,事件发生的时间,struct timeval
是一个时间结构体,包含秒和微秒成员,位于include/uapi/linux/time.h
定义。
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
type
,事件类型code
,事件键值value
,事件私有值,可以用来表示事件状态,如按键的按下与抬起
2.4 常用函数接口
该部分描述的input子系统的常用函数接口,在编写input驱动程序时会常用到的,函数声明位于“include/linux/input.h”
中。
申请input设备内存
申请一个input设备内存,申请成功后并对设备成员执行初始化,如自旋锁、互斥锁、链表等初始化。
struct input_dev __must_check *input_allocate_device(void);
struct input_dev *input_allocate_device(void)
{
static atomic_t input_no = ATOMIC_INIT(-1);
struct input_dev *dev;
dev = kzalloc(sizeof(struct input_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); /* 初始化自旋锁 */
init_timer(&dev->timer); /* 初始化定时器 */
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;
}
- 返回,成功返回
struct input_dev
内存首地址
释放input内存地址
void input_free_device(struct input_dev *dev);
dev
,待释放的input设备内存地址- 返回,无
注册input设备
int __must_check input_register_device(struct input_dev *dev);
dev
,待注册的input设备- 返回,成功返回0,失败返回负值
注销input设备
void input_unregister_device(struct input_dev *dev);
dev
,待注销的input设备- 返回,无
设置事件和事件值
事件和事件值是以位的形式保存,可以使用linux内核位操作宏BIT
进行每一位操作。也可以使用input子系统的专用设置函数设置。
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code);
dev
,注册的input设备type
,待设置的事件类型code
,待设置事件键值
linux内核位操作宏位于“include/linux/bitops.h”
定义。
#define BIT(nr) (1UL << (nr))
#define BIT_ULL(nr) (1ULL << (nr))
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
#define BIT_ULL_MASK(nr) (1ULL << ((nr) % BITS_PER_LONG_LONG))
#define BIT_ULL_WORD(nr) ((nr) / BITS_PER_LONG_LONG)
#define BITS_PER_BYTE 8
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
以注册一个按键设备为例。
- 使用
__set_bit
宏:
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(EV_REP, input_dev->evbit);
__set_bit(KEY_0, input_dev->keybit);
- 使用
BIT_MASK
宏:
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
inpud_dev->keybit[BIT_WORD[BIT_MASK(KEY_0)] |= BIT_MASK(KEY_0);
- 使用
input_set_capability
函数:
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(EV_REP, input_dev->evbit);
input_set_capability(dev, EV_KEY, KEY_0);
传输input事件
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
-
dev
,注册的input设备 -
type
,事件类型 -
code
,事件键值 -
value
,事件私有值 -
返回,无
linux内核进一步封装了专门的事件传输函数,一般情况下我们使用的是封装好的专用函数(最终还是调用input_event
函数),没有专用封装函数,再使用input_event
函数;如向input子系统传输一个按键事件,使用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);
}
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_REL, code, value);
}
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_FF_STATUS, code, value);
}
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_SW, code, !!value);
}
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
static inline void input_mt_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_MT_REPORT, 0);
}
同步事件
传输事件后,还需执行同步事件,通知input子系统传输结束。从同步事件函数原型可以知道,同步过程,本质是向input子系统传输一个同步事件(EV_SYN
)。(也应证了前提及的为什么每个input设备都需要注册一个同步事件)
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
dev
,注册的input设备- 返回,无
3 如何编写input驱动程序
【1】input设备内存分配
struct input_dev *input = NULL;
input = input_allocate_device();
【2】设置事件类型和事件键值
__set_bit(EV_KEY, input->evbit);
__set_bit(EV_REP, input->evbit);
__set_bit(EV_LED, input->evbit);
input_set_capability(dev, EV_KEY, KEY_0);
【3】操作回调方法(如有)
存在一些设备在打开、关闭、清除、写事件时需执行内存分配与释放、辅助组件开启与停止等特殊动作,可以添加回调实例函数。如一个按键输入设备,在打开按键驱动时开启消抖定时器,关闭驱动后释放消抖定时器。
int gpio_keys_open(struct input_dev *input)
{
/* todo */
return 0;
}
void gpio_keys_close(struct input_dev *input)
{
/* todo */
}
【4】设备注册
在驱动初始化时调用注册。
input_register_device(input);
【5】传输事件
在input设备产生事件时,将事件传输(报告)至input核心处理,传输事件完成后,还需以一个同步事件表示本次传输结束。
if(press == 0x00)
{
input_report_key(input, KEY_0, 0X01);
input_sync(input);
}
else
{
input_report_key(input, KEY_0, 0X00);
input_sync(input);
}
【6】驱动出口
驱动出口即是从内核注销input设备,并释放分配的内存资源。
void __exit dev_exit(void)
{
input_unregister_device(input);
input_free_device(input);
}
4 参考文章
【1】input输入子系统