12.Linux之輸入子系統分析(詳解)

在此節以前,咱們學的都是簡單的字符驅動,涉及的內容有字符驅動的框架自動建立設備節點linux中斷poll機制異步通知同步互斥/非阻塞定時器去抖動html

其中驅動框架以下:node

1)寫file_operations結構體的成員函數: .open()、.read()、.write()linux

2)在入口函數裏經過register_chrdev()建立驅動名,生成主設備號,賦入file_operations結構體數組

3)在出口函數裏經過unregister_chrdev() 卸載驅動框架

如有多個不一樣的驅動程序時,應用程序就要打開多個不一樣的驅動設備,因爲是本身寫確定會很清楚,若是給別人來使用時是否是很麻煩?異步

因此須要使用輸入子系統, 使應用程序無需打開多個不一樣的驅動設備便能實現ide

 


1.輸入子系統簡介函數

一樣的輸入子系統也須要輸入驅動的框架,好來辨認應用程序要打開的是哪一個輸入驅動工具

好比: 鼠標、鍵盤、遊戲手柄等等這些都屬於輸入設備;這些輸入設備的驅動都是經過輸入子系統來實現的(固然,這些設備也依賴於usb子系統)spa

這些輸入設備都各有不一樣,那麼輸入子系統也就只能實現他們的共性,差別性則由設備驅動來實現。差別性又體如今哪裏?

最直觀的就表如今這些設備功能上的不一樣了。對於咱們寫驅動的人來講在設備驅動中就只要使用輸入子系統提供的工具(也就是函數)來完成這些「差別」就好了,其餘的則是輸入子系統的工做。這個思想不只存在於輸入子系統,其餘子系統也是同樣(好比:usb子系統、video子系統等)

因此咱們先來分析下輸入子系統input.c的代碼,而後怎麼來使用輸入子系統(在內核中以input來形容輸入子系統)

2.打開input.c,位於內核deivers/input

有如下這麼兩段:

subsys_initcall(input_init);   //修飾入口函數

module_exit(input_exit);     //修飾出口函數

 

顯然輸入子系統是做爲一個模塊存在,咱們先來分析下input_int()入口函數

 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 裏建立一個 input類, input_class變量以下圖:

 

以下圖,咱們啓動內核,再啓動一個input子系統的驅動後,也能夠看到建立了個"input"類 :

 

爲何這裏代碼只建立類沒有使用class_device_create()函數在類下面建立驅動設備?

在下面第8小結會詳細講到,這裏簡單描述:當註冊input子系統的驅動後,纔會有驅動設備,此時這裏的代碼是沒有驅動的

 

(2)上面第14行經過register_chrdev建立驅動設備,其中變量INPUT_MAJOR =13,因此建立了一個主設備爲13的"input"設備。

而後咱們來看看它的操做結構體input_fops,以下圖:

只有一個.open函數,好比當咱們掛載一個新的input驅動,則內核便會調用該.open函數,接下來分析該.open函數

3 而後進入input_open_file函數(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)函數調用了MINOR(inode->i_rdev);讀取子設備號,而後將子設備除以32,找到新掛載的input驅動的數組號,而後放在input_handler 驅動處理函數handler中 

(2)第7行中,若handler有值,說明掛載有這個驅動,就將handler結構體裏的成員file_operations * fops賦到新的file_operations *new_fops裏面

(3)第16行中, 再將新的file_operations *new_fops賦到file-> file_operations  *f_op裏, 此時input子系統的file_operations就等於新掛載的input驅動的file_operations結構體,實現一個偷天換日的效果.

(4)第18行中,而後調用新掛載的input驅動的*new_fops裏面的成員.open函數

4.上面代碼的input_table[]數組在初始時是沒有值的,

因此咱們來看看input_table數組裏面的數據又是在哪一個函數裏被賦值

在input.c函數(drivers/input/input.c)中搜索input_table,找到它在input_register_handler()函數中被賦值,代碼以下:

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繼續來搜索input_register_handler,看看這個函數被誰來調用

以下圖所示,有evdev.c(事件設備)tsdev.c(觸摸屏設備)joydev.c(joystick操做杆設備)keyboard.c(鍵盤設備)mousedev.c(鼠標設備) 這5個內核自帶的設備處理函數註冊到input子系統中

咱們以evdev.c爲例,它在evdev_ini()函數中註冊:

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函數就是本身的寫的操做函數,而後賦到.fops中

(2)第6行中 .minor:用來存放次設備號

其中EVDEV_MINOR_BASE=64, 而後調用input_register_handler(&evdev_handler)後,因爲EVDEV_MINOR_BASE/32=2,因此存到input_table[2]中

 因此當open打開這個input設備,就會進入 input_open_file()函數,執行evdev_handler-> evdev_fops -> .open函數,以下圖所示:

 

 

