如何傳送 USB 大量傳輸要求
本主題提供 USB 大量傳輸的簡短概觀。 它也提供客戶端驅動程式如何從裝置傳送和接收大量數據的逐步指示。
關於大量端點
USB 大量端點可以傳輸大量數據。 大量傳輸很可靠,允許硬體錯誤偵測,而且涉及硬體中的重試次數有限。 對於大量端點的傳輸,頻寬不會保留於總線上。 當有多個以不同類型的端點為目標的傳輸要求時,控制器會先排程傳輸時間關鍵數據,例如時序和中斷封包。 只有在總線上有未使用的頻寬時,控制器才會排程大量傳輸。 在總線上沒有其他重大流量的情況下,大量傳輸可能很快。 不過,當公交車忙於其他傳輸時,大量數據可能會無限期地等候。
以下是大量端點的主要功能:
- 大量端點是選擇性的。 想要傳輸大量數據的 USB 裝置支援它們。 例如,將檔案傳輸至快閃磁碟驅動器、從印表機或掃描器來回傳輸數據。
- USB 全速、高速和超級裝置支援大量端點。 低速裝置不支援大量端點。
- 端點是單向的,而且數據可以透過 IN 或 OUT 方向傳輸。 大量 IN 端點可用來將資料從裝置讀取到主機,並使用大量 OUT 端點將數據從主機傳送至裝置。
- 端點具有CRC位來檢查錯誤,因而提供數據完整性。 針對CRC錯誤,會自動重新傳輸數據。
- SuperSpeed 大量端點可以支援串流。 數據流可讓主機將傳輸傳送至個別串流管道。
- 大量端點的封包大小上限取決於裝置的總線速度。 針對完整速度、高速和超級速度;封包大小上限分別為 64、512 和 1024 個字節。
大量交易
如同所有其他 USB 傳輸,主機一律會起始大量傳輸。 主機與目標端點之間會進行通訊。 USB 通訊協定不會對大量交易中傳送的數據強制執行任何格式。
主機和裝置在總線上的通訊方式取決於裝置連線的速度。 本節說明顯示主機與裝置之間通訊的高速和超速度大量傳輸的一些範例。
您可以使用任何 USB 分析器來查看交易和封包的結構,例如 Beagle、Ellisys、LeCroy USB 通訊協定分析器。 分析器裝置顯示如何透過網路將數據傳送至 USB 裝置或從 USB 裝置接收數據。 在此範例中,讓我們檢查 LeCroy USB 分析器所擷取的一些追蹤。 此範例僅供參考。 這不是 Microsoft 的簽署。
大量 OUT 交易範例
此分析器追蹤會以高速顯示大量 OUT 交易的範例。
在上述追蹤中,主機會藉由將 PID 設定為 OUT 的令牌封包傳送至 OUT (OUT 令牌) ,來起始大量 OUT 傳輸至高速大量端點。 封包包含裝置和目標端點的位址。 在 OUT 封包之後,主機會傳送包含大量承載的數據封包。 如果端點接受傳入數據,則會傳送 ACK 封包。 在此範例中,我們可以看到主機已將 31 個字節傳送至裝置位址:1;端點位址:2。
如果端點在數據封包送達且無法接收數據時忙碌中,裝置可以傳送 NAK 封包。 在此情況下,主機會開始將 PING 封包傳送至裝置。 只要裝置尚未準備好接收數據,裝置就會以 NAK 封包回應。 當裝置就緒時,它會以 ACK 封包回應。 主機接著可以繼續 OUT 傳輸。
此分析器追蹤會顯示範例 SuperSpeed 大量 OUT 交易。
在上述追蹤中,主機會藉由傳送數據封包,將 OUT 交易起始至 SuperSpeed 大量端點。 數據封包包含大量承載、裝置和端點位址。 在此範例中,我們可以看到主機已將 31 個字節傳送至裝置位址:4;端點位址:2。
裝置會接收並認可數據封包,並將 ACK 封包傳回主機。 如果端點在數據封包送達且無法接收數據時忙碌中,裝置可以傳送 NRDY 封包。 不同於高速,在接收NRDY封包之後,主機不會重複輪詢裝置。 相反地,主機會從裝置等候 ERDY。 當裝置準備就緒時,它會傳送 ERDY 封包,而主機接著可以將數據傳送至端點。
大量 IN 交易範例
此分析器追蹤會以高速顯示大量 IN 交易的範例。
在上述追蹤中,主機會藉由將 PID 設定為 IN 的令牌封包傳送至 IN (IN 令牌來起始交易) 。 裝置接著會傳送具有大量承載的數據封包。 如果端點沒有要傳送的數據或尚未準備好傳送數據,裝置可以傳送 NAK 交握封包。 主機會重試 IN 傳輸,直到它從裝置收到 ACK 封包為止。 該 ACK 封包表示裝置已接受數據。
此分析器追蹤會顯示範例 SuperSpeed 大量 IN 交易。
若要從 SuperSpeed 端點起始大量 IN 傳輸,主機會藉由傳送 ACK 封包來啟動大量交易。 USB 規格 3.0 版會將 ACK 和 IN 封包合併成一個 ACK 封包,以優化傳輸的初始部分。 主機不會針對 SuperSpeed 傳送 IN 令牌,而是傳送 ACK 令牌來起始大量傳輸。 裝置會以數據封包回應。 主機接著會藉由傳送 ACK 封包來認可數據封包。 如果端點忙碌且無法傳送數據,裝置可以傳送NRDY的狀態。 在此情況下,主機會等到它從裝置取得 ERDY 封包為止。
大量傳輸的 USB 用戶端驅動程式工作
主機上的應用程式或驅動程式一律會起始大量傳輸來傳送或接收數據。 用戶端驅動程式會將要求提交至 USB 驅動程式堆疊。 USB 驅動程式堆疊會將要求程式傳送到主機控制器,然後將通訊協定封包傳送 (,如上一節所述) 透過網路傳送到裝置。
讓我們看看客戶端驅動程式如何提交大量傳輸的要求,因為應用程式或其他驅動程式的要求。 或者,驅動程式可以自行起始傳輸。 不論方法為何,驅動程式都必須有傳輸緩衝區和要求,才能起始大量傳輸。
對於 KMDF 驅動程式,要求會在架構要求對象中說明, (請參閱 WDF 要求對象參考) 。 用戶端驅動程式會藉由指定 WDFREQUEST 句柄,將要求傳送至 USB 驅動程式堆疊,以呼叫要求物件的方法。 如果客戶端驅動程式正在傳送大量傳輸以回應應用程式或其他驅動程式的要求,架構會建立要求物件,並使用架構佇列物件將要求傳遞至用戶端驅動程式。 在此情況下,客戶端驅動程式可能會使用該要求來傳送大量傳輸。 如果客戶端驅動程式起始要求,驅動程式可以選擇配置自己的要求物件。
如果應用程式或其他驅動程式傳送或要求的數據,傳輸緩衝區會由架構傳遞至驅動程式。 或者,如果驅動程式自行起始傳輸,用戶端驅動程式可以配置傳輸緩衝區,並建立要求物件。
以下是客戶端驅動程式的主要工作:
- 取得傳輸緩衝區。
- 取得、格式化和傳送架構要求物件至USB驅動程式堆疊。
- 實作完成例程,以在USB驅動程式堆疊完成要求時收到通知。
本主題說明這些工作,方法是使用驅動程式起始大量傳輸,因為應用程式要求傳送或接收數據。
若要從裝置讀取數據,用戶端驅動程式可以使用提供的架構連續讀取器物件。 如需詳細資訊,請參閱 如何使用連續讀取器從USB管道讀取數據。
大量傳輸要求範例
假設有一個範例案例,其中應用程式想要讀取或寫入裝置的數據。 應用程式會呼叫 Windows API 來傳送這類要求。 在此範例中,應用程式會使用驅動程式在內核模式中發佈的裝置介面 GUID,開啟裝置的句柄。 然後,應用程式會呼叫 ReadFile 或 WriteFile 來起始讀取或寫入要求。 在該呼叫中,應用程式也會指定要讀取或寫入之數據的緩衝區,以及該緩衝區的長度。
I/O 管理員會收到要求、建立 I/O 要求封包 (IRP) ,並將它轉送至客戶端驅動程式。
架構會攔截要求、建立架構要求物件,並將它新增至架構佇列物件。 架構接著會通知客戶端驅動程式新要求正在等候處理。 該通知是藉由叫用 EvtIoRead 或 EvtIoWrite 的驅動程式佇列回呼例程來完成。
當架構將要求傳遞至用戶端驅動程式時,它會收到下列參數:
- 包含要求的架構佇列物件的 WDFQUEUE 句柄。
- 包含此要求詳細數據之架構要求物件的 WDFREQUEST 句柄。
- 傳輸長度,也就是要讀取或寫入的位元組數目。
在客戶端驅動程式的 EvtIoRead 或 EvtIoWrite 實作中,驅動程式會檢查要求參數,並可選擇性地執行驗證檢查。
如果您使用 SuperSpeed 大量端點的數據流,您會在 URB 中傳送要求,因為 KMDF 本身不支援數據流。 如需提交傳送至大量端點數據流之要求的相關信息,請參閱 如何在USB大量端點中開啟和關閉靜態數據流。
如果您未使用數據流,您可以使用 KMDF 定義的方法來傳送要求,如下列程式所述:
必要條件
開始之前,請確定您有此資訊:
用戶端驅動程序必須已建立架構 USB 目標裝置物件,並藉由呼叫 WdfUsbTargetDeviceCreateWithParameters 方法來取得 WDFUSBDEVICE 句柄。
如果您使用提供給 Microsoft Visual Studio Professional 2012 的 USB 範本,範本程式碼會執行這些工作。 範本程式代碼會取得目標裝置物件的句柄,並儲存在裝置內容中。 如需詳細資訊,請參閱 瞭解USB用戶端驅動程式程式代碼結構 (KMDF) 中的。
包含此要求詳細數據之架構要求物件的 WDFREQUEST 句柄。
要讀取或寫入的位元組數目。
與目標端點相關聯的架構管道物件的WDFUSBPIPE句柄。 您必須藉由列舉管道,在裝置設定期間取得管道句柄。 如需詳細資訊,請參閱 如何列舉 USB 管道。
如果大量端點支持數據流,您必須擁有數據流的管道句柄。 如需詳細資訊,請參閱 如何在USB大量端點中開啟和關閉靜態數據流。
步驟 1:取得傳輸緩衝區
傳輸緩衝區或傳輸緩衝區 MDL 包含要傳送或接收的數據。 本主題假設您要在傳輸緩衝區中傳送或接收數據。 傳輸緩衝區會在 WDF 記憶體物件中描述, (請參閱 WDF 記憶體物件參考) 。 若要取得與傳輸緩衝區相關聯的記憶體物件,請呼叫下列其中一種方法:
- 如需大量 IN 傳輸要求,請呼叫 WdfRequestRetrieveOutputMemory 方法。
- 如需大量 OUT 傳輸要求,請呼叫 WdfRequestRetrieveInputMemory 方法。
用戶端驅動程式不需要釋放此記憶體。 記憶體會與父要求對象相關聯,並在釋放父代時釋放。
步驟 2:格式化架構要求物件並將物件傳送至 USB 驅動程式堆疊
您可以異步或同步傳送傳送要求。
這些是異步方法:
此清單中的方法會格式化要求。 如果您以異步方式傳送要求,請呼叫 WdfRequestSetCompletionRoutine 方法來設定驅動程式實作完成例程的指標, (下一個步驟) 中所述。 若要傳送要求,請呼叫 WdfRequestSend 方法。
如果您以同步方式傳送要求,請呼叫下列方法:
如需程式代碼範例,請參閱這些方法參考主題的一節。
步驟 3:實作要求的完成例程
如果要求是以異步方式傳送,您必須實作完成例程,以在USB驅動程式堆疊完成要求時收到通知。 完成時,架構會叫用驅動程式的完成例程。 架構會傳遞這些參數:
- 要求物件的 WDFREQUEST 句柄。
- 要求之 I/O 目標物件的 WDFIOTARGET 句柄。
- 包含完成資訊 之WDF_REQUEST_COMPLETION_PARAMS 結構的指標。 USB 特定資訊包含在 CompletionParams-Parameters.Usb> 成員中。
- WDFCONTEXT 處理至驅動程式呼叫 WdfRequestSetCompletionRoutine 中指定的內容。
在完成例程中,執行下列工作:
取得 CompletionParams-IoStatus.Status> 值,以檢查要求的狀態。
檢查 USB 驅動程式堆疊所設定的 USBD 狀態。
如果是管道錯誤,請執行錯誤復原作業。 如需詳細資訊,請參閱 如何從USB管道錯誤復原。
檢查傳輸的位元組數目。
當要求的位元組數目已傳送到裝置或從裝置傳送時,就會完成大量傳輸。 如果您藉由呼叫 KMDF 方法傳送要求緩衝區,請檢查 CompletionParams-Parameters.Usb.Completion-Parameters.PipeWrite.Length>> 或 CompletionParams-Parameters.Usb.Completion-Parameters.PipeRead.Length>> 成員中收到的值。
在 USB 驅動程式堆疊傳送一個資料封包中所有要求的位元組的簡單傳輸中,您可以檢查 [長度 ] 值與要求的位元組數目進行比較。 如果 USB 驅動程式堆疊在多個資料封包中傳輸要求,您必須追蹤已傳輸的位元元組數目和剩餘的位元組數目。
如果已傳輸位元組總數,請完成要求。 如果發生錯誤狀況,請使用傳回的錯誤碼來完成要求。 呼叫 WdfRequestComplete 方法來完成要求。 如果您想要設定資訊,例如傳輸的位元元數目,請呼叫 WdfRequestCompleteWithInformation。
請確定當您以資訊完成要求時,位元組數目必須等於或小於要求的位元元組數目。 架構會驗證這些值。 如果已完成要求中所設定的長度大於原始要求長度,就會發生錯誤檢查。
此範例程式代碼示範客戶端驅動程式如何提交大量傳輸要求。 驅動程式會設定完成例程。 該例程會顯示在下一個程式代碼區塊中。
/*++
Routine Description:
This routine sends a bulk write request to the
USB driver stack. The request is sent asynchronously and
the driver gets notified through a completion routine.
Arguments:
Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.
Return Value:
VOID
--*/
VOID Fx3EvtIoWrite(
IN WDFQUEUE Queue,
IN WDFREQUEST Request,
IN size_t Length
)
{
NTSTATUS status;
WDFUSBPIPE pipe;
WDFMEMORY reqMemory;
PDEVICE_CONTEXT pDeviceContext;
pDeviceContext = GetDeviceContext(WdfIoQueueGetDevice(Queue));
pipe = pDeviceContext->BulkWritePipe;
status = WdfRequestRetrieveInputMemory(
Request,
&reqMemory
);
if (!NT_SUCCESS(status))
{
goto Exit;
}
status = WdfUsbTargetPipeFormatRequestForWrite(
pipe,
Request,
reqMemory,
NULL
);
if (!NT_SUCCESS(status))
{
goto Exit;
}
WdfRequestSetCompletionRoutine(
Request,
BulkWriteComplete,
pipe
);
if (WdfRequestSend( Request,
WdfUsbTargetPipeGetIoTarget(pipe),
WDF_NO_SEND_OPTIONS) == FALSE)
{
status = WdfRequestGetStatus(Request);
goto Exit;
}
Exit:
if (!NT_SUCCESS(status)) {
WdfRequestCompleteWithInformation(
Request,
status,
0
);
}
return;
}
此範例程式代碼顯示大量傳輸的完成例程實作。 用戶端驅動程式會在完成例程中完成要求,並設定此要求資訊:狀態和傳輸的位元元組數目。
/*++
Routine Description:
This completion routine is invoked by the framework when
the USB drive stack completes the previously sent
bulk write request. The client driver completes the
the request if the total number of bytes were transferred
to the device.
In case of failure it queues a work item to start the
error recovery by resetting the target pipe.
Arguments:
Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.
Pipe - Handle to the pipe that is the target for this request.
Return Value:
VOID
--*/
VOID BulkWriteComplete(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
_In_ WDFCONTEXT Context
)
{
PDEVICE_CONTEXT deviceContext;
size_t bytesTransferred=0;
NTSTATUS status;
UNREFERENCED_PARAMETER (Target);
UNREFERENCED_PARAMETER (Context);
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"In completion routine for Bulk transfer.\n"));
// Get the device context. This is the context structure that
// the client driver provided when it sent the request.
deviceContext = (PDEVICE_CONTEXT)Context;
// Get the status of the request
status = CompletionParams->IoStatus.Status;
if (!NT_SUCCESS (status))
{
// Get the USBD status code for more information about the error condition.
status = CompletionParams->Parameters.Usb.Completion->UsbdStatus;
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Bulk transfer failed. 0x%x\n",
status));
// Queue a work item to start the reset-operation on the pipe
// Not shown.
goto Exit;
}
// Get the actual number of bytes transferred.
bytesTransferred =
CompletionParams->Parameters.Usb.Completion->Parameters.PipeWrite.Length;
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Bulk transfer completed. Transferred %d bytes. \n",
bytesTransferred));
Exit:
// Complete the request and update the request with
// information about the status code and number of bytes transferred.
WdfRequestCompleteWithInformation(Request, status, bytesTransferred);
return;
}