linux-输入子系统

引言

本章概述linux kernel输入子系统框架,理解input各层的作用和实现的意义。

输入子系统框架

输入子系统是将所有输入设备进行分类,并将其驱动分为事件处理层和设备描述层。
其中,设备描述层,来描述本设备所能够产生的事件,而对应的事件处理层,对设备产生的事件进行处理。在内核中对应的结构体为input_handler和input_device,二者注册进内核,并通过id_table进行匹配。
当例如按键按下后,其通过input_device产生了一个事件,该事件进入input核心层,找到该事件对应的input_handler,如果上层进行了系统调用,则调用input_handler中fops的相应接口。

输入子系统分为:核心层、事件驱动层和设备(描述)层。

核心层

内核提供给输入子系统核心代码,它提供驱动层和设备层所需的一些接口。
核心层相关代码分析如下:

Input.c:
    Input_init:
	    Register_chrdev(INPUT_MAJOR, "input", &input_fops);
注册输入子系统核心层的fops至内核链表,该fops定义为:
static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
};
该fops只有open,上层open时将进入该open函数

Input_open_file:
//该函数参数为设备号inode和file结构体
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *new_fops = fops_get(handler->fops);
//将从input_table中获得的handler结构体里面的fops,赋值给file的fops,并执行,这里相当于替换了输入子系统核心层的fops
file->f_op = new_fops;
err = new_fops->open(inode, file);
//综上,上层Open(Cat/hexdump/app)输入子系统设备,实际执行的是input_table中的handler,handler中的fops,fops中的open函数

input_table中的handler怎么来的?
input_table为静态全局变量,只在该input.c中被使用,查找可得赋值是在:

int input_register_handler(struct input_handler *handler):
	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5])
			return -EBUSY;

		input_table[handler->minor >> 5] = handler;
	}
	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

通过input_register_handler将input_handler结构体插入input_tabel表中。循环读取input_dev_list中的dev成员,调用input_attach_handler判断handler中的id_table能否支持这个dev。
匹配过程如下:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    id = input_match_device(handler->id_table, dev);
    error = handler->connect(handler, dev, id);
}

static const struct input_device_id *input_match_device(const struct input_device_id *id,
							struct input_dev *dev)
{
	int i;
	for (; id->flags || id->driver_info; id++) {
		if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
			if (id->bustype != dev->id.bustype)
				continue;
 
		if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
			if (id->vendor != dev->id.vendor)
				continue;
		if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
			if (id->product != dev->id.product)
				continue;
		if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
			if (id->version != dev->id.version)
				continue;
		MATCH_BIT(evbit,  EV_MAX);
	
		for (i = 0; i < BITS_TO_LONGS(EV_MAX); i++){
			//如果 id 的支持项 > = dev MATCH_BIT 下一项,或者id==0 进行下一项
			if ((id->evbit[i] & dev->evbit[i]) != id->evbit[i]) 
				break; 
		}
		if (i != BITS_TO_LONGS(EV_MAX))
			continue;
	
		MATCH_BIT(keybit, KEY_MAX);
		MATCH_BIT(relbit, REL_MAX);
		MATCH_BIT(absbit, ABS_MAX);
		MATCH_BIT(mscbit, MSC_MAX);
		MATCH_BIT(ledbit, LED_MAX);
		MATCH_BIT(sndbit, SND_MAX);
		MATCH_BIT(ffbit,  FF_MAX);
		MATCH_BIT(swbit,  SW_MAX);
		return id;
	}
	return NULL;
}

总结匹配过程即:将input_dev中的flag和evbit,同input_handler中的id_table进行比较。
如果input_handler中的id_table为NULL,例如evdev.c,那么由input_match_device可知,它可以和任何input_dev相匹配。
那么,假设有一个按键设备,该设备将同时和evdev以及keyboard的handler匹配上。即多个dev可以和一个handler匹配。

事件处理层(input_handler层)

针对input_dev中发起的事件作出相应。
以事件驱动输入子系统evdev.c为例:

