1. 程式人生 > >INPUT輸入子系統

INPUT輸入子系統

一、什麼是input輸入子系統?

1、Linux系統支援的輸入裝置繁多,例如鍵盤、滑鼠、觸控式螢幕、手柄或者是一些輸入裝置像體感輸入等等,Linux系統是如何管理如此之多的不同型別、不同原理、不同的輸入資訊的

輸入裝置的呢?其實就是通過input輸入子系統這套軟體體系來完成的。從整體上來說,input輸入子系統分為3層:上層(輸入事件驅動層)、中層(輸入核心層)、

下層(輸入裝置驅動層),如下圖所示:

聯絡之前學過的驅動框架做對比,input輸入子系統其實就是input輸入裝置的驅動框架,與之前的學過的驅動框架不同的是,input輸入子系統分為3層:上、中、下,所以他的複雜度

要高於之前講的lcd、misc、fb等的驅動框架。

2、圖中Drivers對應的就是下層裝置驅動層,對應各種各樣不同的輸入裝置,Input Core對應的就是中層核心層,Handlers對應的就是上層輸入事件驅動層,最右邊的代表的是使用者空間。

(1)從圖中可以看出,系統中可以註冊多個輸入裝置,每個輸入裝置的可以是不同的,例如一臺電腦上可以帶有滑鼠,鍵盤....。

(2)上層中的各個handler(Keyboard/Mouse/Joystick/Event)是屬於平行關係,他們都是屬於上層。不同的handler下對應的輸入裝置在應用層中的介面命名方式不一樣,例如

Mouse下的輸入裝置在應用層的介面是 /dev/input/mousen (n代表0、1、2...),Joystick下的輸入裝置在應用層的介面是 /dev/input/jsn(n代表0、1、2...),

Event下的輸入裝置在應用層的介面是 /dev/input/eventn(n代表0、1、2...),這個是在input輸入子系統中實現的,下面會分析其中的原由。

(3)輸入核心層其實是負責協調上層和下層,使得上層和下層之間能夠完成資料傳遞。當下層發生輸入事件的時候,整個系統就被激活了,事件就會通過核心層傳遞到上層對應的一個/多個

handler中,最終會傳遞到應用空間。

3、輸入子系統解決了什麼問題?

(1)在GUI介面中,使用者的自由度太大了,可以做的事情太多了,可以響應不同的輸入類裝置,而且還能夠對不同的輸入類裝置的輸入做出不同的動作。例如window中的一個軟

件既可以響應滑鼠輸入事件,也可以相應鍵盤輸入事件,而且這些事件都是預先不知道的。

(2)input子系統解決了不同的輸入類裝置的輸入事件與應用層之間的資料傳輸,使得應用層能夠獲取到各種不同的輸入裝置的輸入事件,input輸入子系統能夠囊括所有的不同種

類的輸入裝置,在應用層都能夠感知到所有發生的輸入事件。

4、input輸入子系統如何工作?

例如以一次滑鼠按下事件為例子來說明我們的input輸入子系統的工作過程:

當我們按下滑鼠左鍵的時候就會觸發中斷(中斷是早就註冊好的),就會去執行中斷所繫結的處理函式,在函式中就會去讀取硬體暫存器來判斷按下的是哪個按鍵和狀態 ---->

將按鍵資訊上報給input core層  ---> input core層處理好了之後就會上報給input event層,在這裡會將我們的輸入事件封裝成一個input_event結構體放入一個緩衝區中 --->  

應用層read就會將緩衝區中的資料讀取出去。

5、相關的資料結構

