1. 程式人生 > 其它 >linux驅動input子系統學習四(輸入事件驅動層)

linux驅動input子系統學習四(輸入事件驅動層)

技術標籤:# 輸入子系統linux

輸入事件層目前系統幫我們區分了三種,

通用各種型別輸入類裝置的evdev,主流,也是將來大方向

mousedev,joydev。

我們學習就以evdev來學習,將來使用通用性也更高。

一、首先是框架性的呼叫輸入核心層實現的input_register_handler和input_unregister_handler來註冊evdev層。

static struct input_handler evdev_handler = {	//事件驅動核心結構體
	.event		= evdev_event,			//打包資料,並上報事件(傳送一個事件)
	.events		= evdev_events,			//傳送多個事件
	.connect	= evdev_connect,		//找到匹配 dev 後進行連線
	.disconnect	= evdev_disconnect,		//斷開連線時使用
	.legacy_minors	= true,
	.minor		= EVDEV_MINOR_BASE,		//次裝置號基數 64	
	.name		= "evdev",				//處理程式的名稱,在/proc/bus/input/handlers 中顯示
	.id_table	= evdev_ids,			//匹配規則,evdev是所有都可以匹配
};

static int __init evdev_init(void)					//註冊事件驅動
{
	return input_register_handler(&evdev_handler);
}

static void __exit evdev_exit(void)					//解除安裝事件驅動
{
	input_unregister_handler(&evdev_handler);
}

module_init(evdev_init);
module_exit(evdev_exit);

evdev有個重要的資料結構,當輸入裝置和事件驅動匹配的時候,就會在connect函式建立dvdev裝置,最後註冊成字元裝置開放到使用者空間。

struct evdev {								// evdev 相當於一個橋樑,連結 input_dev 和 handler
	int open;								//開啟引用計數
	struct input_handle handle;				//關聯的input_handle
	wait_queue_head_t wait;					//等待佇列
	struct evdev_client __rcu *grab;
	struct list_head client_list;			//evdev_client 連結串列,說明一個evdev裝置可以處理多個evdev_client,可以有多個程序訪問
	spinlock_t client_lock; /* protects client_list */
	struct mutex mutex;
	struct device dev;						//dev 是 evdev 的父裝置
	struct cdev cdev;						//cdev字元裝置
	bool exist;								//判斷此 evdev 釋放存在
};

二、介面實現

1.首先我們看一下connect函式,它在註冊handler和dev時,如果匹配上了就會呼叫。(具體可以檢視我的輸入子系統學習三)

/*
 * Create new evdev device. Note that input core serializes calls
 * to connect and disconnect.
 */
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)								//建立新的 evdev 裝置。輸入核心有序連線和斷開
{
	struct evdev *evdev;
	int minor;
	int dev_no;
	int error;

	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);		//申請未使用的minor
	if (minor < 0) {
		error = minor;
		pr_err("failed to reserve new minor: %d\n", error);
		return error;
	}

	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);						//申請 sizeof(struct evdev) 的空間
	if (!evdev) {
		error = -ENOMEM;
		goto err_free_minor;
	}

	INIT_LIST_HEAD(&evdev->client_list);									//初始化 client_list 連結串列
	spin_lock_init(&evdev->client_lock);									//初始化 client_lock 自旋鎖
	mutex_init(&evdev->mutex);												//初始化 evdev 的互斥量
	init_waitqueue_head(&evdev->wait);										//初始化 evdev 的等待佇列
	evdev->exist = true;													//此 evdev 為存在

	dev_no = minor;															//申請到的次裝置號
	/* Normalize device number if it falls into legacy range */
	if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
		dev_no -= EVDEV_MINOR_BASE;											//判斷此 evdev 裝置申請的是 32 箇中的哪一個
	dev_set_name(&evdev->dev, "event%d", dev_no);							//設定此 evdev->dev 的名字

	evdev->handle.dev = input_get_device(dev);								//增加 evdev->dev 的引用計數
	evdev->handle.name = dev_name(&evdev->dev);								//設定名字 event0,1,2,3....
	evdev->handle.handler = handler;										//指向匹配到的 handler
	evdev->handle.private = evdev;											//指向自己 evdev

	evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);							//由輸入裝置的主裝置號13和此裝置號合成一個裝置號				
	evdev->dev.class = &input_class;										//設定 evdev 的類為 input_class
	evdev->dev.parent = &dev->dev;											//輸入裝置 input_dev 是 evdev 是的父裝置
	evdev->dev.release = evdev_free;										//釋放 evdev 資源
	device_initialize(&evdev->dev);											//內部 device 初始化

	error = input_register_handle(&evdev->handle);							//註冊 handle
	if (error)
		goto err_free_evdev;

	cdev_init(&evdev->cdev, &evdev_fops);									//用 evdev_fops 初始化一個 cdev 字元裝置
	evdev->cdev.kobj.parent = &evdev->dev.kobj;
	error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);						//新增一個字元裝置到系統中
	if (error)
		goto err_unregister_handle;

	error = device_add(&evdev->dev);										//真正的建立裝置層次和attribute在sysfs中
	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);
 err_free_minor:
	input_free_minor(minor);
	return error;
}

