USB設備驅動概述 分類: USB OTG驅動 windows驅動程序WDM 2015-06-10 18:15 426人閱讀 評論(0) 收藏

 

第17章  USB設備驅動

USB設備驅動和PCI設備驅動是PC中最主要的兩種設備驅動程序。與PCI協議相比,USB協議更復雜,涉及面較多。本章將介紹USB設備驅動開發。首先介紹USB協議,使讀者對USB協議有個總體認識。而後介紹USB設備在WDM中的開發框架。因爲操做系統的USB總線驅動程序提供了豐富的功能調用,所以開發USB驅動開發變得相對簡單,只須要調用USB總線驅動接口。

17.1  USB總線協議

USB總線協議比PCI協議複雜的多,涉及USB物理層協議,又涉及USB傳輸層協議等。對於USB驅動程序開發者來講,不須要對USB協議的每一個細節都很清楚。本節概要地介紹USB總線協議,並對驅動開發者須要瞭解的地方進行詳細介紹。

17.1.1  USB設備簡介

USB即通用串行總線(UniversalSerial Bus),是一種支持即插即用的新型串行接口。也有人稱之爲「菊鏈(daisy-chaining)」,是由於在一條「線纜」上有連接127 個設備的能力。USB要比標準串行口快得多,其數據傳輸率可達每秒4Mb~12Mb(而老式的串行口最可能是每秒115Kb)。除了具備較高的傳輸率外,它還能給外圍設備提供支持。

須要注意的是,這不是一種新的總線標準,而是計算機系統鏈接外圍設備(如鍵盤、鼠標、打印機等)的輸入/輸出接口標準。到如今爲止,計算機系統鏈接外圍設備的接口尚未統一的標準,例如,鍵盤的插口是圓的、鏈接打印機要用9針或25針的並行接口、鼠標則要用9針或25針的串行接口。USB能把這些不一樣的接口統一塊兒來,僅用一個4針插頭做爲標準插頭,如圖17-1所示。經過這個標準插頭,採用菊花鍊形式能夠把全部的外設鏈接起來,而且不會損失帶寬。USB正在取代當前PC上的串口和並口。

 

17-1  USB的四條傳輸線

以USB方式鏈接設備時,全部的外設都在機箱外鏈接,鏈接外設沒必要再打開機箱;容許外設熱插拔,而沒必要關閉主機電源。USB採用「級聯」方式,即每一個USB設備用一個USB 插頭鏈接到另外一個外設的USB插座上,而其自己又提供一個USB插座供下一個USB外設鏈接用。經過這種相似菊花鏈式的鏈接,一個USB控制器能夠鏈接多達127個外設,而每一個外設間距離(線纜長度)可達5米。USB能智能識別USB鏈上外圍設備的插入或拆卸。

它可以使多個設備在一個端口上運行,速度也比如今的串行口或並行口快得多,並且其總的連線在理論上說能夠無限延長。對PC來講,以上這些都是一些可貴的優勢,由於再也不須要PS/2端口、MIDI端口等各類不一樣的端口了,還能夠隨時隨地在各類設備上任意插拔。能夠在一個端口上運行鼠標、控制手柄、鍵盤以及其餘輸入裝置(例如數碼相機),並且,也沒必要從新啓動系統去作這些工做。如今USB設備正在快速增多,且因爲操做系統已內置支持USB的功能,於是用戶如今就能夠方便地使用。顯然,USB爲PC的外設擴充提供了一個很好的解決方案。

目前USB技術的發展,已經容許用戶在不使用網卡、HUB的狀況下,直接經過USB技術將幾臺計算機鏈接起來組成小型局域網,用戶只須要給各臺計算機起個名字就能夠開始工做。這種網絡具有Ethernet網絡的各類優勢,同時少了Ethernet網絡的許多限制。假設一位用戶上班時使用筆記本電腦,回家時使用PC機,爲實現數據傳輸,他能夠經過採用USB技術的接口將兩部電腦鏈接起來交換資源,其數據傳輸速度可達12Mbps,這是傳統串行口沒法比擬的。並且用戶在組網的時候根本無須考慮DIP、IRQ等問題。此類技術除支持兼容Ethernet的軟硬件外,也支持標準的網絡通訊協議,包括IPX/SPX、NetBEUI和TCP/IP,這爲經過USB技術組成的小局域網鏈接至大型網絡或Internet提供了條件。

17.1.2  USB鏈接拓撲結構

USB設備的鏈接如圖17-2所示,對於每一個PC來講,都有一個或者多個稱爲Host控制器的設備,該Host控制器和一個根Hub做爲一個總體。這個根Hub下能夠接多級的Hub,每一個子Hub又能夠接子Hub。每一個USB做爲一個節點接在不一樣級別的Hub上。

(1)USB Host控制器:每一個PC的主板上都會有多個Host控制器,這個Host控制器其實就是一個PCI設備,掛載在PCI總線上。Host控制器的驅動由微軟公司提供,如圖17-3所示,這是筆者PC中的Host控制器及USB Hub的驅動。值得注意的是,這裏Host分別有兩種驅動,一種是1.0,另外一種是2.0,分別對應着USB協議1.0和USB協議2.0。

 

