linux input系統

         在linux中,像TP、G-sensor、按鍵等模塊都會用到input系統上報事件,下面我們看看input系統是如何實現事件上報的。

        Linux對input子系統進行了高度的抽象,將input系統具體分成三層,input核心層、input事件處理層和input設備驅動層。input核心層(input-core)對input設備驅動層(input-device)和input事件處理層(input-handler)進行管理並進行消息轉發。如下圖:

                              

  • 設備驅動層input-device

input設備驅動層對應的不同的設備驅動,像TP、鼠標、鍵盤等設備驅動,驅動中會註冊input系統,利用input系統上報事件,下面是一個簡單的設備驅動註冊input系統。

static int tiny210_init_input(void)
{
    int err = 0;
    printk(SF_LOG_LEVEL, "%s(..) enter.\n", __FUNCTION__);
    struct inout_dev *input = input_allocate_device(); //申請input設備

    if (!input) {
        printk(KERN_ERR, "input_allocate_device(..) failed.\n");
        return (-ENOMEM);
    }
	//填充input結構體數據
    input->name = "tiny210-keys";
    __set_bit(EV_KEY  ,   input->evbit );
    __set_bit(KEY_HOME,   input->keybit);
    __set_bit(KEY_MENU,   input->keybit);
    err = input_register_device(input); //註冊一個input設備

    if (err) {
        printk(KERN_ERR, "input_register_device(..) = %d.\n", err);
        input_free_device(input);
        input = NULL;
        return (-ENODEV);
    }

    printk(SF_LOG_LEVEL, "%s(..) leave.\n", __FUNCTION__);
    return err;
}

//有中斷或者按鍵事件上報按鍵事件,一般在中斷處理函數中
    key_code = KEY_HOME;
    input_report_key(input, key_code, kevent->value);
    input_sync(input);

      設備驅動層使用主要是去註冊一個input子系統,然後通過註冊的系統,上報想要上報的事件。從上面可以看出,驅動中使用input一般分爲4步,1、input_allocate_device申請input資源;2、填充input結構體,包括name、註冊的事件類型(如按鍵事件、絕對事件等等);3、input_register_device(input)在內核註冊一個input事件;4、當有按鍵或者TP報點產生時,同步上報按鍵事件,這一步一般會在中斷處理函數中進行。

    input_allocate_device和input_register_device都是input-core暴露出來的接口。

int input_register_device(struct input_dev *dev)
{
	static atomic_t input_no = ATOMIC_INIT(0);
	struct input_handler *handler;
	const char *path;
	int error;

	/* Every input device generates EV_SYN/SYN_REPORT events. */
	__set_bit(EV_SYN, dev->evbit);

	/* KEY_RESERVED is not supposed to be transmitted to userspace. */
	__clear_bit(KEY_RESERVED, dev->keybit);

	/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
	input_cleanse_bitmasks(dev);

	if (!dev->hint_events_per_packet)
		dev->hint_events_per_packet =
				input_estimate_events_per_packet(dev);

	/*
	 * If delay and period are pre-set by the driver, then autorepeating
	 * is handled by the driver itself and we don't do it in input.c.
	 */
	init_timer(&dev->timer);
	if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
		dev->timer.data = (long) dev;
		dev->timer.function = input_repeat_key;
		dev->rep[REP_DELAY] = 250;
		dev->rep[REP_PERIOD] = 33;
	}

	if (!dev->getkeycode)
		dev->getkeycode = input_default_getkeycode;

	if (!dev->setkeycode)
		dev->setkeycode = input_default_setkeycode;

	dev_set_name(&dev->dev, "input%ld",
		     (unsigned long) atomic_inc_return(&input_no) - 1);

    //設備驅動模型視圖中增加一個input設備
	error = device_add(&dev->dev);
	if (error)
		return error;

	path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
	pr_info("%s as %s\n",
		dev->name ? dev->name : "Unspecified device",
		path ? path : "N/A");
	kfree(path);

	error = mutex_lock_interruptible(&input_mutex);
	if (error) {
		device_del(&dev->dev);
		return error;
	}

    //鏈接到全局輸入設備鏈表
	list_add_tail(&dev->node, &input_dev_list);

    //匹配全局事件處理handler鏈表,匹配支持該輸入設備的handler
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();

	mutex_unlock(&input_mutex);

	return 0;
}
  • 事件層input-handler

     從上面的圖中可以看出,不同的設備會對應有不同的handler,我們以支持TP的event-handler爲例說明,event-handler代碼在driver/input/evdev.c中。

