다음을 통해 공유


USB 대량 엔드포인트에서 정적 스트림을 열고 닫는 방법

이 문서에서는 정적 스트림 기능에 대해 설명하고 USB 클라이언트 드라이버가 USB 3.0 디바이스의 대량 엔드포인트에서 스트림을 열고 닫는 방법을 설명합니다.

USB 2.0 및 이전 디바이스에서 대량 엔드포인트는 엔드포인트를 통해 단일 데이터 스트림을 보내거나 받을 수 있습니다. USB 3.0 디바이스에서 대량 엔드포인트에는 엔드포인트를 통해 여러 데이터 스트림을 보내고 받을 수 있는 기능이 있습니다.

Windows의 Microsoft 제공 USB 드라이버 스택은 여러 스트림을 지원합니다. 이렇게 하면 클라이언트 드라이버가 USB 3.0 디바이스의 대량 엔드포인트와 연결된 각 스트림에 독립적인 I/O 요청을 보낼 수 있습니다. 다른 스트림에 대한 요청은 직렬화되지 않습니다.

클라이언트 드라이버의 경우 스트림은 동일한 특성 집합을 가진 여러 논리 엔드포인트를 나타냅니다. 특정 스트림에 요청을 보내려면 클라이언트 드라이버에 해당 스트림에 대한 핸들이 필요합니다(엔드포인트의 파이프 핸들과 유사). 스트림에 대한 I/O 요청에 대한 URB는 대량 엔드포인트에 대한 I/O 요청에 대한 URB와 유사합니다. 유일한 차이점은 파이프 핸들입니다. 스트림에 I/O 요청을 보내려면 드라이버는 스트림에 대한 파이프 핸들을 지정합니다.

디바이스를 구성하는 동안 클라이언트 드라이버는 선택 구성 요청과 선택적 인터페이스 요청을 보냅니다. 이러한 요청은 인터페이스의 활성 설정에 정의된 엔드포인트에 대한 파이프 핸들 집합을 검색합니다. 스트림을 지원하는 엔드포인트의 경우 드라이버가 스트림을 열 때까지(다음에 설명) 엔드포인트 파이프 핸들을 사용하여 I/O 요청을 기본 스트림 (첫 번째 스트림)으로 보낼 수 있습니다.

클라이언트 드라이버가 기본 스트림 이외의 스트림에 요청을 보내려는 경우 드라이버는 모든 스트림에 대한 핸들을 열고 가져와야 합니다. 이를 위해 클라이언트 드라이버는 열 스트림 수를 지정하여 오픈 스트림 요청을 보냅니다. 클라이언트 드라이버가 스트림 사용을 완료한 후 드라이버는 필요에 따라 닫기 스트림 요청을 전송하여 닫을 수 있습니다.

KMDF(커널 모드 드라이버 프레임워크)는 정적 스트림을 기본적으로 지원하지 않습니다. 클라이언트 드라이버는 스트림을 열고 닫는 WDM(Windows 드라이버 모델) 스타일 URL을 보내야 합니다. 이 문서에서는 해당 URL의 서식을 지정하고 보내는 방법을 설명합니다. UMDF(사용자 모드 드라이버 프레임워크) 클라이언트 드라이버는 정적 스트림 기능을 사용할 수 없습니다.

이 문서에는 WDM 드라이버로 레이블이 지정된 일부 메모가 포함되어 있습니다. 이러한 참고 사항에서는 스트림 요청을 보내려는 WDM 기반 USB 클라이언트 드라이버의 루틴을 설명합니다.

사전 요구 사항

