Descrittori di configurazione USB
Un dispositivo USB espone le sue funzionalità sotto forma di una serie di interfacce denominate configurazione USB. Ogni interfaccia è costituita da una o più impostazioni alternative e ogni impostazione alternativa è costituita da un set di endpoint. Questo argomento descrive i vari descrittori associati a una configurazione USB.
Una configurazione USB è descritta in un descrittore di configurazione (vedere USB_CONFIGURATION_DESCRIPTOR struttura). Un descrittore di configurazione contiene informazioni sulla configurazione e sulle relative interfacce, impostazioni alternative e gli endpoint. Ogni descrittore di interfaccia o impostazione alternativa è descritto in una struttura USB_INTERFACE_DESCRIPTOR . In una configurazione, ogni descrittore di interfaccia viene seguito in memoria da tutti i descrittori dell'endpoint per l'interfaccia e l'impostazione alternativa. Ogni descrittore dell'endpoint viene archiviato in una struttura di USB_ENDPOINT_DESCRIPTOR .
Si consideri ad esempio un dispositivo webcam USB descritto in Layout dispositivo USB. Il dispositivo supporta una configurazione con due interfacce e la prima interfaccia (indice 0) supporta due impostazioni alternative.
Nell'esempio seguente viene illustrato il descrittore di configurazione per il dispositivo webcam USB:
Configuration Descriptor:
wTotalLength: 0x02CA
bNumInterfaces: 0x02
bConfigurationValue: 0x01
iConfiguration: 0x00
bmAttributes: 0x80 (Bus Powered )
MaxPower: 0xFA (500 mA)
Il campo bConfigurationValue indica il numero per la configurazione definita nel firmware del dispositivo. Il driver client usa il valore del numero per selezionare una configurazione attiva. Per altre informazioni sulla configurazione del dispositivo USB, vedere Come selezionare una configurazione per un dispositivo USB. Una configurazione USB indica anche determinate caratteristiche di alimentazione. Il bmAttributes contiene una maschera di bit che indica se la configurazione supporta la funzionalità di riattivazione remota e se il dispositivo è alimentato dal bus o con alimentazione autonoma. Il campo MaxPower specifica la potenza massima (in unità milliamp) che il dispositivo può disegnare dall'host, quando il dispositivo è alimentato dal bus. Il descrittore di configurazione indica anche il numero totale di interfacce (bNumInterfaces) supportate dal dispositivo.
Nell'esempio seguente viene illustrato il descrittore dell'interfaccia per l'impostazione alternativa 0 dell'interfaccia 0 per il 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"
Nell'esempio precedente si notino i valori di campo bInterfaceNumber e bAlternateSetting . Questi campi contengono valori di indice usati da un driver client per attivare l'interfaccia e una delle relative impostazioni alternative. Per l'attivazione, il driver invia una richiesta di interfaccia selezionata allo stack di driver USB. Lo stack di driver compila quindi una richiesta di controllo standard (SET INTERFACE) e la invia al dispositivo. Si noti il campo bInterfaceClass . Il descrittore dell'interfaccia o il descrittore per una delle relative impostazioni alternative specifica un codice di classe, una sottoclasse e un protocollo. Il valore di 0x0E indica che l'interfaccia è per la classe di dispositivo video. Si noti anche il campo iInterface . Questo valore indica che sono presenti due descrittori di stringa aggiunti al descrittore dell'interfaccia. I descrittori di stringa contengono descrizioni Unicode usate durante l'enumerazione del dispositivo per identificare la funzionalità. Per altre informazioni sui descrittori di stringa, vedere Descrittori di stringhe USB.
Ogni endpoint, in un'interfaccia, descrive un singolo flusso di input o output per il dispositivo. Un dispositivo che supporta i flussi per diversi tipi di funzioni include più interfacce. Un dispositivo che supporta diversi flussi relativi a una funzione può supportare più endpoint in una singola interfaccia.
Tutti i tipi di endpoint (ad eccezione dell'endpoint predefinito) devono fornire descrittori endpoint in modo che l'host possa ottenere informazioni sull'endpoint. Un descrittore dell'endpoint include informazioni, ad esempio l'indirizzo, il tipo, la direzione e la quantità di dati che l'endpoint può gestire. I trasferimenti di dati all'endpoint si basano su tali informazioni.
Nell'esempio seguente viene illustrato un descrittore dell'endpoint per il dispositivo webcam:
Endpoint Descriptor:
bEndpointAddress: 0x82 IN
bmAttributes: 0x01
wMaxPacketSize: 0x0080 (128)
bInterval: 0x01
Il campo bEndpointAddress specifica l'indirizzo dell'endpoint univoco contenente il numero di endpoint (Bits 3..0) e la direzione dell'endpoint (Bit 7). Leggendo questi valori nell'esempio precedente, è possibile determinare che il descrittore descrive un endpoint IN il cui numero di endpoint è 2. L'attributo bmAttributes indica che il tipo di endpoint è isochronous. WMaxPacketSizefield indica il numero massimo di byte che l'endpoint può inviare o ricevere in una singola transazione. I bit 12..11 indicano il numero totale di transazioni che possono essere inviate per microframe. BInterval indica la frequenza con cui l'endpoint può inviare o ricevere dati.
Come ottenere il descrittore di configurazione
Il descrittore di configurazione viene ottenuto dal dispositivo tramite una richiesta di dispositivo standard (GET_DESCRIPTOR), che viene inviata come trasferimento di controllo dallo stack di driver USB. Un driver client USB può avviare la richiesta in uno dei modi seguenti:
Se il dispositivo supporta una sola configurazione, il modo più semplice consiste nel chiamare il metodo WdfUsbTargetDeviceRetrieveConfigDescriptor fornito dal framework.
Per un dispositivo che supporta più configurazioni, se il driver client vuole ottenere il descrittore della configurazione diverso dal primo, il driver deve inviare un DRIVER. Per inviare un'istanza DI, il driver deve allocare, formattare e quindi inviare l'ISTANZA allo stack di driver USB.
Per allocare IL driver client, il driver client deve chiamare il metodo WdfUsbTargetDeviceCreateUrb . Il metodo riceve un puntatore a un OGGETTO URB allocato dallo stack di driver USB.
Per formattare IL driver client, il driver client può usare la macro UsbBuildGetDescriptorRequest . La macro imposta tutte le informazioni necessarie nell'AREA, ad esempio il numero di configurazione definito dal dispositivo per cui recuperare il descrittore. La funzione URB è impostata su URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (vedere _URB_CONTROL_DESCRIPTOR_REQUEST) e il tipo di descrittore è impostato su USB_CONFIGURATION_DESCRIPTOR_TYPE. Usando le informazioni contenute nell'ISTANZA, lo stack di driver USB compila una richiesta di controllo standard e la invia al dispositivo.
Per inviare l'ISTANZA, il driver client deve usare un oggetto richiesta WDF. Per inviare l'oggetto request allo stack di driver USB in modo asincrono, il driver deve chiamare il metodo **WdfRequestSend**. Per inviarlo in modo sincrono, chiamare il metodo WdfUsbTargetDeviceSendUrbSynchronously .
Driver WDM: Un driver client WDM (Windows Driver Model) può ottenere solo il descrittore di configurazione inviando UN'istanza DI. Per allocare IL DRIVER, il driver deve chiamare la routine di USBD_UrbAllocate . Per formattare IL DRIVER, il driver deve chiamare la macro UsbBuildGetDescriptorRequest . Per inviare IL DRIVER, il driver deve associare l'URB a un'IRP e inviare l'IRP allo stack di driver USB. Per altre informazioni, vedere Come inviare UN'istanza DI.
All'interno di una configurazione USB, il numero di interfacce e le relative impostazioni alternative sono variabili. Pertanto, è difficile stimare le dimensioni del buffer necessarie per contenere il descrittore di configurazione. Il driver client deve raccogliere tutte le informazioni in due passaggi. Prima di tutto, determinare il buffer delle dimensioni necessarie per contenere tutti i descrittori di configurazione e quindi inviare una richiesta per recuperare l'intero descrittore. Un driver client può ottenere le dimensioni in uno dei modi seguenti:
Per ottenere il descrittore di configurazione chiamando WdfUsbTargetDeviceRetrieveConfigDescriptor, seguire questa procedura:
- Ottenere le dimensioni del buffer necessario per contenere tutte le informazioni di configurazione chiamando WdfUsbTargetDeviceRetrieveConfigDescriptor. Il driver deve passare NULL nel buffer e una variabile per contenere le dimensioni del buffer.
- Allocare un buffer più grande in base alle dimensioni ricevute tramite la chiamata precedente WdfUsbTargetDeviceRetrieveConfigDescriptor .
- Chiamare di nuovo WdfUsbTargetDeviceRetrieveConfigDescriptor e specificare un puntatore al nuovo buffer allocato nel passaggio 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;
}
Per ottenere il descrittore di configurazione tramite l'invio di UN VALORE DI, seguire questa procedura:
- Allocare un OGGETTO URB chiamando il metodo WdfUsbTargetDeviceCreateUrb .
- Formattare l'URB chiamando la macro UsbBuildGetDescriptorRequest . Il buffer di trasferimento dell'URB deve puntare a un buffer abbastanza grande per contenere una struttura USB_CONFIGURATION_DESCRIPTOR .
- Inviare l'istanza DI COME oggetto richiesta WDF chiamando WdfRequestSend o WdfUsbTargetDeviceSendUrbSynchronously.
- Al termine della richiesta, controllare il membro wTotalLength di USB_CONFIGURATION_DESCRIPTOR. Tale valore indica la dimensione del buffer necessaria per contenere un descrittore di configurazione completo.
- Allocare un buffer più grande in base alle dimensioni recuperate in wTotalLength.
- Emettere la stessa richiesta con il buffer più grande.
Il codice di esempio seguente mostra la chiamata UsbBuildGetDescriptorRequest per ottenere informazioni di configurazione per la configurazione 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;
}
Quando il dispositivo restituisce il descrittore di configurazione, il buffer della richiesta viene riempito con descrittori di interfaccia per tutte le impostazioni alternative e descrittori dell'endpoint per tutti gli endpoint all'interno di un'impostazione alternativa specifica. Per il dispositivo descritto in Layout dispositivo USB, il diagramma seguente illustra come vengono disposte le informazioni di configurazione in memoria.
Il membro bInterfaceNumber basato su zero di USB_INTERFACE_DESCRIPTOR distingue le interfacce all'interno di una configurazione. Per un'interfaccia specificata, il membro bAlternateSetting basato su zero distingue tra le impostazioni alternative dell'interfaccia. Il dispositivo restituisce i descrittori dell'interfaccia in ordine di valori bInterfaceNumber e quindi in ordine di valori bAlternateSetting .
Per cercare un descrittore di interfaccia specificato all'interno della configurazione, il driver client può chiamare USBD_ParseConfigurationDescriptorEx. Nella chiamata, il driver client fornisce una posizione iniziale all'interno della configurazione. Facoltativamente, il driver può specificare un numero di interfaccia, un'impostazione alternativa, una classe, una sottoclasse o un protocollo. La routine restituisce un puntatore al descrittore di interfaccia corrispondente successivo.
Per esaminare un descrittore di configurazione per un endpoint o un descrittore di stringa, usare la routine USBD_ParseDescriptors . Il chiamante fornisce una posizione iniziale all'interno della configurazione e un tipo descrittore, ad esempio USB_STRING_DESCRIPTOR_TYPE o USB_ENDPOINT_DESCRIPTOR_TYPE. La routine restituisce un puntatore al descrittore corrispondente successivo.