Share via


USB 파이프를 열거하는 방법

이 문서에서는 USB 파이프에 대한 개요를 제공하고 USB 드라이버 스택에서 파이프 핸들을 가져오는 데 USB 클라이언트 드라이버에 필요한 단계를 설명합니다.

USB 엔드포인트는 클라이언트 드라이버가 데이터를 보내거나 데이터를 받는 디바이스의 버퍼입니다. 데이터를 보내거나 받기 위해 클라이언트 드라이버는 호스트 컨트롤러에 데이터를 제공하는 USB 드라이버 스택에 I/O 전송 요청을 제출합니다. 그런 다음 호스트 컨트롤러는 특정 프로토콜(엔드포인트 유형에 따라 대량, 인터럽트 또는 등시)을 따라 디바이스 간 데이터를 전송하는 요청을 빌드합니다. 데이터 전송의 모든 세부 정보는 클라이언트 드라이버에서 추상화됩니다. 클라이언트 드라이버가 올바른 형식의 요청을 제출하는 한 USB 드라이버 스택은 요청을 처리하고 데이터를 디바이스로 전송합니다.

디바이스를 구성하는 동안 USB 드라이버 스택은 USB 인터페이스 및 활성 대체 설정에 정의된 각 디바이스의 엔드포인트에 대한 USB 파이프 (호스트 쪽)를 만듭니다. USB 파이프는 호스트 컨트롤러와 엔드포인트 간의 통신 채널입니다. 클라이언트 드라이버의 경우 파이프는 엔드포인트의 논리적 추상화입니다. 데이터 전송을 보내려면 드라이버가 전송 대상인 엔드포인트와 연결된 파이프 핸들을 가져와야 합니다. 또한 드라이버가 오류 조건의 경우 파이프 전송을 중단하거나 파이프를 다시 설정하려는 경우에도 파이프 핸들이 필요합니다.

파이프의 모든 특성은 연결된 엔드포인트 설명자에서 파생됩니다. instance 경우 엔드포인트의 유형에 따라 USB 드라이버 스택은 파이프에 대한 형식을 할당합니다. 대량 엔드포인트의 경우 USB 드라이버 스택은 대량 파이프를 만듭니다. 등시 엔드포인트의 경우 등시 파이프가 만들어집니다. 또 다른 중요한 특성은 호스트 컨트롤러가 요청의 엔드포인트 지점으로 보낼 수 있는 데이터의 양입니다. 해당 값에 따라 클라이언트 드라이버는 전송 버퍼의 레이아웃을 결정해야 합니다.

WDF(Windows Driver Foundation)는 KMDF(커널 모드 드라이버 프레임워크) 및 UMDF(사용자 모드 드라이버 프레임워크)에서 클라이언트 드라이버에 대한 많은 구성 작업을 간소화하는 특수 I/O 대상 개체를 제공합니다. 클라이언트 드라이버는 이러한 개체를 사용하여 인터페이스 수, 각 인터페이스 내의 대체 설정 및 해당 엔드포인트와 같은 현재 구성에 대한 정보를 검색할 수 있습니다. 대상 파이프 개체라고 하는 개체 중 하나는 엔드포인트 관련 작업을 수행합니다. 이 문서에서는 대상 파이프 개체를 사용하여 파이프 정보를 가져오는 방법을 설명합니다.

WDM(Windows 드라이버 모델) 클라이언트 드라이버의 경우 USB 드라이버 스택은 USBD_PIPE_INFORMATION 구조의 배열을 반환합니다. 배열의 요소 수는 선택한 구성에서 인터페이스의 활성 대체 설정에 대해 정의된 엔드포인트 수에 따라 달라집니다. 각 요소에는 특정 엔드포인트에 대해 만든 파이프에 대한 정보가 포함됩니다. 구성을 선택하고 파이프 정보 배열을 가져오는 방법에 대한 자세한 내용은 USB 디바이스에 대한 구성을 선택하는 방법을 참조하세요.

알아야 하는 작업

