Linux輸入子系統分析一

Linux輸入子系統分析一

爲什麼要引入input子系統?

輸入設備分散不堪,用input子系統可以對分散的,不同類別的輸入設備進行統一驅動。
好處:
1. 統一了物理形態各異的輸入設備相似的設備處理,例如各種鼠標,鍵盤,觸摸屏。
2. 提供了用於分發輸入報告給用戶應用程序的簡單事件接口。你的驅動不必創建管理/dev節點以及相關的訪問方法。因此他能夠很方便的調用輸入API以發送鼠標移動,鍵盤按鍵或者觸摸事件給用戶空間。
3. 抽取出了輸入驅動的通用部分,簡化了驅動,並提供一致性。例如:輸入子系統提供了一個底層驅動的集合,支持對串口和鍵盤控制器等硬件輸入的訪問。

input子系統框架結構

這裏寫圖片描述
* 從上層到底層開始分析:

從上層到底層開始分析:

設備節點:

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

handler事件處理層(純軟件層,不涉及硬件)
  1. .描述:與用戶空間交互。爲不同硬件類型提供了用戶訪問以及處理接口。例如:當我們打開設備/dev/input/mice時,會調用到事件處理層的Mouse Handler來處理輸入事件,這也使得驅動層無需關心設備文件的操作,因爲Mouse Handler已經有了對應事件的處理方法。

  2. 主要功能:
    包含不同的解決方案,例如鍵盤,鼠標,觸摸屏等。對於不同的解決方案,都有一個input_handler的結構體。主要成員如下:
    .id_table: 一個存放該handler所支持的設備id的表(其實內部存放的是EV_xxx事件,用於判斷device是否支持該事件)
    .fops: 該handler的file_operation
    .connect: 連接該handler和所支持的device的函數
    .disconnect: 斷開該連接
    .event: 事件處理函數,讓device調用
    h_list: 一個鏈表,該鏈表保存着handler到所支持的所有device的中間站:即handler結構體的指針

input core核心層

  1. 描述:承上啓下。爲驅動層提供輸入設備的註冊和操作接口。由內核代碼drivers/input/input.c構成,他的存在屏蔽了用戶到驅動的交互細節,爲設備驅動層和事件處理層提供了相互通信的界面。

  2. 主要功能:

    • 註冊設備號
    • 對於swi進入的open函數進行第一層處理,並通過次設備號選擇handler進入第二層open,也就是真正的open所在的file_operation,並返回file_operation的fd
    • 提供input_register_device和input_register_handler函數分別用於註冊device和handler

device驅動層(純硬件)

  1. 描述:將底層硬件的輸入轉化爲統一事件形式,向核心層彙報。純硬件操作,包含不同的硬件接口處理,如gpio等

  2. 主要功能:對於不同的具體硬件操作,都對應着一個不同的input_device結構體
    也包含一個h_list鏈表

handler和device

對於handler和device,分別用鏈表input_handler_list和input_device_list進行維護。
當handler或者device減少的時候,分別往這兩個鏈表進行增加或刪除節點。

input子系統調用過程分析

  1. 當外部應用程序需要調用子系統的open函數時,會先通過主設備號進入到核心層,然後通過次設備號進入handler層,再調用.fops內的open函數返回fd
  2. 當外部應用程序需要調用子系統的read函數時,會通過返回的fd調用.fops內的read函數,然後休眠,等待被.event函數喚醒
  3. 當外部中斷到達的時候,會先確定中斷事件,然後調用input_event上報事件,再通過h_list裏面的所有handle調用對應的handler中的.event函數,對read進行喚醒,然後在read中返回,也就是當device有多個對應的handler的時候,input_event會向所有的handler上報事件
  4. 當需要加入新的handler時,需要先構建handler結構體,然後調用註冊函數input_register_handler對該handler進行註冊
    input_register_handler內部實現:往input_handler_list加入新增的handler結點,然後對input_device_list的所有結點,也就是對所有的device進行遍歷。通過.id_table查看該device是否支持該handler。對於支持該handler的device就調用.connecet逐一構建input_handler結構體,連接handler和device
  5. 當需要加入新的device時,需要先構建input_dev結構體,然後調用註冊函數input_register_device對該input_dev進行註冊
    input_register_device內部實現:往input_device_list加入新增的device結點,然後對input_handler_list的所有結點,也就是對所有的handler進行遍歷,通過handler的.id_table查看該handler是否支持device。對於支持該device的handler就調用.connect逐一構建input_handler結構體,連接handler和device

