標籤:
By Fanxiushu 2016-05-22 轉載或引用請註明原始作者
接上文,
在處理好USB資料擷取端的問題之後,接下來進入核心的部分,虛擬USB裝置端的開發工作。
上文簡單介紹過,需要開發虛擬匯流排驅動來類比USB裝置。
所謂虛擬匯流排驅動,就是安裝於System系統裝置下的一個驅動,由PnP管理器建立出一個虛擬匯流排PDO裝置,
我們的虛擬匯流排驅動Attach到這個PDO上,形成一個FDO功能裝置驅動,
然後在我們的驅動中,根據需要建立出若干個 Child PDO裝置,
這些 Child PDO裝置就是我們根據需要類比出來的虛擬設備。
我們的匯流排驅動每當建立出一個 Child PDO並且初始化之後,
調用 IoInvalidateDeviceRelations函數,通知PnP管理器我們的的Child PDO有變化。
於是PnP管理器接著發送 IRP_MN_QUERY_DEVICE_RELATIONS隨插即用訊息給我們的驅動,
等我們把新的所有Child PDO列表告訴給PnP管理器,它接著比較他內部維護的新舊的PDO列表,
知道哪些PDO被新添加,哪些已經被移除。
對於新添加的裝置,PnP管理器發送查詢裝置ID的訊息IRP_MN_QUERY_ID給我們建立的Child PDO,查詢裝置的各種ID,
然後PnP管理器根據裝置ID從註冊表尋找是否已經為這個Child PDO安裝了功能驅動,
如果已經安裝,則載入它,沒安裝則提示使用者安裝新的驅動。
這就是虛擬匯流排驅動的大致架構,原理上來說並不複雜,而且有微軟提供的 例子代碼,
可以閱讀它的例子代碼進一步加深對匯流排驅動原理的理解,或者可以查看我提供在CSDN上的原始碼來加深理解。
我們的匯流排驅動類比的是USB裝置介面,因此Child PDO必須具備USB介面的特性,
USB介面核心部分要處理的,其實就是上文簡單介紹過的USB介面的四種資料轉送方式:
一,控制傳輸,二中斷傳輸,三批量傳輸,四,同步傳輸。
中斷,批量,同步傳輸都比較好處理,而控制傳輸牽涉到的命令很多,因此需要處理多種命令。
windows平台把跟USB介面的裝置進行資料通訊統一使用URB資料包,每個包都指定一個Function功能號,
也就是URB的功能種類。Function的種類大概有20多個,其實依然是從USB介面的四種通訊方式派生出來的,
比如URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER 這個URB功能就是中斷傳輸和批量傳輸的合并。
URB_FUNCTION_ISOCH_TRANSFER就是同步傳輸,
而餘下來的20多個 URB_FUNCTION_XXX可以完全理解成控制傳輸的某個命令。
比如URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE就是控制傳輸中,從USB裝置擷取裝置描述符。
首先列舉中我們的驅動中需要處理的URB_FUNCTION_XXX命令:
(以下是中斷,批量,同步傳輸命令)
URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER(中斷或者批量傳輸)
URB_FUNCTION_ISOCH_TRANSFER(同步傳輸)
(以下全是控制傳輸命令)
通用的控制傳輸命令,當USB裝置傳輸的命令不在微軟定義的URB_FUNCTION時候,可以用它進行傳輸
URB_FUNCTION_CONTROL_TRANSFER
擷取裝置,介面,端點描述符
URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE
URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE
URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT
選擇配置描述符, 介面可選描述符
URB_FUNCTION_SELECT_CONFIGURATION
URB_FUNCTION_SELECT_INTERFACE
擷取USB裝置的Class或Vendor資訊
URB_FUNCTION_CLASS_DEVICE
URB_FUNCTION_CLASS_INTERFACE
URB_FUNCTION_CLASS_ENDPOINT
URB_FUNCTION_CLASS_OTHER
URB_FUNCTION_VENDOR_DEVICE
URB_FUNCTION_VENDOR_INTERFACE
URB_FUNCTION_VENDOR_ENDPOINT
URB_FUNCTION_VENDOR_OTHER
重設或者中斷在某個端點的傳輸
URB_FUNCTION_RESET_PIPE
URB_FUNCTION_ABORT_PIPE
擷取裝置,介面,端點狀態
URB_FUNCTION_GET_STATUS_FROM_DEVICE
URB_FUNCTION_GET_STATUS_FROM_INTERFACE
URB_FUNCTION_GET_STATUS_FROM_ENDPOINT
URB_FUNCTION_GET_STATUS_FROM_OTHER
擷取當前配置,當前介面,當前framenumbber。當前的framehnumber用於同步傳輸
URB_FUNCTION_GET_CONFIGURATION
URB_FUNCTION_GET_INTERFACE
URB_FUNCTION_GET_CURRENT_FRAME_NUMBER
以下是設定或清除FEATURE,主要用於HUB,當然可能某些USB裝置會有用到
URB_FUNCTION_SET_FEATURE_TO_DEVICE
URB_FUNCTION_SET_FEATURE_TO_INTERFACE
URB_FUNCTION_SET_FEATURE_TO_ENDPOINT
URB_FUNCTION_SET_FEATURE_TO_OTHER
URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE
URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE
URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT
URB_FUNCTION_CLEAR_FEATURE_TO_OTHER
windows為何要搞出這麼多FUNCTION命令,估計是為了理解和處理USB控制命令的方便。
但是這些控制命令經過 Host Controller進入真正的USB裝置前,Host Controller依然要把它轉換成 8個位元組的SetupPacket控制命令。
這是硬體需求,而我們是虛擬設備,因此沒必要非得轉成 SetupPacket格式,只要網路通訊中適合我們的就可以。
我們的USB資料擷取端和虛擬USB端,都屬於windows平台,轉成Setuppacket再轉成FUNCTION,反而麻煩,
因此基本是根據URB_FUNCTION做些簡單轉換,這樣方便也快捷。
但是如果你的採集端和USB虛擬端分別屬於不同的平台,比如linux,windows,macos,等各種平台都有,那得使用一個統一的通訊方式。
到時USB通訊協議中規定的格式估計是更好的選擇。
知道哪些URB_FUNCTION命令需要處理,可能大家還是不大明白如何處理這些URB,如何完整的類比一個USB介面,
從而實現把遠方的資料擷取端的USB裝置搬到虛擬端來。
假設你已經熟悉了虛擬匯流排驅動的架構。
虛擬匯流排驅動應該與應用程式層程式有個通訊介面,應用程式使用IOCTL跟驅動通訊。
應用程式層程式通過網路連接到USB資料擷取端,擷取到某個需要被遠端存取的USB裝置的硬體ID,相容ID等初步資訊,
通過CreatePDO IOCTL傳遞給虛擬匯流排驅動,虛擬匯流排驅動根據硬體ID等各種參數建立child PDO裝置,
建立成功後調用IoInvalidateDeviceRelations通知PnP管理器,接下來就是PnP管理器該做的事。
當PnP管理器正確載入根據硬體ID對應的功能驅動之後,這個功能驅動就開始工作。這個功能驅動開始構造URB包,
並且發送URB包到我們在虛擬匯流排驅動中建立的Child PDO裝置上,
接下來,我們的匯流排驅動必須把這些URB資料正確的傳遞到遠端的USB資料擷取端,並且得到正確的響應。
至於如何處理這個主要和核心的過程,每個工程師可能有不同的處理辦法,我們是採用把URB資料傳遞到應用程式層,
然後在應用程式層通過socket通訊端傳遞給資料擷取端,得到採集端的回應資料包之後,再把它傳遞給驅動,
最後我們的虛擬匯流排驅動完成從功能驅動發下來的這個URB資料包。
我們在應用程式層建立一個訊號量,傳遞到驅動,匯流排驅動使用這個訊號量通知應用程式層程式有新的URB資料包到達。
比如上層的功能驅動有個 URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE 的URB資料包投遞給我們的匯流排驅動,
於是匯流排驅動的把這個URB包掛載到Child PDO的等待處理的隊列中,然後增加訊號量,通知應用程式層有URB資料包到達。
應用程式層程式有一個或者多個線程調用 WaitForSingleObject 函數等待這個訊號量,
當WaitForSingleObject成功返回,說明有URB資料包,於是通過DeviceIoControl函數,投遞一個 BEGIN IOCTL到匯流排驅動,
我們的匯流排驅動從Child PDO的等待隊列取出一個URB資料包,分析處理這個URB資料包,
然後再把這個URB掛載到Child PDO的忙碌隊列中,同時產生一個seqno唯一標識這個URB包,完成這個BEGIN IOCTL。
應用程式層程式根據從BEGIN IOCTL擷取到的請求資料 ,發送到遠方的USB資料擷取端,等待對方的回應。
USB資料擷取端回應這個資料包之後,應用程式層程式調用 一個 END IOCTL 到匯流排驅動,
我們的虛擬匯流排驅動根據seqno從Child PDO的忙碌隊列尋找對應的URB包,把從 END IOCTL傳遞的資料,正確的填寫到URB資料包中,
最後完成這個URB包。
這個就是我的匯流排驅動對URB資料包的處理工程,這個跟前幾篇文章介紹的
“檔案過濾驅動實現目錄重新導向“(http://blog.csdn.net/fanxiushu/article/details/43845699)的處理架構是一致的。
如果不熟悉這個過程,可以去看看過濾驅動實現目錄重新導向的章節。
上邊介紹過的URB_FUNCTION_XXX非常之多,為了在 BEGIN IOCTL和END IOCTL簡化資料包,
都統一使用一個資料結構與驅動互動。
如下
struct ioctl_usbtx_header_t
{
ULONGLONG inter_handle; // 是等待處理的檔案IRP 指標
LONG inter_seqno; // 每個IRP的序號,由驅動產生,和inter_handle一起用來保證請求包的唯一性驗證
LONG data_length; // 資料的長度; 如果是讀裝置,則讀取的位元組數; 如果是寫資料到裝置,寫入前是需要寫入的位元組數,寫入成功後,實際寫入的位元組數,ISO 傳輸會包括 iso_packet_hdr_t結構大小
LONG result; // 返回是否成功
LONG reserved1; // 保留
////
int type; // 1 擷取描述符, 2 vendor or class , 3 傳輸資料, 4 重設, 5 擷取狀態, 6 操作feature
int reserved[ 3 ]; //保留
/////
union{
///
struct{
int type; // 1 擷取或設定裝置描述符, 2 設定配置描述符, 3 擷取或設定介面描述符, 4 擷取或設定連接埠描述符
int subtype; // (type=1,3,4) 1 擷取裝置描述符, 2 擷取配置描述符, 3 擷取字串;;;;; (type=2) 1設定config(index=-1 & value=-1 unconfigure), 2 設定 interface
int is_read; // (type=1,3,4) is_read為TRUE擷取描述符,FALSE 設定描述符
int index; // 序號
int value; // 值, 擷取string時定義成language_id
}descriptor;
////////
struct{
int type; //1 CLASS請求, 2 VENDOR請求
int subtype; //1 device; 2 interface ; 3 endpoint; 4 other
int is_read; //是從裝置讀,還是寫入裝置
int request;
int index;
int value;
}vendor;
////
struct {
int type; // 1 控制傳輸, 2 中斷或批量傳輸, 3 同步傳輸
int ep_address; //連接埠位置 如果 (ep_address &0x80) 則是讀,否則寫; 控制傳輸時候,如果為0表示使用預設連接埠
int is_read; //是從裝置讀,還是寫入裝置
union{
int number_packets; //同步傳輸時候,包個數,如果為0,則組合到一起傳輸,>0則在頭後面跟iso_packet_hdr_t結構,大小為 ISO_PACKET_HDR_SIZE + number_packets*sizeof(iso_packet_t)
struct{
unsigned char setup_packet[8]; /////控制傳輸時候,發送的8個位元組的控制碼
};
};
char is_split; ///中斷批量傳輸,或同步傳輸是否拆分成多塊,
char reserved[3]; ///
}transfer;
////////
struct {
int type; /// 1 IOCTL_INTERNAL_USB_RESET_PORT重設裝置; 2 IOCTL_INTERNAL_USB_CYCLE_PORT 重設裝置; 3 重設連接埠URB_FUNCTION_RESET_PIPE; 4 中斷連接埠 URB_FUNCTION_ABORT_PIPE
int ep_address;
}reset;
/////
struct {
int type; /// 1 device; 2 interface ; 3 endpoint; 4 other status; 5 擷取當前配置描述符;6 根據interface擷取當前介面的alterantesetting; 7 擷取current frame number
int index; ///
}status;
//////
struct {
int type; //// 1 SET請求, 2 CLEAR請求
int subtype; /// 1 device; 2 interface ; 3 endpoint; 4 other
int index; ///
int value; ///
}feature;
////////
};
////////////
};
看起來似乎有點多,實際上BEGIN IOCTL和END IOCTL都使用 ioctl_usbtx_header_t 來傳遞各種URB資料,反而方便許多,
到了資料擷取端,也使用同樣的結構進行處理,因為都是windows平台,處理的各種轉換反而少了許多。
資料結構的定義或使用,請下載CSDN上提供的工程。
到此為止,一個基於虛擬匯流排驅動的實現USB裝置遠端存取的功能,基本算完成了,
但是有個不太完善的地方,這樣的虛擬USB裝置像個無主孤魂一樣存在於系統中,它既不附著在某個虛擬跟集線器上,
也沒有對應的虛擬USB控制器,因此在某些應用程式層程式看來,會把它當作不存在。
比如某些按照USB裝置棧的方式枚舉系統中存在的USB裝置 ,這樣的虛擬USB裝置是枚舉不出來的,因為他沒ROOTHUB,也沒USB控制器。
這個概念就跟以前介紹過的虛擬磁碟驅動很類似,使用 微軟的ScsiPort或StorePort模型的虛擬磁碟驅動, 會被當成真正的磁碟,
在磁碟管理器能找到我們的虛擬磁碟,而且可以像真正的磁碟那樣進行分區,格式化等各種基本的磁碟操作。
而在網上提供的一個類似 filedisk架構的虛擬磁碟驅動,也能提供磁碟訪問的功能,但是並不具備StorePort等架構的提供的磁碟驅動功能,
並不被系統視作一個磁碟系統。
我們現在實現的虛擬USB裝置也跟filedisk一樣,不會被系統視作一個真正的USB裝置。
但是它依然能欺騙大部分軟體,就跟filedisk一樣。
如何到達我們的虛擬USB盡善盡美呢? 需要實現虛擬ROOTHUB和虛擬USB控制器。
敬請關注下文關於RootHUB和USB控制器得開發過程。
USB裝置驅動開發之遠端存取USB裝置(二 USB裝置虛擬端)