wince下USB裝置驅動程式導讀

來源:互聯網
上載者:User

隨著USB裝置的不斷增加,我們這些開發人員也就多了對USB裝置進行驅動程式開發的工作。但是對於很多初學者來說,存在以下三個困難:
        一是對WinCE的驅動程式結構瞭解得太少,沒辦法得心應手的專註於驅動程式的開發工作;
        二是對WinCE內建的USB驅動程式的例子沒有弄懂,看到一大堆檔案夾結構和來源程式思維混亂;
        三是幾乎沒有什麼中文的參考資料,不知如何下手。
        第三條是很多開發人員都遇到的,我也一樣,很多朋友問我有沒有什麼資料,我也只能說抱歉,因為我也同樣有這個問題,一切都靠自己的黑暗中摸索,因此本文不談第三條。
        第一條是可以找到資料的,如《Windows CE .NET系統分析及實驗教程》,因此本文也不打算在此花費大量筆墨。
        這樣,本文的著重點就在第二條上面了,通過本文,我希望能讓更多的朋友理解Windows CE下對USB裝置的驅動模型及範例程式中的實現過程,以範例代碼為基礎理順USB裝置驅動程式的開發思路。同樣,本文的讀者對象預期是入門者和準備著手USB驅動開發的人員,驅動開發高手自然就當一笑吧。同時寫本文的目的也是履行我半年前答應很多朋友的諾言,並向我的慵懶致歉。
        好了,在看範例程式之前,我們還有些東西需要瞭解,我們就先來看:

        在此圖中,我們可以非常清晰的看到主機和物理外設之間的結構方式,在主機端,通過USBD模組和HCD模組使用預設的PIPE訪問一個通用的邏輯裝置,實際上就是說USBD和HCD是一組抽象出來的訪問所有USB裝置的邏輯介面,它們負責管理所有USB裝置的串連、載入、移除、資料轉送和通用的配置。其中HCD是主機控制驅動,是為USBD提供底層的功能訪問服務,USBD是USB匯流排驅動,位於HCD的上層,利用HCD的服務提供較高層次抽象的功能。
        由於HCD和USBD都是面向的一致的邏輯裝置介面,那麼對於各種各樣的物理裝置,就需要有唯一對應的裝置驅動程式,這就是中最上層的特殊的PIPE所串連的物理裝置和USB裝置驅動程式。
        有了對這個結構的認識,我們可以明確的是我們要寫的就是最上端的USB裝置驅動程式,在WINCE的範例程式中也稱為USB Client Driver,它是工作於USBD之上,所以實際上我們的工作就變成了利用USBD提供的介面針對特定的物理裝置來完成USB裝置驅動程式,而暫時與其他的部分無關。
        好了,先到這,接下來就準備看一些具體的東西吧!
