Share via


USB 대량 전송 요청을 보내는 방법

이 항목에서는 USB 대량 전송에 대한 간략한 개요를 제공합니다. 또한 클라이언트 드라이버가 디바이스에서 대량 데이터를 보내고 받는 방법에 대한 단계별 지침을 제공합니다.

대량 엔드포인트 정보

USB 대량 엔드포인트는 많은 양의 데이터를 전송할 수 있습니다. 대량 전송은 하드웨어 오류 검색을 허용하는 신뢰할 수 있으며 하드웨어에서 제한된 수의 재시도를 포함합니다. 대량 엔드포인트로 전송하는 경우 대역폭은 버스에서 예약되지 않습니다. 여러 유형의 엔드포인트를 대상으로 하는 여러 전송 요청이 있는 경우 컨트롤러는 먼저 등시 및 인터럽트 패킷과 같은 시간에 중요한 데이터에 대한 전송을 예약합니다. 버스에서 사용할 수 있는 사용되지 않는 대역폭이 있는 경우에만 컨트롤러는 대량 전송을 예약합니다. 버스에 다른 중요한 트래픽이 없는 경우 대량 전송이 빠를 수 있습니다. 그러나 버스가 다른 전송으로 사용 중인 경우 대량 데이터는 무기한 대기할 수 있습니다.

다음은 대량 엔드포인트의 주요 기능입니다.

  • 대량 엔드포인트는 선택 사항입니다. 대용량 데이터를 전송하려는 USB 디바이스에서 지원됩니다. 예를 들어 파일을 플래시 드라이브로 전송하고 프린터 또는 스캐너로 데이터를 전송합니다.
  • USB 최고 속도, 고속 및 SuperSpeed 디바이스는 대량 엔드포인트를 지원합니다. 저속 디바이스는 대량 엔드포인트를 지원하지 않습니다.
  • 엔드포인트는 단방향이며 데이터를 IN 또는 OUT 방향으로 전송할 수 있습니다. Bulk IN 엔드포인트는 디바이스에서 호스트로 데이터를 읽는 데 사용되며 대량 OUT 엔드포인트는 호스트에서 디바이스로 데이터를 보내는 데 사용됩니다.
  • 엔드포인트에는 오류에 대한 검사 CRC 비트가 있으므로 데이터 무결성을 제공합니다. CRC 오류의 경우 데이터가 자동으로 다시 전송됩니다.
  • SuperSpeed 대량 엔드포인트는 스트림을 지원할 수 있습니다. 스트림을 사용하면 호스트가 개별 스트림 파이프로 전송을 보낼 수 있습니다.
  • 대량 엔드포인트의 최대 패킷 크기는 디바이스의 버스 속도에 따라 달라집니다. 최고 속도, 고속 및 SuperSpeed; 최대 패킷 크기는 각각 64, 512 및 1024바이트입니다.

대량 트랜잭션

다른 모든 USB 전송과 마찬가지로 호스트는 항상 대량 전송을 시작합니다. 통신은 호스트와 대상 엔드포인트 간에 이루어집니다. USB 프로토콜은 대량 트랜잭션에서 보낸 데이터에 형식을 적용하지 않습니다.

호스트와 디바이스가 버스에서 통신하는 방법은 디바이스가 연결된 속도에 따라 달라집니다. 이 섹션에서는 호스트와 디바이스 간의 통신을 보여 주는 고속 및 SuperSpeed 대량 전송의 몇 가지 예를 설명합니다.

Beagle, Ellisys, LeCroy USB 프로토콜 분석기와 같은 USB 분석기를 사용하여 트랜잭션 및 패킷의 구조를 볼 수 있습니다. 분석기 디바이스는 유선을 통해 USB 디바이스로 데이터를 보내거나 받는 방법을 보여줍니다. 이 예제에서는 LeCroy USB 분석기에서 캡처한 일부 추적을 살펴보겠습니다. 이 예제는 정보 전용입니다. 이는 Microsoft의 보증이 아닙니다.

Bulk OUT 트랜잭션 예제

