USB裝置驅動開發之遠端存取USB裝置(一)

來源:互聯網
上載者:User

標籤:

                                                                                                                                    By Fanxiushu 2016 05-15  轉載或引用本文,請註明原始作者。


使用過vmware的人都應該知道,vmware虛擬機器有這樣的一個功能,
當在宿主機上插入一個USB裝置的時候,通過設定,可以在vmware的虛擬機器系統裡邊能訪問到這個USB裝置,
而且訪問這個USB裝置,就跟真的把這個USB裝置插入到這個虛擬系統中一樣,跟真實的幾乎沒任何區別。
再看一種情況,假設有兩台機器C和S,C 機器是你正在使用的機器, S機器在遠端,你只能通過遠端控制S。
S機器的配置和功能都很強大,大部分時間你都通過遠端桌面等方式串連到 S機器。
假如你手上有些USB介面的裝置,比如iPhone,iPad,USB網路攝影機等,很想把他們使用起來,
C機器在你身邊,你能而且是只能把這些USB裝置插入到C機器,但是你肯定是非常希望插入這些裝置後,S機器也能正常使用。
這種遠程使用更為強大的S機器的辦法,就是現在所謂的CloudDesktop,虛擬CloudDesktop之類的概念。
因此對於虛擬CloudDesktop開發商而言,解決遠端存取本地裝置,也是基本和重要的課題之一。
再看一個對普通人比較陌生,對iOS開發的人比較熟悉的例子,
iOS的app應用安裝問題,非常煩,不像windows程式,只要開發出來,可以到處複製,到處運行。
自己開發的app,需要Xcode開發環境部署到手機上,以前做這樣的事情,還得花錢買賬戶,升級到Xcode7才稍微開放了一些。
通過Xcode部署到自己手機到也方便,可是如何部署到別人的手機上,而且那個人也不在同一個地方,無法把它的手機直接接到電腦上。
於是,能不能通過遠程方式實現Xcode部署,首先要解決的就是USB的遠端存取的問題,
正是基於這樣的原因,同時也想掌握USB裝置驅動,才開始研究和開發 USB裝置驅動,來嘗試實現這麼一種功能。
(當然有其他更好的方式實現 iOS APP內測,我只是比較另類非要通過Xcode部署App,
我的MacOS是安裝到vmware虛擬機器中,通過vmware虛擬USB的方式來訪問宿主機的USB介面的,
嘗試著在windows宿主機中虛擬出USB裝置,再嘗試讓這個虛擬USB被vmware轉向到 MacOS中,
希望這一想法最終能實現,我可不想再去做MacOS系統的USB驅動)

以下討論的都是基於windows平台的USB裝置驅動開發。

USB只是介面,是裝置和主機進行資料交換的協議介面而已。資料交換無非兩個方向,從裝置到主機和從主機到裝置。
大家所說各種USB裝置,其實是具有USB介面的實現自己某種特定功能的硬體裝置,
比如USB網路攝影機,首先這個硬體是網路攝影機,它是通過USB介面串連到電腦。
這裡不討論USB介面的各種硬體特性,也不是軟體開發的範疇。

windows驅動按照種類來分,大致分為匯流排驅動,功能驅動,過濾驅動三大類。
USB是硬體介面,肯定跟最底層的匯流排驅動脫離不了關係。
匯流排驅動負責管理連在某類匯流排(比如USB匯流排)上的所有裝置,
它負責監控匯流排上的裝置的插入和移除,建立裝置的PDO(物理裝置對象),並通知PnP管理器有新硬體添加,等等。
再看看USB匯流排,事實上在電腦基本匯流排上(比如PCI匯流排等)應該有一個或者多個USB的控制器,
從硬體上來說,就是整合到主板上的控制器晶片。
每個USB控制器有唯一的一個RootHUb(根集線器),根集線器有多個PORT,簡單的說,就是在電腦上看到的USB插口。
因此我們可以簡單把USB匯流排驅動理解成是 USB控制器驅動和RootHUB驅動,
因為USB控制器總是首先被發現,接下來RootHUB裝置交給USB控制器驅動處理,
然後RootHUB驅動接著管理自己的PORT和串連到PORT的USB裝置。

