USB-Konfigurationsdeskriptoren

Ein USB-Gerät macht seine Funktionen in Form einer Reihe von Schnittstellen verfügbar, die als USB-Konfiguration bezeichnet werden. Jede Schnittstelle besteht aus einer oder mehreren alternativen Einstellungen, und jede alternative Einstellung besteht aus einer Gruppe von Endpunkten. In diesem Thema werden die verschiedenen Deskriptoren beschrieben, die einer USB-Konfiguration zugeordnet sind.

Eine USB-Konfiguration wird in einem Konfigurationsdeskriptor beschrieben (siehe USB_CONFIGURATION_DESCRIPTOR-Struktur ). Ein Konfigurationsdeskriptor enthält Informationen zur Konfiguration und ihren Schnittstellen, alternativen Einstellungen und deren Endpunkten. Jeder Schnittstellendeskriptor oder jede alternative Einstellung wird in einer USB_INTERFACE_DESCRIPTOR-Struktur beschrieben. In einer Konfiguration werden jedem Schnittstellendeskriptor im Arbeitsspeicher alle Endpunktdeskriptoren für die Schnittstelle und alternative Einstellung gefolgt. Jeder Endpunktdeskriptor wird in einer USB_ENDPOINT_DESCRIPTOR-Struktur gespeichert.

Betrachten Sie beispielsweise ein USB-Webcamgerät, das unter USB-Gerätelayout beschrieben wird. Das Gerät unterstützt eine Konfiguration mit zwei Schnittstellen, und die erste Schnittstelle (Index 0) unterstützt zwei alternative Einstellungen.

Das folgende Beispiel zeigt den Konfigurationsdeskriptor für das USB-Webcamgerät:

Configuration Descriptor:
wTotalLength:         0x02CA
bNumInterfaces:       0x02
bConfigurationValue:  0x01
iConfiguration:       0x00
bmAttributes:         0x80 (Bus Powered )
MaxPower:             0xFA (500 mA)

Das Feld bConfigurationValue gibt die Nummer für die konfiguration an, die in der Firmware des Geräts definiert ist. Der Clienttreiber verwendet diesen Zahlenwert, um eine aktive Konfiguration auszuwählen. Weitere Informationen zur KONFIGURATION von USB-Geräten finden Sie unter Auswählen einer Konfiguration für ein USB-Gerät. Eine USB-Konfiguration gibt auch bestimmte Leistungseigenschaften an. BmAttributes enthält eine Bitmaske, die angibt, ob die Konfiguration das Remoteaktivierungsfeature unterstützt und ob das Gerät mit Bus- oder Selbststrom versorgt ist. Das Feld MaxPower gibt die maximale Leistung (in Milliampereeinheiten) an, die das Gerät vom Host beziehen kann, wenn das Gerät mit Busbetrieb betrieben wird. Der Konfigurationsdeskriptor gibt auch die Gesamtanzahl von Schnittstellen (bNumInterfaces) an, die das Gerät unterstützt.

Das folgende Beispiel zeigt den Schnittstellendeskriptor für alternative Einstellung 0 der Schnittstelle 0 für das Webcamgerät:

Interface Descriptor:
bInterfaceNumber:     0x00
bAlternateSetting:    0x00
bNumEndpoints:        0x01
bInterfaceClass:      0x0E
bInterfaceSubClass:   0x02
bInterfaceProtocol:   0x00
iInterface:           0x02
0x0409: "Microsoft LifeCam VX-5000"
0x0409: "Microsoft LifeCam VX-5000"

Beachten Sie im vorherigen Beispiel die Feldwerte bInterfaceNumber und bAlternateSetting . Diese Felder enthalten Indexwerte, die ein Clienttreiber zum Aktivieren der Schnittstelle und einer ihrer alternativen Einstellungen verwendet. Zur Aktivierung sendet der Treiber eine Select-Interface-Anforderung an den USB-Treiberstapel. Der Treiberstapel erstellt dann eine Standardsteuerungsanforderung (SET INTERFACE) und sendet sie an das Gerät. Beachten Sie das Feld bInterfaceClass . Der Schnittstellendeskriptor oder der Deskriptor für eine der alternativen Einstellungen gibt einen Klassencode, eine Unterklasse und ein Protokoll an. Der Wert von 0x0E gibt an, dass die Schnittstelle für die Videogeräteklasse gilt. Beachten Sie auch das Feld iInterface . Dieser Wert gibt an, dass dem Schnittstellendeskriptor zwei Zeichenfolgendeskriptoren angefügt werden. Zeichenfolgendeskriptoren enthalten Unicode-Beschreibungen, die während der Geräteaufzählung verwendet werden, um die Funktionalität zu identifizieren. Weitere Informationen zu Zeichenfolgendeskriptoren finden Sie unter USB-Zeichenfolgendeskriptoren.