이 분석기 추적은 빠른 속도로 대량 OUT 트랜잭션의 예를 보여줍니다.

대량 OUT 분석기 트랜잭션 예제의 추적을 보여 주는 스크린샷

이전 추적에서 호스트는 PID가 OUT(OUT 토큰)으로 설정된 토큰 패킷을 전송하여 고속 대량 엔드포인트로 대량 OUT 전송을 시작합니다. 패킷에는 디바이스 및 대상 엔드포인트의 주소가 포함됩니다. OUT 패킷 이후에 호스트는 대량 페이로드가 포함된 데이터 패킷을 보냅니다. 엔드포인트가 들어오는 데이터를 수락하면 ACK 패킷을 보냅니다. 이 예제에서는 호스트가 디바이스 주소:1에 31바이트 전송된 것을 볼 수 있습니다. 엔드포인트 주소: 2.

데이터 패킷이 도착할 때 엔드포인트가 사용 중이고 데이터를 수신할 수 없는 경우 디바이스는 NAK 패킷을 보낼 수 있습니다. 이 경우 호스트는 디바이스에 PING 패킷을 보내기 시작합니다. 디바이스가 데이터를 받을 준비가 되지 않은 한 디바이스는 NAK 패킷으로 응답합니다. 디바이스가 준비되면 ACK 패킷으로 응답합니다. 그런 다음 호스트는 OUT 전송을 다시 시작할 수 있습니다.

이 분석기 추적은 SuperSpeed 대량 OUT 트랜잭션의 예를 보여 줍니다.

SuperSpeed 대량 OUT 데이터 트랜잭션 예제의 추적을 보여 주는 스크린샷

이전 추적에서 호스트는 데이터 패킷을 전송하여 SuperSpeed 대량 엔드포인트에 대한 OUT 트랜잭션을 시작합니다. 데이터 패킷에는 대량 페이로드, 디바이스 및 엔드포인트 주소가 포함됩니다. 이 예제에서는 호스트가 디바이스 주소:4에 31바이트 전송된 것을 확인할 수 있습니다. 엔드포인트 주소: 2.

디바이스는 데이터 패킷을 수신 및 승인하고 ACK 패킷을 호스트로 다시 보냅니다. 데이터 패킷이 도착할 때 엔드포인트가 사용 중이고 데이터를 수신할 수 없는 경우 디바이스는 NRDY 패킷을 보낼 수 있습니다. 고속과 달리 NRDY 패킷을 받은 후 호스트는 디바이스를 반복적으로 폴링하지 않습니다. 대신 호스트는 디바이스에서 ERDY를 기다립니다. 디바이스가 준비되면 ERDY 패킷을 보내고 호스트는 엔드포인트로 데이터를 보낼 수 있습니다.

Bulk IN 트랜잭션 예제

이 분석기 추적은 빠른 속도로 대량 IN 트랜잭션의 예를 보여줍니다.

대량 IN 데이터 트랜잭션 예제의 추적을 보여 주는 스크린샷

이전 추적에서 호스트는 PID가 IN(토큰)으로 설정된 토큰 패킷을 전송하여 트랜잭션을 시작합니다. 그런 다음 디바이스는 대량 페이로드가 있는 데이터 패킷을 보냅니다. 엔드포인트에 보낼 데이터가 없거나 아직 데이터를 보낼 준비가 되지 않은 경우 디바이스는 NAK 핸드셰이크 패킷을 보낼 수 있습니다. 호스트는 디바이스에서 ACK 패킷을 받을 때까지 IN 전송을 다시 시도합니다. 해당 ACK 패킷은 디바이스가 데이터를 수락했음을 의미합니다.

이 분석기 추적은 SuperSpeed 대량 IN 트랜잭션의 예를 보여 줍니다.

예제 데이터 트랜잭션의 추적입니다.

