Condivisione della tecnologia

Flussi statici di elaborazione dei driver di dispositivo USB Windows di trasferimenti batch

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Nei dispositivi USB 2.0 e precedenti, un endpoint in blocco può inviare o ricevere un singolo flusso di dati attraverso l'endpoint. Nei dispositivi USB 3.0, l'endpoint in blocco è in grado di inviare e ricevere più flussi di dati attraverso l'endpoint.

Lo stack di driver USB fornito da Microsoft in Windows supporta più flussi. Ciò consente ai driver client di inviare richieste I/O indipendenti a ciascun flusso associato a un endpoint in blocco in un dispositivo USB 3.0, senza serializzare le richieste a flussi diversi.

Per un driver client, un flusso rappresenta più endpoint logici con lo stesso insieme di caratteristiche. Per inviare una richiesta a un flusso specifico, il driver client necessita di un handle per quel flusso (simile all'handle della pipe di un endpoint). L'URB per le richieste di I/O in streaming è simile all'URB per le richieste di I/O per endpoint in blocco. L'unica differenza è la maniglia del tubo. Per inviare una richiesta I/O a un flusso, il driver specifica un handle di pipe nel flusso.

Durante la configurazione del dispositivo, il driver client invia una richiesta di configurazione selezionata e facoltativamente una richiesta di interfaccia selezionata. Queste richieste recuperano una serie di handle di pipe per gli endpoint definiti nelle impostazioni dell'attività dell'interfaccia. Per gli endpoint in grado di riconoscere il flusso, l'handle della pipe dell'endpoint può essere utilizzato per inviare richieste I/O al flusso predefinito (il primo flusso) finché il driver non apre il flusso.

Se un driver client desidera inviare richieste a flussi diversi da quello predefinito, il driver deve aprire e ottenere gli handle per tutti i flussi. Per fare ciò, il driver client invia una richiesta di flusso aperto specificando il numero di flussi da aprire. Dopo che un driver client ha finito di utilizzare i flussi, il driver può scegliere di chiuderli inviando una richiesta di chiusura del flusso.

Kernel Mode Driver Framework (KMDF) non supporta in modo nativo i flussi statici. I driver client devono utilizzare Windows Driver Model (WDM) per aprire e chiudere i flussi. I driver client UMDF (user-mode Driver Framework) non possono utilizzare la funzionalità di streaming statico.

Quanto segue potrebbe contenere alcuni commenti etichettati Driver WDM. Queste istruzioni descrivono le routine per un driver client USB basato su WDM che desidera inviare richieste di streaming.

prerequisiti

Prima che un driver client possa aprire o chiudere un flusso, il driver deve disporre di:

1. Chiamare il metodo WdfUsbTargetDeviceCreateWithParameters. Il metodo richiede la versione del protocollo client USBD_CLIENT_CONTRACT_VERSION_602. Specificando questa versione, il driver client deve rispettare una serie di regole.

Chiamata per recuperare l'handle WDFUSBDEVICE dell'oggetto dispositivo di destinazione USB del framework. L'handle è necessario per le chiamate successive al flusso aperto. In genere, un driver client si registra nella routine di callback dell'evento EVT_WDF_DEVICE_PREPARE_HARDWARE del driver.

Driver WDM: richiama la routine USBD_CreateHandle e ottiene l'handle USBD registrato dal driver nello stack dei driver USB.

2. Configurato il dispositivo e ottenuto l'handle della pipe WDFUSBPIPE per l'endpoint in blocco che supporta lo streaming. Per ottenere un handle di pipe, chiamare il metodo WdfUsbInterfaceGetConfiguredPipe sulle impostazioni alternative correnti dell'interfaccia nella configurazione selezionata.

Driver WDM: ottiene l'handle della pipe USBD inviando una richiesta di configurazione o selezione dell'interfaccia.

Come aprire un flusso statico

1. Determinare se lo stack di driver USB sottostante e il controller host supportano la funzionalità di streaming statico chiamando il metodo WdfUsbTargetDeviceQueryUsbCapability. In genere, i driver client chiamano le routine nella routine di callback dell'evento EVT_WDF_DEVICE_PREPARE_HARDWARE del driver.

Driver WDM: richiama la routine USBD_QueryUsbCapability. In genere, un driver interroga la funzione da utilizzare nella routine del dispositivo di avvio del driver (IRP_MN_START_DEVICE).

Fornire le seguenti informazioni:

  • L'handle dell'oggetto dispositivo USB recuperato in una chiamata precedente a WdfUsbTargetDeviceCreateWithParameters, utilizzato per registrare il driver client.

Driver WDM: passa l'handle USBD recuperato nella chiamata precedente a USBD_CreateHandle.

Se un driver client desidera utilizzare una funzionalità specifica, il driver deve prima interrogare lo 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 conducente dovrà inviare una richiesta per utilizzare la funzionalità. Alcune richieste richiedono URB, come la funzionalità di streaming discussa nel passaggio 5. Per queste richieste, assicurati di utilizzare lo stesso handle per interrogare la funzione e allocare l'URB. Questo perché lo stack dei driver utilizza gli handle per tenere traccia delle funzionalità supportate che il driver può utilizzare.

Ad esempio, se USBD_HANDLE è stato ottenuto chiamando USBD_CreateHandle, lo stack di driver viene interrogato chiamando USBD_QueryUsbCapability e l'URB viene allocato chiamando USBD_UrbAllocate. Passa lo stesso USBD_HANDLE in entrambe le chiamate.

Se chiami i metodi KMDF, WdfUsbTargetDeviceQueryUsbCapability e WdfUsbTargetDeviceCreateUrb, specifica lo stesso handle WDFUSBDEVICE per l'oggetto di destinazione del framework in queste chiamate al metodo.

  • GUID assegnato a GUID_USB_CAPABILITY_STATIC_STREAMS;
  • Puntatore al buffer di output (che punta a USHORT). Una volta completato, il buffer verrà riempito con (numero massimo di flussi per) ciascun endpoint supportato dal controller host;
  • La lunghezza del buffer di output, in byte. Per i flussi, la lunghezza è sizeof (USHORT);

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

3. Determinare il numero di flussi da aprire. Il numero massimo di flussi che possono essere aperti è limitato da:

  • Il numero massimo di flussi supportati dal controller host. WdfUsbTargetDeviceQueryUsbCapability (riceve WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability) nel buffer di output fornito dal chiamante. Lo stack di driver USB fornito da Microsoft supporta fino a 255 flussi. WdfUsbTargetDeviceQueryUsbCapability tiene conto di questo limite quando calcola il numero di flussi. Il metodo non restituisce mai un valore maggiore di 255.
  • Il numero massimo di flussi supportati dall'endpoint nel dispositivo. Per ottenere questo numero, controlla il descrittore associato dell'endpoint (in Usbspec.h) per USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR. 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 di supporto USBD_ParseConfigurationDescriptorEx e USBD_ParseDescriptor.

Per determinare il numero massimo di flussi, scegli il più piccolo dei due valori supportati dal controller host e dall'endpoint.

4. Assegnare un array di strutture USBD_STREAM_INFORMATION di n elementi, dove n è il numero di flussi da aprire. Il driver client è responsabile della liberazione di questo array dopo che il driver ha terminato di utilizzare il flusso.

5. Assegnare un URB per la richiesta di flusso aperto chiamando il metodo WdfUsbTargetDeviceCreateUrb. Se la chiamata viene completata con successo, il metodo recupera l'oggetto memoria WDF e l'indirizzo della struttura URB allocata dallo stack dei driver USB.

Driver WDM: richiama la routine USBD_UrbAllocate.

6. Imposta il formato URB della richiesta di flusso aperto. URB utilizza la struttura _URB_OPEN_STATIC_STREAMS per definire le richieste. Per formattare un URB, è necessario:

  • Handle della pipe USBD che punta all'endpoint. Se è presente un oggetto pipe WDF, è possibile ottenere l'handle della pipe USBD chiamando il metodo WdfUsbTargetPipeWdmGetPipeHandle.
  • Creato nel passaggio 4 (array di flusso)
  • Puntatore alla struttura URB creata in (passaggio 5).

Per formattare un URB, chiama UsbBuildOpenStaticStreamsRequest e passa le informazioni richieste come valori dei parametri. Assicurati che il numero di flussi specificati in UsbBuildOpenStaticStreamsRequest non superi il numero massimo di flussi supportati.

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

Driver WDM: associa l'URB all'IRP e invia l'IRP allo stack di driver USB.

8. Una volta completata la richiesta, verificare lo stato della richiesta.Se la richiesta dello stack del driver USB fallisce, lo stato URB contiene il relativo codice di errore.

Se lo stato della richiesta (oggetto richiesta IRP o WDF) indica USBD_STATUS_SUCCESS, la richiesta è stata completata correttamente. Array di strutture USBD_STREAM_INFORMATION ricevute al termine del controllo. L'array viene popolato con le informazioni sul flusso richiesto. Lo stack di driver USB popola ciascuna struttura nell'array con informazioni sul flusso, come l'handle ricevuto USBD_PIPE_HANDLE, l'identificatore del flusso e la dimensione numerica massima del trasferimento. Il flusso ora può trasferire i dati.

Per le richieste di flusso aperto, è necessario allocare URB e array. Una volta completata la richiesta di flusso aperto, il driver client deve rilasciare l'URB chiamando il metodo WdfObjectDelete sull'oggetto di memoria WDF associato. Se il driver invia la richiesta in modo sincrono chiamando WdfUsbTargetDeviceSendUrbSynchronously, l'oggetto memoria WDF deve essere rilasciato dopo la restituzione del metodo. Se un driver client invia una richiesta in modo asincrono chiamando WdfRequestSend, il driver deve rilasciare l'oggetto memoria WDF nella routine di completamento implementata dal driver associata alla richiesta.

L'array del flusso può essere rilasciato dopo che il driver client ha terminato di utilizzare il flusso oppure può essere archiviato per le richieste I/O. Nell'esempio di codice incluso di seguito, il driver archivia un array di flussi nel contesto del dispositivo. Il driver rilascia il contesto di dispositivo prima di rilasciare l'oggetto dispositivo.

Come trasferire i dati a un flusso specifico
Per inviare una richiesta di trasferimento dati a un flusso specifico, è richiesto un oggetto di richiesta WDF. In genere, i driver client non necessitano di allocare oggetti di richiesta WDF. Quando il gestore I/O riceve una richiesta da un'applicazione, crea un IRP per la richiesta. L'IRP è stato intercettato dal framework. Il framework quindi alloca un oggetto di richiesta WDF per rappresentare l'IRP. Successivamente, il framework passa l'oggetto della richiesta WDF al driver client. Il driver client può quindi associare l'oggetto richiesta all'URB di trasferimento dati e inviarlo allo stack del driver USB.

Se un driver client non riceve un oggetto di richiesta WDF dal framework e desidera inviare la richiesta in modo asincrono, il driver deve allocare un oggetto di richiesta WDF chiamando il metodo WdfRequestCreate. Formatta il nuovo oggetto chiamando WdfUsbTargetPipeFormatRequestForUrb e invia la richiesta chiamando WdfRequestSend.

Nel caso sincrono, il passaggio dell'oggetto richiesta WDF è facoltativo.

Per trasferire i dati a un flusso, è necessario utilizzare URB. L'URB deve essere formattato chiamando WdfUsbTargetPipeFormatRequestForUrb.

I flussi non supportano i seguenti metodi WDF:

  • WdfUsbTargetPipeFormatRequestForRead
  • Formato della pipetta di destinazione WdfUsbRequestForWrite
  • WdfUsbTargetPipeRead in modo sincrono
  • WdfUsbTargetPipeWriteSincrono

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

  1. Allocare l'URB chiamando WdfUsbTargetDeviceCreateUrb. Questo metodo alloca un oggetto di memoria WDF contenente l'URB appena allocato. I driver client possono scegliere di allocare un URB per ogni richiesta I/O oppure allocare un URB e utilizzarlo per richieste dello stesso tipo.
  2. Formattare l'URB per il trasferimento in blocco chiamando UsbBuildInterruptOrBulkTransferRequest. Nel parametro PipeHandle, specificare l'handle del flusso. L'handle del flusso viene ottenuto nella richiesta precedente, come descritto nella sezione Come aprire un flusso statico.
  3. Formattare l'oggetto della richiesta WDF chiamando il metodo WdfUsbTargetPipeFormatRequestForUrb. Nella chiamata specificare l'oggetto memoria WDF contenente l'URB di trasferimento dati. L'oggetto memoria è stato allocato nel passaggio 1.
  4. Invia l'URB come richiesta WDF chiamando WdfRequestSend o WdfUsbTargetPipeSendUrbSynchronously. Se chiami WdfRequestSend, devi specificare una routine di completamento chiamando WdfRequestSetCompletionRoutine in modo che il driver client possa ricevere una notifica al completamento dell'operazione asincrona. L'URB di trasferimento dati deve essere rilasciato nella routine di completamento.

Driver WDM: assegna un URB chiamando USBD_UrbAllocate e lo formatta per il trasferimento di massa (vedi _URB_BULK_OR_INTERRUPT_TRANSFER). Per formattare un URB, puoi chiamare UsbBuildInterruptOrBulkTransferRequest o formattare manualmente la struttura URB. Specificare l'handle del flusso nel membro UrbBulkOrInterruptTransfer.PipeHandle dell'URB.

Come chiudere un flusso statico

Il driver client può chiudere il flusso una volta terminato di utilizzarlo. Tuttavia, la chiusura della richiesta di streaming è facoltativa. Lo stack di driver USB chiude tutti i flussi quando l'endpoint associato al flusso non è configurato. Gli endpoint non vengono configurati quando si seleziona una configurazione o un'interfaccia alternativa, si elimina un dispositivo, ecc. Se il driver client desidera aprire un numero diverso di flussi, deve chiuderli. Invia una richiesta di chiusura dello streaming:

1. Assegnare la struttura URB chiamando WdfUsbTargetDeviceCreateUrb.

2. Imposta il formato URB per chiudere la richiesta di flusso. Il membro UrbPipeRequest della struttura URB è la struttura _URB_PIPE_REQUEST. Compila i suoi membri come segue:

  • Membro Hdr richiesto di URB_FUNCTION_CLOSE_STATIC_STREAMS_URB_PIPE_REQUEST
  • Il membro PipeHandle deve essere un handle che contiene l'endpoint utilizzato per aprire il flusso.

3. Invia l'URB come richiesta WDF chiamando WdfRequestSend o WdfUsbTargetDeviceSendUrbSynchronously.

L'handle di chiusura richiede la chiusura di tutti i flussi precedentemente aperti dal driver client. Il driver client non può utilizzare le richieste per chiudere un flusso specifico nell'endpoint.

Best practice per l'invio di richieste di streaming statiche

Lo stack di driver USB esegue la verifica sull'URB ricevuto. Per evitare errori di convalida, procedere come segue:

  • Non inviare richieste di flusso aperto o di flusso chiuso agli endpoint che non supportano i flussi. Chiama WdfUsbTargetDeviceQueryUsbCapability (, USBD_QueryUsbCapability) del driver WDM per determinare il supporto di streaming statico e inviare richieste di streaming solo se l'endpoint lo supporta.
  • Non richiedere più flussi (aperti) rispetto al numero massimo di flussi supportati o inviare una richiesta senza specificare il numero di flussi. Il numero di flussi viene determinato 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 un flusso aperto.
  • Non inviare una richiesta di chiusura del flusso a un endpoint che non dispone di un flusso aperto.
  • Dopo aver aperto un flusso statico per un endpoint, non inviare richieste I/O utilizzando l'handle della pipe dell'endpoint ottenuto tramite una configurazione di selezione o una richiesta di interfaccia di selezione. Ciò è vero anche se il flusso statico è chiuso.
Reimpostare e interrompere le operazioni della pipeline

Occasionalmente, i trasferimenti da o verso un endpoint potrebbero non riuscire. Tali errori possono essere causati da condizioni di errore sull'endpoint o sul controller host, ad esempio una condizione di arresto o di arresto. Per eliminare la condizione di errore, il driver client annulla innanzitutto il trasferimento in sospeso e quindi reimposta la pipe associata all'endpoint. Per annullare un trasferimento in sospeso, il driver client può inviare una richiesta di pipe di interruzione. Per reimpostare una pipe, il driver client deve inviare una richiesta di reimpostazione della pipe.

Per lo streaming, le richieste di abort-pipe e reset-pipe non sono supportate per i singoli flussi associati all'endpoint in blocco. Se un trasferimento su una pipe di flusso specifica non riesce, il controller host interromperà i trasferimenti su tutte le altre pipe per gli altri flussi. Per ripristinare la condizione di errore, il driver client deve annullare manualmente il trasferimento su ciascun flusso un handle di pipe per inviare una richiesta di reimpostazione della pipe all'endpoint in blocco. Per questa richiesta, il driver client deve specificare l'handle della pipe dell'endpoint nella 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 un flusso.

  1. NTSTATUS
  2. OpenStreams (
  3. _In_ WDFDEVICE Device,
  4. _In_ WDFUSBPIPE Pipe)
  5. {
  6. NTSTATUS status;
  7. PDEVICE_CONTEXT deviceContext;
  8. PPIPE_CONTEXT pipeContext;
  9. USHORT cStreams = 0;
  10. USBD_PIPE_HANDLE usbdPipeHandle;
  11. WDFMEMORY urbMemory = NULL;
  12. PURB urb = NULL;
  13. PAGED_CODE();
  14. deviceContext =GetDeviceContext(Device);
  15. pipeContext = GetPipeContext (Pipe);
  16. if (deviceContext->MaxStreamsController == 0)
  17. {
  18. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  19. "%!FUNC! Static streams are not supported.");
  20. status = STATUS_NOT_SUPPORTED;
  21. goto Exit;
  22. }
  23. // If static streams are not supported, number of streams supported is zero.
  24. if (pipeContext->MaxStreamsSupported == 0)
  25. {
  26. status = STATUS_DEVICE_CONFIGURATION_ERROR;
  27. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  28. "%!FUNC! Static streams are not supported by the endpoint.");
  29. goto Exit;
  30. }
  31. // Determine the number of streams to open.
  32. // Compare the number of streams supported by the endpoint with the
  33. // number of streams supported by the host controller, and choose the
  34. // lesser of the two values. The deviceContext->MaxStreams value was
  35. // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
  36. // that determined whether or not static streams is supported and
  37. // retrieved the maximum number of streams supported by the
  38. // host controller. The device context stores the values for IN and OUT
  39. // endpoints.
  40. // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
  41. // The number of elements in the array is the number of streams to open.
  42. // The code snippet stores the array in its device context.
  43. cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);
  44. // Allocate an array of streams associated with the IN bulk endpoint
  45. // This array is released in CloseStreams.
  46. pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
  47. NonPagedPool,
  48. sizeof (USBD_STREAM_INFORMATION) * cStreams,
  49. USBCLIENT_TAG);
  50. if (pipeContext->StreamInfo == NULL)
  51. {
  52. status = STATUS_INSUFFICIENT_RESOURCES;
  53. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  54. "%!FUNC! Could not allocate stream information array.");
  55. goto Exit;
  56. }
  57. RtlZeroMemory (pipeContext->StreamInfo,
  58. sizeof (USBD_STREAM_INFORMATION) * cStreams);
  59. // Get USBD pipe handle from the WDF target pipe object. The client driver received the
  60. // endpoint pipe handles during device configuration.
  61. usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);
  62. // Allocate an URB for the open streams request.
  63. // WdfUsbTargetDeviceCreateUrb returns the address of the
  64. // newly allocated URB and the WDFMemory object that
  65. // contains the URB.
  66. status = WdfUsbTargetDeviceCreateUrb (
  67. deviceContext->UsbDevice,
  68. NULL,
  69. &urbMemory,
  70. &urb);
  71. if (status != STATUS_SUCCESS)
  72. {
  73. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  74. "%!FUNC! Could not allocate URB for an open-streams request.");
  75. goto Exit;
  76. }
  77. // Format the URB for the open-streams request.
  78. // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
  79. // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.
  80. UsbBuildOpenStaticStreamsRequest (
  81. urb,
  82. usbdPipeHandle,
  83. (USHORT)cStreams,
  84. pipeContext->StreamInfo);
  85. // Send the request synchronously.
  86. // Upon completion, the USB driver stack populates the array of with handles to streams.
  87. status = WdfUsbTargetPipeSendUrbSynchronously (
  88. Pipe,
  89. NULL,
  90. NULL,
  91. urb);
  92. if (status != STATUS_SUCCESS)
  93. {
  94. goto Exit;
  95. }
  96. Exit:
  97. if (urbMemory)
  98. {
  99. WdfObjectDelete (urbMemory);
  100. }
  101. return status;
  102. }