USB バルク エンドポイントにおける静的ストリームのオープン/クローズ方法

この記事では、静的ストリーム機能について説明し、USB クライアント ドライバーが USB 3.0 デバイスのバルク エンドポイントでストリームを開いたり閉じたりする方法を示しています。

USB 2.0 以前のデバイスでは、一括エンドポイントは、エンドポイントを介して 1 つのデータ ストリームを送受信できます。 USB 3.0 デバイスでは、一括エンドポイントには、エンドポイントを介して複数のデータ ストリームを送受信する機能があります。

Windows の Microsoft 提供の USB ドライバー スタックは、複数のストリームをサポートしています。 これにより、クライアント ドライバーは、USB 3.0 デバイスの一括エンドポイントに関連付けられている各ストリームに独立した I/O 要求を送信できます。 異なるストリームへの要求はシリアル化されません。

クライアント ドライバーの場合、ストリームは同じ特性のセットを持つ複数の論理エンドポイントを表します。 特定のストリームに要求を送信するには、クライアント ドライバーには、そのストリームへのハンドル (エンドポイントのパイプ ハンドルと同様) が必要です。 ストリームへの I/O 要求の URB は、一括エンドポイントへの I/O 要求の URB に似ています。 唯一の違いは、パイプ ハンドルです。 ストリームに I/O 要求を送信するために、ドライバーはストリームへのパイプ ハンドルを指定します。

デバイスの構成中に、クライアント ドライバーは選択構成要求と必要に応じて、インターフェイスの選択要求を送信します。 これらの要求は、インターフェイスのアクティブな設定で定義されているエンドポイントへのパイプ ハンドルのセットを取得します。 ストリームをサポートするエンドポイントの場合、ドライバーがストリームを開くまで (次に説明します) まで、エンドポイント パイプ ハンドルを使用して既定のストリーム (最初のストリーム) に I/O 要求を送信できます。

クライアントドライバーがデフォルトストリーム以外のストリームにリクエストを送信したい場合、ドライバーはすべてのストリームへのハンドルを開いて取得する必要があります。 これを行うために、クライアント ドライバーは、開くストリームの数を指定して、オープン ストリーム要求を送信します。 クライアント ドライバーがストリームの使用を終了した後、ドライバーは必要に応じて、クローズ ストリーム要求を送信することで、それらを閉じることができます。

カーネル モード ドライバー フレームワーク (KMDF) は、本質的に静的ストリームをサポートしません。 クライアント ドライバーは、ストリームを開いたり閉じたりする Windows Driver Model (WDM) スタイルの URL を送信する必要があります。 この記事では、これらの URL を書式設定して送信する方法について説明します。 ユーザー モード ドライバー フレームワーク (UMDF) クライアント ドライバーでは、静的ストリーム機能を使用できません。

この記事には、WDM ドライバーとしてラベル付けされたいくつかのメモが含まれています。 これらのメモでは、ストリーム要求を送信する WDM ベースの USB クライアント ドライバーのルーチンについて説明します。

前提条件

クライアント ドライバーがストリームを開いたり閉じたりするには、ドライバーに次のものが必要です。

  • WdfUsbTargetDeviceCreateWithParameters メソッドを呼び出しました。

    このメソッドでは、クライアント契約のバージョンが USBD_CLIENT_CONTRACT_VERSION_602 である必要があります。 そのバージョンを指定することで、クライアント ドライバーは一連の規則に従う必要があります。 詳細については、「ベスト プラクティス: URB の使用」に関するページを参照してください。

    この呼び出しは、フレームワークの USB ターゲット デバイス オブジェクトに対する WDFUSBDEVICE ハンドルを取得します。 そのハンドルは、後続の呼び出しを行ってストリームを開くために必要です。 通常、クライアント ドライバーは、ドライバーの EVT_WDF_DEVICE_PREPARE_HARDWARE イベント コールバック ルーチンに自身を登録します。

    WDM ドライバー: USBD_CreateHandle ルーチンを呼び出し、USB ドライバー スタックにドライバーの登録の USBD ハンドルを取得します。

  • デバイスを構成し、ストリームをサポートするバルク エンドポイントへの WDFUSBPIPE パイプ ハンドルを取得しました。 パイプ ハンドルを取得するには、選択した構成のインターフェイスの 現在の代替設定で WdfUsbInterfaceGetConfiguredPipe メソッドを呼び出します。

    WDM ドライバー: 選択構成または選択インターフェイス要求を送信して USBD パイプ ハンドルを取得します。 詳細については、「USB デバイス用の構成の選択方法」を参照してください。

