Condividi tramite


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 i flussi in un endpoint bulk di un dispositivo USB 3.0.

Nei dispositivi USB 2.0 e 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. Ciò consente a un driver client di inviare richieste di I/O indipendenti a ogni flusso associato a un endpoint bulk in un dispositivo USB 3.0. Le richieste ai 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 deve disporre di un handle per tale flusso (simile a un handle pipe per un endpoint). L'URB PER una richiesta di I/O a un flusso è simile a quella di una richiesta di I/O a un endpoint bulk. L'unica differenza è il manico del tubo. Per inviare una richiesta di I/O a un flusso, il driver specifica l'handle di pipe per il flusso.

Durante la configurazione del dispositivo, il driver client invia una richiesta di configurazione select e, facoltativamente, una richiesta di interfaccia select. Tali richieste recuperano un set di handle pipe agli endpoint definiti nell'impostazione attiva di un'interfaccia. Per un endpoint che supporta i flussi, l'handle della pipe dell'endpoint può essere usato per inviare richieste di I/O al flusso predefinito (il primo flusso) fino a quando il driver non apre 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. Una volta che il driver del client ha terminato di utilizzare i flussi, può opzionalmente chiuderli inviando una richiesta close-streams.

Kernel Mode Driver Framework (KMDF) non supporta in modo intrinseco i flussi statici. Il driver client deve inviare URB in stile Windows Driver Model (WDM) per aprire e chiudere i flussi. Questo articolo descrive come formattare e inviare tali URL. Un driver client UMDF (User Mode Driver Framework) non può usare la funzionalità di 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 i 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 degli URI.

    La chiamata recupera un handle di tipo WDFUSBDEVICE per l'oggetto dispositivo USB di destinazione del framework. Tale handle è necessario per effettuare chiamate successive per aprire flussi. In genere, il driver client si registra nella routine di callback degli eventi di EVT_WDF_DEVICE_PREPARE_HARDWARE del driver.

    Driver WDM: Chiamare la routine USBD_CreateHandle per ottenere un handle USBD per la registrazione del driver nello stack dei driver USB.

  • Configurato il dispositivo e ottenuto 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: Per ottenere un handle di pipe USBD, inviare una richiesta select-configuration o select-interface. Per altre informazioni, vedere Come selezionare una configurazione per un dispositivo USB.

Come aprire flussi statici

  1. Determinare se lo stack di driver USB sottostante e il controller host supportano la funzionalità dei flussi statici chiamando il metodo WdfUsbTargetDeviceQueryUsbCapability . In genere, il driver client chiama la routine nella routine di callback dell'evento EVT_WDF_DEVICE_PREPARE_HARDWARE del driver.

    Driver WDM: Chiamare la funzione USBD_QueryUsbCapability. In genere, il driver esegue una query per le 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:

    • Un handle per l'oggetto dispositivo USB che è stato recuperato in una chiamata precedente 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 URI, ad esempio la funzionalità flussi (descritta nel passaggio 5). Per tali richieste, assicurarsi di usare lo stesso handle per interrogare le funzionalità e allocare URB. 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'istruzione ODBC chiamando USBD_UrbAllocate. Utilizzare 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 nelle chiamate al metodo.

    • Il 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).

  2. Valutare il valore NTSTATUS restituito. Se la routine viene completata correttamente, restituisce STATUS_SUCCESS, la funzionalità dei flussi statici è supportata. In caso contrario, il metodo restituisce un codice di errore appropriato.

  3. 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, le USBD_ParseConfigurationDescriptorEx e le USBD_ParseDescriptor. Per un esempio di codice, vedere la funzione di esempio denominata RetrieveStreamInfoFromEndpointDesc in Come enumerare le pipe USB.

    Per determinare il numero massimo di flussi, scegliere il minore di due valori supportati dal controller host e dall'endpoint.

  4. 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.

  5. Allocare un URB per la richiesta di flussi aperti chiamando il metodo WdfUsbTargetDeviceCreateUrb. Se la chiamata viene completata correttamente, il metodo recupera un oggetto di memoria WDF e l'indirizzo della struttura ODBC allocata dallo stack di driver USB.

    Driver WDM: Chiamare la routine USBD_UrbAllocate.

  6. Formattare l'URB per la richiesta di flusso aperto. URB usa la struttura _URB_OPEN_STATIC_STREAMS per definire la richiesta. Per formattare l'URB, è necessario:

    • Handle della pipe USB dell'endpoint. Se si dispone di un oggetto pipe WDF, è possibile ottenere l'handle della pipe USBD chiamando il metodo WdfUsbTargetPipeWdmGetPipeHandle .
    • Matrice di flusso (creata nel passaggio 4)
    • Puntatore alla struttura URB (creata nel passaggio 5).

    Per formattare l'URB, 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.

  7. Inviare l'URB come oggetto richiesta WDF chiamando il metodo WdfRequestSend. Per inviare la richiesta in modo sincrono, chiamare invece il metodo WdfUsbTargetDeviceSendUrbSynchronously .

    Driver WDM: Associare l'URB a un IRP e inviare l'IRP nello stack dei driver USB. Per ulteriori informazioni, vedere How to Submit an URB.

  8. Al termine della richiesta, controllare lo stato della richiesta.

    Se lo stack di driver USB non riesce a gestire la richiesta, lo stato dell'URB 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. Esamina l'insieme delle strutture USBD_STREAM_INFORMATION ricevute dopo il 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 di flusso e dimensioni massime di trasferimento dei numeri. I flussi sono ora aperti per trasferire i dati.