複製程式碼

 1 struct input_dev {
 2     const char *name;             //  input裝置的名字
 3     const char *phys;              //  
 4     const char *uniq;              //
 5     struct input_id id;             //  
 6 
 7 //  這些是用來表示該input裝置能夠上報的事件型別有哪些   是用位的方式來表示的
 8     unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
 9     unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
10     unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
11     unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
12     unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
13     unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
14     unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
15     unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
16     unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
17 
18     unsigned int keycodemax;
19     unsigned int keycodesize;
20     void *keycode;
21     int (*setkeycode)(struct input_dev *dev,
22               unsigned int scancode, unsigned int keycode);
23     int (*getkeycode)(struct input_dev *dev,
24               unsigned int scancode, unsigned int *keycode);
25 
26     struct ff_device *ff;
27 
28     unsigned int repeat_key;
29     struct timer_list timer;
30 
31     int sync;
32 
33     int abs[ABS_CNT];
34     int rep[REP_MAX + 1];
35 
36     unsigned long key[BITS_TO_LONGS(KEY_CNT)];
37     unsigned long led[BITS_TO_LONGS(LED_CNT)];
38     unsigned long snd[BITS_TO_LONGS(SND_CNT)];
39     unsigned long sw[BITS_TO_LONGS(SW_CNT)];
40 
41     int absmax[ABS_CNT];
42     int absmin[ABS_CNT];
43     int absfuzz[ABS_CNT];
44     int absflat[ABS_CNT];
45     int absres[ABS_CNT];
46 
47     int (*open)(struct input_dev *dev);              //    裝置的open函式
48     void (*close)(struct input_dev *dev);          
49     int (*flush)(struct input_dev *dev, struct file *file);
50     int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);     //  上報事件
51 
52     struct input_handle *grab;
53 
54     spinlock_t event_lock;
55     struct mutex mutex;
56 
57     unsigned int users;
58     bool going_away;
59 
60     struct device dev;                 //  內建的device結構體變數
61 
62     struct list_head    h_list;    //  用來掛接input_dev 裝置連線的所有handle 的一個連結串列頭
63     struct list_head    node;    //  作為連結串列節點掛接到  input_dev_list 連結串列上  (input_dev_list連結串列是input核心層維護的一個用來掛接所有input裝置的一個連結串列頭)
64 };

複製程式碼

複製程式碼

 1 struct input_handler {
 2 
 3     void *private;            //  私有資料
 4 
 5     void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);   //  handler用於向上層上報輸入事件的函式
 6     bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
 7     bool (*match)(struct input_handler *handler, struct input_dev *dev);            //   match 函式用來匹配handler 與 input_dev 裝置
 8     int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);   //  當handler 與 input_dev 匹配成功之後用來連線
 9     void (*disconnect)(struct input_handle *handle);          //  斷開handler 與 input_dev 之間的連線
10     void (*start)(struct input_handle *handle);                    
11 
12     const struct file_operations *fops;             //  一個file_operations 指標
13     int minor;                                      //  該handler 的編號 (在input_table 陣列中用來計算陣列下標) input_table陣列就是input子系統用來管理註冊的handler的一個數據結構
14     const char *name;                               //  handler的名字
15 
16     const struct input_device_id *id_table;      //  指向一個 input_device_id  型別的陣列,用來進行與input裝置匹配時用到的資訊
17  
18     struct list_head    h_list;       //  用來掛接handler 上連線的所有handle 的一個連結串列頭
19     struct list_head    node;        //  作為一個連結串列節點掛接到 input_handler_list 連結串列上(input_handler_list 連結串列是一個由上層handler參維護的一個用來掛接所有註冊的handler的連結串列頭)
20 };

複製程式碼

複製程式碼

 1 struct input_handle {
 2 
 3     void *private;               //   handle  的私有資料
 4 
 5     int open;                     //  這個也是用來做開啟計數的
 6     const char *name;       //   該handle 的名字
 7 
 8     struct input_dev *dev;                //  用來指向該handle 繫結的input_dev 結構體
 9     struct input_handler *handler;    //  用來指向該handle 繫結的 handler 結構體
10 
11     struct list_head    d_node;      //  作為一個連結串列節點掛接到與他繫結的input_dev ->hlist 連結串列上
12     struct list_head    h_node;      //  作為一個連結串列節點掛接到與他繫結的handler->hlist 連結串列上
13 };

複製程式碼

複製程式碼

 1 struct input_device_id {
 2 
 3     kernel_ulong_t flags;    //  這個flag 表示我們的這個 input_device_id 是用來匹配下面的4個情況的哪一項
 4                                     //  flag == 1表示匹配匯流排  2表示匹配供應商   4表示匹配產品  8表示匹配版本
 5     __u16 bustype;
 6     __u16 vendor;
 7     __u16 product;
 8     __u16 version;
 9 
10     kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
11     kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
12     kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
13     kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
14     kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
15     kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
16     kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
17     kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
18     kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];
19 
20     kernel_ulong_t driver_info;
21 };

複製程式碼

二、輸入核心層原始碼分析(核心版本:2.6.35.7)

input輸入子系統中的所有原始碼都放在 drivers\input 這個目錄中,input.c檔案就是核心層的原始碼檔案。在input目錄中還可以看到一些資料夾,例如gameport、joystick

keyboard、misc、mouse....,這些資料夾裡面存放的就是屬於這類的input輸入裝置的裝置驅動原始碼,可以理解為input輸入子系統的下層。