接下來,我們就來分析一下CE中的範例程式,我用的是4.2版本的,所以下面的內容是4.2版本中的程式。這裡的程式是通過檔案夾的形式組織在一起的,所以我們還是像以前學習CE的時候那樣,先來瞭解與此相關的檔案夾結構,如。

        在USB檔案夾下,分成了CLASS,CLIENTS,COMMON,HCD,INC,USBD幾個檔案夾,其中INC和COMMON裡面有一個lock.c的程式,這個程式很明顯是將要被其他USB有關的驅動程式所使用的一個鎖,代碼很簡單,只是一個類似臨界區的封裝體,可以保護多線程對同一記憶體地區的讀寫訪問,可以先不去管它。CLIENTS檔案夾可能最初微軟的開發人員是用來放置裝置驅動程式的,但是後來沒有放,而發布的時候也沒有刪除,所以遺留了下來,裡面是個空的檔案夾,所以沒用實際用處。USBD和HCD是前述的底層驅動,裡面含有很多子檔案夾和程式,由於我們只針對USB裝置驅動,因此對這兩部分不做分析,不興趣的朋友可以自己去瞭解。
        重點就在CLASS檔案夾了,展開來看,裡面又包含了COMMON、HID、PRINTER、STORAGE幾個檔案夾,同樣,COMMON裡面存放的來源程式是為HID、PRINTER、STORAGE所共有的。HID是USB輸入裝置如鍵盤/滑鼠的範例驅動程式,PRINTER是USB印表機的範例驅動程式,STORAGE是USB存放裝置如隨身碟的範例程式。
        我們此次以USB存放裝置為例,所以再來展開STORAGE檔案夾,其中的INC檔案夾裡面是標頭檔,CLASS是USB存放裝置的驅動程式,DISK是磁碟驅動程式。這裡為什麼有兩個驅動程式呢,我來簡要解釋一下。
        驅動程式工作在硬體與作業系統之間,它有兩個功能,一個是 將作業系統轉寄來的操作以符合指定硬體裝置的形式控制硬體裝置,另一個是向作業系統提供這個提供者。比如說隨身碟,一方面驅動程式要把作業系統對隨身碟的識別、讀、寫等操作轉換成隨身碟的動作,另一方面又告訴作業系統這是個隨身碟,可以當成一個檔案夾或檔案系統來用,能夠接受標準的檔案操作命令。所以此處存在兩個驅動。
        另外還有一個檔案夾,WINCE420\PUBLIC\COMMON\DDK\INC,這裡面是與裝置驅動有關的標頭檔,對於USB裝置,相關的檔案有USB100.H, USBTYPES.H, USBDI.H,這裡面前兩個裡面關於USB的定義是完全符合USB規範的,不是隨便定義出來的,而USBDI.H檔案裡的內容就是USBD匯流排驅動程式向USB裝置驅動程式提供的介面描述,在開發USB裝置驅動時必須要包含此標頭檔,這樣才可以得到USBD介面的原型。
此前,我們共同瞭解了USB驅動在CE中的位置結構,也瞭解了範例驅動程式的檔案夾結構,接下來,我們就要瞭解一下USBD為我們提供了哪些介面來實現裝置訪問以及驅動程式管理的功能。找到USBDI.H,不要告訴我你找不到吧,不管你用什麼編輯器,記事本也好,PB也好,VC/EVC或者VS都行,開啟它,我們一起來瞭解一下USBD為我們提供了什麼。
        我們首先看到的一個大的結構體就是_USB_DRIVER_SETTINGS,注意這個結構體不是USB規範中的USB裝置描述,而是為了CE裝置管理員載入USB裝置驅動程式方便而建立的。該結構體中對供應商描述、裝置描述和Interface的描述是用來匹配註冊表中對USB裝置驅動的註冊表鍵,當裝置管理員發現你裝置的這些值與註冊表中的這些值相符時,就會載入你的驅動。也就是說它是與你的裝置唯一對應的東西,是一種標識。該結構體的供應商部分的描述需要根據你的裝置的供應商資訊來填,裝置描述的裝置類、子類、協議等可以在USB規範中找到,在USB100.H標頭檔中也有一部分,在後面的範例程式中也定義了一部分。
        在接下來有三個函數是必須由USB裝置驅動程式實現的,這也就是我們在MSDN裡或其他CE的文檔裡所看到的,這幾個函數就是:
        USBDeviceAttach:裝置載入的時候由系統調用
        USBInstallDriver:裝置第一次載入的時候由系統調用,用來安裝註冊表配置以便搜尋裝置
        USBUnInstallDriver:裝置移除後清理由上一個函數寫入的註冊表配置
        這樣在我們的驅動程式中就一定要按照這三個函數的原型來實現,否則就不能為裝置管理員所識別。其實除了這三個,個我覺得第四個也是必須的,這就是一個函數指標所指向的函數:
        *LPDEVICE_NOTIFY_ROUTINE
