Auswählen einer Konfiguration für ein USB-Gerät

Um eine Konfiguration für ein USB-Gerät auszuwählen, muss der Clienttreiber für das Gerät mindestens eine der unterstützten Konfigurationen auswählen und die alternativen Einstellungen jeder zu verwendenden Schnittstelle angeben. Der Clienttreiber packt diese Optionen in einer Select-Configuration-Anforderung und sendet die Anforderung an den von Microsoft bereitgestellten USB-Treiberstapel, insbesondere an den USB-Bustreiber (USB-Hub-PDO). Der USB-Bustreiber wählt jede Schnittstelle in der angegebenen Konfiguration aus und richtet einen Kommunikationskanal oder eine Pipe zu jedem Endpunkt innerhalb der Schnittstelle ein. Nach Abschluss der Anforderung erhält der Clienttreiber ein Handle für die ausgewählte Konfiguration und Pipehandles für die Endpunkte, die in der aktiven alternativen Einstellung für jede Schnittstelle definiert sind. Der Clienttreiber kann dann die empfangenen Handles verwenden, um Konfigurationseinstellungen zu ändern und E/A-Lese- und Schreibanforderungen an einen bestimmten Endpunkt zu senden.

Ein Clienttreiber sendet eine Select-Configuration-Anforderung in einem USB-Anforderungsblock (URB) des Typs URB_FUNCTION_SELECT_CONFIGURATION. Die Prozedur in diesem Thema beschreibt, wie Sie die USBD_SelectConfigUrbAllocateAndBuild Routine verwenden, um diese URB zu erstellen. Die Routine weist Arbeitsspeicher für eine URB zu, formatiert die URB für eine Select-Configuration-Anforderung und gibt die Adresse der URB an den Clienttreiber zurück.

Alternativ können Sie eine URB-Struktur zuordnen und dann die URB manuell oder durch Aufrufen des Makros UsbBuildSelectConfigurationRequest formatieren.

Voraussetzungen