(點擊查看大圖)圖17-2  USB鏈接拓撲結構

2USB Hub:每一個USBHost控制器都會自帶一個USB Hub,被稱爲根(Root)Hub。這個根Hub能夠接子(Sub)Hub,每一個Hub上掛載USB設備。通常PC8USB口,經過外接USB Hub,能夠插更多的USB設備。當USB設備插入到USBHub或從上面拔出時,都會發出電信號通知系統。這樣能夠枚舉USB設備,例如當被插入的時候,系統就會建立一個USB物理總線,並詢問用戶安裝設備驅動。如圖17-4所示爲一個典型的USB Hub的示意圖。

 

(點擊查看大圖)圖17-3  USB HostUSB Hub驅動 

 

 

(點擊查看大圖)圖17-4  USB Hub示意圖

(3)USB設備:USB設備就是插在USB總線上工做的設備,廣義地講USB Hub也算是USB設備。每一個根USB Hub下能夠直接或間接地鏈接127個設備,而且彼此不會干擾。對於用戶來講,能夠當作是USB設備和USB控制器直接相連,之間通訊須要知足USB的通訊協議。

有的USB設備功能單一,直接掛載在USB Hub上。而有的USB設備功能複雜,會將多個USB功能連在一塊兒,成爲一個複合設備,它甚至能夠本身內部帶一個Hub,這個Hub下接多個USB子設備,其和多個子設備做爲一個總體當作一個USB設備,如圖17-5所示。

以上是USB的物理拓撲結構,但對於用戶來講,能夠略去USB Hub的概念,或者說USB Hub的概念對於用戶能夠當作是透明的。用戶只須要將USB設備理解成一個USB Host鏈接多個邏輯設備。可能邏輯設備1和邏輯設備2是集中在第一個物理設備裏,例若有的手機鏈接計算機後,系統會當作多個USB設備加載。所以,做爲用戶須要用如圖17-6所示的邏輯拓撲結構理解USB拓撲結構。

 

17-5  符合設備

 

 

17-6  USB邏輯拓撲結構

但對於具體USB設備來講,每一個USB設備的傳輸絕對不會影響其餘USB設備的傳輸。例如,在有USB設備傳輸的時候,其餘USB設備的帶寬不會被佔用。對於USB設備來講,每一個USB設備是直接鏈接到USB Host控制器上的。所以,應該用如圖17-7所示的視角考慮USB設備的通訊。

 

17-7  用戶對USB設備的觀察

17.1.3  USB通訊的流程

USB的鏈接模式是Host和Devcie的鏈接模式,它不一樣於早期的串口和並口,全部的請求必須是Host向Device發出,這就使Host端設計相對複雜,而Device端設計相對簡單。Host端會在主板的南橋設計好,而Device的廠商衆多,廠商只須要遵循USB協議,重點精力能夠放在設備的研發上,而與PC的通訊不用過多考慮。

在USB的通訊中,能夠當作是一個分層的協議。分爲三個層次,即最底層USB總線接口層、USB設備層、功能通訊層,如圖17-8所示。

 

17-8  USB協議

以USB攝像頭設備爲例,視頻播放軟件想經過USB總線獲得USB攝像頭捕捉的視頻數據,這就至關於在功能層上。Clinet SW是視頻播放軟件,Function是USB攝像頭。而這些數據的讀取須要USB設備層提供的服務,在這一層上,主要是USB設備的驅動調度Host控制器向USB攝像頭髮出讀請求。每一個USB設備會有多個管道,使用哪一個管道,傳輸的大小都須要指定。這個層次的USBSystem SW就是USB攝像頭的驅動程序。而在USB設備一端通常會有小單片機或者處理芯片負責響應這種讀請求,而這一層的傳輸又依賴於USB總線接口層的服務。在這一層,徹底是USB的物理協議,包括如何分紅更小的包(packages)傳輸,如何保證每次包傳輸不丟失數據等。

對於USB設備驅動程序員,主要是工做在USB設備層,向「上」對應用程序提供讀寫等接口,向「下」將讀取某個管道的請求發往USB Host控制器驅動程序,它實現了最底層的傳輸請求。

對於每一個USB設備,都有一個或者多個的接口(Interface),每一個Interface都有多個端點(Endpoints),每一個端點經過管道(Pipes)和USB Host控制器鏈接。每一個USB設備都會有一個特殊的端點,即Endpoint0,它負責傳輸設備的描述信息,同時也負責傳輸PC與設備之間的控制信息,如圖17-9所示。

 

17-9  USB管道與端點

17.1.4  USB四種傳輸模式