這個指標所指向的函數是用來接收通知訊息的,既然微軟說任何USB裝置必須實現USB_CLOSE_DEVICE訊息的響應,那麼這個指標所指向的函數自然也就是必須要實現的了。
        繼續向下看,是一組函數的原型,這些函數就是USBD向裝置驅動程式提供的服務介面,有些函數是可以任意調用的,用來完成版本資訊讀取、註冊表操作和裝置驅動程式註冊,這些函數有:
GetUSBDVersion    RegisterClientDriverID    UnRegisterClientDriverID    RegisterClientSettings
UnRegisterClientSettings    OpenClientRegistryKey
        還有大量的函數是必須通過指標調用的,通常只允許在驅動程式中調用,為了方例使用,在這裡給出一個_USB_FUNCS的結構體,每一個結構體成員對應了一個函數指標,這樣在驅動程式中要想使用USBD函數只能通過一個結構體變數來進行了,在這裡我們要記住這個結構體的名字,並且微軟還對這個結構體變數進行了以下的類型定義:
typedef struct _USB_FUNCS USB_FUNCS, * PUSB_FUNCS, * LPUSB_FUNCS;
typedef struct _USB_FUNCS const * PCUSB_FUNCS;
typedef struct _USB_FUNCS const * LPCUSB_FUNCS;
        好了,到此我們發現,大部分的USB工作都已經被USBD完成了,我們為了實現自己的裝置驅動,只需要利用這些指標或函數,來實現四個我們自己的函數,然後在其中匹配上我們自己的裝置就可以了。是不是忒簡單,沒錯,總不能剛開始就把自己嚇得不行,那樣後面可就沒法做了。
在上次瞭解了所有USBD介面函數以後,我們已經有了很多基礎知識了,回顧USB範例的檔案夾結構,我們還能記得USB\CLASS\COMMON這個檔案夾下是存放所公用部分的來源程式,它是微軟專門抽象出來的能為大多數USB裝置驅動程式服務的一些結構體以及函數的封裝,我們這次再來概略的瞭解一下這裡面的來源程式。
        這裡麵包含了三個程式,分別是:
        remlock        usbclient        utils
        下面我們分別來瞭解一下這三個程式的功能和介面,很顯然,USB裝置驅動程式肯定是會用到這其中的一部分函數的,因此我們不一定需要讀懂這其中的每一行,但至少要對這些函數有個印象,不至於在讀驅動程式時不知道函數的來源。
        remlock程式是一個移除裝置的鎖,利用這個結構體
  typedef struct _REMOVE_LOCK
  {
      BOOL Removed;
      LONG IoCount;
      HANDLE RemoveEvent;
  } REMOVE_LOCK, *PREMOVE_LOCK
