Technology Sharing

Windows USB device driver development - static stream processing batch transfer

2024-07-12

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

In USB 2.0 and earlier devices, a bulk endpoint can send or receive a single stream of data through the endpoint. In USB 3.0 devices, a bulk endpoint can send and receive multiple streams of data through the endpoint.

The Microsoft-provided USB driver stack in Windows supports multiple streams. This enables the client driver to send independent I/O requests to each stream associated with a bulk endpoint in a USB 3.0 device without serializing requests to different streams.

To the client driver, streams represent multiple logical endpoints with the same set of characteristics. To send a request to a specific stream, the client driver needs a handle to the stream (similar to a pipe handle to an endpoint). The URB for stream I/O requests is similar to the URB for I/O requests to bulk endpoints. The only difference is the pipe handle. To send an I/O request to a stream, the driver specifies the pipe handle in the stream.

During device configuration, the client driver sends a select configuration request and, optionally, a select interface request. These requests retrieve a set of pipe handles for the endpoints defined in the active settings for the interface. For endpoints that support streams, the endpoint pipe handles can be used to send I/O requests to the default stream (the first stream) until the driver opens a stream.

If the client driver wants to send requests to streams other than the default stream, the driver must open and obtain handles to all streams. To do this, the client driver sends an open stream request by specifying the number of streams to open. After the client driver is done using the streams, the driver can choose to close them by sending a close stream request.

The Kernel-Mode Driver Framework (KMDF) does not natively support static streams. Client drivers must use the Windows Driver Model (WDM) to open and close streams. User-Mode Driver Framework (UMDF) client drivers cannot use the static streams feature.

The following may contain some comments labeled WDM Drivers. These notes describe routines for WDM-based USB client drivers that want to send stream requests.

prerequisites

Before a client driver can open or close a stream, the driver must have:

1. Call the WdfUsbTargetDeviceCreateWithParameters method. The method requires the USBD_CLIENT_CONTRACT_VERSION_602 client contract version. By specifying this version, the client driver must follow a set of rules.

Called to retrieve the WDFUSBDEVICE handle of the framework's USB target device object. This handle is required to make subsequent calls on the opened stream. Typically, a client driver registers itself in the driver's EVT_WDF_DEVICE_PREPARE_HARDWARE event callback routine.

WDM drivers: Call the USBD_CreateHandle routine and obtain the USBD handle that the driver registered in the USB driver stack.

2. Configure the device and obtain a WDFUSBPIPE pipe handle for the bulk endpoint that supports streaming. To obtain the pipe handle, call the WdfUsbInterfaceGetConfiguredPipe method on the current alternate settings for the interface in the selected configuration.

WDM driver: Gets the USBD pipe handle by sending a select-configuration or select-interface request.

How to open static stream

1. Determine whether the underlying USB driver stack and host controller support the static streams capability by calling the WdfUsbTargetDeviceQueryUsbCapability method. Typically, the client driver calls this routine in the driver's EVT_WDF_DEVICE_PREPARE_HARDWARE event callback routine.

WDM drivers: Call the USBD_QueryUsbCapability routine. Typically, a driver queries for capabilities to use in the driver's start-device routine (IRP_MN_START_DEVICE).

Provide the following information:

  • A handle to the USB device object retrieved in a previous call to WdfUsbTargetDeviceCreateWithParameters, used to register the client driver.

WDM Driver: Passes the USBD handle retrieved in the previous call to USBD_CreateHandle.

If the client driver wants to use a specific feature, the driver must first query the underlying USB driver stack to determine if the driver stack and host controller support the feature. If the feature is supported, then and only then should the driver send a request to use the feature. Some requests require a URB, such as the stream features discussed in step 5. For these requests, make sure that you use the same handle to query the feature and to allocate the URB. This is because the driver stack uses the handle to keep track of supported features that the driver can use.

For example, if you obtained a USBD_HANDLE by calling USBD_CreateHandle, query the driver stack by calling USBD_QueryUsbCapability, and allocate a URB by calling USBD_UrbAllocate. Pass the same USBD_HANDLE in both calls.

If you call the KMDF methods, WdfUsbTargetDeviceQueryUsbCapability and WdfUsbTargetDeviceCreateUrb, specify the same WDFUSBDEVICE handle for the framework target object in these method calls.

  • The GUID assigned to GUID_USB_CAPABILITY_STATIC_STREAMS;
  • Pointer to output buffer (pointer to USHORT). When completed, the buffer will be filled with each endpoint (maximum number of streams) supported by the host controller;
  • The length of the output buffer, in bytes. For streams, the length is sizeof (USHORT);

