Linux input子系统上报事件讲解(以重力传感器lis2dw12驱动为例)

input子系统背景

以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux内核为了能够处理各种不同类型的输入设备,(比如 触摸屏 ,鼠标 , 键盘 , 操纵杆 ),设计并实现了为驱动层程序的实现提供统一接口函数;为上层应用提供试图统一的抽象层 , 即是Linux 输入子系统 。

引入输入子系统的好处:

  • 统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。
  • 驱动不必创建、管理/dev节点以及相关的访问方法。例如,在终端系统中,我们不需要去管有多少个键盘,多少个鼠标。应用程序只要从输入子系统中去取对应的事件(按键,鼠标移位等)就可以了。而底层设备也不需要直接对接应用程序,只要处理并处理对应的事件即可。
  • 抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问。

一句话概括,输入子系统是所有I/O设备驱动与应用程序之间的中间层。

input子系统框架

linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。

驱动层:对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。

将底层的硬件输入转化为统一事件形式,向输入核心(Input Core)汇报。

Input Core:对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。

它承上启下:

  • 为驱动层提供输入设备注册与操作接口,如:input_register_device;
  • 通知事件处理层对事件进行处理;在/proc下产生相应的设备信息。

事件处理层:主要是和用户空间交互。(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下会生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。

实现设备驱动核心工作是:向系统报告按键等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。驱动报告事件经过inputCore和Eventhandler到达用户空间。

对于linux输入子系统的框架结构如下图1所示:

在这里插入图片描述

/dev/input/下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。

事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。

输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

在这里插入图片描述

由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。

lis2dw12驱动代码分析

上面说到input子系统设备驱动层主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层,接下来就摘取部分lis2dw12驱动代码来分析下事件的上报过程。在lis2dw12驱动初始化函数probe中,调用了lis2dw12_input_init函数进行了input初始化。

#define INPUT_EVENT_TYPE	EV_MSC
#define INPUT_EVENT_X		MSC_SERIAL
#define INPUT_EVENT_Y		MSC_PULSELED
#define INPUT_EVENT_Z		MSC_GESTURE
#define INPUT_EVENT_TIME_MSB	MSC_SCAN
#define INPUT_EVENT_TIME_LSB	MSC_MAX

static int lis2dw12_input_init(struct lis2dw12_sensor_data *sdata, u16 bustype)
{
	int err = 0;

	sdata->input_dev = input_allocate_device();//调用input_allocate_device()函数分配了一个 input_dev 结构体
	if (!sdata->input_dev) {
		dev_err(sdata->cdata->dev, "failed to allocate input device");
		return -ENOMEM;
	}

	sdata->input_dev->name = lis2dw12_sensor_name[sdata->sindex].description;
	sdata->input_dev->id.bustype = bustype;
	sdata->input_dev->dev.parent = sdata->cdata->dev;
	input_set_drvdata(sdata->input_dev, sdata);

	__set_bit(INPUT_EVENT_TYPE, sdata->input_dev->evbit);//调用__set_bit()函数设置 input_dev 所支持的事件类型。
	__set_bit(INPUT_EVENT_TIME_MSB, sdata->input_dev->mscbit);
	__set_bit(INPUT_EVENT_TIME_LSB, sdata->input_dev->mscbit);
	__set_bit(INPUT_EVENT_X, sdata->input_dev->mscbit);

	if (sdata->sindex == LIS2DW12_ACCEL) {
		__set_bit(INPUT_EVENT_Y, sdata->input_dev->mscbit);
		__set_bit(INPUT_EVENT_Z, sdata->input_dev->mscbit);
	}

	err = input_register_device(sdata->input_dev);//调用 input_register_device()函数对其进行了注册
	if (err) {
		dev_err(sdata->cdata->dev, "unable to register sensor %s\n",
			sdata->name);
		input_free_device(sdata->input_dev);
	}

	return err;
}

事件类型由 input_dev 的evbit 成员来表示,从上面可以看出,驱动里面evbit 成员设置EV_MSC事件,并在mscbit位图里设置了X、Y、Z等键值支持。注意,一个设备可以支持一种或者多种事件类型。常用的事件类型如下:

EV_SYN     0x00    同步事件
EV_KEY     0x01    按键事件
EV_REL     0x02    相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS     0x03    绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC     0x04    其它
EV_SW      0x05    开关
EV_LED     0x11    按键/设备灯
EV_SND     0x12    声音/警报
EV_REP     0x14    重复
EV_FF      0x15    力反馈
EV_PWR    0x16    电源
EV_FF_STATUS    0x17   力反馈状态
EV_MAX    0x1f    事件类型最大个数和提供位掩码支持

键值的定义在内核代码的include/uapi/linux/input-event-codes.h里面

#define MSC_SERIAL              0x00
#define MSC_PULSELED            0x01
#define MSC_GESTURE             0x02
#define MSC_RAW                 0x03
#define MSC_SCAN                0x04
#define MSC_TIMESTAMP           0x05
#define MSC_MAX                 0x07
#define MSC_CNT                 (MSC_MAX+1)

在probe函数里面还初始化了一个工作队列和定时器,然后在定时器回调函数里面调用了queue_work启动lis2dw12_acc_poll_function_work函数,该函数里面从寄存器里面获取到X、Y、Z三轴数据后通过input_event函数上报。

INIT_WORK(&cdata->sensors[LIS2DW12_ACCEL].input_work, lis2dw12_acc_poll_function_work);
···
lis2dw12_workqueue = create_workqueue(cdata->name);
···
hrtimer_init(&cdata->sensors[LIS2DW12_ACCEL].hr_timer, CLOCK_MONOTONIC,
		     HRTIMER_MODE_REL);
	cdata->sensors[LIS2DW12_ACCEL].hr_timer.function =
					&lis2dw12_hrtimer_acc_callback;
···
newTime = MS_TO_NS(sdata->poll_ms);
sdata->oldktime = ktime_set(0, newTime);
hrtimer_start(&sdata->hr_timer, sdata->oldktime, HRTIMER_MODE_REL);

static enum hrtimer_restart lis2dw12_hrtimer_acc_callback(struct hrtimer *timer)
{
	struct lis2dw12_sensor_data *sdata;

	sdata = container_of((struct hrtimer *)timer, struct lis2dw12_sensor_data,
			     hr_timer);

	sdata->timestamp = lis2dw12_get_time_ns();
	queue_work(lis2dw12_workqueue, &sdata->input_work);
	hrtimer_forward(timer, ktime_get(), sdata->oldktime);

	return HRTIMER_RESTART;
}

static void lis2dw12_acc_poll_function_work(struct work_struct *input_work)
{
	struct lis2dw12_sensor_data *sdata;
	sdata = container_of((struct work_struct *)input_work,
			     struct lis2dw12_sensor_data, input_work);

	lis2dw12_get_acc_data(sdata->cdata);
}

static int lis2dw12_get_acc_data(struct lis2dw12_data *cdata)
{
	u8 data[LIS2DW12_OUT_XYZ_SIZE];
	int err, xyz[3];
	struct lis2dw12_sensor_data *sdata = &cdata->sensors[LIS2DW12_ACCEL];

	err = cdata->tf->read(cdata, LIS2DW12_OUTX_L_ADDR, LIS2DW12_OUT_XYZ_SIZE,
			      data, true);
	if (err < 0) {
		dev_err(cdata->dev, "get acc data failed %d\n", err);
		return err;
	} else {
		xyz[0] = lis2dw12_data_align_bit(data[1], data[0], cdata->power_mode);
		xyz[1] = lis2dw12_data_align_bit(data[3], data[2], cdata->power_mode);
		xyz[2] = lis2dw12_data_align_bit(data[5], data[4], cdata->power_mode);

		xyz[0] *= sdata->c_gain;
		xyz[1] *= sdata->c_gain;
		xyz[2] *= sdata->c_gain;

		lis2dw12_report_3axes_event(sdata, xyz, sdata->timestamp);
	}
	
	return 0;
}

static void lis2dw12_report_3axes_event(struct lis2dw12_sensor_data *sdata,
				      s32 *xyz, s64 timestamp)
{
	struct input_dev *input = sdata->input_dev;

	if (!sdata->enabled)
		return;

	input_event(input, INPUT_EVENT_TYPE, INPUT_EVENT_X, xyz[0]);
	input_event(input, INPUT_EVENT_TYPE, INPUT_EVENT_Y, xyz[1]);
	input_event(input, INPUT_EVENT_TYPE, INPUT_EVENT_Z, xyz[2]);
	input_event(input, INPUT_EVENT_TYPE, INPUT_EVENT_TIME_MSB,
		    timestamp >> 32);
	input_event(input, INPUT_EVENT_TYPE, INPUT_EVENT_TIME_LSB,
		    timestamp & 0xffffffff);
	input_sync(input);//通常,用input_sync来完成对输入事件的上报。
}

lis2dw12驱动调试

从上面分析可以知道lis2dw12的上报过程,接下来可以通过编写一个测试用例来调试下上报的键值和数据等是不是正常的。上面说到,/dev/input/下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作,所下所示,测试代码里面先对/dev/input/目录进行扫描,然后使用open和read来打开设备文件和读取上报事件。

#define DEV_INPUT_EVENT "/dev/input"
#define EVENT_DEV_NAME "event"

#define TYPE_NAME_NUM 6
#define CODE_NAME_NUM 8
char type_name[TYPE_NAME_NUM][10] = {
	"EV_SYN",
	"EV_KEY",
	"EV_REL",
	"EV_ABS",
	"EV_MSC",
	"EV_SW",
};

char code_name[CODE_NAME_NUM][50] = {
	"INPUT_EVENT_X",
	"INPUT_EVENT_Y",
	"INPUT_EVENT_Z",
	"MSC_RAW",
	"INPUT_EVENT_TIME_MSB",
	"MSC_TIMESTAMP",
	"???",
	"INPUT_EVENT_TIME_LSB",
};

static int is_event_device(const struct dirent *dir) {
	return strncmp(EVENT_DEV_NAME, dir->d_name, 5) == 0;
}

static char* scan_devices(void)
{
	struct dirent **namelist;
	int i, ndev, devnum;
	char *filename;
	int max_device = 0;

	ndev = scandir(DEV_INPUT_EVENT, &namelist, is_event_device, versionsort);
	if (ndev <= 0)
	return NULL;

	fprintf(stderr, "Available devices:\n");

	for (i = 0; i < ndev; i++)
	{
	char fname[64];
	int fd = -1;
	char name[256] = "???";

	snprintf(fname, sizeof(fname),
		"%s/%s", DEV_INPUT_EVENT, namelist[i]->d_name);
	fd = open(fname, O_RDONLY);
	if (fd < 0)
		continue;
	ioctl(fd, EVIOCGNAME(sizeof(name)), name);

	fprintf(stderr, "%s:    %s\n", fname, name);
	close(fd);

	sscanf(namelist[i]->d_name, "event%d", &devnum);
	if (devnum > max_device)
		max_device = devnum;

	free(namelist[i]);
	}

	fprintf(stderr, "Select the device event number [0-%d]: ", max_device);
	scanf("%d", &devnum);

	if (devnum > max_device || devnum < 0)
	return NULL;

	asprintf(&filename, "%s/%s%d",
		DEV_INPUT_EVENT, EVENT_DEV_NAME,
		devnum);

	return filename;
}

static int print_events(int fd)
{
	struct input_event ev[64];
	int i, rd;
	int stop = 0;
	fd_set rdfs;

	FD_ZERO(&rdfs);
	FD_SET(fd, &rdfs);

	while (!stop) {
	select(fd + 1, &rdfs, NULL, NULL, NULL);
	if (stop)
		break;
	rd = read(fd, ev, sizeof(ev));

	if (rd < (int) sizeof(struct input_event)) {
		printf("expected %d bytes, got %d\n", (int) sizeof(struct input_event), rd);
		perror("\nevtest: error reading");
		return 1;
	}
	printf("--------------------------------------------\n");
	for (i = 0; i < rd / sizeof(struct input_event); i++) {
		unsigned int type, code;

		type = ev[i].type;
		code = ev[i].code;

		printf("Event: time %ld.%06ld, ", ev[i].time.tv_sec, ev[i].time.tv_usec);
		if (type < TYPE_NAME_NUM && code < CODE_NAME_NUM)
			printf("<%s><%s><value=%d>\n", type_name[ev[i].type], code_name[ev[i].code], ev[i].value);
		else
			printf("<type=%d><code=%d><value=%d>\n", ev[i].type, ev[i].code, ev[i].value);
	}

	}

	ioctl(fd, EVIOCGRAB, (void*)0);
	return EXIT_SUCCESS;
}

int main()
{
	int fd;
	char *filename = NULL;

	filename = scan_devices();
	if (!filename) {
	printf("scan_devices error!!!\n");
	return -1;
	}

	if ((fd = open(filename, O_RDONLY)) < 0) {
	printf("open error!!!\n");
	}

	print_events(fd);
	close(fd);

	return 0;
}

整个测试用例比较简单,主要功能是将上报的事件读取并打印,验证下上报的键值和数值是否正常,调试结果如下,可以看出,这些数据都是驱动里面上报的值,而且三个轴的数据随着方向的变化而变化,说明驱动是正常的:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值