Function of USB Composite Device fails to start if not first function on device


Symptoms


USB Composite Devices consist of multiple Functions (functional devices) within a single USB device package.

Some USB function drivers (drivers which support USB functional devices) may work correctly for single-function USB devices, but may not correctly support functional devices within a USB Composite Device. The affected USB function will be marked in Device Manager with a Code 10 error(CM_PROB_FAILED_START).

This problem does not occur with similar USB devices using the same USB function drivers, when the device is configured as a single-function (not Composite) device.

Cause


Affected USB function drivers may not correctly use the USBD_ParseConfigurationDescriptorEx or USBD_ParseConfigurationDescriptor routines to locate their USB Interface descriptors within the USB Composite Device's Configuration Descriptor.

For example, the following sample code demonstrates a technique for parsing a USB Configuration Descriptor that does not successfully locate the relevant USB Interface descriptors for a USB function of USB Composite Device.

Incorrect Sample A:
/* pConfigurationDescriptor points to the descriptor previously
requested from the driver. */
PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor;

/* pInterfaceList points to an array with
pConfigurationDescriptor->bNumInterfaces entries. */
PUSBD_INTERFACE_LIST_ENTRY pInterfaceList;

PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor;
LONG InterfaceNumber = 0;
LONG NumInterfaces = 0;

PURB pUrb;

NumInterfaces = pConfigurationDescriptor->bNumInterfaces;

pInterfaceList = AllocPool (NumInterfaces * sizeof(USBD_INTERFACE_LIST_ENTRY));

for (InterfaceNumber = 0;
InterfaceNumber < NumInterfaces;
InterfaceNumber++
)
{
/* Get the next matching descriptor. Here we implicitly use
the fact that the interface descriptors are laid out in
order in memory.
*/
pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx (
pConfigurationDescriptor,
pConfigurationDescriptor,
InterfaceNumber,
0, // alternate setting
-1, // interface class
-1, // interface subclass
-1, // interface protocol
);
pInterfaceList.Interface[InterfaceNumber] = pInterfaceDescriptor;
}

// allocate the URB
pUrb = USBD_CreateConfigurationRequestEx(
pConfigurationDescriptor,
pInterfaceList
);


Incorrect Sample B:
/*++
Routine Description:
This helper routine selects the specified configuration.

Arguments:
ConfigurationDescriptor - Pointer to the configuration
descriptor for the device. The caller receives this pointer
from the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE request.

Return Value: NT status value
--*/

NTSTATUS
SelectConfigurationOnDevice ( IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor)
{

PURB urb = NULL;
NTSTATUS ntStatus;
PUSBD_INTERFACE_LIST_ENTRY ListOfInterfaces = NULL;
PUSB_INTERFACE_DESCRIPTOR InterfaceDescriptor = NULL;
PUSBD_INTERFACE_INFORMATION Interface = NULL;
LONG interfaceIndex;
LONG numOfInterfaces;
USBD_PIPE_HANDLE pipeHandle;

//1. Get the number of interfaces in the configuration
numOfInterfaces = ConfigurationDescriptor->bNumInterfaces;

//2. Allocate for the array
ListOfInterfaces = (PUSBD_INTERFACE_LIST_ENTRY)ExAllocatePool (
NonPagedPool,
sizeof(USBD_INTERFACE_LIST_ENTRY) *
(numOfInterfaces + 1));

if(!ListOfInterfaces)
{
//Failed to allocate memory for pInterfaceList
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}

// 3. Initialize the array by setting all members to NULL.
RtlZeroMemory(ListOfInterfaces, sizeof (
USBD_INTERFACE_LIST_ENTRY) *
(numOfInterfaces + 1));

// 4. Enumerate interfaces in the configuration.
for ( interfaceIndex = 0;
interfaceIndex < numOfInterfaces;
interfaceIndex++)
{
InterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
ConfigurationDescriptor,
ConfigurationDescriptor,
interfaceIndex,
0, -1, -1, -1);

if (!InterfaceDescriptor)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}