클라이언트 드라이버가 파이프를 열거하기 전에 다음 요구 사항이 충족되는지 확인합니다.

  • 클라이언트 드라이버가 프레임워크 USB 대상 디바이스 개체를 만들었어야 합니다.

    Microsoft Visual Studio Professional 2012와 함께 제공되는 USB 템플릿을 사용하는 경우 템플릿 코드는 이러한 작업을 수행합니다. 템플릿 코드는 대상 디바이스 개체에 대한 핸들을 가져오고 디바이스 컨텍스트에 저장합니다.

    KMDF 클라이언트 드라이버:

    KMDF 클라이언트 드라이버는 WdfUsbTargetDeviceCreateWithParameters 메서드를 호출하여 WDFUSBDEVICE 핸들을 가져와야 합니다. 자세한 내용은 KMDF(USB 클라이언트 드라이버 코드 구조) 이해의 "디바이스 소스 코드"를 참조하세요.

    UMDF 클라이언트 드라이버:

    UMDF 클라이언트 드라이버는 프레임워크 대상 디바이스 개체를 쿼리하여 IWDFUsbTargetDevice 포인터를 가져와야 합니다. 자세한 내용은 UMDF(USB 클라이언트 드라이버 코드 구조) 이해에서 "IPnpCallbackHardware 구현 및 USB 관련 작업"을 참조하세요.

  • 디바이스에 활성 구성이 있어야 합니다.

    USB 템플릿을 사용하는 경우 코드는 각 인터페이스에서 첫 번째 구성 및 기본 대체 설정을 선택합니다. 해당 기본 설정을 변경하는 방법에 대한 자세한 내용은 USB 인터페이스에서 대체 설정을 선택하는 방법을 참조하세요.

    KMDF 클라이언트 드라이버:

    KMDF 클라이언트 드라이버는 WdfUsbTargetDeviceSelectConfig 메서드를 호출해야 합니다.

    UMDF 클라이언트 드라이버:

    UMDF 클라이언트 드라이버의 경우 프레임워크는 해당 구성의 각 인터페이스에 대한 첫 번째 구성 및 기본 대체 설정을 선택합니다.

KMDF 클라이언트 드라이버에서 USB 파이프 핸들 가져오기

프레임워크는 USB 드라이버 스택에서 연 각 파이프를 USB 대상 파이프 개체로 나타냅니다. KMDF 클라이언트 드라이버는 대상 파이프 개체의 메서드에 액세스하여 파이프에 대한 정보를 가져올 수 있습니다. 데이터 전송을 수행하려면 클라이언트 드라이버에 WDFUSBPIPE 파이프 핸들이 있어야 합니다. 파이프 핸들을 얻으려면 드라이버가 활성 구성의 인터페이스 및 대체 설정을 열거한 다음 각 설정에 정의된 엔드포인트를 열거해야 합니다. 각 데이터 전송에 대해 열거형 작업을 수행하는 데 비용이 많이 들 수 있습니다. 따라서 한 가지 방법은 디바이스가 구성된 후 파이프 핸들을 가져와 드라이버 정의 디바이스 컨텍스트에 저장하는 것입니다. 드라이버가 데이터 전송 요청을 받으면 드라이버는 디바이스 컨텍스트에서 필요한 파이프 핸들을 검색하고 이를 사용하여 요청을 보낼 수 있습니다. 예를 들어 클라이언트 드라이버가 디바이스의 구성을 변경하는 경우 대체 설정을 선택하면 드라이버도 새 파이프 핸들을 사용하여 디바이스 컨텍스트를 새로 고쳐야 합니다. 그렇지 않으면 드라이버가 부실 파이프 핸들에서 전송 요청을 잘못 보낼 수 있습니다.

파이프 핸들은 제어 전송에 필요하지 않습니다. 제어 전송 요청을 보내기 위해 WDF 클라이언트 드라이버는 프레임워크 디바이스 개체에 의해 노출된 WdfUsbDevicexxxx 메서드를 호출합니다. 이러한 메서드를 사용하려면 기본 엔드포인트를 대상으로 하는 제어 전송을 시작하려면 WDFUSBDEVICE 핸들이 필요합니다. 이러한 전송의 경우 요청에 대한 I/O 대상은 기본 엔드포인트이며 WDFUSBPIPE 핸들에 의해 추상화된 WDFIOTARGET 핸들로 표시됩니다. 디바이스 수준에서 WDFUSBDEVICE 핸들은 기본 엔드포인트에 대한 WDFUSBPIPE 핸들의 추상화입니다.