input目錄下的evdev.c、joydev.c、mousedev.c..分別對應上層的各個不同的handler的原始碼。

1、輸入核心層模組註冊函式input_init

在Linux中實現為一個模組的方法,所以可以在核心配置的進行動態的載入和解除安裝,這樣做的原由是,存在有些系統中不需要任何

的輸入類裝置,這樣就可以將input輸入子系統這個模組去掉(上層也是實現為模組的),使得核心儘量變得更小。

複製程式碼

 1 static int __init input_init(void)
 2 {
 3     int err;
 4 
 5     input_init_abs_bypass();
 6 
 7     err = class_register(&input_class);                //  建立裝置類    /sys/class/input
 8     if (err) {
 9         printk(KERN_ERR "input: unable to register input_dev class\n");
10         return err;
11     }
12 
13     err = input_proc_init();           //    proc檔案系統相關的初始化
14     if (err)
15         goto fail1;
16 
17     err = register_chrdev(INPUT_MAJOR, "input", &input_fops);       //   註冊字元裝置驅動   主裝置號13   input_fops 中只實現了open函式,所以他的原理其實和misc其實是一樣的
18     if (err) {
19         printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
20         goto fail2;
21     }
22 
23     return 0;
24 
25  fail2:    input_proc_exit();
26  fail1:    class_unregister(&input_class);
27     return err;
28 }

複製程式碼

(1)input_proc_init函式

複製程式碼

 1 static int __init input_proc_init(void)
 2 {
 3     struct proc_dir_entry *entry;
 4 
 5     proc_bus_input_dir = proc_mkdir("bus/input", NULL);    /* 在/proc/bus/目錄下建立input目錄 */
 6     if (!proc_bus_input_dir)
 7         return -ENOMEM;
 8 
 9     entry = proc_create("devices", 0, proc_bus_input_dir,  /* 在/proc/bus/input/目錄下建立devices檔案 */
10                 &input_devices_fileops);
11     if (!entry)
12         goto fail1;
13 
14     entry = proc_create("handlers", 0, proc_bus_input_dir, /* 在/proc/bus/input/目錄下建立handlers檔案 */
15                 &input_handlers_fileops);
16     if (!entry)
17         goto fail2;
18 
19     return 0;
20 
21  fail2:    remove_proc_entry("devices", proc_bus_input_dir);
22  fail1: remove_proc_entry("bus/input", NULL);
23     return -ENOMEM;
24 }

複製程式碼

當我們啟動系統之後進入到proc檔案系統中,確實可以看到在/proc/bus/input/目錄下有兩個檔案devices和handlers,這兩個檔案就是在這裡被建立的。我們cat devices 和 cat handlers

時對應的操作方法(show)就被封裝在input_devices_fileops和input_handlers_fileops結構體中。

(2)input_fops變數

複製程式碼

 1 static int input_open_file(struct inode *inode, struct file *file)
 2 {
 3     struct input_handler *handler;                                                //  定義一個input_handler指標
 4     const struct file_operations *old_fops, *new_fops = NULL;   //  定義兩個file_operations指標
 5     int err;
 6 
 7     err = mutex_lock_interruptible(&input_mutex);
 8     if (err)
 9         return err;
10 
11     /* No load-on-demand here? */
12     handler = input_table[iminor(inode) >> 5];         //  通過次裝置號在 input_table  陣列中找到對應的 handler 
13     if (handler)
14         new_fops = fops_get(handler->fops);           //  將handler 中的fops 指標賦值給 new_fops
15 
16     mutex_unlock(&input_mutex);
17 
18     /*
19      * That's _really_ odd. Usually NULL ->open means "nothing special",
20      * not "no device". Oh, well...
21      */
22     if (!new_fops || !new_fops->open) {
23         fops_put(new_fops);
24         err = -ENODEV;
25         goto out;
26     }
27 
28     old_fops = file->f_op;           //   將 file->fops 先儲存到 old_fops 中,以便出錯時能夠恢復
29     file->f_op = new_fops;          //   用new_fops 替換 file 中 fops 
30 
31     err = new_fops->open(inode, file);       //  執行 file->open  函式
32     if (err) {
33         fops_put(file->f_op);
34         file->f_op = fops_get(old_fops);
35     }
36     fops_put(old_fops);
37 out:
38     return err;
39 }

複製程式碼

2、核心層提供給裝置驅動層的介面函式

input裝置驅動框架留給裝置驅動層的介面函式主要有3個:

      input_allocate_device。分配一塊input_dev結構體型別大小的記憶體

      input_set_capability。設定輸入裝置可以上報哪些輸入事件

      input_register_device。向input核心層註冊裝置

