技術共有

Windows USB デバイス ドライバー開発 - バッチ転送の静的ストリームの処理

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

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) は、静的ストリームをネイティブにサポートしません。 クライアント ドライバーは、ストリームを開いたり閉じたりするために Windows Driver Model (WDM) を使用する必要があります。 ユーザーモード ドライバー フレームワーク (UMDF) クライアント ドライバーは、静的ストリーミング機能を使用できません。

以下には、「WDM ドライバー」というラベルの付いたコメントが含まれている場合があります。 これらの手順では、ストリーミング要求を送信する WDM ベースの USB クライアント ドライバーのルーチンについて説明します。

前提条件

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

1. WdfUsbTargetDeviceCreateWithParameters メソッドを呼び出します。このメソッドには USBD_CLIENT_CONTRACT_VERSION_602 クライアント プロトコル バージョンが必要です。 このバージョンを指定すると、クライアント ドライバーは一連のルールに従う必要があります。

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

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

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

WDM ドライバー: select-configuration または select-interface リクエストを送信することにより、USBD パイプ ハンドルを取得します。

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

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

WDM ドライバー: USBD_QueryUsbCapability ルーチンを呼び出します。 通常、ドライバーは、ドライバーのスタートアップ デバイス ルーチン (IRP_MN_START_DEVICE) で使用される関数をクエリします。

次の情報を入力してください。

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

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

クライアント ドライバーが特定の機能を使用したい場合、ドライバーはまず基礎となる USB ドライバー スタックをクエリして、ドライバー スタックとホスト コントローラーがその機能をサポートしているかどうかを判断する必要があります。 機能がサポートされている場合にのみ、ドライバーはその機能を使用するリクエストを送信する必要があります。 ステップ 5 で説明したストリーミング機能など、特定のリクエストには URB が必要です。 これらのリクエストでは、必ず同じハンドルを使用して関数をクエリし、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 (WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability を呼び出し元が提供する出力バッファーで受け取ります)。 Microsoft が提供する USB ドライバー スタックは、最大 255 のストリームをサポートします。 WdfUsbTargetDeviceQueryUsbCapability は、ストリーム数を計算するときにこの制限を考慮します。 このメソッドは 255 を超える値を返すことはありません。
  • デバイスのエンドポイントによってサポートされるストリームの最大数。 この番号を取得するには、エンドポイント コンパニオン記述子 (Usbspec.h 内) の USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR を確認してください。 エンドポイント コンパニオン記述子を取得するには、構成記述子を解析する必要があります。 構成記述子を取得するには、クライアント ドライバーは WdfUsbTargetDeviceRetrieveConfigDescriptor メソッドを呼び出す必要があります。 ヘルパー ルーチン、USBD_ParseConfigurationDescriptorEx および USBD_ParseDescriptor を使用する必要があります。

フローの最大数を決定するには、ホスト コントローラーとエンドポイントでサポートされている 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で作成(ストリーム配列)
  • (手順 5) で作成した URB 構造体へのポインタ。

URB をフォーマットするには、UsbBuildOpenStaticStreamsRequest を呼び出し、必要な情報をパラメーター値として渡します。 UsbBuildOpenStaticStreamsRequest に指定するストリーム数が、サポートされる最大ストリーム数を超えないようにしてください。

7. WdfRequestSend メソッドを呼び出して、URB を WDF 要求オブジェクトとして送信します。 要求を同期的に送信するには、代わりに WdfUsbTargetDeviceSendUrbSynchronously メソッドを呼び出します。

WDM ドライバー: URB を IRP に関連付け、IRP を USB ドライバー スタックに送信します。

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 要求オブジェクトの受け渡しはオプションです。

データをストリームに転送するには、URB を使用する必要があります。 URB は、WdfUsbTargetPipeFormatRequestForUrb を呼び出してフォーマットする必要があります。

ストリームは次の WDF メソッドをサポートしていません。

  • WdfUsbTargetPipeFormatRequestForRead
  • WdfUsbTargetPipeFormatRequestForWrite
  • WdfUsbTargetPipeRead 同期
  • WdfUsbTargetPipeWrite 同期

次の手順では、クライアント ドライバーがフレームワークから要求オブジェクトを受信することを前提としています。

  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_FUNCTION_CLOSE_STATIC_STREAMS_URB_PIPE_REQUEST の必須 Hdr メンバー
  • PipeHandle メンバーは、ストリームを開くために使用されるエンドポイントを含むハンドルである必要があります。

3. WdfRequestSend または WdfUsbTargetDeviceSendUrbSynchronously を呼び出して、URB を WDF 要求として送信します。

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

静的ストリーミングリクエストを送信するためのベストプラクティス

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

  • ストリームをサポートしていないエンドポイントにオープン ストリーム リクエストまたはクローズ ストリーム リクエストを送信しないでください。 WDM ドライバーの WdfUsbTargetDeviceQueryUsbCapability (、USBD_QueryUsbCapability) を呼び出して静的ストリーミング サポートを決定し、エンドポイントがサポートしている場合にのみストリーミング リクエストを送信します。
  • サポートされている最大ストリーム数を超えるストリーム (オープン) を要求したり、ストリーム数を指定せずに要求を送信したりしないでください。 ストリームの数は、USB ドライバー スタックとデバイス エンドポイントによってサポートされるストリームの数に基づいて決定されます。
  • すでにオープン ストリームがあるエンドポイントにオープン ストリーム リクエストを送信しないでください。
  • オープンストリームを持たないエンドポイントにクローズストリームリクエストを送信しないでください。
  • エンドポイントの静的ストリームを開いた後は、選択構成または選択インターフェイス要求を通じて取得したエンドポイント パイプ ハンドルを使用して I/O 要求を送信しないでください。 これは、静的ストリームが閉じている場合でも当てはまります。