//5. Populate the element in the array.
ListOfInterfaces[interfaceIndex].InterfaceDescriptor = InterfaceDescriptor;
}
...


When the USB function driver is loaded for a function of a USB Composite Device, the USB Configuration Descriptor is provided by the Microsoft USB Composite Common Generic Parent driver (Usbccgp.sys). The Configuration Descriptor only contains the Interface Descriptors and other descriptors that pertain to the specific child function for which the USB function driver is loaded. The number of Interfaces reported in this "partial" Configuration Descriptor is less than the total number of Interfaces defined for the USB Composite Device. Thus, the Interface Numbers for the USB Interfaces contained in this "partial" Configuration Descriptor may be higher than the number of Interfaces reported in this "partial" Configuration Descriptor.

As a result, calling the USBD_ParseConfigurationDescriptorEx or USBD_ParseConfigurationDescriptor routines to search for Interface numbers between 0 and the number of Interfaces in the "partial" Configuration Descriptor may fail to return any Interface Descriptors. If the USB function driver fails to find any Interface Descriptors in its device's Configuration Descriptor, the USB function driver will not be able to configure and communicate with the device, and wil fail to start successfully.

Resolution


Correct USB Function Driver implementation


Search for all Interfaces in the Configuration Descriptor, regardless of Interface Number

The following sample code snippets demonstrateshow to search for any and all Interfaces in the Configuration Descriptor:

Correct Sample A:

/* pConfigurationDescriptor points to the descriptor previously
requested from the driver. */
PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor;

/* pInterfaceList points to an array with
pConfigurationDescriptor->bNumInterfaces entries. */
PUSBD_INTERFACE_LIST_ENTRY pInterfaceList;

PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor;
LONG InterfaceNumber = 0;
LONG NumInterfaces = 0;

PURB pUrb;

PUCHAR pStartPosition = (PUCHAR)pConfigurationDescriptor;

NumInterfaces = pConfigurationDescriptor->bNumInterfaces;

pInterfaceList = AllocPool (NumInterfaces * sizeof(USBD_INTERFACE_LIST_ENTRY));

for (InterfaceNumber = 0;
InterfaceNumber < NumInterfaces;
InterfaceNumber++
)
{
/* Get the next matching descriptor. Here we implicitly use
the fact that the interface descriptors are laid out in
order in memory.
*/
pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx (
pConfigurationDescriptor,
pStartPosition, // start of search within Config Descriptor
-1, // interface number
0, // alternate setting
-1, // interface class
-1, // interface subclass
-1, // interface protocol
);
pInterfaceList.Interface[InterfaceNumber] = pInterfaceDescriptor;
if (!pInterfaceDescriptor)
{
break;
}
pStartPosition = (PUCHAR)pInterfaceDescriptor + pInterfaceDescriptor->bLength;
}

// allocate the URB
pUrb = USBD_CreateConfigurationRequestEx(
pConfigurationDescriptor,
pInterfaceList
);


Correct Sample B:
/*++

Routine Description:
This helper routine selects the specified configuration.

Arguments:
ConfigurationDescriptor - Pointer to the configuration
descriptor for the device. The caller receives this pointer
from the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE request.

Return Value: NT status value
--*/

