输入设备驱动框架

目录

  1. 输入设备驱动框架简介
  2. 输入核心层
  3. 设备驱动程序
  4. 输入事件驱动程序

输入设备驱动框架简介

输入设备(案件、键盘、鼠标、触摸屏等)是典型的字符设备。该类驱动的工作特点是,底层在进行按键或者触屏等其他动作时会产生一个中断,或者驱动中定时获取底层的操作,当检测到动作发生的时候,回去读取键值或者座标数据,然后将数据存储在一段缓冲区中,字符设备驱动负责管理者这块缓冲区,而用户则直接通过字符设备驱动的用户接口进行键值的读取。

这些操作中,只有中断键值适合具体的设备相关,但是对于缓冲区的管理是和具体的设备无关的,所以linux中加入了一个input的核心层代码专门用于处理公共的工作,这样才符合linux下的驱动框架。

输入事件驱动程序:drivers\input\dvdev.c drivers\input\joydev.c drivers\input\mousedev.c 

输入核心:drivers\input\input.c

输入设备驱动程序:drivers\input\gpio_keys.c

输入核心层

输入核心层提供了底层的设备驱动程序需要的API,申请和释放一个输入设备

struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);

申请完设备之后,需要将设备注册到内核中:函数中使用的参数就是input_allocate_device函数的返回值

int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);

还提供了一些报告输入事件的接口:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/*报告指定type、code的输入事件*/
void input_report_key(struct input_dev *dev, unsigned int code, int 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_sync(struct input_dev *dev);
/*报告同步事件*/

input_report_key、input_report_rel、input_report_abs其实只是type不同,最终调用的还是input_event函数,一般调用完上述这四个函数之后,都需要调用input_sync,使上述发送的报告事件生效。

所有的事件都可以使用一个结构体表示:

struct input_event {
	struct timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};

设备驱动程序

以gpio_keys.c为例,本文件实现了一套通用的GPIO按键驱动,因为它将硬件相关配置瓶坯在版文件中,并且符合platform驱动框架,所以可以使用在各个处理器中。

static int gpio_keys_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
	struct gpio_keys_drvdata *ddata;
	struct input_dev *input;
	size_t size;
	int i, error;
	int wakeup = 0;

	if (!pdata) {
		pdata = gpio_keys_get_devtree_pdata(dev);
		if (IS_ERR(pdata))
			return PTR_ERR(pdata);
	}

	size = sizeof(struct gpio_keys_drvdata) +
			pdata->nbuttons * sizeof(struct gpio_button_data);
	ddata = devm_kzalloc(dev, size, GFP_KERNEL);
	if (!ddata) {
		dev_err(dev, "failed to allocate state\n");
		return -ENOMEM;
	}

	input = devm_input_allocate_device(dev);
	if (!input) {
		dev_err(dev, "failed to allocate input device\n");
		return -ENOMEM;
	}

	ddata->pdata = pdata;
	ddata->input = input;
	mutex_init(&ddata->disable_lock);

	platform_set_drvdata(pdev, ddata);
	input_set_drvdata(input, ddata);

	input->name = pdata->name ? : pdev->name;
	input->phys = "gpio-keys/input0";
	input->dev.parent = &pdev->dev;
	input->open = gpio_keys_open;
	input->close = gpio_keys_close;

	input->id.bustype = BUS_HOST;
	input->id.vendor = 0x0001;
	input->id.product = 0x0001;
	input->id.version = 0x0100;

	/* Enable auto repeat feature of Linux input subsystem */
	if (pdata->rep)
		__set_bit(EV_REP, input->evbit);

	for (i = 0; i < pdata->nbuttons; i++) {
		const struct gpio_keys_button *button = &pdata->buttons[i];
		struct gpio_button_data *bdata = &ddata->data[i];

		error = gpio_keys_setup_key(pdev, input, bdata, button);
		if (error)
			return error;

		if (button->wakeup)
			wakeup = 1;
	}

	error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
	if (error) {
		dev_err(dev, "Unable to export keys/switches, error: %d\n",
			error);
		return error;
	}

	error = input_register_device(input);
	if (error) {
		dev_err(dev, "Unable to register input device, error: %d\n",
			error);
		goto err_remove_group;
	}

	device_init_wakeup(&pdev->dev, wakeup);

	return 0;

err_remove_group:
	sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
	return error;
}

函数中主要完成了三个工作:

  1. 分配一个输入设备devm_input_allocate_device
  2. 53-63行对这个dpio进行了配置,包括申请中断等
  3. input_register_device注册这个设备到linux中

完成初始化工作之后,只要等案件或者其他输入动作发生之后,进行数据的读取并且报告事件即可

static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
	struct gpio_button_data *bdata = dev_id;
	const struct gpio_keys_button *button = bdata->button;
	struct input_dev *input = bdata->input;
	unsigned long flags;

	BUG_ON(irq != bdata->irq);

	spin_lock_irqsave(&bdata->lock, flags);

	if (!bdata->key_pressed) {
		if (bdata->button->wakeup)
			pm_wakeup_event(bdata->input->dev.parent, 0);

		input_event(input, EV_KEY, button->code, 1);
		input_sync(input);

		if (!bdata->release_delay) {
			input_event(input, EV_KEY, button->code, 0);
			input_sync(input);
			goto out;
		}

		bdata->key_pressed = true;
	}

	if (bdata->release_delay)
		mod_timer(&bdata->release_timer,
			jiffies + msecs_to_jiffies(bdata->release_delay));
out:
	spin_unlock_irqrestore(&bdata->lock, flags);
	return IRQ_HANDLED;
}

函数中最重要的两行代码如下所示:

input_event(input, EV_KEY, button->code, 0);
input_sync(input);

GPIO按键驱动通过这两个函数来汇报按键事件以及同步事件

输入事件驱动程序

用户最终获取按键数据是通过drivers\input\dvdev.c drivers\input\joydev.c drivers\input\mousedev.c 中的read函数

static ssize_t evdev_read(struct file *file, char __user *buffer,
			  size_t count, loff_t *ppos)
{
	struct evdev_client *client = file->private_data;
	struct evdev *evdev = client->evdev;
	struct input_event event;
	size_t read = 0;
	int error;

	if (count != 0 && count < input_event_size())
		return -EINVAL;

	for (;;) {
		if (!evdev->exist || client->revoked)
			return -ENODEV;

		if (client->packet_head == client->tail &&
		    (file->f_flags & O_NONBLOCK))
			return -EAGAIN;

		/*
		 * count == 0 is special - no IO is done but we check
		 * for error conditions (see above).
		 */
		if (count == 0)
			break;

		while (read + input_event_size() <= count &&
		       evdev_fetch_next_event(client, &event)) {

			if (input_event_to_user(buffer + read, &event))
				return -EFAULT;

			read += input_event_size();
		}

		if (read)
			break;

		if (!(file->f_flags & O_NONBLOCK)) {
			error = wait_event_interruptible(evdev->wait,
					client->packet_head != client->tail ||
					!evdev->exist || client->revoked);
			if (error)
				return error;
		}
	}

	return read;
}

函数的17-19行首先判断是否是阻塞读,如果不是则直接退出报错,因为这种的事件只能阻塞读,41-43就是阻塞等待,当设备驱动调用input_event发送事件之后们就会唤醒这个阻塞的操作,这样用户就可以读取到输入事件的值了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章