來實現在裝置移除時進行的同步控制。其中Removed成員是對裝置是否已經移除的標識,IoCount成員是對裝置進行訪問的數量,這也是驅動程式中常用的行為,就像此前我們看到的那個Lock程式一樣,RemoveEvent是一個核心事件,熟悉WIN32編程的應該都很清楚,它是核心通知應用程式的一種方式,也是線程這間並發控制的一種手段,如果不熟悉,還是像我在以前文章中提到的那樣,一定要找WINDOWS進階編程之類的書把它學明白,否則就很難控制驅動程式了。
        利用它實現的那幾個函數就不說了,與臨界區的用法是一樣的。另外提一句,在此程式中有類似InterlockedIncrement這樣的函數,這種函數是WIN32 API函數,專門用來提供多線程對同一變數的同步訪問的,可以通過MSDN查到詳細用法。
        usbclient程式是對USBD進行封裝以供USB裝置驅動程式使用的函數介面,通過usbclient.h我們可以發現裡面是關於資料轉送、屬性設定、狀態原因和複位的一組函數原形的定義,我們再看usbclient.c檔案,這些函數大部分都擁有一個LPCUSB_FUNCS類型的參數,回顧上次我們對USBD的瞭解可知,正是通過這一參數才能訪問USBD提供的服務功能,瀏覽一下函數的實現發現,確實每個函數都是通過這個參數調用了USBD的函數,然後處理調用後的結果,所以這裡只是多了一層封裝,使得驅動程式的編寫更加清晰易於維護。
        另外,這裡我們要留意一下IssueBulkTransfer()、IssueInterruptTransfer()、IssueVendorTransfer() 這三個函數,它們實現了通用的Bulk傳輸、中斷傳輸和自訂的傳輸方式,在驅動程式中要用得到。
        utils程式很簡單,是對註冊表操作的封裝,利用_REG_VALUE_DESCR這個結構體和GetSetKeyValues()函數可以方便的訪問註冊表,在驅動程式的安裝中會用得較多。
        又說了這麼多東西,雖然沒有看多少程式,但我們又離驅動程式近了一層,至少知道了很多函數是要在驅動程式中用到的,如果有興趣,可以具體閱讀每一個函數的實現方法,但我覺得這並不影響對驅動程式的開發。如果是我寫驅動,在沒有特別的情況下,我會把這些公用的來源程式照搬過來,這可是能極大的縮短開發週期的事哦!

正如所料,接下來我們就進入到DRIVERS\USB\CLASS\STORAGE\CLASS檔案夾下,接觸USB裝置驅動程式。
        我們先來瞭解兩個標頭檔,分別是STORAGE\INC\usbmsc.h和STORAGE\CLASS\usbmscp.h,其中前者是USB存放裝置公用的標頭檔,後者是需要按照自己的裝置更改的標頭檔。我們先來看前者。
        在usbmsc.h這個標頭檔中,前邊定義了很多常量,包括子類和協議的常量,這是從哪裡來的呢?前文我們已經提到過,這些量值是依據USB裝置規範得來的,在規範上都作了定義,所以此處的值必須與USB規範中的相一致。再向下的命令塊結構體和資料區塊結構體是用來與USB裝置通訊用的,可以通過這兩個結構體的執行個體與USB裝置傳輸資料。 下面的函數原型就不說了,前文提到過,在這裡只記得有這幾個函數就行了。
        再來看usbmscp.h這個標頭檔,這個標頭檔是要按照自己的需要和USB裝置來進行修改的,比如DRIVER_NAME_SZ是驅動程式的名字,RESET_TIMEOUT 是一個逾時的預設值。還有很重要的一個就是USBMSC_DRIVER_SETTINGS的設定,這個設定是與USBDI.H中的USB_DRIVER_SETTINGS結構體一一對應的,為了符合我自己的裝置,通常要把dwVendorId和dwProductId等設定成裝置的對應值,比如我的隨身碟的VendorID是0x058F,ProductID是0x9321,那我就會把這兩個值對應的寫在相應的位置上。同時在系統註冊表中也會利用這兩個值修改註冊表的鍵以便裝置管理員可以順利的找到我的裝置驅動。
        下面還有一個_USBMSC_DEVICE結構體,它是用來描述你自己的USB存放裝置的,是封裝了USBD函數表指標、磁碟裝置指標、管道和配置項的最重要的資料結構,在驅動程式實現上此資料結構就是重點的參數,鑒於範例程式對每一個結構體元素都作了明確的注釋,此處我就不一一描述了,它就像C++中的類一樣,是最後把一些小類組合起來的可以最終使用的結構。
        好了,對這兩個標頭檔有所瞭解以後,我們就進入最關鍵的部分,來源程式。我們接下來來看usbmsc.c這個檔案。為什麼要先看這個檔案而不是同一檔案夾下的其他幾個檔案呢?我來解釋一下。在這個檔案夾中有一個usbmsc.def的檔案,大家都知道它是定義了匯出函數的,通常與它同名的程式檔案都會含有DllEntry的入口,既然入口在這,那我們自然就先來看這個檔案了。如果用到了其他的檔案,再看不遲。
        這可是一個有1000多行的來源程式,但不要害怕,我們只看最主要的,別的函數的實現你可以自己去研究。首先看到檔案的DllEntry入口之前有5個函數原型的定義,從函數名上就可以知道這個函數的功能了,很顯然這幾個函數是程式實現過程中被調用的,所以目前知道功能就行了,不用瞭解實現方法。忘了說一句,這個程式中包含了bot.h和cbit.h兩個標頭檔,可見程式中是要用到它們的功能的,不過先不管它,繼續往下看。
        DllEntry入口函數的下面,就是USBInstallDriver()這個函數了,它的作用是進行與USB裝置相關的註冊表操作,主要的語句是:
bRc = RegisterClientDriverID( wsUsbDeviceID );
bRc = RegisterClientSettings( szDriverLibFile, wsUsbDeviceID, NULL, &usbDriverSettings );
即先註冊裝置類別,然後是裝置細節。 同樣,USBUnInstallDriver()函數是以相反的順序解除註冊資訊的。 這幾個與註冊有關的函數在前面我們提到過,是由USBD介面提供的,這裡我們可以看到USBD對裝置驅動程式的重要性。
在繼續向下看,我們發現了USBDeviceAttach()函數,這可是最重要的地方了,當有USB裝置插入插口以後,作業系統是如何識別它的呢,如何將其做為一個檔案夾加以訪問的呢?我們就來解開這裡的謎團。
        為了我們方便說明,我將此程式簡化如下:

        後面的程式將以此行號進行說明。
我們來看程式的第4行,這裡有一個判斷語句,它是在判斷插入的裝置是否是USBMSC_INTERFACE_CLASS類型的,這個常量是在usbmsc.h檔案中定義的,也就是說如果裝置不是USB存放裝置,那麼就結束這個函數,也就是此驅動只能處理USB存放裝置。
        當發現裝置符合此驅動程式的要求後,就通過函數ParseUsbDescriptors()來解析這個裝置,這個函數在下面的程式中將被實現,我們可以看一下該函數的函數體,很顯然,它是在為裝置進行各種配置,這就不多說它了。
        再往下,分配記憶體,設定標誌,從註冊表中讀取資訊。注意,這裡讀取到的註冊表資訊是Drivers\\USB\\ClientDrivers\\Mass_Storage_Class和bInterfaceSubClass變數組合成的註冊表鍵下的值,具體可參閱來源程式,這個註冊表鍵下放置的內容是
[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\Mass_Storage_Class\6]
"DLL"="USBDISK6.DLL"
"Prefix"="DSK"
"Folder"="USB Disk"
"IOCTL"=dword:4
"IClass"="{A4E7EDDA-E575-4252-9D6B-4195D48BB865}"
由此可以看出,通過此處的註冊表讀取,驅動程式可以知道這個裝置將通過哪種形式以及哪個DLL向作業系統提供介面。同時也為後續的操作進行了準備。
        最關鍵的部分就在接下來的LoadDriver()那句,載入了另一個驅動程式的DLL檔案,就是上述註冊表中的USBDISK6.DLL檔案,計數器增一,取到該檔案中UsbDiskAttach函數及UsbDiskDetach函數的地址,註冊事件通知處理函數,然後調用了該DLL檔案中的UsbDiskAttach函數。
        由此可見,USB裝置驅動程式有兩層功能,一方面是識別出指定的裝置並進行配置,另一方面按照要求調用更高層的驅動程式來向作業系統提供介面。當調用了USBDISK6.DLL後,作業系統就會按該檔案中的程式以一個磁碟的形式或檔案夾的形式進行處理,通過檔案系統的操作,就可以對其進行讀寫控制了。我們也可以看一下HID裝置的這個函數,它也是通過這種方式讓作業系統知道把USB裝置識別成滑鼠裝置的。
        前文我們說過還有一個通知訊息的回呼函數,我們在剛才的程式體中已經發現通過:
UsbFuncs->lpRegisterNotificationRoutine( hDevice, UsbDeviceNotify, pUsbDevice );
語句已經對這個函數進行了設定。我們再向下來看一下這個函數的函數體。這個函數很簡單,只要對USB_CLOSE_DEVICE訊息進行處理,既然是要關閉USB裝置,那麼調用USBDISK6.DLL中的Detach函數是必須的,讓上層的驅動程式進行釋放,然後將引用計數減一,如果不再有裝置引用此驅動程式,則FreeLibrary(),僅此而已。
        其餘的函數可以再仔細研究一下,在此就不詳細描述了,接下來我們要弄明白的就是到底作業系統是如何通過抽象的DISK讀寫具體的裝置呢?
帶著上次留下的疑問,我們繼續來學習作業系統如何通過USBDISK讀寫USB裝置的。我們先看USB\CLASS\STORAGE\DISK\SCSI2\usbdisk6.def檔案。在這個檔案中可以看到,該DLL一共匯出了14個函數,其中兩個是上次內容當中被裝置驅動程式調用的UsbDiskAttach和UsbDiskDetach,餘下的是一組以DSK開頭的流驅動介面,易見,USBDISK是以流驅動的形式向作業系統提供服務的。
        為了清晰起見,以下大量的程式我們並不學習,而只關心裝置讀寫,因此我們來看DISK.C這個程式檔案。找到DSK_Read和DSK_Write兩個函數,令我們大失所望,因為這兩個函數都是形如
UNREFERENCED_PARAMETER(pDevice);
UNREFERENCED_PARAMETER(pBuffer);
UNREFERENCED_PARAMETER(BufferLength);
DEBUGMSG(ZONE_ERR,(TEXT("DSK_Read\n")));
SetLastError(ERROR_INVALID_FUNCTION);
return 0;
這樣的實現,也就是說使用者無法通過常規的ReadFile和WriteFile函數使用這個裝置,那怎麼辦?是否意味著這個DISK無法讀寫呢?當然不是,我們應該馬上想到DSK_IOControl()這個函數,當遇到某些裝置無法用常規的檔案操作函數操作時,我們有DeviceIoControl()使用者函數可以使用,而這個函數就會調用到驅動程式中的DSK_IOControl函數。
        在這個函數中,我們找到了對IOCTL_DISK_READ等命令的處理常式,其中最關鍵的一句就是ScsiRWSG(pDevice, pSgReq, pDevice->Lun, bRead),即調用了一個ScsiRWSG的函數。
        在Scsi2.c這個程式中,我們找到了這個函數,其中SG指的是一種讀寫緩衝區的資料結構,實際上就是帶有緩衝區及長度的一個結構體,是CE下磁碟裝置通用的讀寫資料結構,可以在diskio.h中找到它的定義。在這個函數中我們發現它再次調用了ScsiReadWrite()這個函數進行讀寫操作,找到這個函數,裡面有我們最重要的一行調用,即調用了UsbsDataTransfer()函數,還記得這個函數在哪見過嗎?沒錯,就是在USB裝置的驅動程式當中。
        通過這一過程我們發現,那些Scsi的函數都只是在準備一些緩衝區、資料結構等,並沒有對硬體進行操作,真正要操作硬體裝置的還是由驅動程式來完成的,可見,裝置驅動程式是有著很強階層的,下層是專門針對物理裝置的,上層是針對作業系統的抽象裝置的,下層是隨身碟等物理實體,上層是檔案夾,二者通過一定的通訊或調用機制完成了裝置在作業系統下的正常工作。
        回到usbmsc.c程式中來,找到UsbsDataTransfer函數,這個函數很簡單,根據傳輸協議調用CBIT_DataTransfer()或BOT_DataTransfer() 即可。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.