(1)input_allocate_device函式

複製程式碼

 1 struct input_dev *input_allocate_device(void)
 2 {
 3     struct input_dev *dev;                 //   定義一個 input_dev  指標
 4 
 5     dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);   //  申請分配記憶體
 6     if (dev) {
 7         dev->dev.type = &input_dev_type;          //  確定input裝置的 裝置型別     input_dev_type
 8         dev->dev.class = &input_class;                //  確定input裝置所屬的裝置類   class
 9         device_initialize(&dev->dev);                   //  input裝置的初始化
10         mutex_init(&dev->mutex);                        //  互斥鎖初始化
11         spin_lock_init(&dev->event_lock);            //  自旋鎖初始化
12         INIT_LIST_HEAD(&dev->h_list);                 //  input_dev -> h_list 連結串列初始化
13         INIT_LIST_HEAD(&dev->node);                 //  input_dev -> node 連結串列初始化
14 
15         __module_get(THIS_MODULE);
16     }
17 
18     return dev;
19 }

複製程式碼

(2)input_set_capability函式:

函式原型:input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)

引數:dev就是裝置的input_dev結構體變數

        type表示裝置可以上報的事件型別

        code表示上報這類事件中的那個事件

注意:input_set_capability函式一次只能設定一個具體事件,如果裝置可以上報多個事件,則需要重複呼叫這個函式來進行設定,例如:

input_set_capability(dev, EV_KEY, KEY_Q);         // 至於函式內部是怎麼設定的,將會在後面進行分析。

input_set_capability(dev, EV_KEY, KEY_W);

input_set_capability(dev, EV_KEY, KEY_E);

具體的這些類下面有哪些具體的輸入事件,請看 drivers\input\input.h 這個檔案。

(3)input_register_device函式:

複製程式碼

 1 int input_register_device(struct input_dev *dev)      //  註冊input輸入裝置
 2 {
 3     static atomic_t input_no = ATOMIC_INIT(0);
 4     struct input_handler *handler;                          //  定義一個  input_handler 結構體指標
 5     const char *path;
 6     int error;
 7 
 8     /* Every input device generates EV_SYN/SYN_REPORT events. */
 9     __set_bit(EV_SYN, dev->evbit);                  //   每一個input輸入裝置都會發生這個事件
10 
11     /* KEY_RESERVED is not supposed to be transmitted to userspace. */
12     __clear_bit(KEY_RESERVED, dev->keybit);  //  清除KEY_RESERVED 事件對應的bit位,也就是不傳輸這種型別的事件
13 
14     /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
15     input_cleanse_bitmasks(dev);           //   確保input_dev中的用來記錄事件的變數中沒有提到的位掩碼是乾淨的。
16 
17     /*
18      * If delay and period are pre-set by the driver, then autorepeating
19      * is handled by the driver itself and we don't do it in input.c.
20      */
21     init_timer(&dev->timer);
22     if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
23         dev->timer.data = (long) dev;
24         dev->timer.function = input_repeat_key;
25         dev->rep[REP_DELAY] = 250;
26         dev->rep[REP_PERIOD] = 33;
27     }
28 
29     if (!dev->getkeycode)
30         dev->getkeycode = input_default_getkeycode;
31 
32     if (!dev->setkeycode)
33         dev->setkeycode = input_default_setkeycode;
34 
35     dev_set_name(&dev->dev, "input%ld",                                  //   設定input裝置物件的名字    input+數字
36              (unsigned long) atomic_inc_return(&input_no) - 1);
37 
38     error = device_add(&dev->dev);         //   新增裝置       例如:          /sys/devices/virtual/input/input0     
39     if (error)
40         return error;
41 
42     path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);  //  獲取input裝置物件所在的路徑      /sys/devices/virtual/input/input_xxx   
43     printk(KERN_INFO "input: %s as %s\n",
44         dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
45     kfree(path);
46 
47     error = mutex_lock_interruptible(&input_mutex);
48     if (error) {
49         device_del(&dev->dev);
50         return error;
51     }
52 
53     list_add_tail(&dev->node, &input_dev_list);             //   連結串列掛接:    將 input_dev->node 作為節點掛接到 input_dev_list  連結串列上
54 
55     list_for_each_entry(handler, &input_handler_list, node)  //  遍歷input_handler_list 連結串列上的所有handler
56         input_attach_handler(dev, handler);                        //  將handler與input裝置進行匹配
57 
58     input_wakeup_procfs_readers();                //  更新proc 檔案系統
59 
60     mutex_unlock(&input_mutex);
61 
62     return 0;
63 }

