USB パイプ エラーからの回復方法
Note
この記事はデバイス ドライバー開発者を対象としています。 USB デバイスに関する問題が発生している場合は、「Windows での USB-C の問題を修正する」を参照してください。
この記事では、USB パイプへのデータ転送が失敗した場合に試すことができる手順について説明しています。 この記事で説明しているメカニズムでは、バルク、割り込み、アイソクロナス パイプでの中止、リセット、サイクル ポート操作を取り上げています。
USB クライアント ドライバーは、デフォルトのエンドポイントに制御転送を送信することでデバイスと通信します。デバイスのバルク、割り込み、アイソクロナス エンドポイントへのデータ転送。 場合によっては、エンドポイントでのストール状態などのさまざまな理由により、これらの転送が失敗することがあります。 転送が失敗した場合、エラー状態が解消されるまで、関連するパイプは要求を処理できません。
制御転送の場合、USB ドライバー スタックはエラー状態を自動的にクリアします。 データ転送の場合、クライアントはエラー状態から回復するための適切な手順を実行する必要があります。 データ転送が失敗すると、USB ドライバー スタックは、失敗した USBD 状態コードを介してクライアント ドライバーにエラーを報告します。 状態コードに基づいて、ドライバーはエラー回復メカニズムを提供できます。
この記事では、これらの操作によるエラー回復に関するガイドラインを提供します。
- USB パイプをリセットする
- デバイスが接続されている USB ポートをリセットする
- USB ポートを循環して、クライアント ドライバーのデバイス スタックを再列挙する
エラー状態をクリアするには、リセット パイプ操作から開始し、必要な場合にのみ、リセット ポートやサイクル ポートなどのより複雑な操作を実行します。
さまざまな復旧メカニズムの調整について:
クライアント ドライバーは、回復のためのさまざまな操作を調整し、特定の時点で 1 つの方法のみが使用されるようにする必要があります。 たとえば、一括と割り込みの 2 つのエンドポイントを持つデバイスについて考えてみます。 いくつかのデータ転送要求をデバイスに送信した後、ドライバーはバルク パイプ上で要求が失敗したことを認識します。 これらのエラーから回復するために、ドライバーは一括パイプをリセットします。 ただし、この操作では転送エラーは解決せず、一括転送は失敗し続けます。 したがって、ドライバーは、USB ポートをリセットする要求を発行します。 一方、転送は割り込みパイプで失敗し始め、次にデバイスのリセット要求が開始されます。 割り込み転送エラーから復旧するために、ドライバーは割り込みパイプに対してリセット パイプ要求を発行します。 これら 2 つの操作が調整されていない場合、両方のパイプで障害が発生したため、ドライバーが 2 つのデバイスのリセット操作を同時に開始する可能性があります。 これらの同時操作は問題になる可能性があります。
クライアント ドライバーは、特定の時点でリセット ポート操作またはサイクル ポート操作を 1 つだけ実行するようにする必要があります。 これらの操作中、どのパイプでもリセット パイプ操作が進行中であってはならず、ドライバーは新しいリセット パイプ要求を発行してはなりません。
知っておくべきこと
この記事では、カーネル モード ドライバー フレームワーク (KMDF) を使用します。
前提条件
クライアント ドライバーによって、フレームワーク USB ターゲット デバイス オブジェクトが作成されている必要があります。
Microsoft Visual Studio Professional 2012 で提供される USB テンプレートを使用している場合は、テンプレート コードがそれらのタスクを実行します。 テンプレート コードによりターゲット デバイス オブジェクトのハンドルが取得され、デバイス コンテキストに格納されます。
KMDF クライアント ドライバーでは、WdfUsbTargetDeviceCreateWithParameters メソッドを呼び出すことで WDFUSBDEVICE ハンドルを取得する必要があります。 詳細については、「USB クライアント ドライバー コード構造について (KMDF)」の「デバイスのソース コード」を参照してください。
クライアント ドライバーには、フレームワーク ターゲット パイプ オブジェクトへのハンドルが必要です。 詳細については、「USB パイプを列挙する方法」をご参照ください。
手順 1: エラー状態の原因を特定する
クライアント ドライバーは、USB 要求ブロック (URB) を使用してデータ転送を開始します。 要求が完了すると、USB ドライバー スタックは、転送が成功したか失敗したかを示す USBD 状態コードを返します。 エラーが発生した場合、USBD コードはエラーの理由を示します。
- WdfUsbTargetDeviceSendUrbSynchronously メソッドを呼び出して URB を送信した場合は、メソッドが戻った後で URB 構造体の Hdr.Status メンバーをチェックします。
- WdfRequestSend メソッドを呼び出して URB を非同期的に送信した場合は、EVT_WDF_REQUEST_COMPLETION_ROUTINE の状態をチェックします。 Params パラメーターは、WDF_REQUEST_COMPLETION_PARAMS 構造体を指します。 USBD 状態コードをチェックするには、Usb->UsbdStatus メンバーを調べます。 コードの詳細については、USBD_STATUS を参照してください。
転送エラーは、USBD_STATUS_STALL_PID や USBD_STATUS_BABBLE_DETECTED などのデバイス エラーが原因で発生する可能性があります。 また、ホスト コントローラーによって報告されたエラー (USBD_STATUS_XACT_ERROR など) が原因で発生する場合もあります。
手順 2: デバイスがポートに接続されているかどうかを確認する
パイプまたはデバイスをリセットする要求を発行する前に、デバイスが接続されていることを確認します。 WdfUsbTargetDeviceIsConnectedSynchronous メソッドを呼び出すことによって、デバイスの接続状態を確認できます。
手順 3: パイプへの保留中のすべての転送をキャンセルする
パイプまたはポートをリセットする要求を送信する前に、USB ドライバー スタックがまだ完了していないパイプへの保留中の転送要求をすべてキャンセルします。 要求は、次のいずれかの方法でキャンセルすることができます。
WdfIoTargetStop メソッドを呼び出して、I/O ターゲットを停止します。
I/O ターゲットを停止するには、まず、WdfUsbTargetPipeGetIoTarget メソッドを呼び出して、フレームワーク パイプ オブジェクトに関連付けられている WDFIOTARGET ハンドルを取得します。 ハンドルを使用して、WdfIoTargetStop を呼び出します。 呼び出しで、アクションを WdfIoTargetCancelSentIo (WDF_IO_TARGET_SENT_IO_ACTION を参照)** に設定して、USB ドライバー スタックが完了していないすべての要求をキャンセルするようにフレームワークに指示します。 完了した要求の場合、クライアント ドライバーは、完了コールバックがフレームワークによって呼び出されるまで待機する必要があります。
パイプ中止要求を送信します。 次のいずれかのメソッドを呼び出して、要求を送信できます。
WdfUsbTargetPipeAbortSynchronously メソッドを呼び出します。
呼び出しは同期的であり、保留中のすべての要求が取り消された後にのみ返されます。 WdfUsbTargetPipeAbortSynchronously は、省略可能な Request パラメーターを受け取ります。 事前に割り当てられたフレームワーク要求オブジェクトに WDFREQUEST ハンドルを渡すことをお勧めします。 このパラメーターを使用すると、ドライバーがアクセスできない内部要求オブジェクトの代わりに、指定された要求オブジェクトを使用するフレームワークが有効になります。 このパラメーター値により、メモリ不足により WdfUsbTargetPipeAbortSynchronously が失敗しないようになります。
パイプ中止要求の要求オブジェクトを書式設定するには、WdfUsbTargetPipeFormatRequestForAbort メソッドを呼び出してから、WdfRequestSend メソッドを呼び出して要求を送信します。
ドライバーが非同期的に要求を送信する場合は、ドライバーが実装するドライバーの EVT_WDF_REQUEST_COMPLETION_ROUTINE へのポインターを指定する必要があります。 ポインターを指定するには、WdfRequestSetCompletionRoutine メソッドを呼び出します。
ドライバーは、WdfRequestSend の要求オプションの 1 つとして WDF_REQUEST_SEND_OPTION_SYNCHRONOUS を指定することで、同期的に要求を送信できます。 要求を同期的に送信する場合は、代わりに WdfUsbTargetPipeAbortSynchronously を呼び出します。
手順 4: USB パイプをリセットする
パイプをリセットして、エラー復旧を開始します。 次のいずれかのメソッドを呼び出して、パイプ リセット要求を送信できます。
WdfUsbTargetPipeResetSynchronousously を呼び出して、リセット パイプ要求を同期的に送信します。
パイプ リセット要求の要求オブジェクトを書式設定するには、WdfUsbTargetPipeFormatRequestForReset メソッドを呼び出してから、WdfRequestSend メソッドを呼び出して要求を送信します。 これらの呼び出しは、手順 3 で説明したパイプ中止要求の呼び出しと似ています。
Note
パイプのリセット操作が完了するまで、新しい転送要求を送信しないでください。
パイプ リセット要求は、デバイスとホスト コントローラー ハードウェアのエラー状態をクリアします。 デバイス エラーをクリアするために、USB ドライバー スタックは、ENDPOINT_HALT 機能セレクターを使用して CLEAR_FEATURE 制御要求をデバイスに送信します。 要求の受信者は、パイプに関連付けられているエンドポイントです。 等時性パイプでエラー状態が発生すると等時性エンドポイントが自動的にクリアされるため、ドライバー スタックはデバイスをクリアするためのアクションを実行しません。
ホスト コントローラーエラーをクリアするために、ドライバー スタックはパイプの HALT 状態をクリアし、パイプのデータ トグルを 0 にリセットします。
手順 5: USB ポートをリセットする
パイプのリセット操作でエラー状態が解消せず、データ転送が失敗し続ける場合は、ポート リセット要求を送信します。
デバイスへのすべての転送をキャンセルします。 これを行うには、現在の構成内のすべてのパイプを列挙し、各パイプに対してスケジュールされている保留中の要求をキャンセルします。
デバイスの I/O ターゲットを停止します。
WdfUsbTargetDeviceGetIoTarget メソッドを呼び出して、フレームワーク ターゲット デバイス オブジェクトに関連付けられている WDFIOTARGET ハンドルを取得します。 次に、WdfIoTargetStop を呼び出して、WDFIOTARGET ハンドルを指定します。 呼び出しで、アクションを WdfIoTargetCancelSentIo (WDF_IO_TARGET_SENT_IO_ACTION) に設定します。
WdfUsbTargetDeviceResetPortSynchronously メソッドを呼び出して、ポート リセット要求を送信します。
リセット ポート操作により、デバイスが USB バスで再列挙されます。 USB ドライバー スタックは、列挙の後にデバイス構成を保持します。 ドライバー スタックによって既存のパイプ ハンドルが有効なままであることが保証されるため、クライアント ドライバーは以前に取得したパイプ ハンドルを使用できます。
複合デバイスの個々の関数をリセットすることはできません。 複合デバイスの場合、特定の関数のクライアント ドライバーがポート リセット要求を送信すると、デバイス全体がリセットされます。 USB デバイスが状態を維持している場合、そのポート リセット要求は他の機能のクライアント ドライバーに影響を与える可能性があります。 したがって、クライアント ドライバーがポートをリセットする前にパイプのリセットを試みることが重要です。
手順 6: USB ポートをサイクルする
ポート サイクルの操作は、デバイスが電気的に切断されない点を除いて、デバイスのプラグが抜かれてポートに再び差し込まれる場合と似ています。 デバイスが切断され、ソフトウェアで再接続されます。 この操作により、デバイスのリセットと列挙が行われます。 その結果、PnP マネージャーはデバイス ノードを再構築します。
ポートのリセット操作でエラー状態が解消せず、データ転送が失敗し続ける場合は、ポート サイクル要求を送信します。
デバイスへのすべての転送をキャンセルします。 現在の構成の各パイプにスケジュールされている保留中のリクエストを必ずキャンセルしてください (ステップ 3 を参照)。
デバイスの I/O ターゲットを停止します。
WdfUsbTargetDeviceGetIoTarget メソッドを呼び出して、フレームワーク ターゲット デバイス オブジェクトに関連付けられている WDFIOTARGET ハンドルを取得します。 次に、WdfIoTargetStop を呼び出して、WDFIOTARGET ハンドルを指定します。 呼び出しで、アクションを WdfIoTargetCancelSentIo (WDF_IO_TARGET_SENT_IO_ACTION) に設定します。
次のいずれかのメソッドを呼び出して、ポート サイクル要求を送信します。
- WdfUsbTargetDeviceCyclePortSynchronous を呼び出して、ポート サイクル要求を同期的に送信します。
- パイプ サイクル要求の要求オブジェクトを書式設定するには、WdfUsbTargetDeviceFormatRequestForCyclePort メソッドを呼び出してから、WdfRequestSend メソッドを呼び出して要求を送信します。 これらの呼び出しは、手順 3 で説明したパイプ中止要求の呼び出しと似ています。
クライアント ドライバーは、ポート サイクル要求が完了した後にのみ、デバイスに転送要求を送信できます。 これは、USB ドライバー スタックがポート サイクル要求を処理している間にデバイス ノードが削除されるためです。
ポート サイクル要求により、デバイスが再列挙されます。 USB ドライバー スタックは、デバイスが切断されたことを PnP マネージャーに通知します。 PnP マネージャーは、クライアント ドライバーに関連付けられているデバイス スタックを破棄します。 ドライバー スタックはデバイスをリセットし、USB バス上で再列挙し、デバイスが接続されたことを PnP マネージャーに通知します。 PnP マネージャーは、USB デバイスのデバイス スタックを再構築します。
ポート サイクルの操作の結果、デバイスに対してハンドルを開いているアプリケーションはデバイスの取り外し通知を受け取ります (アプリケーションがそのような通知に登録されている場合)。 これに対して、アプリケーションはデバイスが切断されたメッセージをユーザーに報告する場合があります。 これはユーザー エクスペリエンスに影響を与えるため、クライアント ドライバーは、他の回復メカニズムでエラー状態が解決されない場合にのみポート サイクル要求を選択する必要があります。
複合デバイスのポート リセット操作 (手順 6 で説明) と同様に、ポート サイクル操作はデバイスの個々の機能ではなく、デバイス全体に影響します。