當USB插入USB總線時,USB控制器會自動爲該USB設備分配一個數字來標示這個設備。另外,在設備的每一個端點都有一個數字來代表這個端點。
USB設備驅動向USB控制器驅動請求的每次傳輸被稱爲一個事務(Transaction),事務有四種類型,分別是Bulk Transaction、Control Transaction、Interrupt Transaction和IsochronousTransaction。每次事務都會分解成若干個數據包在USB總線上傳輸。每次傳輸必須歷經兩個或三個部分,第一部分是USB控制器向USB設備發出命令,第二部分是USB控制器和USB設備之間傳遞讀寫請求,其方向主要看第一部分的命令是讀仍是寫,第二部分有時候能夠沒有。第三部分是握手信號。如下針對這四種傳輸,分別進行講解。

1.Bulk傳輸事務

顧名思義,改種事務傳輸主要是大塊的數據,傳送這種事務的管道叫作Bulk管道。這種事務傳輸的時候分爲三部分,如圖17-10所示。第一部分是Host端發出一個Bulk的令牌請求,若是令牌是IN請求則是從Device到Host的請求,若是是OUT令牌,則是從Host到Device端的請求。
第二部分是傳送數據的階段,根據先前請求的令牌的類型,數據傳輸有多是IN方向,也有多是OUT方向。傳輸數據的時候用DATA0和DATA1令牌攜帶着數據交替傳送。

第三部分是握手信號。若是數據是IN方向,握手信號應該是Host端發出,若是是OUT方向,握手信號應該是Device端發出。握手信號能夠爲ACK,表示正常響應,也能夠是NAK表示沒有正確傳送。STALL表示出現主機不可預知的錯誤。
在第二部分,即傳輸數據包的時候,數據傳送由DATA0和DATA1數據包交替發送。數據傳輸格式DATA1和DATA0,這兩個是重複數據,確保在1數據丟失時0能夠補上,不至於數據丟失。如圖17-11所示。

 

(點擊查看大圖)圖17-10  Bulk傳輸

 

 

17-11  Bulk傳輸時的令牌

2.控制傳輸事務

控制傳輸是負責向USB設置一些控制信息,傳送這種事務的管道是控制管道。在每一個USB設備中都會有控制管道,也就是說控制管道在USB設備中是必須的。控制傳輸也分爲三個階段,即令牌階段、數據傳送階段、握手階段,如圖17-12所示。

 

17-12  控制傳輸事務

3.中斷傳輸事務

在USB設備中,有種處理機制相似於PCI中斷的機制,這就是中斷事務。中斷事務的數據量很小,通常用於通知Host某個事件的來臨,例如USB鼠標,鼠標移動或者鼠標單擊等操做都會經過中斷管道來向Host傳送事件。在中斷事務中,也分爲三個階段,即令牌階段、數據傳輸階段、握手階段,如圖17-13所示。

 

(點擊查看大圖)圖17-13  中斷傳輸事務

4.同步傳輸事務

USB設備中還有一種事務叫同步傳輸事務,這種事務能保證傳輸的同步性。例如,在USB攝像頭中傳輸視頻數據的時候會採用這種事務,這種事務能保證每秒有固定的傳輸量,但與Bulk傳輸不一樣,它容許有必定的誤碼率,這樣符合視頻會議等傳輸的需求,由於視頻會議首先要保證明時性,在必定條件下,容許有必定的誤碼率。同步傳輸事務有隻有兩個階段,即令牌階段、數據階段,由於不關心數據的正確性,故沒有握手階段,如圖17-14所示。

 

17-14  同步傳輸事務

17.2  Windows下的USB驅動

在Windows上開發USB驅動相對來講比較簡單,主要是由於微軟已經提供了完備的USB總線驅動,程序員編寫的設備驅動只需調用總線驅動便可。在Windows上還有一些工具軟件能夠幫助開發者查看USB的各種信息,包括設備描述符、配置描述符等。固然,這些描述符在驅動中也會用到。本節將介紹這些工具軟件,並介紹這些描述符。

17.2.1  觀察USB設備的工具

在學習編寫USB驅動以前,有幾個USB查看工具須要向讀者介紹一下,經過用這些工具能方便地學習USB協議。

首先須要介紹的就是DDK中提供的工具,該工具叫usbview,位於DDK的子目錄src\wdm\usb\usbview下,須要用DDK編譯環境進行編譯。如圖17-15所示爲usbview的界面,在筆者的計算機裏插入了一個USB移動硬盤,在這個軟件中已經清楚地列舉除了該USB設備的各個信息,如圖設備描述符、管道描述符等。

 

(點擊查看大圖)圖17-15  USBView

另外一個有用的工具是BusHound,如圖17-16所示。BusHound用於監視USB設備的傳輸數據,它的實現原理是在USB設備驅動之上加載一層過濾驅動程序,將IRP進行攔截,所以能夠觀察到全部USB數據的傳輸。使用該軟件時須要指明監視哪一種USB設備,如圖17-16所示,在須要監視的設備上打鉤。筆者這裏監視的是一個USB移動硬盤。另外,在下面會列出該設備的基本信息,如管道0是控制管道,管道1是輸出管道,管道2是輸入管道。

 

(點擊查看大圖)圖17-16  BusHound