複製程式碼

(4)input_attach_handler函式:

input_attach_handler就是input_register_device函式中用來對下層的裝置驅動和上層的handler進行匹配的一個函式,只有匹配成功之後就會呼叫上層handler中的connect函式

進行連線繫結。

複製程式碼

 1 static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
 2 {
 3     const struct input_device_id *id;                //   定義一個input_device_id 的指標
 4     int error;
 5 
 6     id = input_match_device(handler, dev);   //  通過這個函式進行handler與input裝置的匹配工作
 7     if (!id)
 8         return -ENODEV;
 9 
10     error = handler->connect(handler, dev, id);  //  匹配成功則呼叫 handler 中的 connect 函式進行連線
11     if (error && error != -ENODEV)
12         printk(KERN_ERR
13             "input: failed to attach handler %s to device %s, "
14             "error: %d\n",
15             handler->name, kobject_name(&dev->dev.kobj), error);
16 
17     return error;
18 }
19 
20 
21 
22 static const struct input_device_id *input_match_device(struct input_handler *handler,
23                             struct input_dev *dev)
24 {
25     const struct input_device_id *id;            //   定義一個 input_device_id  指標
26     int i;
27 
28     for (id = handler->id_table; id->flags || id->driver_info; id++) {  //  依次遍歷handler->id_table 所指向的input_device_id 陣列中的各個元素
29                                                                                                                     //  依次進行下面的匹配過程
30         if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)         //    匹配匯流排
31             if (id->bustype != dev->id.bustype)
32                 continue;
33 
34         if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)  //  匹配供應商
35             if (id->vendor != dev->id.vendor)
36                 continue;
37 
38         if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)  //  匹配產品
39             if (id->product != dev->id.product)
40                 continue;
41 
42         if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)  //  匹配版本
43             if (id->version != dev->id.version)
44                 continue;
45 
46     //    下面的這些是匹配我們上傳的事件是否屬實
47         MATCH_BIT(evbit,  EV_MAX);
48         MATCH_BIT(keybit, KEY_MAX);
49         MATCH_BIT(relbit, REL_MAX);
50         MATCH_BIT(absbit, ABS_MAX);
51         MATCH_BIT(mscbit, MSC_MAX);
52         MATCH_BIT(ledbit, LED_MAX);
53         MATCH_BIT(sndbit, SND_MAX);
54         MATCH_BIT(ffbit,  FF_MAX);
55         MATCH_BIT(swbit,  SW_MAX);
56 
57         if (!handler->match || handler->match(handler, dev))
58             return id;        //    如果陣列中的某個匹配成功了就返回他的地址
59     }
60 
61     return NULL;
62 }

複製程式碼

input_attach_handler函式做的事情有兩件:呼叫input_match_device函式進行裝置與handler的匹配、匹配成功呼叫handler的連線函式進行連線(至於如何連線將會在後面說到)。

3、核心層提供給事件驅動層的介面函式

在input輸入核心層向事件驅動層提供的介面主要有兩個:

    input_register_handler。事件驅動層向核心層註冊handler

    input_register_handle。事件驅動層向核心層註冊handle。   注意上面的是handler,這裡是handle,不一樣,後面會說到。

(1)input_register_handler函式:

複製程式碼

 1 int input_register_handler(struct input_handler *handler)    //  向核心層註冊handler
 2 {
 3     struct input_dev *dev;          //  定義一個input_dev 指標
 4     int retval; 
 5 
 6     retval = mutex_lock_interruptible(&input_mutex);
 7     if (retval)
 8         return retval;
 9 
10     INIT_LIST_HEAD(&handler->h_list);      //  初始化 handler->h_list 連結串列
11 
12     if (handler->fops != NULL) {          //  如果 handler -> fops 存在
13         if (input_table[handler->minor >> 5]) {  //  如果input_table 陣列中沒有該handler  的位置了 則返回
14             retval = -EBUSY;
15             goto out;
16         }
17         input_table[handler->minor >> 5] = handler;  //  將 handler 指標存放在input_table 陣列中去
18     }
19 
20     list_add_tail(&handler->node, &input_handler_list);   //  將 handler 通過 handler -> node 節點 掛接到 input_handler_list 連結串列上
21 
22     list_for_each_entry(dev, &input_dev_list, node)     //  遍歷 input_dev_list 連結串列下掛接的所有的 input_dev 裝置
23         input_attach_handler(dev, handler);          //  然後進行匹配
24 
25     input_wakeup_procfs_readers();             //  更新proc 檔案系統
26 
27  out:
28     mutex_unlock(&input_mutex);
29     return retval;
30 }