클라이언트 드라이버가 스트림을 열거나 닫기 전에 드라이버에 다음이 있어야 합니다.

  • WdfUsbTargetDeviceCreateWithParameters 메서드라고 합니다.

    메서드를 사용하려면 클라이언트 계약 버전을 USBD_CLIENT_CONTRACT_VERSION_602 합니다. 해당 버전을 지정하여 클라이언트 드라이버는 규칙 집합을 준수해야 합니다. 자세한 내용은 모범 사례: URL 사용을 참조하세요.

    호출은 프레임워크의 USB 대상 디바이스 개체에 대한 WDFUSBDEVICE 핸들을 검색합니다. 이 핸들은 열린 스트림에 대한 후속 호출을 위해 필요합니다. 일반적으로 클라이언트 드라이버는 드라이버의 EVT_WDF_DEVICE_PREPARE_HARDWARE 이벤트 콜백 루틴에 자신을 등록합니다.

    WDM 드라이버:USBD_CreateHandle 루틴을 호출하고 USB 드라이버 스택에 드라이버 등록을 위한 USBD 핸들을 가져옵니다.

  • 디바이스를 구성하고 스트림을 지원하는 대량 엔드포인트에 대한 WDFUSBPIPE 파이프 핸들을 얻었습니다. 파이프 핸들을 가져오려면 선택한 구성에서 인터페이스의 현재 대체 설정에서 WdfUsbInterfaceGetConfiguredPipe 메서드를 호출합니다.

    WDM 드라이버: select-configuration 또는 select-interface 요청을 전송하여 USBD 파이프 핸들을 가져옵니다. 자세한 내용은 USB 디바이스에 대한 구성을 선택하는 방법을 참조하세요.

정적 스트림을 여는 방법

  1. 기본 USB 드라이버 스택과 호스트 컨트롤러가 WdfUsbTargetDeviceQueryUsbCapability 메서드를 호출하여 정적 스트림 기능을 지원하는지 여부를 확인합니다. 일반적으로 클라이언트 드라이버는 드라이버의 EVT_WDF_DEVICE_PREPARE_HARDWARE 이벤트 콜백 루틴에서 루틴을 호출합니다.

    WDM 드라이버:USBD_QueryUsbCapability 루틴을 호출합니다. 일반적으로 드라이버는 드라이버의 시작 디바이스 루틴(IRP_MN_START_DEVICE)에서 사용하려는 기능을 쿼리합니다. 코드 예제는 USBD_QueryUsbCapability 참조하세요.

    다음 정보를 지정합니다.

    • 클라이언트 드라이버 등록을 위해 WdfUsbTargetDeviceCreateWithParameters에 대한 이전 호출에서 검색된 USB 디바이스 개체에 대한 핸들입니다.

      WDM 드라이버: 이전 호출에서 검색된 USBD 핸들을 USBD_CreateHandle 전달합니다.

      클라이언트 드라이버가 특정 기능을 사용하려는 경우 드라이버는 먼저 기본 USB 드라이버 스택을 쿼리하여 드라이버 스택과 호스트 컨트롤러가 기능을 지원하는지 여부를 확인해야 합니다. 기능이 지원되는 경우에만 드라이버는 기능을 사용하도록 요청을 보내야 합니다. 일부 요청에는 스트림 기능(5단계에서 설명)과 같은 URL이 필요합니다. 이러한 요청의 경우 동일한 핸들을 사용하여 기능을 쿼리하고 URL을 할당해야 합니다. 드라이버 스택은 핸들을 사용하여 드라이버에서 사용할 수 있는 지원되는 기능을 추적하기 때문입니다.

      instance 경우 USBD_CreateHandle 호출하여 USBD_HANDLE 얻은 경우 USBD_QueryUsbCapability 호출하여 드라이버 스택을 쿼리하고 USBD_UrbAllocate 호출하여 URB를 할당합니다. 두 호출에서 동일한 USBD_HANDLE 전달합니다.

      KMDF 메서드인 WdfUsbTargetDeviceQueryUsbCapabilityWdfUsbTargetDeviceCreateUrb를 호출하는 경우 해당 메서드 호출에서 프레임워크 대상 개체에 동일한 WDFUSBDEVICE 핸들을 지정합니다.

    • GUID_USB_CAPABILITY_STATIC_STREAMS 할당된 GUID입니다.

    • 출력 버퍼(USHORT에 대한 포인터)입니다. 완료되면 버퍼는 호스트 컨트롤러에서 지원하는 최대 스트림 수(엔드포인트당)로 채워집니다.

    • 출력 버퍼의 길이(바이트)입니다. 스트림의 경우 길이는 입니다 sizeof (USHORT).

  2. 반환된 NTSTATUS 값을 평가합니다. 루틴이 성공적으로 완료되고 STATUS_SUCCESS 반환하면 정적 스트림 기능이 지원됩니다. 그렇지 않으면 메서드는 적절한 오류 코드를 반환합니다.

  3. 열 스트림 수를 결정합니다. 열 수 있는 최대 스트림 수는 다음으로 제한됩니다.

    최대 스트림 수를 확인하려면 호스트 컨트롤러와 엔드포인트에서 지원하는 두 값 중 더 작은 값을 선택합니다.

  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단계에서 만든)
    • URB 구조체에 대한 포인터입니다(5단계에서 생성됨).

    URB의 형식을 지정하려면 UsbBuildOpenStaticStreamsRequest를 호출하고 필요한 정보를 매개 변수 값으로 전달합니다. UsbBuildOpenStaticStreamsRequest에 지정된 스트림 수가 지원되는 최대 스트림 수를 초과하지 않는지 확인합니다.

  7. WdfRequestSend 메서드를 호출하여 URB를 WDF 요청 개체로 보냅니다. 요청을 동기적으로 보내려면 WdfUsbTargetDeviceSendUrbSynchronously 메서드를 대신 호출합니다.

    WDM 드라이버: URB를 IRP와 연결하고 IRP를 USB 드라이버 스택에 제출합니다. 자세한 내용은 URB를 제출하는 방법을 참조하세요.

  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는 프레임워크에 의해 가로채집니다. 그런 다음 프레임워크는 IRP를 나타내는 WDF 요청 개체를 할당합니다. 그런 다음 프레임워크는 WDF 요청 개체를 클라이언트 드라이버에 전달합니다. 그런 다음 클라이언트 드라이버는 요청 개체를 데이터 전송 URB와 연결하고 USB 드라이버 스택으로 보낼 수 있습니다.