NTSTATUS
SelectConfigurationOnDevice ( IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor)
{
PURB urb = NULL;
NTSTATUS ntStatus;
PUSBD_INTERFACE_LIST_ENTRY ListOfInterfaces = NULL;
PUSB_INTERFACE_DESCRIPTOR InterfaceDescriptor = NULL;
PUSBD_INTERFACE_INFORMATION Interface = NULL;
LONG interfaceIndex;
LONG numOfInterfaces;
USBD_PIPE_HANDLE pipeHandle;

PUCHAR StartPosition = (PUCHAR)ConfigurationDescriptor;

//1. Get the number of interfaces in the configuration
numOfInterfaces = ConfigurationDescriptor->bNumInterfaces;

//2. Allocate for the array
ListOfInterfaces = (PUSBD_INTERFACE_LIST_ENTRY)ExAllocatePool (
NonPagedPool,
sizeof(USBD_INTERFACE_LIST_ENTRY) *
(numOfInterfaces + 1));

if(!ListOfInterfaces)
{
//Failed to allocate memory for pInterfaceList

ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}

// 3. Initialize the array by setting all members to NULL.
RtlZeroMemory(ListOfInterfaces, sizeof (
USBD_INTERFACE_LIST_ENTRY) *
(numOfInterfaces + 1));


// 4. Enumerate interfaces in the configuration.
for ( interfaceIndex = 0;
interfaceIndex < numOfInterfaces;
interfaceIndex++)
{
InterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
ConfigurationDescriptor,
StartPosition, // StartPosition
-1, // InterfaceNumber
0, // AlternateSetting
-1, // InterfaceClass
-1, // InterfaceSubClass
-1); // InterfaceProtocol

if (!InterfaceDescriptor)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}

StartPosition = (PUCHAR)InterfaceDescriptor + InterfaceDescriptor->bLength;

//5. Populate the element in the array.
ListOfInterfaces[interfaceIndex].InterfaceDescriptor = InterfaceDescriptor;
}
...


Search for Interfaces of the appropriate Class, SubClass, and/or Protocol

Correct Sample A:

/* pConfigurationDescriptor points to the descriptor previously
requested from the driver. */
PUSB_CONFIGURATION_DESCRIPTOR pConfigurationDescriptor;

/* pInterfaceList points to an array with
pConfigurationDescriptor->bNumInterfaces entries. */
PUSBD_INTERFACE_LIST_ENTRY pInterfaceList;

PUSB_INTERFACE_DESCRIPTOR pInterfaceDescriptor;
LONG InterfaceNumber = 0;
LONG NumInterfaces = 0;

PURB pUrb;

PUCHAR pStartPosition = (PUCHAR)pConfigurationDescriptor;

NumInterfaces = pConfigurationDescriptor->bNumInterfaces;

pInterfaceList = AllocPool (NumInterfaces * sizeof(USBD_INTERFACE_LIST_ENTRY));

for (InterfaceNumber = 0;
InterfaceNumber < NumInterfaces;
InterfaceNumber++
)
{
/* Get the next matching descriptor. Here we implicitly use
the fact that the interface descriptors are laid out in
order in memory.
*/
pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx (
pConfigurationDescriptor,
pStartPosition,
-1, // interface number
0, // alternate setting
MY_FUNCTION_CLASS, // interface class
MY_FUNCTION_SUBCLASS, // interface subclass
MY_FUNCTION_PROTOCOL, // interface protocol
);
pInterfaceList.Interface[InterfaceNumber] = pInterfaceDescriptor;
if (!pInterfaceDescriptor)
{
break;
}
pStartPosition = (PUCHAR)pInterfaceDescriptor + pInterfaceDescriptor->bLength;
}

// allocate the URB
pUrb = USBD_CreateConfigurationRequestEx(
pConfigurationDescriptor,
pInterfaceList
);


Workaround in USB Device Design

If you are the vendor of an affected USB device which is still under design, and you are unable to change the USB function driver to resolve this problem, you may be able to work around this problem by changing the design of your device.

Reorder the Interfaces in your device's Configuration Descriptor
Make sure the Interface (or Interfaces) for the affected function appear first in the list of Interface Descriptors contained within your device's Configuration Descriptor. For example, the Interface(s) for the affected function would appear first device's Configuration Descriptor, and be numbered starting with number 0. The Interfaces for any functions would appear afterward in the device's Configuration Descriptor, and would be assigned subsequent interface numbers.

Implement your USB device as a Compound Device

A USB Compound Device consists of a single physical package which implements multiple functions and an embedded hub with a single USB cable. A compound device appears to the host as a hub with one or more non-removable USB devices. Each of these devices would be implemented as single-function devices.

More Information


USB Configuration Descriptors

A Configuration Descriptor for a USB device contains a Configuration Header followed by descriptors for the Interfaces associated with the selected configuration of the USB device, as well as additional descriptors that may be associated with each Interface such as Endpoint descriptors and class-specific descriptors.