제어 전송 및 KMDF 메서드를 보내는 방법에 대한 자세한 내용은 USB 컨트롤 전송을 보내는 방법을 참조하세요.

  1. 파이프 핸들을 저장하도록 디바이스 컨텍스트 구조를 확장합니다.

    디바이스의 엔드포인트를 알고 있는 경우 WDFUSBPIPE 멤버를 추가하여 연결된 USB 파이프 핸들을 저장하여 디바이스 컨텍스트 구조를 확장합니다. 예를 들어 다음과 같이 디바이스 컨텍스트 구조를 확장할 수 있습니다.

    typedef struct _DEVICE_CONTEXT {
        WDFUSBDEVICE    UsbDevice;
        WDFUSBINTERFACE UsbInterface;
        WDFUSBPIPE      BulkReadPipe;   // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      BulkWritePipe;  // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      InterruptPipe;  // Pipe opened for the interrupt IN endpoint.
        WDFUSBPIPE      StreamInPipe;   // Pipe opened for stream IN endpoint.
        WDFUSBPIPE      StreamOutPipe;  // Pipe opened for stream OUT endpoint.
        UCHAR           NumberConfiguredPipes;  // Number of pipes opened.
        ...
        ...                                     // Other members. Not shown.
    
    } DEVICE_CONTEXT, *PDEVICE_CONTEXT;
    
  2. 파이프 컨텍스트 구조를 선언합니다.

    각 파이프는 파이프 컨텍스트라는 다른 구조에 엔드포인트 관련 특성을 저장할 수 있습니다. 디바이스 컨텍스트와 마찬가지로 파이프 컨텍스트는 엔드포인트와 연결된 파이프에 대한 정보를 저장하기 위한 데이터 구조(클라이언트 드라이버에 의해 정의됨)입니다. 디바이스를 구성하는 동안 클라이언트 드라이버는 파이프 컨텍스트에 대한 포인터를 프레임워크에 전달합니다. 프레임워크는 구조체의 크기에 따라 메모리 블록을 할당하고 프레임워크 USB 대상 파이프 개체를 사용하여 해당 메모리 위치에 대한 포인터를 저장합니다. 클라이언트 드라이버는 포인터를 사용하여 파이프 컨텍스트의 멤버에 파이프 정보에 액세스하고 저장할 수 있습니다.

    typedef struct _PIPE_CONTEXT {
    
        ULONG MaxPacketSize;
        ULONG MaxStreamsSupported;
        PUSBD_STREAM_INFORMATION StreamInfo;
    } PIPE_CONTEXT, *PPIPE_CONTEXT;
    
    WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PIPE_CONTEXT, GetPipeContext)
    
    

    이 예제에서 파이프 컨텍스트는 하나의 전송으로 보낼 수 있는 최대 바이트 수를 저장합니다. 클라이언트 드라이버는 해당 값을 사용하여 전송 버퍼의 크기를 결정할 수 있습니다. 선언에는 인라인 함수 GetPipeContext를 생성하는 WDF_DECLARE_CONTEXT_TYPE_WITH_NAME 매크로도 포함됩니다. 클라이언트 드라이버는 해당 함수를 호출하여 파이프 컨텍스트를 저장하는 메모리 블록에 대한 포인터를 검색할 수 있습니다.

    컨텍스트에 대한 자세한 내용은 Framework 개체 컨텍스트 공간을 참조하세요.

    프레임워크에 포인터를 전달하기 위해 클라이언트 드라이버는 먼저 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE 호출하여 파이프 컨텍스트를 초기화합니다. 그런 다음 WdfUsbTargetDeviceSelectConfig (구성 선택용) 또는 WdfUsbInterfaceSelectSetting (대체 설정 선택용)을 호출하는 동안 파이프 컨텍스트에 포인터를 전달합니다.

  3. 디바이스 구성 요청이 완료되면 인터페이스를 열거하고 구성된 파이프에 대한 파이프 핸들을 가져옵니다. 이 정보 집합이 필요합니다.

    • 현재 설정이 포함된 인터페이스에 대한 WDFUSBINTERFACE 핸들입니다. 활성 구성에서 인터페이스를 열거하여 해당 핸들을 가져올 수 있습니다. 또는 WdfUsbTargetDeviceSelectConfigWDF_USB_DEVICE_SELECT_CONFIG_PARAMS 구조체에 대한 포인터를 제공한 경우 Type.SingleInterface.ConfiguredUsbInterface 멤버(단일 인터페이스 디바이스의 경우) 또는 Type.MultiInterface.Pairs.UsbInterface 멤버(다중 인터페이스 디바이스의 경우)에서 핸들을 가져올 수 있습니다.
    • 현재 설정의 엔드포인트에 대해 열린 파이프 수입니다. WdfUsbInterfaceGetNumConfiguredPipes 메서드를 호출하여 특정 인터페이스에서 해당 번호를 가져올 수 있습니다.
    • 구성된 모든 파이프에 대한 WDFUSBPIPE 핸들입니다. WdfUsbInterfaceGetConfiguredPipe 메서드를 호출하여 핸들을 가져올 수 있습니다.

    파이프 핸들을 얻은 후 클라이언트 드라이버는 메서드를 호출하여 파이프의 유형과 방향을 결정할 수 있습니다. 드라이버는 WDF_USB_PIPE_INFORMATION 구조에서 엔드포인트에 대한 정보를 가져올 수 있습니다. 드라이버는 WdfUsbTargetPipeGetInformation 메서드를 호출하여 채워진 구조를 가져올 수 있습니다. 또는 드라이버가 WdfUsbInterfaceGetConfiguredPipe 호출의 구조체에 대한 포인터를 제공할 수 있습니다.