根集線器上的PORT不單可以接真正的USB裝置,也可以再次串連子HUB,
每個子HUB可以接真正的USB裝置,或者再接孫子HUB, 這樣形成了一顆以RootHUB為根的樹,
正是依靠這樣的結構,每個USB控制器可以管理最多127個USB裝置(理論上是這樣)。
當我們把USB裝置插入到RootHUB, 或者子HUB,或者孫子HUB等中,這些HUB會上報裝置插入通知,
其實是USB控制器輪詢這些HUB連接埠狀態,從而獲得通知,
RootHUB驅動負責建立這個裝置的PDO(物理裝置對象),並且通知PnP管理器有新裝置添加。
PnP管理器負責根據這個裝置的資訊,載入這個裝置對應的功能驅動程式。
對應的USB裝置功能驅動載入成功之後,就開始真正的USB介面通訊了。
對於功能驅動來說,它只需要把資料直接發給RootHUB驅動建立的 PDO就能完成通訊了。

所謂的功能驅動就是這個USB裝置是做什麼用的,比如是個USB網路攝影機,或者是個USB鍵盤等。
功能驅動在匯流排驅動的上面,USB介面協議是標準通用的協議,凡是具備USB介面的裝置,底層通訊都是一樣的。
這個就是能實現遠程共用各種USB裝置基礎。

USB功能驅動在windows平台下通訊使用URB(USB Request BLOCK)的方式,
(這個URB是不是跟前幾篇文章中介紹的磁碟驅動通訊使用的SRB很相近, 幾乎是同一個模子裡刻出來的)
windows已經幫我們實現了大部分的USB底層通訊內容,
我們只需構造 適當的URB資料包,就可以跟USB裝置進行資料互動(這種URB包的種類大概有20來個)。
直接給 PDO 發送 IRP_MJ_INTERNAL_DEVICE_CONTROL 命令,
命令中包含 URB包就可以跟USB裝置完成一次資料互動。

而USB裝置的PDO,接收到URB的IRP_MJ_INTERNAL_DEVICE_CONTROL命令之後,
開始真正的跟USB裝置進行物理層層級的資料通訊,
它得把URB資料轉交給RootHUB裝置,RootHUB再交給真正的裝置。
至於如何完成通訊過程,具體到硬體處理過程。
這就不是這篇文章討論的內容,除非你想去實現一個真正的USB控制器驅動。

如何?遠端存取USB裝置呢?
通過上邊的簡單介紹,應該對USB通訊過程有個大致的瞭解,
我們只需在USB匯流排層中,攔截到某個USB通訊資料,把這些資料通過網路轉寄到遠程機器,
在遠程機器上虛擬出一個USB裝置,再把資料輸入給這個虛擬設備,於是這個虛擬USB裝置,就能被正確識別和使用。
而且因為攔截和處理的是USB介面的底層資料,所以凡是具備USB介面的裝置,都能被正確識別,
也就是如果是個USB介面的網路攝影機,遠程機器的虛擬USB也被識別成同樣的網路攝影機,
如果是個USB鍵盤,遠程機器的虛擬USB也被當成是USB鍵盤。
原理並不複雜,得開發一個虛擬匯流排驅動,由虛擬USB匯流排驅動類比出虛擬USB裝置,
這個跟以前介紹過的虛擬磁碟很相似,
可惜的是對於虛擬磁碟驅動,微軟提供了專門的ScsiPort或StorPort模組來完成類似功能。
而USB虛擬設備驅動得我們自己開發USB匯流排驅動。
上面還介紹過,USB匯流排驅動包括USB裝置控制器驅動和RootHUB驅動,
按照層次來說,USB控制器管理RootHUB,RootHUB管理USB裝置,USB屬於最低級的僱員,
如果真要按照真實硬體的層次來實現虛擬USB系統,可夠受的。
好在因為是虛擬USB裝置,而不是真正的硬體,虛擬環境下,能把不必要的一些東西簡化。
可以去掉控制器和RootHUB,只需虛擬USB裝置即可,
雖然這對某些特殊程式不能用外,大部分情況都能正常使用。
(至於如何按照真實硬體層次同時實現虛擬USB控制器和虛擬RootHUB,後續章節會介紹到)

匯流排驅動開發的架構這裡就不做過多介紹,下章介紹如何處理虛擬USB裝置時候,會做些解釋。
詳細的可以查看WDK驅動例子裡邊的toaster例子代碼,
WDK7提供了WDM和WDF的例子,WDK8和WDK10把WDM給刪除了,只提供了WDF的代碼,
你如要研究匯流排驅動究竟做了些什麼,還是最好看他的WDM例子,稍後在CSDN上提供的虛擬USB驅動,
也是採用WDM開發的工程,並且也並不是抄襲WDM的toaster例子在上邊填寫自己的代碼,
而是按照自己的習慣重新組織了代碼架構。

要遠端存取USB裝置,首先我們得把它分成兩大模組,
首先,得有採集真實USB裝置資料的採集端,為了方便,下文統稱為採集端或者服務端。
其次才能在遠端虛擬出USB裝置,然後輸入採集到的資料,完成USB裝置訪問, 下文統稱為用戶端或者虛擬USB端。

