1. 建立輸入裝置驅動程式¶
1.1. 最簡單的示例¶
下面是一個非常簡單的輸入裝置驅動程式示例。該裝置只有一個按鈕,並且可以在 i/o 埠 BUTTON_PORT 訪問該按鈕。按下或釋放時,會發生 BUTTON_IRQ。驅動程式可能如下所示
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/io.h>
static struct input_dev *button_dev;
static irqreturn_t button_interrupt(int irq, void *dummy)
{
input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
input_sync(button_dev);
return IRQ_HANDLED;
}
static int __init button_init(void)
{
int error;
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
return -EBUSY;
}
button_dev = input_allocate_device();
if (!button_dev) {
printk(KERN_ERR "button.c: Not enough memory\n");
error = -ENOMEM;
goto err_free_irq;
}
button_dev->evbit[0] = BIT_MASK(EV_KEY);
button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
error = input_register_device(button_dev);
if (error) {
printk(KERN_ERR "button.c: Failed to register device\n");
goto err_free_dev;
}
return 0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(BUTTON_IRQ, button_interrupt);
return error;
}
static void __exit button_exit(void)
{
input_unregister_device(button_dev);
free_irq(BUTTON_IRQ, button_interrupt);
}
module_init(button_init);
module_exit(button_exit);
1.2. 示例的作用¶
首先,它必須包含 <linux/input.h> 檔案,該檔案與輸入子系統介面。這提供了所有需要的定義。
在 _init 函式中,該函式在模組載入或啟動核心時呼叫,它會獲取所需的資源(它還應檢查裝置是否存在)。
然後,它使用 input_allocate_device() 分配一個新的輸入裝置結構,並設定輸入位域。這樣,裝置驅動程式會告訴輸入系統的其他部分它是什麼 - 哪些事件可以由該輸入裝置生成或接受。我們的示例裝置只能生成 EV_KEY 型別的事件,並且這些事件中只有 BTN_0 事件程式碼。因此,我們只設置這兩個位。我們可以使用
set_bit(EV_KEY, button_dev->evbit);
set_bit(BTN_0, button_dev->keybit);
同樣,但對於多個位,第一種方法往往更短。
然後,示例驅動程式透過呼叫
input_register_device(button_dev);
來註冊輸入裝置結構。這會將 button_dev 結構新增到輸入驅動程式的連結串列中,並呼叫裝置處理模組的 _connect 函式,以告訴他們新的輸入裝置已出現。 input_register_device() 可能會休眠,因此不得從中斷或持有自旋鎖的情況下呼叫。
在使用中,驅動程式唯一使用的函式是
button_interrupt()
它在來自按鈕的每次中斷時檢查其狀態,並透過
input_report_key()
呼叫輸入系統來報告它。無需檢查中斷例程是否未向輸入系統報告兩個相同的值事件(例如,按下,按下),因為 input_report_* 函式會自行檢查。
然後是
input_sync()
呼叫,以告訴那些接收事件的人,我們已傳送完整的報告。這在單按鈕情況下似乎並不重要,但對於滑鼠移動來說非常重要,因為您不希望 X 和 Y 值被單獨解釋,因為那會導致不同的移動。
1.3. dev->open() 和 dev->close()¶
如果驅動程式必須重複輪詢裝置,因為它沒有來自裝置的中斷,並且輪詢過於昂貴而無法一直進行,或者如果裝置使用有價值的資源(例如,中斷),則可以使用開啟和關閉回撥來了解何時可以停止輪詢或釋放中斷,以及何時必須恢復輪詢或再次獲取中斷。為此,我們會將以下內容新增到我們的示例驅動程式中
static int button_open(struct input_dev *dev)
{
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
return -EBUSY;
}
return 0;
}
static void button_close(struct input_dev *dev)
{
free_irq(IRQ_AMIGA_VERTB, button_interrupt);
}
static int __init button_init(void)
{
...
button_dev->open = button_open;
button_dev->close = button_close;
...
}
請注意,輸入核心會跟蹤裝置的的使用者數量,並確保僅當第一個使用者連線到裝置時才呼叫 dev->open(),並且僅當最後一個使用者斷開連線時才呼叫 dev->close()。對兩個回撥的呼叫都是序列化的。
如果成功,open() 回撥應返回 0,如果失敗,則返回任何非零值。 close() 回撥(為空)必須始終成功。
1.4. 抑制輸入裝置¶
抑制裝置意味著忽略來自它的輸入事件。因此,它是關於維護與輸入處理程式的關係 - 無論是已經存在的關係,還是在裝置處於抑制狀態時將要建立的關係。
如果抑制了裝置,則任何輸入處理程式都不會收到來自它的事件。
沒有人想要來自裝置的事件這一事實被進一步利用,透過在抑制和取消抑制操作上分別呼叫裝置的 close()(如果有使用者)和 open()(如果有使用者)。實際上,close() 的含義是停止向輸入核心提供事件,而 open() 的含義是開始向輸入核心提供事件。
在抑制時呼叫裝置的 close() 方法(如果有使用者)允許驅動程式節省電量。可以直接關閉裝置電源,也可以透過釋放驅動程式在使用執行時 PM 時在 open() 中獲得的執行時 PM 引用來節省電量。
抑制和取消抑制與輸入處理程式開啟和關閉裝置是正交的。在任何處理程式肯定與其匹配之前,使用者空間可能希望預先抑制裝置。
抑制和取消抑制也與裝置作為喚醒源是正交的。作為喚醒源在系統休眠時起作用,而不是在系統執行時起作用。驅動程式應如何對其抑制、休眠和作為喚醒源之間的互動進行程式設計是特定於驅動程式的。
以網路裝置為例 - 關閉網路介面並不意味著應該無法透過此介面在 LAN 上喚醒系統。因此,即使在抑制狀態下,也可能存在應被視為喚醒源的輸入驅動程式。實際上,在許多 I2C 輸入裝置中,它們的中斷被宣告為喚醒中斷,並且其處理發生在驅動程式的核心中,該核心不知道特定於輸入的抑制(也不應該知道)。包含多個介面的複合裝置可以在每個介面的基礎上進行抑制,例如,抑制一個介面不應影響裝置作為喚醒源的能力。
如果一個裝置在抑制狀態下要被視為喚醒源,則在對其 suspend() 進行程式設計時必須特別小心,因為它可能需要呼叫裝置的 open()。根據 close() 對於所討論的裝置意味著什麼,在進入休眠狀態之前不 open() 它可能會導致無法提供任何喚醒事件。裝置無論如何都會進入休眠狀態。
1.5. 基本事件型別¶
最簡單的事件型別是 EV_KEY,用於鍵和按鈕。它透過
input_report_key(struct input_dev *dev, int code, int value)
報告給輸入系統。有關程式碼的允許值(從 0 到 KEY_MAX),請參見 uapi/linux/input-event-codes.h。 Value 被解釋為真值,即任何非零值都表示鍵被按下,零值表示鍵被釋放。僅當值與之前不同時,輸入程式碼才會生成事件。
除了 EV_KEY 之外,還有另外兩種基本事件型別:EV_REL 和 EV_ABS。它們用於裝置提供的相對值和絕對值。相對值可能是例如滑鼠在 X 軸上的移動。滑鼠將其報告為與上次位置的相對差異,因為它沒有任何絕對座標系可以工作。絕對事件主要用於操縱桿和數字化儀 - 在絕對座標系中工作的裝置。
使裝置報告 EV_REL 按鈕與 EV_KEY 一樣簡單;只需設定相應的位並呼叫
input_report_rel(struct input_dev *dev, int code, int value)
函式。僅針對非零值生成事件。
但是,EV_ABS 需要特別小心。在呼叫 input_register_device 之前,您必須為您的裝置的每個絕對軸填充 input_dev 結構中的其他欄位。如果我們的按鈕裝置也具有 ABS_X 軸
button_dev.absmin[ABS_X] = 0;
button_dev.absmax[ABS_X] = 255;
button_dev.absfuzz[ABS_X] = 4;
button_dev.absflat[ABS_X] = 8;
或者,您可以直接說
input_set_abs_params(button_dev, ABS_X, 0, 255, 4, 8);
此設定適用於操縱桿 X 軸,最小值為 0,最大值為 255(操縱桿必須能夠達到此值,有時報告更多也沒有問題,但它必須始終能夠達到最小值和最大值),資料中的噪聲高達 +- 4,並且中心平坦位置的大小為 8。
如果您不需要 absfuzz 和 absflat,您可以將它們設定為零,這意味著該東西是精確的,並且始終返回到完全中心位置(如果有)。
1.6. BITS_TO_LONGS()、BIT_WORD()、BIT_MASK()¶
來自 bitops.h 的這三個宏可以幫助進行一些位域計算
BITS_TO_LONGS(x) - returns the length of a bitfield array in longs for
x bits
BIT_WORD(x) - returns the index in the array in longs for bit x
BIT_MASK(x) - returns the index in a long for bit x
1.7. id* 和 name 欄位¶
dev->name 應在輸入裝置驅動程式註冊輸入裝置之前設定。它是一個字串,如“Generic button device”,包含裝置的使用者友好名稱。
id* 欄位包含裝置的匯流排 ID(PCI、USB 等)、供應商 ID 和裝置 ID。匯流排 ID 在 input.h 中定義。供應商和裝置 ID 在 pci_ids.h、usb_ids.h 和類似的包含檔案中定義。這些欄位應由輸入裝置驅動程式在註冊之前設定。
idtype 欄位可用於輸入裝置驅動程式的特定資訊。
id 和 name 欄位可以透過 evdev 介面傳遞給使用者空間。
1.8. keycode、keycodemax、keycodesize 欄位¶
這三個欄位應由具有密集鍵對映的輸入裝置使用。 keycode 是一個用於將掃描碼對映到輸入系統鍵碼的陣列。 keycode max 應包含陣列的大小,keycode size 應包含其中每個條目的大小(以位元組為單位)。
使用者空間可以使用相應 evdev 介面上的 EVIOCGKEYCODE 和 EVIOCSKEYCODE ioctl 來查詢和更改當前掃描碼到鍵碼的對映。當裝置填充了所有 3 個上述欄位時,驅動程式可以依賴於核心的預設實現來設定和查詢鍵碼對映。
1.9. dev->getkeycode() 和 dev->setkeycode()¶
getkeycode() 和 setkeycode() 回撥允許驅動程式覆蓋輸入核心提供的預設鍵碼/keycodesize/keycodemax 對映機制,並實現稀疏鍵碼對映。
1.10. 按鍵自動重複¶
...很簡單。它由 input.c 模組處理。不使用硬體自動重複,因為它在許多裝置中不存在,即使存在,有時也會損壞(在鍵盤上:東芝筆記型電腦)。要為您的裝置啟用自動重複,只需在 dev->evbit 中設定 EV_REP。一切都將由輸入系統處理。
1.11. 其他事件型別,處理輸出事件¶
到目前為止,其他事件型別是
EV_LED - 用於鍵盤 LED。
EV_SND - 用於鍵盤蜂鳴聲。
它們與例如按鍵事件非常相似,但是它們沿相反的方向 - 從系統到輸入裝置驅動程式。如果您的輸入裝置驅動程式可以處理這些事件,則必須在 evbit 中設定相應的位,並且還必須設定回撥例程
button_dev->event = button_event;
int button_event(struct input_dev *dev, unsigned int type,
unsigned int code, int value)
{
if (type == EV_SND && code == SND_BELL) {
outb(value, BUTTON_BELL);
return 0;
}
return -1;
}
此回撥例程可以從中斷或 BH 呼叫(儘管這不是規則),因此不得休眠,並且完成時間不得太長。
1.12. 輪詢輸入裝置¶
透過將輸入裝置結構和回撥傳遞給函式
int input_setup_polling(struct input_dev *dev,
void (*poll_fn)(struct input_dev *dev))
來設定輸入輪詢。在回撥中,裝置應使用常規的 input_report_* 函式和 input_sync,就像其他裝置一樣。
還有一個函式
void input_set_poll_interval(struct input_dev *dev, unsigned int interval)
用於配置裝置將被輪詢的間隔(以毫秒為單位)。