複製程式碼

通過分析了上面的input_register_device和這裡的input_register_handler函式可以知道:註冊裝置的時候,不一定是先註冊了handler才能夠註冊裝置。當註冊裝置時,會先將

裝置掛接到裝置管理連結串列(input_dev_list)上,然後再去遍歷input_handler_list連結串列匹配hander。同樣對於handler註冊的時候,也會先將handler掛接到handler管理連結串列

(input_handler_list)上,然後再去遍歷input_dev_list連結串列匹配裝置。所以從這裡可以看出來,這種機制好像之前說過的platform匯流排下裝置和驅動的匹配過程。

而且一個input_dev可以與多個handler匹配成功,從而可以在sysfs中建立多個裝置檔案,也可以在/dev/目錄下建立多個裝置節點,並且他們的次裝置號是不一樣的,這個很好理解。

所以就是導致一個裝置對應多個次裝置號,那這樣有沒有錯呢?當然是沒有錯的。例如在我們的Ubuntu中,/dev/input/event3 和 

/dev/input/mouse1 都是對應滑鼠這個裝置。

(2)input_register_handle函式

這個函式的作用就是註冊一個handle,也就是實現上圖中的將各個handle連線起來構成一個環形的結構,再呼叫這個函式之前已經將handle中的dev和handler已經是填充好了的,

具體的這個函式程式碼就不去分析了。

其實handler、input_dev、handle3這之間的關係,在之前就已經接觸過了,講Linux裝置驅動模型底層架構的時候遇到過,下面用一副關係圖來描述他們之間的一個關係:

從本質上講,input_dev與handler是多對多的關係,從上圖可以看出來,一個input_dev可以對應多個handler,一個handler也可以對應多個input_dev。因為在匹配的時候,

一個input_dev會與所有的handler都進行匹配的,並不是匹配成功一次就退出。

從圖中可以看出來,一個handle就是用來記錄系統中一對匹配成功的handler和device,我們可以從這個handle出發得到handler的資訊,還可以得到device的資訊。所以正因為有這樣的

功能,所以可以由handler經過handle最終獲取到device的資訊,同理也可以從device從發經過handle最終獲取到handler的資訊。這種運用方法將會在後面的分析中看到。

4、總結:

核心層(其實就是驅動框架)提供的服務有哪些:

(1)建立裝置類、註冊字元裝置

(2)向裝置驅動層提供註冊介面

(3)提供上層handler和下層device之間的匹配函式

(4)向上層提供註冊handler的介面

二、輸入事件驅動層原始碼分析

input輸入子系統的輸入事件驅動層(上層)其實是由各個handler構成的,各個handler之間是屬於平行關係,不存在相互呼叫的現象。目前用的最多是event,今天就以這個handler

為例分析他的原始碼,以便對handler的實現有一定的瞭解,前面說到過,input輸入子系統的原始碼都在 drivers\input\這個目錄下,其中 drivers\input\evdev.c就是event

的原始碼檔案。

從evdev.c檔案的末尾可以看到使用了module_init、module_exit這些巨集,說明核心中將這部分實現為模組的方式,這其實很好理解,因為input核心層都是實現為模組的方式,而

上層是要依賴於核心層才能夠註冊、才能夠工作的,而核心層都已經實現為模組了,那麼上層不更得需要這樣做嗎。好了,廢話不多說開始分析程式碼。

1、模組註冊函式:

evdev_handler變數就是本次分析的handler對應的結構體變數,變數中填充最重要的有3個:

evdev_event函式:

evdev_connect函式:

evdev_fops變數:

2、相關的資料結構

複製程式碼

 1 struct evdev {     
 2     int exist;
 3     int open;                                //  這個是用來作為裝置被開啟的計數
 4     int minor;                               //   handler 與 input裝置匹配成功之後建立的裝置對應的device的次裝置號相對於基準次裝置號的偏移量
 5     struct input_handle handle;   //   內建的一個  handle ,裡面記錄了匹配成功的input_dev 和 handler            
 6     wait_queue_head_t wait;
 7     struct evdev_client *grab;
 8     struct list_head client_list;       //   用來掛接與 evdev 匹配成功的evdev_client 的一個連結串列頭 
 9     spinlock_t client_lock; /* protects client_list */
10     struct mutex mutex;             //  互斥鎖
11     struct device dev;                 //  這個是handler 與 input裝置匹配成功之後建立的裝置對應的device
12 };

