Linux 驱动学习(四) input 子系统实验

Linux驱动学习(四) input 子系统实验

前言

  • 按键、鼠标、键盘、触摸屏等都属于 input 设备,内核专门设计了 input 子系统的框架来管理这些输入设备;
  • 输入设备本质上还是字符设备,只是在字符设备的基础上又套了 input 框架;
  • 用户只需上报输入事件(比如按键值、坐标等信息),由 input 核心层负责处理这些事件;

input 子系统讲解

主要代码路径

  • 主要代码路径:
    1. include\linux\input.h
    2. include\linux\gpio_keys.h
    3. include\uapi\linux\input.h
    4. include\uapi\linux\major.h
    5. drivers\input\input.c
    6. drivers\input\keyboard\gpio_keys.c

简介

  • input 子系统与 pinctl、gpio 子系统处于同一地位,都是 Linux 内核针对某一类设备而设计的框架;顾名思义,input 子系统是管理输入设备的;
  • 不同输入设备的事件信息含义不同,例如按键和键盘表示的是按键信息,鼠标和触摸屏表示的是坐标信息,因此在应用层的处理就不同;
  • 我们做驱动的,暂时不用操心应用层的事情,只需要按照要求上报输入设备的输入事件即可;
  • input 子系统框架又分为如下3层:
    • input 驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容
    • input 核心层:承上启下,为驱动层提供设备的注册和操作接口;通知事件层对输入事件进行
      处理;
    • input 事件处理层:主要和用户空间进行交互
  • 通过编写输入设备的驱动,给用户空间提供可访问的设备节点,input 子系统框架结构图如下图所示:
    在这里插入图片描述
  1. 左边(硬件输入设备)就是最底层的具体设备,比如按键、USB 键盘/鼠标等;
  2. 中间部分属于 Linux 内核空间(分为驱动层、核心层、事件处理层);
  3. 最右边的就是用户空间,所有输入设备都会对应一个设备访问节点,用户以访问文件的方式访问设备节点,进而做一些数据处理;
  4. 我们编写驱动只需要关注驱动层即可(核心层和事件处理层虽然不用修改,但也要理解)

驱动编写流程

  • 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 来获取具体的输入事件和对应的事件值(比如按键事件的按键值);

实验

待更新。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值