1. 程式人生 > >輸入子系統筆記!

輸入子系統筆記!

lcd http open match 輸入子系統 char 打開 連接 是什麽

學完第一期基礎驅動入門後,接著開始講input子系統。看著視頻,心裏有個大概對輸入子系統的認識,但是說不出來。百度一下,摘抄吧。

https://blog.csdn.net/zouleideboke/article/details/70650001

1.什麽是子系統?

內核是操作系統的核心。Linux內核提供很多基本功能,如虛擬內存、多任務、共享庫、需求加載、共享寫時拷貝(Copy-On-Write)以及網絡功能等。增加各種不同功能導致內核代碼不斷增加。
Linux內核把不同功能分成不同的子系統,通過一種整體的結構把各種功能集合在一起,提高了工作效率。同時還提供動態加載模塊的方式,為動態修改內核功能提供了靈活性。

2.linux 驅動子系統

linux內核中自帶了很多的驅動子系統,其中比較典型的有:input子系統,led子系統,framebuffer子系統(LCD),I2c子系統等,這些子系統它是通過一層一層的函數傳遞與封裝,它實現了設備驅動的註冊,以及定義了file_operations結構體裏面的各種函數操作等,不需要在單獨的設備驅動代碼中進行註冊,定義,直接調用相應的的子系統即可,

3.linux input子系統

以前學了單獨的按鍵設備驅動以及led驅動,實際上,在linux中實現這些設備驅動,有一種更為推薦的方法,就是input輸入子系統。平常我們的按鍵,觸摸屏,鼠標等輸入型設備都可以利用input接口

(這個接口是什麽呢?可以看我對按鍵驅動分析就一目了然)來簡化驅動程序並實現設備驅動。Input子系統處理輸入事務,任何輸入設備的驅動程序都可以通過Input輸入子系統提供的接口註冊到內核,利用子系統提供的功能來與用戶空間交互.

我對input 子系統的理解是:其實input 子系統是對linux輸入設備驅動進行了高度抽象最終分成了三層:包括input設備驅動層,核心層,事件處理層,也就是說我們之前移植的按鍵,usb,觸摸屏驅動代碼也只是子系統的一部分,起初我們自己編寫的那些驅動代碼都是分散的,按鍵是按鍵,led是led,都沒有對這些不同類別的輸入設備驅動統一起來,也就是說input 子系統這種機制的出現,最大的優點我覺得就是為內核大大的簡化了驅動程序!!!這個input子系統最神秘之處就是它的核心層部分,這個核心層做了什麽呢?它在內核中是怎麽定義的?

2.linux輸入子系統原理

linux輸入子系統(linux input subsystem)從上到下由三層實現,分別為:輸入子系統事件處理層(EventHandler)、輸入子系統核心層(InputCore)和輸入子系統設備驅動層

設備驅動層:主要實現對硬件設備的讀寫訪問,中斷設置,並把硬件產生的事件轉換為核心層定義的規範提交給事件處理層。

核心層: 為設備驅動層提供了規範和接口。設備驅動層只要關心如何驅動硬件並獲得硬件數據(例如按下的按鍵數據),然後調用核心層提供的接口,核心層會自動把數據提交給事件處理層。子系統核心層是鏈接其他兩個層之間的紐帶與橋梁,向下提供驅動層的接口,向上提供事件處理層的接口。

事件處理層:事件處理層負責與用戶程序打交道,將硬件驅動層傳來的事件報告給用戶程序。

對於linux輸入子系統的框架結構圖:

技術分享圖片

分析:/dev/input目錄下顯示的是已經註冊在內核中的設備編程接口,用戶通過open這些設備文件來打開不同的輸入設備進行硬件操作。

事件處理層為不同硬件類型提供了用戶訪問及處理接口。例如當我們打開設備/dev/input/mice時,會調用到事件處理層的Mouse Handler來處理輸入事件,這也使得設備驅動層無需關心設備文件的操作,因為Mouse Handler已經有了對應事件處理的方法。

輸入子系統由內核代碼drivers/input/input.c構成,它的存在屏蔽了用戶到設備驅動的交互細節,為設備驅動層和事件處理層提供了相互通信的統一界面。

linux輸入子系統的事件處理機制

技術分享圖片

分析:由上圖可知輸入子系統核心層提供的支持以及如何上報事件到input event drivers。

作為輸入設備的驅動開發者,需要做以下幾步:

1. 在驅動加載模塊中,設置你的input設備支持的事件類型。(事件類型有:EV_SYN 0x00 同步事件;

                                  EV_KEY 0x01 按鍵事件;

                                 EV_LED 0x11 按鍵/設備燈等)

2. 註冊中斷處理函數,例如鍵盤設備需要編寫按鍵的擡起、放下,觸摸屏設備需要編寫按下、擡起、絕對移動,鼠標設備需要編寫單擊、擡起、相對移動,並且需要在必要的時候提交硬件數據(鍵值/坐標/狀態等等)

