第二期驅動篇——2.1 輸入子系統—框架分析

輸入子系統——框架分析

  • 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
  • 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
  • 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》
  • 開發環境:Linux 2.6.22.6 內核、arm-linux-gcc-3.4.5-glibc-2.3.6工具鏈


一、前言

  對於之前寫驅動的時候,我們都是在APP文件中打開了一個特定的設備文件如/dev/buttons,但是一般在實際應用過程中不會打開/dev/buttons。一般是直接scanf()就去獲得了按鍵的輸入。
  以前寫的那些驅動程序有一下缺點:

  1. 不通用,只能自已使用而非通用。
  2. 耦合性不高,無法讓其他應用程序“無縫”的使用。

  解決這個問題的答案就需要:使用現成的驅動——輸入子系統(input 子系統)把自已的設備相關的驅動放到內核中這種驅動架構中去
  在使用之前,我們需要分析輸入子系統(input 子系統)的大致框架

二、框架分析

對於輸入子系統,其主要文件在/driver/input.c

1、入口函數input_init()

分析:在這個入口函數中,通過register_chrdev(INPUT_MAJOR, "input", &input_fops);註冊了一個設備。

/* 入口函數 
 3. 註冊了一個設備
 */
static int __init input_init(void)
{
	/*........*/
	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
	/*........*/
}

2、file_operation結構體

分析:只有open函數,沒有其他的函數,肯定在open函數中進行了其他的操作

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

3、input_open_file函數

分析:在input_open_file()函數中

  1. 根據inode的此設備號找到對應的句柄hander
  2. 把該hander->fops賦值給新建file_operations結構體new_fops
  3. 保存file->f_op並將其賦值new_fops
  4. 調用new_fops->open()函數

可以看出這個函數屬於被調用函數,而且是通過一個設備的次設備號作爲索引找到對應的句柄。

static int input_open_file(struct inode *inode, struct file *file)
{
	/* 根據此設備號,在input_table[]數組裏面找到對應的input_handler句柄 */
	struct input_handler *handler = input_table[iminor(inode) >> 5];
	const struct file_operations *old_fops, *new_fops = NULL;
	int err;

	/* 通過上面得到的input_handler句柄找到對應的file_operation的fops結構體,並賦值給新建的new_fops */
	if (!handler || !(new_fops = fops_get(handler->fops)))
		return -ENODEV;

	/* 判斷new_fops結構體的open函數是否爲空 */
	if (!new_fops->open) {
		fops_put(new_fops);
		return -ENODEV;
	}
	/* 把打開文件的file_operation結構體賦值給old_fops */
	old_fops = file->f_op;
	
	/* 把新得到的new_fops賦值給打開文件的fops: 起中轉作用 */
	file->f_op = new_fops;
	
	/* 根據這個new_fops結構體調用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);
	return err;
}

4、誰負責存儲input_table[]這個數組的項——input_register_handler()函數

input_table[]原型

static struct input_handler *input_table[8];

input_register_handler()函數
分析:

  1. 句柄的的次設備號作爲索引,存儲在對應的input_table[ ]項中
  2. 把該句柄的node放入鏈表
  3. 對於每一個input_dev,都調用input_attach_handler(),根據input_handler的id_table判斷能否支持這個input_dev
/* 註冊handle函數 */
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_add_tail(&handler->node, &input_handler_list);

	// 對於每個input_dev,調用input_attach_handler
	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler); // 根據input_handler的id_table判斷能否支持這個input_dev
	/*.....*/
	return 0;
}

5、誰使用這個input_register_handler()函數——evdev.c

通過搜索可以查詢到如下文件會調用input_register_handler(),拿去其中的evdev.c驅動文件進行說明。
在這裏插入圖片描述

5.1 入口函數evdev_init()

在這個入口函數只調用了input_register_handler(),並且看見有evdev_handler這個變量。

 static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}

5.2 input_handler類型的結構體evdev_handler

