USB设备总线拓扑结构生成原理及方法
在Windows系统的内核中,USB堆栈提供了一系列的API接口。内核或者应用程序可以使用这些API接口来确定USB总线的拓扑结构和相关的USB设备信息。
我们常用的USBTreeViewe是基于微软开源usbview二次开发的,而usbview就是使用这些API接口来实现USB总线拓扑结构和USB设备信息的获取的。
USB设备分类
USB设备一般挂在USB集线器上,USB集线器也可以挂在他的上一层USB集线器上,但最终是挂在USB根集线器上的,USB根集线器是由USB主机控制器来创建的。所以USB的设备总共可以分为三类:
- USB主机控制器
- USB集线器(包括根集线器)
- USB普通设备
当然,在USB规范中,也将USB集线器归为USB设备.USB集线器也占用USB地址。
USB总线枚举
在Windows系统中,USB根集线器的设备名遵循HCD0,HCD1…HCD9 的命名规则。我们可以使用如下的方式来枚举系统中所有USB主机控制器。
TCHAR HCName[16];
for (int HCNum = 0; HCNum < NUM_HCS_TO_CHECK; HCNum++)
{
_stprintf_s(HCName, sizeof(HCName) / sizeof(HCName[0]), _T("\\\\.\\HCD%d"), HCNum);
printf("%s\n", HCName);
HANDLE hHCDev = CreateFile(HCName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hHCDev != INVALID_HANDLE_VALUE)
{
EnumerateHostController(hHCDev);
CloseHandle(hHCDev);
}
}
实际在使用过程中,我们使用SETUPAPI函数枚举GUID_DEVINTERFACE_USB_HOST_CONTROLLER的设备类型。
/* 3ABF6F2D-71C4-462a-8A92-1E6861E6AF27 */
DEFINE_GUID(GUID_DEVINTERFACE_USB_HOST_CONTROLLER, 0x3abf6f2d, 0x71c4, 0x462a, 0x8a, 0x92, 0x1e, \
0x68, 0x61, 0xe6, 0xaf, 0x27);
获取根集线器
应用程序获取了USB主机控制器的句柄后,可以向其下发 USB_GET_ROOT_HUB_NAME来获取其下的USB根集线器名称。其参数为:
typedef struct _USB_ROOT_HUB_NAME {
ULONG ActualLength; /* OUTPUT */
/* NULL terminated unicode symbolic name for the root hub */
WCHAR RootHubName[1]; /* OUTPUT */
} USB_ROOT_HUB_NAME, *PUSB_ROOT_HUB_NAME;
其中ActualLength指定的是整个结构体的大小,而非RootHubName的大小。
获取的USB根集线器名称一般为这样的:
USB#ROOT_HUB30#4&31789dfb&0&0#{f18a0e88-c30c-11d0-8815-00a0c906bed8}
我们对其加上设备前导头好可得到:
\\.\USB#ROOT_HUB30#4&31789dfb&0&0#{f18a0e88-c30c-11d0-8815-00a0c906bed8}
这样的字符串。使用该字符串可以使用CreateFile来打开根集线器。
USB根集线器的枚举
当然,和USB根集控器一样,我们也可以通过SETUPAPI使用GUID_DEVINTERFACE_USB_HUB枚举系统中所有USB集线器(包括根集线器和扩展集线器)。
/* f18a0e88-c30c-11d0-8815-00a0c906bed8 */ DEFINE_GUID(GUID_DEVINTERFACE_USB_HUB, 0xf18a0e88, 0xc30c, 0x11d0, 0x88, 0x15, 0x00, \ 0xa0, 0xc9, 0x06, 0xbe, 0xd8);
对于USB集线器,比较关注的是是其下有多少个下游端口,其连接状态是什么。这可以通过IOCTL_USB_GET_NODE_INFORMATION来获取到。获取到的数据结构USB_NODE_INFORMATION如下:
typedef struct _USB_HUB_DESCRIPTOR {
UCHAR bDescriptorLength;
UCHAR bDescriptorType;
UCHAR bNumberOfPorts;
USHORT wHubCharacteristics;
UCHAR bPowerOnToPowerGood;
UCHAR bHubControlCurrent;
UCHAR bRemoveAndPowerMask[64];
} USB_HUB_DESCRIPTOR, *PUSB_HUB_DESCRIPTOR;
typedef struct _USB_HUB_INFORMATION {
/*
copy of data from hub descriptor
*/
USB_HUB_DESCRIPTOR HubDescriptor;
BOOLEAN HubIsBusPowered;
} USB_HUB_INFORMATION, *PUSB_HUB_INFORMATION;
typedef struct _USB_NODE_INFORMATION {
USB_HUB_NODE NodeType; /* hub, mi parent */
union {
USB_HUB_INFORMATION HubInformation;
USB_MI_PARENT_INFORMATION MiParentInformation;
} u;
} USB_NODE_INFORMATION, *PUSB_NODE_INFORMATION;
通过解析USB集线器描述符中的bNumberOfPorts即可获取USB集线器的端口数。
USB集线器的数据编号从1开始,到bNumberOfPorts。这时可以通过IOCTL_USB_GET_NODE_CONNECTION_INFORMATION来获取指定USB端口下设备的连接信息。
typedef struct _USB_NODE_CONNECTION_INFORMATION {
ULONG ConnectionIndex; /* INPUT */
/* usb device descriptor returned by this device
during enumeration */
USB_DEVICE_DESCRIPTOR DeviceDescriptor; /* OUTPUT */
UCHAR CurrentConfigurationValue;/* OUTPUT */
BOOLEAN LowSpeed;/* OUTPUT */
BOOLEAN DeviceIsHub;/* OUTPUT */
USHORT DeviceAddress;/* OUTPUT */
ULONG NumberOfOpenPipes;/* OUTPUT */
USB_CONNECTION_STATUS ConnectionStatus;/* OUTPUT */
USB_PIPE_INFO PipeList[0];/* OUTPUT */
} USB_NODE_CONNECTION_INFORMATION, *PUSB_NODE_CONNECTION_INFORMATION;
- 对于普通设备,可通过IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION来获取USB设备的相关描述符内容。
- 对于USB集线器,可通过IOCTL_USB_GET_NODE_CONNECTION_NAME来获取其扩展集线器的名称。其后就和根集器的过程一样。