static const struct input_device_id evdev_ids[] = { //設備ID
	{ .driver_info = 1 },	/* Matches all devices */
	{ },			/* Terminating zero entry */
};

MODULE_DEVICE_TABLE(input, evdev_ids);

static struct input_handler evdev_handler = {
	.event		= evdev_event,  //消息處理接口
	.connect	= evdev_connect, // input-core匹配到device後調用的接口
	.disconnect	= evdev_disconnect, 
	.fops		= &evdev_fops, //管理改輸入操作的接口類
	.minor		= EVDEV_MINOR_BASE, //handler名稱
	.name		= "evdev",
	.id_table	= evdev_ids, //設備id,標識支持的device型號
};

static int __init evdev_init(void)
{
    //註冊handler,input_register_handler是input-core提供的接口
	return input_register_handler(&evdev_handler);
}

      input_register_handler函數在/driver/input/input.c中,也是input-core暴露出來的接口:

static struct input_handler *input_table[8];
//初始化全局input-handler列表
static LIST_HEAD(input_handler_list);
int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;
	int retval;

	retval = mutex_lock_interruptible(&input_mutex);
	if (retval)
		return retval;

	INIT_LIST_HEAD(&handler->h_list);

	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5]) {
			retval = -EBUSY;
			goto out;
		}
        //註冊的handler添加到input_table中,input_table最多支持8種handler(例如按鍵、鼠標爲不同hander),每種handler最多支持32個設備
		input_table[handler->minor >> 5] = handler; 
	}

    //添加到input-handler全局鏈表
	list_add_tail(&handler->node, &input_handler_list);

    //遍歷input_dev_list全局設備鏈表,尋找handler支持的設備
	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler); 

	input_wakeup_procfs_readers();

 out:
	mutex_unlock(&input_mutex);
	return retval;
}
  • 核心層input-core

      核心層input-core完成的工作包括:

     1) 直接跟字符設備驅動框架交互,字符設備驅動框架根據主設備號來進行管理,而input-core則是依賴於次設備號來進行分類管理。Input子系統的所有輸入設備的主設備號都是13,其對應input-core定義的structfile_operations input_fops.驅動架構層通過主設備號13獲取到input_fops,之後的處理便交給input_fops進行。

    2) 提供接口供事件處理層(input-handler)和輸入設備(input-device)註冊,併爲輸入設備找到匹配的事件處理者。

    3) 將input-device產生的消息(如觸屏座標和壓力值)轉發給input-handler,或者將input-handler的消息傳遞給input-device(如鼠標的閃燈命令)。

    input-core代碼主要在/driver/input/input.c中:

static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,
	.llseek = noop_llseek,
};

static int __init input_init(void)
{
	int err;

    //sys/class/下面創建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;

    //dev下面創建設備節點
	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
	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;
}
static int __init input_proc_init(void)
{
	struct proc_dir_entry *entry;

	proc_bus_input_dir = proc_mkdir("bus/input", NULL);
	if (!proc_bus_input_dir)
		return -ENOMEM;

    // bus/input下面建立devices節點
	entry = proc_create("devices", 0, proc_bus_input_dir,
			    &input_devices_fileops);
	if (!entry)
		goto fail1;

    // bus/input下面建立handlers節點
	entry = proc_create("handlers", 0, proc_bus_input_dir,
			    &input_handlers_fileops);
	if (!entry)
		goto fail2;

	return 0;

 fail2:	remove_proc_entry("devices", proc_bus_input_dir);
 fail1: remove_proc_entry("bus/input", NULL);
	return -ENOMEM;
}

      除了在/dev下面簡歷主設備號爲13的input設備節點,還會在設備模型/sys/class目錄註冊設備類,在/proc/bus/input目錄產生設備信息,可以在對應的節點下查看支持的device和handler。

  • input-core關聯匹配input-device和input-handler

      在上面的input_register_device和input_register_handler接口中,最後都會調用input_attach_handler接口來匹配輸入事件與對應的handler。

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;
	int error;

    //匹配handler和device的ID,event_handler默認處理所有的事件類型設備
	id = input_match_device(handler, dev);
	if (!id)
		return -ENODEV;

    //匹配成功後調用handler的connect接口,既evdev_connect
	error = handler->connect(handler, dev, id);
	if (error && error != -ENODEV)
		pr_err("failed to attach handler %s to device %s, error: %d\n",
		       handler->name, kobject_name(&dev->dev.kobj), error);

	return error;
}