静的ストリームを開く方法

  1. WdfUsbTargetDeviceQueryUsbCapability メソッドを呼び出して、基になる USB ドライバー スタックとホスト コントローラーが静的ストリーム機能をサポートしているかどうかを判断します。 通常、クライアント ドライバーは、ドライバーの EVT_WDF_DEVICE_PREPARE_HARDWARE イベント コールバック ルーチンでルーチンを呼び出します。

    WDM ドライバー: USBD_QueryUsbCapability ルーチンを呼び出します。 通常、ドライバーは、ドライバーのスタート デバイス ルーチン (IRP_MN_START_DEVICE) で使用する機能を照会します。 コード例については、USBD_QueryUsbCapability を参照してください。

    次の情報を指定します。

    • WdfUsbTargetDeviceCreateWithParameters の前回の呼び出しで取得された USB デバイス オブジェクトへのハンドル。クライアント ドライバーの登録用です。

      WDM ドライバー: 前の呼び出しで取得した USBD ハンドルを USBD_CreateHandle に渡します。

      クライアント ドライバーが特定の機能を使用したい場合、ドライバーはまず基礎となる USB ドライバー スタックをクエリして、ドライバー スタックとホスト コントローラーがその機能をサポートしているかどうかを判断する必要があります。 機能がサポートされている場合にのみ、ドライバーはその機能を使用する要求を送信する必要があります。 一部の要求には、ストリーム機能などの URL が必要です (手順 5 で説明します)。 これらの要求では、機能のクエリと URB の割り当てに同じハンドルを使用していることを確認してください。 これは、ドライバー スタックがハンドルを使用して、ドライバーが使用できるサポートされている機能を追跡するためです。

      たとえば、(USBD_CreateHandle を呼び出して) USBD_HANDLE を取得した場合は、USBD_QueryUsbCapability を呼び出してドライバー スタックにクエリを実行し、USBD_UrbAllocate を呼び出して URB を割り当てます。 両方の呼び出しで同じ USBD_HANDLE を渡します。

      KMDF メソッド WdfUsbTargetDeviceQueryUsbCapability および WdfUsbTargetDeviceCreateUrb を呼び出す場合は、これらのメソッド呼び出しでフレームワーク ターゲット オブジェクトに対して同じ WDFUSBDEVICE ハンドルを指定します。

    • GUID_USB_CAPABILITY_STATIC_STREAMS に割り当てられた GUID。

    • 出力バッファー (USHORT へのポインター)。 完了すると、バッファーには、ホスト コントローラーでサポートされている (エンドポイントごとの) ストリームの最大数が入力されます。

    • 出力バッファの長さ (バイト単位)。 ストリームの場合、長さは sizeof (USHORT) です。

  2. 返された NTSTATUS 値を評価します。 ルーチンが正常に完了し、STATUS_SUCCESS を返す場合は、静的ストリーム機能がサポートされます。 それ以外の場合、メソッドは適切なエラー コードを返します。

  3. 開くストリームの数を決定します。 開くことができるストリームの最大数は、次の方法で制限されます。

    • ホスト コントローラーでサポートされるストリームの最大数。 この番号は、呼び出し元が指定した出力バッファーで WdfUsbTargetDeviceQueryUsbCapability (WDM ドライバー、USBD_QueryUsbCapability) によって受信されます。 マイクロソフト提供の USB ドライバー スタックは、最大 255 のストリームをサポートしています。 WdfUsbTargetDeviceQueryUsbCapability では、ストリームの数を計算する際にその制限を考慮します。 このメソッドは、255 より大きい値を返すことはありません。
    • デバイス内のエンドポイントでサポートされるストリームの最大数。 その番号を取得するには、エンドポイント コンパニオン記述子を調べます (Usbspec.h の USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR を参照してください)。 エンドポイント コンパニオン記述子を取得するには、構成記述子を解析する必要があります。 構成記述子を取得するには、クライアント ドライバーが WdfUsbTargetDeviceRetrieveConfigDescriptor メソッドを呼び出す必要があります。 ヘルパー ルーチン、USBD_ParseConfigurationDescriptorEx および USBD_ParseDescriptor を使用する必要があります。 コード例については、「USB パイプを列挙する方法」の RetrieveStreamInfoFromEndpointDesc という名前の関数の例を参照してください。

    ストリームの最大数を決定するには、ホスト コントローラーとエンドポイントでサポートされる 2 つの値のうち小さい方を選択します。

  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_REQUESTHdr メンバーは、URB_FUNCTION_CLOSE_STATIC_STREAMS である必要があります
    • PipeHandle メンバーは、使用中の開いているストリームを含むエンドポイントへのハンドルである必要があります。
  3. WdfRequestSend または WdfUsbTargetDeviceSendUrbSynchronously を呼び出して、URB を WDF 要求として送信します。

クローズ ハンドル要求は、クライアント ドライバーによって以前に開かれたすべてのストリームを閉じます。 クライアント ドライバーは、要求を使用してエンドポイント内の特定のストリームを閉じることはできません。

静的ストリーム要求を送信するためのベスト プラクティス

USB ドライバー スタックは、受信した URB で検証を実行します。 検証エラーを回避するには:

  • ストリームをサポートしていないエンドポイントには、オープン ストリームまたはクローズ ストリーム要求を送信しないでください。 WdfUsbTargetDeviceQueryUsbCapability (WDM ドライバー、USBD_QueryUsbCapability) を呼び出して静的ストリームのサポートを決定し、エンドポイントがサポートしている場合にのみストリーム要求を送信します。
  • サポートされているストリームの最大数を超える数 (開くストリームの数) をリクエストしたり、ストリーム数を指定せずに要求を送信したりしないでください。 USB ドライバー スタックとデバイスのエンドポイントでサポートされているストリームの数に基づいてストリームの数を決定します。
  • 既に開いているストリームがあるエンドポイントには、オープン ストリーム要求を送信しないでください。
  • 開いているストリームがないエンドポイントには、クローズ ストリーム要求を送信しないでください。
  • エンドポイントの静的ストリームが開いた後は、構成選択要求またはインターフェイス選択要求を通じて取得したエンドポイント パイプ ハンドルを使用して 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;
}