다음 코드 예제에서는 현재 설정의 파이프를 열거합니다. 디바이스의 대량 및 인터럽트 엔드포인트에 대한 파이프 핸들을 가져오고 드라이버의 디바이스 컨텍스트 구조에 저장합니다. 연결된 파이프 컨텍스트에 각 엔드포인트의 최대 패킷 크기를 저장합니다. 엔드포인트가 스트림을 지원하는 경우 OpenStreams 루틴을 호출하여 정적 스트림을 엽니다. OpenStreams 구현은 USB 대량 엔드포인트에서 정적 스트림을 열고 닫는 방법에 나와 있습니다.

특정 대량 엔드포인트가 정적 스트림을 지원하는지 여부를 확인하기 위해 클라이언트 드라이버는 엔드포인트 설명자를 검사합니다. 이 코드는 다음 코드 블록에 표시된 RetrieveStreamInfoFromEndpointDesc라는 도우미 루틴에서 구현됩니다.

NTSTATUS
    FX3EnumeratePipes(
    _In_ WDFDEVICE Device)

{
    NTSTATUS                    status;
    PDEVICE_CONTEXT             pDeviceContext;
    UCHAR                       i;
    PPIPE_CONTEXT               pipeContext;
    WDFUSBPIPE                  pipe;
    WDF_USB_PIPE_INFORMATION    pipeInfo;

    PAGED_CODE();

    pDeviceContext = GetDeviceContext(Device);

    // Get the number of pipes in the current alternate setting.
    pDeviceContext->NumberConfiguredPipes = WdfUsbInterfaceGetNumConfiguredPipes(
        pDeviceContext->UsbInterface);

    if (pDeviceContext->NumberConfiguredPipes == 0)
    {
        status = USBD_STATUS_BAD_NUMBER_OF_ENDPOINTS;
        goto Exit;
    }
    else
    {
        status = STATUS_SUCCESS;
    }

    // Enumerate the pipes and get pipe information for each pipe.
    for (i = 0; i < pDeviceContext->NumberConfiguredPipes; i++)
    {
        WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);

        pipe =  WdfUsbInterfaceGetConfiguredPipe(
            pDeviceContext->UsbInterface,
            i,
            &pipeInfo);

        if (pipe == NULL)
        {
            continue;
        }

        pipeContext = GetPipeContext (pipe);

        // If the pipe is a bulk endpoint that supports streams,
        // If the host controller supports streams, open streams.
        // Use the endpoint as an IN bulk endpoint.
        // Store the maximum packet size.

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {

            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamInPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamInPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkReadPipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsOutEndpoint (pipe))
        {
            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamOutPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamOutPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkWritePipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeInterrupt == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {
            pDeviceContext->InterruptPipe = pipe;
            pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            continue;
        }

    }

Exit:
    return status;
}