2.disconnect,和connect相反

static void evdev_disconnect(struct input_handle *handle)
{
	struct evdev *evdev = handle->private;					//handle 的私有資料就是 handle 本身 evdev

	device_del(&evdev->dev);
	evdev_cleanup(evdev);
	input_free_minor(MINOR(evdev->dev.devt));
	input_unregister_handle(handle);
	put_device(&evdev->dev);
}

3.對一個裝置驅動層傳送過來的事件打包

/*
 * Pass incoming events to all connected clients.					傳遞來到的事件給所有連線的客戶端
 */
static void evdev_events(struct input_handle *handle,
			 const struct input_value *vals, unsigned int count)	
{
	struct evdev *evdev = handle->private;							//此處的私有資料就是 evdev 本身
	struct evdev_client *client;
	ktime_t ev_time[EV_CLK_MAX];

	ev_time[EV_CLK_MONO] = ktime_get();								//設定對應的時間
	ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);
	ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],
						 TK_OFFS_BOOT);

	rcu_read_lock();

	client = rcu_dereference(evdev->grab);

	if (client)														//如果該evdev有個專用的client,那麼就將事件發給它如果該evdev
		evdev_pass_values(client, vals, count, ev_time);
	else
		list_for_each_entry_rcu(client, &evdev->client_list, node)
			evdev_pass_values(client, vals, count, ev_time);		//否則,發給 evdev 客戶連結串列上的所有客戶

	rcu_read_unlock();
}

/*
 * Pass incoming event to all connected clients.
 */
static void evdev_event(struct input_handle *handle,
			unsigned int type, unsigned int code, int value)	//傳遞輸入進來的事件給所有的客戶
{
	struct input_value vals[] = { { type, code, value } };

	evdev_events(handle, vals, 1);
}

傳送資料給client函式

static void __pass_event(struct evdev_client *client,
			 const struct input_event *event)						//使用非同步通知函式通知上層
{
	client->buffer[client->head++] = *event;						//把 *event 放在 struct evdev_client 的緩衝區中
	client->head &= client->bufsize - 1;

	if (unlikely(client->head == client->tail)) {
		/*
		 * This effectively "drops" all unconsumed events, leaving
		 * EV_SYN/SYN_DROPPED plus the newest event in the queue.
		 */
		client->tail = (client->head - 2) & (client->bufsize - 1);

		client->buffer[client->tail].time = event->time;
		client->buffer[client->tail].type = EV_SYN;
		client->buffer[client->tail].code = SYN_DROPPED;
		client->buffer[client->tail].value = 0;

		client->packet_head = client->tail;
	}

	if (event->type == EV_SYN && event->code == SYN_REPORT) {		//事件的型別是同步型別,事件的編碼是隻上報一次
		client->packet_head = client->head;							//下一次從緩衝區的第一個位置取資料
		kill_fasync(&client->fasync, SIGIO, POLL_IN);				//傳送一個非同步通知,通知該開啟該client的應用程式,執行訊號處理函式
	}
}