SuperSpeed 엔드포인트에서 대량 IN 전송을 시작하려면 호스트는 ACK 패킷을 전송하여 대량 트랜잭션을 시작합니다. USB 사양 버전 3.0은 ACK 및 IN 패킷을 하나의 ACK 패킷으로 병합하여 전송의 초기 부분을 최적화합니다. 호스트는 IN 토큰 대신 SuperSpeed의 경우 대량 전송을 시작하기 위해 ACK 토큰을 보냅니다. 디바이스가 데이터 패킷으로 응답합니다. 그런 다음 호스트는 ACK 패킷을 전송하여 데이터 패킷을 승인합니다. 엔드포인트가 사용 중이고 데이터를 보낼 수 없는 경우 디바이스는 NRDY의 상태 보낼 수 있습니다. 이 경우 호스트는 디바이스에서 ERDY 패킷을 받을 때까지 기다립니다.

대량 전송을 위한 USB 클라이언트 드라이버 작업

호스트의 애플리케이션 또는 드라이버는 항상 데이터를 보내거나 받기 위해 대량 전송을 시작합니다. 클라이언트 드라이버는 USB 드라이버 스택에 요청을 제출합니다. USB 드라이버 스택은 요청을 호스트 컨트롤러로 프로그래밍한 다음, 유선을 통해 프로토콜 패킷(이전 섹션에 설명된 대로)을 디바이스에 보냅니다.

클라이언트 드라이버가 애플리케이션 또는 다른 드라이버의 요청 결과로 대량 전송 요청을 제출하는 방법을 살펴보겠습니다. 또는 드라이버가 자체적으로 전송을 시작할 수 있습니다. 접근 방식에 관계없이 대량 전송을 시작하려면 드라이버에 전송 버퍼와 요청이 있어야 합니다.

KMDF 드라이버의 경우 요청은 프레임워크 요청 개체에 설명되어 있습니다( WDF 요청 개체 참조 참조). 클라이언트 드라이버는 요청을 USB 드라이버 스택으로 보낼 WDFREQUEST 핸들을 지정하여 요청 개체의 메서드를 호출합니다. 클라이언트 드라이버가 애플리케이션 또는 다른 드라이버의 요청에 대한 응답으로 대량 전송을 보내는 경우 프레임워크는 요청 개체를 만들고 프레임워크 큐 개체를 사용하여 클라이언트 드라이버에 요청을 전달합니다. 이 경우 클라이언트 드라이버는 대량 전송을 보내기 위해 해당 요청을 사용할 수 있습니다. 클라이언트 드라이버가 요청을 시작한 경우 드라이버는 자체 요청 개체를 할당하도록 선택할 수 있습니다.

애플리케이션 또는 다른 드라이버가 데이터를 보내거나 요청한 경우 전송 버퍼는 프레임워크에 의해 드라이버에 전달됩니다. 또는 클라이언트 드라이버가 전송 버퍼를 할당하고 드라이버가 자체적으로 전송을 시작하는 경우 요청 개체를 만들 수 있습니다.

클라이언트 드라이버에 대한 기본 작업은 다음과 같습니다.

  1. 전송 버퍼를 가져옵니다.
  2. 프레임워크 요청 개체를 가져와서 포맷하고 USB 드라이버 스택으로 보냅니다.
  3. 완료 루틴을 구현하여 USB 드라이버 스택이 요청을 완료할 때 알림을 받습니다.

이 항목에서는 애플리케이션의 데이터 전송 또는 수신 요청의 결과로 드라이버가 대량 전송을 시작하는 예제를 사용하여 이러한 작업에 대해 설명합니다.

디바이스에서 데이터를 읽기 위해 클라이언트 드라이버는 프레임워크 제공 연속 판독기 개체를 사용할 수 있습니다. 자세한 내용은 USB 파이프에서 데이터를 읽기 위해 연속 판독기를 사용하는 방법을 참조하세요.

대량 전송 요청 예제