Jeder Endpunkt in einer Schnittstelle beschreibt einen einzelnen Datenstrom mit Eingabe oder Ausgabe für das Gerät. Ein Gerät, das Datenströme für verschiedene Arten von Funktionen unterstützt, verfügt über mehrere Schnittstellen. Ein Gerät, das mehrere Datenströme unterstützt, die sich auf eine Funktion beziehen, kann mehrere Endpunkte auf einer einzelnen Schnittstelle unterstützen.

Alle Arten von Endpunkten (mit Ausnahme des Standardendpunkts) müssen Endpunktdeskriptoren bereitstellen, damit der Host Informationen zum Endpunkt abrufen kann. Ein Endpunktdeskriptor enthält Informationen, z. B. adresse, typ, richtung und die Datenmenge, die der Endpunkt verarbeiten kann. Die Datenübertragungen an den Endpunkt basieren auf diesen Informationen.

Das folgende Beispiel zeigt einen Endpunktdeskriptor für das Webcamgerät:

Endpoint Descriptor:
bEndpointAddress:   0x82  IN
bmAttributes:       0x01
wMaxPacketSize:     0x0080 (128)
bInterval:          0x01

Das Feld bEndpointAddress gibt die eindeutige Endpunktadresse an, die die Endpunktnummer (Bits 3..0) und die Richtung des Endpunkts (Bit 7) enthält. Durch Das Lesen dieser Werte im vorherigen Beispiel können wir feststellen, dass der Deskriptor einen IN-Endpunkt beschreibt, dessen Endpunktnummer 2 ist. Das bmAttributes-Attribut gibt an, dass der Endpunkttyp isochron ist. Das wMaxPacketSizefield gibt die maximale Anzahl von Bytes an, die der Endpunkt in einer einzelnen Transaktion senden oder empfangen kann. Bits 12..11 gibt die Gesamtzahl der Transaktionen an, die pro Microframe gesendet werden können. Der bInterval gibt an, wie oft der Endpunkt Daten senden oder empfangen kann.

Abrufen des Konfigurationsdeskriptors

Der Konfigurationsdeskriptor wird vom Gerät über eine Standardgeräteanforderung (GET_DESCRIPTOR) abgerufen, die als Steuerungsübertragung vom USB-Treiberstapel gesendet wird. Ein USB-Clienttreiber kann die Anforderung auf eine der folgenden Arten initiieren:

  • Wenn das Gerät nur eine Konfiguration unterstützt, besteht die einfachste Möglichkeit darin, die vom Framework bereitgestellte WdfUsbTargetDeviceRetrieveConfigDescriptor-Methode aufzurufen.

  • Wenn der Clienttreiber für ein Gerät, das mehrere Konfigurationen unterstützt, den Deskriptor der Konfiguration abrufen möchte, muss der Treiber eine URB übermitteln. Um eine URB zu übermitteln, muss der Treiber die URB zuordnen, formatieren und dann an den USB-Treiberstapel übermitteln.

    Um die URB zuzuordnen, muss der Clienttreiber die WdfUsbTargetDeviceCreateUrb-Methode aufrufen. Die -Methode empfängt einen Zeiger auf eine VOM USB-Treiberstapel zugeordnete URB.

    Zum Formatieren der URB kann der Clienttreiber das Makro UsbBuildGetDescriptorRequest verwenden. Das Makro legt alle erforderlichen Informationen in der URB fest, z. B. die gerätedefinierte Konfigurationsnummer, für die der Deskriptor abgerufen werden soll. Die URB-Funktion ist auf URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE festgelegt (siehe _URB_CONTROL_DESCRIPTOR_REQUEST), und der Typ des Deskriptors ist auf USB_CONFIGURATION_DESCRIPTOR_TYPE festgelegt. Mithilfe der in der URB enthaltenen Informationen erstellt der USB-Treiberstapel eine Standardsteuerungsanforderung und sendet sie an das Gerät.

    Um die URB zu übermitteln, muss der Clienttreiber ein WDF-Anforderungsobjekt verwenden. Um das Anforderungsobjekt asynchron an den USB-Treiberstapel zu senden, muss der Treiber die **WdfRequestSend**-Methode aufrufen. Um sie synchron zu senden, rufen Sie die WdfUsbTargetDeviceSendUrbSynchronously-Methode auf.

    WDM-Treiber: Ein WdM-Clienttreiber (Windows Driver Model) kann den Konfigurationsdeskriptor nur abrufen, indem er eine URB übermittelt. Um die URB zuzuordnen, muss der Treiber die USBD_UrbAllocate Routine aufrufen. Um die URB zu formatieren, muss der Treiber das Makro UsbBuildGetDescriptorRequest aufrufen. Um die URB zu übermitteln, muss der Treiber die URB einem IRP zuordnen und den IRP an den USB-Treiberstapel übermitteln. Weitere Informationen finden Sie unter Übermitteln einer URB.

