1. 新建一個輸入設(shè)備驅(qū)動程序
1.0 一個最簡單的例子
本文由DroidPhone 翻譯:http://blog.csdn.net/droidphone
Kernel版本:V3.4.10
以下是一個非常簡單的輸入設(shè)備驅(qū)動程序。該設(shè)備只有一個按鍵,它通過BUTTON_PORT這一i/o端口訪問,當(dāng)按下或釋放該按鍵,會發(fā)生BUTTON_IRQ中斷,驅(qū)動程序看起來就像這樣:
[cpp]?view plain?copy
#include???
#include???
#include???
#include???
#include???
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.1 例子驅(qū)動的工作過程
首先它必須包含頭文件,它是input子系統(tǒng)的接口,它提供了所有必要的定義信息。
_init初始化函數(shù),可以通過模塊進(jìn)行加載,也可以編譯進(jìn)內(nèi)核中,它收集設(shè)備需要的資源(也會檢查設(shè)備是否存在)。
接著,它用input_allocate_device()分配了一個input device結(jié)構(gòu),設(shè)置它的bitfields,設(shè)備驅(qū)動程序通過這一方式把自身的信息通知input子系統(tǒng)的其他部分:它能產(chǎn)生或者接受什么事件。我們的例子設(shè)備只能產(chǎn)生EV_KEY類型的事件,而且只能發(fā)出BTN_0事件code。這樣我們只需要設(shè)置這兩個bits,我們也可以用
[cpp]?view plain?copy
set_bit(EV_KEY,?button_dev.evbit);??
set_bit(BTN_0,?button_dev.keybit);??
進(jìn)行設(shè)置,但是上面例子代碼中的方法可以一次設(shè)置更多的位。
然后,例子驅(qū)動用下面的函數(shù)注冊input device結(jié)構(gòu):
[cpp]?view plain?copy
input_register_device(&button_dev);??
這會把button_dev結(jié)構(gòu)添加到input driver的全局鏈表中,調(diào)用device handler模塊中的_connect函數(shù)來通知他一個新的設(shè)備出現(xiàn)了。input_register_device()可能會休眠,所以他不能在中斷或者持有一個spinlock的情況下被使用。
功能上,該驅(qū)動只使用了函數(shù):
[cpp]?view plain?copy
button_interrupt()??
在每次中斷中通過檢查按鍵的狀態(tài),并通過以下函數(shù)上報:
[cpp]?view plain?copy
input_report_key()??
該函數(shù)會進(jìn)入input子系統(tǒng)。中斷服務(wù)程序不必檢查它是否會給input子系統(tǒng)報告value重復(fù)的事件(例如:按下,按下)。因為input_report_*函數(shù)自己會對此進(jìn)行檢查。
然后就是調(diào)用:
[cpp]?view plain?copy
input_sync()??
該函數(shù)告訴事件的接收者,我們已經(jīng)發(fā)送了一次完整的報告信息。這對于我們這個只有一個按鍵的設(shè)備好像不太重要,但有些情況下它是非常重要的,例如當(dāng)鼠標(biāo)移動后,你不希望X和Y值被分開解釋,因為那樣會被解釋為兩次移動。
1.2 dev->open() and dev->close()
當(dāng)驅(qū)動因為設(shè)備沒有提供中斷能力時,它需要不停地查詢設(shè)備的狀態(tài),但是如果一直進(jìn)行這個查詢顯得有點浪費。有時設(shè)備需要使用一些有價值的資源(例如中斷)。這時,我們可以使用open和close回調(diào)函數(shù)來實現(xiàn)動態(tài)地停止查詢和釋放中斷和決定何時再次恢復(fù)查詢和獲取中斷。要實現(xiàn)這一功能,我們的例子驅(qū)動需要添加以下代碼:
[cpp]?view plain?copy
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;??
...??
}??
需要注意的是,input核心會保持設(shè)備的使用計數(shù)來保證dev->open()只有當(dāng)?shù)谝粋€用戶連接該設(shè)備時才被調(diào)用,而dev->close()只有當(dāng)最后一個用戶斷開和設(shè)備的連接時才被調(diào)用。對兩個回調(diào)的調(diào)用都是串行化的。
當(dāng)調(diào)用成功時,open()回調(diào)返回0,返回非0則表示發(fā)生了錯誤。返回類型是void的close()回調(diào)必須一直成功。
1.3 基本事件類型(types)
最簡單的事件類型是EV_KEY,它用于鍵盤和按鈕,它通過以下函數(shù)上報給input子系統(tǒng):
[cpp]?view plain?copy
input_report_key(struct?input_dev?*dev,?int?code,?int?value)??
linux/input.h定義了該類型可用的values和code (從0 到 KEY_MAX)。Value被解釋為真假值,也就是任何非0值意味著鍵被按下,0則意味著鍵被松開。input子系統(tǒng)的代碼只有當(dāng)value的值和之前的值不同時才會生成一次事件。
除了EV_KEY外,還有另外兩個基本的事件類型:EV_REL和EV_ABS 。它們用來提供設(shè)備的相對和絕對值。鼠標(biāo)移動是一種相對值,鼠標(biāo)報告相對于上一個位置的相對差值,因為它工作在沒有任何絕對坐標(biāo)系系統(tǒng)中。絕對值事件對游戲操縱桿和數(shù)字化儀有用,這些設(shè)備工作在一個絕對坐標(biāo)系統(tǒng)中。
設(shè)備上報EV_REL就像EV_KEY一樣簡單,只要設(shè)置相應(yīng)的位然后調(diào)用以下函數(shù)即可:
[cpp]?view plain?copy
input_report_rel(struct?input_dev?*dev,?int?code,?int?value)??
只有當(dāng)非0的value時,事件才會被產(chǎn)生。
不過EV_ABS需要一點點特別的處理。在調(diào)用input_register_device之前,你需要在input_dev結(jié)構(gòu)中為你的設(shè)備所支持的軸填充的額外字段。假如我們的按鈕設(shè)備有ABS_X軸:
[cpp]?view plain?copy
button_dev.absmin[ABS_X]?=?0;??
button_dev.absmax[ABS_X]?=?255;??
button_dev.absfuzz[ABS_X]?=?4;??
button_dev.absflat[ABS_X]?=?8;??
或者,你只需要這樣:
[cpp]?view plain?copy
input_set_abs_params(button_dev,?ABS_X,?0,?255,?4,?8);??
上述設(shè)置適合于一個游系操縱桿設(shè)備,它有一個X軸,最小值是0,最大值是255(這表明它必須能提供的范圍,偶爾上報超出該范圍也不會有問題,但它必須要能達(dá)到最大和最小值)
,它的噪聲范圍是+-4,并且有一個大小是8的中心點。
如果你不需要absfuzz和absflat,你可以把它們設(shè)置為0,這意味著它是絕對準(zhǔn)確的并總是返回正中心位置(如果他有的話)。
1.4 BITS_TO_LONGS(), BIT_WORD(), BIT_MASK()
這3個宏來自bitops.h,有助于進(jìn)行位域計算:
BITS_TO_LONGS(x) - 返回x位的位域數(shù)組需要多少個long類型來組成。
BIT_WORD(x) - 返回位域數(shù)組中第x位所對應(yīng)的按long為單位的索引。
BIT_MASK(x) - 返回位x對應(yīng)的long型的mask值。
1.5 The id* and name fields
dev->name字段應(yīng)該要在驅(qū)動程序注冊輸入設(shè)備之前設(shè)置好。它是一個像'Generic button device'之類的對用戶友好的設(shè)備名字符串
id*字段包含了總線的ID(PCI,USB...),廠商ID和設(shè)備ID。總線IDs在input.h中定義。廠商和設(shè)備IDs在pci_ids.h,usb_ids.h和類似的頭文件中定義。這些字段也應(yīng)該在注冊設(shè)備之前被設(shè)置好。
idtype字段可以被用作輸入設(shè)備的專有信息。
這些id和name字段可以通過evdev接口傳遞到用戶空間中。
1.6 keycode, keycodemax, keycodesize 字段
這3個字段用于需要鍵值映射的輸入設(shè)備,keycode是一個用于把掃描碼轉(zhuǎn)換為輸入系統(tǒng)鍵值的數(shù)組,keycodemax是這個數(shù)組的大小,keycodesize則是該數(shù)組每個元素的大小(以字節(jié)為單位)
用戶空間可以通過相應(yīng)的evdev接口,使用EVIOCGKEYCODE和EVIOCSKEYCODE ioctls來查詢和修改當(dāng)前的掃描碼到鍵值的映射表。當(dāng)設(shè)備填充了3個上述的字段,驅(qū)動程序可以根據(jù)kernel的默認(rèn)實現(xiàn)來設(shè)置和查詢鍵值映射表。
1.7 dev->getkeycode() and dev->setkeycode()
getkeycode()和setkeycode()回調(diào)允許驅(qū)動程序覆寫由input核心代碼提供的對keycode/keycodemax/keycodesize的默認(rèn)映射機制,從而可以實現(xiàn)稀疏的映射方式。
1.8 按鍵的autorepeat
... 很簡單,它由input.c模塊處理。硬件autorepeat沒有被使用,因為很多設(shè)備不存在該功能,而且就算存在該功能,有時候也不正常(例如,Toshiba筆記本中的鍵盤)。要使能你的設(shè)備的autorepeat功能,只要設(shè)置dev->evbit中的EV_REP位即可,其它的事情都有輸入子系統(tǒng)處理。
1.9 其它的事件types, 輸出事件處理
現(xiàn)在為止,其它的事件types有:
EV_LED - 用作鍵盤的LEDs燈。
EV_SND - 用于鍵盤的蜂鳴器。
它們和鍵盤事件很相似,但是它們按另一個方向走動 - 從系統(tǒng)到輸入設(shè)備驅(qū)動程序。如果你的驅(qū)動程序可以處理這些事件,必須設(shè)置evbit中相應(yīng)的位,而且要實現(xiàn)一個回調(diào)函數(shù):
[cpp]?view plain?copy
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;??
}??
這個回調(diào)可以在中斷上下文或者BH上下文中被調(diào)用,所以它不能睡眠,不要處理太長的時間。
?
評論
查看更多