다음 코드 예제에서는 클라이언트 드라이버가 파이프를 열거하는 동안 호출하는 RetrieveStreamInfoFromEndpointDesc라는 도우미 루틴을 보여 줍니다.

다음 코드 예제에서 클라이언트 드라이버는 파이프를 열거하는 동안 이전 도우미 루틴인 RetrieveStreamInfoFromEndpointDesc를 호출합니다. 루틴은 먼저 구성 설명자를 가져오고 구문 분석하여 엔드포인트 설명자를 검색합니다. 파이프의 엔드포인트 설명자에 USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE 설명자가 포함된 경우 드라이버는 엔드포인트에서 지원하는 최대 스트림 수를 검색합니다.

/*++
Routine Description:

This routine parses the configuration descriptor and finds the endpoint
with which the specified pipe is associated.
It then retrieves the maximum number of streams supported by the endpoint.
It stores maximum number of streams in the pipe context.

Arguments:

Device - WDFUSBDEVICE handle to the target device object.
The driver obtained that handle in a previous call to
WdfUsbTargetDeviceCreateWithParameters.

Pipe - WDFUSBPIPE handle to the target pipe object.

Return Value:

NTSTATUS
++*/

VOID RetrieveStreamInfoFromEndpointDesc (
    WDFDEVICE Device,
    WDFUSBPIPE Pipe)
{
    PDEVICE_CONTEXT                                 deviceContext                = NULL;
    PUSB_CONFIGURATION_DESCRIPTOR                   configDescriptor             = NULL;
    WDF_USB_PIPE_INFORMATION                        pipeInfo;
    PUSB_COMMON_DESCRIPTOR                          pCommonDescriptorHeader      = NULL;
    PUSB_INTERFACE_DESCRIPTOR                       pInterfaceDescriptor         = NULL;
    PUSB_ENDPOINT_DESCRIPTOR                        pEndpointDescriptor          = NULL;
    PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR   pEndpointCompanionDescriptor = NULL;
    ULONG                                           maxStreams;
    ULONG                                           index;
    BOOLEAN                                         found                        = FALSE;
    UCHAR                                           interfaceNumber = 0;
    UCHAR                                           alternateSetting = 1;
    PPIPE_CONTEXT                                   pipeContext = NULL;
    NTSTATUS                                        status;

    PAGED_CODE();

    deviceContext = GetDeviceContext (Device);
    pipeContext = GetPipeContext (Pipe);

    // Get the configuration descriptor of the currently selected configuration
    status = FX3RetrieveConfigurationDescriptor (
        deviceContext->UsbDevice,
        &deviceContext->ConfigurationNumber,
        &configDescriptor);

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not retrieve the configuration descriptor.");

        status = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    if (deviceContext->ConfigurationNumber == 1)
    {
        alternateSetting = 1;
    }
    else
    {
        alternateSetting = 0;
    }

    // Get the Endpoint Address of the pipe
    WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
    WdfUsbTargetPipeGetInformation (Pipe, &pipeInfo);

    // Parse the ConfigurationDescriptor (including all Interface and
    // Endpoint Descriptors) and locate a Interface Descriptor which
    // matches the InterfaceNumber, AlternateSetting, InterfaceClass,
    // InterfaceSubClass, and InterfaceProtocol parameters.
    pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
        configDescriptor,
        configDescriptor,
        interfaceNumber,  //Interface number is 0.
        alternateSetting,  // Alternate Setting is 1
        -1, // InterfaceClass, ignore
        -1, // InterfaceSubClass, ignore
        -1  // InterfaceProtocol, ignore
        );

    if (pInterfaceDescriptor == NULL )
    {
        // USBD_ParseConfigurationDescriptorEx failed to retrieve Interface Descriptor.
        goto Exit;
    }

    pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR) pInterfaceDescriptor;

    for(index = 0; index < pInterfaceDescriptor->bNumEndpoints; index++)
    {

        pCommonDescriptorHeader = USBD_ParseDescriptors(
            configDescriptor,
            configDescriptor->wTotalLength,
            pCommonDescriptorHeader,
            USB_ENDPOINT_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader == NULL)
        {
            // USBD_ParseDescriptors failed to retrieve Endpoint Descriptor unexpectedly.
            goto Exit;
        }

        pEndpointDescriptor = (PUSB_ENDPOINT_DESCRIPTOR) pCommonDescriptorHeader;

        // Search an Endpoint Descriptor that matches the EndpointAddress
        if (pEndpointDescriptor->bEndpointAddress == pipeInfo.EndpointAddress)
        {

            found = TRUE;
            break;
        }

        // Skip the current Endpoint Descriptor and search for the next.
        pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)pCommonDescriptorHeader)
            + pCommonDescriptorHeader->bLength);
    }

    if (found)
    {
        // Locate the SuperSpeed Endpoint Companion Descriptor
        // associated with the endpoint descriptor
        pCommonDescriptorHeader = USBD_ParseDescriptors (
            configDescriptor,
            configDescriptor->wTotalLength,
            pEndpointDescriptor,
            USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader != NULL)
        {
            pEndpointCompanionDescriptor =
                (PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR) pCommonDescriptorHeader;

            maxStreams = pEndpointCompanionDescriptor->bmAttributes.Bulk.MaxStreams;

            if (maxStreams == 0)
            {
                pipeContext->MaxStreamsSupported = 0;
                pipeContext->IsStreamsCapable = FALSE;
            }
            else
            {
                pipeContext->IsStreamsCapable = TRUE;
                pipeContext->MaxStreamsSupported = 1 << maxStreams;
            }
        }
        else
        {
            KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                "USBD_ParseDescriptors failed to retrieve SuperSpeed Endpoint Companion Descriptor unexpectedly.\n" ));
        }
    }
    else
    {
        pipeContext->MaxStreamsSupported = 0;
        pipeContext->IsStreamsCapable = FALSE;
    }

