12.Linux之输入子系统分析(详解)
在此節(jié)之前,我們學的都是簡單的字符驅動,涉及的內(nèi)容有字符驅動的框架、自動創(chuàng)建設備節(jié)點、linux中斷、poll機制、異步通知、同步互斥/非阻塞、定時器去抖動。
其中驅動框架如下:
1)寫file_operations結構體的成員函數(shù): .open()、.read()、.write()
2)在入口函數(shù)里通過register_chrdev()創(chuàng)建驅動名,生成主設備號,賦入file_operations結構體
3)在出口函數(shù)里通過unregister_chrdev() 卸載驅動
若有多個不同的驅動程序時,應用程序就要打開多個不同的驅動設備,由于是自己寫肯定會很清楚,如果給別人來使用時是不是很麻煩?
所以需要使用輸入子系統(tǒng), 使應用程序無需打開多個不同的驅動設備便能實現(xiàn)
?
1.輸入子系統(tǒng)簡介
同樣的輸入子系統(tǒng)也需要輸入驅動的框架,好來辨認應用程序要打開的是哪個輸入驅動
比如: 鼠標、鍵盤、游戲手柄等等這些都屬于輸入設備;這些輸入設備的驅動都是通過輸入子系統(tǒng)來實現(xiàn)的(當然,這些設備也依賴于usb子系統(tǒng))
這些輸入設備都各有不同,那么輸入子系統(tǒng)也就只能實現(xiàn)他們的共性,差異性則由設備驅動來實現(xiàn)。差異性又體現(xiàn)在哪里?
最直觀的就表現(xiàn)在這些設備功能上的不同了。對于我們寫驅動的人來說在設備驅動中就只要使用輸入子系統(tǒng)提供的工具(也就是函數(shù))來完成這些“差異”就行了,其他的則是輸入子系統(tǒng)的工作。這個思想不僅存在于輸入子系統(tǒng),其他子系統(tǒng)也是一樣(比如:usb子系統(tǒng)、video子系統(tǒng)等)
所以我們先來分析下輸入子系統(tǒng)input.c的代碼,然后怎么來使用輸入子系統(tǒng)(在內(nèi)核中以input來形容輸入子系統(tǒng))
2.打開input.c,位于內(nèi)核deivers/input
有以下這么兩段:
subsys_initcall(input_init); //修飾入口函數(shù) module_exit(input_exit); //修飾出口函數(shù)?
顯然輸入子系統(tǒng)是作為一個模塊存在,我們先來分析下input_int()入口函數(shù)
1 static int __init input_init(void) 2 { 3 int err; 4 err = class_register(&input_class); //(1)注冊類,放在/sys/class 5 if (err) { 6 printk(KERN_ERR "input: unable to register input_dev class\n"); 7 return err; 8 } 9 10 err = input_proc_init(); //在/proc下面建立相關的文件 11 if (err) 12 goto fail1; 13 14 err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //(2)注冊驅動 15 if (err) { 16 printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); 17 goto fail2; 18 } 19 20 21 22 return 0; 23 24 25 26 fail2: input_proc_exit(); 27 28 fail1: class_unregister(&input_class); 29 30 return err; 31 32 }?
(1)上面第4行”err = class_register(&input_class);”是在/sys/class 里創(chuàng)建一個 input類, input_class變量如下圖:
?
如下圖,我們啟動內(nèi)核,再啟動一個input子系統(tǒng)的驅動后,也可以看到創(chuàng)建了個"input"類 :
?
為什么這里代碼只創(chuàng)建類,沒有使用class_device_create()函數(shù)在類下面創(chuàng)建驅動設備?
在下面第8小結會詳細講到,這里簡單描述:當注冊input子系統(tǒng)的驅動后,才會有驅動設備,此時這里的代碼是沒有驅動的
?
(2)上面第14行通過register_chrdev創(chuàng)建驅動設備,其中變量INPUT_MAJOR =13,所以創(chuàng)建了一個主設備為13的"input"設備。
然后我們來看看它的操作結構體input_fops,如下圖:
只有一個.open函數(shù),比如當我們掛載一個新的input驅動,則內(nèi)核便會調(diào)用該.open函數(shù),接下來分析該.open函數(shù)
3 然后進入input_open_file函數(shù)(drivers/input/input.c)
1 static int input_open_file(struct inode *inode, struct file *file) 2 { 3 struct input_handler *handler = input_table[iminor(inode) >> 5]; // (1) 4 const struct file_operations *old_fops, *new_fops = NULL; 5 int err; 6 7 if (!handler || !(new_fops = fops_get(handler->fops))) //(2) 8 return -ENODEV; 9 10 if (!new_fops->open) { 11 fops_put(new_fops); 12 return -ENODEV; 13 } 14 15 old_fops = file->f_op; 16 file->f_op = new_fops; //(3) 17 18 err = new_fops->open(inode, file); //(4) 19 if (err) { 20 fops_put(file->f_op); 21 file->f_op = fops_get(old_fops); 22 } 23 24 fops_put(old_fops); 25 26 return err; 27 }?
(1)第3行中,其中iminor (inode)函數(shù)調(diào)用了MINOR(inode->i_rdev);讀取子設備號,然后將子設備除以32,找到新掛載的input驅動的數(shù)組號,然后放在input_handler 驅動處理函數(shù)handler中?
(2)第7行中,若handler有值,說明掛載有這個驅動,就將handler結構體里的成員file_operations * fops賦到新的file_operations *old_fops里面
(3)第16行中, 再將新的file_operations *old_fops賦到file-> file_operations ?*f_op里, 此時input子系統(tǒng)的file_operations就等于新掛載的input驅動的file_operations結構體,實現(xiàn)一個偷天換日的效果.
(4)第18行中,然后調(diào)用新掛載的input驅動的*old_fops里面的成員.open函數(shù)
4.上面代碼的input_table[]數(shù)組在初始時是沒有值的,
所以我們來看看input_table數(shù)組里面的數(shù)據(jù)又是在哪個函數(shù)里被賦值
在input.c函數(shù)(drivers/input/input.c)中搜索input_table,找到它在input_register_handler()函數(shù)中被賦值,代碼如下:
1 int input_register_handler(struct input_handler *handler) 2 { 3 ... ... 4 input_table[handler->minor >> 5] = handler; //input_table[]被賦值 5 ... ... 6 list_add_tail(&handler->node, &input_handler_list); //然后將這個input_handler放到input_handler_list鏈表中 7 ... ... 8 }就是將驅動處理程序input_handler注冊到input_table[]中,然后放在input_handler_list鏈表中,后面會講這個鏈表
5繼續(xù)來搜索input_register_handler,看看這個函數(shù)被誰來調(diào)用
如下圖所示,有evdev.c(事件設備),tsdev.c(觸摸屏設備),joydev.c(joystick操作桿設備),keyboard.c(鍵盤設備),mousedev.c(鼠標設備) 這5個內(nèi)核自帶的設備處理函數(shù)注冊到input子系統(tǒng)中
我們以evdev.c為例,它在evdev_ini()函數(shù)中注冊:
static int __init evdev_init(void) {return input_register_handler(&evdev_handler); //注冊 }?
6我們來看看這個evdev_handler變量是什么結構體,:
1 static struct input_handler evdev_handler = { 2 .event = evdev_event, 3 .connect = evdev_connect, //(4) 4 .disconnect = evdev_disconnect, 5 .fops = &evdev_fops, //(1) 6 .minor = EVDEV_MINOR_BASE, //(2) 7 .name = "evdev", 8 .id_table = evdev_ids, //(3) 9 };就是我們之前看的input_handler驅動處理結構體
(1) 第5行中.fops:文件操作結構體,其中evdev_fops函數(shù)就是自己的寫的操作函數(shù),然后賦到.fops中
(2)第6行中 .minor:用來存放次設備號
其中EVDEV_MINOR_BASE=64, 然后調(diào)用input_register_handler(&evdev_handler)后,由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中
?所以當open打開這個input設備,就會進入 input_open_file()函數(shù),執(zhí)行evdev_handler-> evdev_fops -> .open函數(shù),如下圖所示:
?
?
(3)第8行中.id_table : 表示能支持哪些輸入設備,比如某個驅動設備的input_dev->的id和某個input_handler的id_table相匹配,就會調(diào)用.connect連接函數(shù),如下圖
(4)第3行中.connect:連接函數(shù),將設備input_dev和某個input_handler建立連接,如下圖
?
7我們先來看看上圖的input_register_device()函數(shù),如何創(chuàng)建驅動設備的
搜索input_register_device,發(fā)現(xiàn)內(nèi)核自己就已經(jīng)注冊了很多驅動設備
7.1然后進入input_register_device()函數(shù),代碼如下:
1 int input_register_device(struct input_dev *dev) //*dev:要注冊的驅動設備 2 { 3 ... ... 4 list_add_tail(&dev->node, &input_dev_list); //(1)放入鏈表中 5 ... ... 6 list_for_each_entry(handler, &input_handler_list, node) //(2) 7 input_attach_handler(dev, handler); 8 ... ... 9 }?
(1)第4行中,將要注冊的input_dev驅動設備放在input_dev_list鏈表中
(2)第6行中,其中input_handler_list在前面講過,就是存放每個input_handle驅動處理結構體,
然后list_for_each_entry()函數(shù)會將每個input_handle從鏈表中取出,放到handler中
最后會調(diào)用input_attach_handler()函數(shù),將每個input_handle的id_table進行判斷,若兩者支持便進行連接。
7.2然后我們在回過頭來看注冊input_handler的input_register_handler()函數(shù),如下圖所示
所以,不管新添加input_dev還是input_handler,都會進入input_attach_handler()判斷兩者id是否有支持, 若兩者支持便進行連接。
7.3我們來看看input_attach_handler()如何實現(xiàn)匹配兩者id的:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { ... ... id = input_match_device(handler->id_table, dev); //匹配兩者if (!id) //若不匹配,return退出 return -ENODEV; error = handler->connect(handler, dev, id); //調(diào)用input_handler ->connect函數(shù)建立連接 ... ...}?
若兩者匹配成功,就會自動進入input_handler 的connect函數(shù)建立連接
8我們還是以evdev.c(事件驅動) 的evdev_handler->connect函數(shù)
來分析是怎樣建立連接的,如下圖:
8.1 evdev_handler的.connect函數(shù)是evdev_connect(),代碼如下:
1 static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) 2 { 3 ... ... 4 for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); //查找驅動設備的子設備號 5 if (minor == EVDEV_MINORS) { // EVDEV_MINORS=32,所以該事件下的驅動設備最多存32個, 6 printk(KERN_ERR "evdev: no more free evdev devices\n"); 7 return -ENFILE; //沒找到驅動設備 8 } 9 ... ... 10 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //分配一個input_handle全局結構體(沒有r) 11 ... ... 12 evdev->handle.dev = dev; //指向參數(shù)input_dev驅動設備 13 evdev->handle.name = evdev->name; 14 evdev->handle.handler = handler; //指向參數(shù) input_handler驅動處理結構體 15 evdev->handle.private = evdev; 16 sprintf(evdev->name, "event%d", minor); //(1)保存驅動設備名字, event%d 17 ... ... 18 devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), //(2) 將主設備號和次設備號轉換成dev_t類型 19 cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);// (3)在input類下創(chuàng)建驅動設備 20 21 ... ... 22 error = input_register_handle(&evdev->handle); //(4)注冊這個input_handle結構體 23 24 ... ... 25 }
(1) 第16行中,是在保存驅動設備名字,名為event%d, 比如下圖(鍵盤驅動)event1: 因為沒有設置子設備號,默認從小到大排列,其中event0是表示這個input子系統(tǒng),所以這個鍵盤驅動名字就是event1
(2)第18行中,是在保存驅動設備的主次設備號,其中主設備號INPUT_MAJOR=13,因為EVDEV_MINOR_BASE=64,所以此設備號=64+驅動程序本事子設備號, 比如下圖(鍵盤驅動)event1: ?主次設備號就是13,65
(3)在之前在2小結里就分析了input_class類結構,所以第19行中,會在/sys/class/input類下創(chuàng)建驅動設備event%d,比如下圖(鍵盤驅動)event1:
(4)最終會進入input_register_handle()函數(shù)來注冊,代碼在下面
8.2?input_register_handle()函數(shù)如下:
1 int input_register_handle(struct input_handle *handle) 2 { 3 struct input_handler *handler = handle->handler; //handler= input_handler驅動處理結構體 4 5 list_add_tail(&handle->d_node, &handle->dev->h_list); //(1) 6 list_add_tail(&handle->h_node, &handler->h_list); // (2) 7 8 if (handler->start) 9 handler->start(handle); 10 return 0; 11 }?
(1)在第5行中, 因為handle->dev指向input_dev驅動設備,所以就是將handle->d_node放入到input_dev驅動設備的h_list鏈表中,
即input_dev驅動設備的h_list鏈表就指向handle->d_node
(2) 在第6行中,?同樣, input_handler驅動處理結構體的h_list也指向了handle->h_node
最終如下圖所示:
?
兩者的.h_list都指向了同一個handle結構體,然后通過.h_list 來找到handle的成員.dev和handler,便能找到對方,便建立了連接
9建立了連接后,又如何讀取evdev.c(事件驅動) 的evdev_handler->.fops->.read函數(shù)?
事件驅動的.read函數(shù)是evdev_read()函數(shù),我們來分析下:
static ssize_t evdev_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos) {... ... /*判斷應用層要讀取的數(shù)據(jù)是否正確*/ if (count < evdev_event_size()) return -EINVAL;/*在非阻塞操作情況下,若client->head == client->tail|| evdev->exist時(沒有數(shù)據(jù)),則return返回*/if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) return -EAGAIN;/*若client->head == client->tail|| evdev->exist時(沒有數(shù)據(jù)),等待中斷進入睡眠狀態(tài) */retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);... ... //上傳數(shù)據(jù) }?
10若read函數(shù)進入了休眠狀態(tài),又是誰來喚醒?
我們搜索這個evdev->wait這個等待隊列變量,找到evdev_event函數(shù)里喚醒:
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { ... ...wake_up_interruptible(&evdev->wait); //有事件觸發(fā),便喚醒等待中斷 }?
其中evdev_event()是evdev.c(事件驅動) 的evdev_handler->.event成員,如下圖所示:
?
當有事件發(fā)生了,比如對于按鍵驅動,當有按鍵按下時,就會進入.event函數(shù)中處理事件
11分析下,是誰調(diào)用evdev_event()這個.event事件驅動函數(shù)
應該就是之前分析的input_dev那層調(diào)用的
我們來看看內(nèi)核 gpio_keys_isr()函數(shù)代碼例子就知道了 (driver/input/keyboard/gpio_key.c)
static irqreturn_t gpio_keys_isr(int irq, void *dev_id) {/*獲取按鍵值,賦到state里*/... .../*上報事件*/ input_event(input, type, button->code, !!state); input_sync(input); ??//同步信號通知,表示事件發(fā)送完畢 }?
顯然就是通過input_event()來調(diào)用.event事件函數(shù),我們來看看:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_handle *handle; ... .../* 通過input_dev ->h_list鏈表找到input_handle驅動處理結構體*/ list_for_each_entry(handle, &dev->h_list, d_node) if (handle->open) //如果input_handle之前open 過,那么這個就是我們的驅動處理結構體handle->handler->event(handle, type, code, value); //調(diào)用evdev_event()的.event事件函數(shù) }?
若之前驅動input_dev和處理input_handler已經(jīng)通過input_handler 的.connect函數(shù)建立起了連接,那么就調(diào)用evdev_event()的.event事件函數(shù),如下圖所示:
?
12本節(jié)總結分析:
1.注冊輸入子系統(tǒng),進入put_init():
1)創(chuàng)建主設備號為13的"input"字符設備
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);?
2.open打開驅動,進入input_open_file():
1)更新設備的file_oprations
file->f_op=fops_get(handler->fops);?
2)執(zhí)行file_oprations->open函數(shù)
err = new_fops->open(inode, file);?
3.注冊input_handler,進入input_register_handler():
1)添加到input_table[]處理數(shù)組中
input_table[handler->minor >> 5] = handler;?
2)添加到input_handler_list鏈表中
list_add_tail(&handler->node, &input_handler_list);?
3)判斷input_dev的id,是否有支持這個驅動的設備
list_for_each_entry(dev, &input_dev_list, node) //遍歷查找input_dev_list鏈表里所有input_dev input_attach_handler(dev, handler); //判斷兩者id,若兩者支持便進行連接。4.注冊input_dev,進入input_register_device():
1)放在input_dev_list鏈表中
list_add_tail(&dev->node, &input_dev_list);?
2)判斷input_handler的id,是否有支持這個設備的驅動
list_for_each_entry(handler, &input_handler_list, node) //遍歷查找input_handler_list鏈表里所有input_handler input_attach_handler(dev, handler); //判斷兩者id,若兩者支持便進行連接。?
5.判斷input_handler和input_dev的id,進入input_attach_handler():
1)匹配兩者id,
input_match_device(handler->id_table, dev); //匹配input_handler和dev的id,不成功退出函數(shù)?
2)匹配成功調(diào)用input_handler ->connect
handler->connect(handler, dev, id); //建立連接6.建立input_handler和input_dev的連接,進入input_handler->connect():
1)創(chuàng)建全局結構體,通過input_handle結構體連接雙方
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //創(chuàng)建兩者連接的input_handle全局結構體 list_add_tail(&handle->d_node, &handle->dev->h_list); //連接input_dev->h_list list_add_tail(&handle->h_node, &handler->h_list); // 連接input_handle->h_list7.有事件發(fā)生時,比如按鍵中斷,在中斷函數(shù)中需要進入input_event()上報事件:
1)找到驅動處理結構體,然后執(zhí)行input_handler->event()
list_for_each_entry(handle, &dev->h_list, d_node) // 通過input_dev ->h_list鏈表找到input_handle驅動處理結構體 if (handle->open) //如果input_handle之前open 過,那么這個就是我們的驅動處理結構體(有可能一個驅動設備在不同情況下有不同的驅動處理方式)handle->handler->event(handle, type, code, value); //調(diào)用evdev_event()的.event事件函數(shù)?
?
然后在下一節(jié)便開始實現(xiàn)輸入子系統(tǒng)的鍵盤按鍵驅動
?
?
總結
以上是生活随笔為你收集整理的12.Linux之输入子系统分析(详解)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle 数据库字段名与实体类字段名
- 下一篇: 1周第1课 Linux 认知、安装 Ce