2. Evaluate the returned NTSTATUS value. If the routine completes successfully, STATUS_SUCCESS is returned, and static stream functionality is supported. Otherwise, the method returns the appropriate error code.

3. Determine the number of streams to open. The maximum number of streams that can be opened is subject to the following restrictions:

  • The maximum number of streams supported by the host controller. WdfUsbTargetDeviceQueryUsbCapability (WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability) receives the output buffer provided by the caller. The Microsoft-provided USB driver stack supports a maximum of 255 streams. WdfUsbTargetDeviceQueryUsbCapability takes that limitation into account when calculating the number of streams. The method never returns a value greater than 255.
  • The maximum number of streams supported by an endpoint in the device. To get this number, check the endpoint companion descriptor (see USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR in Usbspec.h). To get the endpoint companion descriptor, you must parse the configuration descriptor. To get the configuration descriptor, the client driver must call the WdfUsbTargetDeviceRetrieveConfigDescriptor method. You must use the helper routines, USBD_ParseConfigurationDescriptorEx and USBD_ParseDescriptor.

To determine the maximum number of streams, choose the smaller of the two values ​​supported by the host controller and the endpoint.

4. Allocate an array of USBD_STREAM_INFORMATION structures containing n elements, where n is the number of streams to be opened. The client driver is responsible for freeing this array after the driver has finished using the streams.

5. Allocate a URB for the open stream request by calling the WdfUsbTargetDeviceCreateUrb method. If the call completes successfully, the method retrieves the address of the WDF memory object and the URB structure allocated by the USB driver stack.

WDM drivers: Call the USBD_UrbAllocate routine.

6. Set the URB format for the open stream request. URB uses the _URB_OPEN_STATIC_STREAMS structure to define the request. To set the format of the URB, you need to:

  • The USBD pipe handle pointing to the endpoint. If you have a WDF pipe object, you can get the USBD pipe handle by calling the WdfUsbTargetPipeWdmGetPipeHandle method.
  • Created in step 4 (stream array)
  • Pointer to the URB structure created in (step 5).

To format the URB, call UsbBuildOpenStaticStreamsRequest and pass the required information as parameter values. Make sure that the number of streams specified to UsbBuildOpenStaticStreamsRequest does not exceed the maximum number of streams supported.

7. Send the URB as a WDF request object by calling the WdfRequestSend method. To send the request synchronously, call the WdfUsbTargetDeviceSendUrbSynchronously method instead.

WDM driver: Associates a URB with an IRP and submits the IRP to the USB driver stack.

8. After the request is completed, check the status of the request.If a USB driver stack request fails, the URB status contains the relevant error code.

If the status of the request (IRP or WDF request object) indicates USBD_STATUS_SUCCESS, the request completed successfully. Examine the array of USBD_STREAM_INFORMATION structures received upon completion. The array is populated with information about the requested streams. The USB driver stack populates each structure in the array with stream information, such as the handle received by USBD_PIPE_HANDLE, the stream identifier, and the maximum numeric transfer size. The stream is now ready to transfer data.

For an open stream request, a URB and array need to be allocated. After the open stream request is completed, the client driver must release the URB by calling the WdfObjectDelete method on the associated WDF memory object. If the driver sent the request synchronously by calling WdfUsbTargetDeviceSendUrbSynchronously, the WDF memory object must be released after the method returns. If the client driver sent the request asynchronously by calling WdfRequestSend, the driver must release the WDF memory object in the driver-implemented completion routine associated with the request.

You can release the stream array after the client driver has finished using the streams, or store the stream array for an I/O request. In the code example included below, the driver stores the stream array in the device context. The driver releases the device context before releasing the device object.

How to transfer data to a specific stream
To send a data transfer request to a specific stream, a WDF request object is required. Typically, the client driver does not need to allocate a WDF request object. When the I/O manager receives a request from an application, the I/O manager creates an IRP for the request. This IRP is intercepted by the framework. The framework then allocates a WDF request object to represent the IRP. After that, the framework passes the WDF request object to the client driver. The client driver can then associate the request object with a data transfer URB and send it to the USB driver stack.

If the client driver has not received a WDF request object from the framework and wants to send a request asynchronously, the driver must allocate a WDF request object by calling the WdfRequestCreate method, format the new object by calling WdfUsbTargetPipeFormatRequestForUrb, and send the request by calling WdfRequestSend.

In the synchronous case, passing a WDF request object is optional.

To transfer data to a stream, you must use a URB. You must set the format of the URB by calling WdfUsbTargetPipeFormatRequestForUrb.