輸入設備開發步驟

  1. 在驅動加載模塊中,設置你的input設備支持的事件類型。
  2. 註冊中斷處理函數。例如:鍵盤設備需要編寫鍵盤的擡起,放下。觸摸屏設備需要編寫按下,擡起,絕對移動。鼠標設備需要編寫單擊,擡起,相對移動,並且需要在必要的時候提交硬件數據。比如:鍵值,座標,狀態等。
  3. 將輸入設備註冊到輸入子系統中。

數據結構分析

input_dev

struct input_dev
{
     void *private//輸入設備私有指針,一般指向用於描述設備驅動層的設備結構
     const char *name;     //輸入設備名稱
     const char *phys;     //設備節點名稱
     const char *uniq;     //指定唯一的ID號,就像MAC地址一樣
     struct input_id id;     //用於匹配事件處理層handler,標識設備驅動特徵。包含總線類型,生產廠商,產品類型,版本
     unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //用來記錄支持事件類型的位圖,設置方式:dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS)
     unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];     //用來記錄支持的按鍵值的位圖
     unsigned long relbit[BITS_TO_LONGS(REL_CNT)];     //用來記錄支持的相對座標的位圖
     unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];     //用來記錄支持的絕對座標的位圖(觸摸屏),設置方法:input_set_abs_params
     unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   //其他功能
     unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   //led
     unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   //beep
     unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];   //作用力功能
     unsigned long swbit[BITS_TO_LONGS(SW_CNT)];   //開關功能
     unsigned int keycodemax;     //支持的按鍵值的個數
     unsigned int keycodesize;        //每個鍵值的字節數
     void *keycode;          //指向按鍵池,存儲按鍵值的數組首地址
     int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);     //修改鍵值的函數,可選
     int (*getkeycode)(struct input_dev *dev, int scancode, int keycode);     //獲取掃描碼鍵值的函數,可選
     struct ff_device *ff;          //用於強制更新輸入設備的部分內容
     unsigned int repeat_key;     //最近一次按鍵值,用於連擊
     struct timer_list timer;     //自動連擊計時器
     int state;     //設備狀態
     int sync;     //最後一次同步後沒有新的事件置1,如果是1說明事件同步完成
     int abs[ABS_MAX + 1];     //當前各個座標的值
     int rep[REP_MAX + 1];     //自動連擊的參數
     unsigned long key[BITS_TO_LONGS(KEY_CNT)];     //反應當前按鍵狀態的位圖
      unsigned long led[BITS_TO_LONGS(LED_CNT)];   //反應當前led狀態的位圖
      unsigned long snd[BITS_TO_LONGS(SND_CNT)];   //反應當前beep狀態的位圖
     unsigned long sw[BITS_TO_LONGS(SW_CNT)];  
      int absmax[ABS_MAX + 1];     //當前各個座標的最大值
      int absmin[ABS_MAX + 1];     //當前各個座標的最小值
      int absfuzz[ABS_MAX + 1];     //當前各個座標的分辨率
      int absflat[ABS_MAX + 1];     //當前各個座標的基準值
     int (*open)(struct input_dev *dev);     //打開函數
      int (*close)(struct input_dev *dev);     //關閉函數
      int (*flush)(struct input_dev *dev, struct file *file);     //斷開連接時沖刷數據
      int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);     //事件處理,回調函數,可選
     struct input_handle *grab;     //類似私有指針,可以直接訪問到事件處理接口event
     spinlock_t event_lock;
     struct mutex mutex;     //用於open,close函數的連續訪問互斥
     unsigned int users;     //設備使用計數
     int going_away;
     struct device dev;     //設備結構體
     struct list_head h_list;     //handle鏈表
     struct list_head node;     //input_dev鏈表
};

