天天看點

S3C2440 輸入子系統概念介紹(十二)

https://www.cnblogs.com/lifexy/p/7542989.html

輸入子系統概念介紹

    在以前的按鍵程式上,有個缺點,沒辦法用在被人的現成的應用程式上,比如QT等應用程式。别的應用程式不會打開/dev/buttons,有可能打開現成的裝置/dev/tty。也有可能直接調用scanf(),就可以擷取按鍵的輸入。

    以前寫的應用程式,隻有你知道怎麼用,但是其他人不知道怎麼用。

寫出一個通用的驅動程式,讓已經現成的應用程式無縫的移植到你的單闆上。(無縫就是不需要修改别人的應用程式)

使用現成的驅動。在核心裡面現成的驅動程式,把自己需要的東西融合進去。(現成的驅動程式就是輸入input子系統)

以前的字元裝置驅動程式步驟:

1、确定主裝置号major;

2、構造一個file_operations結構體(open函數、read函數、write函數);

3、告訴核心,register_chrdev,注冊字元裝置驅動程式; 

4、入口函數調用注冊函數; 

5、有入口函數,必然有出口函數;

一、輸入(input)子系統架構:(現成的架構,系統已做好,現成的架構也有上面的幾個步驟)

打開核心層input.c(中轉作用),位于drivers\input目錄下

subsys_initcall(input_init);
module_exit(input_exit);
           

入口函數input_init,出口函數input_exit。

二、顯然輸入子系統是作為一個子產品存在,我們先來分析一下input_init()入口函數

static int __init input_init(void)
{
	int err;

	err = class_register(&input_class);
	if (err) {
		printk(KERN_ERR "input: unable to register input_dev class\n");
		return err;
	}

	err = input_proc_init();
	if (err)
		goto fail1;

	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
	if (err) {
		printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
		goto fail2;
	}

	return 0;

 fail2:	input_proc_exit();
 fail1:	class_unregister(&input_class);
	return err;
}
           

(1)上面第15行,register_chrdev(INPUT_MAJOR, "input", &input_fops);,以前注冊裝置需要自己寫的,現在核心代碼裡面有了。注冊了主裝置号為13。看一下input_fops(file_operantions)結構體:

S3C2440 輸入子系統概念介紹(十二)

為什麼隻有一個open函數,不是要讀按鍵嗎?

顯然open函數做了某些工作。

三、然後進入input_open_file函數裡面:

static int input_open_file(struct inode *inode, struct file *file)
{
	struct input_handler *handler = input_table[iminor(inode) >> 5];    //(1)
	const struct file_operations *old_fops, *new_fops = NULL;
	int err;

	if (!handler || !(new_fops = fops_get(handler->fops)))    //(2)
		return -ENODEV;

	if (!new_fops->open) {
		fops_put(new_fops);
		return -ENODEV;
	}
	old_fops = file->f_op;
	file->f_op = new_fops;    //(3)

	err = new_fops->open(inode, file);    //(4)

	if (err) {
		fops_put(file->f_op);
		file->f_op = fops_get(old_fops);
	}
	fops_put(old_fops);
	return err;
}

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

(1)第三行中, iminor(inode)調用了MINOR(inode->i_rdev)來擷取次裝置号,将次裝置号處理32。input_handler輸入處理器(處理句柄),handler=input_table。input_table數組,根據次裝置号所打開的檔案指派給handler。

(2)第七行中,input_handler有個file_operations結構體,fops_get那個file_operations結構體。

(3)第十五行中,現在所打開的檔案f_op 等于 新的f_op(file_operantions結構體)。

(4)第十七行中,調用新的f_op的open函數。

以後應用程式app讀read時,最終會調用到file->f_op->read。

input_table數組由誰來構造?

四、input_register_handler會構造input_table數組:

int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;

	INIT_LIST_HEAD(&handler->h_list);

	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5])
			return -EBUSY;

		input_table[handler->minor >> 5] = handler;    //(1)
	}

	list_add_tail(&handler->node, &input_handler_list);

	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();
	return 0;
}
           

五、搜尋一下input_register_handler函數會被誰調用:

我們可以從下圖可以知道,evdev.c(事件裝置),joydev.c(遊戲句柄裝置),keyboard.c(鍵盤裝置),mousedev.c(滑鼠裝置),tsdev.c(觸摸屏裝置)。

S3C2440 輸入子系統概念介紹(十二)

以evdev.c為例子,它在evdev_exit()注冊:

static void __exit evdev_exit(void)
{
	input_unregister_handler(&evdev_handler);    //注冊
}
           

六、再來看一下edev_handler結構體:(純軟體)

static struct input_handler evdev_handler = {
	.event =	evdev_event,
	.connect =	evdev_connect,
	.disconnect =	evdev_disconnect,
	.fops =		&evdev_fops,
	.minor =	EVDEV_MINOR_BASE,
	.name =		"evdev",
	.id_table =	evdev_ids,
};
           

(1)第五中.fops:file_oprations結構體。

(2)第六行中.minor:其中,EVDEV_MINOR_BASE=64,次裝置是64。然後調用input_register_handler注冊,相當于EVDEV_MINOR_BASE/32=2,是以放在數組的第二項EVDEV_MINOR_BASE[2]。

是以open時,就會調用input_open_file,執行evdev_handler->edev_fops->.open

(3)第八行中.id_table:表示handler能夠支援哪一些輸入裝置。hadler和devices進行比較,handler處理器(軟體)能否支援device(裝置),如果能夠支援裝置,則調用.connect函數。

(4)第三行中.connect:連接配接函數,将input_device和input_handler建立連接配接。