애플리케이션이 디바이스에 데이터를 읽거나 쓰려는 예제 시나리오를 고려합니다. 애플리케이션은 Windows API를 호출하여 이러한 요청을 보냅니다. 이 예제에서 애플리케이션은 커널 모드에서 드라이버가 게시한 디바이스 인터페이스 GUID를 사용하여 디바이스에 대한 핸들을 엽니다. 그런 다음, 애플리케이션은 ReadFile 또는 WriteFile 을 호출하여 읽기 또는 쓰기 요청을 시작합니다. 또한 이 호출에서 애플리케이션은 읽거나 쓸 데이터와 해당 버퍼의 길이를 포함하는 버퍼를 지정합니다.

I/O 관리자는 요청을 수신하고, IRP(I/O 요청 패킷)를 만들고, 클라이언트 드라이버에 전달합니다.

프레임워크는 요청을 가로채 프레임워크 요청 개체를 만들고 프레임워크 큐 개체에 추가합니다. 그런 다음 프레임워크는 클라이언트 드라이버에 새 요청이 처리되기를 기다리고 있음을 알 수 있습니다. 이 알림은 EvtIoRead 또는 EvtIoWrite에 대한 드라이버의 큐 콜백 루틴을 호출하여 수행됩니다.

프레임워크가 클라이언트 드라이버에 요청을 전달하면 다음 매개 변수를 받습니다.

  • 요청을 포함하는 프레임워크 큐 개체에 대한 WDFQUEUE 핸들입니다.
  • 이 요청에 대한 세부 정보가 포함된 프레임워크 요청 개체에 대한 WDFREQUEST 핸들입니다.
  • 전송 길이, 즉 읽거나 쓸 바이트 수입니다.

클라이언트 드라이버의 EvtIoRead 또는 EvtIoWrite 구현에서 드라이버는 요청 매개 변수를 검사하고 필요에 따라 유효성 검사를 수행할 수 있습니다.

SuperSpeed 대량 엔드포인트의 스트림을 사용하는 경우 KMDF는 기본적으로 스트림을 지원하지 않으므로 URB에서 요청을 보냅니다. 대량 엔드포인트의 스트림으로 전송 요청을 제출하는 방법에 대한 자세한 내용은 USB 대량 엔드포인트 에서 정적 스트림을 열고 닫는 방법을 참조하세요.

스트림을 사용하지 않는 경우 KMDF 정의 메서드를 사용하여 다음 절차에 설명된 대로 요청을 보낼 수 있습니다.

사전 요구 사항

시작하기 전에 다음 정보가 있는지 확인합니다.

1단계: 전송 버퍼 가져오기

전송 버퍼 또는 전송 버퍼 MDL에는 보내거나 받을 데이터가 포함됩니다. 이 항목에서는 전송 버퍼에서 데이터를 보내거나 받는 것으로 가정합니다. 전송 버퍼는 WDF 메모리 개체에 설명되어 있습니다( WDF 메모리 개체 참조 참조). 전송 버퍼와 연결된 메모리 개체를 얻으려면 다음 메서드 중 하나를 호출합니다.

클라이언트 드라이버는 이 메모리를 해제할 필요가 없습니다. 메모리는 부모 요청 개체와 연결되며 부모가 해제될 때 해제됩니다.

2단계: 프레임워크 요청 개체의 형식을 지정하고 USB 드라이버 스택으로 보내기

전송 요청을 비동기적으로 또는 동기적으로 보낼 수 있습니다.

다음은 비동기 메서드입니다.

이 목록의 메서드는 요청의 형식을 지정합니다. 요청을 비동기적으로 보내는 경우 WdfRequestSetCompletionRoutine 메서드(다음 단계에서 설명)를 호출하여 드라이버 구현 완료 루틴에 대한 포인터를 설정합니다. 요청을 보내려면 WdfRequestSend 메서드를 호출합니다.

요청을 동기적으로 보내는 경우 다음 메서드를 호출합니다.

코드 예제는 해당 메서드에 대한 참조 topics 예제 섹션을 참조하세요.

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 드라이버 스택이 요청된 모든 바이트를 하나의 데이터 패킷으로 보내는 간단한 전송에서 Length 값을 요청된 바이트 수와 비교할 검사 있습니다. 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;
}