Schritt 1: Erstellen eines Arrays von USBD_INTERFACE_LIST_ENTRY Strukturen

  1. Rufen Sie die Anzahl der Schnittstellen in der Konfiguration ab. Diese Informationen sind im bNumInterfaces-Member der USB_CONFIGURATION_DESCRIPTOR-Struktur enthalten.

  2. Erstellen Sie ein Array von USBD_INTERFACE_LIST_ENTRY Strukturen. Die Anzahl der Elemente im Array muss eins mehr als die Anzahl der Schnittstellen sein. Initialisieren Sie das Array, indem Sie RtlZeroMemory aufrufen.

    Der Clienttreiber gibt alternative Einstellungen in jeder zu aktivierenden Schnittstelle im Array von USBD_INTERFACE_LIST_ENTRY-Strukturen an.

    • Das InterfaceDescriptor-Element jeder Struktur verweist auf den Schnittstellendeskriptor, der die alternative Einstellung enthält.
    • Das Interface-Element jeder Struktur verweist auf eine USBD_INTERFACE_INFORMATION-Struktur , die Pipeinformationen in ihrem Pipes-Element enthält. Pipes speichert Informationen zu jedem Endpunkt, der in der alternativen Einstellung definiert ist.
  3. Rufen Sie einen Schnittstellendeskriptor für jede Schnittstelle (oder ihre alternative Einstellung) in der Konfiguration ab. Sie können diese Schnittstellendeskriptoren abrufen, indem Sie USBD_ParseConfigurationDescriptorEx aufrufen.

    Informationen zu Funktionstreibern für ein zusammengesetztes USB-Gerät:

    Wenn es sich bei dem USB-Gerät um ein zusammengesetztes Gerät handelt, wird die Konfiguration vom von Microsoft bereitgestellten generischen übergeordneten USB-Treiber (Usbccgp.sys) ausgewählt. Ein Clienttreiber, der einer der Funktionstreiber des zusammengesetzten Geräts ist, kann die Konfiguration nicht ändern, aber der Treiber kann trotzdem eine Select-Configuration-Anforderung über Usbccgp.sys senden.

    Vor dem Senden dieser Anforderung muss der Clienttreiber eine URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE Anforderung übermitteln. Als Antwort ruft Usbccgp.sys einen teilweisen Konfigurationsdeskriptor ab, der nur Schnittstellendeskriptoren und andere Deskriptoren enthält, die sich auf die spezifische Funktion beziehen, für die der Clienttreiber geladen wird. Die Anzahl der Schnittstellen, die im Feld bNumInterfaces eines partiellen Konfigurationsdeskriptors gemeldet werden, ist kleiner als die Gesamtzahl der Schnittstellen, die für das gesamte zusammengesetzte USB-Gerät definiert sind. Darüber hinaus gibt in einem partiellen Konfigurationsdeskriptor die bInterfaceNumber eines Schnittstellendeskriptors die tatsächliche Schnittstellennummer relativ zum gesamten Gerät an. Beispielsweise können Usbccgp.sys einen partiellen Konfigurationsdeskriptor mit dem Wert bNumInterfaces von 2 und dem Wert bInterfaceNumber von 4 für die erste Schnittstelle melden. Beachten Sie, dass die Schnittstellennummer größer ist als die Anzahl der gemeldeten Schnittstellen.

    Vermeiden Sie beim Auflisten von Schnittstellen in einer partiellen Konfiguration die Suche nach Schnittstellen, indem Sie Schnittstellennummern basierend auf der Anzahl der Schnittstellen berechnen. Wenn im vorherigen Beispiel USBD_ParseConfigurationDescriptorEx in einer Schleife aufgerufen wird, die bei null beginnt, auf endet und in (bNumInterfaces - 1)jeder Iteration den Schnittstellenindex (im Parameter InterfaceNumber angegeben) erhöht, kann die Routine nicht die richtige Schnittstelle abrufen. Stellen Sie stattdessen sicher, dass Sie nach allen Schnittstellen im Konfigurationsdeskriptor suchen, indem Sie -1 in InterfaceNumber übergeben. Implementierungsdetails finden Sie im Codebeispiel in diesem Abschnitt.

    Informationen dazu, wie Usbccgp.sys eine Select-Configuration-Anforderung verarbeitet, die von einem Clienttreiber gesendet wird, finden Sie unter Konfigurieren von Usbccgp.sys zum Auswählen einer nicht standardmäßigen USB-Konfiguration.

  4. Legen Sie für jedes Element (mit Ausnahme des letzten Elements) im Array den Member InterfaceDescriptor auf die Adresse eines Schnittstellendeskriptors fest. Legen Sie für das erste Element im Array den Member InterfaceDescriptor auf die Adresse des Schnittstellendeskriptors fest, der die erste Schnittstelle in der Konfiguration darstellt. Legen Sie für das nth-Element im Array den InterfaceDescriptor-Member auf die Adresse des Schnittstellendeskriptors fest, der die n th-Schnittstellein der Konfiguration darstellt.

  5. Das InterfaceDescriptor-Element des letzten Elements muss auf NULL festgelegt werden.

Schritt 2: Abrufen eines Zeigers auf eine VOM USB-Treiberstapel zugeordnete URB

Rufen Sie als Nächstes USBD_SelectConfigUrbAllocateAndBuild auf, indem Sie die auszuwählende Konfiguration und das aufgefüllte Array USBD_INTERFACE_LIST_ENTRY Strukturen angeben. Die Routine führt die folgenden Aufgaben aus:

  • Erstellt eine URB, füllt sie mit Informationen zur angegebenen Konfiguration, ihren Schnittstellen und Endpunkten und legt den Anforderungstyp auf URB_FUNCTION_SELECT_CONFIGURATION fest.

  • Weist innerhalb dieser URB eine USBD_INTERFACE_INFORMATION-Struktur für jeden Schnittstellendeskriptor zu, den der Clienttreiber angibt.

  • Legt das Interface-Element des nth-Elements des vom Aufrufer bereitgestellten USBD_INTERFACE_LIST_ENTRY-Arrays auf die Adresse der entsprechenden USBD_INTERFACE_INFORMATION-Struktur in der URB fest.

  • Initialisiert die Schnittstellennummer, AlternateSetting, NumberOfPipes, Pipes[i]. MaximumTransferSize und Pipes[i]. PipeFlags-Elemente .

    Hinweis

    In Windows 7 und ealier hat der Clienttreiber eine URB für eine Select-Configuration-Anforderung erstellt, indem er USBD_CreateConfigurationRequestEx aufruft. In Windows 2000 USBD_CreateConfigurationRequestExPipes[i] initialisiert. MaximumTransferSize auf die standardmäßige maximale Übertragungsgröße für eine einzelne URB-Lese-/Schreibanforderung. Der Clienttreiber kann eine andere maximale Übertragungsgröße in den Pipes[i] angeben. MaximumTransferSize. Der USB-Stapel ignoriert diesen Wert in Windows XP, Windows Server 2003 und höheren Versionen des Betriebssystems. Weitere Informationen zu MaximumTransferSize finden Sie unter Festlegen von USB-Übertragungs- und Paketgrößen unter USB-Bandbreitenzuordnung.