該軟件將USB的傳輸徹底進行監視,包括每一個USB的各個管道中的傳輸狀況,都一一進行記錄,很是有利於調試驅動。如圖17-17所示,Device一欄中標識是何種設備,例如27.2意味着第27號設備的第2號管道。Phase一欄標識傳輸是輸入仍是輸出。在Data一欄中記錄着一次傳輸的具體內容。

 

(點擊查看大圖)圖17-17  BusHound監視數據傳輸

17.2.2  USB設備請求

USB的請求是經過控制管道傳輸的,請求是8個字節,按照如表17-1所示的排列發送:

表17-1

偏移量

變量

大小

數值

描    

0

bmRequestType

1字節

位圖

第7位: 數據方向位

0 = 主機到設備

1 = 設備到主機

第6-5位:類型

0 = 標準

1 = 

2 = 廠商自定義

3 = 保留

第4-0位:接收者

0 = 對設備的請求

1 = 對接口的請求

2 = 對管道(端點)的請求

3 = 其餘

4-31 = 保留

1

bRequest

1字節

數值

請求類別

2

wValue

2字節

數值

不一樣請求含義不一樣

4

wIndex

2字節

數值

不一樣請求含義不一樣

6

wLength

2字節

數值

表示須要有多少數據返回

其中,bRequest表明不一樣的USB請求,它們分別是如下的幾種請求,定義在DDK的usb100.h文件中:

#define USB_REQUEST_GET_STATUS                   0x00
#define USB_REQUEST_CLEAR_FEATURE                0x01
#define USB_REQUEST_SET_FEATURE                 0x03
#define USB_REQUEST_SET_ADDRESS                   0x05
#define USB_REQUEST_GET_DESCRIPTOR                0x06
#define USB_REQUEST_SET_DESCRIPTOR               0x07
#define USB_REQUEST_GET_CONFIGURATION           0x08
#define USB_REQUEST_SET_CONFIGURATION          0x09
#define USB_REQUEST_GET_INTERFACE                0x0A
#define USB_REQUEST_SET_INTERFACE                0x0B

17.2.3  設備描述符

在控制管道發起USB設備請求,其中很常見的請求是USB_REQUEST_GET_ DESCRIPTOR,即請求USB設備回答設備或者管道描述符。在請求描述符時,bmRequestType能夠指定是針對設備仍是針對管道的。

當請求設備描述符後,設備會回答主機該設備的設備描述符,設備描述符是一種固定的數據結構,它定義在DDK中的usb100.h文件中。

typedef struct _USB_DEVICE_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
USHORT bcdUSB;
UCHAR bDeviceClass;
UCHAR bDeviceSubClass;
UCHAR bDeviceProtocol;
UCHAR bMaxPacketSize0;
USHORT idVendor;
USHORT idProduct;
USHORT bcdDevice;
UCHAR iManufacturer;
UCHAR iProduct;
UCHAR iSerialNumber;
UCHAR bNumConfigurations;
} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;

bLength:設備描述符的bLength域應等於18。

bDescriptorType:bDescriptorType域應等於1,以指出該結構是一個設備描述符。

bcdUSB:bcdUSB域包含該描述符遵循的USB規範的版本號(以BCD編碼)。如今,設備可使用值0x0100或0x0110來指出它所遵循的是1.0版本仍是1.1版本的USB規範。

bDeviceClass:指出設備類型。

bDeviceSubClass:指出設備子類型。

bDeviceProtocol:指出設備類型所使用的協議。

bMaxPacketSize0:設備描述符的bMaxPacketSize0域,給出了默認控制端點(端點0)上的數據包容量的最大值。

idVendor:廠商代碼。

idProduct:廠商專用的產品標識。

bcdDevice:bcdDevice指出設備的發行版本號(0x0100對應版本1.0)。

iManufacturer、iProduct、iSerialNumber:iManufacturer、iProduct和iSerialNumber域指向一個串描述符,該串描述符用人類可讀的語言描述設備生產廠商、產品和序列號。這些串是可選的,0值表明沒有描述串。若是在設備上放入了序列號串,Microsoft建議應使每一個物理設備的序列號惟一。

bNumConfigurations:bNumConfigurations指出該設備能實現多少種配置。Microsoft的驅動程序僅工做於設備的第一種配置(1號配置)。
如圖17-18所示爲筆者用BusHound截獲的USB移動硬盤的請求設備描述符,前面已經介紹過,在控制管道中,傳輸分爲三個階段。第一階段是令牌階段,這裏Host向設備發送「80 06 00 01 00 00 12 00」8個字節,能夠參見表17-1中的解釋。第二階段是數據傳輸階段,方向是由設備傳給主機,這個例子中設備給主機傳遞了18(0x12)個字節,這18個字節對應着USB_DEVICE_DESCRIPTOR數據結構。第三階段是握手階段,在BusHound軟件中沒有體現出來。

 

(點擊查看大圖)圖17-18  BusHound抓取設備描述符

17.2.4  配置描述符

