下面舉個簡單的例子來詳細說明一下驅動程式的開發過程。
例如我們有個USB Mouse裝置,裝置資訊描述如下:
Device Descriptor:
bcdUSB: 0x0100
bDeviceClass: 0x00
bDeviceSubClass: 0x00
bDeviceProtocol: 0x00
bMaxPacketSize0: 0x08 (8)
idVendor: 0x05E3 (Genesys Logic Inc.)
idProduct: 0x0001
bcdDevice: 0x0101
iManufacturer: 0x00
iProduct: 0x01
iSerialNumber: 0x00
bNumConfigurations: 0x01
ConnectionStatus: DeviceConnected
Current Config Value: 0x01
Device Bus Speed: Low
Device Address: 0x02
Open Pipes: 1
Endpoint Descriptor:
bEndpointAddress: 0x81
Transfer Type: Interrupt
wMaxPacketSize: 0x0003 (3)
bInterval: 0x0A
可以看出上述裝置有一個中斷PIPE,包的最大值為3。可能有人問上述的值怎麼得到的,win2k 的DDK中有個usbview的常式,編譯一下,將你的USB裝置插到PC機的USB口中,運行usbview.exe即可看得相應的裝置資訊。
有了這些基本資料,就可以編寫USB裝置了,首先聲明一下,下面的代碼取自微軟的USB滑鼠樣本程式,著作權歸微軟所有,此處僅僅借用來描述一下USB滑鼠驅動的開發過程,讀者如需要引用此代碼,需要得到微軟的同意。
首先,必須輸出USBD要求調用的三個函數,首先到裝置插入到USB連接埠時,USBD會調用USBDeviceAttach()函數,相應的代碼如下:
extern "C" BOOL
USBDeviceAttach(
USB_HANDLE hDevice, // USB裝置控制代碼
LPCUSB_FUNCS lpUsbFuncs, // USBDI的函數集合
LPCUSB_INTERFACE lpInterface, // 裝置介面描述資訊
LPCWSTR szUniqueDriverId, // 裝置ID描述字串。
LPBOOL fAcceptControl, // 返回TRUE,標識我們可以控制此裝置, 反之表示不能控制
DWORD dwUnused)
{
*fAcceptControl = FALSE;
// 我們的滑鼠裝置有特定的描述資訊,要檢測是否是我們的裝置。
if (lpInterface == NULL)
return FALSE;
// 列印相關的USB裝置介面描述資訊。
DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: DeviceAttach, IF %u, #EP:%u, Class:%u, Sub:%u,Prot:% u/r/n"), lpInterface->Descriptor.bInterfaceNumber,lpInterface->Descriptor.bNumEndpoints, lpInterface->Descriptor.bInterfaceClass,lpInterface->Descriptor.bInterfaceSubClass,lpInterface->Descriptor.bInterfaceProtocol));
// 初試資料USB滑鼠類,產生一個接受USB滑鼠資料的線程
CMouse * pMouse = new CMouse(hDevice, lpUsbFuncs, lpInterface);
if (pMouse == NULL)
return FALSE;
if (!pMouse->Initialize())
{
delete pMouse;
return FALSE;
}
// 註冊一個監控USB裝置事件的回呼函數,用於監控USB裝置是否已經拔掉。
(*lpUsbFuncs->lpRegisterNotificationRoutine)(hDevice,
USBDeviceNotifications, pMouse);
*fAcceptControl = TRUE;
return TRUE;
}
第二個函數是 USBInstallDriver()函數,
一些基本定義如下:
const WCHAR gcszRegisterClientDriverId[] = L"RegisterClientDriverID";
const WCHAR gcszRegisterClientSettings[] = L"RegisterClientSettings";
const WCHAR gcszUnRegisterClientDriverId[] = L"UnRegisterClientDriverID";
const WCHAR gcszUnRegisterClientSettings[] = L"UnRegisterClientSettings";
const WCHAR gcszMouseDriverId[] = L"Generic_Sample_Mouse_Driver";
函數介面如下:
extern "C" BOOL
USBInstallDriver(
LPCWSTR szDriverLibFile) // @parm [IN] - Contains client driver DLL name
{
BOOL fRet = FALSE;
HINSTANCE hInst = LoadLibrary(L"USBD.DLL");
// 註冊USB裝置資訊
if(hInst)
{
LPREGISTER_CLIENT_DRIVER_ID pRegisterId = (LPREGISTER_CLIENT_DRIVER_ID)
GetProcAddress(hInst, gcszRegisterClientDriverId);
LPREGISTER_CLIENT_SETTINGS pRegisterSettings =
(LPREGISTER_CLIENT_SETTINGS) GetProcAddress(hInst,
gcszRegisterClientSettings);
if(pRegisterId && pRegisterSettings)
{
USB_DRIVER_SETTINGS DriverSettings;
DriverSettings.dwCount = sizeof(DriverSettings);
// 設定我們的特定的資訊。
DriverSettings.dwVendorId = USB_NO_INFO;
DriverSettings.dwProductId = USB_NO_INFO;
DriverSettings.dwReleaseNumber = USB_NO_INFO;
DriverSettings.dwDeviceClass = USB_NO_INFO;
DriverSettings.dwDeviceSubClass = USB_NO_INFO;
DriverSettings.dwDeviceProtocol = USB_NO_INFO;
DriverSettings.dwInterfaceClass = 0x03; // HID
DriverSettings.dwInterfaceSubClass = 0x01; // boot device
DriverSettings.dwInterfaceProtocol = 0x02; // mouse
fRet = (*pRegisterId)(gcszMouseDriverId);
if(fRet)
{
fRet = (*pRegisterSettings)(szDriverLibFile,
gcszMouseDriverId, NULL, &DriverSettings);
if(!fRet)
{
//BUGBUG unregister the Client Driver’s ID
}
}
}
else
{
RETAILMSG(1,(TEXT("!USBMouse: Error getting USBD function pointers/r/n")));
}
FreeLibrary(hInst);
}
return fRet;
}
上述代碼主要用於產生USB裝置驅動程式需要的註冊表資訊,需要注意的是:USB裝置驅動程式不使用標準的註冊表函數,而是使用RegisterClientDriverID()和RegisterClientSettings來註冊相應的裝置資訊。
另外一個函數是USBUninstallDriver()函數,具體代碼如下:
extern "C" BOOL
USBUnInstallDriver()
{
BOOL fRet = FALSE;
HINSTANCE hInst = LoadLibrary(L"USBD.DLL");
if(hInst)
{
LPUN_REGISTER_CLIENT_DRIVER_ID pUnRegisterId =
(LPUN_REGISTER_CLIENT_DRIVER_ID)
GetProcAddress(hInst, gcszUnRegisterClientDriverId);
LPUN_REGISTER_CLIENT_SETTINGS pUnRegisterSettings =
(LPUN_REGISTER_CLIENT_SETTINGS) GetProcAddress(hInst,
gcszUnRegisterClientSettings);
if(pUnRegisterSettings)
{
USB_DRIVER_SETTINGS DriverSettings;
DriverSettings.dwCount = sizeof(DriverSettings);
// 必須填入與註冊時相同的資訊。
DriverSettings.dwVendorId = USB_NO_INFO;
DriverSettings.dwProductId = USB_NO_INFO;
DriverSettings.dwReleaseNumber = USB_NO_INFO;
DriverSettings.dwDeviceClass = USB_NO_INFO;
DriverSettings.dwDeviceSubClass = USB_NO_INFO;
DriverSettings.dwDeviceProtocol = USB_NO_INFO;
DriverSettings.dwInterfaceClass = 0x03; // HID
DriverSettings.dwInterfaceSubClass = 0x01; // boot device
DriverSettings.dwInterfaceProtocol = 0x02; // mouse
fRet = (*pUnRegisterSettings)(gcszMouseDriverId, NULL,
&DriverSettings);
}
if(pUnRegisterId)
{
BOOL fRetTemp = (*pUnRegisterId)(gcszMouseDriverId);
fRet = fRet ? fRetTemp : fRet;
}
FreeLibrary(hInst);
}
return fRet;
}
此函數主要用於刪除USBInstallDriver()時建立的註冊表資訊,同樣的它使用自己的函數介面UnRegisterClientDriverID()和UnRegisterClientSettings()來做相應的處理。
另外一個需要處理的註冊的監控通知函數USBDeviceNotifications():
extern "C" BOOL USBDeviceNotifications(LPVOID lpvNotifyParameter, DWORD dwCode,
LPDWORD * dwInfo1, LPDWORD * dwInfo2, LPDWORD * dwInfo3,
LPDWORD * dwInfo4)
{
CMouse * pMouse = (CMouse *)lpvNotifyParameter;
switch(dwCode)
{
case USB_CLOSE_DEVICE:
//刪除相關的資源。
delete pMouse;
return TRUE;
}
return FALSE;
}
USB滑鼠的類的定義如下:
class CMouse
{
public:
CMouse::CMouse(USB_HANDLE hDevice, LPCUSB_FUNCS lpUsbFuncs,
LPCUSB_INTERFACE lpInterface);
~CMouse();
BOOL Initialize();
private:
// 傳輸完畢調用的回呼函數
static DWORD CALLBACK MouseTransferCompleteStub(LPVOID lpvNotifyParameter);
// 中斷處理函數
static ULONG CALLBACK CMouse::MouseThreadStub(PVOID context);
DWORD MouseTransferComplete();
DWORD MouseThread();
BOOL SubmitInterrupt();
BOOL HandleInterrupt();
BOOL m_fClosing;
BOOL m_fReadyForMouseEvents;
HANDLE m_hEvent;
HANDLE m_hThread;
USB_HANDLE m_hDevice;
USB_PIPE m_hInterruptPipe;
USB_TRANSFER m_hInterruptTransfer;
LPCUSB_FUNCS m_lpUsbFuncs;
LPCUSB_INTERFACE m_pInterface;
BOOL m_fPrevButton1;
BOOL m_fPrevButton2;
BOOL m_fPrevButton3;
// 資料接受緩衝區。
BYTE m_pbDataBuffer[8];
};
具體實現如下:
// 建構函式,初始化時調用
CMouse::CMouse(USB_HANDLE hDevice, LPCUSB_FUNCS lpUsbFuncs,
LPCUSB_INTERFACE lpInterface)
{
m_fClosing = FALSE;
m_fReadyForMouseEvents = FALSE;
m_hEvent = NULL;
m_hThread = NULL;
m_hDevice = hDevice;
m_hInterruptPipe = NULL;
m_hInterruptTransfer = NULL;
m_lpUsbFuncs = lpUsbFuncs;
m_pInterface = lpInterface;
m_fPrevButton1 = FALSE;
m_fPrevButton2 = FALSE;
m_fPrevButton3 = FALSE;
memset(m_pbDataBuffer, 0, sizeof(m_pbDataBuffer));
}
// 解構函式,用於清除申請的資源。
CMouse::~CMouse()
{
// 通知系統去關閉相關的函數介面。
m_fClosing = TRUE;
// Wake up the connection thread again and give it time to die.
if (m_hEvent != NULL)
{
// 通知關閉資料接受線程。
SetEvent(m_hEvent);
if (m_hThread != NULL)
{
DWORD dwWaitReturn;
dwWaitReturn = WaitForSingleObject(m_hThread, 1000);
if (dwWaitReturn != WAIT_OBJECT_0)
{
TerminateThread(m_hThread, DWORD(-1));
}
CloseHandle(m_hThread);
m_hThread = NULL;
}
CloseHandle(m_hEvent);
m_hEvent = NULL;
}
if(m_hInterruptTransfer)
(*m_lpUsbFuncs->lpCloseTransfer)(m_hInterruptTransfer);
if(m_hInterruptPipe)
(*m_lpUsbFuncs->lpClosePipe)(m_hInterruptPipe);
}
// 初始化USB滑鼠驅動程式
BOOL CMouse::Initialize()
{
LPCUSB_DEVICE lpDeviceInfo = (*m_lpUsbFuncs->lpGetDeviceInfo)(m_hDevice);
// 檢測配置:USB滑鼠應該只有一個中斷管道
if ((m_pInterface->lpEndpoints[0].Descriptor.bmAttributes & USB_ENDPOINT_TYPE_MASK) != USB_ENDPOINT_TYPE_INTERRUPT)
{
RETAILMSG(1,(TEXT("!USBMouse: EP 0 wrong type (%u)!/r/n"),
m_pInterface->lpEndpoints[0].Descriptor.bmAttributes));
return FALSE;
}
DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: EP 0:MaxPacket: %u, Interval: %u/r/n"),
m_pInterface->lpEndpoints[0].Descriptor.wMaxPacketSize,
m_pInterface->lpEndpoints[0].Descriptor.bInterval));
m_hInterruptPipe = (*m_lpUsbFuncs->lpOpenPipe)(m_hDevice,
&m_pInterface->lpEndpoints[0].Descriptor);
if (m_hInterruptPipe == NULL) {
RETAILMSG(1,(TEXT("Mouse: Error opening interrupt pipe/r/n")));
return (FALSE);
}
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (m_hEvent == NULL)
{
RETAILMSG(1,(TEXT("USBMouse: Error on CreateEvent for connect event/r/n")));
return(FALSE);
}
// 建立資料接受線程
m_hThread = CreateThread(0, 0, MouseThreadStub, this, 0, NULL);
if (m_hThread == NULL)
{
RETAILMSG(1,(TEXT("USBMouse: Error on CreateThread/r/n")));
return(FALSE);
}
return(TRUE);
}
// 從USB滑鼠裝置中讀出資料,產生相應的滑鼠事件。
BOOL CMouse::SubmitInterrupt()
{
if(m_hInterruptTransfer)
(*m_lpUsbFuncs->lpCloseTransfer)(m_hInterruptTransfer);
// 從USB滑鼠PIPE中讀資料
m_hInterruptTransfer = (*m_lpUsbFuncs->lpIssueInterruptTransfer)
(m_hInterruptPipe, MouseTransferCompleteStub, this,
USB_IN_TRANSFER | USB_SHORT_TRANSFER_OK, // 表示讀資料
min(m_pInterface->lpEndpoints[0].Descriptor.wMaxPacketSize,
sizeof(m_pbDataBuffer)),
m_pbDataBuffer,
NULL);
if (m_hInterruptTransfer == NULL)
{
DEBUGMSG(ZONE_ERROR,(L "!USBMouse: Error in IssueInterruptTransfer/r/n"));
return FALSE;
}
else
{
DEBUGMSG(ZONE_TRANSFER,(L"USBMouse::SubmitInterrupt,Transfer:0x%X/r/n",
m_hInterruptTransfer));
}
return TRUE;
}
// 處理滑鼠中斷傳輸的資料
BOOL CMouse::HandleInterrupt()
{
DWORD dwError;
DWORD dwBytes;
DWORD dwFlags = 0;
INT dx = (signed char)m_pbDataBuffer[1];
INT dy = (signed char)m_pbDataBuffer[2];
BOOL fButton1 = m_pbDataBuffer[0] & 0x01 ? TRUE : FALSE;
BOOL fButton2 = m_pbDataBuffer[0] & 0x02 ? TRUE : FALSE;
BOOL fButton3 = m_pbDataBuffer[0] & 0x04 ? TRUE : FALSE;
if (!(*m_lpUsbFuncs->lpGetTransferStatus)(m_hInterruptTransfer, &dwBytes,&dwError))
{
DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Error in GetTransferStatus(0x%X)/r/n"),
m_hInterruptTransfer));
return FALSE;
}
else
{
DEBUGMSG(ZONE_TRANSFER,(TEXT("USBMouse::HandleInterrupt, hTransfer 0x%X complete (%u bytes, Error:%X)/r/n"),
m_hInterruptTransfer,dwBytes,dwError));
}
if (!SubmitInterrupt())
return FALSE;
if(dwError != USB_NO_ERROR)
{
DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Error 0x%X in interrupt transfer/r/n"),dwError));
return TRUE;
}
if(dwBytes < 3)
{
DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Invalid byte cnt %u from interrupt transfer/r/n"),dwBytes));
return TRUE;
}
if(dx || dy)
dwFlags |= MOUSEEVENTF_MOVE;
if(fButton1 != m_fPrevButton1)
{
if(fButton1)
dwFlags |= MOUSEEVENTF_LEFTDOWN;
else
dwFlags |= MOUSEEVENTF_LEFTUP;
}
if(fButton2 != m_fPrevButton2)
{
if(fButton2)
dwFlags |= MOUSEEVENTF_RIGHTDOWN;
else
dwFlags |= MOUSEEVENTF_RIGHTUP;
}
if(fButton3 != m_fPrevButton3)
{
if(fButton3)
dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
else
dwFlags |= MOUSEEVENTF_MIDDLEUP;
}
m_fPrevButton1 = fButton1;
m_fPrevButton2 = fButton2;
m_fPrevButton3 = fButton3;
DEBUGMSG(ZONE_EVENTS,
(TEXT("USBMouse event: dx:%d, dy:%d, dwFlags:0x%X (B1:%u, B2:%u, B3:%u)/r/n"),
dx,dy,dwFlags,fButton1,fButton2,fButton3));
// 通知系統產生滑鼠事件
if (m_fReadyForMouseEvents)
mouse_event(dwFlags, dx, dy, 0, 0);
else
m_fReadyForMouseEvents = IsAPIReady(SH_WMGR);
return TRUE;
}
DWORD CALLBACK CMouse::MouseTransferCompleteStub(LPVOID lpvNotifyParameter)
{
CMouse * pMouse = (CMouse *)lpvNotifyParameter;
return(pMouse->MouseTransferComplete());
}
// 資料轉送完畢回呼函數
DWORD CMouse::MouseTransferComplete()
{
if (m_hEvent)
SetEvent(m_hEvent);
return 0;
}
ULONG CALLBACK CMouse::MouseThreadStub(PVOID context)
{
CMouse * pMouse = (CMouse *)context;
return(pMouse->MouseThread());
}
// USB滑鼠線程
DWORD CMouse::MouseThread()
{
DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: Worker thread started/r/n")));
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
if (SubmitInterrupt())
{
while (!m_fClosing)
{
WaitForSingleObject(m_hEvent, INFINITE);
if (m_fClosing)
break;
if ((*m_lpUsbFuncs->lpIsTransferComplete)(m_hInterruptTransfer))
{
if (!HandleInterrupt())
break;
}
else
{
RETAILMSG(1,(TEXT("!USBMouse: Event signalled, but transfer not complete/r/n")));
// The only time this should happen is if we get an error on the transfer
ASSERT(m_fClosing || (m_hInterruptTransfer == NULL));
break;
}
}
}
RETAILMSG(1,(TEXT("USBMouse: Worker thread exiting/r/n")));
return(0);
}
看到了沒有,其實USB的驅動程式編寫就這麼簡單,類似的其他裝置,例如印表機裝置,就有Bulk OUT PIPE,需要Bulk傳輸,那就需要瞭解一下IssueBulkTransfer()的應用。當然如果是開發USB Mass Storage Disk的驅動,那就需要瞭解更多的協議,例如Bulk-Only Transport協議等。
微軟的Windows CE的Platform Build中已經帶有USB Printer和USB Mass Storage Disk的驅動的原始碼了,好好研究一下,你一定回受益非淺的。