//每個handler支持的最多設備數
#define EVDEV_MINORS		32
//evdev_table的每一個元素對應一個input-device
static struct evdev *evdev_table[EVDEV_MINORS];
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;
	int minor;
	int error;

    //從0開始找到一個未用的索引號
	for (minor = 0; minor < EVDEV_MINORS; minor++)
		if (!evdev_table[minor])
			break;

	if (minor == EVDEV_MINORS) {
		pr_err("no more free evdev devices\n");
		return -ENFILE;
	}

	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	if (!evdev)
		return -ENOMEM;

	INIT_LIST_HEAD(&evdev->client_list);
	spin_lock_init(&evdev->client_lock);
	mutex_init(&evdev->mutex);
	init_waitqueue_head(&evdev->wait);

    //次設備文件名,這裏是我們能看到的最終生成的設備文件event0、event1等
	dev_set_name(&evdev->dev, "event%d", minor);
	evdev->exist = true;
	evdev->minor = minor;
    //填充evdev結構體
	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;

    //在設備驅動視圖/sys/class/input和/sys/devices目錄下產生eventX設備
    //最終依賴event機制和mdev在/dev目錄生成對應的設備文件
	evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
	evdev->dev.class = &input_class;
	evdev->dev.parent = &dev->dev;
	evdev->dev.release = evdev_free;
	device_initialize(&evdev->dev);

    //註冊handler,關聯input-handler和input-device
	error = input_register_handle(&evdev->handle);
	if (error)
		goto err_free_evdev;
	//將evdev放入evdev_table中
	error = evdev_install_chrdev(evdev);
	if (error)
		goto err_unregister_handle;

	error = device_add(&evdev->dev);
	if (error)
		goto err_cleanup_evdev;

	return 0;

 err_cleanup_evdev:
	evdev_cleanup(evdev);
 err_unregister_handle:
	input_unregister_handle(&evdev->handle);
 err_free_evdev:
	put_device(&evdev->dev);
	return error;
}

      evdev_table代表evdev_handler所管理的底層input-device(通過input-handle管理)和應用層已打開該設備的進程、同步的相關結構和消息隊列(evdev_client記錄)。

struct evdev {
	int open;
	int minor;
    //管理handler對應的device
	struct input_handle handle;
	wait_queue_head_t wait;
    //記錄應用層已經open過eventX設備的進程、同步異步相關結構和消息池
	struct evdev_client __rcu *grab;
	struct list_head client_list;
	spinlock_t client_lock; /* protects client_list */
	struct mutex mutex;
	struct device dev;
	bool exist;
};

     input-handler與input-device是通過input-handle來關聯的,在input_register_handle函數中,將input-handle中的d_node和h_node分別關聯到input-device和input-handler中,這樣通過input-handler可以快速找到input-device,通過input-device也可以快速找到input-handler。

    至此,我們可以得到以下evdev-handler管理下的示意圖:

                                      

  • 應用層open、read操作

       input-device註冊成功後,會生成不同的節點,如下所示:

                                     

      應用層如果要得到相應的數據,例如得到按鍵操作的鍵值,會去open、read節點,例如open(dev/input/event0),下面我們看看open過程,設備節點的註冊是在input_init實現的,register_chrdev(INPUT_MAJOR, "input", &input_fops);open函數會調用input_open_file函數:

static int input_open_file(struct inode *inode, struct file *file)
{
	struct input_handler *handler;
	const struct file_operations *old_fops, *new_fops = NULL;
	int err;

	err = mutex_lock_interruptible(&input_mutex);
	if (err)
		return err;

	/* No load-on-demand here? */
    //iminor(inode)爲次設備號,根據次設備號找到註冊的input-handler
    //input_table在input_register_handler時會填充
	handler = input_table[iminor(inode) >> 5];
	if (handler)  //handler下面對應的fops
		new_fops = fops_get(handler->fops);

	mutex_unlock(&input_mutex);

	/*
	 * That's _really_ odd. Usually NULL ->open means "nothing special",
	 * not "no device". Oh, well...
	 */
	if (!new_fops || !new_fops->open) {
		fops_put(new_fops);
		err = -ENODEV;
		goto out;
	}

	old_fops = file->f_op;
	file->f_op = new_fops; 
    //執行對應的handler下面的open
	err = new_fops->open(inode, file);
	if (err) {
		fops_put(file->f_op);
		file->f_op = fops_get(old_fops);
	}
	fops_put(old_fops);
out:
	return err;
}

      input_open_file會通過查找不同的handler,找到對應的open函數,我們繼續已evdev_handler爲例。

static int evdev_open(struct inode *inode, struct file *file)
{
	struct evdev *evdev;
	struct evdev_client *client;
	int i = iminor(inode) - EVDEV_MINOR_BASE;
	unsigned int bufsize;
	int error;

	if (i >= EVDEV_MINORS)
		return -ENODEV;

	error = mutex_lock_interruptible(&evdev_table_mutex);
	if (error)
		return error;
    //input_attach_handler時填充
	evdev = evdev_table[i];
	if (evdev)
		get_device(&evdev->dev);
	mutex_unlock(&evdev_table_mutex);

	if (!evdev)
		return -ENODEV;

	bufsize = evdev_compute_buffer_size(evdev->handle.dev);

	client = kzalloc(sizeof(struct evdev_client) +
				bufsize * sizeof(struct input_event),
			 GFP_KERNEL);
	if (!client) {
		error = -ENOMEM;
		goto err_put_evdev;
	}

	client->bufsize = bufsize;
	spin_lock_init(&client->buffer_lock);
    //根據當前進程產生client的名稱
	snprintf(client->name, sizeof(client->name), "%s-%d",
			dev_name(&evdev->dev), task_tgid_vnr(current));
	wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
    //將代表打開該設備的進程相關的數據結構client和evdev綁定
	client->evdev = evdev;
	evdev_attach_client(evdev, client);

    //執行input-device層的open
	error = evdev_open_device(evdev);
	if (error)
		goto err_free_client;

	file->private_data = client;
	nonseekable_open(inode, file);

	return 0;

 err_free_client:
	evdev_detach_client(evdev, client);
	wake_lock_destroy(&client->wake_lock);
	kfree(client);
 err_put_evdev:
	put_device(&evdev->dev);
	return error;
}

       evdev_open_device會調用到設備驅動中的open,一般像TP、G-sensor註冊input系統沒有定義open接口。

        上面open獲得的fd句柄對應的file_operations是evdev_handler的evdev_fops,所以應用層read的時候最終會調用evdev_read接口。上面open過程中,我們看到evdev_client結構體,evdev_client中的buffer作爲一個消息隊列會保存input-core轉發上來的底層消息:

struct evdev_client {
	unsigned int head;
	unsigned int tail;
	unsigned int packet_head; /* [future] position of the first element of next packet */
	spinlock_t buffer_lock; /* protects access to buffer, head and tail */
	struct wake_lock wake_lock;
	char name[28];
    //異步通知上層等待進程
	struct fasync_struct *fasync;
	struct evdev *evdev;
	struct list_head node;
	unsigned int bufsize;
    //消息隊列,input-core轉發的底層觸屏消息會放到這裏
    //上層應用進程也會在這裏讀取
	struct input_event buffer[];
};

       下面看看evdev_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;
	int retval;

    //判斷讀取長度是否小於單個input-event的長度
	if (count < input_event_size())
		return -EINVAL;

    //判斷消息隊列消息是否爲空
	if (!(file->f_flags & O_NONBLOCK)) {
        //等待消息隊列不爲空事件
        //阻塞在這,等待wait釋放,消息更新
		retval = wait_event_interruptible(evdev->wait,
			 client->packet_head != client->tail || !evdev->exist);
		if (retval)
			return retval;
	}

	if (!evdev->exist)
		return -ENODEV;

    //將消息隊列的消息取出並通過copy_to_user填到buffer中返回 
	while (retval + input_event_size() <= count &&
	       evdev_fetch_next_event(client, &event)) {

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

		retval += input_event_size();
	}

	if (retval == 0 && file->f_flags & O_NONBLOCK)
		retval = -EAGAIN;
	return retval;
}

       當沒有數據時,wait_event_interruptible阻塞,上層進程將進入睡眠。直到有數據更新時,喚醒進程,那什麼時候會有數據更新了?我們在前面介紹input設備驅動的時候有說到input_report_key(input, key_code, kevent->value);和input_sync(input);數據就是在這裏更新的。以TP爲例,TP在產生中斷後,會觸發中斷處理函數,調度到中斷下部分後,會去讀取TP的報點,然後通過input_report_abs和input_sync接口將數據更新到input-core中。

      input_report_abs、input_report_key、input_sync都是input.h中暴露出來的接口,最後都是調用input-core層的input-event接口:

void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value)
{
	unsigned long flags;

	if (is_event_supported(type, dev->evbit, EV_MAX)) {

		spin_lock_irqsave(&dev->event_lock, flags);
		add_input_randomness(type, code, value);
		input_handle_event(dev, type, code, value);
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}
}
static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
	int disposition = INPUT_IGNORE_EVENT;

	switch (type) {

	case EV_SYN:
		switch (code) {
		case SYN_CONFIG:
			disposition = INPUT_PASS_TO_ALL;
			break;
	
	case EV_ABS:
        //檢測input-device初始化聲明是否支持該編碼ABS_MAX
		if (is_event_supported(code, dev->absbit, ABS_MAX))
			disposition = input_handle_abs_event(dev, code, &value);

		break;
//非同步事件
	if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
		dev->sync = false;
	//不是傳遞給input-core處理事件
	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);

	if (disposition & INPUT_PASS_TO_HANDLERS)
		input_pass_event(dev, type, code, value);
}
static void input_pass_event(struct input_dev *dev,
			     unsigned int type, unsigned int code, int value)
{
	struct input_handler *handler;
	struct input_handle *handle;

	rcu_read_lock();

    //輸入設備對應的input-handle,其handler就是evdev_handler
	handle = rcu_dereference(dev->grab);
	if (handle) //調用到evdev_event
		handle->handler->event(handle, type, code, value);
	……
}

static void evdev_event(struct input_handle *handle,
			unsigned int type, unsigned int code, int value)
{
	struct evdev *evdev = handle->private;
	struct evdev_client *client;
	struct input_event event;
	struct timespec ts;

    //填充輸入消息結構體
	ktime_get_ts(&ts);
	event.time.tv_sec = ts.tv_sec;
	event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
	event.type = type;
	event.code = code;
	event.value = value;

	rcu_read_lock();

	client = rcu_dereference(evdev->grab);
	if (client) //將消息寫入client結構體中的buffer中
		evdev_pass_event(client, &event);
	else
		list_for_each_entry_rcu(client, &evdev->client_list, node)
			evdev_pass_event(client, &event);

	rcu_read_unlock();
	//喚醒等待讀取消息進程,input_sync函數調用下來就會喚醒進程
	if (type == EV_SYN && code == SYN_REPORT)
		wake_up_interruptible(&evdev->wait);  
}

         wake_up_interruptible(&evdev->wait)調用後會喚醒執行在evdev_read中wait_event_interruptible等待讀取消息的進程,繼續下面的執行過程,從client的buffer中取出消息,並通過copy_to_user返回給應用程序。

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