Exit:
    if (configDescriptor)
    {
        ExFreePoolWithTag (configDescriptor, USBCLIENT_TAG);
    }

    return;
}

UMDF 클라이언트 드라이버에서 파이프 핸들 가져오기

UMDF 클라이언트 드라이버는 COM 인프라를 사용하고 프레임워크 디바이스 개체와 쌍을 이루는 COM 콜백 클래스를 구현합니다. KMDF 드라이버와 마찬가지로 UMDF 클라이언트 드라이버는 디바이스가 구성된 후에만 파이프 정보를 가져올 수 있습니다. 파이프 정보를 가져오려면 클라이언트 드라이버가 활성 설정을 포함하는 프레임워크 인터페이스 개체의 IWDFUsbTargetPipe 인터페이스에 대한 포인터를 가져와야 합니다. 드라이버는 인터페이스 포인터를 사용하여 해당 설정의 파이프를 열거하여 프레임워크 대상 파이프 개체에 의해 노출되는 IWDFUsbTargetPipe 인터페이스 포인터를 가져올 수 있습니다.

드라이버가 파이프 열거를 시작하기 전에 드라이버는 디바이스 구성 및 지원되는 엔드포인트에 대해 알고 있어야 합니다. 이 정보에 따라 드라이버는 파이프 개체를 클래스 멤버 변수로 저장할 수 있습니다.

다음 코드 예제에서는 Visual Studio Professional 2012와 함께 제공되는 USB UMDF 템플릿을 확장합니다. 시작 코드에 대한 설명은 USB UMDF(클라이언트 드라이버 코드 구조) 이해에서 "IPnpCallbackHardware 구현 및 USB 관련 작업"을 참조하세요.

여기에 표시된 대로 CDevice 클래스 선언을 확장합니다. 이 예제 코드에서는 디바이스가 OSR FX2 보드라고 가정합니다. 설명자 레이아웃에 대한 자세한 내용은 USB 디바이스 레이아웃을 참조하세요.

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallbackHardware
{

public:
    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallbackHardware)
    END_COM_MAP()

    CMyDevice() :
        m_FxDevice(NULL),
        m_IoQueue(NULL),
        m_FxUsbDevice(NULL)
    {
    }

    ~CMyDevice()
    {
    }