static void evdev_pass_values(struct evdev_client *client,
			const struct input_value *vals, unsigned int count,
			ktime_t *ev_time)										//把事件發給 evdev_client,喚醒等待佇列
{
	struct evdev *evdev = client->evdev;							//獲取此客戶端要訪問的 evdev 裝置
	const struct input_value *v;
	struct input_event event;
	bool wakeup = false;											//預設不喚醒

	if (client->revoked)											//如果保留了,直接返回
		return;

	event.time = ktime_to_timeval(ev_time[client->clk_type]);		//根據客戶端的時間型別,獲取對應的時間

	/* Interrupts are disabled, just acquire the lock. */			//中斷被關閉,獲取鎖
	spin_lock(&client->buffer_lock);

	for (v = vals; v != vals + count; v++) {						//迴圈傳送
		event.type = v->type;
		event.code = v->code;
		event.value = v->value;
		__pass_event(client, &event);								//使用非同步通知函式通知上層
		if (v->type == EV_SYN && v->code == SYN_REPORT)				//事件的型別是同步型別,事件的編碼是隻上報一次,此時要喚醒等待佇列
			wakeup = true;
	}

	spin_unlock(&client->buffer_lock);

	if (wakeup)
		wake_up_interruptible(&evdev->wait);						//喚醒 evdev 的等待佇列
}

4.應用層的呼叫介面file_operation

static const struct file_operations evdev_fops = {
	.owner		= THIS_MODULE,
	.read		= evdev_read,
	.write		= evdev_write,
	.poll		= evdev_poll,
	.open		= evdev_open,
	.release	= evdev_release,
	.unlocked_ioctl	= evdev_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= evdev_ioctl_compat,
#endif
	.fasync		= evdev_fasync,
	.flush		= evdev_flush,
	.llseek		= no_llseek,
};

操作函式比較多,我們主要分析連個最常用的,open,和read

當我們使用open開啟時,會建立evdev_client 和 ecdev字元裝置建立聯絡,通過evdev的handle找到對應的device,開啟此裝置。

static int evdev_open(struct inode *inode, struct file *file)
{
	struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);	//由 inode->i_cdev 成員獲取 evdev 裝置
	unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
	unsigned int size = sizeof(struct evdev_client) +
					bufsize * sizeof(struct input_event);
	struct evdev_client *client;
	int error;

	client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);				//申請 size 大小的記憶體
	if (!client)
		client = vzalloc(size);
	if (!client)
		return -ENOMEM;

	client->bufsize = bufsize;										//設定事件緩衝區的大小
	spin_lock_init(&client->buffer_lock);
	client->evdev = evdev;											//此次開啟字元裝置所使用的 evdev 
	evdev_attach_client(evdev, client);								//把此 dvdev 附著在 evdev_client

	error = evdev_open_device(evdev);								//開啟此 evdev 字元裝置
	if (error)
		goto err_free_client;

	file->private_data = client;									//file 的私有資料設定為 evdev_client
	nonseekable_open(inode, file);

	return 0;

 err_free_client:
	evdev_detach_client(evdev, client);
	kvfree(client);
	return error;
}
int input_open_device(struct input_handle *handle)
{
	struct input_dev *dev = handle->dev;
	int retval;

	retval = mutex_lock_interruptible(&dev->mutex);
	if (retval)
		return retval;

	if (dev->going_away) {
		retval = -ENODEV;
		goto out;
	}

	handle->open++;				//增加引用計數

	if (!dev->users++ && dev->open)
		retval = dev->open(dev);		//.open		= input_proc_devices_open, 開啟輸入裝置

	if (retval) {
		dev->users--;
		if (!--handle->open) {
			/*
			 * Make sure we are not delivering any more events
			 * through this handle
			 */
			synchronize_rcu();
		}
	}

 out:
	mutex_unlock(&dev->mutex);
	return retval;
}

對應應用層的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;
		
		/* 當client緩衝區無資料;evdev不存在;檔案非阻塞開啟,那個read直接返回錯誤 */
		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;

		/* 當要讀取的資料大於struct input_event且client裡面的buffer有資料,則把資料拷貝到使用者空間*/
		while (read + input_event_size() <= count &&
		       evdev_fetch_next_event(client, &event)) {

			if (input_event_to_user(buffer + read, &event))	/* 對copy_to_user的封裝 */
				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;
}