USBView程式是開發、Debug USB裝置必用的好用工具,該工具是Microsoft DDK的範常式式,該程式範例教導使用者如何寫一個搜尋、列舉USB裝置的程式,但是這個程式是使用Win32 API方式撰寫,沒有一定的底子的使用者確實不容易看懂,加上沒有驅動程式相關經驗的使用者來說,更是不容易瞭解它的運作流程,司徒剛好有看過並且修改過此程式,所以趁著還有些許記憶時,整理這一篇文章並分享給有需要的人
USBView位於$DDK_ROOT\src\usb\usbview資料夾

Win32 API程式的進入點位於WinMain副程式(usbview.c)
int WINAPI WinMain (
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow)
{
MSG msg;
HACCEL hAccel;
ghInstance = hInstance;
ghSplitCursor = LoadCursor(ghInstance,
MAKEINTRESOURCE(IDC_SPLIT));
if(!ghSplitCursor){
OOPS();
return 0;
}
hAccel = LoadAccelerators(ghInstance,
MAKEINTRESOURCE(IDACCEL));
if(!hAccel){
OOPS();
return 0;
}
if(!CreateTextBuffer()){
return 0;
}
if(!CreateMainWindow(nCmdShow)){
return 0;
}
while(GetMessage(&msg, NULL, 0, 0)){
if(!TranslateAccelerator(ghMainWnd,
hAccel,
&msg) &&
!IsDialogMessage(ghMainWnd,
&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
DestroyTextBuffer();
CHECKFORLEAKS();
return 1;
}
WinMain副程式需要撰寫的程式內容就是一般Win32 API的處理流程,首先載入Cursor、Accelerator資源檔,接著呼叫CreateTextBuffer副程式配置記憶體,再來就是呼叫CreateMainWindow副程式產生主視窗,目前USBView是使用Dialog作為視窗基礎,所以視窗產生完畢後就是開始做訊息取得、分發的動作,也就是接下來的while迴圈內容,這些處理流程都是很制式的,因為視窗程式都是使用訊息作為傳遞的橋樑,而系統若要傳遞訊息給視窗程式,系統就會將訊息放在視窗的訊息緩衝區,所以主程式必須透過GetMessage函式取得訊息並分發給自己的主程式,那自己的主程式要如何收到訊息呢?在CreateMainWindow副程式會呼叫CreateDialog函式並且註冊一個Callback副程式,而該Callback副程式就是負責接收訊息的副程式
司徒把CreateMainWindow(usbview.c)列出來給大家看一下
BOOL CreateMainWindow(int nCmdShow)
{
RECT rc;
InitCommonControls();
ghMainWnd = CreateDialog(ghInstance,
MAKEINTRESOURCE(IDD_MAINDIALOG),
NULL,
MainDlgProc);
if(ghMainWnd == NULL){
OOPS();
return FALSE;
}
GetWindowRect(ghMainWnd, &rc);
gBarLocation = (rc.right - rc.left) / 3;
ResizeWindows(FALSE, 0);
ShowWindow(ghMainWnd, nCmdShow);
UpdateWindow(ghMainWnd);
return TRUE;
}
接著如何繼續追蹤程式呢?總不可能每個副程式慢慢看,更不可能使用WinDbg追蹤,因為這樣也太累了,仔細想想,由於USBView是視窗程式,所以,全部的訊息一定會透過Callback副程式做後續處理,所以直接看Callback副程式絕對錯不了
LRESULT CALLBACK MainDlgProc (
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
switch(uMsg){
HANDLE_MSG(hWnd, WM_INITDIALOG, USBView_OnInitDialog);
HANDLE_MSG(hWnd, WM_CLOSE, USBView_OnClose);
HANDLE_MSG(hWnd, WM_COMMAND, USBView_OnCommand);
HANDLE_MSG(hWnd, WM_LBUTTONDOWN, USBView_OnLButtonDown);
HANDLE_MSG(hWnd, WM_LBUTTONUP, USBView_OnLButtonUp);
HANDLE_MSG(hWnd, WM_MOUSEMOVE, USBView_OnMouseMove);
HANDLE_MSG(hWnd, WM_SIZE, USBView_OnSize);
HANDLE_MSG(hWnd, WM_NOTIFY, USBView_OnNotify);
HANDLE_MSG(hWnd, WM_DEVICECHANGE, USBView_OnDeviceChange);
}
return 0;
}
HANDLE_MSG是一個巨集,該巨集如下
#define HANDLE_MSG(hwnd, message, fn) / case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
該巨集的意思就是將HANDLE_MSG轉換成HANDLE_ + Message,如:WM_CREATE將會轉換成HANDLE_WM_CREATE,而HANDLE_WM_CREATE也是一個巨集,如下定義
#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)
使用者看到這裡應該就可以知道,它其實只是做switch指令的判斷,並且執行fn副程式,然後return執行後的結果,如果使用者不想用這麼難懂的巨集,只要自己寫一個switch指令判斷就可以
司徒將目前USBView的訊息整理如下
| 訊息 | 功能 |
|---|---|
| WM_INITDIALOG | 視窗初始化訊息 |
| WM_CLOSE | 視窗結束訊息 |
| WM_COMMAND | 接收按鈕的訊息 |
| WM_LBUTTONDOWN | 滑鼠左鍵按下的訊息 |
| WM_LBUTTONUP | 滑鼠左鍵放開的訊息 |
| WM_MOUSEMOVE | 滑鼠移動的訊息 |
| WM_SIZE | 視窗縮放的訊息 |
| WM_NOTIFY | 系統通知的訊息 |
| WM_DEVICECHANGE | 註冊的裝置訊息 |
整理完訊息之後,我們只需要看WM_INITDIALOG、WM_NOTIFY、WM_DEVICECHANGE這三個訊息就可以,因為其它訊息都是制式的處理手法,不會是我們分析的重點,接著我們先看WM_INITDIALOG(usbview.c)在做什麼
BOOL USBView_OnInitDialog (
HWND hWnd,
HWND hWndFocus,
LPARAM lParam)
{
HFONT hFont;
HIMAGELIST himl;
HICON hicon;
DEV_BROADCAST_DEVICEINTERFACE broadcastInterface;
// Register to receive notification when a USB device is plugged in.
broadcastInterface.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
broadcastInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
memcpy( &(broadcastInterface.dbcc_classguid),
&(GUID_CLASS_USB_DEVICE),
sizeof(struct _GUID));
gNotifyDevHandle = RegisterDeviceNotification(hWnd,
&broadcastInterface,
DEVICE_NOTIFY_WINDOW_HANDLE);
// Now register for Hub notifications.
memcpy( &(broadcastInterface.dbcc_classguid),
&(GUID_CLASS_USBHUB),
sizeof(struct _GUID));
gNotifyHubHandle = RegisterDeviceNotification(hWnd,
&broadcastInterface,
DEVICE_NOTIFY_WINDOW_HANDLE);
//end add
ghTreeWnd = GetDlgItem(hWnd, IDC_TREE);
//added
if((himl = ImageList_Create(15, 15,
FALSE, 2, 0)) == NULL)
{
OOPS();
}
hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_ICON));
giGoodDevice = ImageList_AddIcon(himl, hicon);
hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_BADICON));
giBadDevice = ImageList_AddIcon(himl, hicon);
hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_COMPUTER));
giComputer = ImageList_AddIcon(himl, hicon);
hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_HUB));
giHub = ImageList_AddIcon(himl, hicon);
hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_NODEVICE));
giNoDevice = ImageList_AddIcon(himl, hicon);
TreeView_SetImageList(ghTreeWnd, himl, TVSIL_NORMAL);
// end add
ghEditWnd = GetDlgItem(hWnd, IDC_EDIT);
ghStatusWnd = GetDlgItem(hWnd, IDC_STATUS);
ghMainMenu = GetMenu(hWnd);
if(ghMainMenu == NULL){
OOPS();
}
hFont = CreateFont(13, 8, 0, 0,
400, 0, 0, 0,
0, 1, 2, 1,
49, TEXT("Courier"));
SendMessage(ghEditWnd,
WM_SETFONT,
(WPARAM) hFont,
0);
RefreshTree();
return FALSE;
}
該副程式也很簡單,只是註冊USB Hub通知(透過RegisterDeviceNotification函式)並且產生TreeView視窗,而每個TreeView的Node也都有特定的圖示表示(透過LoadIcon和ImageList_AddIcon),需要注意的是,透過RegisterDeviceNotification函式註冊的訊息,系統會使用WM_DEVICECHANGE訊息作回調,也就是當註冊的裝置插入或者拔除時,系統都會透過WM_DEVICECHANGE訊息通知主程式,那比較有問題的是GUID部份,使用者怎麼會知道有哪些GUID可以使用呢?其實這一些都是微軟定義的裝置GUID,它們位於USBIODef.h中
/* f18a0e88-c30c-11d0-8815-00a0c906bed8 */ DEFINE_GUID(GUID_DEVINTERFACE_USB_HUB, 0xf18a0e88, 0xc30c, 0x11d0, 0x88, 0x15, 0x00, \ 0xa0, 0xc9, 0x06, 0xbe, 0xd8); /* A5DCBF10-6530-11D2-901F-00C04FB951ED */ DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, \ 0xC0, 0x4F, 0xB9, 0x51, 0xED); /* 3ABF6F2D-71C4-462a-8A92-1E6861E6AF27 */ DEFINE_GUID(GUID_DEVINTERFACE_USB_HOST_CONTROLLER, 0x3abf6f2d, 0x71c4, 0x462a, 0x8a, 0x92, 0x1e, \ 0x68, 0x61, 0xe6, 0xaf, 0x27); /* 4E623B20-CB14-11D1-B331-00A0C959BBD2 */ DEFINE_GUID(GUID_USB_WMI_STD_DATA, 0x4E623B20L, 0xCB14, 0x11D1, 0xB3, 0x31, 0x00,\ 0xA0, 0xC9, 0x59, 0xBB, 0xD2); /* 4E623B20-CB14-11D1-B331-00A0C959BBD2 */ DEFINE_GUID(GUID_USB_WMI_STD_NOTIFICATION, 0x4E623B20L, 0xCB14, 0x11D1, 0xB3, 0x31, 0x00,\ 0xA0, 0xC9, 0x59, 0xBB, 0xD2); #define GUID_CLASS_USBHUB GUID_DEVINTERFACE_USB_HUB #define GUID_CLASS_USB_DEVICE GUID_DEVINTERFACE_USB_DEVICE #define GUID_CLASS_USB_HOST_CONTROLLER GUID_DEVINTERFACE_USB_HOST_CONTROLLER
如果是自己開發的USB裝置以及驅動程式,使用者也可以使用這種註冊方式
我們已經知道WM_DEVICECHANGE是一個重要的副程式,所以它的程式碼可能會很大也很複雜,所以我們把它保留到最後再分析,我們先分析另一個WM_NOTIFY(usbview.c)副程式
LRESULT USBView_OnNotify (
HWND hWnd,
int DlgItem,
LPNMHDR lpNMHdr)
{
if(lpNMHdr->code == TVN_SELCHANGED){
HTREEITEM hTreeItem;
hTreeItem = ((NM_TREEVIEW *)lpNMHdr)->itemNew.hItem;
if(hTreeItem){
UpdateEditControl(ghEditWnd,
ghTreeWnd,
hTreeItem);
}
}
return 0;
}
使用者看到應該會嚇一跳,程式碼怎麼這麼簡單,因為該副程式只做TreeView的顯示處理,也就是當使用者點選某個裝置時,在右邊的EDIT視窗顯示相關訊息,所以顯示裝置訊息的程式就寫在UpdateEditControl副程式裡面,這一部份都只是附加文字到EDIT視窗而已,使用者自己看一下就可以知道了
接著我們看一下USBView最重要的副程式WM_DEVICECHANGE(usbview.c)
BOOL USBView_OnDeviceChange (
HWND hwnd,
UINT uEvent,
DWORD dwEventData)
{
if(gDoAutoRefresh){
switch(uEvent){
case DBT_DEVICEARRIVAL:
case DBT_DEVICEREMOVECOMPLETE:
RefreshTree();
break;
}
}
return TRUE;
}
該副程式判斷DBT_DEVICEARRIVAL、DBT_DEVICEREMOVECOMPLETE訊息並做列舉的動作,所以繼續往RefreshTree看下去
RefreshTree副程式(usbview.c)如下
VOID RefreshTree (VOID)
{
CHAR statusText[128];
ULONG devicesConnected;
// Clear the selection of the TreeView, so that when the tree is
// destroyed, the control won't try to constantly "shift" the
// selection to another item.
//
TreeView_SelectItem(ghTreeWnd, NULL);
// Clear the edit control
//
SetWindowText(ghEditWnd, "");
// Destroy the current contents of the TreeView
//
if(ghTreeRoot){
WalkTree(ghTreeRoot, CleanupItem, 0);
TreeView_DeleteAllItems(ghTreeWnd);
ghTreeRoot = NULL;
}
// Create the root tree node
//
ghTreeRoot = AddLeaf(TVI_ROOT, 0, "My Computer", ComputerIcon);
if(ghTreeRoot != NULL){
// Enumerate all USB buses and populate the tree
//
EnumerateHostControllers(ghTreeRoot, &devicesConnected);
//
// Expand all tree nodes
//
WalkTree(ghTreeRoot, ExpandItem, 0);
// Update Status Line with number of devices connected
//
wsprintf(statusText, "Devices Connected: %d Hubs Connected: %d",
devicesConnected, TotalHubs);
SetWindowText(ghStatusWnd, statusText);
}
else{
OOPS();
}
}
該副程式把TreeView刪除重建並且呼叫EnumerateHostControllers副程式做USB Hub列舉的動作,所以繼續往EnumerateHostControllers副程式看下去
EnumerateHostControllers副程式(enum.c)如下
VOID EnumerateHostControllers (
HTREEITEM hTreeParent,
ULONG *DevicesConnected)
{
char HCName[16];
int HCNum;
HANDLE hHCDev;
PCHAR leafName;
HDEVINFO deviceInfo;
SP_DEVICE_INTERFACE_DATA deviceInfoData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceDetailData;
ULONG index;
ULONG requiredLength;
TotalDevicesConnected = 0;
TotalHubs = 0;
// Iterate over some Host Controller names and try to open them.
//
for(HCNum = 0; HCNum < NUM_HCS_TO_CHECK; HCNum++){
wsprintf(HCName, "\\\\.\\HCD%d", HCNum);
hHCDev = CreateFile(HCName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
// If the handle is valid, then we've successfully opened a Host
// Controller. Display some info about the Host Controller itself,
// then enumerate the Root Hub attached to the Host Controller.
//
if(hHCDev != INVALID_HANDLE_VALUE){
leafName = HCName + sizeof("\\\\.\\") - sizeof("");
EnumerateHostController(hTreeParent,
hHCDev,
leafName);
CloseHandle(hHCDev);
}
}
// Now iterate over host controllers using the new GUID based interface
//
deviceInfo = SetupDiGetClassDevs((LPGUID)&GUID_CLASS_USB_HOST_CONTROLLER,
NULL,
NULL,
(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
if(deviceInfo != INVALID_HANDLE_VALUE){
deviceInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
for(index=0;
SetupDiEnumDeviceInterfaces(deviceInfo,
0,
(LPGUID)&GUID_CLASS_USB_HOST_CONTROLLER,
index,
&deviceInfoData);
index++)
{
SetupDiGetDeviceInterfaceDetail(deviceInfo,
&deviceInfoData,
NULL,
0,
&requiredLength,
NULL);
deviceDetailData = GlobalAlloc(GPTR, requiredLength);
deviceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
SetupDiGetDeviceInterfaceDetail(deviceInfo,
&deviceInfoData,
deviceDetailData,
requiredLength,
&requiredLength,
NULL);
hHCDev = CreateFile(deviceDetailData->DevicePath,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
// If the handle is valid, then we've successfully opened a Host
// Controller. Display some info about the Host Controller itself,
// then enumerate the Root Hub attached to the Host Controller.
//
if(hHCDev != INVALID_HANDLE_VALUE){
leafName = deviceDetailData->DevicePath;
EnumerateHostController(hTreeParent,
hHCDev,
leafName);
CloseHandle(hHCDev);
}
GlobalFree(deviceDetailData);
}
SetupDiDestroyDeviceInfoList(deviceInfo);
}
*DevicesConnected = TotalDevicesConnected;
}
該副程式要分成兩部份來看,第一部份是使用CreateFile函式去開啟HCD(Host Control Device)並且列舉USB裝置,而第二部份則是使用SetupAPI函式開啟HCD並且列舉USB裝置
為什麼會有這樣的區分呢?因為驅動程式載入時,驅動程式會使用SymbolLink或GUID註冊名稱給User Mode的應用程式開啟,如果是使用SymbolLink則必須使用第一種方式開啟,反之,若驅動程式使用GUID註冊,則User Mode的應用程式就必須使用SetupAPI函式去取得實際路徑
如果是使用SymbolLink則直接使用CreateFile函式開啟就可以了,如果是使用SetupAPI函式的方式開啟,則需要先取得GUID並且使用此GUID去搜尋是否有該裝置,如果有發現裝置則取得該裝置名稱,然後一樣使用CreateFile函式開啟即可
開啟HCD後,就呼叫EnumerateHostController(目前分析的副程式是EnumerateHostControllers,最後多一個s)做列舉裝置的動作,雖然分成兩種方式開啟HCD,但是緊接著都是一樣呼叫EnumerateHostController副程式處理,所以繼續往EnumerateHostController副程式分析
EnumerateHostController副程式(enum.c)如下
VOID EnumerateHostController (
HTREEITEM hTreeParent,
HANDLE hHCDev,
PCHAR leafName)
{
PCHAR driverKeyName;
PCHAR deviceDesc;
PCHAR deviceId;
HTREEITEM hHCItem;
PCHAR rootHubName;
PLIST_ENTRY listEntry;
PUSBHOSTCONTROLLERINFO hcInfo;
PUSBHOSTCONTROLLERINFO hcInfoInList;
// Allocate a structure to hold information about this host controller.
//
hcInfo = (PUSBHOSTCONTROLLERINFO)ALLOC(sizeof(USBHOSTCONTROLLERINFO));
if(hcInfo != NULL){
hcInfo->DeviceInfoType = HostControllerInfo;
// Obtain the driver key name for this host controller.
//
driverKeyName = GetHCDDriverKeyName(hHCDev);
if(driverKeyName){
// Don't enumerate this host controller again if it already
// on the list of enumerated host controllers.
//
listEntry = EnumeratedHCListHead.Flink;
while(listEntry != &EnumeratedHCListHead){
hcInfoInList = CONTAINING_RECORD(listEntry,
USBHOSTCONTROLLERINFO,
ListEntry);
if(strcmp(driverKeyName, hcInfoInList->DriverKey) == 0){
// Already on the list, exit
//
FREE(driverKeyName);
FREE(hcInfo);
return;
}
listEntry = listEntry->Flink;
}
// Obtain the device id string for this host controller.
// (Note: this a tmp global string buffer, make a copy of
// this string if it will used later.)
//
deviceId = DriverNameToDeviceDesc(driverKeyName, TRUE);
if(deviceId){
ULONG ven, dev, subsys, rev;
if (sscanf(deviceId,
"PCI\\VEN_%x&DEV_%x&SUBSYS_%x&REV_%x",
&ven, &dev, &subsys, &rev) != 4)
{
OOPS();
}
hcInfo->DriverKey = driverKeyName;
hcInfo->VendorID = ven;
hcInfo->DeviceID = dev;
hcInfo->SubSysID = subsys;
hcInfo->Revision = rev;
}
else{
OOPS();
}
// Obtain the device description string for this host controller.
// (Note, this a tmp global string buffer, make a copy of
// this string if it will be used later.)
//
deviceDesc = DriverNameToDeviceDesc(driverKeyName, FALSE);
if(deviceDesc){
leafName = deviceDesc;
}
else{
OOPS();
}
// Add this host controller to the USB device tree view.
//
hHCItem = AddLeaf(hTreeParent,
(LPARAM)hcInfo,
leafName,
GoodDeviceIcon);
if(hHCItem){
// Add this host controller to the list of enumerated
// host controllers.
//
InsertTailList(&EnumeratedHCListHead,
&hcInfo->ListEntry);
// Get the name of the root hub for this host
// controller and then enumerate the root hub.
//
rootHubName = GetRootHubName(hHCDev);
if(rootHubName != NULL){
EnumerateHub(hHCItem,
rootHubName,
NULL, // ConnectionInfo
NULL, // ConfigDesc
NULL, // StringDescs
"RootHub" // DeviceDesc
);
}
else{
// Failure obtaining root hub name.
OOPS();
}
}
else{
// Failure adding host controller to USB device tree
// view.
OOPS();
FREE(driverKeyName);
FREE(hcInfo);
}
}
else{
// Failure obtaining driver key name.
OOPS();
FREE(hcInfo);
}
}
}
該副程式首先取得HCD的DriverKey名稱(透過GetHCDDriverKeyName副程式),使用的是IOCTL_GET_HCD_DRIVERKEY_NAME,該IOCTL是微軟定義的,所有HCD都會支援此IOCTL(因為它是預設支援的IOCTL),而取得的名稱就是HCD註冊在Registry的名稱,那為何要去取得Registry名稱呢?因為有此名稱才能存取Registry裡面的相關資訊,而一般Registry都會紀錄驅動程式的詳細資訊,如果使用者已經知道絕對路徑,那麼使用RegOpenKey函式開啟也可以達到相同的目的
接著呼叫DriverNameToDeviceDesc副程式(透過Registry方式)去取得晶片的名稱,該名稱就是顯示在TreeView上面的名稱,而GetHCDDriverKeyName副程式比較簡單,它使用IOCTL的方式去取得HCD的DriverKey名稱,至於DriverNameToDeviceDesc副程式(devnode.c)的部份,因為它比較特別,所以司徒需要好好解析它一下
PCHAR DriverNameToDeviceDesc (PCHAR DriverName, BOOLEAN DeviceId)
{
DEVINST devInst;
DEVINST devInstNext;
CONFIGRET cr;
ULONG walkDone = 0;
ULONG len;
// Get Root DevNode
//
cr = CM_Locate_DevNode(&devInst,
NULL,
0);
if(cr != CR_SUCCESS){
return NULL;
}
// Do a depth first search for the DevNode with a matching
// DriverName value
//
while(!walkDone){
// Get the DriverName value
//
len = sizeof(buf);
cr = CM_Get_DevNode_Registry_Property(devInst,
CM_DRP_DRIVER,
NULL,
buf,
&len,
0);
// If the DriverName value matches, return the DeviceDescription
//
if(cr == CR_SUCCESS && _stricmp(DriverName, buf) == 0){
len = sizeof(buf);
if(DeviceId){
cr = CM_Get_Device_ID(devInst,
buf,
len,
0);
}
else{
cr = CM_Get_DevNode_Registry_Property(devInst,
CM_DRP_DEVICEDESC,
NULL,
buf,
&len,
0);
}
if(cr == CR_SUCCESS){
return buf;
}
else{
return NULL;
}
}
// This DevNode didn't match, go down a level to the first child.
//
cr = CM_Get_Child(&devInstNext,
devInst,
0);
if(cr == CR_SUCCESS){
devInst = devInstNext;
continue;
}
// Can't go down any further, go across to the next sibling. If
// there are no more siblings, go back up until there is a sibling.
// If we can't go up any further, we're back at the root and we're
// done.
//
for(;;){
cr = CM_Get_Sibling(&devInstNext,
devInst,
0);
if(cr == CR_SUCCESS){
devInst = devInstNext;
break;
}
cr = CM_Get_Parent(&devInstNext,
devInst,
0);
if(cr == CR_SUCCESS){
devInst = devInstNext;
}
else{
walkDone = 1;
break;
}
}
}
return NULL;
}
CM_xxx函數是關於Registry的相關操作,而某些CM_xxx函數是可以使用SetupAPI函式替代的
該副程式首先取得Node Handle(CM_Locate_DevNode),接著取得Registry Property(CM_Get_DevNode_Registry_Property),而Property可以有如下選項
| 旗標 | 說明 |
|---|---|
| CM_DEVCAP_LOCKSUPPORTED | LockSupported |
| CM_DEVCAP_EJECTSUPPORTED | EjectSupported |
| CM_DEVCAP_REMOVABLE | Removable |
| CM_DEVCAP_DOCKDEVICE | DockDevice |
| CM_DEVCAP_UNIQUEID | UniqueID |
| CM_DEVCAP_SILENTINSTALL | SilentInstall |
| CM_DEVCAP_RAWDEVICEOK | RawDeviceOK |
| CM_DEVCAP_SURPRISEREMOVALOK | SurpriseRemovalOK |
| CM_DEVCAP_HARDWAREDISABLED | HardwareDisabled |
| CM_DEVCAP_NONDYNAMIC | NonDynamic |
這一些旗標是可以取得相關資訊的旗標,司徒要比較特別提到的是CM_Get_Parent這個函數,因為當我們取得的資料是HID裝置資料時,其實可以再使用一次CM_Get_Parent取得USB裝置資料,然後再使用一次CM_Get_Parent時,就可以取得Hub的裝置資料,CM_Get_Parent這個函數非常好用,因為一般我們寫User Mode的HID裝置列舉時,都會使用SetupDiEnumDeviceInfo函式去取得裝置資訊,但是,卻不知道如何取得USB裝置資料,所以這個時候只要呼叫CM_Get_Parent函式就可以取得USB裝置的資料,由於HID協定是USB最底層協定,它的上一層協定就是USB協定,所以才會有這樣的順序關係,範常式式如下
DWORD devInstParent; BOOLEAN bRet da.cbSize = sizeof(da); bRet = SetupDiEnumDeviceInfo(hDev, iEnumIndex, &da); CM_Get_Device_ID(da.DevInst, pDeviceID, sizeof(TCHAR) * 255, 0); CM_Get_Parent(&devInstParent, da.DevInst, 0); CM_Get_Device_ID(devInstParent, pDeviceID, sizeof(pDeviceID) * 255, 0);
此段程式可以使用在User Mode的HID列舉階段,透過該段程式就可以取得USB裝置的絕對路徑,因為HID裝置的操作和USB裝置的操作是不一樣的,所以一旦擁有USB裝置的Handle就可以非常方便使用其它相關函式
接著繼續往下分析,DriverNameToDeviceDesc副程式是取得裝置的描述名稱,如:人性化介面裝置,內部使用的方式也是透過CM_xxx函數,接著就是將取得的名稱顯示在TreeView上面,最重要的EnumerateHub副程式,因為它會繼續列舉每個Port的裝置
EnumerateHub副程式(enum.c)如下
VOID EnumerateHub (
HTREEITEM hTreeParent,
PCHAR HubName,
PUSB_NODE_CONNECTION_INFORMATION_EX ConnectionInfo,
PUSB_DESCRIPTOR_REQUEST ConfigDesc,
PSTRING_DESCRIPTOR_NODE StringDescs,
PCHAR DeviceDesc)
{
PUSB_NODE_INFORMATION hubInfo;
HANDLE hHubDevice;
HTREEITEM hItem;
PCHAR deviceName;
BOOL success;
ULONG nBytes;
PVOID info;
CHAR leafName[512]; // XXXXX how big does this have to be?
// Initialize locals to not allocated state so the error cleanup routine
// only tries to cleanup things that were successfully allocated.
//
info = NULL;
hubInfo = NULL;
hHubDevice = INVALID_HANDLE_VALUE;
// Allocate some space for a USBDEVICEINFO structure to hold the
// hub info, hub name, and connection info pointers. GPTR zero
// initializes the structure for us.
//
if(ConnectionInfo != NULL){
info = ALLOC(sizeof(USBEXTERNALHUBINFO));
}
else{
info = ALLOC(sizeof(USBROOTHUBINFO));
}
if(info == NULL){
OOPS();
goto EnumerateHubError;
}
// Allocate some space for a USB_NODE_INFORMATION structure for this Hub,
//
hubInfo = (PUSB_NODE_INFORMATION)ALLOC(sizeof(USB_NODE_INFORMATION));
if(hubInfo == NULL){
OOPS();
goto EnumerateHubError;
}
// Keep copies of the Hub Name, Connection Info, and Configuration
// Descriptor pointers
//
if(ConnectionInfo != NULL){
((PUSBEXTERNALHUBINFO)info)->DeviceInfoType = ExternalHubInfo;
((PUSBEXTERNALHUBINFO)info)->HubInfo = hubInfo;
((PUSBEXTERNALHUBINFO)info)->HubName = HubName;
((PUSBEXTERNALHUBINFO)info)->ConnectionInfo = ConnectionInfo;
((PUSBEXTERNALHUBINFO)info)->ConfigDesc = ConfigDesc;
((PUSBEXTERNALHUBINFO)info)->StringDescs = StringDescs;
}
else{
((PUSBROOTHUBINFO)info)->DeviceInfoType = RootHubInfo;
((PUSBROOTHUBINFO)info)->HubInfo = hubInfo;
((PUSBROOTHUBINFO)info)->HubName = HubName;
}
// Allocate a temp buffer for the full hub device name.
//
deviceName = (PCHAR)ALLOC(strlen(HubName) + sizeof("\\\\.\\"));
if(deviceName == NULL){
OOPS();
goto EnumerateHubError;
}
// Create the full hub device name
//
strcpy(deviceName, "\\\\.\\");
strcpy(deviceName + sizeof("\\\\.\\") - 1, HubName);
// Try to hub the open device
//
hHubDevice = CreateFile(deviceName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
// Done with temp buffer for full hub device name
//
FREE(deviceName);
if(hHubDevice == INVALID_HANDLE_VALUE){
OOPS();
goto EnumerateHubError;
}
//
// Now query USBHUB for the USB_NODE_INFORMATION structure for this hub.
// This will tell us the number of downstream ports to enumerate, among
// other things.
//
success = DeviceIoControl(hHubDevice,
IOCTL_USB_GET_NODE_INFORMATION,
hubInfo,
sizeof(USB_NODE_INFORMATION),
hubInfo,
sizeof(USB_NODE_INFORMATION),
&nBytes,
NULL);
if(!success){
OOPS();
goto EnumerateHubError;
}
// Build the leaf name from the port number and the device description
//
if(ConnectionInfo){
wsprintf(leafName, "[Port%d] ", ConnectionInfo->ConnectionIndex);
strcat(leafName, ConnectionStatuses[ConnectionInfo->ConnectionStatus]);
strcat(leafName, " : ");
}
else{
leafName[0] = 0;
}
if(DeviceDesc){
strcat(leafName, DeviceDesc);
}
else{
strcat(leafName, HubName);
}
// Now add an item to the TreeView with the PUSBDEVICEINFO pointer info
// as the LPARAM reference value containing everything we know about the
// hub.
//
hItem = AddLeaf(hTreeParent,
(LPARAM)info,
leafName,
HubIcon);
if(hItem == NULL){
OOPS();
goto EnumerateHubError;
}
// Now recursively enumrate the ports of this hub.
//
EnumerateHubPorts(
hItem,
hHubDevice,
hubInfo->u.HubInformation.HubDescriptor.bNumberOfPorts
);
CloseHandle(hHubDevice);
return;
EnumerateHubError:
//
// Clean up any stuff that got allocated
//
if(hHubDevice != INVALID_HANDLE_VALUE){
CloseHandle(hHubDevice);
hHubDevice = INVALID_HANDLE_VALUE;
}
if(hubInfo){
FREE(hubInfo);
}
if(info){
FREE(info);
}
if(HubName){
FREE(HubName);
}
if(ConnectionInfo){
FREE(ConnectionInfo);
}
if(ConfigDesc){
FREE(ConfigDesc);
}
if(StringDescs != NULL){
PSTRING_DESCRIPTOR_NODE Next;
do{
Next = StringDescs->Next;
FREE(StringDescs);
StringDescs = Next;
}while(StringDescs != NULL);
}
}
該副程式開啟Hub(不要跟之前的HCD搞混)並且取得Port的狀況(透過IOCTL_USB_GET_NODE_INFORMATION),而這個Port資訊就是代表有幾個實際Port數,有了這個資訊後,就可以更進一步的取得每個Port的裝置資訊(透過EnumerateHubPorts副程式),因此繼續往EnumerateHubPorts副程式分析,而EnumerateHubPorts副程式應該就是最後結尾的副程式了
EnumerateHubPorts副程式(enum.c)如下
VOID EnumerateHubPorts (
HTREEITEM hTreeParent,
HANDLE hHubDevice,
ULONG NumPorts)
{
HTREEITEM hItem;
ULONG index;
BOOL success;
PUSB_NODE_CONNECTION_INFORMATION_EX connectionInfoEx;
PUSB_DESCRIPTOR_REQUEST configDesc;
PSTRING_DESCRIPTOR_NODE stringDescs;
PUSBDEVICEINFO info;
PCHAR driverKeyName;
PCHAR deviceDesc;
CHAR leafName[512]; // XXXXX how big does this have to be?
int icon;
// Loop over all ports of the hub.
//
// Port indices are 1 based, not 0 based.
//
for(index=1; index <= NumPorts; index++){
ULONG nBytesEx;
// Allocate space to hold the connection info for this port.
// For now, allocate it big enough to hold info for 30 pipes.
//
// Endpoint numbers are 0-15. Endpoint number 0 is the standard
// control endpoint which is not explicitly listed in the Configuration
// Descriptor. There can be an IN endpoint and an OUT endpoint at
// endpoint numbers 1-15 so there can be a maximum of 30 endpoints
// per device configuration.
//
// Should probably size this dynamically at some point.
//
nBytesEx = sizeof(USB_NODE_CONNECTION_INFORMATION_EX) +
sizeof(USB_PIPE_INFO) * 30;
connectionInfoEx = (PUSB_NODE_CONNECTION_INFORMATION_EX)ALLOC(nBytesEx);
if(connectionInfoEx == NULL){
OOPS();
break;
}
//
// Now query USBHUB for the USB_NODE_CONNECTION_INFORMATION_EX structure
// for this port. This will tell us if a device is attached to this
// port, among other things.
//
connectionInfoEx->ConnectionIndex = index;
success = DeviceIoControl(hHubDevice,
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX,
connectionInfoEx,
nBytesEx,
connectionInfoEx,
nBytesEx,
&nBytesEx,
NULL);
if(!success){
PUSB_NODE_CONNECTION_INFORMATION connectionInfo;
ULONG nBytes;
// Try using IOCTL_USB_GET_NODE_CONNECTION_INFORMATION
// instead of IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX
//
nBytes = sizeof(USB_NODE_CONNECTION_INFORMATION) +
sizeof(USB_PIPE_INFO) * 30;
connectionInfo = (PUSB_NODE_CONNECTION_INFORMATION)ALLOC(nBytes);
connectionInfo->ConnectionIndex = index;
success = DeviceIoControl(hHubDevice,
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION,
connectionInfo,
nBytes,
connectionInfo,
nBytes,
&nBytes,
NULL);
if(!success){
OOPS();
FREE(connectionInfo);
FREE(connectionInfoEx);
continue;
}
// Copy IOCTL_USB_GET_NODE_CONNECTION_INFORMATION into
// IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX structure.
//
connectionInfoEx->ConnectionIndex =
connectionInfo->ConnectionIndex;
connectionInfoEx->DeviceDescriptor =
connectionInfo->DeviceDescriptor;
connectionInfoEx->CurrentConfigurationValue =
connectionInfo->CurrentConfigurationValue;
connectionInfoEx->Speed =
connectionInfo->LowSpeed ? UsbLowSpeed : UsbFullSpeed;
connectionInfoEx->DeviceIsHub =
connectionInfo->DeviceIsHub;
connectionInfoEx->DeviceAddress =
connectionInfo->DeviceAddress;
connectionInfoEx->NumberOfOpenPipes =
connectionInfo->NumberOfOpenPipes;
connectionInfoEx->ConnectionStatus =
connectionInfo->ConnectionStatus;
memcpy(&connectionInfoEx->PipeList[0],
&connectionInfo->PipeList[0],
sizeof(USB_PIPE_INFO) * 30);
FREE(connectionInfo);
}
// Update the count of connected devices
//
if(connectionInfoEx->ConnectionStatus == DeviceConnected){
TotalDevicesConnected++;
}
if(connectionInfoEx->DeviceIsHub){
TotalHubs++;
}
// If there is a device connected, get the Device Description
//
deviceDesc = NULL;
if(connectionInfoEx->ConnectionStatus != NoDeviceConnected){
driverKeyName = GetDriverKeyName(hHubDevice,
index);
if(driverKeyName){
deviceDesc = DriverNameToDeviceDesc(driverKeyName, FALSE);
FREE(driverKeyName);
}
}
// If there is a device connected to the port, try to retrieve the
// Configuration Descriptor from the device.
//
if(gDoConfigDesc &&
connectionInfoEx->ConnectionStatus == DeviceConnected)
{
configDesc = GetConfigDescriptor(hHubDevice,
index,
0);
}
else{
configDesc = NULL;
}
if(configDesc != NULL &&
AreThereStringDescriptors(&connectionInfoEx->DeviceDescriptor,
(PUSB_CONFIGURATION_DESCRIPTOR)(configDesc+1)))
{
stringDescs = GetAllStringDescriptors(
hHubDevice,
index,
&connectionInfoEx->DeviceDescriptor,
(PUSB_CONFIGURATION_DESCRIPTOR)(configDesc+1));
}
else{
stringDescs = NULL;
}
// If the device connected to the port is an external hub, get the
// name of the external hub and recursively enumerate it.
//
if(connectionInfoEx->DeviceIsHub){
PCHAR extHubName;
extHubName = GetExternalHubName(hHubDevice,
index);
if(extHubName != NULL){
EnumerateHub(hTreeParent, //hPortItem,
extHubName,
connectionInfoEx,
configDesc,
stringDescs,
deviceDesc);
}
}
else{
// Allocate some space for a USBDEVICEINFO structure to hold the
// Config Descriptors, Strings Descriptors, and connection info
// pointers. GPTR zero initializes the structure for us.
//
info = (PUSBDEVICEINFO) ALLOC(sizeof(USBDEVICEINFO));
if(info == NULL){
OOPS();
if(configDesc != NULL){
FREE(configDesc);
}
FREE(connectionInfoEx);
break;
}
info->DeviceInfoType = DeviceInfo;
info->ConnectionInfo = connectionInfoEx;
info->ConfigDesc = configDesc;
info->StringDescs = stringDescs;
wsprintf(leafName, "[Port%d] ", index);
strcat(leafName, ConnectionStatuses[connectionInfoEx->ConnectionStatus]);
if(deviceDesc){
strcat(leafName, " : ");
strcat(leafName, deviceDesc);
}
if(connectionInfoEx->ConnectionStatus == NoDeviceConnected){
icon = NoDeviceIcon;
}
else if(connectionInfoEx->CurrentConfigurationValue){
icon = GoodDeviceIcon;
}
else{
icon = BadDeviceIcon;
}
hItem = AddLeaf(hTreeParent, //hPortItem,
(LPARAM)info,
leafName,
icon);
}
}
}
該副程式取得每個Port的裝置資訊(透過IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX或IOCTL_USB_GET_NODE_CONNECTION_INFORMATION),將每個取得的裝置資訊(GetConfigDescriptor和GetAllStringDescriptors)加入TreeView中,這個副程式比較詭異的地方是再度呼叫EnumerateHub副程式,這樣會導致遞迴呼叫,司徒想了一下才知道,這是因為每個USB Hub可以繼續往下串接USB Hub(USB最多能支援127個裝置、USB Hub最大串接層數是6層),所以使用遞迴反而可以讓程式更簡潔,不失為一個好的寫法,所以這一個部份應該是USBView的精髓所在
司徒認為分析到此應該有很多人還是搞不清楚,所以司徒就畫一個流程圖幫助大家構思它的整體運作