Come aprire e chiudere flussi statici in un endpoint bulk USB
Questo articolo illustra la funzionalità dei flussi statici e spiega come un driver client USB può aprire e chiudere flussi in un endpoint bulk di un dispositivo USB 3.0.
Nei dispositivi USB 2.0 e versioni precedenti, un endpoint in blocco può inviare o ricevere un singolo flusso di dati tramite l'endpoint. Nei dispositivi USB 3.0 gli endpoint in blocco hanno la possibilità di inviare e ricevere più flussi di dati tramite l'endpoint.
Lo stack di driver USB fornito da Microsoft in Windows supporta più flussi. In questo modo un driver client può inviare richieste di I/O indipendenti a ogni flusso associato a un endpoint bulk in un dispositivo USB 3.0. Le richieste a flussi diversi non vengono serializzate.
Per un driver client, i flussi rappresentano più endpoint logici con lo stesso set di caratteristiche. Per inviare una richiesta a un flusso specifico, il driver client necessita di un handle per tale flusso (simile a un handle di pipe per un endpoint). L'oggetto SSH per una richiesta di I/O a un flusso è simile a quello di UNA richiesta di I/O a un endpoint bulk. L'unica differenza è l'handle di pipe. Per inviare una richiesta di I/O a un flusso, il driver specifica l'handle di pipe al flusso.
Durante la configurazione del dispositivo, il driver client invia una richiesta di configurazione select e, facoltativamente, una richiesta di interfaccia select. Queste richieste recuperano un set di handle pipe agli endpoint definiti nell'impostazione attiva di un'interfaccia. Per un endpoint che supporta i flussi, è possibile usare l'handle della pipe dell'endpoint per inviare richieste di I/O al flusso predefinito (il primo flusso) fino a quando il driver non ha aperto i flussi (descritti di seguito).
Se il driver client vuole inviare richieste a flussi diversi dal flusso predefinito, il driver deve aprire e ottenere handle per tutti i flussi. A tale scopo, il driver client invia una richiesta open-streams specificando il numero di flussi da aprire. Al termine dell'uso dei flussi, il driver può facoltativamente chiuderli inviando una richiesta close-streams.
Kernel Mode Driver Framework (KMDF) non supporta intrinsecamente i flussi statici. Il driver client deve inviare URL di stile WINDOWS Driver Model (WDM) che aprono e chiudono i flussi. Questo articolo descrive come formattare e inviare tali URL. Un driver client UMDF (User Mode Driver Framework) non può usare la funzionalità flussi statici.
L'articolo contiene alcune note etichettate come driver WDM. Queste note descrivono le routine per un driver client USB basato su WDM che vuole inviare richieste di flusso.
Prerequisiti
Prima che un driver client possa aprire o chiudere flussi, il driver deve avere:
Chiamato il metodo WdfUsbTargetDeviceCreateWithParameters .
Il metodo richiede che la versione del contratto client sia USBD_CLIENT_CONTRACT_VERSION_602. Specificando tale versione, il driver client deve rispettare un set di regole. Per altre informazioni, vedere Procedure consigliate: Uso di URL.
La chiamata recupera un handle WDFUSBDEVICE all'oggetto dispositivo di destinazione USB del framework. Tale handle è necessario per effettuare chiamate successive ai flussi aperti. In genere, il driver client si registra nella routine di callback dell'evento di EVT_WDF_DEVICE_PREPARE_HARDWARE del driver.
Driver WDM: Chiamare la routine USBD_CreateHandle e ottenere un handle USBD per la registrazione del driver con lo stack di driver USB.
Configurare il dispositivo e ottenere un handle pipe WDFUSBPIPE all'endpoint bulk che supporta i flussi. Per ottenere l'handle pipe, chiamare il metodo WdfUsbInterfaceGetConfiguredPipe sull'impostazione alternativa corrente di un'interfaccia nella configurazione selezionata.
Driver WDM: Ottenere un handle di pipe USBD inviando una richiesta select-configuration o select-interface. Per altre informazioni, vedere Come selezionare una configurazione per un dispositivo USB.
Come aprire flussi statici
Determinare se lo stack di driver USB sottostante e il controller host supportano la funzionalità flussi statici chiamando il metodo WdfUsbTargetDeviceQueryUsbCapability . In genere, il driver client chiama la routine nella routine di callback degli eventi EVT_WDF_DEVICE_PREPARE_HARDWARE del driver.
Driver WDM: Chiamare la routine USBD_QueryUsbCapability . In genere, il driver esegue una query sulle funzionalità che vuole usare nella routine del dispositivo di avvio del driver (IRP_MN_START_DEVICE). Per un esempio di codice, vedere USBD_QueryUsbCapability.
Specificare le informazioni seguenti:
Handle per l'oggetto dispositivo USB recuperato, in una precedente chiamata a WdfUsbTargetDeviceCreateWithParameters, per la registrazione del driver client.
Driver WDM: Passare l'handle USBD recuperato nella chiamata precedente a USBD_CreateHandle.
Se il driver client vuole usare una particolare funzionalità, il driver deve prima eseguire una query sullo stack di driver USB sottostante per determinare se lo stack di driver e il controller host supportano la funzionalità. Se la funzionalità è supportata, solo allora il driver deve inviare una richiesta per usare la funzionalità. Alcune richieste richiedono URL, ad esempio la funzionalità flussi (descritta nel passaggio 5). Per tali richieste, assicurarsi di usare lo stesso handle per eseguire query sulle funzionalità e allocare GLI URL. Ciò è dovuto al fatto che lo stack di driver usa handle per tenere traccia delle funzionalità supportate che un driver può usare.
Ad esempio, se è stato ottenuto un USBD_HANDLE (chiamando USBD_CreateHandle), eseguire una query sullo stack di driver chiamando USBD_QueryUsbCapability e allocare l'oggetto ODBC chiamando USBD_UrbAllocate. Passare lo stesso USBD_HANDLE in entrambe le chiamate.
Se si chiamano i metodi KMDF, WdfUsbTargetDeviceQueryUsbCapability e WdfUsbTargetDeviceCreateUrb, specificare lo stesso handle WDFUSBDEVICE per l'oggetto di destinazione del framework in tali chiamate al metodo.
GUID assegnato a GUID_USB_CAPABILITY_STATIC_STREAMS.
Buffer di output (puntatore a USHORT). Al termine, il buffer viene riempito con il numero massimo di flussi (per endpoint) supportati dal controller host.
Lunghezza, in byte, del buffer di output. Per i flussi, la lunghezza è
sizeof (USHORT)
.
Valutare il valore NTSTATUS restituito. Se la routine viene completata correttamente, restituisce STATUS_SUCCESS, la funzionalità flussi statici è supportata. In caso contrario, il metodo restituisce un codice di errore appropriato.
Determinare il numero di flussi da aprire. Il numero massimo di flussi che è possibile aprire è limitato da:
- Numero massimo di flussi supportati dal controller host. Tale numero viene ricevuto da WdfUsbTargetDeviceQueryUsbCapability (per i driver WDM, USBD_QueryUsbCapability), nel buffer di output fornito dal chiamante. Lo stack di driver USB fornito da Microsoft supporta fino a 255 flussi. WdfUsbTargetDeviceQueryUsbCapability prende in considerazione tale limitazione durante il calcolo del numero di flussi. Il metodo non restituisce mai un valore maggiore di 255.
- Numero massimo di flussi supportati dall'endpoint nel dispositivo. Per ottenere tale numero, esaminare il descrittore complementare dell'endpoint (vedere USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR in Usbspec.h). Per ottenere il descrittore complementare dell'endpoint, è necessario analizzare il descrittore di configurazione. Per ottenere il descrittore di configurazione, il driver client deve chiamare il metodo WdfUsbTargetDeviceRetrieveConfigDescriptor . È necessario utilizzare le routine helper, USBD_ParseConfigurationDescriptorEx e USBD_ParseDescriptor. Per un esempio di codice, vedere la funzione di esempio denominata RetrieveStreamInfoFromEndpointDesc in Come enumerare pipe USB.
Per determinare il numero massimo di flussi, scegliere il minore di due valori supportati dal controller host e dall'endpoint.
Allocare una matrice di strutture USBD_STREAM_INFORMATION con n elementi, dove n è il numero di flussi da aprire. Il driver client è responsabile del rilascio di questa matrice al termine dell'uso dei flussi da parte del driver.
Allocare un'istanza DI PER la richiesta open-streams chiamando il metodo WdfUsbTargetDeviceCreateUrb . Se la chiamata viene completata correttamente, il metodo recupera un oggetto di memoria WDF e l'indirizzo della struttura METHOD allocata dallo stack di driver USB.
Driver WDM: Chiamare la routine USBD_UrbAllocate .
Formattare l'oggetto XAML per la richiesta open-stream. L'UTILITÀ UTILIZZA la struttura _URB_OPEN_STATIC_STREAMS per definire la richiesta. Per formattare l'elemento TYPE, è necessario:
- Handle della pipe USBD all'endpoint. Se si dispone di un oggetto pipe WDF, è possibile ottenere l'handle di pipe USBD chiamando il metodo WdfUsbTargetPipeWdmGetPipeHandle .
- Matrice di flusso (creata nel passaggio 4)
- Puntatore alla struttura DELL'OGGETTO ( creato nel passaggio 5).
Per formattare l'oggetto COMMAND, chiamare UsbBuildOpenStaticStreamsRequest e passare le informazioni necessarie come valori dei parametri. Assicurarsi che il numero di flussi specificati in UsbBuildOpenStaticStreamsRequest non superi il numero massimo di flussi supportati.
Inviare IL FILE COME oggetto richiesta WDF chiamando il metodo WdfRequestSend . Per inviare la richiesta in modo sincrono, chiamare invece il metodo WdfUsbTargetDeviceSendUrbSynchronously .
Driver WDM: Associare l'oggetto ODBC a un IRP e inviare l'IRP allo stack di driver USB. Per altre informazioni, vedere How to Submit an ENUMERAT.
Al termine della richiesta, controllare lo stato della richiesta.
Se lo stack di driver USB non riesce, lo stato DELL'ISTRUZIONE CONTIENE il codice di errore pertinente. Alcune condizioni di errore comuni sono descritte nella sezione Osservazioni.
Se lo stato della richiesta (IRP o l'oggetto richiesta WDF) indica USBD_STATUS_SUCCESS, la richiesta è stata completata correttamente. Esaminare la matrice di strutture USBD_STREAM_INFORMATION ricevute al completamento. La matrice viene compilata con informazioni sui flussi richiesti. Lo stack di driver USB popola ogni struttura nella matrice con informazioni sul flusso, ad esempio handle (ricevuti come USBD_PIPE_HANDLE), identificatori del flusso e dimensioni massime del trasferimento dei numeri. I flussi sono ora aperti per trasferire i dati.
Per una richiesta open-streams, è necessario allocare un oggetto DASH e una matrice. Il driver client deve rilasciare l'oggetto ODBC chiamando il metodo WdfObjectDelete nell'oggetto di memoria WDF associato, dopo il completamento della richiesta di flussi aperti. Se il driver ha inviato la richiesta in modo sincrono chiamando WdfUsbTargetDeviceSendUrbSynchronously, deve rilasciare l'oggetto memoria WDF, dopo il completamento del metodo. Se il driver client ha inviato la richiesta in modo asincrono chiamando WdfRequestSend, il driver deve rilasciare l'oggetto memoria WDF nella routine di completamento implementata dal driver associata alla richiesta.
La matrice di flusso può essere rilasciata al termine dell'uso del driver client usando flussi o le ha archiviate per le richieste di I/O. Nell'esempio di codice incluso in questo articolo, il driver archivia la matrice dei flussi nel contesto del dispositivo. Il driver rilascia il contesto di dispositivo appena prima di rilasciare l'oggetto dispositivo viene rimosso.
Come trasferire dati a un flusso specifico
Per inviare una richiesta di trasferimento dati a un flusso specifico, è necessario un oggetto richiesta WDF. In genere, il driver client non è necessario per allocare un oggetto richiesta WDF. Quando gestione I/O riceve una richiesta da un'applicazione, Gestione I/O crea un IRP per la richiesta. L'IRP viene intercettata dal framework. Il framework alloca quindi un oggetto richiesta WDF per rappresentare l'IRP. Successivamente, il framework passa l'oggetto richiesta WDF al driver client. Il driver client può quindi associare l'oggetto richiesta al trasferimento dei dati ODBC e inviarlo allo stack di driver USB.
Se il driver client non riceve un oggetto richiesta WDF dal framework e vuole inviare la richiesta in modo asincrono, il driver deve allocare un oggetto richiesta WDF chiamando il metodo WdfRequestCreate . Formattare il nuovo oggetto chiamando WdfUsbTargetPipeFormatRequestForUrb e inviare la richiesta chiamando WdfRequestSend.
Nei casi sincroni, il passaggio di un oggetto richiesta WDF è facoltativo.
Per trasferire i dati ai flussi, è necessario usare gli URL. È necessario formattare l'oggetto INDUBBIAMENTE chiamando WdfUsbTargetPipeFormatRequestForUrb.
I metodi WDF seguenti non sono supportati per i flussi:
- WdfUsbTargetPipeFormatRequestForRead
- WdfUsbTargetPipeFormatRequestForWrite
- WdfUsbTargetPipeReadSynchronously
- WdfUsbTargetPipeWriteSynchronously
La procedura seguente presuppone che il driver client riceva l'oggetto richiesta dal framework.
Allocare un oggetto DEVICE chiamando WdfUsbTargetDeviceCreateUrb. Questo metodo alloca un oggetto di memoria WDF che contiene l'oggetto THREAD appena allocato. Il driver del client può scegliere di allocare un OGGETTO ODBC per ogni richiesta di I/O o allocarlo e riutilizzarlo per lo stesso tipo di richiesta.
Formattare l'oggetto JSON per un trasferimento in blocco chiamando UsbBuildInterruptOrBulkTransferRequest. Nel parametro PipeHandle specificare l'handle per il flusso. Gli handle di flusso sono stati ottenuti in una richiesta precedente, descritta nella sezione Come aprire flussi statici .
Formattare l'oggetto richiesta WDF chiamando il metodo WdfUsbTargetPipeFormatRequestForUrb . Nella chiamata specificare l'oggetto memoria WDF che contiene il trasferimento dei dati TRAMITE. L'oggetto memoria è stato allocato nel passaggio 1.
Inviare IL FILE COME richiesta WDF chiamando WdfRequestSend o WdfUsbTargetPipeSendUrbSynchronously. Se si chiama WdfRequestSend, è necessario specificare una routine di completamento chiamando WdfRequestSetCompletionRoutine in modo che il driver client possa ricevere una notifica al termine dell'operazione asincrona. È necessario liberare il trasferimento dei dati DALLAT nella routine di completamento.
Driver WDM: Allocare un'istanza di CLASSE chiamando USBD_UrbAllocate e formattandola per il trasferimento in blocco (vedere _URB_BULK_OR_INTERRUPT_TRANSFER). Per formattare l'OGGETTO, è possibile chiamare usbBuildInterruptOrBulkTransferRequest o formattare manualmente la struttura DELL'OGGETTO. Specificare l'handle per il flusso nel membro ErroneaBulkOrInterruptTransfer.PipeHandle dell'OGGETTO.
Come chiudere flussi statici
Il driver client può chiudere i flussi al termine dell'uso del driver. Tuttavia, la richiesta di close-stream è facoltativa. Lo stack di driver USB chiude tutti i flussi quando l'endpoint associato ai flussi viene deconfigurato. Un endpoint viene deconfigurato quando viene selezionata una configurazione o un'interfaccia alternativa, il dispositivo viene rimosso e così via. Un driver client deve chiudere i flussi se il driver vuole aprire un numero diverso di flussi. Per inviare una richiesta close-stream:
Allocare una struttura DEVICE chiamando WdfUsbTargetDeviceCreateUrb.
Formattare l'oggetto DASH per la richiesta close-streams. Il membro Dell'Elemento Dell'OGGETTO È una struttura _URB_PIPE_REQUEST. Compilare i relativi membri come indicato di seguito:
- Il membro Hdr di _URB_PIPE_REQUEST deve essere URB_FUNCTION_CLOSE_STATIC_STREAMS
- Il membro PipeHandle deve essere l'handle dell'endpoint che contiene i flussi aperti in uso.
Inviare IL CERTIFICATO come richiesta WDF chiamando WdfRequestSend o WdfUsbTargetDeviceSendUrbSynchronously.
La richiesta di handle di chiusura chiude tutti i flussi aperti in precedenza dal driver client. Il driver client non può usare la richiesta per chiudere flussi specifici nell'endpoint.
Procedure consigliate per l'invio di una richiesta di flussi statici
Lo stack di driver USB esegue le convalide sull'oggetto RESTITUITO ricevuto. Per evitare errori di convalida:
- Non inviare una richiesta open-stream o close-stream a un endpoint che non supporta i flussi. Chiamare WdfUsbTargetDeviceQueryUsbCapability (per i driver WDM USBD_QueryUsbCapability) per determinare il supporto dei flussi statici e inviare richieste di flussi solo se l'endpoint li supporta.
- Non richiedere un numero (di flussi da aprire) che supera il numero massimo di flussi supportati o inviare una richiesta senza specificare il numero di flussi. Determinare il numero di flussi in base al numero di flussi supportati dallo stack di driver USB e dall'endpoint del dispositivo.
- Non inviare una richiesta di flusso aperto a un endpoint che dispone già di flussi aperti.
- Non inviare una richiesta di close-stream a un endpoint che non dispone di flussi aperti.
- Dopo che i flussi statici sono aperti per un endpoint, non inviare richieste di I/O usando l'handle della pipe dell'endpoint ottenuto tramite una configurazione select-configuration o richieste select-interface. Questo vale anche se i flussi statici sono stati chiusi.
Operazioni di reimpostazione e interruzione della pipe
A volte, i trasferimenti da o verso un endpoint possono avere esito negativo. Tali errori possono derivare da una condizione di errore nell'endpoint o nel controller host, ad esempio un blocco o una condizione di arresto. Per cancellare la condizione di errore, il driver client annulla prima i trasferimenti in sospeso e quindi reimposta la pipe con cui è associato l'endpoint. Per annullare i trasferimenti in sospeso, il driver client può inviare una richiesta abort-pipe. Per reimpostare una pipe, il driver client deve inviare una richiesta di reimpostazione della pipe.
Per i trasferimenti di flusso, le richieste abort-pipe e reset-pipe non sono supportate per i singoli flussi associati all'endpoint in blocco. Se un trasferimento ha esito negativo su una particolare pipe di flusso, il controller host arresta i trasferimenti su tutte le altre pipe (per altri flussi). Per eseguire il ripristino dalla condizione di errore, il driver client deve annullare manualmente i trasferimenti a ogni flusso. Il driver client deve quindi inviare una richiesta di reimpostazione pipe usando l'handle pipe per l'endpoint bulk. Per tale richiesta, il driver client deve specificare l'handle pipe all'endpoint in una struttura di _URB_PIPE_REQUEST e impostare la funzione HDR (Hdr.Function) su URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL.
Esempio completo
Nell'esempio di codice seguente viene illustrato come aprire flussi.
NTSTATUS
OpenStreams (
_In_ WDFDEVICE Device,
_In_ WDFUSBPIPE Pipe)
{
NTSTATUS status;
PDEVICE_CONTEXT deviceContext;
PPIPE_CONTEXT pipeContext;
USHORT cStreams = 0;
USBD_PIPE_HANDLE usbdPipeHandle;
WDFMEMORY urbMemory = NULL;
PURB urb = NULL;
PAGED_CODE();
deviceContext =GetDeviceContext(Device);
pipeContext = GetPipeContext (Pipe);
if (deviceContext->MaxStreamsController == 0)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Static streams are not supported.");
status = STATUS_NOT_SUPPORTED;
goto Exit;
}
// If static streams are not supported, number of streams supported is zero.
if (pipeContext->MaxStreamsSupported == 0)
{
status = STATUS_DEVICE_CONFIGURATION_ERROR;
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Static streams are not supported by the endpoint.");
goto Exit;
}
// Determine the number of streams to open.
// Compare the number of streams supported by the endpoint with the
// number of streams supported by the host controller, and choose the
// lesser of the two values. The deviceContext->MaxStreams value was
// obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
// that determined whether or not static streams is supported and
// retrieved the maximum number of streams supported by the
// host controller. The device context stores the values for IN and OUT
// endpoints.
// Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
// The number of elements in the array is the number of streams to open.
// The code snippet stores the array in its device context.
cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);
// Allocate an array of streams associated with the IN bulk endpoint
// This array is released in CloseStreams.
pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
NonPagedPool,
sizeof (USBD_STREAM_INFORMATION) * cStreams,
USBCLIENT_TAG);
if (pipeContext->StreamInfo == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not allocate stream information array.");
goto Exit;
}
RtlZeroMemory (pipeContext->StreamInfo,
sizeof (USBD_STREAM_INFORMATION) * cStreams);
// Get USBD pipe handle from the WDF target pipe object. The client driver received the
// endpoint pipe handles during device configuration.
usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);
// Allocate an URB for the open streams request.
// WdfUsbTargetDeviceCreateUrb returns the address of the
// newly allocated URB and the WDFMemory object that
// contains the URB.
status = WdfUsbTargetDeviceCreateUrb (
deviceContext->UsbDevice,
NULL,
&urbMemory,
&urb);
if (status != STATUS_SUCCESS)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not allocate URB for an open-streams request.");
goto Exit;
}
// Format the URB for the open-streams request.
// The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
// pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.
UsbBuildOpenStaticStreamsRequest (
urb,
usbdPipeHandle,
(USHORT)cStreams,
pipeContext->StreamInfo);
// Send the request synchronously.
// Upon completion, the USB driver stack populates the array of with handles to streams.
status = WdfUsbTargetPipeSendUrbSynchronously (
Pipe,
NULL,
NULL,
urb);
if (status != STATUS_SUCCESS)
{
goto Exit;
}
Exit:
if (urbMemory)
{
WdfObjectDelete (urbMemory);
}
return status;
}