每一個設備有一個或多個配置描述符,它們描述了設備能實行的各類配置方式。DDK中定義的配置描述符結構以下:

typedef struct _USB_CONFIGURATION_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
USHORT wTotalLength;
UCHAR bNumInterfaces;
UCHAR bConfigurationValue;
UCHAR iConfiguration;
UCHAR bmAttributes;
UCHAR MaxPower;
} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR;

bLength:bLength應該爲9。

bDescriptorType:bDescriptorType應該爲2,便是一個9字節長的配置描述符。

wTotalLength:wTotalLength域爲該配置描述符長度加上該配置內全部接口和端點描述符長度的總和。一般,主機在發出一個GET_DESCRIPTOR請求並正確接收到9字節長的配置描述符後,就會再發出一個GET_DESCRIPTOR請求並指定這個總長度。第二個請求把這個大聯合描述符傳輸回來。

bNumInterfaces:指出該配置有多少個接口。

bConfigurationValue:bConfigurationValue域是該配置的索引值。

iConfiguration:iConfiguration域是一個可選的串描述符索引,指向描述該配置的Unicode字符串。此值爲0代表該配置沒有串描述符。

bmAttributes:bmAttributes字節包含描述該配置中設備電源和其餘特性的位掩碼。

MaxPower:MaxPower域中指出要從USB總線上獲取的最大電流量。

如圖17-19所示爲筆者用BusHound截獲的USB移動硬盤的請求配置描述符。其中第一行是Host向Device發送Token令牌,而第二行是Device向Host返回的數據。

 

(點擊查看大圖)圖17-19  BusHound抓取設配置描述符

17.2.5  接口描述符

每一個配置有一個或多個接口描述符,它們描述了設備提供功能的接口。

 typedef struct _USB_INTERFACE_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
UCHAR bInterfaceNumber;
UCHAR bAlternateSetting;
UCHAR bNumEndpoints;
UCHAR bInterfaceClass;
UCHAR bInterfaceSubClass;
UCHAR bInterfaceProtocol;
UCHAR iInterface;
} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR;

bLength:bLength應該爲9。

bDescriptorType:bDescriptorType域應爲4。

bInterfaceNumber:bInterfaceNumber是索引值。

bAlternateSetting:bAlternateSetting是索引值。用在SET_INTERFACE控制事務中以指定要激活的接口。

bNumEndpoints:bNumEndpoints域指出該接口有多少個端點,不包括端點0,端點0被認爲老是存在的,而且是接口的一部分。

bInterfaceClass:bInterfaceClass爲接口類。

bInterfaceSubClass:bInterfaceSubClass爲子接口類。

bInterfaceProtocol:bInterfaceProtocol爲協議。

iInterface:iInterface是一個串描述符的索引,0表示該接口無描述串。

如圖17-20所示爲筆者用BusHound截獲的USB移動硬盤的請求配置描述符和接口描述符。其中第一行是Host向Device發送Token令牌,而第二行是Device向Host返回的數據。能夠看出圖中有一個配置描述符,後面緊接着一個接口描述符(「09 04 00 00 02 00 06 50 05」),後面還有兩個接口描述符(下一節介紹)。

 

(點擊查看大圖)圖17-20  BusHound抓取接口描述符

17.2.6  端點描述符

接口能夠沒有或有多個端點描述符,它們描述了處理事務的端點。DDK中定義的端點描述符結構以下:

 typedef struct _USB_ENDPOINT_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
UCHAR bEndpointAddress;
UCHAR bmAttributes;
USHORT wMaxPacketSize;
UCHAR bInterval;
} USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR;

bLength:bLength應爲7。

bDescriptorType:bDescriptorType應該爲5。

bEndpointAddress:bEndpointAddress域編碼端點的方向性和端點號。

bmAttributes:bmAttributes的低兩位指出端點的類型。0表明控制端點,1表明等時端點,2表明批量端點,3表明中斷端點。

wMaxPacketSize:wMaxPacketSize值指出該端點在一個事務中能傳輸的最大數據量。

bInterval:中斷端點和等時端點描述符還有一個用於指定循檢間隔時間的bInterval域。

17.3  USB驅動開發實例

本節具體介紹如何進行USB驅動的開發,本節採用的源碼來源自DDK的源程序,其位置在DDK子目錄的src\wdm\usb\bulkusb目錄下。該示例很全面地支持了即插即用IRP的處理,也很全面地支持了電源管理,同時很好地支持了USB設備的bulk讀寫。若是從頭開發USB驅動,每每很難達到USB驅動的穩定性,因此強烈建議讀者在此驅動修改的基礎上進行USB驅動開發。

17.3.1  功能驅動與物理總線驅動

DDK已經爲USB驅動開發人員提供了功能強大的USB物理總線驅動(PDO),程序員須要作的事情是完成功能驅動(FDO)的開發。驅動開發人員不須要了解USB如何將請求轉化成數據包等細節,程序員只須要指定何種管道,發送何種數據便可。