Innerhalb einer USB-Konfiguration sind die Anzahl der Schnittstellen und ihre alternativen Einstellungen variabel. Daher ist es schwierig, die Größe des Puffers vorherzusagen, die zum Halten des Konfigurationsdeskriptors erforderlich ist. Der Clienttreiber muss alle diese Informationen in zwei Schritten sammeln. Bestimmen Sie zunächst, welche Größe der Puffer erforderlich ist, um den gesamten Konfigurationsdeskriptor zu enthalten, und stellen Sie dann eine Anforderung zum Abrufen des gesamten Deskriptors. Ein Clienttreiber kann die Größe auf eine der folgenden Arten abrufen:

Führen Sie die folgenden Schritte aus, um den Konfigurationsdeskriptor durch Aufrufen von WdfUsbTargetDeviceRetrieveConfigDescriptor abzurufen:

  1. Rufen Sie WdfUsbTargetDeviceRetrieveConfigDescriptor auf, um die Größe des Puffers abzurufen, der zum Aufnehmen aller Konfigurationsinformationen erforderlich ist. Der Treiber muss NULL im Puffer übergeben, und eine Variable muss die Größe des Puffers enthalten.
  2. Ordnen Sie einen größeren Puffer basierend auf der Größe zu, die über den vorherigen WdfUsbTargetDeviceRetrieveConfigDescriptor-Aufruf empfangen wurde.
  3. Rufen Sie WdfUsbTargetDeviceRetrieveConfigDescriptor erneut auf, und geben Sie einen Zeiger auf den neuen Puffer an, der in Schritt 2 zugewiesen wurde.
 NTSTATUS RetrieveDefaultConfigurationDescriptor (
    _In_  WDFUSBDEVICE  UsbDevice,
    _Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor 
    )
{
    NTSTATUS ntStatus = -1;

    USHORT sizeConfigDesc;

    PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;

    PAGED_CODE();

    *ConfigDescriptor  = NULL;

    ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
        UsbDevice, 
        NULL,
        &sizeConfigDesc);

    if (sizeConfigDesc == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor size.");

        goto Exit;
    }
    else
    {
        fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
            NonPagedPool, 
            sizeConfigDesc,
            USBCLIENT_TAG);

        if (!fullConfigDesc)
        {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            goto Exit;
        }  
    }

    RtlZeroMemory (fullConfigDesc, sizeConfigDesc);

    ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
        UsbDevice, 
        fullConfigDesc,
        &sizeConfigDesc);

    if (!NT_SUCCESS(ntStatus))
    {           
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor.");

        goto Exit;
    }

    *ConfigDescriptor = fullConfigDesc;

Exit:

    return ntStatus;   
}

Führen Sie die folgenden Schritte aus, um den Konfigurationsdeskriptor durch Übermitteln einer URB abzurufen:

  1. Ordnen Sie eine URB zu, indem Sie die WdfUsbTargetDeviceCreateUrb-Methode aufrufen.
  2. Formatieren Sie die URB, indem Sie das Makro UsbBuildGetDescriptorRequest aufrufen. Der Übertragungspuffer der URB muss auf einen Puffer verweisen, der groß genug ist, um eine USB_CONFIGURATION_DESCRIPTOR Struktur aufzunehmen.
  3. Übermitteln Sie die URB als WDF-Anforderungsobjekt, indem Sie WdfRequestSend oder WdfUsbTargetDeviceSendUrbSynchronously aufrufen.
  4. Überprüfen Sie nach Abschluss der Anforderung das wTotalLength-Element von USB_CONFIGURATION_DESCRIPTOR. Dieser Wert gibt die Größe des Puffers an, der erforderlich ist, um einen vollständigen Konfigurationsdeskriptor zu enthalten.
  5. Ordnen Sie einen größeren Puffer basierend auf der in wTotalLength abgerufenen Größe zu.
  6. Stellen Sie dieselbe Anforderung mit dem größeren Puffer aus.

Der folgende Beispielcode zeigt den UsbBuildGetDescriptorRequest-Aufruf für eine Anforderung zum Abrufen von Konfigurationsinformationen für die i-ten Konfiguration:

NTSTATUS FX3_RetrieveConfigurationDescriptor (
    _In_ WDFUSBDEVICE  UsbDevice,
    _In_ PUCHAR ConfigurationIndex,
    _Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor 
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    USB_CONFIGURATION_DESCRIPTOR configDesc;
    PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;

    PURB urb = NULL;

    WDFMEMORY urbMemory = NULL;

    PAGED_CODE();

    RtlZeroMemory (&configDesc, sizeof(USB_CONFIGURATION_DESCRIPTOR));
    *ConfigDescriptor = NULL;

    // Allocate an URB for the get-descriptor request. 
    // WdfUsbTargetDeviceCreateUrb returns the address of the 
    // newly allocated URB and the WDFMemory object that 
    // contains the URB.

    ntStatus = WdfUsbTargetDeviceCreateUrb (
        UsbDevice,
        NULL,
        &urbMemory,
        &urb);

    if (!NT_SUCCESS (ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not allocate URB for an open-streams request.");

        goto Exit;
    }

       // Format the URB.
    UsbBuildGetDescriptorRequest (
        urb,                                                        // Points to the URB to be formatted
        (USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),  // Size of the URB.
        USB_CONFIGURATION_DESCRIPTOR_TYPE,                          // Type of descriptor
        *ConfigurationIndex,                                        // Index of the configuration
        0,                                                          // Not used for configuration descriptors
        &configDesc,                                                // Points to a USB_CONFIGURATION_DESCRIPTOR structure
        NULL,                                                       // Not required because we are providing a buffer not MDL
        sizeof(USB_CONFIGURATION_DESCRIPTOR),                       // Size of the USB_CONFIGURATION_DESCRIPTOR structure.
        NULL                                                        // Reserved.
        );

       // Send the request synchronously.
    ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
        UsbDevice,
        NULL,
        NULL,
        urb);

    if (configDesc.wTotalLength == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor size.");

        ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    // Allocate memory based on the retrieved size. 
       // The allocated memory is released by the caller.
    fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
        NonPagedPool, 
        configDesc.wTotalLength,
        USBCLIENT_TAG);

    RtlZeroMemory (fullConfigDesc, configDesc.wTotalLength);

    if (!fullConfigDesc)
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;
    }

       // Format the URB.
    UsbBuildGetDescriptorRequest (
        urb,                                                        
        (USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),  
        USB_CONFIGURATION_DESCRIPTOR_TYPE,                          
        *ConfigurationIndex,                                         
        0,                                                          
        fullConfigDesc,                                                 
        NULL,                                                       
        configDesc.wTotalLength,                       
        NULL                                                        
        );

       // Send the request again.
    ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
        UsbDevice,
        NULL,
        NULL,
        urb);

    if ((fullConfigDesc->wTotalLength == 0) || !NT_SUCCESS (ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor.");

        ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

       // Return to the caller.
    *ConfigDescriptor = fullConfigDesc;

Exit:

    if (urbMemory)
    {
        WdfObjectDelete (urbMemory);
    }

    return ntStatus;
}

Wenn das Gerät den Konfigurationsdeskriptor zurückgibt, wird der Anforderungspuffer mit Schnittstellendeskriptoren für alle alternativen Einstellungen und Endpunktdeskriptoren für alle Endpunkte innerhalb einer bestimmten alternativen Einstellung gefüllt. Für das unter USB-Gerätelayout beschriebene Gerät veranschaulicht das folgende Diagramm, wie Konfigurationsinformationen im Arbeitsspeicher angeordnet sind.

Diagramm eines Konfigurationsdeskriptorlayouts.

Das zero-basierte bInterfaceNumber-Element von USB_INTERFACE_DESCRIPTOR unterscheidet Schnittstellen innerhalb einer Konfiguration. Für eine bestimmte Schnittstelle unterscheidet das nullbasierte bAlternateSetting-Element zwischen alternativen Einstellungen der Schnittstelle. Das Gerät gibt Schnittstellendeskriptoren in der Reihenfolge der bInterfaceNumber-Werte und dann in der Reihenfolge der bAlternateSetting-Werte zurück.

Um innerhalb der Konfiguration nach einem bestimmten Schnittstellendeskriptor zu suchen, kann der Clienttreiber USBD_ParseConfigurationDescriptorEx aufrufen. Im Aufruf stellt der Clienttreiber eine Startposition innerhalb der Konfiguration bereit. Optional kann der Treiber eine Schnittstellennummer, eine alternative Einstellung, eine Klasse, eine Unterklasse oder ein Protokoll angeben. Die Routine gibt einen Zeiger auf den nächsten übereinstimmenden Schnittstellendeskriptor zurück.

Um einen Konfigurationsdeskriptor für einen Endpunkt oder Zeichenfolgendeskriptor zu untersuchen, verwenden Sie die USBD_ParseDescriptors Routine. Der Aufrufer stellt eine Startposition innerhalb der Konfiguration und einen Deskriptortyp bereit, z. B. USB_STRING_DESCRIPTOR_TYPE oder USB_ENDPOINT_DESCRIPTOR_TYPE. Die Routine gibt einen Zeiger auf den nächsten übereinstimmenden Deskriptor zurück.