3.將輸入設備註冊到輸入子系統中

3.input 核心層

input核心層位於/drivers/input/input.c

3.1 入口和出口

 1 static const struct file_operations input_fops = {
 2     .owner = THIS_MODULE,
 3     .open = input_open_file,
 4 };
 5 static int __init input_init(void)
 6 {
 7     int err;
 8     err = class_register(&input_class);//註冊input類,這裏就是生成了sys/class/input目錄,在該目錄下會看到驅動鏈接文件
 9     if (err) {
10         printk(KERN_ERR "input: unable to register input_dev class\n");
11         return err;
12     }
13     err = input_proc_init();//在proc目錄下建立input子系統目錄及交互文件,即/proc/bus/input目錄下的devices文件和handlers文件 
14     if (err)
15         goto fail1;
16 
17     err = register_chrdev(INPUT_MAJOR, "input", &input_fops);//註冊一個主設備號為INPUT_MAJOR(13),次設備號為0~255,文件操作符為input_fops的字符設備
18     if (err) {
19         printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
20         goto fail2;
21     }
22     return 0;
23  fail2:    input_proc_exit();
24  fail1:    class_unregister(&input_class);
25     return err;
26 }
27 static void __exit input_exit(void)
28 {
29     input_proc_exit();
30     unregister_chrdev(INPUT_MAJOR, "input");
31     class_unregister(&input_class);
32 }
33 subsys_initcall(input_init);
34 module_exit(input_exit);

這個初始化函數中,有一個註冊字符設備函數register_chrdev(第17行),它用到了input_fops,這個結構體為struct file_operations,但這裏他只有一個input_open_file——open成員。

3.2 input_open_file函數

 1 static int input_open_file(struct inode *inode, struct file *file)
 2 {
 3     struct input_handler *handler = input_table[iminor(inode) >> 5];//根據所打開的驅動的次設備號inode,查找對應input_handler
 4     const struct file_operations *old_fops, *new_fops = NULL;
 5     int err;
 6     /* No load-on-demand here? */
 7     if (!handler || !(new_fops = fops_get(handler->fops)))//new_fops賦值為該驅動的file_operations
 8         return -ENODEV;
      ...

17 old_fops = file->f_op; 18 file->f_op = new_fops;//所打開的文件的f_op等於新的new_fops 19 err = new_fops->open(inode, file);//打開新的驅動      ...25 return err; 26 }

分析:根據inode,在input_table數組中查找的對應的input_handler,然後從中提取出.fops成員復制給new_fops,並調用其成員下的.open函數。old_fops是為了防止出錯做備份用的。

3.3 input_table的分析,input_table由誰構造?

首先input_table在Input.c中定義

1 static struct input_handler *input_table[8];

2 構造

Evbug.c/Evdev.c/Joydev.c/Keyboard.c/Mousedev.c....的入口函數

  input_register_handler //向上註冊

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

 //以Evdev.c為例
static int __init evdev_init(void)
{ 
   return input_register_handler(&evdev_handler); 
}
static struct input_handler evdev_handler = {
      .event =    evdev_event,
      .connect =    evdev_connect,
      .disconnect =    evdev_disconnect,
      .fops =        &evdev_fops,//這個結構體裏有各種讀寫操作函數
      .minor =    EVDEV_MINOR_BASE,//次設備號 宏定義64,handler->minor >> 5後,就是2 所以它在第三項
      .name =        "evdev",
      .id_table =    evdev_ids,//用於比較是否要打開的設備是否匹配
 };
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;//這裏evdev_handler添加到input_table[2]中
    }
    ...
    return 0;
}

分析:input_table由input_register_handler函數構造。

input_register_handler

  input_table[handler->minor >> 5] = handler;//放入數組

  list_add_tail(&handler->node, &input_handler_list);//放入鏈表

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

以上Evbug.c/Evdev.c/Joydev.c/Keyboard.c/Mousedev.c....向input.c註冊handler,為輸入設備實現一個接口

查看evdev_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,//表示這個接口能夠支持那些輸入設備,如果能夠支持就調用.connect函數,
};

3.4註冊輸入設備——input_register_device

input_register_device

    list_add_tail(&dev->node, &input_dev_list);//放入鏈表

    list_for_each_entry(handler, &input_handler_list, node)//對於每一個input_handler,都調用input_attach_handler函數

      input_attach_handler(dev, handler);//根據input_handler的id_table判斷是否支持這個input_dev

3.5input_attach_handler函數作用

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
   ...
    id = input_match_device(handler->id_table, dev);//根據id_table和dev判斷是否匹配
    if (!id)
       return -ENODEV;
     error = handler->connect(handler, dev, id);//如果匹配,執行.conect函數
     ...
     return error;
}

總結:註冊input_dev或input_handler時,會兩兩比較,根據input_handler的id_table判斷這個input_handler能否支持這個input_dev,如果能夠支持,則調用input_handler的connect函數建立連接。

輸入子系統筆記!