複製程式碼

複製程式碼

 1 struct evdev_client {
 2     struct input_event buffer[EVDEV_BUFFER_SIZE];    //  用來存放input_dev 事件的緩衝區
 3     int head;
 4     int tail;
 5     spinlock_t buffer_lock; /* protects access to buffer, head and tail */
 6     struct fasync_struct *fasync;
 7     struct evdev *evdev;              //   evdev 指標
 8     struct list_head node;            //  作為一個連結串列節點掛接到相應的 evdev->client_list 連結串列上
 9     struct wake_lock wake_lock;
10     char name[28];            //  名字
11 };

複製程式碼

1 struct input_event {
2     struct timeval time;        //  事件發生的事件
3     __u16 type;                    //  事件的型別
4     __u16 code;                   //   事件的碼值
5     __s32 value;                   //  事件的狀態
6 };

3、函式詳解

(1)evdev_connect函式分析:

複製程式碼

 1 static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
 2              const struct input_device_id *id)
 3 {
 4     struct evdev *evdev;                 //  定義一個 evdev 指標
 5     int minor;
 6     int error;
 7 
 8     for (minor = 0; minor < EVDEV_MINORS; minor++)  //  從evdev_table 陣列中找到一個沒有被使用的最小的陣列項  最大值32
 9         if (!evdev_table[minor])
10             break;
11 
12     if (minor == EVDEV_MINORS) {
13         printk(KERN_ERR "evdev: no more free evdev devices\n");
14         return -ENFILE;
15     }
16 
17     evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);   //  給evdev 申請分配記憶體
18     if (!evdev)
19         return -ENOMEM;
20 
21     INIT_LIST_HEAD(&evdev->client_list);            //  初始化 evdev->client_list 連結串列
22     spin_lock_init(&evdev->client_lock);              //  初始化自旋鎖 evdev->client_lock
23     mutex_init(&evdev->mutex);                          //  初始化互斥鎖 evdev->mutex
24     init_waitqueue_head(&evdev->wait);
25 
26     dev_set_name(&evdev->dev, "event%d", minor);  // 設定input裝置的名字
27     evdev->exist = 1;
28     evdev->minor = minor;                                     //  input裝置的次裝置號的偏移量 
29 
30     evdev->handle.dev = input_get_device(dev);              //  將我們傳進來的 input_dev 指標存放在 evdev->handle.dev 中
31     evdev->handle.name = dev_name(&evdev->dev);     //  設定 evdev -> dev 物件的名字,並且把名字賦值給 evdev->handle.name
32     evdev->handle.handler = handler;          //  將我們傳進來的 handler 指標存放在 handle.handler 中
33     evdev->handle.private = evdev;             //  把evdev 作為handle 的私有資料
34 
35     evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);        //  設定 evdev->device 裝置的裝置號
36     evdev->dev.class = &input_class;                                                  //  將 input_class 作為 evdev->device 的裝置類
37     evdev->dev.parent = &dev->dev;                                                // 將input_dev  -> device 作為evdev->device 的父裝置
38     evdev->dev.release = evdev_free;                   //  evdev -> device 裝置的解除安裝函式
39     device_initialize(&evdev->dev);                      //  裝置初始化
40 
41     error = input_register_handle(&evdev->handle);       //  註冊handle 
42     if (error)
43         goto err_free_evdev;
44 
45     error = evdev_install_chrdev(evdev);       // 安裝evdev   其實就是將evdev 結構體指標存放在evdev_table陣列當中  下標就是evdev->minor
46     if (error)
47         goto err_unregister_handle;
48 
49     error = device_add(&evdev->dev);     //  新增裝置到系統          /sys/devices/virtual/input/input0/event0        event0就是表示建立的裝置檔案
50     if (error)
51         goto err_cleanup_evdev;
52 
53     return 0;
54 
55  err_cleanup_evdev:
56     evdev_cleanup(evdev);
57  err_unregister_handle:
58     input_unregister_handle(&evdev->handle);
59  err_free_evdev:
60     put_device(&evdev->dev);
61     return error;
62 }

複製程式碼

這裡搞清楚:   /sys/devices/virtual/input/input0  這個裝置是在註冊input_dev時建立的,而input0/event0就是在handler和input_dev匹配成功之後建立的,也會在/dev/目錄

下建立裝置節點。 

(2)evdev_open分析

