USB パイプの列挙方法

この記事では、USB パイプの概要を説明し、USB クライアント ドライバーが USB ドライバー スタックからパイプ ハンドルを取得するために必要な手順について説明します。

USB エンドポイントは、クライアント ドライバーがデータを送受信するデバイス内のバッファーです。 データを送受信するために、クライアント ドライバーは USB ドライバー スタックに I/O 転送要求を送信し、ホスト コントローラーにデータを提示します。 次に、ホスト コントローラーは特定のプロトコル (エンドポイントの種類 (バルク、割り込み、またはアイソクロナス) に応じて) に従い、デバイスとの間でデータを転送するリクエストを作成します。 データ転送のすべての詳細は、クライアント ドライバーから抽象化されます。 クライアント ドライバーが適切な形式の要求を送信する限り、USB ドライバー スタックは要求を処理し、データをデバイスに転送します。

デバイスの構成中に、USB ドライバー スタックは、USB インターフェイスとそのアクティブな代替設定で定義されている各デバイスのエンドポイントの USB パイプ (ホスト側) を作成します。 USB パイプは、ホスト コントローラーとエンドポイントの間のコミュニケーション チャネルです。 クライアント ドライバーの場合、パイプはエンドポイントの論理的な抽象化です。 データ転送を送信するには、ドライバーが転送のターゲットであるエンドポイントに関連付けられているパイプ ハンドルを取得する必要があります。 また、エラー状態が発生した場合に、ドライバーがパイプの転送を中止またはリセットする必要がある場合にも、パイプ ハンドルが必要です。

パイプのすべての属性は、関連付けられているエンドポイント記述子から派生します。 たとえば、エンドポイントの種類に応じて、USB ドライバー スタックはパイプの種類を割り当てます。 一括エンドポイントの場合、USB ドライバー スタックは一括パイプを作成します。等時性エンドポイントの場合は、等時性パイプが作成されます。 もう 1 つの重要な属性は、ホスト コントローラーが要求のエンドポイント ポイントに送信できるデータの量です。 その値に応じて、クライアント ドライバーは転送バッファーのレイアウトを決定する必要があります。

Windows Driver Foundation (WDF) には、クライアント ドライバーの構成タスクの多くを簡略化する、カーネル モード ドライバー フレームワーク (KMDF)ユーザー モード ドライバー フレームワーク (UMDF) の特殊な I/O ターゲット オブジェクトが用意されています。 これらのオブジェクトを使用することにより、クライアント ドライバーは、インターフェイスの数、各インターフェイス内の代替設定、エンドポイントなど、現在の構成に関する情報を取得できます。 ターゲット パイプ オブジェクトと呼ばれるオブジェクトの 1 つは、エンドポイント関連のタスクを実行します。 この記事では、ターゲット パイプ オブジェクトを使用してパイプ情報を取得する方法について説明します。

Windows ドライバー モデル (WDM) クライアント ドライバーの場合、USB ドライバー スタックは、USBD_PIPE_INFORMATION 構造体の配列を返します。 配列内の要素の数は、選択した構成内のインターフェイスのアクティブな代替設定に対して定義されたエンドポイントの数によって異なります。 各要素には、特定のエンドポイント用に作成されたパイプに関する情報が含まれています。 構成の選択とパイプ情報の配列の取得については、「USB デバイスの構成を選択する方法」を参照してください。

知っておくべきこと

クライアント ドライバーがパイプを列挙する前に、次の要件が満たされていることを確認します。

  • クライアント ドライバーによって、フレームワーク USB ターゲット デバイス オブジェクトが作成されている必要があります。

    Microsoft Visual Studio Professional 2012 に付属する USB テンプレートを使用している場合、テンプレート コードでこれらのタスクが実行されます。 テンプレート コードによりターゲット デバイス オブジェクトのハンドルが取得され、デバイス コンテキストに格納されます。

    KMDF クライアント ドライバー:

    KMDF クライアント ドライバーでは、WdfUsbTargetDeviceCreateWithParameters メソッドを呼び出すことで WDFUSBDEVICE ハンドルを取得する必要があります。 詳細については、「USB クライアント ドライバー コード構造について (KMDF)」の「デバイスのソース コード」を参照してください。

    UMDF クライアント ドライバー:

    UMDF クライアント ドライバーは、フレームワーク ターゲット デバイス オブジェクトを問い合わせることで、IWDFUsbTargetDevice ポインターを取得する必要があります。 詳細については、「USB クライアント ドライバー コード構造について (UMDF)」の「IPnpCallbackHardware 実装と USB 固有のタスク」を参照してください。

  • デバイスにはアクティブな構成が必要です。

    USB テンプレートを使用している場合、コードは各インターフェイスの最初の構成と既定の代替設定を選択します。 既定の設定を変更する方法については、「USBインターフェイスで代替設定を選択する方法」を参照してください。

    KMDF クライアント ドライバー:

    KMDF クライアント ドライバーは、WdfUsbTargetDeviceSelectConfig メソッドを呼び出す必要があります。

    UMDF クライアント ドライバー:

    UMDF クライアント ドライバーの場合、フレームワークは最初の構成と、その構成内の各インターフェイスの既定の代替設定を選択します。