為了採集USB資料,倒是費了一些周折,首先想到的就是過濾驅動來採集USB裝置資料。
我們先看看USB介面的通訊方式,USB介面就是用來資料通訊的,通訊方式是它的核心內容之一。
一共有4種通訊方式:
一,控制傳輸,主要是發送各種控制命令給USB裝置,主要使用的是預設連接埠0進行傳輸,任何USB裝置一旦串連上主機,
      Host都會給他一個預設連接埠0,否則就沒法跟主機通訊了。
二,中斷傳輸,看名字好像是真的硬體中斷一樣,不然,USB的中斷是偽中斷,其實就是USB控制器定時查詢USB狀態,
       看到標記為中斷的標誌,然後才進行資料轉送,中斷的相應速度,就得看USB控制器得輪詢速度了。
三,批量傳輸,顧名思義,就是大資料轉送,用於非常大資料轉送的場所,比如隨身碟。
四,同步傳輸,這個傳輸方式我是比較費解的,主機給一塊大的記憶體塊,然後設定一些區塊(packet),
       每個區塊設定在這塊大記憶體塊的位移以及每個區塊讀寫長度,
       然後給USB裝置,USB裝置根據packet,同時填充每個區塊的資料,可能有些區塊傳輸不到資料或者只一部分資料,
       這是同步傳輸允許的,它是一種不保證資料完整的傳輸,主要用於USB視頻等要求比較即時但是對資料品質相對不高的情況,
       比如USB網路攝影機。

不管哪種傳輸,USB介面的通訊總是主機主動發起的,
即使資料是從裝置傳輸到主機,也依然是主機首先發起傳輸命令給USB裝置,
USB裝置再把資料填寫到主機發起的這個命令提供的buffer。

一開始想著給USB類驅動掛載 LowerFilters 過濾驅動,想著這樣就能攔截所有的USB資料。
但是這樣想,總覺得不大對勁(因為所有URB包都是主機主動發起的,過濾驅動的攔截對我們這樣的需求沒有意義)。
後來明白了其實所有URB包,都是主機主動發起的,那為何乾脆不找到某個USB裝置在系統中建立的PDO裝置,
直接給他發送URB包來採集資料。這麼想了,也這麼做了。結果資料倒是採集到了,當然也把整個系統給弄藍屏了。
因為當我發送URB_FUNCTION_SELECT_CONFIGURATION重新選擇配置描述符時候,
載入到這個USB裝置的他自己的功能驅動還在運行, 因為發送select命令,迫使USB裝置重新選擇配置描述符,
上邊的功能驅動並不知道,還在使用它原來的配置,結果自然得藍屏罷工了。
而我們要遠端存取USB裝置,就是在遠端虛擬USB裝置完全控制本地的真實USB,它得被獨佔使用。
否則如果遠程虛擬USB裝置發送一個類似SELECT等改變裝置狀態的控制命令過來,載入到真實USB裝置上邊的功能驅動將無法正常運行。

明白了這個道理,總算知道了該如何採集USB裝置資料。
就是給USB裝置開發自己的功能驅動,讓他替換掉原來的真正的功能驅動。
自己開發的這個功能驅動中,處理所有的URB資料包;
根據USB虛擬設備通過網路傳遞來的USB請求資料,產生URB包發給USB裝置進行資料轉送處理。

至於USB的功能驅動如何開發,對我們來說,最主要和唯一要做的就是如何處理USB的四種資料轉送方式。
這樣的例子,WDK例子代碼裡也提供了關於批量傳輸和同步傳輸的例子,把他們組合起來使用,就是我們需要的。
也可以尋求應用程式層層級的解決方案,當然首先想到的就是WINUSB,
這個號稱是應用程式層的USB驅動,其實就是把USB的四種傳輸方式封裝到應用程式層來給不熟悉驅動開發的程式員使用。
不過非常可惜的是老的WINUSB不支援同步傳輸,在win8.1以上的系統才開始支援同步傳輸。
如果真採用winsub,現在大量使用的win7,winxp使用者就沒法用了。
還有開源的libusb,這個工程倒是不錯,他主要活躍在linux平台,也有對應的windows版本,
同樣比較可惜的是,他雖然處理了同步傳輸,但是對於同步傳輸需要傳遞小區塊(packet),它並沒實現。
而是一笼統的跟批量傳輸一樣,只提供一個大記憶體,對於我們的虛擬USB裝置,需要根據Packet返回的資訊,
確定每個小區塊的傳輸情況。當然也可以適當小修改,讓libusb完成這麼一個功能。