private:
    IWDFDevice *            m_FxDevice;
    CMyIoQueue *            m_IoQueue;
    IWDFUsbTargetDevice *   m_FxUsbDevice;
    IWDFUsbInterface *      m_pIUsbInterface;  //Pointer to the target interface object.
    IWDFUsbTargetPipe *     m_pIUsbInputPipe;  // Pointer to the target pipe object for the bulk IN endpoint.
    IWDFUsbTargetPipe *     m_pIUsbOutputPipe; // Pointer to the target pipe object for the bulk OUT endpoint.
    IWDFUsbTargetPipe *     m_pIUsbInterruptPipe; // Pointer to the target pipe object for the interrupt endpoint.

private:
    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

public:
    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );

    HRESULT                     // Declare a helper function to enumerate pipes.
    ConfigureUsbPipes(
        );

public:
    // IPnpCallbackHardware methods
    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

CDevice 클래스 정의에서 CreateUsbIoTargets라는 도우미 메서드를 구현합니다. 이 메서드는 드라이버가 대상 디바이스 개체에 대한 포인터를 가져온 후 IPnpCallbackHardware::OnPrepareHardware 구현에서 호출됩니다.

HRESULT  CMyDevice::CreateUsbIoTargets()
{
    HRESULT                 hr;
    UCHAR                   NumEndPoints = 0;
    IWDFUsbInterface *      pIUsbInterface = NULL;
    IWDFUsbTargetPipe *     pIUsbPipe = NULL;

    if (SUCCEEDED(hr))
    {
        UCHAR NumInterfaces = pIUsbTargetDevice->GetNumInterfaces();

        WUDF_TEST_DRIVER_ASSERT(1 == NumInterfaces);

        hr = pIUsbTargetDevice->RetrieveUsbInterface(0, &pIUsbInterface);
        if (FAILED(hr))
        {
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Unable to retrieve USB interface from USB Device I/O Target %!HRESULT!",
                        hr
                        );
        }
        else
        {
            m_pIUsbInterface = pIUsbInterface;

            DriverSafeRelease (pIUsbInterface); //release creation reference
        }
     }

    if (SUCCEEDED(hr))
    {
        NumEndPoints = pIUsbInterface->GetNumEndPoints();

        if (NumEndPoints != NUM_OSRUSB_ENDPOINTS)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Has %d endpoints, expected %d, returning %!HRESULT! ",
                        NumEndPoints,
                        NUM_OSRUSB_ENDPOINTS,
                        hr
                        );
        }
    }

    if (SUCCEEDED(hr))
    {
        for (UCHAR PipeIndex = 0; PipeIndex < NumEndPoints; PipeIndex++)
        {
            hr = pIUsbInterface->RetrieveUsbPipeObject(PipeIndex,
                                                  &pIUsbPipe);

            if (FAILED(hr))
            {
                TraceEvents(TRACE_LEVEL_ERROR,
                            TEST_TRACE_DEVICE,
                            "%!FUNC! Unable to retrieve USB Pipe for PipeIndex %d, %!HRESULT!",
                            PipeIndex,
                            hr
                            );
            }
            else
            {
                if ( pIUsbPipe->IsInEndPoint() )
                {
                    if ( UsbdPipeTypeInterrupt == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInterruptPipe = pIUsbPipe;
                    }
                    else if ( UsbdPipeTypeBulk == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInputPipe = pIUsbPipe;
                    }
                    else
                    {
                        pIUsbPipe->DeleteWdfObject();
                    }
                }
                else if ( pIUsbPipe->IsOutEndPoint() && (UsbdPipeTypeBulk == pIUsbPipe->GetType()) )
                {
                    m_pIUsbOutputPipe = pIUsbPipe;
                }
                else
                {
                    pIUsbPipe->DeleteWdfObject();
                }

                DriverSafeRelease(pIUsbPipe);  //release creation reference
            }
        }

        if (NULL == m_pIUsbInputPipe || NULL == m_pIUsbOutputPipe)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Input or output pipe not found, returning %!HRESULT!",
                        hr
                        );
        }
    }

    return hr;
}

