【Linux驱动编程】input子系统分析与应用

1 input子系统概述

  一个标准化的计算机由五大硬件组成,分别是控制器(Control)、运算器(Datapath)、存储器(Memory)、输入设备(Input system)、输出设备(Output system)。对于计算机而言,输入设备种类繁多,常见的键盘、鼠标、触摸面板,嵌入式计算机中还有游戏手柄、触摸屏、按键、重力传感器、陀螺仪等等,这些设备类型不同,实现原理差异化,且输入信息和响应的对象也不一样。如果没有一个标准化程序框架,对于应用程序和驱动程序而言,会显得异常混乱,也不便于添加新的输入设备。


  linux作为一个通用操作系统,为方便管理一系列的输入设备,引入了一个“input子系统”,屏蔽了输入设备的底层差异,提供一个标准的用户接口(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设备。

在这里插入图片描述

Ubuntu16.04下的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输入子系统

【2】Linux之输入子系统分析(详解)

原创文章 128 获赞 147 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/105736980