我是自己開發的驅動,當然得感謝libusb提供的優秀代碼,簡潔而易懂。
否則也不會這麼快掌握和開發出自己的驅動來處理USB裝置的資料擷取。

採集USB資料的功能驅動倒是開發出來了,可是卻有個非常大的麻煩,如何把我們自己的驅動載入到各種USB裝置上,
讓windows替換掉原來的驅動,而且在不再使用我們的驅動的時候,再換回原來的驅動。

再回來看看PnP管理器如何給某個裝置載入功能驅動,
當匯流排驅動枚舉到有某個裝置插入進來,建立PDO,並且調用IoInvalidateDeviceRelations 函數通知PnP管理器裝置列表有改變,
PnP管理髮送IRP_MN_QUERY_DEVICE_RELATIONS給匯流排驅動查詢所有PDOs列表,並且比較新舊列表,知道某個PDO被添加進來
於是,PnP管理器發送IRP_MN_QUERY_ID給這個PDO查詢硬體ID,
查詢到硬體ID之後,PnP管理器搜尋註冊表的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum 下已經安裝的驅動,
他根據硬體ID來尋找enum下的子項,找到之後,就開始載入驅動。沒找到會找相容ID對應的驅動。
如果都沒找到,則彈出需要安裝驅動的提示框。

顯然,我們只要在我們的自己的功能驅動的inf安裝檔案中,填寫正硬體ID,就能被正確載入。
然而我們的目的是讓我們的驅動,能載入到各種USB裝置上邊,各個USB裝置的硬體ID都不一樣,
不可能每個裝置都製作一個inf安裝檔案,那可夠嗆。
也許我們可以給我們的inf產生一個USB通用的相容ID,
但是PnP管理器首先查詢的是硬體ID,如果對應硬體ID的驅動沒裝,到時可以找到我們的驅動並且安裝,
可是大部分USB裝置都是有驅動的,有些是windows自己都有的,大部分裝置都能被windows識別並且安裝他自己的驅動。

如何解決這個問題呢,最好是在應用程式層找到解決辦法,
幸好有vmware,雖然vmware沒提供原始碼,但是它已經做到這種效果了,可以從它的程式中尋找蛛絲馬跡。
vmware安裝目錄中有個vmware-usbarbitrator64.exe程式,看著程式的名字就是處理USB的,
用depends查看這個程式使用了哪些WIN32 API,終於,vmware-usbarbitrator64.exe匯入的setupapi.dll動態庫中,
使用了一個函數SetupDiSetSelectedDriverW,這個肯定就是跟如何安裝驅動有關的,
於是再用Google滿世界的搜尋這個函數的相關串連,終於找到 http://www.google.com/patents/US8825909 。
(中國的使用者需要翻牆才能訪問),原來他們早就解決了這麼一個問題,居然還申請了專利,可想而知他們對智慧財產權的重視。
大致原理就是給原來裝置的硬體ID,在註冊表中添加我們的自己開發的功能驅動的硬體ID(這個硬體ID可以隨意,只要是唯一的就行),
然後利用setupapi動態庫中函數,重新構造驅動列表,這時候我們自己的驅動就會在他的列表中,然後使用SetupDiSetSelectedDriver,
SetupDiSetSelectedDevice,InstallSelectedDriver等函數動態載入我們的驅動。
如何給註冊表添加我們的硬體ID,主要用到 CM_Add_ID 函數,其實這個函數在底層用得是 SetupDiSetDeviceRegistryProperty函數。
SetupDiSetDeviceRegistryProperty 使用SPDRP_HARDWAREID 參數就可以設定硬體ID,
本來這個功能在win7,winxp,甚至win8都能工作的好好的,到了win10,被微軟給禁止了,不允許設定硬體ID,
不過這也說得通,本來硬體ID對每個裝置就是唯一的,不允許隨意修改。
可是這樣可就苦了我,得另外找辦法解決win10下動態載入自己的驅動的問題。
也許把vmware的辦法稍微做些修改,就又能在win10 正常使用了,總的思路還是得想法修改硬體ID,
這樣才能欺騙PnP管理器載入我們提供的驅動程式。
在寫這篇文章的時候,我正在忙著研究開發虛擬USB控制器和虛擬RootHUB的功能,
因此沒沒時間再去尋找資料如何解決win10下動態安裝驅動的問題,
反正利用 http://www.google.com/patents/US8825909 說的辦法已經能在win7下正常處理這個問題了,等以後有時間再來解決。

下章繼續虛擬USB裝置的開發,敬請關注稍後在CSDN上提供的部分原始碼。
                   未完待續。。。

USB裝置驅動開發之遠端存取USB裝置(一)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.