A USB Configuration Descriptor for a single-Interface device may appear as follows:

Configuration Header (USB_CONFIGURATION_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_CONFIGURATION_DESCRIPTOR_TYPE (0x02)
- wTotalLength = total length, in bytes, of all data for the configuration
- bNumInterfaces = 1 (single Interface)
...
Interface Descriptor (USB_INTERFACE_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_DESCRIPTOR_TYPE (0x04)
- bInterfaceNumber = 0
- bAlternateSetting
- bNumEndpoints
- bInterfaceClass
- bInterfaceSubClass
- bInterfaceProtocol
...
Endpoint Descriptor(s) (USB_ENDPOINT_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE (0x05)
- bEndpointAddress
...
Class-Specific Descriptor(s) (optional)
- bLength = size of this descriptor in bytes
- bDescriptorType = class-specific descriptor type
...
...

A complete USB Configuration Descriptor for a multiple-Interface (Composite) device may appear as follows:

Configuration Header (USB_CONFIGURATION_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_CONFIGURATION_DESCRIPTOR_TYPE (0x02)
- wTotalLength = total length, in bytes, of all data for the configuration
- bNumInterfaces = 3 (multiple Interfaces, Composite device)
...
Interface Association Descriptor (USB_INTERFACE_ASSOCIATION_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_ASSOCIATION_DESCRIPTOR_TYPE (0x0B)
- bFirstInterface = 0
- bInterfaceCount = 2
- bFunctionClass
- bFunctionSubClass
- bFunctionProtocol
...
Interface Descriptor (USB_INTERFACE_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_DESCRIPTOR_TYPE (0x04)
- bInterfaceNumber = 0
- bAlternateSetting
- bNumEndpoints
- bInterfaceClass
- bInterfaceSubClass
- bInterfaceProtocol
...
Endpoint Descriptor(s) (USB_ENDPOINT_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE (0x05)
- bEndpointAddress
...
Class-Specific Descriptor(s) (optional)
- bLength = size of this descriptor in bytes
- bDescriptorType = class-specific descriptor type
...
...
Interface Descriptor (USB_INTERFACE_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_DESCRIPTOR_TYPE (0x04)
- bInterfaceNumber = 1
- bAlternateSetting
- bNumEndpoints
- bInterfaceClass
- bInterfaceSubClass
- bInterfaceProtocol
...
Endpoint Descriptor(s) (USB_ENDPOINT_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE (0x05)
- bEndpointAddress
...
Class-Specific Descriptor(s) (optional)
- bLength = size of this descriptor in bytes
- bDescriptorType = class-specific descriptor type
...
...
Interface Descriptor (USB_INTERFACE_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_DESCRIPTOR_TYPE (0x04)
- bInterfaceNumber = 2
- bAlternateSetting
- bNumEndpoints
- bInterfaceClass
- bInterfaceSubClass
- bInterfaceProtocol
...
Endpoint Descriptor(s) (USB_ENDPOINT_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE (0x05)
- bEndpointAddress
...
Class-Specific Descriptor(s) (optional)
- bLength = size of this descriptor in bytes
- bDescriptorType = class-specific descriptor type
...
...

The partial USB Configuration Descriptor presented by Usbccgp to the USB function driver for the first function of the above device may appear as follows:

Configuration Header (USB_CONFIGURATION_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_CONFIGURATION_DESCRIPTOR_TYPE (0x02)
- wTotalLength = total length, in bytes, of all data for the configuration
- bNumInterfaces = 2
...
Interface Association Descriptor (USB_INTERFACE_ASSOCIATION_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_ASSOCIATION_DESCRIPTOR_TYPE (0x0B)
- bFirstInterface = 0
- bInterfaceCount = 2
- bFunctionClass
- bFunctionSubClass
- bFunctionProtocol
...
Interface Descriptor (USB_INTERFACE_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_DESCRIPTOR_TYPE (0x04)
- bInterfaceNumber = 0
- bAlternateSetting
- bNumEndpoints
- bInterfaceClass
- bInterfaceSubClass
- bInterfaceProtocol
...
Endpoint Descriptor(s) (USB_ENDPOINT_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE (0x05)
- bEndpointAddress
...
Class-Specific Descriptor(s) (optional)
- bLength = size of this descriptor in bytes
- bDescriptorType = class-specific descriptor type
...
...
Interface Descriptor (USB_INTERFACE_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_DESCRIPTOR_TYPE (0x04)
- bInterfaceNumber = 1
- bAlternateSetting
- bNumEndpoints
- bInterfaceClass
- bInterfaceSubClass
- bInterfaceProtocol
...
Endpoint Descriptor(s) (USB_ENDPOINT_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE (0x05)
- bEndpointAddress
...
Class-Specific Descriptor(s) (optional)
- bLength = size of this descriptor in bytes
- bDescriptorType = class-specific descriptor type
...
...

The partial USB Configuration Descriptor presented by Usbccgp to the USB function driver for the second function of the above device may appear as follows:

Configuration Header (USB_CONFIGURATION_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_CONFIGURATION_DESCRIPTOR_TYPE (0x02)
- wTotalLength = total length, in bytes, of all data for the configuration
- bNumInterfaces = 1
...
Interface Descriptor (USB_INTERFACE_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_INTERFACE_DESCRIPTOR_TYPE (0x04)
- bInterfaceNumber = 2
- bAlternateSetting
- bNumEndpoints
- bInterfaceClass
- bInterfaceSubClass
- bInterfaceProtocol
...
Endpoint Descriptor(s) (USB_ENDPOINT_DESCRIPTOR)
- bLength = size of this descriptor in bytes
- bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE (0x05)
- bEndpointAddress
...
Class-Specific Descriptor(s) (optional)
- bLength = size of this descriptor in bytes
- bDescriptorType = class-specific descriptor type
...
...

Using one of the incorrect sample code implementations, a USB function driver loaded for this example second function would call USBD_ParseConfigurationDescriptorEx to search for Interface number 0. When this call failed to return a matching Interface Descriptor, the loop would increment the Interface number counter to 1, then terminate the loop since this counter now equals the number of interfaces reported for this partial Configuration Descriptor.



References

For more information on USB descriptor formats used by USB client drivers, see the following topics in the Windows Driver Kit (WDK) documentation:

USB_COMMON_DESCRIPTOR Structure
USB_CONFIGURATION_DESCRIPTOR Structure
USB_INTERFACE_DESCRIPTOR Structure
USB_ENDPOINT_DESCRIPTOR Structure

For more information on routines used to parse USB descriptors, see the following topic sin the Windows Driver Kit (WDK) documentation:

USBD_ParseConfigurationDescriptorEx
USBD_ParseDescriptors

For more information on USB Composite and Compound devices, see the following sections of the USB 2.0 specification:
  • 4.8.2.2 Functions
  • 5.2.3 Physical Bus Topology

The USB 2.0 specification is available for download from:

http://www.usb.org/developers/docs

Affected Microsoft Windows drivers

The following drivers provided in Microsoft Windows are known to exhibit this problem:

Windows 7 and Windows Server 2008 R2:
  • Usb8023.sys, Usb8023x.sys, Usb80236.sys (Remote NDIS [RNDIS] USB drivers)
  • BthUsb.sys (Bluetooth USB Miniport Driver)
  • UsbCamd.sys (Universal Serial Bus Camera Driver)
Windows Vista and Windows Server 2008:
  • Usb8023.sys, Usb8023x.sys, Usb80236.sys (Remote NDIS [RNDIS] USB drivers)
  • BthUsb.sys (Bluetooth USB Miniport Driver)
  • UsbCamd.sys (Universal Serial Bus Camera Driver)
For USB composite devices with functions which use these in-box drivers, the device vendor may be able to work around the problem by reconfiguring their device so that the Interface(s) for the affected function appear(s) first in the USB Composite Device's Configuration Descriptor, or by implementing the device as a USB Compound device, as described above.