當功能驅動想向某個管道發出讀寫請求時,首先構造請求發給USB總線驅動。這種請求是標準的USB請求,被稱爲URB(USB Request Block),即USB請求塊。這種URB被髮送到USB物理總線驅動之後,被USB總線驅動所解釋,進而轉化成請求發往USB HOST驅動或者USB HUB驅動,如圖17-21所示。

 

17-21  總線驅動與功能驅動的關係

能夠看出,USB總線驅動完成了大部分工做,並留給USB功能驅動標準的接口,即URB請求。USB驅動開發人員只須要根據不一樣的USB設備的設計要求,在相應的管道中發起URB請求便可。

17.3.2  構造USB請求包

USB驅動在與USB設備通訊的時候,如在控制管道中獲取設備描述符、配置描述符、端點描述符,或者在Bulk管道中獲取大量數據,都是經過建立USB請求包(URB)來完成的。URB中填充須要對USB的請求,而後將URB做爲IRP的一個參數傳遞給底層的USB總線驅動。在USB總線驅動中,可以解釋不一樣URB,並將其轉化爲USB總線上的相應數據包。

DDK提供了構造URB的內核函數UsbBuildGetDescriptorRequest,其聲明以下:

VOID 
UsbBuildGetDescriptorRequest(
IN OUT PURB  Urb,
IN USHORT  Length,
IN UCHAR  DescriptorType,
IN UCHAR  Index,
IN USHORT  LanguageId,
IN PVOID  TransferBuffer  OPTIONAL,
IN PMDL  TransferBufferMDL  OPTIONAL,
IN ULONG  TransferBufferLength,
IN PURB  Link  OPTIONAL
);

Urb:用來輸出的URB結構的指針。

Length:用來描述該URB結構的大小。

DescriptorType:描述該URB的類型。它能夠是USB_DEVICE_DESCRIPTOR_TYPE、USB_CONFIGURATION_DESCRIPTOR_TYPE和USB_STRING_DESCRIPTOR_ TYPE。

Index:用來描述設備描述符的索引。

LanguageId:用來描述語言ID。

TransferBuffer:若是用緩衝區讀取設備,TransferBuffer是緩衝區內存的指針。

TransferBufferMDL:若是用直接讀取內存時,TransferBufferMDL是直接讀取內存時MDL的指針。

TransferBufferLength:對於該URB所操做內存的大小。

在功能驅動中,全部與USB的通訊,都須要用這個函數建立URB,並經過IRP發送到底層USB總線驅動,如下是一個最基本的示例。

#001               UsbBuildGetDescriptorRequest(
#002                       urb,
#003                       (USHORT) sizeof(struct _URB_
CONTROL_DESCRIPTOR_REQUEST),
#004                       USB_DEVICE_DESCRIPTOR_TYPE,
#005                       0,
#006                       0,
#007                       deviceDescriptor,
#008                       NULL,
#009                       siz,
#010                       NULL);

17.3.3  發送USB請求包

功能驅動將URB包構造完畢後,就能夠發送到底層總線驅動上了。URB包要和一個IRP相關聯起來,這就須要用IoBuildDeviceIoControlRequest建立一個IO控制碼的IRP,而後將URB做爲IRP的參數,用IoCallDriver將URB發送到底層總線驅動上。因爲上層驅動沒法知道底層驅動是同步仍是異步完成的,所以須要作一個判斷。if語句判斷當異步完成IRP時,用事件等待總線驅動完成這個IRP。

 #001   //該函數實現對發送URBUSB物理總線驅動
#002   NTSTATUS
#003   CallUSBD(
#004       IN PDEVICE_OBJECT DeviceObject,
#005       IN PURB           Urb
#006       )
#007   {
#008       PIRP               irp;
#009       KEVENT             event;
#010       NTSTATUS           ntStatus;
#011       IO_STATUS_BLOCK    ioStatus;
#012       PIO_STACK_LOCATION nextStack;
#013       PDEVICE_EXTENSION  deviceExtension;
#014  
#015    //
首先是變量初始化
#016       irp = NULL;
#017       deviceExtension = DeviceObject->DeviceExtension;
#018       //
初始化事件
#019       KeInitializeEvent(&event, NotificationEvent, FALSE);
#020    //
建立IO控制碼相關的IRP
#021       irp = IoBuildDeviceIoControlRequest
(IOCTL_INTERNAL_USB_SUBMIT_URB,
#022                                           deviceExtension->
TopOfStackDeviceObject,
#023                                           NULL,
#024                                           0,
#025                                           NULL,
#026                                           0,
#027                                           TRUE,
#028                                           &event,
#029                                           &ioStatus);
#030  
#031       if(!irp) {
#032     //
若是IRP建立失敗則返回
#033           BulkUsb_DbgPrint(1, ("IoBuildDeviceIo
ControlRequest failed\n"));
#034           return STATUS_INSUFFICIENT_RESOURCES;
#035       }
#036    //
獲得下一層設備棧
#037       nextStack = IoGetNextIrpStackLocation(irp);
#038       ASSERT(nextStack != NULL);
#039       nextStack->Parameters.Others.Argument1 = Urb;
#040       BulkUsb_DbgPrint(3, ("CallUSBD::"));
#041       BulkUsb_IoIncrement(deviceExtension);
#042    //
經過IoCallDriverIRP發送到底層驅動
#043       ntStatus = IoCallDriver(deviceExtension->
TopOfStackDeviceObject, irp);
#044    //
若是IRP是異步完成時,等待其結束
#045       if(ntStatus == STATUS_PENDING) {
#046     //
等待IRP結束
#047           KeWaitForSingleObject(&event,
#048                                 Executive,
#049                                 KernelMode,
#050                                 FALSE,
#051                                 NULL);
#052           ntStatus = ioStatus.Status;
#053       }
#054       //
調用結束
#055       BulkUsb_DbgPrint(3, ("CallUSBD::"));
#056       BulkUsb_IoDecrement(deviceExtension);
#057       return ntStatus;
#058   }

