輸入設備驅動框架

目錄

  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發送事件之後們就會喚醒這個阻塞的操作,這樣用戶就可以讀取到輸入事件的值了

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