클라이언트 드라이버가 프레임워크에서 WDF 요청 개체를 받지 않고 요청을 비동기적으로 보내려는 경우 드라이버는 WdfRequestCreate 메서드를 호출하여 WDF 요청 개체를 할당해야 합니다. WdfUsbTargetPipeFormatRequestForUrb를 호출하여 새 개체의 서식을 지정하고 WdfRequestSend를 호출하여 요청을 보냅니다.

동기 사례에서 WDF 요청 개체를 전달하는 것은 선택 사항입니다.

스트림으로 데이터를 전송하려면 URL을 사용해야 합니다. URB는 WdfUsbTargetPipeFormatRequestForUrb를 호출하여 형식을 지정해야 합니다.

다음 WDF 메서드는 스트림에 대해 지원 되지 않습니다 .

다음 절차에서는 클라이언트 드라이버가 프레임워크에서 요청 개체를 수신한다고 가정합니다.

  1. WdfUsbTargetDeviceCreateUrb를 호출하여 URB를 할당합니다. 이 메서드는 새로 할당된 URB를 포함하는 WDF 메모리 개체를 할당합니다. 클라이언트 드라이버는 모든 I/O 요청에 URB를 할당하거나 URB를 할당하고 동일한 유형의 요청에 다시 사용하도록 선택할 수 있습니다.

  2. UsbBuildInterruptOrBulkTransferRequest를 호출하여 대량 전송을 위해 URB 형식을 지정합니다. PipeHandle 매개 변수에서 스트림에 대한 핸들을 지정합니다. 스트림 핸들은 정적 스트림을 여는 방법 섹션에 설명된 이전 요청에서 가져옵니다.

  3. WdfUsbTargetPipeFormatRequestForUrb 메서드를 호출하여 WDF 요청 개체의 서식을 지정합니다. 호출에서 데이터 전송 URB를 포함하는 WDF 메모리 개체를 지정합니다. 메모리 개체는 1단계에서 할당되었습니다.

  4. 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_PIPE_REQUEST Hdr 멤버는 URB_FUNCTION_CLOSE_STATIC_STREAMS
    • PipeHandle 멤버는 사용 중인 열린 스트림을 포함하는 엔드포인트에 대한 핸들이어야 합니다.
  3. WdfRequestSend 또는 WdfUsbTargetDeviceSendUrbSynchronously를 호출하여 URB를 WDF 요청으로 보냅니다.

닫기 핸들 요청은 클라이언트 드라이버에서 이전에 연 모든 스트림을 닫습니다. 클라이언트 드라이버는 요청을 사용하여 엔드포인트의 특정 스트림을 닫을 수 없습니다.

정적 스트림 요청을 보내는 모범 사례

USB 드라이버 스택은 수신된 URB에서 유효성 검사를 수행합니다. 유효성 검사 오류를 방지하려면 다음을 수행합니다.

  • 스트림을 지원하지 않는 엔드포인트에 오픈 스트림 또는 닫기 스트림 요청을 보내지 마세요. WdfUsbTargetDeviceQueryUsbCapability(WDM 드라이버의 경우 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;
}