此段代碼能夠在配套光盤中本章的sys目錄下找到。

17.3.4  USB設備初始化

USB驅動的初始化和通常驅動相似,首先是進入入口函數DriverEntry,在DriverEntry函數中,分別指定各個IRP的派遣函數地址、指定AddDevice例程函數地址、指定Unload例程函數地址等。

在AddDevice例程中,建立功能設備對象,而後將該對象掛載在總線設備對象之上,從而造成設備棧。另外爲設備建立一個設備連接,便於應用程序能夠找到這個設備。也能夠根據具體須要,從註冊表中讀取一些必要的設置。

17.3.5  USB設備的插拔

因爲USB設備驅動是基於WDM框架的,所以須要對即插即用消息進行處理。BulkUSB程序對即插即用IRP的支持很是完善,具體能夠參照其代碼,這裏簡單提一下其對插拔的處理。

插拔設備會設計4個即插即用IRP,包括IRP_MN_START_DEVICE、IRP_MN_STOP_ DEVICE、IRP_MN_EJECT和IRP_MN_SURPRISE_REMOVAL。其中,IRP_MN_START_DEVICE消息是當驅動爭取加載並運行時,操做系統的即插即用管理器會將這個IRP發往設備驅動。所以,當得到這個IRP後,USB驅動須要得到USB設備類別描述符,如設備描述符、配置描述符、接口描述符、端點描述符等。並經過這些描述符,從中獲取有用信息,記錄在設備擴展中。

IRP_MN_STOP_DEVICE是設備關閉前,即插即用管理器發的IRP。USB驅動得到這個IRP時,應該儘快結束當前執行的IRP,並將其逐個取消掉。另外,在設備擴展中還應該有表示當前狀態的變量,當IRP_MN_STOP_DEVICE來臨時,將當前狀態記錄成中止狀態。

IRP_MN_EJECT是設備被正常彈出,而IRP_MN_SURPRISE_REMOVAL則是設備非天然彈出,有可能意外掉電或者強行拔出等。在這種IRP到來的時候,應該強迫全部未完成的讀寫IRP結束並取消。而且將當前的設備狀態設置成設備被拔掉。

17.3.6  USB設備的讀寫

USB設備接口主要是爲了傳送數據,80%的傳輸是經過Bulk管道。在BulkUSB驅動中,Bulk管道的讀取是在IRP_MJ_READ和IRP_MJ_WRITE的派遣函數中,這樣在應用程序中就能夠經過ReadFile和WriteFile等API對設備進行操做了。

在IRP_MJ_READ和IRP_MJ_WRITE的派遣例程中設置了完成例程,如圖17-22所示。其原理是將讀寫的大小分紅單位爲BULKUSB_MAX_TRANSFER_SIZE的若干塊,依次將請求發往底層USB總線驅動。第一個塊是派遣例程先設置BULKUSB_MAX_TRANSFER_SIZE大小的讀寫,並設置完成例程,而後將請求發往USB總線驅動。當USB總線驅動完成BULKUSB_MAX_TRANSFER_SIZE大小的讀寫後,會調用讀寫的完成例程。

這時候在完成例程中再次發起BULKUSB_MAX_TRANSFER_SIZE大小的讀寫,並將請求發往底層USB總線驅動,當USB總線驅動完成後,又會進入完成例程。以後發送第三個數據塊,而且依此類推直到傳送完畢。

 

17-22  USB讀寫派遣例程與完成例程

如下是BulkUSB的讀寫派遣函數的部分代碼:

 #001   NTSTATUS
