Descriptores de configuración USB
Un dispositivo USB expone sus capacidades en forma de una serie de interfaces denominadas configuración USB. Cada interfaz consta de una o varias configuraciones alternativas, y cada configuración alternativa se compone de un conjunto de puntos de conexión. En este tema se describen los distintos descriptores asociados a una configuración USB.
Una configuración USB se describe en un descriptor de configuración (consulte USB_CONFIGURATION_DESCRIPTOR estructura). Un descriptor de configuración contiene información sobre la configuración y sus interfaces, opciones alternativas y sus puntos de conexión. Cada descriptor de interfaz o configuración alternativa se describe en una estructura de USB_INTERFACE_DESCRIPTOR . En una configuración, todos los descriptores de interfaz siguen en memoria todos los descriptores de punto de conexión para la interfaz y la configuración alternativa. Cada descriptor de punto de conexión se almacena en una estructura USB_ENDPOINT_DESCRIPTOR .
Por ejemplo, considere un dispositivo web USB que se describe en Diseño de dispositivo USB. El dispositivo admite una configuración con dos interfaces y la primera interfaz (índice 0) admite dos configuraciones alternativas.
En el ejemplo siguiente se muestra el descriptor de configuración del dispositivo cámara web USB:
Configuration Descriptor:
wTotalLength: 0x02CA
bNumInterfaces: 0x02
bConfigurationValue: 0x01
iConfiguration: 0x00
bmAttributes: 0x80 (Bus Powered )
MaxPower: 0xFA (500 mA)
El campo bConfigurationValue indica el número de la configuración definida en el firmware del dispositivo. El controlador cliente usa ese valor numérico para seleccionar una configuración activa. Para obtener más información sobre la configuración del dispositivo USB, vea Cómo seleccionar una configuración para un dispositivo USB. Una configuración USB también indica ciertas características de energía. BmAttributes contiene una máscara de bits que indica si la configuración admite la característica de reactivación remota y si el dispositivo está alimentado por bus o autopropulsado. El campo MaxPower especifica la potencia máxima (en unidades de miliamp) que el dispositivo puede extraer del host, cuando el dispositivo está alimentado por bus. El descriptor de configuración también indica el número total de interfaces (bNumInterfaces) que admite el dispositivo.
En el ejemplo siguiente se muestra el descriptor de interfaz para la configuración alternativa 0 de interfaz 0 para el dispositivo webcam:
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"
En el ejemplo anterior, tenga en cuenta los valores de campo bInterfaceNumber y bAlternateSetting . Estos campos contienen valores de índice que usa un controlador de cliente para activar la interfaz y una de sus opciones alternativas. Para la activación, el controlador envía una solicitud select-interface a la pila de controladores USB. A continuación, la pila de controladores crea una solicitud de control estándar (SET INTERFACE) y la envía al dispositivo. Anote el campo bInterfaceClass . El descriptor de interfaz o el descriptor de cualquiera de sus valores alternativos especifican un código de clase, una subclase y un protocolo. El valor de 0x0E indica que la interfaz es para la clase de dispositivo de vídeo. Además, observe el campo iInterface . Ese valor indica que hay dos descriptores de cadena anexados al descriptor de interfaz. Los descriptores de cadena contienen descripciones Unicode que se usan durante la enumeración del dispositivo para identificar la funcionalidad. Para obtener más información sobre los descriptores de cadena, consulte Descriptores de cadena USB.
Cada punto de conexión, en una interfaz, describe un único flujo de entrada o salida para el dispositivo. Un dispositivo que admite secuencias para diferentes tipos de funciones tiene varias interfaces. Un dispositivo que admite varios flujos que pertenecen a una función puede admitir varios puntos de conexión en una sola interfaz.
Todos los tipos de puntos de conexión (excepto el punto de conexión predeterminado) deben proporcionar descriptores de punto de conexión para que el host pueda obtener información sobre el punto de conexión. Un descriptor de punto de conexión incluye información, como su dirección, tipo, dirección y la cantidad de datos que puede controlar el punto de conexión. Las transferencias de datos al punto de conexión se basan en esa información.
En el ejemplo siguiente se muestra un descriptor de punto de conexión para el dispositivo webcam:
Endpoint Descriptor:
bEndpointAddress: 0x82 IN
bmAttributes: 0x01
wMaxPacketSize: 0x0080 (128)
bInterval: 0x01
El campo bEndpointAddress especifica la dirección del punto de conexión único que contiene el número de punto de conexión (Bits 3..0) y la dirección del punto de conexión (bit 7). Al leer esos valores en el ejemplo anterior, podemos determinar que el descriptor describe un punto de conexión IN cuyo número de punto de conexión es 2. El atributo bmAttributes indica que el tipo de extremo es isócrono. wMaxPacketSizefield indica el número máximo de bytes que el punto de conexión puede enviar o recibir en una sola transacción. Los bits 12..11 indican el número total de transacciones que se pueden enviar por microframe. bInterval indica la frecuencia con la que el punto de conexión puede enviar o recibir datos.
Obtención del descriptor de configuración
El descriptor de configuración se obtiene del dispositivo a través de una solicitud de dispositivo estándar (GET_DESCRIPTOR), que se envía como transferencia de control mediante la pila de controladores USB. Un controlador de cliente USB puede iniciar la solicitud de una de las siguientes maneras:
Si el dispositivo solo admite una configuración, la manera más fácil es llamar al método WdfUsbTargetDeviceRetrieveConfigDescriptor proporcionado por el marco.
En el caso de un dispositivo que admita varias configuraciones, si el controlador cliente quiere obtener el descriptor de la configuración que no sea la primera, el controlador debe enviar un URB. Para enviar un URB, el controlador debe asignar, dar formato y, a continuación, enviar el URB a la pila del controlador USB.
Para asignar el URB, el controlador cliente debe llamar al método WdfUsbTargetDeviceCreateUrb . El método recibe un puntero a un URB asignado por la pila de controladores USB.
Para dar formato al URB, el controlador cliente puede usar la macro UsbBuildGetDescriptorRequest . La macro establece toda la información necesaria en el URB, como el número de configuración definido por el dispositivo para el que se va a recuperar el descriptor. La función URB se establece en URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (vea _URB_CONTROL_DESCRIPTOR_REQUEST) y el tipo de descriptor se establece en USB_CONFIGURATION_DESCRIPTOR_TYPE. Mediante el uso de la información contenida en el URB, la pila del controlador USB crea una solicitud de control estándar y la envía al dispositivo.
Para enviar el URB, el controlador cliente debe usar un objeto de solicitud WDF. Para enviar el objeto de solicitud a la pila de controladores USB de forma asincrónica, el controlador debe llamar al método **WdfRequestSend**. Para enviarlo de forma sincrónica, llame al método WdfUsbTargetDeviceSendUrbSynchronously .
Controladores WDM: Un controlador cliente del modelo de controlador de Windows (WDM) solo puede obtener el descriptor de configuración enviando un URB. Para asignar el URB, el controlador debe llamar a la rutina USBD_UrbAllocate . Para dar formato al URB, el controlador debe llamar a la macro UsbBuildGetDescriptorRequest . Para enviar el URB, el controlador debe asociar el URB a un IRP y enviar el IRP a la pila del controlador USB. Para obtener más información, vea Cómo enviar un URB.
Dentro de una configuración USB, el número de interfaces y sus opciones alternativas son variables. Por lo tanto, es difícil predecir el tamaño del búfer necesario para contener el descriptor de configuración. El controlador cliente debe recopilar toda esa información en dos pasos. En primer lugar, determine el búfer de tamaño necesario para contener todo el descriptor de configuración y, a continuación, emita una solicitud para recuperar todo el descriptor. Un controlador de cliente puede obtener el tamaño de una de las maneras siguientes:
Para obtener el descriptor de configuración mediante una llamada a WdfUsbTargetDeviceRetrieveConfigDescriptor, realice estos pasos:
- Obtenga el tamaño del búfer necesario para contener toda la información de configuración mediante una llamada a WdfUsbTargetDeviceRetrieveConfigDescriptor. El controlador debe pasar NULL en el búfer y una variable para contener el tamaño del búfer.
- Asigne un búfer mayor en función del tamaño recibido a través de la llamada anterior a WdfUsbTargetDeviceRetrieveConfigDescriptor .
- Vuelva a llamar a WdfUsbTargetDeviceRetrieveConfigDescriptor y especifique un puntero al nuevo búfer asignado en el paso 2.
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;
}
Para obtener el descriptor de configuración mediante el envío de un URB, realice estos pasos:
- Asigne un URB llamando al método WdfUsbTargetDeviceCreateUrb .
- Para dar formato al URB, llame a la macro UsbBuildGetDescriptorRequest . El búfer de transferencia del URB debe apuntar a un búfer lo suficientemente grande como para contener una estructura de USB_CONFIGURATION_DESCRIPTOR .
- Envíe el URB como un objeto de solicitud WDF llamando a WdfRequestSend o WdfUsbTargetDeviceSendUrbSynchronously.
- Una vez completada la solicitud, compruebe el miembro wTotalLength de USB_CONFIGURATION_DESCRIPTOR. Ese valor indica el tamaño del búfer necesario para contener un descriptor de configuración completo.
- Asigne un búfer mayor según el tamaño recuperado en wTotalLength.
- Emita la misma solicitud con el búfer mayor.
En el código de ejemplo siguiente se muestra la llamada UsbBuildGetDescriptorRequest para obtener información de configuración para la configuración i-th:
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;
}
Cuando el dispositivo devuelve el descriptor de configuración, el búfer de solicitudes se rellena con descriptores de interfaz para todas las opciones alternativas y descriptores de punto de conexión para todos los puntos de conexión dentro de una configuración alternativa determinada. En el caso del dispositivo descrito en Diseño de dispositivo USB, en el diagrama siguiente se muestra cómo se diseña la información de configuración en la memoria.
El miembro bInterfaceNumber de base cero de USB_INTERFACE_DESCRIPTOR distingue interfaces dentro de una configuración. Para una interfaz determinada, el miembro bAlternateSetting de base cero distingue entre la configuración alternativa de la interfaz. El dispositivo devuelve descriptores de interfaz en orden de los valores bInterfaceNumber y, a continuación, en orden de los valores bAlternateSetting .
Para buscar un descriptor de interfaz determinado dentro de la configuración, el controlador cliente puede llamar a USBD_ParseConfigurationDescriptorEx. En la llamada, el controlador cliente proporciona una posición inicial dentro de la configuración. Opcionalmente, el controlador puede especificar un número de interfaz, una configuración alternativa, una clase, una subclase o un protocolo. La rutina devuelve un puntero al siguiente descriptor de interfaz coincidente.
Para examinar un descriptor de configuración para un punto de conexión o descriptor de cadena, use la rutina USBD_ParseDescriptors . El llamador proporciona una posición inicial dentro de la configuración y un tipo de descriptor, como USB_STRING_DESCRIPTOR_TYPE o USB_ENDPOINT_DESCRIPTOR_TYPE. La rutina devuelve un puntero al siguiente descriptor coincidente.