七、再來看一下input_register_device函數:

int input_register_device(struct input_dev *dev)
{
... ...
	list_add_tail(&dev->node, &input_dev_list);    //(1)放傳入連結表
... ...
	list_for_each_entry(handler, &input_handler_list, node)    //(2)
	    input_attach_handler(dev, handler);    //(2)
... ...
	return 0;
}
           

(1)第4行中,将input_devid結構體,放入input_dev_list連結清單裡面。

(2)第6行中,對于每一個input_handler,都調用input_attach_handler,input_attach_handler根據input_handler的id_table判斷能否支援那個input_dev(輸入裝置)。

八、然後再回過頭看input_handler的input_register_handler()函數:

int input_register_handler(struct input_handler *handler)
{
... ...
	input_table[handler->minor >> 5] = handler;    //(1)
... ...
	list_add_tail(&handler->node, &input_handler_list);    //(2)

	list_for_each_entry(dev, &input_dev_list, node)    //(3)
		input_attach_handler(dev, handler);
... ...
	return 0;
}
           

(1)第4行中,首先将input_device放入一個數組裡面。

(2)第6行中,再講input_device放到一個連結清單input_handler_list裡面。

(3)第8行中,對于每一個input_device,都調用input_attach_handler,input_attach_handler根據input_handler的id_table判斷能否支援那個input_dev(輸入裝置)。

是以,不管是先添加input_handler還是input_dev,調用input_attach_handler(),判斷兩者是否比對、支援。

九、我們來看一下input_attach_handler()函數:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
	id = input_match_device(handler->id_table, dev);    //根據handler的id_table和輸入裝置dev進行比較
	if (!id)    //比對失敗
		return -ENODEV;

	error = handler->connect(handler, dev, id);    //如果比對調用handler的connect函數
... ...
	return error;
}
           

根據handler的id_table和輸入裝置dev進行比較,是否比對。如果比對,調用hadler裡面的connect函數建立連接配接。

注冊input_dev或者input_handler時,會兩兩比較左邊的input_dev和右邊的input_handler,根據input_handler的id_table判斷這個input_handler能否支援這個input_dev,如果能支援,則調用input_handler的connect函數建立“連接配接”。

十、舉個列子(evdev.c事件驅動):evdev_handler->connect函數

10.1 來分析怎麼樣建立連接配接:

S3C2440 輸入子系統概念介紹(十二)

10.2 evdev_handler的.connect()函數是evdev_connect():

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{
... ...
	for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
	if (minor == EVDEV_MINORS) {
		printk(KERN_ERR "evdev: no more free evdev devices\n");
		return -ENFILE;
	}
... ...
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);    //(1)配置設定一個input_handle結構體(沒有r)
... ...
	evdev->handle.dev = dev;    //(2)設定input_handle,指向左邊的input_dev結構體
	evdev->handle.name = evdev->name;
	evdev->handle.handler = handler;    //(2)指向右邊的input_handler結構體
	evdev->handle.private = evdev;
	sprintf(evdev->name, "event%d", minor);
... ...
	devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
	cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);
... ...
	error = input_register_handle(&evdev->handle);    //(3)注冊handle
... ...
}
           

(1)第10行中,配置設定一個input_handle結構體(沒有r)。

(2)第12行中,設定input_handle,dev指向左邊的input_dev結構體,.handler指向右邊的input_handler結構體。

(3)第21行中,注冊handle。

10.3 最終進入input_register_handle()函數注冊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);    //(1)
	list_add_tail(&handle->h_node, &handler->h_list);    //(2)

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

	return 0;
}
           

(1)第五行中,把傳進來的handle放入一個輸入裝置dev的連結清單h_list裡面。

(2)第六行中,把handle放入右邊handler的連結清單h_list裡面。

連接配接的時候構造一個input_handle,裡面.dev指向input_device,裡面.handler指向input_handler。

input_device的.h_list指向input_handle,input_handler的h_.list指向input_handle。

可以從input_device輸入裝置,通過.h_list找到input_handle,從裡面的.handler找到右邊的handler處理者。

也可從input_handler,通過.h_list找到input_handle,從裡面的.dev找到左邊的能支援的裝置dev。

S3C2440 輸入子系統概念介紹(十二)

總結一下:

1、配置設定一個input_handle結構體

2、input_handle.dev = input_dev    //指向左邊的input_dev

     input_handle.handler = input_handler    //指向右邊的 input_handler    

3、注冊:

    input_handler->h_list = &input_handle;

    input_dev->h_list = &input_handle;

十一、怎麼讀取按鍵evdev_read(時間驅動)?

static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
... ...
	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);
... ...
}
           

十二、read函數進入休眠,被誰喚醒呢?evdev_event事件處理函數

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
... ...
	wake_up_interruptible(&evdev->wait);//事件發生,喚醒
}
           
S3C2440 輸入子系統概念介紹(十二)

十三、分下一下,evdev_event被誰調用?gpio_keys_isr(在drivers\input\keyboard\gpio_keys.c檔案)

在裝置的中斷服務程式裡,确定事件是什麼,然後調用相應的input_handler的event處理函數。

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
... ...
    input_event(input, type, button->code, !!state);    ///上報事件
    input_sync(input);
... ...
}
           

通過input_event調用.event事件函數:

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

	list_for_each_entry(handle, &dev->h_list, d_node)//對連結清單的每一項handle
		if (handle->open)//如果handle已經打開
			handle->handler->event(handle, type, code, value);
}
           
S3C2440 輸入子系統概念介紹(十二)
S3C2440 輸入子系統概念介紹(十二)

繼續閱讀