複製程式碼

 1 static int evdev_open(struct inode *inode, struct file *file)
 2 {
 3     struct evdev *evdev;                       //  定義一個 evdev 結構體指標
 4     struct evdev_client *client;             //   定義一個evdev_client 指標
 5     int i = iminor(inode) - EVDEV_MINOR_BASE;   //  通過inode 獲取 需要開啟的裝置對應的evdev_table 陣列中的下標變數
 6     int error;
 7 
 8     if (i >= EVDEV_MINORS)
 9         return -ENODEV;
10 
11     error = mutex_lock_interruptible(&evdev_table_mutex);
12     if (error)
13         return error;
14     evdev = evdev_table[i];             //  從evdev_table  陣列中找到evdev 
15     if (evdev)
16         get_device(&evdev->dev);
17     mutex_unlock(&evdev_table_mutex);
18 
19     if (!evdev)
20         return -ENODEV;
21 
22     client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);      //  給 client 申請分配記憶體
23     if (!client) {
24         error = -ENOMEM;
25         goto err_put_evdev;
26     }
27 
28     spin_lock_init(&client->buffer_lock);
29     snprintf(client->name, sizeof(client->name), "%s-%d",
30             dev_name(&evdev->dev), task_tgid_vnr(current));
31     wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
32     client->evdev = evdev;                          //  通過client->evdev 指標指向 evdev
33     evdev_attach_client(evdev, client);   //  其實這個函式就是做了一個連結串列掛接:  client->node  掛接到 evdev->client_list
34 
35     error = evdev_open_device(evdev); //  開啟 evdev 裝置   最終就會開啟 input_dev -> open 函式
36     if (error)
37         goto err_free_client;
38 
39     file->private_data = client;              //   將evdev_client 作為file 的私有資料存在
40     nonseekable_open(inode, file);
41 
42     return 0;
43 
44  err_free_client:
45     evdev_detach_client(evdev, client);
46     kfree(client);
47  err_put_evdev:
48     put_device(&evdev->dev);
49     return error;
50 }

複製程式碼

4、總結:

(1)其實下層可以上報的事件都在我們的核心中是定義好的,我們都可以上報這些事,但是input子系統的上層輸入事件驅動層的各個handler只能夠處理某一些事件(event除外),

例如joy handler只能處理搖桿型別的事件,key handler只能處理鍵盤,內部實現的原理就是會在核心層做handler和device匹配的過程。如果我們的上報的事件與多個handler都

能夠匹配成功,那麼繫結之後核心層會向這多個handler都上報事件,再由handler上報給應用層。

(2)input設備註冊的流程:

下層通過呼叫核心層的函式來向子系統註冊input輸入裝置

/******************************************************************************/

input_register_device

    device_add:  /sys/devices/virtual/input/input0

    連結串列掛接: input_dev->node    ------->  input_dev_list

    input_attach_handler                          //  進行input_dev和handler之間的匹配

        呼叫handler->connect進行連線

            構建evdev結構體,加入evdev_table陣列

            input_register_handle

            device_add:  /sys/devices/virtual/input/input0/event0

/*******************************************************************************/

(3)handler註冊流程

/****************************************************************/

input_register_handler

    input_table[handler->minor >> 5] = handler

    連結串列掛接:  handler->node  ----->   input_handler_list

    input_attach_handler

        handler->connect                  // 呼叫handler的connect函式進行連線

/****************************************************************/

(4)事件如何傳遞到應用層

input子系統下層通過呼叫input_event函式項核心層上報資料

input_event

    input_handle_event

        input_pass_event

            handler->event()            //  最終會呼叫到handler 中的event函式

                evdev_pass_event

                    client->buffer[client->head++] = *event;     //  會將input輸入事件資料存放在evdev_client結構體中的緩衝去中

當我們的應用層通過open開啟event0這個裝置節點時最終會呼叫到input_init函式中註冊的字元裝置input時註冊的file_operations->open() 函式

input_open_file

    handler = input_table[iminor(inode) >> 5]

    handler->fops->open()          

        evdev = evdev_table[i];

        evdev_open_device

            input_open_device

                input_dev->open()         //  最終就是執行input裝置中的open函式

        file->private_data = evdev_client; 

所以當我們在應用層呼叫read函式時,最終會呼叫到handler->fops->read函式

evdev_read

    evdev_fetch_next_event

        *event = client->buffer[client->tail++]      //  將evdev_client->buffer中的資料取走

    input_event_to_user

        copy_to_user                  //  拷貝到使用者空間

/********************************************************************************************/