USB物理设备通过USBCCGP拆分成逻辑设备
2025-06-16
0
0
USB规范中定义一个接口代表一个功能,故一般情况下一个接口描述符及其下的描述符实现一个特定的功能。
但在USB规范中,也定义一些特定的类设备,如UVC和UAC设备,其一般是需要2个接口描述符实现一个设备功能。如UVC、UAC设备下,其一般会有一个控制接口描述符和N{0~N}个音视频流接口描述符。对于这种特别的情况,USB规范为了使用更加友好的呈现给用户,其使用了接口关联描述符IAD来实现关联。
所以,对于一个配置描述符,其一般可以抽象成1个或者多个逻辑设备,其逻辑设备的大小一般小于等于接口描述符的数量。
逻辑设备的拆分
一个USB设备选择配置SET_CONFIGURATION之后,其配置描述符就确定了。
这样对于Windows系统,可以通过USBD_ParseConfigurationDescriptorEx函数来拆分设备各个接口描述符的地址。
PUSB_INTERFACE_DESCRIPTOR
USBD_ParseConfigurationDescriptorEx(
IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor,
IN PVOID StartPosition,
IN LONG InterfaceNumber,
IN LONG AlternateSetting,
IN LONG InterfaceClass,
IN LONG InterfaceSubClass,
IN LONG InterfaceProtocol
);
对于大部分的配置描述符,其第一个接口描述符的InterfaceNumber为0,并且后续连续。但其实很多设备根据就不遵守这个规则,他们惟一遵守的可能就是对于多个接口描述符复合的设备,其下一个接口描述符只与上一个保持联系,其它根据不做任何保证。比如一个有UVC,UAC、HID的设备,其接口描述符的索引如下就很正常:
- 配置描述符声明有5个接口
- UVC设备
- 控制接口描述符索引为2
- 视频流接口描述符索引为3=2+1
- UAC设备
- 控制接口描述符索引为6
- 音频流接口描述符索引为7=6+1
- HID设备
- 接口描述符索引为9
所以对于使用USBD_ParseConfigurationDescriptorEx,更合适的代码也许如此:
PUSB_CONFIGURATION_DESCRIPTOR configDesc = ?
PCOMM_DESCRIPTOR pComm = configDesc;
for(int i=0;i<configDesc->bNumInterfaces;i++)
{
interfaceDesc = USBD_ParseConfigurationDescriptorEx(
configDesc,
pComm,
-1,
0,//不取备用接口
-1,
-1,
-1);
pComm= (PCOMM_DESCRIPTOR)((PUCHAR)pComm+pComm->length);
}
拆分出的接口描述符组,个人更倾向于通过其接口描述符的bInterfaceClass来进行逻辑设备分组。所以有的设备有一个接口描述符,有的有多个接口描述符。对于功能设备,其至少存如下信息:
- ULONG numInterfaces
- PUSB_INTERFACE_DESCRIPTOR InterfaceDescriptorStart //实际由USBD_INTERFACE_LIST_ENTRY functionInterfaceList代替
另外一点是需要对设备的设备描述符修正(friendlyName):
DeviceDesc.iProduct = functionInterfaceList[0].InterfaceDescriptor->iInterface;
逻辑设备配置描述符的生成
/*
* BuildFunctionConfigurationDescriptor
*
*
* Note: this function cannot be pageable because internal
* ioctls may be sent at IRQL==DISPATCH_LEVEL.
*/
NTSTATUS BuildFunctionConfigurationDescriptor(
PFUNCTION_PDO_EXT functionPdoExt,
PUCHAR buffer,
ULONG bufferLength,
PULONG bytesReturned)
{
PUSB_CONFIGURATION_DESCRIPTOR functionConfigDesc;
PUSB_COMMON_DESCRIPTOR commonDesc;
PUSB_INTERFACE_DESCRIPTOR thisIfaceDesc;
PUCHAR scratch;
ULONG totalLength;
NTSTATUS status;
ULONG i;
// BUGBUG - change this to use the USBD ParseConfiguration function
/*
* The function's configuration descriptor will include
* a subset of the interface descriptors in the parent's
* configuration descriptor.
*/
//物理设备的配置描述符
PUSB_CONFIGURATION_DESCRIPTOR parentConfigDesc = functionPdoExt->parentFdoExt->selectedConfigDesc;
PUCHAR parentConfigDescEnd = (PUCHAR)((PUCHAR)parentConfigDesc + parentConfigDesc->wTotalLength);
/*
* First calculate the total length of what we'll be copying.
* It will include a configuration descriptor followed by
* some number of interface descriptors.
* Each interface descriptor may be followed by a some number
* of class-specific descriptors.
*/
totalLength = sizeof(USB_CONFIGURATION_DESCRIPTOR);
for (i = 0; i < functionPdoExt->numInterfaces; i++)
{
/*
* We will copy the interface descriptor and all following
* descriptors until either the next interface
* descriptor or the end of the entire
* configuration descriptor.
*/
thisIfaceDesc = functionPdoExt->functionInterfaceList[i].InterfaceDescriptor;
commonDesc = (PUSB_COMMON_DESCRIPTOR)thisIfaceDesc;
do {
totalLength += commonDesc->bLength;
commonDesc = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)commonDesc) + commonDesc->bLength);
}
#if 0
if((PUCHAR)commonDesc >= parentConfigDescEnd)
{
break;
}
if(commonDesc->bDescriptorType == USB_INTERFACE_DESCRIPTOR_TYPE)
{
if((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->bInterfaceNumber != thisIfaceDesc->bInterfaceNumber)
{
break;
}
}
while(1);
#else
while (((PUCHAR)commonDesc < parentConfigDescEnd)
//到下一个接口描述符停止 if(isinterface || interfaceNum == thisinterfacenum)
&&((commonDesc->bDescriptorType != USB_INTERFACE_DESCRIPTOR_TYPE) || (((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->bInterfaceNumber == thisIfaceDesc->bInterfaceNumber)));
#endif
}
scratch = ALLOCPOOL(NonPagedPool, totalLength);
if (scratch){
PUCHAR pch;
pch = scratch;
RtlCopyMemory(pch, parentConfigDesc, sizeof(USB_CONFIGURATION_DESCRIPTOR));
pch += sizeof(USB_CONFIGURATION_DESCRIPTOR);
for (i = 0; i < functionPdoExt->numInterfaces; i++) {
/*
* Copy the interface descriptor and all following
* descriptors until either the next interface
* descriptor or the end of the entire
* configuration descriptor.
*/
thisIfaceDesc = functionPdoExt->functionInterfaceList[i].InterfaceDescriptor;
ASSERT(thisIfaceDesc->bDescriptorType == USB_INTERFACE_DESCRIPTOR_TYPE);
commonDesc = (PUSB_COMMON_DESCRIPTOR)thisIfaceDesc;
do {
RtlCopyMemory(pch, commonDesc, commonDesc->bLength);
pch += commonDesc->bLength;
commonDesc = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)commonDesc) + commonDesc->bLength);
}
while (((PUCHAR)commonDesc < parentConfigDescEnd) &&
((commonDesc->bDescriptorType != USB_INTERFACE_DESCRIPTOR_TYPE) ||
(((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->bInterfaceNumber == thisIfaceDesc->bInterfaceNumber)));
}
/*
* This 'function' child's config descriptor contains
* a subset of the parent's interface descriptors.
* Update the child's configuration descriptor's size
* to reflect the possibly-reduced number of interface descriptors.
*/
functionConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR)scratch;
functionConfigDesc->bNumInterfaces = (UCHAR)functionPdoExt->numInterfaces;
functionConfigDesc->wTotalLength = (USHORT)totalLength;
/*
* Copy as much of the config descriptor as will fit in the caller's buffer.
* Return success whether or not the caller's buffer was actually big enough (BUGBUG ? - this is what usbhub did).
*/
*bytesReturned = MIN(bufferLength, totalLength);
RtlCopyMemory(buffer, scratch, *bytesReturned);
DBGDUMPBYTES("BuildFunctionConfigurationDescriptor built config desc for function", buffer, *bytesReturned);
FREEPOOL(scratch);
status = STATUS_SUCCESS;
}
else {
ASSERT(scratch);
status = STATUS_INSUFFICIENT_RESOURCES;
*bytesReturned = 0;
}
return status;
}
HID人机交互QQ群:564808376
UAC音频QQ群:218581009
UVC相机QQ群:331552032
BOT&UASP大容量存储QQ群:258159197
STC-USB单片机QQ群:315457461
USB技术交流QQ群2:580684376
USB技术交流QQ群:952873936