文章目錄
- 前言
- 一、輸入子系統的作用
- 二、輸入子系統架構分析
-
- 1.輸入子系統架構圖
- 2.輸入子系統分析
-
- 2.1 核心層分析
- 2.2 事件層分析
- 2.3 input device driver層分析
- 3. input事件分析
- 三、按鍵驅動執行個體
- 總結
前言
本文僅作為學習筆記總結
提示:以下是本篇文章正文内容,下面案例可供參考
一、輸入子系統的作用
- 相容所有輸入裝置——Linux系統支援的輸入裝置繁多,例如鍵盤、滑鼠、觸摸屏、搖桿或者是一些輸入裝置像體感輸入等等,Linux系統為了管理如此之多的不同類型、不同原理、不同的輸入資訊的,引入了輸入子系統,統一了實體形态各異的相似的輸入裝置的處理功能。
- 統一的應用操作接口——提供了用于分發輸入報告給使用者應用程式的簡單的事件(event)接口。你的驅動不必建立、管理/dev節點以及相關的通路方法。是以它能夠很友善的調用輸入API以發送滑鼠移動、鍵盤按鍵,或觸摸事件給使用者空間。
- 統一的程式設計驅動方法——抽取出了輸入驅動的通用部分,簡化了驅動,并提供了一緻性。
二、輸入子系統架構分析
1.輸入子系統架構圖
2.輸入子系統分析
由上面的圖我們可以看到,輸入子系統由三部分組成:Event handler、input core、Input driver,我們接下來會主要通過檢視源碼來分析這三部分各自的功能及之間的聯系。
2.1 核心層分析
核心層代碼的實作是在drivers/input/input.c中,我們從該檔案開始分析:
首先我們看到整個檔案是一個驅動子產品,很顯然輸入子系統是作為一個子產品存在的,然後我們找到入口函數:
由上圖可知,入口函數注冊了一個input的類,然後在proc下建立了相關的檔案,之後與input_fops綁定,注冊了驅動,這就是入口函數做的工作,我們發現這裡并沒有在該類下面建立裝置檔案,那以後我們需要讀取一個輸入的時候,比如說按鍵輸入,我們怎麼擷取資訊呢?先不着急,肯定會在後面建立一個裝置檔案的。而當我們打開相應的裝置檔案擷取資訊的時候,就會調用這裡的input_fops中的.open函數。我們分析一下.open函數的作用:
從截圖中的四步可以看到,.open主要是根據次裝置号在一個input_table【】的全局數組中找到一個對應的handler,然後執行該handler的fops指派給file_f_op,然後執行新的open函數。從上面的步驟我們可以分析出來,input.c中的fops.open函數其實就是起一個承接的作用,并沒有另外實質的作用。
分析到這裡有兩個疑惑:
- handler哪來的?input_table數組成員哪來的?
- 什麼時候建立裝置檔案呢?主次裝置号哪來的?
接下來我們分析解答這兩個問題。
2.2 事件層分析
我們知道輸入子系統的其中一個作用是提供統一的應用操作接口,事件層就實作了該部分的功能,這部分是純軟體的實作。就是說我們的應用層讀取輸入裝置的時候,所有的接口都是在這裡實作的。
上面的第一個疑問handler哪來的?input_table數組成員哪來的?這裡我們解釋一下,input_table初始化之後是一個空數組,然後我們在注冊一個handler的時候,會把該handler放到該數組中。注冊函數如下:
如下圖所示,有evdev.c(事件裝置),tsdev.c(觸摸屏裝置),joydev.c(joystick操作杆裝置),keyboard.c(鍵盤裝置),mousedev.c(滑鼠裝置) 這5個核心自帶的裝置處理函數注冊到input子系統中,我們以evdev為例說明。
打開evdev.c檔案,看到也是一個子產品檔案,可知當子產品加載的時候,就用調用初始化函數,我們這裡來看一下,加載函數會直接調用input_register_handler()向input中注冊一個handler,即會根據.minor将該handler儲存到handler_table【】中,然後将該handler加入到核心系統中的handler連結清單中,加入到連結清單中幹什麼呢?他會根據.id_table與裝置進行比對。至于怎麼比對,我們介紹了裝置添加之後再分析。
到此,我們知道,當打開一個裝置檔案的時候調用.open函數,最終就會把fops替換成handler.fos,這裡即evdev_fops,我們在應用層open,read等操作就都對應這些接口了。
2.3 input device driver層分析
裝置層其實就是說一個外部的輸入裝置怎麼加入到系統中來,并且怎麼通過系統将它的資料傳給應用層去。之前我們分析handler的時候說過,每注冊一個handler的時候,都會将這個handler與一個裝置連結清單中的每一項進行比對。這個裝置連結清單怎麼來的呢?我們這裡先分析一下input_dev的注冊函數,由于内容比較多,我們這裡隻借去出這三個函數,可以看到,注冊一個dev其實也會将這個dev加入到輸入子系統的連結清單中,然後遍裡handler的連結清單,進行比對。上面沒有介紹怎麼比對,這裡介紹一下,下面三個圖是比對用的到結構體,input_dev中的id與handler->id_table進行比對。如果比對上了,就會進行一些操作,但是不會停止周遊連結清單繼續比對,是以說,可能會一個裝置比對到多個事件,當然反過來也有可能。
int input_register_device(struct input_dev *dev)
{
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
}
上面提到,比對之後會進行一些操作,到底進行什麼操作呢?其實比對成功之後會調用如下函數,即handler對應的connect。該函數我就不列出源碼來分析了,隻是說一下該函數的功能:
- 配置設定一個input_handle結構體。
-
input_handle.dev = input_dev; // 指向左邊的input_dev
input_handle.handler = input_handler; // 指向右邊的input_handler
-
input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
其實簡單一點說,就是弄一個連結清單,把包含了handler與對應的dev成員的結構體變量handle儲存在該連結清單中,并且把原來的handler與dev中的一個指針指向該handle,這樣做的目的就是讓任何一個handler或者dev能找到對應的handle,以至于進一步找到對應的handler或者dev。并且該函數還會調用class_device_create來建立字元裝置檔案,這也就解釋了我的第二個疑惑,主次裝置号哪來的?其實主裝置号是在事件層寫死的,次裝置号事件層也規定好了範圍,會自動找到還沒有用到的次裝置号,然後生成一個字元裝置檔案,當想要通路裝置的時候,直接打開該檔案就行了。
3. input事件分析
其實說了這麼多,輸入子系統的出現其實目的很簡單,就是為各種各樣的輸入裝置提供一個統一的上傳資料的接口,這個統一的接口類型是定義在input.c中的。資料類型如下:
上面這個結構體就是input子系統中用來傳遞資訊的結構體,可以看到,每個資訊都有一個時間,及該事件的類型,事件的健值及值。現在可能會又有疑惑,什麼是類型?什麼是健值?好,下面解釋:
(1)type:即事件的類型
input系統中你定義了如下的事件類型
一個裝置可以有多個裝置類型,比如滑鼠,移動即出發了相對坐标事件,點選出發按鍵事件。
(2)code:即事件的健值
code指事件的鍵值,在事件類型中的子事件,每一個事件類型都有其對應的一系列鍵值
例如按鍵事件,那麼你這個按鍵表示按鍵1還是按鍵2還是滑鼠左鍵
例如絕對位置事件,那麼你上報的這個事件是指X軸還是Y軸
input.h中定義了如下健值:
按鍵類型的
相對位置
絕對位置
(3)value
value是code(鍵值)對應的值,其解釋随code類型的變化而變化
例如在按鍵事件中,如果code表示按鍵1,那麼value等于1表示按鍵1按下,等于0等于按鍵1未按下
在絕對位置事件中,如果code表示X軸坐标,那麼value就表示觸摸點在X軸坐标的位置
三、按鍵驅動執行個體
上面分析了很多,但是有的地方也很籠統,這裡就舉個簡單的按鍵例子。
其實向輸入子系統注冊一個裝置很簡單,分為如下幾步:
- 配置設定一個input_dev結構體
- 設定
- 注冊
- 硬體相關的代碼,比如在中斷服務程式裡上報事件
下面列出jz2440_v3的按鍵加入到出入子系統中的代碼:
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms後啟動定時器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松開 : 最後一個參數: 0-松開, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}
static int buttons_init(void)
{
int i;
/* 1. 配置設定一個input_dev結構體 */
buttons_dev = input_allocate_device();;
/* 2. 設定 */
/* 2.1 能産生哪類事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2 能産生這類操作裡的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
/* 3. 注冊 */
input_register_device(buttons_dev);
/* 4. 硬體相關的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
這裡再介紹一下input_event函數:
總結
一個輸入子系統弄了整整兩天,路漫漫其悠遠兮啊