Cómo seleccionar una configuración para un dispositivo USB
Para seleccionar una configuración para un dispositivo USB, el controlador cliente del dispositivo debe elegir al menos una de las configuraciones admitidas y especificar la configuración alternativa de cada interfaz que se va a usar. El controlador cliente empaqueta esas opciones en una solicitud select-configuration y la envía a la pila de controladores USB proporcionados por Microsoft, específicamente el controlador de bus USB (PDO del concentrador USB). El controlador de bus USB selecciona cada interfaz de la configuración especificada y configura un canal de comunicación, o canalización, en cada punto de conexión dentro de la interfaz. Una vez completada la solicitud, el controlador cliente recibe un identificador para la configuración seleccionada y los identificadores de canalización de los puntos de conexión definidos en la configuración alternativa activa para cada interfaz. A continuación, el controlador cliente puede usar los identificadores recibidos para cambiar las opciones de configuración y enviar solicitudes de lectura y escritura de E/S a un punto de conexión determinado.
Un controlador cliente envía una solicitud de configuración de selección en un bloque de solicitud USB (URB) del tipo URB_FUNCTION_SELECT_CONFIGURATION. En el procedimiento de este tema se describe cómo usar la rutina USBD_SelectConfigUrbAllocateAndBuild para compilar ese URB. La rutina asigna memoria para un URB, da formato al URB para una solicitud select-configuration y devuelve la dirección del URB al controlador cliente.
Como alternativa, puede asignar una estructura URB y, a continuación, dar formato al URB manualmente o llamando a la macro UsbBuildSelectConfigurationRequest.
Requisitos previos
A partir de Windows 8, USBD_SelectConfigUrbAllocateAndBuild reemplaza USBD_CreateConfigurationRequestEx.
Antes de enviar una solicitud select-configuration, debe tener un identificador USBD para el registro del controlador cliente con la pila de controladores USB. Para crear una llamada de identificador USBD USBD_CreateHandle.
Asegúrese de que ha obtenido el descriptor de configuración (estructura USB_CONFIGURATION_DESCRIPTOR) de la configuración que se va a seleccionar. Normalmente, envía un URB del tipo URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (consulte _URB_CONTROL_DESCRIPTOR_REQUEST) para recuperar información sobre la configuración del dispositivo. Para más información, consulte Descriptores de la configuración de USB.
Paso 1: Crear una matriz de estructuras USBD_INTERFACE_LIST_ENTRY
Obtenga el número de interfaces de la configuración. Esta información se incluye en el miembro bNumInterfaces de la estructura USB_CONFIGURATION_DESCRIPTOR.
Cree una matriz de estructuras USBD_INTERFACE_LIST_ENTRY. El número de elementos de la matriz debe ser uno más que el número de interfaces. Inicialice la matriz llamando a RtlZeroMemory.
El controlador cliente especifica una configuración alternativa en cada interfaz que se va a habilitar, en la matriz de estructuras USBD_INTERFACE_LIST_ENTRY.
- El miembro InterfaceDescriptor de cada estructura apunta al descriptor de interfaz que contiene la configuración alternativa.
- El miembro Interface de cada estructura apunta a una estructura USBD_INTERFACE_INFORMATION que contiene información de canalización en su miembro Pipes. Pipes almacena información sobre cada punto de conexión definido en la configuración alternativa.
Obtenga un descriptor de interfaz para cada interfaz (o su configuración alternativa) en la configuración. Puede obtener esos descriptores de interfaz llamando a USBD_ParseConfigurationDescriptorEx.
Acerca de los controladores de función para un dispositivo compuesto USB:
Si el dispositivo USB es un dispositivo compuesto, la configuración la selecciona el controlador primario genérico USB proporcionado por Microsoft (Usbccgp.sys). Un controlador cliente, que es uno de los controladores de función del dispositivo compuesto, no puede cambiar la configuración, pero el controlador puede enviar una solicitud select-configuration a través de Usbccgp.sys.
Antes de enviar esa solicitud, el controlador cliente debe enviar una solicitud URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE. En respuesta, Usbccgp.sys recupera un descriptor de configuración parcial que solo contiene descriptores de interfaz y otros descriptores que pertenecen a la función específica para la que se carga el controlador cliente. El número de interfaces notificadas en el campo bNumInterfaces de un descriptor de configuración parcial es menor que el número total de interfaces definidas para todo el dispositivo compuesto USB. Además, en un descriptor de configuración parcial, bInterfaceNumber de un descriptor de interfaz indica el número de interfaz real en relación con todo el dispositivo. Por ejemplo, Usbccgp.sys podría notificar un descriptor de configuración parcial con el valor bNumInterfaces de 2 y bInterfaceNumber de 4 para la primera interfaz. Tenga en cuenta que el número de interfaz es mayor que el número de interfaces notificadas.
Al enumerar interfaces en una configuración parcial, evite buscar interfaces mediante el cálculo de números de interfaz en función del número de interfaces. En el ejemplo anterior, si se llama a USBD_ParseConfigurationDescriptorEx en un bucle que comienza en cero, termina en
(bNumInterfaces - 1)
e incrementa el índice de interfaz (especificado en el parámetro InterfaceNumber ) en cada iteración, la rutina no obtiene la interfaz correcta. En su lugar, asegúrese de buscar todas las interfaces en el descriptor de configuración pasando -1 en InterfaceNumber. Para obtener más información sobre la implementación, consulte el ejemplo de código de esta sección.Para obtener información sobre cómo Usbccgp.sys controla una solicitud de configuración de selección enviada por un controlador cliente, consulte Configuración de Usbccgp.sys para seleccionar una configuración de USB no predeterminada.
Para cada elemento (excepto el último) de la matriz, establezca el miembro InterfaceDescriptor con la dirección de un descriptor de interfaz. Para el primer elemento de la matriz, establezca el miembro InterfaceDescriptor con la dirección del descriptor de interfaz que representa la primera interfaz de la configuración. De forma similar, para el enésimo elemento de la matriz, establezca el miembro InterfaceDescriptor con la dirección del descriptor de interfaz que representa la enésima interfaz de la configuración.
El miembro InterfaceDescriptor del último elemento debe establecerse en NULL.
Paso 2: Obtener un puntero a un URB asignado por la pila de controladores USB
A continuación, llame a USBD_SelectConfigUrbAllocateAndBuild especificando la configuración que se va a seleccionar y la matriz rellenada de estructuras USBD_INTERFACE_LIST_ENTRY. La rutina realiza las tareas siguientes:
Crea un URB y lo rellena con información sobre la configuración especificada, sus interfaces y puntos de conexión, y establece el tipo de solicitud en URB_FUNCTION_SELECT_CONFIGURATION.
Dentro de ese URB, asigna una estructura USBD_INTERFACE_INFORMATION para cada descriptor de interfaz que especifique el controlador cliente.
Establece el miembro Interface del enésimo elemento de la matriz USBD_INTERFACE_LIST_ENTRY proporcionada por el llamador con la dirección de la estructura USBD_INTERFACE_INFORMATION correspondiente en el URB.
Inicializa los miembros InterfaceNumber, AlternateSetting, NumberOfPipes, Pipes[i].MaximumTransferSize y Pipes[i].PipeFlags members.
Nota:
En Windows 7 y versiones anteriores, el controlador cliente creó un URB para una solicitud select-configuration llamando a USBD_CreateConfigurationRequestEx. En Windows 2000 USBD_CreateConfigurationRequestEx inicializa Pipes[i].MaximumTransferSize con el tamaño de transferencia máximo predeterminado para una única solicitud de lectura y escritura de URB. El controlador cliente puede especificar un tamaño de transferencia máximo diferente en pipes[i]. MaximumTransferSize. La pila USB omite este valor en Windows XP, Windows Server 2003 y versiones posteriores del sistema operativo. Para obtener más información sobre MaximumTransferSize, consulte "Establecimiento de tamaños de paquete y transferencia USB" en Asignación de ancho de banda USB.
Paso 3: Enviar el URB a la pila de controladores USB
Para enviar el URB a la pila de controladores USB, el controlador cliente debe enviar una solicitud de control de E/S IOCTL_INTERNAL_USB_SUBMIT_URB. Para obtener información sobre cómo enviar un URB, consulte Cómo enviar un URB.
Después de recibir el URB, la pila de controladores USB rellena el resto de los miembros de cada estructura USBD_INTERFACE_INFORMATION. En concreto, el miembro de matriz Pipes se rellena con información sobre las canalizaciones asociadas a los puntos de conexión de la interfaz.
Paso 4: Al finalizar la solicitud, inspeccione las estructuras USBD_INTERFACE_INFORMATION y el URB
Una vez que la pila de controladores USB completa el IRP para la solicitud, la pila devuelve la lista de configuraciones alternativas y las interfaces relacionadas de la matriz USBD_INTERFACE_LIST_ENTRY.
El miembro Pipes de cada estructura USBD_INTERFACE_INFORMATION apunta a una matriz de estructuras USBD_PIPE_INFORMATION que contiene información sobre las canalizaciones asociadas a cada punto de conexión de esa interfaz determinada. El controlador cliente puede obtener identificadores de canalización de Pipes[i]. PipeHandle y usarlos para enviar solicitudes de E/S a canalizaciones específicas. El miembro Pipes[i].PipeType especifica el tipo de punto de conexión y la transferencia admitidos por esa canalización.
Dentro del miembro UrbSelectConfiguration del URB, la pila de controladores USB devuelve un identificador que puede usar para seleccionar una configuración de interfaz alternativa enviando otro URB del tipo URB_FUNCTION_SELECT_INTERFACE (solicitud select-interface). Para asignar y compilar la estructura URB para esa solicitud, llame a USBD_SelectInterfaceUrbAllocateAndBuild.
Es posible que se produzca un error en la solicitud select-configuration y select-interface si no hay ancho de banda suficiente para admitir los puntos de conexión isocrónicos, de control e interrupción dentro de las interfaces habilitadas. En ese caso, el controlador de bus USB establece el miembro Status del encabezado URB en USBD_STATUS_NO_BANDWIDTH.
En el código de ejemplo siguiente se muestra cómo crear una matriz de estructuras USBD_INTERFACE_LIST_ENTRY y llamar a USBD_SelectConfigUrbAllocateAndBuild. En el ejemplo se envía la solicitud de forma sincrónica mediante una llamada a SubmitUrbSync. Para ver el ejemplo de código de SubmitUrbSync, consulte Cómo enviar un 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;
}
Deshabilitación de una configuración para un dispositivo USB
Para deshabilitar un dispositivo USB, cree y envíe una solicitud select-configuration con un descriptor de configuración NULL. Para ese tipo de solicitud, puede reutilizar el URB que creó para la solicitud que seleccionó una configuración en el dispositivo. Como alternativa, puede asignar un nuevo URB llamando a USBD_UrbAllocate. Antes de enviar la solicitud, debe dar formato al URB mediante la macro UsbBuildSelectConfigurationRequest, como se muestra en el código de ejemplo siguiente.
URB Urb;
UsbBuildSelectConfigurationRequest(
&Urb,
sizeof(_URB_SELECT_CONFIGURATION),
NULL
);