USB通用驱动源码分析
+ -

USB物理设备通过USBCCGP拆分成逻辑设备

2025-06-16 0 0

USB规范中定义一个接口代表一个功能,故一般情况下一个接口描述符及其下的描述符实现一个特定的功能。

如对于HID设备,接口描述符下是HID描述符端点描述符等。

但在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设备
  • 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   

0 篇笔记 写笔记

USB物理设备通过USBCCGP拆分成逻辑设备
USB规范中定义一个接口代表一个功能,故一般情况下一个接口描述符及其下的描述符实现一个特定的功能。如对于HID设备,接口描述符下是HID描述符和端点描述符等。但在USB规范中,也定义一些特定的类设备,如UVC和UAC设备,其一般是需要2个接口描述符实现一个设备功能。如UVC、UAC设备下,其......
关注公众号
  • HID人机交互
  • Linux&USB
  • UAC音频
  • CDC
  • TYPE-C
  • USB规范
  • USB大容量存储
  • USB百科
  • USB周边
  • UVC摄像头
  • Windows系统USB
  • 音视频博客
  • 取消
    感谢您的支持,我会继续努力的!
    扫码支持
    扫码打赏,你说多少就多少

    打开支付宝扫一扫,即可进行扫码打赏哦

    您的支持,是我们前进的动力!