Schritt 3: Übermitteln der URB an den USB-Treiberstapel

Um die URB an den USB-Treiberstapel zu übermitteln, muss der Clienttreiber eine IOCTL_INTERNAL_USB_SUBMIT_URB E/A-Steuerungsanforderung senden. Informationen zum Übermitteln einer URB finden Sie unter Übermitteln einer URB.

Nach dem Empfang der URB füllt der USB-Treiberstapel die restlichen Member jeder USBD_INTERFACE_INFORMATION-Struktur aus. Insbesondere wird das Pipes-Arrayelement mit Informationen zu den Pipes gefüllt, die den Endpunkten der Schnittstelle zugeordnet sind.

Schritt 4: Überprüfen Sie bei Abschluss der Anforderung die USBD_INTERFACE_INFORMATION Strukturen und die URB

Nachdem der USB-Treiberstapel die IRP für die Anforderung abgeschlossen hat, gibt der Stapel die Liste der alternativen Einstellungen und die zugehörigen Schnittstellen im USBD_INTERFACE_LIST_ENTRY Array zurück.

  1. Das Pipes-Element jeder USBD_INTERFACE_INFORMATION-Struktur verweist auf ein Array von USBD_PIPE_INFORMATION Strukturen, das Informationen zu den Pipes enthält, die jedem Endpunkt dieser bestimmten Schnittstelle zugeordnet sind. Der Clienttreiber kann Pipehandles aus Pipes[i] abrufen. PipeHandle und verwenden Sie sie zum Senden von E/A-Anforderungen an bestimmte Rohre. Die Pipes[i]. PipeType-Member gibt den Typ des Endpunkts und der Übertragung an, die von dieser Pipe unterstützt werden.

  2. Innerhalb des UrbSelectConfiguration-Members der URB gibt der USB-Treiberstapel ein Handle zurück, mit dem Sie eine alternative Schnittstelleneinstellung auswählen können, indem Sie eine weitere URB vom Typ URB_FUNCTION_SELECT_INTERFACE (Select-Interface Request) übermitteln. Um die URB-Struktur für diese Anforderung zuzuordnen und zu erstellen, rufen Sie USBD_SelectInterfaceUrbAllocateAndBuild auf.

    Die Select-Configuration-Anforderung und die Select-Interface-Anforderung schlagen möglicherweise fehl, wenn die Bandbreite nicht ausreicht, um die isochronen, Steuerungs- und Unterbrechungsendpunkte innerhalb der aktivierten Schnittstellen zu unterstützen. In diesem Fall legt der USB-Bustreiber das Statuselement des URB-Headers auf USBD_STATUS_NO_BANDWIDTH fest.

Der folgende Beispielcode zeigt, wie Sie ein Array von USBD_INTERFACE_LIST_ENTRY Strukturen erstellen und USBD_SelectConfigUrbAllocateAndBuild aufrufen. Im Beispiel wird die Anforderung synchron gesendet, indem SubmitUrbSync aufgerufen wird. Das Codebeispiel für SubmitUrbSync finden Sie unter Übermitteln einer URB.