對於這個結構體中的函數,我們下面會進行分析。

static struct input_handler evdev_handler = {
	.event 		=	evdev_event,		//事件
	.connect 	=	evdev_connect,		//聯繫
	.disconnect =	evdev_disconnect,	//不聯繫
	.fops 		=	&evdev_fops,		//file_operation結構體
	.minor 		=	EVDEV_MINOR_BASE,	//次設備號
	.name 		=	"evdev",			//設備名字
	.id_table	=	evdev_ids,			//用來綁定設備和驅動
};

6、大致框架

到目前爲止,我們可以分析出大致的框架,目前分析的是在純軟件中,那麼對於實際的硬件呢?下面就來分析下在input.c文件中input_register_device()函數。
在這裏插入圖片描述

7、input_register_device()函數

分析:

  1. 把該設備的node放入鏈表
  2. 對於每一個input_handler,都調用input_attach_handler(),根據input_handler的id_table判斷能否支持這個input_dev
int input_register_device(struct input_dev *dev)
{
	/*......*/
	
	// 放入鏈表
	list_add_tail(&dev->node, &input_dev_list);
	
	// 對於每一個input_handler,都調用input_attach_handler
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler); // 根據input_handler的id_table判斷能否支持這個input_dev

	input_wakeup_procfs_readers();

	return 0;
}

8、誰使用這個input_register_device()函數——具體的設備文件

通過搜索可以查詢到如下文件會調用input_register_device(),對於一個驅動文件可以支持多個設備文件,而input_register_device()函數就是把這些設備註冊進驅動文件中。
在這裏插入圖片描述

9、大致框架2

在這裏插入圖片描述

10、input_attach_handler()函數

分析:可以發現到input_register_device()input_register_handle()的處理過程很相似,而且都調用了input_attach_handler()函數
在這裏插入圖片描述
input_attach_handler()函數

分析:

  1. 進行匹配: 註冊input_devinput_handler時,會兩兩比較左邊的input_dev和右邊的input_handler
  2. 根據input_handler的id_table判斷這個input_handler能否支持這個input_dev
  3. 如果能支持,則調用input_handler的connect函數建立"連接"
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;
	int error;

	/* 進行匹配:註冊input_dev或input_handler時,會兩兩比較左邊的input_dev和右邊的input_handler */
	if (handler->blacklist && input_match_device(handler->blacklist, dev))
		return -ENODEV;

	/* 根據input_handler的id_table判斷這個input_handler能否支持這個input_dev */
	id = input_match_device(handler->id_table, dev);
	if (!id)
		return -ENODEV;

	/* 如果能支持,則調用input_handler的connect函數建立"連接" */
	error = handler->connect(handler, dev, id);
	if (error && error != -ENODEV)
		printk(KERN_ERR
			"input: failed to attach handler %s to device %s, "
			"error: %d\n",
			handler->name, kobject_name(&dev->cdev.kobj), error);

	return error;
}

11、如何建立連接——分析驅動文件evdev.c

對於不同的驅動,建立連接的方式都不一樣,這裏分析驅動文件evdev.cevdev_connect()函數

分析:

  1. 分配一個input_handle結構體
  2. 設置
    input_handle.dev = input_dev; // 指向左邊的input_dev
    input_handle.handler = input_handler; // 指向右邊的input_handler
  3. 註冊:調用input_register_handle()函數
    input_handler->h_list = &input_handle;
    inpu_dev->h_list = &input_handle;
