So wählen Sie eine Konfiguration für ein USB-Gerät aus
Um eine Konfiguration für ein USB-Gerät auszuwählen, muss der Client-Treiber für das Gerät mindestens eine der unterstützten Konfigurationen auswählen und die alternativen Einstellungen für jede zu verwendende Schnittstelle angeben. Der Client-Treiber packt diese Auswahl in eine select-configuration-Anforderung und sendet die Anforderung an den von Microsoft bereitgestellten USB-Treiber-Stack, insbesondere den USB-Bustreiber (USB Hub PDO). Der USB-Bustreiber wählt jede Schnittstelle in der angegebenen Konfiguration aus und richtet für jeden Endpunkt innerhalb der Schnittstelle einen Kommunikationskanal oder eine Pipe ein. Nach Abschluss der Anfrage erhält der Client-Treiber ein Handle für die ausgewählte Konfiguration und Pipe-Handles für die Endpunkte, die in der aktiven alternativen Einstellung für jede Schnittstelle definiert sind. Der Client-Treiber kann dann die empfangenen Handles verwenden, um die Konfigurationseinstellungen zu ändern und um E/A-Lese- und Schreibanfragen an einen bestimmten Endpunkt zu senden.
Ein Client-Treiber sendet eine Select-Configuration-Anforderung in einem USB Request Block (URB) des Typs URB_FUNCTION_SELECT_CONFIGURATION. Die Prozedur in diesem Thema beschreibt, wie Sie die Routine USBD_SelectConfigUrbAllocateAndBuild verwenden, um diesen URB zu erstellen. Die Routine weist Speicher für einen URB zu, formatiert den URB für eine Select-Configuration-Anfrage und gibt die Adresse des URBs an den Client-Treiber zurück.
Alternativ können Sie eine URB-Struktur zuweisen und den URB dann manuell oder durch Aufruf des Makros UsbBuildSelectConfigurationRequest formatieren.
Voraussetzungen
Ab Windows 8 ersetzt USBD_SelectConfigUrbAllocateAndBuildUSBD_CreateConfigurationRequestEx.
Bevor Sie eine Select-Configuration-Anfrage senden, müssen Sie ein USBD-Handle für die Registrierung Ihres Client-Treibers beim USB-Treiber-Stack haben. Um ein USBD-Handle zu erstellen, rufen Sie USBD_CreateHandle auf.
Stellen Sie sicher, dass Sie den Konfigurationsdeskriptor (USB_CONFIGURATION_DESCRIPTOR-Struktur) der auszuwählenden Konfiguration erhalten haben. Normalerweise senden Sie ein URB vom Typ URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (siehe _URB_CONTROL_DESCRIPTOR_REQUEST), um Informationen über die Gerätekonfiguration abzurufen. Weitere Informationen finden Sie unter USB-Konfigurationsdeskriptoren.
Schritt 1: Erstellen Sie ein Array von USBD_INTERFACE_LIST_ENTRY-Strukturen
Ermitteln Sie die Anzahl der Schnittstellen in der Konfiguration. Diese Information ist in dem bNumInterfaces-Mitglied der USB_CONFIGURATION_DESCRIPTOR-Struktur enthalten.
Erstellen Sie ein Array von USBD_INTERFACE_LIST_ENTRY-Strukturen. Die Anzahl der Elemente in dem Array muss um eins höher sein als die Anzahl der Schnittstellen. Initialisieren Sie das Array durch den Aufruf von RtlZeroMemory.
Der Client-Treiber legt in dem Array der USBD_INTERFACE_LIST_ENTRY-Strukturen alternative Einstellungen für jede zu aktivierende Schnittstelle fest.
- Der InterfaceDescriptor-Mitglied jeder Struktur zeigt auf den Schnittstellendeskriptor, der die alternative Einstellung enthält.
- Das Interface-Mitglied jeder Struktur verweist auf eine USBD_INTERFACE_INFORMATION-Struktur, die in ihrem Pipes-Mitglied Pipe-Informationen enthält. Pipes speichert Informationen über jeden Endpunkt, der in der alternativen Einstellung definiert ist.
Holen Sie sich einen Schnittstellendeskriptor für jede Schnittstelle (oder ihre alternative Einstellung) in der Konfiguration. Sie erhalten diese Schnittstellendeskriptoren durch den Aufruf von USBD_ParseConfigurationDescriptorEx.
Zu den Funktionstreibern für ein USB-Composite-Gerät:
Wenn es sich bei dem USB-Gerät um ein Composite-Gerät handelt, wird die Konfiguration von dem von Microsoft zur Verfügung gestellten USB Generic Parent Driver (Usbccgp.sys) ausgewählt. Ein Client-Treiber, der einer der Funktionstreiber des Composite-Geräts ist, kann die Konfiguration nicht ändern, aber der Treiber kann dennoch eine Select-Configuration-Anfrage über Usbccgp.sys senden.
Bevor er diese Anfrage sendet, muss der Client-Treiber eine URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE-Anforderung senden. 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 Client-Treiber geladen ist. Die Anzahl der Schnittstellen, die im Feld bNumInterfaces eines Teilkonfigurationsdeskriptors angegeben wird, ist geringer als die Gesamtzahl der für das gesamte USB-Composite-Gerät definierten Schnittstellen. Darüber hinaus gibt in einem Schnittstellendeskriptor bInterfaceNumber die tatsächliche Schnittstellennummer bezogen auf das gesamte Gerät an. Zum Beispiel könnte Usbccgp.sys einen Schnittstellendeskriptor mit bNumInterfaces-Wert von 2 und bInterfaceNumber-Wert 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 Teilkonfiguration die Suche nach Schnittstellen, indem Sie die Schnittstellennummern anhand der Anzahl der Schnittstellen berechnen. Wenn im vorangegangenen Beispiel USBD_ParseConfigurationDescriptorEx in einer Schleife aufgerufen wird, die bei Null beginnt, bei
(bNumInterfaces - 1)
endet und den Schnittstellenindex (angegeben im Parameter InterfaceNumber) in jeder Iteration erhöht, gelingt es der Routine nicht, die richtige Schnittstelle zu finden. Stellen Sie stattdessen sicher, dass Sie nach allen Schnittstellen im Konfigurationsdeskriptor suchen, indem Sie -1 in InterfaceNumber übergeben. Einzelheiten zur Implementierung finden Sie in dem Code-Beispiel in diesem Abschnitt.Informationen darüber, wie Usbccgp.sys eine von einem Client-Treiber gesendete Select-Configuration-Anfrage behandelt, finden Sie unter Konfiguration von Usbccgp.sys zur Auswahl einer nicht standardmäßigen USB-Konfiguration.
Legen Sie für jedes Element (außer dem letzten Element) im Array das Mitglied InterfaceDescriptor auf die Adresse eines Schnittstellendeskriptors fest. Für das erste Element im Array legen Sie das Element InterfaceDescriptor auf die Adresse des Schnittstellendeskriptors fest, der die erste Schnittstelle in der Konfiguration darstellt. In ähnlicher Weise legen Sie für das n-te Element im Array das InterfaceDescriptor-Mitglied auf die Adresse des Schnittstellendeskriptors fest, der die n-te Schnittstelle in der Konfiguration darstellt.
Das Element InterfaceDescriptor des letzten Elements muss auf NULL festgelegt werden.
Schritt 2: Holen Sie sich einen Zeiger auf einen vom USB-Treiber-Stack zugewiesenes URB
Als nächstes rufen Sie USBD_SelectConfigUrbAllocateAndBuild auf, indem Sie die auszuwählende Konfiguration und das gefüllte Array von USBD_INTERFACE_LIST_ENTRY-Strukturen angeben. Die Routine führt die folgenden Aufgaben aus:
Sie erstellt ein URB und füllt es mit Informationen über die angegebene Konfiguration, ihre Schnittstellen und Endpunkte und legt den Anfragetyp auf URB_FUNCTION_SELECT_CONFIGURATION fest.
Ordnet innerhalb dieses URB eine USBD_INTERFACE_INFORMATION-Struktur für jeden Schnittstellendeskriptor zu, den der Client-Treiber angibt.
Setzt das Interface-Mitglied des n-ten Elements des vom Aufrufer bereitgestellten USBD_INTERFACE_LIST_ENTRY-Arrays auf die Adresse der entsprechenden USBD_INTERFACE_INFORMATION-Struktur im URB.
Initialisiert die Mitglieder InterfaceNumber, AlternateSetting, NumberOfPipes, Pipes[i].MaximumTransferSize und Pipes[i].PipeFlags.
Hinweis
In Windows 7 und früher erstellte der Client-Treiber einen URB für eine Select-Configuration-Anfrage durch den Aufruf von USBD_CreateConfigurationRequestEx. In Windows 2000 initialisiert USBD_CreateConfigurationRequestExPipes[i].MaximumTransferSize auf die standardmäßige maximale Übertragungsgröße für eine einzelne URB-Lese-/Schreibanforderung. Der Client-Treiber kann eine andere maximale Übertragungsgröße in Pipes[i].MaximumTransferSize angeben. Der USB-Stack ignoriert diesen Wert in Windows XP, Windows Server 2003 und späteren Versionen des Betriebssystems. Weitere Informationen über MaximumTransferSize finden Sie unter „Festlegen von USB-Übertragungs- und Paketgrößen“ in USB-Bandbreitenzuweisung.
Schritt 3: Senden Sie den URB an den USB-Treiber-Stack
Um die URB an den USB-Treiber-Stack zu senden, muss der Client-Treiber eine IOCTL_INTERNAL_USB_SUBMIT_URB E/A-Steuerungsanfrage senden. Informationen zum Senden eines URB finden Sie unter Wie man einen URB sendet.
Nach dem Empfang des URB füllt der USB-Treiber-Stack den Rest der Mitglieder jeder USBD_INTERFACE_INFORMATION-Struktur. Insbesondere das Array Pipes wird mit Informationen über die Pipes gefüllt, die mit den Endpunkten der Schnittstelle verbunden sind.
Schritt 4: Prüfen Sie nach Abschluss der Anfrage die USBD_INTERFACE_INFORMATION-Strukturen und den URB
Nachdem der USB-Treiber Stack den IRP für die Anfrage abgeschlossen hat, gibt der Stack die Liste der alternativen Einstellungen und der zugehörigen Schnittstellen im Array USBD_INTERFACE_LIST_ENTRY zurück.
Das Pipes-Mitglied jeder USBD_INTERFACE_INFORMATION-Struktur verweist auf ein Array von USBD_PIPE_INFORMATION-Strukturen, das Informationen über die Pipes enthält, die mit jedem Endpunkt der jeweiligen Schnittstelle verbunden sind. Der Client-Treiber kann Pipe-Handles von Pipes[i].PipeHandle erhalten und diese verwenden, um E/A-Anfragen an bestimmte Pipes zu senden. Das Mitglied Pipes[i].PipeType gibt den Typ des Endpunkts und der Übertragung an, der von dieser Pipe unterstützt wird.
Im UrbSelectConfiguration-Mitglied des URB gibt der USB-Treiber Stack ein Handle zurück, mit dem Sie eine alternative Schnittstelleneinstellung auswählen können, indem Sie ein weiteres URB vom Typ URB_FUNCTION_SELECT_INTERFACE (Select-Interface-Anfrage) senden. Um die URB-Struktur für diese Anfrage zuzuweisen und zu erstellen, rufen Sie USBD_SelectInterfaceUrbAllocateAndBuild auf.
Die Select-Configuration-Anfrage und die Select-Interface-Anfrage können fehlschlagen, wenn die Bandbreite nicht ausreicht, um die isochronen, Kontroll- und Interrupt-Endpunkte innerhalb der aktivierten Schnittstellen zu unterstützen. In diesem Fall legt der USB-Bustreiber das Status-Mitglied des URB-Headers auf USBD_STATUS_NO_BANDWIDTH fest.
Der folgende Beispiel Code zeigt, wie Sie ein Array von USBD_INTERFACE_LIST_ENTRY-Strukturen erstellen und USBD_SelectConfigUrbAllocateAndBuild aufrufen. Das Beispiel sendet die Anfrage synchron durch den Aufruf von SubmitUrbSync. Ein Code-Beispiel für SubmitUrbSync finden Sie unter Wie man einen URB sendet.
/*++
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 senden Sie eine Select-Configuration-Anfrage mit einem NULL-Konfigurationsdeskriptor. Für diese Art von Anfrage können Sie den URB wiederverwenden, den Sie für eine Anfrage erstellt haben, die eine Konfiguration im Gerät ausgewählt hat. Alternativ können Sie einen neuen URB zuweisen, indem Sie USBD_UrbAllocate aufrufen. Bevor Sie die Anfrage senden, müssen Sie die URB formatieren, indem Sie das Makro UsbBuildSelectConfigurationRequest verwenden, wie im folgenden Beispielcode gezeigt.
URB Urb;
UsbBuildSelectConfigurationRequest(
&Urb,
sizeof(_URB_SELECT_CONFIGURATION),
NULL
);