UMDF에서 클라이언트 드라이버는 파이프 인덱스를 사용하여 데이터 전송 요청을 보냅니다. 파이프 인덱스는 설정에서 엔드포인트에 대한 파이프를 열 때 USB 드라이버 스택에서 할당한 숫자입니다. 파이프 인덱스를 가져오려면**IWDFUsbTargetPipe::GetInformation** 메서드를 호출합니다. 메서드는 WINUSB_PIPE_INFORMATION 구조를 채웁니다. PipeId 값은 파이프 인덱스임을 나타냅니다.

대상 파이프에서 읽기 및 쓰기 작업을 수행하는 한 가지 방법은 IWDFUsbInterface::GetWinUsbHandle 을 호출하여 WinUSB 핸들을 가져온 다음 WinUSB 함수를 호출하는 것입니다. 예를 들어 드라이버는 WinUsb_ReadPipe 또는 WinUsb_WritePipe 함수를 호출할 수 있습니다. 이러한 함수 호출에서 드라이버는 파이프 인덱스 를 지정해야 합니다. 자세한 내용은 WinUSB 함수를 사용하여 USB 디바이스에 액세스하는 방법을 참조하세요.

WDM 기반 클라이언트 드라이버에 대한 파이프 핸들

구성을 선택한 후 USB 드라이버 스택은 각 디바이스의 엔드포인트에 대한 파이프를 설정합니다. USB 드라이버 스택은 USBD_PIPE_INFORMATION 구조체의 배열을 반환합니다. 배열의 요소 수는 선택한 구성에서 인터페이스의 활성 대체 설정에 대해 정의된 엔드포인트 수에 따라 달라집니다. 각 요소에는 특정 엔드포인트에 대해 만든 파이프에 대한 정보가 포함됩니다. 파이프 핸들을 가져오는 방법에 대한 자세한 내용은 USB 디바이스에 대한 구성 선택 방법을 참조하세요.

I/O 전송 요청을 빌드하려면 클라이언트 드라이버에 해당 엔드포인트와 연결된 파이프에 대한 핸들이 있어야 합니다. 클라이언트 드라이버는 배열의 USBD_PIPE_INFORMATIONPipeHandle 멤버에서 파이프 핸들을 가져올 수 있습니다.

파이프 핸들 외에도 클라이언트 드라이버에는 파이프 유형도 필요합니다. 클라이언트 드라이버는 PipeType 멤버를 검사하여 파이프 유형을 확인할 수 있습니다.

엔드포인트 유형에 따라 USB 드라이버 스택은 다양한 유형의 파이프를 지원합니다. 클라이언트 드라이버는 USBD_PIPE_INFORMATIONPipeType 멤버를 검사하여 파이프 유형을 확인할 수 있습니다. 파이프 유형이 다르면 I/O 트랜잭션을 수행하려면 다양한 유형의 USB 요청 블록(URL)이 필요합니다.

그런 다음 클라이언트 드라이버는 URB를 USB 드라이버 스택에 제출합니다. USB 드라이버 스택은 요청을 처리하고 지정된 데이터를 요청된 대상 파이프로 보냅니다.

URB에는 대상 파이프 핸들, 전송 버퍼 및 길이와 같은 요청에 대한 정보가 포함됩니다. URB 공용 구조체 내의 각 구조는 특정 멤버인 TransferFlags, TransferBuffer, TransferBufferLengthTransferBufferMDL을 공유합니다. TransferFlags 멤버에는 각 URB 형식에 해당하는 형식별 플래그가 있습니다. 모든 데이터 전송 URL의 경우 TransferFlags 의 USBD_TRANSFER_DIRECTION_IN 플래그는 전송 방향을 지정합니다. 클라이언트 드라이버는 디바이스에서 데이터를 읽도록 USBD_TRANSFER_DIRECTION_IN 플래그를 설정합니다. 드라이버는 이 플래그를 지우고 디바이스에 데이터를 보냅니다. 메모리 또는 MDL에 상주하는 버퍼에서 데이터를 읽거나 쓸 수 있습니다. 두 경우 모두 드라이버는 TransferBufferLength 멤버의 버퍼 크기를 지정합니다. 드라이버는 TransferBuffer 멤버에 상주 버퍼를 제공하고 TransferBufferMDL 멤버의 MDL을 제공합니다. 드라이버가 제공하는 항목 중 어느 것을 제공하든 다른 하나는 NULL이어야 합니다.