/*++

Routine Description:
This helper routine selects the specified configuration.

Arguments:
USBDHandle - USBD handle that is retrieved by the 
client driver in a previous call to the USBD_CreateHandle routine.

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 SelectConfiguration (PDEVICE_OBJECT DeviceObject,
                              PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor)
{
    PDEVICE_EXTENSION deviceExtension;
    PIO_STACK_LOCATION nextStack;
    PIRP irp;
    PURB urb = NULL;

    KEVENT    kEvent;
    NTSTATUS ntStatus;    

    PUSBD_INTERFACE_LIST_ENTRY   interfaceList = NULL; 
    PUSB_INTERFACE_DESCRIPTOR    interfaceDescriptor = NULL;
    PUSBD_INTERFACE_INFORMATION  Interface = NULL;
    USBD_PIPE_HANDLE             pipeHandle;

    ULONG                        interfaceIndex;

    PUCHAR StartPosition = (PUCHAR)ConfigurationDescriptor;

    deviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    // Allocate an array for the list of interfaces
    // The number of elements must be one more than number of interfaces.
    interfaceList = (PUSBD_INTERFACE_LIST_ENTRY)ExAllocatePool (
        NonPagedPool, 
        sizeof(USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    if(!interfaceList)
    {
        //Failed to allocate memory
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    // Initialize the array by setting all members to NULL.
    RtlZeroMemory (interfaceList, sizeof (
        USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

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

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

        // Set the interface entry
        interfaceList[interfaceIndex].InterfaceDescriptor = interfaceDescriptor;
        interfaceList[interfaceIndex].Interface = NULL;

        // Move the position to the next interface descriptor
        StartPosition = (PUCHAR)interfaceDescriptor + interfaceDescriptor->bLength;

    }

    // Make sure that the InterfaceDescriptor member of the last element to NULL.
    interfaceList[deviceExtension->NumInterfaces].InterfaceDescriptor = NULL;

    // Allocate and build an URB for the select-configuration request.
    ntStatus = USBD_SelectConfigUrbAllocateAndBuild(
        deviceExtension->UsbdHandle, 
        ConfigurationDescriptor, 
        interfaceList,
        &urb);

    if(!NT_SUCCESS(ntStatus)) 
    {
        goto Exit;
    }

    // Allocate the IRP to send the buffer down the USB stack.
    // The IRP will be freed by IO manager.
    irp = IoAllocateIrp((deviceExtension->NextDeviceObject->StackSize)+1, TRUE);  

    if (!irp)
    {
        //Irp could not be allocated.
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    ntStatus = SubmitUrbSync( 
        deviceExtension->NextDeviceObject, 
        irp, 
        urb, 
        CompletionRoutine);

    // Enumerate the pipes in the interface information array, which is now filled with pipe
    // information.

    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        ULONG i;

        Interface = interfaceList[interfaceIndex].Interface;

        for(i=0; i < Interface->NumberOfPipes; i++) 
        {
            pipeHandle = Interface->Pipes[i].PipeHandle;

            if (Interface->Pipes[i].PipeType == UsbdPipeTypeInterrupt)
            {
                deviceExtension->InterruptPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkInPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkOutPipe = pipeHandle;
            }
        }
    }

Exit:

    if(interfaceList) 
    {
        ExFreePool(interfaceList);
        interfaceList = NULL;
    }

    if (urb)
    {
        USBD_UrbFree( deviceExtension->UsbdHandle, urb); 
    }

    return ntStatus;
}

NTSTATUS CompletionRoutine ( PDEVICE_OBJECT DeviceObject,
                            PIRP           Irp,
                            PVOID          Context)
{
    PKEVENT kevent;

    kevent = (PKEVENT) Context;

    if (Irp->PendingReturned == TRUE)
    {
        KeSetEvent(kevent, IO_NO_INCREMENT, FALSE);
    }

    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Select-configuration request completed. \n" ));

    return STATUS_MORE_PROCESSING_REQUIRED;
}

Deaktivieren einer Konfiguration für ein USB-Gerät

Um ein USB-Gerät zu deaktivieren, erstellen und übermitteln Sie eine Select-Configuration-Anforderung mit einem NULL-Konfigurationsdeskriptor. Für diesen Anforderungstyp können Sie die URB wiederverwenden, die Sie für die Anforderung erstellt haben, die eine Konfiguration im Gerät ausgewählt hat. Alternativ können Sie eine neue URB zuordnen, indem Sie USBD_UrbAllocate aufrufen. Vor dem Übermitteln der Anforderung müssen Sie die URB mithilfe des Makros UsbBuildSelectConfigurationRequest formatieren, wie im folgenden Beispielcode gezeigt.

URB Urb;
UsbBuildSelectConfigurationRequest(
  &Urb,
  sizeof(_URB_SELECT_CONFIGURATION),
  NULL
);