Freigeben über


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 Reihe von Endpunkten. In diesem Thema werden die verschiedenen Deskriptoren beschrieben, die einer USB-Konfiguration zugeordnet sind.

Eine USB-Konfiguration wird in einer Konfigurationsbeschreibung beschrieben (siehe USB_CONFIGURATION_DESCRIPTOR Struktur). Ein Konfigurationsdeskriptor enthält Informationen über die Konfiguration und die zugehörigen Schnittstellen, alternative Einstellungen und deren Endpunkte. Jede Schnittstellenbeschreibung oder alternative Einstellung wird in einer USB_INTERFACE_DESCRIPTOR Struktur beschrieben. In einer Konfiguration folgt im Arbeitsspeicher jedem Schnittstellendeskriptor alle Endpunktdeskriptoren für die Schnittstelle und die alternativen Einstellungen. Jeder Endpunktdeskriptor wird in einer USB_ENDPOINT_DESCRIPTOR Struktur gespeichert.

Betrachten Sie z. B. ein USB-Webcamgerät, das im USB-Gerätelayout beschrieben ist. 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. Für weitere Informationen zur Konfiguration von USB-Geräten siehe Wie man eine Konfiguration für ein USB-Gerät auswählt. Eine USB-Konfiguration gibt auch bestimmte Leistungsmerkmale an. Die bmAttributes enthält eine Bitmaske, die angibt, ob die Konfiguration das Remote-Wake-Up-Feature unterstützt und ob das Gerät busbetrieben oder selbstbetrieben ist. Das Feld "MaxPower " gibt die maximale Leistung (in Milliampeinheiten) an, die das Gerät vom Host zeichnen kann, wenn das Gerät busbetrieben ist. Der Konfigurationsdeskriptor gibt auch die Gesamtanzahl der 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 Werte von bInterfaceNumber und bAlternateSetting. Diese Felder enthalten Indexwerte, die ein Clienttreiber zum Aktivieren der Schnittstelle und einer seiner alternativen Einstellungen verwendet. Für die Aktivierung sendet der Treiber eine Auswahlschnittstellenanforderung an den USB-Treiberstapel. Der Treiberstapel erstellt dann eine Standardsteuerelementanforderung (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 sich die Schnittstelle für die Videogeräteklasse befindet. Beachten Sie außerdem das iInterface-Feld . Dieser Wert gibt an, dass an den Schnittstellendeskriptor zwei Zeichenfolgendeskriptoren angefügt sind. 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 Eingabe- oder Ausgabedatenstrom 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 einzigen Schnittstelle unterstützen.

Alle Endpunkttypen (mit Ausnahme des Standardendpunkts) müssen Endpunktdeskriptoren bereitstellen, damit der Host Informationen über den Endpunkt abrufen kann. Ein Endpunktdeskriptor enthält Informationen, z. B. die Adresse, den Typ, die 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 bestimmen, 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 geben die Gesamtzahl der Transaktionen an, die pro Mikroframe gesendet werden können. Der bInterval gibt an, wie oft der Endpunkt Daten senden oder empfangen kann.

So erhalten Sie die Konfigurationsbeschreibung

Der Konfigurationsdeskriptor wird über eine Standardgeräteanforderung (GET_DESCRIPTOR) vom Gerät 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.

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

    Um die URB zuzuweisen, muss der Clienttreiber die WdfUsbTargetDeviceCreateUrb-Methode aufrufen. Die Methode empfängt einen Zeiger auf einen URB, der vom USB-Treiberstapel zugewiesen wurde.

    Zum Formatieren der URB kann der Clienttreiber das UsbBuildGetDescriptorRequest-Makro verwenden. Das Makro legt alle erforderlichen Informationen in der URB fest, z. B. die vom Gerät definierte 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 wird auf USB_CONFIGURATION_DESCRIPTOR_TYPE festgelegt. Durch die Verwendung 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. Rufen Sie die Methode WdfUsbTargetDeviceSendUrbSynchronously auf, um ihn synchron zu senden.

    WDM-Treiber: Ein Windows Driver Model (WDM)-Clienttreiber kann nur den Konfigurationsdeskriptor abrufen, indem er eine URB übermittelt. Um die URB zuzuweisen, muss der Treiber die USBD_UrbAllocate Routine aufrufen. Zum Formatieren der URB muss der Treiber das UsbBuildGetDescriptorRequest-Makro aufrufen. Um die URB zu übermitteln, muss der Treiber die URB einem IRP zuordnen und das IRP an den USB-Treiberstapel übermitteln. Weitere Informationen finden Sie unter How to Submit an URB.For more information, see How to Submit an 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, der zum Halten des Konfigurationsdeskriptors erforderlich ist. Der Clienttreiber muss alle diese Informationen in zwei Schritten sammeln. Ermitteln Sie zunächst die Puffergröße, die erforderlich ist, um den gesamten Konfigurationsdeskriptor aufzunehmen, und senden Sie dann eine Anforderung, um den gesamten Deskriptor abzurufen. 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 die Größe des Puffers ab, der zum Halten aller Konfigurationsinformationen erforderlich ist, indem Sie WdfUsbTargetDeviceRetrieveConfigDescriptor aufrufen. Der Treiber muss NULL als Puffer übergeben und eine Variable übergeben, die die Größe des Puffers speichert.
  2. Weisen 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 ist.
 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 eines URB abzurufen:

  1. Weisen Sie eine URB zu, indem Sie die WdfUsbTargetDeviceCreateUrb-Methode aufrufen.
  2. Formatieren Sie die URB durch Aufrufen des UsbBuildGetDescriptorRequest-Makros . Der Übertragungspuffer des URB muss auf einen Puffer verweisen, der groß genug ist, um eine USB_CONFIGURATION_DESCRIPTOR Struktur zu enthalten.
  3. Übermitteln Sie die URB als WDF-Anforderungsobjekt, indem Sie WdfRequestSend oder WdfUsbTargetDeviceSendUrbSynchronously aufrufen.
  4. Nach Abschluss der Anforderung überprüfen Sie das Mitglied wTotalLength von USB_CONFIGURATION_DESCRIPTOR. Dieser Wert gibt die Größe des Puffers an, der erforderlich ist, um einen vollständigen Konfigurationsdeskriptor zu enthalten.
  5. Weisen 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-th-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 die Konfigurationsbeschreibung 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 im USB-Gerätelayout beschriebene Gerät veranschaulicht das folgende Diagramm, wie Konfigurationsinformationen im Arbeitsspeicher angeordnet sind.

Diagramm eines Konfigurationsdeskriptorlayouts.

Das nullbasierte bInterfaceNumber-Element von USB_INTERFACE_DESCRIPTOR unterscheidet Schnittstellen innerhalb einer Konfiguration. Bei einer bestimmten 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.

Verwenden Sie die USBD_ParseDescriptors Routine, um einen Konfigurationsdeskriptor für einen Endpunkt oder einen Zeichenfolgendeskriptor zu untersuchen. 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.