#002   BulkUsb_DispatchReadWrite(
#003       IN PDEVICE_OBJECT DeviceObject,
#004       IN PIRP           Irp
#005       )
#006   {
#007       PMDL                    mdl;
#008       PURB                    urb;
#009       ULONG                   totalLength;
#010       ULONG                   stageLength;
#011       ULONG                  urbFlags;
#012       BOOLEAN               read;
#013       NTSTATUS               ntStatus;
#014       ULONG_PTR             virtualAddress;
#015       PFILE_OBJECT          fileObject;
#016       PDEVICE_EXTENSION     deviceExtension;
#017       PIO_STACK_LOCATION   irpStack;
#018       PIO_STACK_LOCATION   nextStack;
#019       PBULKUSB_RW_CONTEXT   rwContext;
#020       PUSBD_PIPE_INFORMATION pipeInformation;
#021  
#022    //
初始化變量
#023    urb = NULL;
#024       mdl = NULL;
#025       rwContext = NULL;
#026       totalLength = 0;
#027       irpStack = IoGetCurrentIrpStackLocation(Irp);
#028       fileObject = irpStack->FileObject;
#029       read = (irpStack->MajorFunction == IRP_MJ_READ) ? TRUE : FALSE;
#030       deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
#031    //....

#032  
#033    //
設置完成例程的參數
#034       rwContext = (PBULKUSB_RW_CONTEXT) ExAllocatePool(NonPagedPool,
#035                                  sizeof(BULKUSB_RW_CONTEXT));
#036    //...
 
#037       if(Irp->MdlAddress) {
#038           totalLength = MmGetMdlByteCount(Irp->MdlAddress);
#039       }
#040    //
設置URB標誌
#041       urbFlags = USBD_SHORT_TRANSFER_OK;
#042       virtualAddress = (ULONG_PTR) MmGetMdlVirtualAddress(Irp->MdlAddress);
#043  
#044    //
判斷是讀仍是寫
#045       if(read) {
#046           urbFlags |= USBD_TRANSFER_DIRECTION_IN;
#047       }
#048       else {
#049           urbFlags |= USBD_TRANSFER_DIRECTION_OUT;
#050       }
#051  
#052    //
設置本次讀寫的大小
#053       if(totalLength > BULKUSB_MAX_TRANSFER_SIZE) {
#054           stageLength = BULKUSB_MAX_TRANSFER_SIZE;
#055       }
#056       else {
#057           stageLength = totalLength;
#058       }
#059  
#060    //
創建MDL
#061       mdl = IoAllocateMdl((PVOID) virtualAddress,
#062                           totalLength,
#063                           FALSE,
#064                           FALSE,
#065                           NULL);
#066    //
將新MDL進行映射
#067       IoBuildPartialMdl(Irp->MdlAddress,
#068                         mdl,
#069                         (PVOID) virtualAddress,
#070                         stageLength);
#071  
#072    //
申請URB數據結構
#073       urb = ExAllocatePool(NonPagedPool,sizeof
(struct _URB_BULK_OR_INTERRUPT_ TRANSFER));
#074  
#075    //
創建Bulk管道的URB
#076       UsbBuildInterruptOrBulkTransferRequest(
#077                               urb,
#078                               sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),
#079                               pipeInformation->PipeHandle,
#080                               NULL,
#081                               mdl,
#082                               stageLength,
#083                               urbFlags,
#084                               NULL);
#085  
#086    //
設置完成例程參數  
#087       rwContext->Urb             = urb;
#088       rwContext->Mdl             = mdl;
#089       rwContext->Length          = totalLength - stageLength;
#090       rwContext->Numxfer         = 0;
#091       rwContext->VirtualAddress  = virtualAddress + stageLength;
#092       rwContext->DeviceExtension = deviceExtension;
#093    //
設置設備堆棧
#094       nextStack = IoGetNextIrpStackLocation(Irp);
#095       nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
#096       nextStack->Parameters.Others.Argument1 = (PVOID) urb;
#097       nextStack->Parameters.DeviceIoControl.IoControlCode =
#098                                                IOCTL_INTERNAL_USB_SUBMIT_URB;
#099    //
設置完成例程
#100       IoSetCompletionRoutine(Irp,
#101                              (PIO_COMPLETION_ROUTINE)BulkUsb_ReadWriteCompletion,
#102                              rwContext,
#103                              TRUE,
#104                              TRUE,
#105                              TRUE);
#106    //
將當前IRP阻塞
#107       IoMarkIrpPending(Irp);
#108    //
IRP轉發到底層USB總線驅動
#109       ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject,
#110                               Irp);
#111    //...
略去對不成功時的處理
#112       return STATUS_PENDING;
#113   }

此段代碼能夠在配套光盤中本章的sys目錄下找到。

17.4  小結

本章介紹了USB總線協議的基本框架,其中包括USB總線的拓撲結構,USB通訊的流程,還有USB的四種傳輸模式。筆者用一些工具軟件帶領讀者分析了各類USB令牌、設備描述符等。

USB驅動程序的主要功能就是設置這些USB令牌,和獲取USB設備描述符。USB驅動程序將這些請求最終轉化爲USB請求包(URB包),而後發往USB總線驅動程序。USB總線驅動提供了豐富的功能,它封裝了USB協議,提供了標準的接口。這使得USB驅動程序的編寫變得簡單,程序員沒必要過多地瞭解USB總線協議,就能夠編寫出功能強大的USB驅動程序。

相關文章
相關標籤/搜索