Per una richiesta di flussi aperti, è necessario allocare un URB e un array. Il driver client deve rilasciare l'istruzione 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 la restituzione 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 dopo che il driver client ha finito di utilizzare i flussi o li ha archiviati per le richieste di I/O. Nell'esempio di codice incluso in questo articolo, il driver archivia la matrice di flussi nel contesto di dispositivo. Il driver rilascia il contesto del dispositivo appena prima che l'oggetto del dispositivo venga rimosso.

Come trasferire i dati in un flusso specifico

Per inviare una richiesta di trasferimento dati a un flusso specifico, è necessario un oggetto richiesta WDF. Di solito, il driver client non deve 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 intercettato dal framework. Il framework alloca quindi un oggetto richiesta WDF per rappresentare l'IRP. Successivamente, il framework passa l'oggetto richiesta WDF al driver del client. Il driver client può quindi associare l'oggetto richiesta all'URB di trasferimento dati 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 di richiesta WDF è facoltativo.

Per trasferire i dati ai flussi, è necessario usare gli URI. Per formattare l'URB, è necessario chiamare WdfUsbTargetPipeFormatRequestForUrb.

I metodi WDF seguenti non sono supportati per i flussi:

La procedura seguente presuppone che il driver client riceva l'oggetto richiesta dal framework.

  1. Allocare un URB chiamando WdfUsbTargetDeviceCreateUrb. Questo metodo alloca un oggetto di memoria WDF che contiene l'URB appena allocato. Il driver client può scegliere di allocare un URB per ogni richiesta di I/O o allocare un URB e riutilizzarlo per lo stesso tipo di richiesta.

  2. Formattare l'URB per un trasferimento di massa 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 .

  3. Formattare l'oggetto richiesta WDF chiamando il metodo WdfUsbTargetPipeFormatRequestForUrb . Nella chiamata, specifica l'oggetto di memoria WDF che contiene l'URB di trasferimento dati. L'oggetto memoria è stato allocato nel passaggio 1.

  4. Inviare l'URB 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 dati URB nella routine di completamento.

Driver WDM: Allocare un URB chiamando USBD_UrbAllocate e formattarlo per il trasferimento in blocco (vedere _URB_BULK_OR_INTERRUPT_TRANSFER). Per formattare l'URB, è possibile chiamare UsbBuildInterruptOrBulkTransferRequest o formattare manualmente la struttura URB. Specificare l'handle per il flusso nella proprietà UrbBulkOrInterruptTransfer.PipeHandle dell'URB.

Come chiudere i flussi statici

Il driver client può chiudere i flussi dopo aver terminato di usarli. Tuttavia, la richiesta 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 di chiusura del flusso:

  1. Allocare una struttura URB chiamando WdfUsbTargetDeviceCreateUrb.

  2. Formatta l'URB per la richiesta di chiusura-flussi. Il membro UrbPipeRequest della struttura URB è 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.
  3. Invia l'URB 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 verifiche sull'URB 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 flusso di chiusura 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 richiesta di configurazione *select-configuration* o di interfaccia *select-interface*. Questo vale anche se i flussi statici sono stati chiusi.

Operazioni di reimpostazione e interruzione del canale

A volte, i trasferimenti da o verso un endpoint possono non riuscire. Tali errori possono derivare da una condizione di errore nell'endpoint o nel controller host, ad esempio una condizione di stallo o 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 di interruzione pipe. Per reimpostare una pipe, il driver client deve inviare una richiesta reset-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 non riesce in 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 reset-pipe usando l'handle del pipe all'endpoint bulk. Per tale richiesta, il driver client deve specificare l'handle della pipe all'endpoint in una struttura _URB_PIPE_REQUEST e impostare la funzione URB (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;
}
  • Operazioni di I/O USB