input_handler:事件處理器的數據結構,代表一個事件處理器

  • 幾個操作函數

    1. void(*event)(struct input_handler *handle, unsigned int type, unsigned int code, int value);//事件處理器接收到來自input設備的事件時調用,負責處理事件
    2. int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);//當一個input設備模塊註冊到內核的時候調用,將事件處理器與輸入設備聯繫起來
    3. void (*disconnect)(struct input_handle *hanlde);//與connect相反
    4. void (*start)(struct input_handle *handle);
  • 兩個ID

    1. const struct input_device_id *id_table;//事件處理器支持的input設備
    2. const struct input_device_id *blacklist;//事件處理器忽略的input設備
  • 兩個鏈表
    struct list_head h_list;//用來鏈接他所支持的input_handle結構,input_dev和input_handler配對之後會生成一個input_handle結構
    struct list_head node;//鏈接了所有註冊到內核的事件處理器,鏈接到input_handler_list

input_handle:代表一個配對成功的input_dev和input_handler

struct input_handle
{
     void *private;//每個配對的事件處理器都會分配一個對應的設備結構,如evdev事件處理器evdev結構,注意這個結果和設備驅動層的input_dev不同。初始化handle時,保存到這裏
     int open;//打開標誌,每個input_handle打開後才能操作,一般通過事件處理器的open方法間接設置
     const char *name;
     struct input_dev *dev;//關聯的input_dev結構
     struct input_handler *handler;//關聯的input_handler結構
     struct list_head d_node;//input_handle通過d_node連接到input_dev的h_list鏈表
     struct list_head h_node;//input_handle通過h_node連接到input_handler的h_list鏈表
};

三個結構體之間的關係

  • input_dev是硬件驅動層,代表一個input設備。通過全局的input_dev_list連接到一起,設備註冊的時候實現這個操作
  • input_handler是事件處理層,代表一個事件處理器。通過全局的input_handler_list連接到一起,事件處理器註冊的時候實現這個操作(事件處理器不需要我們寫)
  • input_handle屬於核心層,代表配對的input設備和input事件處理器。沒有全局鏈表,他註冊的時候把自己分別掛在了input_dev和input_handler的h_list上
  • 小結:通過input_dev和input_handler就可以找到input_handle在設備註冊和事件處理器。註冊的時候都需要配對,配對之後就會實現鏈接。通過input_handle也可以找到input_dev和input_handler

補充結構體

  • evdev設備結構體
    在配對成功的時候生成,由handler->connect生成,對應設備文件是/class/input/event(n)
    例如觸摸屏驅動的event0,這個設備是用戶空間要訪問的設備,因爲沒有對應的硬件可以理解爲他是一個虛擬設備。但是通過handle->dev就可以找到input_dev結構,而他對應着觸摸屏,設備文件爲/class/input/input0.這個設備結構體生成之後保存在evdev_table中,索引值是minor
struct evdev
{
     int exist;
     int open;//打開標誌
     int minor;//次設備號
     struct input_handle handle;//關聯的input_handle
     wait_queue_head_t wait;//等待隊列,當進程讀取設備,而沒有事件產生的時候,進程就會睡在上面
     struct evdev_client *grab;//強制綁定的evdev_client結構
     struct list_head client_list;//evdev_client鏈表,這說明一個evdev設備可以處理多個evdev_client,可以有多個進程訪問evdev設備
     spinlock_t client_lock;
     struct mutex mutex;
     struct device dev;//這是一個設備結構
};
  • evdev用戶端結構
    這個結構在進程打開evdev0設備的時候調用evdev的open方法,在open中創建這個結構,並初始化。在關閉設備文件的時候釋放這個結構
struct evdev_client
{
     struct input_event buffer[EVDEV_BUFFER_SIZE];// input_event數據結構的數組, input_event代表一個事件,基本成員:類型type,編碼code,值value
     int head;//針對buffer數組的索引
     int tail;//針對buffer數組的索引,當head和tail相等的時候,說明沒有事件
     spinlock_t buffer_lock;//保護buffer的訪問
     struct fasync_struct *fasync;//異步通知函數
     struct evdev *evdev;//evdev設備
     struct list_head node;//evdev_client鏈表項
};
發佈了32 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章