KMDF クライアント ドライバーでの USB パイプ ハンドルの取得

フレームワークは、USB ドライバー スタックによって開かれる各パイプを USB ターゲット パイプ オブジェクトとして表します。 KMDF クライアント ドライバーは、ターゲット パイプ オブジェクトのメソッドにアクセスして、パイプに関する情報を取得できます。 データ転送を実行するには、クライアント ドライバーに WDFUSBPIPE パイプ ハンドルが必要です。 パイプ ハンドルを取得するには、ドライバーはアクティブな構成のインターフェイスと代替設定を列挙し、各設定で定義されているエンドポイントを列挙する必要があります。 データ転送ごとに列挙操作を実行すると、コストがかかる場合があります。 したがって、1 つのアプローチとして、デバイスの構成後にパイプ ハンドルを取得し、ドライバー定義のデバイス コンテキストに格納する方法があります。 ドライバーがデータ転送要求を受信すると、ドライバーはデバイス コンテキストから必要なパイプ ハンドルを取得し、それらを使用して要求を送信できます。 クライアント ドライバーがデバイスの構成を変更する場合 (たとえば、代替設定を選択する場合)、ドライバーは新しいパイプ ハンドルでデバイス コンテキストを更新する必要もあります。 そうしないと、ドライバーが古いパイプ ハンドルに対して転送要求を誤って送信する可能性があります。

パイプ ハンドルは、制御転送には必要ありません。 制御転送要求を送信するために、WDF クライアント ドライバーは、フレームワーク デバイス オブジェクトによって公開される WdfUsbDevicexxxx メソッドを呼び出します。 これらのメソッドでは、既定のエンドポイントを対象とする制御転送を開始するために WDFUSBDEVICE ハンドルが必要です。 このような転送の場合、要求の I/O ターゲットはデフォルトのエンドポイントであり、WDFIOTARGET ハンドルによって表され、WDFUSBPIPE ハンドルによって表されます。 デバイス レベルでは、WDFUSBDEVICE ハンドルは、既定のエンドポイントに対する WDFUSBPIPE ハンドルの抽象化です。

制御転送の送信と KMDF メソッドの詳細については、「USB 制御転送を送信する方法」を参照してください。

  1. パイプ ハンドルを格納するようにデバイス コンテキスト構造を拡張します。

    デバイス内のエンドポイントがわかっている場合は、関連する USB パイプ ハンドルを格納する WDFUSBPIPE メンバーを追加して、デバイス コンテキスト構造を拡張します。 たとえば、次に示すように、デバイス コンテキスト構造を拡張できます。

    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)
    
    

    この例では、パイプ コンテキストには 1 回の転送で送信できる最大バイト数が格納されます。 クライアント ドライバーは、その値を使用して転送バッファーのサイズを決定できます。 宣言には、インライン関数 GetPipeContext を生成する WDF_DECLARE_CONTEXT_TYPE_WITH_NAME マクロも含まれています。 クライアント ドライバーは、その関数を呼び出して、パイプ コンテキストを格納するメモリ ブロックへのポインターを取得できます。

    コンテキストの詳細については、「フレームワーク オブジェクト コンテキスト空間」を参照してください。

    フレームワークへのポインターを渡すために、クライアント ドライバーは、最初に 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 ルーチンを呼び出して静的ストリームを開きます。 Openストリーム の実装については、「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_SUPERSP Enterprise Edition D_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 クライアント ドライバーでの USB パイプ ハンドルの取得

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 値はパイプ インデックスを示します。

ターゲット パイプで読み取りおよび書き込み操作を実行する方法の 1 つは、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 要求ブロック (URB) が必要になります。

クライアント ドライバーは、USB ドライバー スタックに URB を送信します。 USB ドライバー スタックは要求を処理し、指定されたデータを要求されたターゲット パイプに送信します。

URB には、ターゲット パイプ ハンドル、転送バッファー、その長さなどの要求に関する情報が含まれています。 URB 共用体内の各構造体は、TransferFlagsTransferBufferTransferBufferLength、および TransferBufferMDL という特定のメンバーを共有します。 TransferFlags メンバーには、各 URB 型に対応する型固有のフラグがあります。 すべてのデータ転送 URL について、TransferFlags の USBD_TRANSFER_DIRECTION_IN フラグによって転送の方向が指定されます。 クライアント ドライバーは、デバイスからデータを読み取るために USBD_TRANSFER_DIRECTION_IN フラグを設定します。 ドライバーは、デバイスにデータを送信するためにこのフラグをクリアします。 データは、メモリまたは MDL に常駐するバッファーから読み取ったり書き込んだりできます。 どちらの場合も、ドライバーは TransferBufferLength メンバー内のバッファーのサイズを指定します。 ドライバーは、TransferBuffer メンバーに常駐バッファーを提供し、TransferBufferMDL メンバーの MDL を提供します。 ドライバーがどちらを提供しても、もう一方は NULL である必要があります。