How to open and close static streams in a USB bulk endpoint
https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/how-to-open-streams-in-a-usb-endpoint
This topic discussesstatic streams capabilityand explains how a USB client driver can open and close streams in a bulk endpoint of a USB 3.0 device.
In USB 2.0 and earlier devices, a bulk endpoint can send or receive a single data stream through the endpoint. In USB 3.0 devices, bulk endpoints have the capability to send and receive multiple data streams through the endpoint.
The Microsoft-provided USB driver stack in Windows 8 supports multiple streams. This enables a client driver to send independent I/O requests to each stream associated with a bulk endpoint in a USB 3.0 device. The requests to different streams are not serialized.
For a client driver, streams represent multiple logical endpoints that have the same set of characteristics. To send a request to a particular stream, the client driver needs a handle to that stream (similar to a pipe handle for an endpoint). The URB for an I/O request to a stream is similar to an URB for a I/O request to a bulk endpoint. The only difference is the pipe handle. To send an I/O request to a stream, the driver specifies the pipe handle to the stream.
During device configuration, the client driver sends a select-configuration request and optionally a select-interface request. Those requests retrieve a set of pipe handles to the endpoints that are defined in the active setting of an interface. For an endpoint that supports streams, the endpoint pipe handle can be used to send I/O requests to the to thedefault 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 so, the client driver sends anopen-streams requestby specifying the number of streams to open. After the client driver is finished using streams, the driver can optionally close them by sending aclose-streams request.
Kernel Mode Driver Framework (KMDF) does not support static streams intrinsically. The client driver must send Windows Driver Model (WDM) style URBs that open and close streams. This topic describes how to format and send those URBs. A User Mode Driver Framework (UMDF)-client driver cannot use the static streams capability.
The topic contains some notes that are labeled asWDM drivers. Those notes describe routines for a WDM-based USB client driver that wants to send stream requests.
Prerequisites
Before a client driver can open or close streams, the driver must have:
The method requires the client contract version to be USBD_CLIENT_CONTRACT_VERSION_602. By specifying that version the client driver must adhere to a set of rules. For more information, seeBest Practices: Using URBs.
The call retrieves a WDFUSBDEVICE handle to the framework's USB target device object. That handle is required in order to make subsequent calls to open streams. Typically, the client driver registers itself in the driver'sEVT_WDF_DEVICE_PREPARE_HARDWAREevent callback routine.
WDM drivers: **Call the [USBD_CreateHandle**](https://msdn.microsoft.com/library/windows/hardware/hh406241) routine and obtain a USBD handle for the driver's registration with the USB driver stack.
Configured the device and obtained a WDFUSBPIPE pipe handle to the bulk endpoint that supports streams. To obtain the pipe handle, call theWdfUsbInterfaceGetConfiguredPipemethod on the current alternate setting of an interface in the selected configuration.
**WDM drivers: **Obtain a USBD pipe handle by sending a select-configuration or select-interface request. For more information, seeHow to Select a Configuration for a USB Device.
Instructions
How to open static streams
Determine whether the underlying USB driver stack and the host controller supports the static streams capability by calling theWdfUsbTargetDeviceQueryUsbCapabilitymethod. Typically, the client driver calls the routine in the driver'sEVT_WDF_DEVICE_PREPARE_HARDWAREevent callback routine.
WDM drivers: **Call the [USBD_QueryUsbCapability](https://msdn.microsoft.com/library/windows/hardware/hh406230) routine. Typically, the driver queries for the capabilities that it wants to use in the driver's start-device routine ([IRP_MN_START_DEVICE.
Provide the following information:
A handle to the USB device object that was retrieved, in a previous call toWdfUsbTargetDeviceCreateWithParameters, for client driver registration.
If the client driver wants to use a particular capability, the driver must first query the underlying USB driver stack to determine whether the driver stack and the host controller support the capability. If the capability is supported, only then, the driver should send a request to use the capability. Some requests require URBs, such as the streams capability (discussed in step 5). For those requests, make sure that you use the same handle to query capabilities and allocate URBs. That is because the driver stack uses handles to track the supported capabilities that a driver can use.
For instance, if you obtained a USBD_HANDLE (by callingUSBD_CreateHandle), query the driver stack by callingUSBD_QueryUsbCapability, and allocate the URB by callingUSBD_UrbAllocate. Pass the same USBD_HANDLE in both those calls.
If you call KMDF methods,WdfUsbTargetDeviceQueryUsbCapabilityandWdfUsbTargetDeviceCreateUrb, specify the same WDFUSBDEVICE handle to the framework target object in those method calls.
The GUID assigned to GUID_USB_CAPABILITY_STATIC_STREAMS.
- An output buffer (pointer to USHORT). Upon completion the buffer is filled with the maximum number of streams (per endpoint) that are supported by the host controller.
- The length, in bytes, of the output buffer. For streams the length is
sizeof (USHORT)
.
Evaluate the returned NTSTATUS value. If the routine completes successfully, returns STATUS_SUCCESS, the static streams capability is supported. Otherwise, the method returns an appropriate error code.
Determine the number of streams to open. The maximum number of streams that can be opened is limited by:
- The maximum number of streams supported by the host controller. That number is received byWdfUsbTargetDeviceQueryUsbCapability(for WDM drivers,USBD_QueryUsbCapability), in the caller-supplied output buffer. The Microsoft-provided USB driver stack supports up to 255 streams.WdfUsbTargetDeviceQueryUsbCapabilitytakes that limitation into consideration while calculating the number of streams. The method never returns a value that is greater than 255.
- The maximum number of streams supported by the endpoint in the device. To get that number, inspect the endpoint companion descriptor (seeUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTORin Usbspec.h). To obtain the endpoint companion descriptor, you must parse the configuration descriptor. To obtain the configuration descriptor, the client driver must call theWdfUsbTargetDeviceRetrieveConfigDescriptormethod. You must use the helper routines,USBD_ParseConfigurationDescriptorExandUSBD_ParseDescriptor. For code example, see the example function named RetrieveStreamInfoFromEndpointDesc inHow to enumerate USB pipes.
To determine the maximum number of streams, choose the lesser of two values supported by the host controller and the endpoint.
- Allocate an array ofUSBD_STREAM_INFORMATIONstructures withnelements, wherenis the number of streams to open. The client driver is responsible for releasing this array after the driver is finished using streams.
Allocate an URB for the open-streams request by calling theWdfUsbTargetDeviceCreateUrbmethod. If the call completes successfully, the method retrieves a WDF memory object and the address of theURBstructure that is allocated by the USB driver stack.
Format the URB for the open-stream request. The URB uses the_URB_OPEN_STATIC_STREAMSstructure to define the request. To format the URB you need:
- The USBD pipe handle to the endpoint. If you have a WDF pipe object, you can obtain the USBD pipe handle by calling theWdfUsbTargetPipeWdmGetPipeHandlemethod.
- The stream array (created in step 4)
- A pointer to theURBstructure (created in step 5).
To format the URB, callUsbBuildOpenStaticStreamsRequestand pass the required information as parameter values. Make sure that the number of streams specified toUsbBuildOpenStaticStreamsRequestdoes not exceed the maximum number of supported streams.
Send the URB as a WDF request object by calling theWdfRequestSendmethod. To send the request synchronously, call theWdfUsbTargetDeviceSendUrbSynchronouslymethod instead.
**WDM drivers: **Associate the URB with an IRP, and submit the IRP to the USB driver stack. For more information, seeHow to Submit an URB.
After the request is complete, check the status of the request.
If USB driver stack fails the request, the URB status contains the relevant error code. Some common failure conditions are described in the Remarks section.
If the status of the request (IRP or the WDF request object) indicates USBD_STATUS_SUCCESS, the request was completed successfully. Inspect the array ofUSBD_STREAM_INFORMATIONstructures received on completion. The array is filled with information about the requested streams. The USB driver stack populates each structure in the array with stream information, such as handles (received as USBD_PIPE_HANDLE), stream identifiers, and the maximum number transfer size. Streams are now open to transfer data.
For an open-streams request, you'll need to allocate an URB and an array. The client driver must release the URB by calling theWdfObjectDeletemethod on the associated WDF memory object, after the open streams request completes. If the driver sent the request synchronously by callingWdfUsbTargetDeviceSendUrbSynchronously, then it must release the WDF memory object, after the method returns. If the client driver sent the request asynchronously by callingWdfRequestSend, the driver must release the WDF memory object in the driver-implemented completion routine associated with the request.
The stream array can be released after the client driver is finished using streams or has stored them for I/O requests. In the code example included in this topic, the driver stores the streams array in the device context. The driver releases the device context just before releasing the device object is removed.
How to transfer data to a particular stream
To send a data transfer request to a particular stream, you'll need a WDF request object. Typically, the client driver is not required 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. That 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 the data transfer URB and send it down to the USB driver stack.
If the client driver does not receive a WDF request object from the framework and wants to send the request asynchronously, the driver must allocate a WDF request object by calling theWdfRequestCreatemethod. Format the new object by callingWdfUsbTargetPipeFormatRequestForUrb, and the send the request by callingWdfRequestSend.
In the synchronous cases, passing a WDF request object is optional.
To transfer data to streams, you must use URBs. The URB must be formatted by callingWdfUsbTargetPipeFormatRequestForUrb.
The following WDF methods arenotsupported for streams:
The following procedure assumes that the client driver receives the request object from framework.
- Allocate an URB by callingWdfUsbTargetDeviceCreateUrb. This method allocates a WDF memory object that contains the newly allocated URB. The client driver can choose to allocate an URB for every I/O request or allocate an URB and reuse it for the same type of request.
- Format the URB for a bulk transfer by callingUsbBuildInterruptOrBulkTransferRequest. In thePipeHandleparameter, specify the handle to the stream. The stream handles were obtained in a previous request, described in theHow to open static streamssection.
- Format the WDF request object by calling theWdfUsbTargetPipeFormatRequestForUrbmethod. In the call, specify the WDF memory object that contains the data transfer URB. The memory object was allocated in step 1.
- Send the URB as a WDF request either by callingWdfRequestSendorWdfUsbTargetPipeSendUrbSynchronously. If you callWdfRequestSend, you must specify a completion routine by callingWdfRequestSetCompletionRoutineso that the client driver can get notified when the asynchronous operation is complete. You must free the data transfer URB in the completion routine.
WDM drivers: **Allocate an URB by calling [USBD_UrbAllocate_URB_BULK_OR_INTERRUPT_TRANSFERUsbBuildInterruptOrBulkTransferRequest](https://msdn.microsoft.com/library/windows/hardware/ff538953) or format the URB structure manually. Specify the handle to the stream in the URB's **UrbBulkOrInterruptTransfer.PipeHandlemember.
How to close static streams
The client driver can close streams after the driver is finished using them. However, the close-stream request is optional. The USB driver stack closes all streams when the endpoint associated with the streams is de-configured. An endpoint is de-configured when an alternate configuration or interface is selected, the device is removed, and so on. A client driver must close streams if the driver wants to open a different number of streams. To send a close-stream request:
Format the URB for the close-streams request. TheUrbPipeRequestmember of theURBstructure is an_URB_PIPE_REQUESTstructure. Fill in its members as follows:
- TheHdrmember of_URB_PIPE_REQUESTmust be URB_FUNCTION_CLOSE_STATIC_STREAMS
- ThePipeHandlemember must be the handle to the endpoint that contains the open streams in use.
The close-handle request closes all streams that were previously opened by the client driver. The client driver cannot use the request to close specific streams in the endpoint.
Remarks
Best practices for sending a static streams request
The USB driver stack performs a number of validations on the received URB. To avoid validation errors, the client driver must consider the following:
- Do not send an open-stream or close-stream request to an endpoint that does not support streams. CallWdfUsbTargetDeviceQueryUsbCapability(for WDM drivers,USBD_QueryUsbCapability) to determine static streams support and only send streams requests if the endpoint supports them.
- Do not request a number (of streams to open) that exceeds the maximum number of streams supported 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's endpoint.
- Do not send an open-stream request to an endpoint that already has open streams.
- Do not send a close-stream request to an endpoint that does not have open streams.
- After static streams are open for an endpoint, do not send I/O requests by using the endpoint pipe handle that was obtained through a select-configuration or select-interface requests. This is true even if the static streams have been closed.
Reset and abort pipe operations
At times, transfers to or from an endpoint can fail. Such failures can result from an error condition on the endpoint or host controller, such as a stall or halt condition. To clear the error condition, the client driver first cancels pending transfers and then resets the pipe with which the endpoint is associated. To cancel pending transfers, the client driver can send an abort-pipe request. To reset a pipe, the client driver must send a reset-pipe request.
In the case of stream transfers, both abort-pipe and reset-pipe requests are not supported for individual streams associated with the bulk endpoint. If a transfer fails on a particular stream pipe, the host controller stops transfers on all other pipes (for other streams). To recover from the error condition, the client driver should manually cancel transfers to each stream. Then, the client driver must send a reset-pipe request by using the pipe handle to bulk endpoint. For that request, the client driver must specify the pipe handle to the endpoint in a_URB_PIPE_REQUESTstructure 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 streams.
CopyNTSTATUS
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;
}