The stream does not support the following WDF methods:

  • WdfUsbTargetPipeFormatRequestForRead
  • WdfUsbTargetPipeFormatRequestForWrite
  • WdfUsbTargetPipeReadSynchronously
  • WdfUsbTargetPipeWriteSynchronously

The following procedure assumes that the client driver receives a request object from the framework.

  1. Allocate a URB by calling WdfUsbTargetDeviceCreateUrb. This method allocates a WDF memory object that contains the newly allocated URB. The client driver can choose to allocate a URB for each I/O request, or allocate a URB and use it for requests of the same type.
  2. Format the URB for bulk transfer by calling UsbBuildInterruptOrBulkTransferRequest. In the PipeHandle parameter, specify the handle of the stream. The stream handle is obtained in the previous request, as described in the How to Open a Static Stream section.
  3. Format the WDF request object by calling the WdfUsbTargetPipeFormatRequestForUrb method. In the call, specify the WDF memory object that contains the data transfer URB. The memory object was allocated in step 1.
  4. Send the URB as a WDF request by calling WdfRequestSend or WdfUsbTargetPipeSendUrbSynchronously. If you call WdfRequestSend, you must specify a completion routine by calling WdfRequestSetCompletionRoutine so that the client driver can be notified when the asynchronous operation completes. You must release the data transfer URB in the completion routine.

WDM drivers: Allocate a URB by calling USBD_UrbAllocate and format it for bulk transfers (see _URB_BULK_OR_INTERRUPT_TRANSFER). To format the URB, you can call UsbBuildInterruptOrBulkTransferRequest or manually format the URB structure. Specify the handle to the stream in the UrbBulkOrInterruptTransfer.PipeHandle member of the URB.

How to turn off static streaming

The client driver can close streams after the driver has finished using the streams. However, a close stream request is optional. The USB driver stack closes all streams when the endpoint associated with the streams is deconfigured. Endpoints are deconfigured when an alternate configuration or interface is selected, when a device is removed, and so on. If the client driver wants to open a different number of streams, it must close the streams. To send a close stream request:

1. Allocate a URB structure by calling WdfUsbTargetDeviceCreateUrb.

2. Set the URB format for the close stream request. The UrbPipeRequest member of the URB structure is a _URB_PIPE_REQUEST structure. Fill in its members as follows:

  • The Hdr member of URB_FUNCTION_CLOSE_STATIC_STREAMS_URB_PIPE_REQUEST is required
  • The PipeHandle member must be the handle of the endpoint that contains the open stream being used.

3. Send the URB as a WDF request by calling WdfRequestSend or WdfUsbTargetDeviceSendUrbSynchronously.

The close handle request closes all streams previously opened by the client driver. The client driver cannot use the request to close a specific stream in the endpoint.

Best practices for sending static streaming requests

The USB driver stack performs validation on received URBs. To avoid validation errors:

  • Do not send open stream or close stream requests to an endpoint that does not support streams. Call the WDM driver's WdfUsbTargetDeviceQueryUsbCapability (, USBD_QueryUsbCapability) to determine static stream support, and send stream requests only if the endpoint supports it.
  • Do not request more streams than the maximum number of streams supported (open), or send a request without specifying the number of streams. Determine the number of streams based on the number of streams supported by the USB driver stack and the device endpoint.
  • Do not send an open stream request to an endpoint that already has an open stream.
  • Do not send a close stream request to an endpoint that does not have an open stream.
  • After you open a static stream for an endpoint, do not send I/O requests using the endpoint pipe handle obtained through a select configuration or select interface request, even if the static stream is closed.
Resetting and Aborting Pipeline Operations

Sometimes, transfers to or from an endpoint might fail. Such failures might be caused by error conditions on the endpoint or the host controller, such as a stop or halt condition. To clear the error condition, the client driver first cancels the pending transfer and then resets the pipe associated with the endpoint. To cancel the pending transfer, the client driver can send an abort pipe request. To reset the pipe, the client driver must send a reset pipe request.

For stream transfers, abort-pipe and reset-pipe requests are not supported for a single stream associated with a bulk endpoint. If a transfer on a specific stream's pipe fails, the host controller stops transfers on all other pipes for other streams (). To recover from the error condition, the client driver should manually cancel the transfer to each stream. The client driver must then send a reset-pipe request to the bulk endpoint using the pipe handle. For this request, the client driver must specify the endpoint's pipe handle in a _URB_PIPE_REQUEST structure and set the URB function (Hdr.Function) to URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL.

Complete Example

The following code example shows how to open a stream.

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