10分鐘完成一個(gè)USB驅動(dòng)程序
(Rayyang2000傾情奉獻)
很多寫(xiě)Windows Device Driver的開(kāi)發(fā)人員基本上都是使用Windows DDK進(jìn)行開(kāi)發(fā)的。但是,現在也有不少人都開(kāi)始借助一些輔助工具。筆者去年開(kāi)始接觸到riverStudio,發(fā)現它真的是一個(gè)不錯的開(kāi)發(fā)工具,不僅寫(xiě)代碼的時(shí)候思路清晰,而且和DDK的結合很好。
當然,也有很多人覺(jué)得用DriverStudio不夠正宗,或者說(shuō)不能很好的理解Windows Device Driver的架構。我感覺(jué)這就有點(diǎn)像MFC和SDK的關(guān)系,關(guān)于這個(gè)問(wèn)題在很多地方都有爭論,比如在萬(wàn)千新聞組上,就討論了將近2個(gè)月。每個(gè)人都有自己的最?lèi)?ài),都有自己的習慣,只要你能把事情做好,我想用什么方法應該都是一樣的。如果你已經(jīng)習慣了用DDK開(kāi)發(fā),那完全還可以繼續用下去;如果你覺(jué)得DriverStudio不錯,那嘗試用一個(gè)可以給你按照OOP概念來(lái)編程的工具有什么不好呢?
在驅動(dòng)開(kāi)發(fā)網(wǎng)上,經(jīng)??吹接腥嗽?xún)問(wèn)一些關(guān)于DriverStudio的使用的問(wèn)題。我正好很有幸用它作了幾個(gè)驅動(dòng)程序,包括VXD, KMD和WDM,稍微有點(diǎn)心得,因此想寫(xiě)下來(lái)給大家作一個(gè)小小的參考。如果其中有錯誤,歡迎大家給我指出,謝謝。
下面我就介紹一下用DriverStudio開(kāi)發(fā)一個(gè)USB驅動(dòng)程序的過(guò)程。這個(gè)USB設備有3個(gè)雙向端點(diǎn),每個(gè)端點(diǎn)的配置如下:
EP 類(lèi)型 地址 buffer(Bytes)
0 IN/OUT Control 0x80/0x00 16/16
1 IN/OUT Bulk 0x81/0x01 16/16
2 IN/OUT Bulk 0x82/0x02 64/64
我們的驅動(dòng)程序需要實(shí)現的功能就是控制設備上的LED燈的亮和滅,以及通過(guò)Endpoint 2對設備進(jìn)行讀寫(xiě)。
由于DriveStudio由幾個(gè)部分組成,我們寫(xiě)這個(gè)驅動(dòng)程序只要用到DriverWorks,因此下面我們就簡(jiǎn)稱(chēng)它為DW。在這里,我們假定讀者已經(jīng)正確的安裝了DW,并且已經(jīng)編譯好了各個(gè)庫文件。
1. 首先,我們通過(guò)快捷方式“Setup DDK and Start MSVC“來(lái)啟動(dòng)VC IDE。這個(gè)快捷方式所指向的程序,會(huì )進(jìn)行一些必要的設置,然后再啟動(dòng)VC IDE,這樣我們的程序就可以使用DDK和DW的頭文件和庫了。
2. 從VC IDE的菜單"DriverStudio"中選擇"DriverWizard", 在如圖1所示的對話(huà)框中, 寫(xiě)上項目名稱(chēng). 在這里, 我們將這個(gè)項目稱(chēng)為: TEST, 所在的目錄為D:\TEST. 然后點(diǎn)按鈕"Next >".
圖1
3. 在接下來(lái)的這個(gè)對話(huà)框中(如圖2), 我們需要選擇驅動(dòng)程序的類(lèi)型. 由于USB設備驅動(dòng)程序是WDM類(lèi)型的, 所以我們選擇第二項并且點(diǎn)按鈕"Next >".
圖2
4. 在第3個(gè)對話(huà)框中(如圖3), 選擇我們的驅動(dòng)程序所操作的總線(xiàn)類(lèi)型. 這里, 我們選擇USB. 在USB Vendor ID和USB Product ID中填入USB設備的VID和PID. 假定我們的USB設備的VID和PID分別是16進(jìn)制的0471和1801. 然后點(diǎn)按鈕"Next >". 關(guān)于VID和PID的規定請參考USB-IF的規范.
圖3
5. 在接下來(lái)的對話(huà)框中(如圖4), 我們需要加入Endpoint 1和Endpoint 2的定義. 由于在USB中規定Endpoint 0是必須存在的, 所以我們不需要對Endpoint 0進(jìn)行定義. 點(diǎn)"Add..."按鈕, 彈出一個(gè)如圖5所示的對話(huà)框. 我們將它修改成如圖6所示. 其中, 按照USB的規定, 對于端點(diǎn), 它的地址是1; 按照前面說(shuō)明的設備的特點(diǎn), Endpoint 1的最大的包大小為16字節, 因此在"Max Transer Size"中填入16; Endpoint Name可以通過(guò)"Suggest Name"得到. 按照這些原則, 繼續設置其他的配置, 以使對話(huà)框4變成如圖7所示. 接下來(lái), 繼續按"Next >"按鈕.
圖4
圖5
圖6
圖7
6. 在如圖8所示的對話(huà)框中, 可以填入我們需要的Driver Class的名字和文件名. 一般我們不需要更改. 繼續按"Next >"按鈕.
圖8
7. 在如圖9所示的對話(huà)框中, 因為不需要給其他的驅動(dòng)程序提供接口, 也不需要提供Flush功能, 所以不需要任何修改, 直接按"Next >"按鈕.
圖9
8. 在如圖10所示的對話(huà)框中, 我們選擇給端點(diǎn)2產(chǎn)生BULK Read的代碼, 并且按"Next >"按鈕. DW會(huì )給我們產(chǎn)生一套對端點(diǎn)2進(jìn)行讀的代碼, 不用修改, 就可以直接使用.
圖10
9. 在如圖11所示的對話(huà)框中, 我們選擇給端點(diǎn)2產(chǎn)生BULK Write的代碼, 并且按"Next "按鈕. 這樣, DW也會(huì )給我們產(chǎn)生一套對端點(diǎn)2進(jìn)行寫(xiě)的代碼, 不用修改, 就可以直接使用.
圖11
10. 對于如圖12的對話(huà)框, 我們直接按"Next >"按鈕. 這里是設置是否要將I/O請求排隊, 在這里, 我們不需要排隊.
圖12
11. 在如圖13所示的對話(huà)框中, 我們不需要創(chuàng )建任何注冊表項, 所以直接按"Next >"按鈕.
圖13
12. 如圖14所示的對話(huà)框, 是讓我們設置一些驅動(dòng)程序的屬性, 比如接口, 緩沖區之類(lèi)的. 一般的都可以使用缺省設置. 繼續按"Next >"按鈕.
圖14
13. 在如圖15所示的對話(huà)框中, 是讓我們給驅動(dòng)程序增加一些IOCTL接口. 我們只增加一個(gè)如圖16所示的IOCTL來(lái)控制USB設備的LED燈. 然后按"Next >"按鈕.
圖15
圖16
14. 在最后一個(gè)如圖17所示的對話(huà)框中, 可以設置一些驅動(dòng)程序的屬性, 產(chǎn)生一個(gè)console測試程序. 按下"Finish"按鈕, 就結束了Wizard.
圖17
這樣, 我們就創(chuàng )建好了一個(gè)基本的驅動(dòng)程序, 下面來(lái)看看還要做哪些工作才可以和我們的設備以及上層的應用程序通訊.
把函數NTSTATUS TESTDevice::TEST_IOCTL_LED_Handler(KIrp I)改成如下面的樣子:
NTSTATUS TESTDevice::TEST_IOCTL_LED_Handler(KIrp I)
{
NTSTATUS status = STATUS_INVALID_PARAMETER;
t << "Entering TESTDevice::TEST_IOCTL_LED_Handler, " << I << EOL;
__try
{
// TODO: Verify that the input parameters are correct
// If not, return STATUS_INVALID_PARAMETER
if(I.IoctlOutputBufferSize() || !I.IoctlBuffer() ||
(I.IoctlInputBufferSize() != sizeof(UCHAR)))
__leave;
// TODO: Handle the the ZBUARD_IOCTL_LED_ON request, or
// defer the processing of the IRP (i.e. by queuing) and set
// status to STATUS_PENDING.
PURB pUrb = m_Lower.BuildVendorRequest(
NULL, // transfer buffer
0, // transfer buffer size
0, // request reserved bits
(UCHAR)(*(PUCHAR)I.IoctlBuffer()), // request. 1 = LED_ON, 0 = LED_OFF
0 // Value
);
// transmit
status = m_Lower.SubmitUrb(pUrb, NULL, NULL, 5000L);
}
__finally
{
// TODO: Assuming that the request was handled here. Set I.Information
// to indicate how much data to copy back to the user.
I.Information() = 0;
I.Status() = status;
}
return status;
}
這個(gè)函數是控制LED燈的,它是通過(guò)USB Vendor Request來(lái)向設備傳送的。其中,request=1的時(shí)候表示讓LED亮,request=0的時(shí)候讓LED滅。它是通過(guò)DeviceIoControl由上層應用程序傳下來(lái)。
再看看讀寫(xiě)部分,經(jīng)過(guò)檢查NTSTATUS TESTDevice::Read(KIrp I)和NTSTATUS TESTDevice::Write(KIrp I)可以發(fā)現,DW已經(jīng)給我們寫(xiě)好了讀寫(xiě)的代碼,我們可以直接使用了。這些代碼就是在上面的第8和第9步中產(chǎn)生的代碼。
最后,修改編譯一下DriverStudio產(chǎn)生的測試程序Test_TEST程序,我們就可以通過(guò)命令行來(lái)測試我們的驅動(dòng)程序了。對于LED的控制,我們可以直觀(guān)的在設備上看到,但對于讀寫(xiě)的操作就需要和firmware程序配合,這已經(jīng)超出了本文的范圍,不在這里討論了。
通過(guò)上面的講解,我們可以看到有了DriverStudio,就可以快速的產(chǎn)生一個(gè)驅動(dòng)程序,然后在里面作一些小的改動(dòng)就可以使用了。即使是寫(xiě)一個(gè)比較復雜的USB驅動(dòng)程序,我們也可以不用管一些系統的IRP處理,只要專(zhuān)注于我們自己的特定應用就可以了。而且它把一個(gè)驅動(dòng)程序概括成幾個(gè)類(lèi)的概念,并且DW還附帶有一些很有用的STL類(lèi),在VC IDE里面有了一個(gè)很清晰直觀(guān)的表示。這樣,對一些從上層應用轉向驅動(dòng)程序的開(kāi)發(fā)人員,或者一些對C++/OOP很熟悉但不太了解系統內核的開(kāi)發(fā)人員,都比較容易上手。即使對于推崇直接用DDK編程的人來(lái)說(shuō),通過(guò)閱讀DriverStudio附帶的源代碼,也可以對驅動(dòng)程序的開(kāi)發(fā)有一個(gè)更加深入的了解。