int input_register_handle(struct input_handle *handle)
{
	struct input_handler *handler = handle->handler;

	//放入鏈表尾部
	list_add_tail(&handle->d_node, &handle->dev->h_list);	//dev->h_list
	list_add_tail(&handle->h_node, &handler->h_list);		//handler->h_list

	if (handler->start)
		handler->start(handle);

	return 0;
}
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	/*...........*/

	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一個input_handle

	/*...........*/
	// 設置
	evdev->handle.dev = dev;  // 指向左邊的input_dev
	evdev->handle.name = evdev->name;
	evdev->handle.handler = handler;  // 指向右邊的input_handler
	evdev->handle.private = evdev;
	sprintf(evdev->name, "event%d", minor);

	//放入到構建的數組中
	evdev_table[minor] = evdev;

	/*......*/

	// 註冊
	error = input_register_handle(&evdev->handle);
	
	/*......*/
}(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	/*...........*/

	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一個input_handle

	/*...........*/
	// 設置
	evdev->handle.dev = dev;  // 指向左邊的input_dev
	evdev->handle.name = evdev->name;
	evdev->handle.handler = handler;  // 指向右邊的input_handler
	evdev->handle.private = evdev;
	sprintf(evdev->name, "event%d", minor);

	//放入到構建的數組中
	evdev_table[minor] = evdev;

	/*......*/

	// 註冊
	error = input_register_handle(&evdev->handle);
	
	/*......*/
}

12、大致框架3

此時對於整個輸入子系統,它的大致框架如下:

  1. 分上下兩層:對於核心層:將純軟件(input_handler)部分硬件部分( input_dev)聯繫起來
  2. 在下層中,純軟部分通過input_register_handler()向上註冊處理方式——驅動文件硬件部分input_register_device()向上註冊硬件層——即具體的設備文件
  3. 二者註冊之後,會兩兩進行比較,查看其中的handler是否支持dev
  4. 若支持,則調用input_handler()結構體中的.connect函數進行連接
  5. 連接的方式:{
    ①、分配一個input_handle結構體
    ②、設置
    input_handle.dev = input_dev; // 指向左邊的input_dev
    input_handle.handler = input_handler; // 指向右邊的input_handler

    ③、註冊:調用input_register_handle()函數
    input_handler->h_list = &input_handle;
    inpu_dev->h_list = &input_handle;
  6. 連接之後,可用通過兩邊的結構體中.h_list鏈表中的任一一邊找到對方
    在這裏插入圖片描述

三、實際APP的read函數

也是引用evdec.c文件來說明
對於APP中調用read()函數,最終會調用到設備的evdev_read()函數

1、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;
	int retval;

	if (count < evdev_event_size())
		return -EINVAL;

	// 無數據並且是非阻塞方式打開,則立刻返回
	if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
		return -EAGAIN;

	// 否則休眠
	retval = wait_event_interruptible(evdev->wait,
		client->head != client->tail || !evdev->exist);
	
	/*........*/

	return retval;
}

2、evdev_event()事件函數

在這個函數進行對休眠函數進行激活

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
	/*.............*/
	
	wake_up_interruptible(&evdev->wait);	//激活
}

3、誰調用evdev_event()事件函數

猜:應該是硬件相關的代碼,input_dev那層調用
在設備的中斷服務程序裏,確定事件是什麼,然後調用相應的input_handler的event處理函數

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
	/*.............*/

		if (irq == gpio_to_irq(gpio)) {
			unsigned int type = button->type ?: EV_KEY;
			int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;
			
			// 上報事件
			input_event(input, type, button->code, !!state);
			input_sync(input);
		}
	}

	return IRQ_HANDLED;
}

4、input_event()函數

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	/*.....*/
	
	if (dev->grab)
		dev->grab->handler->event(dev->grab, type, code, value);
	else
		list_for_each_entry(handle, &dev->h_list, d_node)
			/* 如果handle打開,則調用該handle->handler->event */
			if (handle->open)
				handle->handler->event(handle, type, code, value);
}	

5、過程總結

  1. 應用程序讀,最終會導致 handler 中的read函數。
  2. 沒有數據可讀時就休眠有休眠就會喚醒,搜索的結果even函數來喚醒
  3. 猜測是input_dev層的設備中斷服務程序調用event函數。
  4. 通過這個event函數可以最終追蹤到純軟件部分的input_handler結構體中的.event成員。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章