在 USB 2.0 和更早版本的設備中,批量端點可以通過該端點發送或接收單個數據流。 在 USB 3.0 設備中,批量端點能夠通過該端點發送和接收多個數據流。
Windows 中 Microsoft 提供的 USB 驅動程序堆棧支持多個流。 這使客戶端驅動程序能夠將獨立的 I/O 請求發送到與 USB 3.0 設備中的批量端點關聯的每個流,不會序列化對不同流的請求。
對于客戶端驅動程序,流表示具有相同特征集的多個邏輯端點。 若要將請求發送到特定流,客戶端驅動程序需要該流的句柄 (類似于端點) 的管道句柄。 流 I/O 請求的 URB 類似于針對批量端點的 I/O 請求的 URB。 唯一的區別是管道句柄。 若要向流發送 I/O 請求,驅動程序會指定流中的管道句柄。
在設備配置期間,客戶端驅動程序發送選擇配置請求和選擇接口請求(可選)。 這些請求檢索接口的活動設置中定義的端點的一組管道句柄。 對于支持流的端點,端點管道句柄可用于將 I/O 請求發送到默認流 (第一個流) ,直到驅動程序打開流 。
如果客戶端驅動程序想要將請求發送到默認流以外的流,則驅動程序必須打開并獲取所有流的句柄。 為此,客戶端驅動程序通過指定要 打開的流 數來發送開放流請求。 客戶端驅動程序使用完流后,驅動程序可以選擇通過發送 關閉流請求來關閉它們。
內核模式驅動程序框架 (KMDF) 本身不支持靜態流。 客戶端驅動程序必須使用 Windows 驅動程序模型 (WDM) ,以打開和關閉流。 用戶模式驅動程序框架 (UMDF) 客戶端驅動程序無法使用靜態流功能。
下面可能包含一些標記為 WDM 驅動程序的注釋。 這些說明描述了想要發送流請求的基于 WDM 的 USB 客戶端驅動程序的例程。
先決條件
在客戶端驅動程序可以打開或關閉流之前,驅動程序必須具有:
1. 調用 WdfUsbTargetDeviceCreateWithParameters 方法。方法要求USBD_CLIENT_CONTRACT_VERSION_602客戶端協定版本。 通過指定該版本,客戶端驅動程序必須遵守一組規則。?
調用檢索框架的 USB 目標設備對象的 WDFUSBDEVICE 句柄。 需要該句柄才能對打開的流進行后續調用。 通常,客戶端驅動程序在驅動程序的 EVT_WDF_DEVICE_PREPARE_HARDWARE 事件回調例程中注冊自身。
WDM 驅動程序: 調用 USBD_CreateHandle 例程并獲取 USB 驅動程序堆棧中驅動程序注冊的 USBD 句柄。
2. 配置了設備并獲取了支持流的批量端點的 WDFUSBPIPE 管道句柄。 若要獲取管道句柄,請在所選配置中的接口的當前備用設置上調用 WdfUsbInterfaceGetConfiguredPipe 方法。
WDM 驅動程序: 通過發送 select-configuration 或 select-interface 請求獲取 USBD 管道句柄。?
如何打開靜態流
1.通過調用 WdfUsbTargetDeviceQueryUsbCapability 方法,確定基礎 USB 驅動程序堆棧和主機控制器是否支持靜態流功能。 通常,客戶端驅動程序在驅動程序的 EVT_WDF_DEVICE_PREPARE_HARDWARE 事件回調例程中調用例程。
WDM 驅動程序: 調用 USBD_QueryUsbCapability 例程。 通常,驅動程序會查詢要在驅動程序的啟動設備例程中使用的功能, (IRP_MN_START_DEVICE) 。?
提供以下信息:
- 在先前調用 WdfUsbTargetDeviceCreateWithParameters 時檢索到的 USB 設備對象的句柄,用于注冊客戶端驅動程序。
WDM 驅動程序: 將上一次調用中檢索到的 USBD 句柄傳遞給 USBD_CreateHandle。
如果客戶端驅動程序想要使用特定功能,則驅動程序必須首先查詢基礎 USB 驅動程序堆棧,以確定驅動程序堆棧和主機控制器是否支持該功能。 如果支持該功能,則只有這樣,驅動程序才應發送使用該功能的請求。 某些請求需要 URB,例如步驟 5 中 討論的流功能。 對于這些請求,請確保使用相同的句柄來查詢功能和分配 URB。 這是因為驅動程序堆棧使用句柄來跟蹤驅動程序可以使用的受支持功能。
例如,如果通過調用 USBD_CreateHandle獲取 了USBD_HANDLE ,則通過調用 USBD_QueryUsbCapability 查詢驅動程序堆棧,并通過調用 USBD_UrbAllocate 來分配 URB。 在這兩個調用中傳遞相同的USBD_HANDLE。
如果調用 KMDF 方法、 WdfUsbTargetDeviceQueryUsbCapability 和 WdfUsbTargetDeviceCreateUrb,請在這些方法調用中為框架目標對象指定相同的 WDFUSBDEVICE 句柄。
- 分配給GUID_USB_CAPABILITY_STATIC_STREAMS的 GUID;
- 輸出緩沖區 (指向 USHORT) 的指針。 完成后,緩沖區將填充主機控制器支持的每個端點 (的最大流數) ;
- 輸出緩沖區的長度,以字節表示。 對于流,長度為 sizeof (USHORT);
2.評估返回的 NTSTATUS 值。 如果例程成功完成,則返回STATUS_SUCCESS,則支持靜態流功能。 否則,該方法將返回相應的錯誤代碼。
3.確定要打開的流數。 可打開的最大流數受以下限制:
- 主機控制器支持的最大流數。 WdfUsbTargetDeviceQueryUsbCapability (接收調用方提供的輸出緩沖區中的 WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability) 。 Microsoft 提供的 USB 驅動程序堆棧最多支持 255 個流。 WdfUsbTargetDeviceQueryUsbCapability 在計算流數時考慮了該限制。 方法永遠不會返回大于 255 的值。
- 設備中的端點支持的最大流數。 若要獲取該數字,請檢查端點配套描述符 (在 Usbspec.h ) 中查看USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR。 若要獲取端點配套描述符,必須分析配置描述符。 若要獲取配置描述符,客戶端驅動程序必須調用 WdfUsbTargetDeviceRetrieveConfigDescriptor 方法。 必須使用幫助程序例程, USBD_ParseConfigurationDescriptorEx 和 USBD_ParseDescriptor。?
若要確定流的最大數目,請選擇主機控制器和端點支持的兩個值中的較小一個。
4.分配包含 n 個元素的USBD_STREAM_INFORMATION結構的數組,其中 n 是要打開的流數。 客戶端驅動程序負責在驅動程序使用完流后釋放此數組。
5.通過調用 WdfUsbTargetDeviceCreateUrb 方法為開放流請求分配 URB。 如果調用成功完成,該方法將檢索 WDF 內存對象以及 USB 驅動程序堆棧分配的 URB 結構的地址。
WDM 驅動程序: 調用 USBD_UrbAllocate 例程。
6.設置開放流請求的 URB 格式。 URB 使用 _URB_OPEN_STATIC_STREAMS 結構來定義請求。 若要設置 URB 的格式,需要:
- 指向端點的 USBD 管道句柄。 如果有 WDF 管道對象,可以通過調用 WdfUsbTargetPipeWdmGetPipeHandle 方法獲取 USBD 管道句柄。
- 在步驟 4 中創建 (流數組)
- 指向 (步驟 5) 中創建的 URB 結構的指針。
若要設置 URB 的格式,請調用 UsbBuildOpenStaticStreamsRequest 并將所需的信息作為參數值傳遞。 確保指定到 UsbBuildOpenStaticStreamsRequest 的流數不超過支持的最大流數。
7.通過調用 WdfRequestSend 方法將 URB 作為 WDF 請求對象發送。 若要以同步方式發送請求,請改為調用 WdfUsbTargetDeviceSendUrbSynchronously 方法。
WDM 驅動程序: 將 URB 與 IRP 相關聯,并將 IRP 提交到 USB 驅動程序堆棧。
8.請求完成后,檢查請求的狀態。如果 USB 驅動程序堆棧請求失敗,則 URB 狀態包含相關的錯誤代碼。
如果請求的狀態 (IRP 或 WDF 請求對象) 指示USBD_STATUS_SUCCESS,則表示請求已成功完成。 檢查完成時收到的 USBD_STREAM_INFORMATION 結構的數組。 數組中填充了有關所請求流的信息。 USB 驅動程序堆棧使用流信息填充數組中的每個結構,例如USBD_PIPE_HANDLE?接收的句柄 、流標識符和最大數字傳輸大小。 流現在可傳輸數據。
對于開放流請求,需要分配 URB 和數組。 在打開的流請求完成后,客戶端驅動程序必須通過在關聯的 WDF 內存對象上調用 WdfObjectDelete 方法來釋放 URB。 如果驅動程序通過調用 WdfUsbTargetDeviceSendUrbSynchronously 以同步方式發送請求,則必須在方法返回后釋放 WDF 內存對象。 如果客戶端驅動程序通過調用 WdfRequestSend 異步發送了請求,則驅動程序必須在與請求關聯的驅動程序實現的完成例程中釋放 WDF 內存對象。
可以在客戶端驅動程序使用完流后釋放流數組,或者為 I/O 請求存儲流數組。 在下面中包含的代碼示例中,驅動程序將流數組存儲在設備上下文中。 驅動程序在釋放設備對象之前釋放設備上下文。
如何將數據傳輸到特定流
若要向特定流發送數據傳輸請求,需要 WDF 請求對象。 通常,客戶端驅動程序不需要分配 WDF 請求對象。 當 I/O 管理器收到來自應用程序的請求時,I/O 管理器會為該請求創建 IRP。 該 IRP 被框架截獲。 然后,框架分配一個 WDF 請求對象來表示 IRP。 之后,框架將 WDF 請求對象傳遞給客戶端驅動程序。 然后,客戶端驅動程序可以將請求對象與數據傳輸 URB 相關聯,并將其發送到 USB 驅動程序堆棧。
如果客戶端驅動程序未從框架接收 WDF 請求對象,并且想要以異步方式發送請求,則驅動程序必須通過調用 WdfRequestCreate 方法分配 WDF 請求對象。 通過調用 WdfUsbTargetPipeFormatRequestForUrb 設置新對象的格式,并通過調用 WdfRequestSend 發送請求。
在同步情況下,傳遞 WDF 請求對象是可選的。
若要將數據傳輸到流,必須使用 URB。 必須通過調用 WdfUsbTargetPipeFormatRequestForUrb 設置 URB 的格式。
流 不支持 以下 WDF 方法:
- WdfUsbTargetPipeFormatRequestForRead
- WdfUsbTargetPipeFormatRequestForWrite
- WdfUsbTargetPipeReadSynchronously
- WdfUsbTargetPipeWriteSynchronously
以下過程假定客戶端驅動程序從框架接收請求對象。
- 通過調用 WdfUsbTargetDeviceCreateUrb 來分配 URB。 此方法分配包含新分配的 URB 的 WDF 內存對象。 客戶端驅動程序可以選擇為每個 I/O 請求分配 URB,或分配 URB 并將其用于同一類型的請求。
- 通過調用 UsbBuildInterruptOrBulkTransferRequest 格式化 URB 進行批量傳輸。 在 PipeHandle 參數中,指定流的句柄。 流句柄是在上一個請求中獲取的,如 如何打開靜態流 部分所述。
- 通過調用 WdfUsbTargetPipeFormatRequestForUrb 方法設置 WDF 請求對象的格式。 在調用中,指定包含數據傳輸 URB 的 WDF 內存對象。 在步驟 1 中分配了內存對象。
- 通過調用 WdfRequestSend 或 WdfUsbTargetPipeSendUrbSynchronously 將 URB 作為 WDF 請求發送。 如果調用 WdfRequestSend,則必須通過調用 WdfRequestSetCompletionRoutine 來指定完成例程,以便客戶端驅動程序可以在異步操作完成時收到通知。 必須在完成例程中釋放數據傳輸 URB。
WDM 驅動程序: 通過調用 USBD_UrbAllocate 分配 URB,并格式化它進行批量傳輸 (請參閱 _URB_BULK_OR_INTERRUPT_TRANSFER) 。 若要設置 URB 的格式,可以調用 UsbBuildInterruptOrBulkTransferRequest 或手動設置 URB 結構的格式。 在 URB 的 UrbBulkOrInterruptTransfer.PipeHandle 成員中指定流的句柄。
如何關閉靜態流
客戶端驅動程序可以在驅動程序使用完流后關閉流。 但是,關閉流請求是可選的。 當取消配置與流關聯的端點時,USB 驅動程序堆棧將關閉所有流。 選擇備用配置或接口、刪除設備等時,將取消配置端點。 如果客戶端驅動程序想要打開不同數量的流,則必須關閉流。 發送關閉流請求:
1.通過調用 WdfUsbTargetDeviceCreateUrb 來分配 URB 結構。
2.設置關閉流請求的 URB 格式。 URB 結構的 UrbPipeRequest 成員是_URB_PIPE_REQUEST結構。 按如下所示填寫其成員:
- 必須URB_FUNCTION_CLOSE_STATIC_STREAMS_URB_PIPE_REQUEST的 Hdr 成員
- PipeHandle 成員必須是包含正在使用的打開流的端點的句柄。
3.通過調用 WdfRequestSend 或 WdfUsbTargetDeviceSendUrbSynchronously 將 URB 作為 WDF 請求發送。
關閉句柄請求關閉以前由客戶端驅動程序打開的所有流。 客戶端驅動程序無法使用請求關閉端點中的特定流。
發送靜態流請求的最佳做法
USB 驅動程序堆棧對收到的 URB 執行驗證。 若要避免驗證錯誤,請執行以下操作:
- 不要向不支持流的端點發送開放流或關閉流請求。 調用 WDM 驅動程序的 WdfUsbTargetDeviceQueryUsbCapability (,USBD_QueryUsbCapability) 來確定靜態流支持,并且僅在端點支持時發送流請求。
- 不要請求超過支持的最大流數的流 (打開) ,或者在未指定流數的情況下發送請求。 根據 USB 驅動程序堆棧和設備端點支持的流數確定流數。
- 不要向已具有開放流的端點發送開放流請求。
- 不要向沒有開放流的端點發送關閉流請求。
- 為端點打開靜態流后,請勿使用通過選擇配置或選擇接口請求獲取的端點管道句柄發送 I/O 請求。 即使靜態流已關閉,也是如此。
重置和中止管道操作
有時,傳入或傳出端點的傳輸可能會失敗。 此類故障可能是由于端點或主機控制器上的錯誤條件(例如停止或停止條件)導致的。 為了清除錯誤條件,客戶端驅動程序首先取消掛起的傳輸,然后重置與端點關聯的管道。 若要取消掛起的傳輸,客戶端驅動程序可以發送中止管道請求。 若要重置管道,客戶端驅動程序必須發送重置管道請求。
對于流傳輸,與批量端點關聯的單個流不支持 abort-pipe 和 reset-pipe 請求。 如果特定流管道上的傳輸失敗,主機控制器將停止) 其他流 (的所有其他管道上的傳輸。 若要從錯誤條件中恢復,客戶端驅動程序應手動取消到每個流的傳輸。 然后,客戶端驅動程序必須使用管道句柄向批量端點發送重置管道請求。 對于該請求,客戶端驅動程序必須在 _URB_PIPE_REQUEST 結構中指定端點的管道句柄,并將 URB 函數 (Hdr.Function) 設置為URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL。
完整示例
下面的代碼示例演示如何打開流。
NTSTATUSOpenStreams (_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;
}