パイプライン操作のリセットと中止

場合によっては、エンドポイントとの間の転送が失敗することがあります。 このような障害は、エンドポイントまたはホスト コントローラーのエラー状態 (停止状態や停止状態など) によって引き起こされる可能性があります。 エラー状態をクリアするには、クライアント ドライバーはまず保留中の転送をキャンセルし、次にエンドポイントに関連付けられたパイプをリセットします。 保留中の転送をキャンセルするために、クライアント ドライバーは中止パイプ リクエストを送信できます。 パイプをリセットするには、クライアント ドライバーがパイプのリセット要求を送信する必要があります。

ストリーミングの場合、一括エンドポイントに関連付けられた個々のストリームでは、パイプ中止リクエストとリセットパイプリクエストはサポートされません。 特定のストリーム パイプでの転送が失敗した場合、ホスト コントローラーは他のストリームの他のすべてのパイプでの転送を停止し、エラー状態から回復するには、クライアント ドライバーは各ストリームへの転送を手動でキャンセルする必要があります。リセット パイプ要求をバルク エンドポイントに送信するためのパイプ ハンドル。この要求の場合、クライアント ドライバーは _URB_PIPE_REQUEST 構造体でエンドポイントのパイプ ハンドルを指定し、URB 関数 (Hdr.Function) を URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL に設定する必要があります。

完全な例

次のコード例は、ストリームを開く方法を示しています。

  1. NTSTATUS
  2. OpenStreams (
  3. _In_ WDFDEVICE Device,
  4. _In_ WDFUSBPIPE Pipe)
  5. {
  6. NTSTATUS status;
  7. PDEVICE_CONTEXT deviceContext;
  8. PPIPE_CONTEXT pipeContext;
  9. USHORT cStreams = 0;
  10. USBD_PIPE_HANDLE usbdPipeHandle;
  11. WDFMEMORY urbMemory = NULL;
  12. PURB urb = NULL;
  13. PAGED_CODE();
  14. deviceContext =GetDeviceContext(Device);
  15. pipeContext = GetPipeContext (Pipe);
  16. if (deviceContext->MaxStreamsController == 0)
  17. {
  18. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  19. "%!FUNC! Static streams are not supported.");
  20. status = STATUS_NOT_SUPPORTED;
  21. goto Exit;
  22. }
  23. // If static streams are not supported, number of streams supported is zero.
  24. if (pipeContext->MaxStreamsSupported == 0)
  25. {
  26. status = STATUS_DEVICE_CONFIGURATION_ERROR;
  27. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  28. "%!FUNC! Static streams are not supported by the endpoint.");
  29. goto Exit;
  30. }
  31. // Determine the number of streams to open.
  32. // Compare the number of streams supported by the endpoint with the
  33. // number of streams supported by the host controller, and choose the
  34. // lesser of the two values. The deviceContext->MaxStreams value was
  35. // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
  36. // that determined whether or not static streams is supported and
  37. // retrieved the maximum number of streams supported by the
  38. // host controller. The device context stores the values for IN and OUT
  39. // endpoints.
  40. // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
  41. // The number of elements in the array is the number of streams to open.
  42. // The code snippet stores the array in its device context.
  43. cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);
  44. // Allocate an array of streams associated with the IN bulk endpoint
  45. // This array is released in CloseStreams.
  46. pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
  47. NonPagedPool,
  48. sizeof (USBD_STREAM_INFORMATION) * cStreams,
  49. USBCLIENT_TAG);
  50. if (pipeContext->StreamInfo == NULL)
  51. {
  52. status = STATUS_INSUFFICIENT_RESOURCES;
  53. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  54. "%!FUNC! Could not allocate stream information array.");
  55. goto Exit;
  56. }
  57. RtlZeroMemory (pipeContext->StreamInfo,
  58. sizeof (USBD_STREAM_INFORMATION) * cStreams);
  59. // Get USBD pipe handle from the WDF target pipe object. The client driver received the
  60. // endpoint pipe handles during device configuration.
  61. usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);
  62. // Allocate an URB for the open streams request.
  63. // WdfUsbTargetDeviceCreateUrb returns the address of the
  64. // newly allocated URB and the WDFMemory object that
  65. // contains the URB.
  66. status = WdfUsbTargetDeviceCreateUrb (
  67. deviceContext->UsbDevice,
  68. NULL,
  69. &urbMemory,
  70. &urb);
  71. if (status != STATUS_SUCCESS)
  72. {
  73. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  74. "%!FUNC! Could not allocate URB for an open-streams request.");
  75. goto Exit;
  76. }
  77. // Format the URB for the open-streams request.
  78. // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
  79. // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.
  80. UsbBuildOpenStaticStreamsRequest (
  81. urb,
  82. usbdPipeHandle,
  83. (USHORT)cStreams,
  84. pipeContext->StreamInfo);
  85. // Send the request synchronously.
  86. // Upon completion, the USB driver stack populates the array of with handles to streams.
  87. status = WdfUsbTargetPipeSendUrbSynchronously (
  88. Pipe,
  89. NULL,
  90. NULL,
  91. urb);
  92. if (status != STATUS_SUCCESS)
  93. {
  94. goto Exit;
  95. }
  96. Exit:
  97. if (urbMemory)
  98. {
  99. WdfObjectDelete (urbMemory);
  100. }
  101. return status;
  102. }