如何在 USB 大量端點中開啟和關閉靜態數據流
本文討論 靜態串流功能 ,並說明 USB 用戶端驅動程式如何在 USB 3.0 裝置的大量端點中開啟和關閉串流。
在USB 2.0和更早版本的裝置中,大量端點可以透過端點傳送或接收單一數據流。 在USB 3.0裝置中,大量端點能夠透過端點傳送和接收多個數據流。
Windows 中 Microsoft 提供的 USB 驅動程式堆疊支援多個數據流。 這可讓客戶端驅動程式將獨立 I/O 要求傳送至與 USB 3.0 裝置中大量端點相關聯的每個數據流。 對不同數據流的要求不會串行化。
針對客戶端驅動程式,數據流代表多個具有相同特性集的邏輯端點。 若要將要求傳送至特定數據流,用戶端驅動程式需要該數據流的句柄 (類似於端點) 的管道句柄。 數據流 I/O 要求的 URB 類似於大量端點之 I/O 要求的 URB。 唯一的差異是管道句柄。 若要將 I/O 要求傳送至數據流,驅動程式會指定數據流的管道句柄。
在裝置設定期間,客戶端驅動程式會傳送 select-configuration 要求,並選擇性地傳送 select-interface 要求。 這些要求會擷取一組管道句柄,指向介面的作用中設定中所定義的端點。 對於支持數據流的端點,端點管道句柄可用來將 I/O 要求傳送至 預設數據流 , (第一個數據流) ,直到驅動程式 (下一個) 討論為止。
如果客戶端驅動程式想要將要求傳送至預設數據流以外的數據流,驅動程式必須開啟並取得所有數據流的句柄。 若要這樣做,客戶端驅動程式會藉由指定要開啟的數據流數目來傳送 開放數據流要求 。 使用數據流完成用戶端驅動程序之後,驅動程式可以藉由傳送 關閉數據流要求來選擇性地關閉它們。
核心模式驅動程式架構 (KMDF) 內部不支援靜態數據流。 用戶端驅動程序必須傳送 Windows 驅動程式模型 (WDM) 樣式 URL,以開啟和關閉數據流。 本文說明如何格式化和傳送這些 URL。 使用者模式驅動程式架構 (UMDF) 客戶端驅動程式無法使用靜態數據流功能。
本文包含一些標示為 WDM 驅動程式的附註。 這些附註描述想要傳送串流要求的 WDM 型 USB 用戶端驅動程式例程。
必要條件
在用戶端驅動程式可以開啟或關閉資料流之前,驅動程式必須具有:
呼叫 WdfUsbTargetDeviceCreateWithParameters 方法。
方法需要客戶端合約版本USBD_CLIENT_CONTRACT_VERSION_602。 藉由指定該版本,用戶端驅動程序必須遵守一組規則。 如需詳細資訊,請參閱 最佳做法:使用URL。
呼叫會擷取架構 USB 目標裝置物件的 WDFUSBDEVICE 句柄。 需要該句柄,才能對開啟數據流進行後續呼叫。 一般而言,客戶端驅動程式會在驅動程式 的 EVT_WDF_DEVICE_PREPARE_HARDWARE 事件回呼例程中註冊本身。
WDM 驅動程式: 呼叫 USBD_CreateHandle 例程,並取得USB驅動程式與USB驅動程式堆疊註冊的USBD句柄。
設定裝置並取得 WDFUSBPIPE 管道句柄給支援數據流的大量端點。 若要取得管道句柄,請在所選組態中介面的目前替代設定上呼叫 WdfUsbInterfaceGetConfiguredPipe 方法。
WDM 驅動程式: 傳送 select-configuration 或 select-interface 要求,以取得 USBD 管道句柄。 如需詳細資訊,請參閱 如何選取USB裝置的設定。
如何開啟靜態數據流
呼叫 WdfUsbTargetDeviceQueryUsbCapability 方法,判斷基礎 USB 驅動程式堆疊和主機控制器是否支援靜態數據流功能。 一般而言,客戶端驅動程式會呼叫驅動程式 EVT_WDF_DEVICE_PREPARE_HARDWARE 事件回呼例程中的例程。
WDM 驅動程式: 呼叫 USBD_QueryUsbCapability 例程。 一般而言,驅動程式會查詢其想要在驅動程序啟動裝置例程中使用的功能 , (IRP_MN_START_DEVICE) 。 如需程式代碼範例,請參閱 USBD_QueryUsbCapability。
請提供下列資訊:
在先前呼叫 WdfUsbTargetDeviceCreateWithParameters 以進行用戶端驅動程式註冊時擷取的 USB 裝置物件的句柄。
WDM 驅動程式: 傳遞先前呼叫中所擷取的USBD句柄,以 USBD_CreateHandle。
如果客戶端驅動程式想要使用特定功能,驅動程式必須先查詢基礎 USB 驅動程式堆疊,以判斷驅動程式堆疊和主機控制器是否支援此功能。 如果支援此功能,則驅動程式應該傳送要求以使用此功能。 某些要求需要 URB,例如步驟 5) 中所討論的數據流功能 (。 對於這些要求,請確定您使用相同的句柄來查詢功能和配置 URL。 這是因為驅動程式堆疊會使用句柄來追蹤驅動程式可以使用的支援功能。
例如,如果您藉由呼叫 USBD_CreateHandle) 取得USBD_HANDLE (, 請呼叫 USBD_QueryUsbCapability 查詢驅動程式堆棧,並呼叫 USBD_UrbAllocate 來配置 URB。 在這兩個呼叫中傳遞相同的USBD_HANDLE。
如果您呼叫 KMDF 方法, WdfUsbTargetDeviceQueryUsbCapability 和 WdfUsbTargetDeviceCreateUrb,請在這些方法呼叫中為架構目標物件指定相同的 WDFUSBDEVICE 句柄。
指派給GUID_USB_CAPABILITY_STATIC_STREAMS的 GUID。
輸出緩衝區 (USHORT) 指標。 完成時,緩衝區會填入主機控制器所支援之每個端點) 的最大數據流數目 (。
輸出緩衝區的長度 (以位元組為單位)。 對於資料流,長度為
sizeof (USHORT)
。
評估傳回的NTSTATUS值。 如果例程順利完成,則會傳回STATUS_SUCCESS,則支援靜態數據流功能。 否則,方法會傳回適當的錯誤碼。
決定要開啟的數據流數目。 可以開啟的數據流數目上限受限於:
- 主機控制器支持的數據流數目上限。 WdfUsbTargetDeviceQueryUsbCapability (wDM 驅動程式USBD_QueryUsbCapability) 在呼叫端提供的輸出緩衝區中收到該數位。 Microsoft 提供的 USB 驅動程式堆疊最多可支援 255 個數據流。 WdfUsbTargetDeviceQueryUsbCapability 會在計算數據流數目時考慮該限制。 方法永遠不會傳回大於 255 的值。
- 裝置中端點所支持的數據流數目上限。 若要取得該數位,請檢查端點隨附描述元 (請參閱 Usbspec.h) 中的 USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR 。 若要取得端點隨附描述元,您必須剖析組態描述元。 若要取得設定描述項,用戶端驅動程序必須呼叫 WdfUsbTargetDeviceRetrieveConfigDescriptor 方法。 您必須使用協助程式例程、 USBD_ParseConfigurationDescriptorEx 和 USBD_ParseDescriptor。 如需程式代碼範例,請參閱 如何列舉 USB 管道中名為 RetrieveStreamInfoFromEndpointDesc 的範例函式。
若要判斷數據流數目上限,請選擇主機控制器和端點所支援兩個值的較小數目。
使用 n 元素配置USBD_STREAM_INFORMATION結構的數位,其中 n 是要開啟的數據流數目。 用戶端驅動程式負責在使用數據流完成驅動程式之後釋放此陣列。
呼叫 WdfUsbTargetDeviceCreateUrb 方法,為開放數據流要求配置 URB。 如果呼叫順利完成,方法會擷取 WDF 記憶體物件,以及 USB 驅動程式堆疊所配置 之 URB 結構的位址。
WDM 驅動程式: 呼叫 USBD_UrbAllocate 例程。
格式化開啟數據流要求的 URB。 URB 會使用 _URB_OPEN_STATIC_STREAMS 結構來定義要求。 若要格式化 URB,您需要:
- 端點的 USBD 管道句柄。 如果您有 WDF 管道物件,您可以呼叫 WdfUsbTargetPipeWdmGetPipeHandle 方法來取得 USBD 管道句柄。
- 在步驟 4) 中建立的數據流陣列 (
- 在步驟 5) 中建立之 URB 結構的指標 (。
若要格式化 URB,請呼叫 UsbBuildOpenStaticStreamsRequest 並將必要資訊傳遞為參數值。 請確定指定給 UsbBuildOpenStaticStreamsRequest 的數據流數目不超過支持的數據流數目上限。
呼叫 WdfRequestSend 方法,以 WDF 要求物件的形式傳送 URB。 若要同步傳送要求,請改為呼叫 WdfUsbTargetDeviceSendUrbSynchronously 方法。
WDM 驅動程式: 將 URB 與 IRP 產生關聯,並將 IRP 提交至 USB 驅動程式堆疊。 如需詳細資訊,請參閱 如何提交 URB。
要求完成之後,請檢查要求的狀態。
如果 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。 URB 必須藉由呼叫 WdfUsbTargetPipeFormatRequestForUrb 來格式化。
資料流 不支援 下列 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 驅動程式堆疊會關閉所有數據流。 選取替代組態或介面、移除裝置等等時,就會取消設定端點。 如果驅動程式想要開啟不同數目的數據流,用戶端驅動程式必須關閉數據流。 若要傳送關閉資料流要求:
呼叫 WdfUsbTargetDeviceCreateUrb 來配置 URB 結構。
格式化關閉數據流要求的 URB。 URB 結構的 UrbPipeRequest 成員是_URB_PIPE_REQUEST結構。 填入其成員,如下所示:
- _URB_PIPE_REQUEST的 Hdr 成員必須URB_FUNCTION_CLOSE_STATIC_STREAMS
- PipeHandle 成員必須是包含使用中開啟數據流之端點的句柄。
呼叫 WdfRequestSend 或 WdfUsbTargetDeviceSendUrbSynchronously,以 WDF 要求的形式傳送 URB。
關閉句柄要求會關閉客戶端驅動程式先前開啟的所有數據流。 用戶端驅動程式無法使用要求來關閉端點中的特定數據流。
傳送靜態數據流要求的最佳做法
USB 驅動程式堆疊會在收到的 URB 上執行驗證。 若要避免驗證錯誤:
- 請勿將開啟數據流或關閉數據流要求傳送至不支持數據流的端點。 針對 WDM驅動程式呼叫 WdfUsbTargetDeviceQueryUsbCapability (,USBD_QueryUsbCapability) 判斷靜態數據流支援,而且只有在端點支援時才會傳送數據流要求。
- 請勿要求數據流 (數目以開啟) 超過支持的數據流數目上限,或傳送要求,而不需指定數據流數目。 根據 USB 驅動程式堆疊和裝置端點支援的數據流數目來決定資料流數目。
- 請勿將開啟數據流要求傳送至已開啟數據流的端點。
- 請勿將關閉數據流要求傳送至沒有開啟數據流的端點。
- 開啟端點的靜態數據流之後,請勿使用透過 select-configuration 或 select-interface 要求取得的端點管道句柄來傳送 I/O 要求。 即使靜態數據流已關閉,也是如此。
重設和中止管道作業
有時候,往返端點的傳輸可能會失敗。 這類失敗可能是因為端點或主機控制器發生錯誤狀況,例如停止或停止狀況。 若要清除錯誤狀況,用戶端驅動程式會先取消擱置的傳輸,然後重設端點相關聯的管道。 若要取消擱置的傳輸,客戶端驅動程式可以傳送中止管道要求。 若要重設管道,用戶端驅動程序必須傳送重設管道要求。
對於數據流傳輸,與大量端點相關聯的個別數據流不支援中止管道和重設管道要求。 如果特定數據流管道上的傳輸失敗,主機控制器會停止所有其他管道上的傳輸, (其他數據流) 。 若要從錯誤狀況復原,用戶端驅動程序應該手動取消傳送至每個數據流。 然後,客戶端驅動程式必須使用管道句柄來傳送重設管道要求來大量端點。 針對該要求,用戶端驅動程式必須指定管線句柄至 _URB_PIPE_REQUEST 結構中的端點,並將 URB 函式 (Hdr.Function) 設定為 URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL。
完整範例
下列程式代碼範例示範如何開啟數據流。
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;
}