(3)第8行中.id_table : 表示能支持哪些輸入設備,好比某個驅動設備的input_dev->的id和某個input_handler的id_table相匹配,就會調用.connect鏈接函數,以下圖

(4)第3行中.connect:鏈接函數,將設備input_dev和某個input_handler創建鏈接,以下圖

 

7咱們先來看看上圖的input_register_device()函數,如何建立驅動設備的

搜索input_register_device,發現內核本身就已經註冊了不少驅動設備

7.1而後進入input_register_device()函數,代碼以下:

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()函數會將每一個input_handle從鏈表中取出,放到handler中

最後會調用input_attach_handler()函數,將每一個input_handle的id_table進行判斷,若二者支持便進行鏈接。

7.2而後咱們在回過頭來看註冊input_handler的input_register_handler()函數,以下圖所示

因此,無論新添加input_dev仍是input_handler,都會進入input_attach_handler()判斷二者id是否有支持, 若二者支持便進行鏈接。

7.3咱們來看看input_attach_handler()如何實現匹配二者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);  //調用input_handler ->connect函數創建鏈接
... ...

}

 

若二者匹配成功,就會自動進入input_handler 的connect函數創建鏈接

8咱們仍是以evdev.c(事件驅動) 的evdev_handler->connect函數

來分析是怎樣創建鏈接的,以下圖:

8.1 evdev_handler的.connect函數是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;              //指向參數input_dev驅動設備
13 evdev->handle.name = evdev->name;
14 evdev->handle.handler = handler;    //指向參數 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類下建立驅動設備 20 21 ... ... 22 error = input_register_handle(&evdev->handle); //(4)註冊這個input_handle結構體 23 24 ... ... 25 }

(1) 第16行中,是在保存驅動設備名字,名爲event%d, 好比下圖(鍵盤驅動)event1: 由於沒有設置子設備號,默認從小到大排列,其中event0是表示這個input子系統,因此這個鍵盤驅動名字就是event1

(2)第18行中,是在保存驅動設備的主次設備號,其中主設備號INPUT_MAJOR=13,由於EVDEV_MINOR_BASE=64,因此此設備號=64+驅動程序本事子設備號, 好比下圖(鍵盤驅動)event1:  主次設備號就是13,65

(3)在以前在2小結裏就分析了input_class類結構,因此第19行中,會在/sys/class/input類下建立驅動設備event%d,好比下圖(鍵盤驅動)event1:

(4)最終會進入input_register_handle()函數來註冊,代碼在下面

8.2 input_register_handle()函數以下:

 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函數?

事件驅動的.read函數是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;

/*在非阻塞操做狀況下,若client->head == client->tail|| evdev->exist時(沒有數據),則return返回*/
 if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
 
/*若client->head == client->tail|| evdev->exist時(沒有數據),等待中斷進入睡眠狀態  */
  retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);

  ... ...           //上傳數據

}

 

10若read函數進入了休眠狀態,又是誰來喚醒?

咱們搜索這個evdev->wait這個等待隊列變量,找到evdev_event函數裏喚醒:

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
... ...
 wake_up_interruptible(&evdev->wait);   //有事件觸發,便喚醒等待中斷
}

 

其中evdev_event()是evdev.c(事件驅動) 的evdev_handler->.event成員,以下圖所示:

 

當有事件發生了,好比對於按鍵驅動,當有按鍵按下時,就會進入.event函數中處理事件

11分析下,是誰調用evdev_event()這個.event事件驅動函數

應該就是以前分析的input_dev那層調用的

咱們來看看內核 gpio_keys_isr()函數代碼例子就知道了 (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);                        //同步信號通知,表示事件發送完畢
}

 

顯然就是經過input_event()來調用.event事件函數,咱們來看看:

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); //調用evdev_event()的.event事件函數 

}

 

若以前驅動input_dev和處理input_handler已經經過input_handler 的.connect函數創建起了鏈接,那麼就調用evdev_event()的.event事件函數,以下圖所示:

 

12本節總結分析:

1.註冊輸入子系統,進入put_init():

1)建立主設備號爲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)執行file_oprations->open函數

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

 

3.註冊input_handler,進入input_register_handler():

1)添加到input_table[]處理數組中

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_handlerinput_devid,進入input_attach_handler():

1)匹配二者id,

input_match_device(handler->id_table, dev);        //匹配input_handler和dev的id,不成功退出函數

 

2)匹配成功調用input_handler ->connect

handler->connect(handler, dev, id);              //創建鏈接

6.創建input_handlerinput_dev的鏈接,進入input_handler->connect():

1)建立全局結構體,經過input_handle結構體鏈接雙方

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);    //建立二者鏈接的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_list

7.有事件發生時,好比按鍵中斷,在中斷函數中須要進入input_event()上報事件:

1)找到驅動處理結構體,而後執行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); //調用evdev_event()的.event事件函數 

 

 

而後在下一節便開始實現輸入子系統的鍵盤按鍵驅動

相關文章
相關標籤/搜索