//入口函数: 
static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}
//evdev的handler结构体:
static struct input_handler evdev_handler = {
	.event =	evdev_event,	       //事件上报
	.connect =	evdev_connect,  	   //连接dev和handler
	.disconnect =	evdev_disconnect,
	.fops =		&evdev_fops,         //文件操作接口
	.minor =	EVDEV_MINOR_BASE,    //event0的次设备号
	.name =		"evdev",
	.id_table =	evdev_ids,	       //设备id
};
//设备id:
static const struct input_device_id evdev_ids[] = {
	{ .driver_info = 1 },	/* Matches all devices */
	{ },			/* Terminating zero entry */
};
其中,struct input_device_id *id_table用于和dev中的xxbit进行比较,来判断是否匹配。
如此,在evdev_init中完成了evdev的handler的注册

=====================================
此外,一个设备dev可以被多个handler匹配,从而执行相应的kbd_event
keyboard.c:
static struct input_handler kbd_handler = {
	.event		= kbd_event,
	.connect	= kbd_connect,
	.disconnect	= kbd_disconnect,
	.start		= kbd_start,
	.name		= "kbd",
	.id_table	= kbd_ids,
};
static const struct input_device_id kbd_ids[] = {
	{
                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
                .evbit = { BIT(EV_KEY) },
        },

	{
                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
                .evbit = { BIT(EV_SND) },
        },

	{ },    /* Terminating entry */
};

input_dev层

一般情况下,这里是我们需要开发的代码。
linux内核支持的输入设备有:鼠标、按键、触摸屏、手柄等。这些设备对应的驱动已经存在在内核中了。
我们需要做的是写一个input_dev,该input_dev中设定了设备会向输入子系统中输入什么事件。
示例:

/* 参考drivers\input\keyboard\gpio_keys.c */

#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>

#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>

struct pin_desc{
	int irq;
	char *name;
	unsigned int pin;
	unsigned int key_val;
};

struct pin_desc pins_desc[4] = {
	{IRQ_EINT0,  "S2", S3C2410_GPF0,   KEY_L},
	{IRQ_EINT2,  "S3", S3C2410_GPF2,   KEY_S},
	{IRQ_EINT11, "S4", S3C2410_GPG3,   KEY_ENTER},
	{IRQ_EINT19, "S5",  S3C2410_GPG11, KEY_LEFTSHIFT},
};

static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	/* 10ms后启动定时器 */
	irq_pd = (struct pin_desc *)dev_id;
	mod_timer(&buttons_timer, jiffies+HZ/100);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static void buttons_timer_function(unsigned long data)
{
	struct pin_desc * pindesc = irq_pd;
	unsigned int pinval;

	if (!pindesc)
		return;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 : 最后一个参数: 0-松开, 1-按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
		input_sync(buttons_dev);
	}
	else
	{
		/* 按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
		input_sync(buttons_dev);
	}
}

static int buttons_init(void)
{
	int i;
	
	/* 1. 分配一个input_dev结构体 */
	buttons_dev = input_allocate_device();;

	/* 2. 设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, buttons_dev->evbit);
	set_bit(EV_REP, buttons_dev->evbit);
	
	/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
	set_bit(KEY_L, buttons_dev->keybit);
	set_bit(KEY_S, buttons_dev->keybit);
	set_bit(KEY_ENTER, buttons_dev->keybit);
	set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);

	/* 3. 注册 */
	input_register_device(buttons_dev);
	
	/* 4. 硬件相关的操作 */
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	add_timer(&buttons_timer);
	
	for (i = 0; i < 4; i++)
	{
		request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
	}
	
	return 0;
}

static void buttons_exit(void)
{
	int i;
	for (i = 0; i < 4; i++)
	{
		free_irq(pins_desc[i].irq, &pins_desc[i]);
	}

	del_timer(&buttons_timer);
	input_unregister_device(buttons_dev);
	input_free_device(buttons_dev);	
}

module_init(buttons_init);

module_exit(buttons_exit);

MODULE_LICENSE("GPL");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值