USB セレクティブ サスペンド

Note

この記事はデバイス ドライバー開発者を対象としています。 USB デバイスで問題が発生した場合は、「USB の一般的な問題のトラブルシューティング」を参照してください。

USB のセレクティブ サスペンド機能により、ハブ ドライバーはハブの他のポートの動作に影響を与えることなく、個々のポートをサスペンドできます。 USB デバイスのセレクティブ サスペンドは、バッテリーの節約に役立つため、ポータブル コンピューターでは特に便利です。 指紋リーダーや生体認証スキャナーなどの多くのデバイスは断続的にのみ電力を必要とします。 デバイスが使用されていないときに、そのようなデバイスを一時停止することで、全体的な電力消費を抑えることができます。 さらに重要なのは、セレクティブ サスペンドが有効でないデバイスでは、USB ホスト コントローラーによりシステム メモリに存在する転送スケジュールが無効にならない可能性があることです。 ホスト コントローラーからスケジューラーへのダイレクト メモリ アクセス (DMA) 転送により、システムのプロセッサが C3 などのより深いスリープ状態に入れなくなる可能性があります。

USB デバイスをセレクティブ サスペンドするための 2 つの異なるメカニズムとして、アイドル要求 IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) と電源設定 IRP (IRP_MN_SET_POWER) があります。 使用するメカニズムは、オペレーティング システムとデバイスのタイプ (複合デバイスか非複合デバイスか) によって異なります。

セレクティブ サスペンド メカニズムの選択

複合デバイスのインターフェイスに対して、待機ウェイク IRP (IRP_MN_WAIT_WAKE) を使用してリモート ウェイクアップを可能にするクライアント ドライバーは、デバイスをセレクティブ サスペンドするためにアイドル要求 IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) メカニズムを使用する必要があります。

リモート ウェイクアップについては、以下を参照してください。

Windows オペレーティング システムのバージョンによって、非複合デバイスのドライバーがセレクティブ サスペンドを有効にする方法が決まります。

  • Windows XP: Windows XP では、すべてのクライアント ドライバーはアイドル要求 IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION を使用してデバイスの電源を切る必要があります。 クライアント ドライバーがデバイスのセレクティブ サスペンドに WDM 電源 IRP を使用しないようにしてください。 この対処により、他のデバイスがセレクティブ サスペンドすることがなくなります。
  • Windows Vista 以降のバージョンの Windows: Windows Vista 以降のバージョンの Windows では、ドライバー作成者がデバイスの電源をオフにするための選択肢が増えました。 Windows Vista は Windows アイドル要求 IRP メカニズムをサポートしていますが、ドライバーはこのメカニズムを使用する必要はありません。

次の表では、アイドル要求 IRP の使用が必要なシナリオと、WDM 電源 IRP を使用して USB デバイスをサスペンドできるシナリオを示しています。

Windows バージョン 複合デバイス上の機能、ウェイク用の設定あり 複合デバイス上の機能、ウェイク用の設定なし 単一インターフェイス USB デバイス
Windows 7 アイドル要求 IRP を使用 WDM 電源 IRP を使用 WDM 電源 IRP を使用
Windows サーバー 2008 アイドル要求 IRP を使用 WDM 電源 IRP を使用 WDM 電源 IRP を使用
Windows Vista アイドル要求 IRP を使用 WDM 電源 IRP を使用 WDM 電源 IRP を使用
Windows Server 2003 アイドル要求 IRP を使用 アイドル要求 IRP を使用 アイドル要求 IRP を使用
Windows XP アイドル要求 IRP を使用 アイドル要求 IRP を使用 アイドル要求 IRP を使用

このセクションでは、Windows のセレクティブ サスペンドのメカニズムについて説明します。

USB アイドル要求 IRP の送信

デバイスがアイドル状態になると、クライアント ドライバーはアイドル要求 IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) を送信することでバス ドライバーに通知します。 バス ドライバーは、デバイスを低電力状態にしても安全であると判断した後、アイドル要求 IRP でスタックに渡されたクライアント デバイス ドライバーのコールバックルーチンを呼び出します。

コールバック ルーチンでは、クライアント ドライバーは保留中の I/O 操作をすべてキャンセルし、すべての USB I/O IRP が完了するまで待つ必要があります。 その後、IRP_MN_SET_POWER 要求を発行して、WDM デバイスの電力状態を D2 に変更できます。 コールバック ルーチンは、D2 要求が完了するまで待ってから、制御を呼び出し元に戻します。 アイドル通知コールバック ルーチンの詳細については、「USB アイドル通知コールバック ルーチン」を参照してください。

バス ドライバーは、アイドル通知コールバック ルーチンを呼び出した後、アイドル要求 IRP を完了しません。 代わりに、バス ドライバーは、次の条件のいずれかが満たされるまで、アイドル要求 IRP を保留中にします。

  • IRP_MN_SUPRISE_REMOVAL または IRP_MN_REMOVE_DEVICE IRP が受信されます。 これらの IRP のいずれかが受信されると、アイドル要求 IRP は STATUS_CANCELLED で完了します。
  • バス ドライバーは、デバイスを動作電力状態 (D0) にする要求を受信します。 この要求を受信すると、バス ドライバーは保留中のアイドル要求 IRP を STATUS_SUCCESS で完了します。

アイドル要求 IRP の使用には、次の制限が適用されます。

  • アイドル要求 IRP を送信するとき、ドライバーはデバイス電力状態 D0 にある必要があります。
  • ドライバーは、デバイス スタックごとにアイドル要求 IRP を 1 つのみ送信する必要があります。

次の WDM サンプル コードは、デバイス ドライバーが USB アイドル要求 IRP を送信する手順を示しています。 次のコード例では、エラー チェックが省略されています。

  1. IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION IRP を割り当てて初期化する

    irp = IoAllocateIrp (DeviceContext->TopOfStackDeviceObject->StackSize, FALSE);
    nextStack = IoGetNextIrpStackLocation (irp);
    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION;
    nextStack->Parameters.DeviceIoControl.InputBufferLength =
    sizeof(struct _USB_IDLE_CALLBACK_INFO);
    
  2. アイドル要求情報構造体 (USB_IDLE_CALLBACK_INFO) を割り当て、初期化します。

    idleCallbackInfo = ExAllocatePool (NonPagedPool,
    sizeof(struct _USB_IDLE_CALLBACK_INFO));
    idleCallbackInfo->IdleCallback = IdleNotificationCallback;
    // Put a pointer to the device extension in member IdleContext
    idleCallbackInfo->IdleContext = (PVOID) DeviceExtension;  
    nextStack->Parameters.DeviceIoControl.Type3InputBuffer =
    idleCallbackInfo;
    
  3. 完了ルーチンを設定します。

    クライアント ドライバーは完了ルーチンをアイドル要求 IRP に関連付ける必要があります。 アイドル通知完了ルーチンとコード例の詳細については、「USB アイドル要求 IRP 完了ルーチン」を参照してください。

    IoSetCompletionRoutine (irp,
        IdleNotificationRequestComplete,
        DeviceContext,
        TRUE,
        TRUE,
        TRUE);
    
  4. アイドル要求をデバイス拡張機能に保存します。

    deviceExtension->PendingIdleIrp = irp;
    
    
  5. アイドル要求を親ドライバーに送信します。

    ntStatus = IoCallDriver (DeviceContext->TopOfStackDeviceObject, irp);
    

USB アイドル要求のキャンセル

特定の状況では、デバイス ドライバーは、バス ドライバーに送信されたアイドル要求 IRP をキャンセルすることが必要になる場合があります。 この状況になるのは、デバイスが取り外された場合、アイドル状態になってアイドル要求を送信した後にアクティブになった場合、またはシステム全体が低いシステム電力状態に遷移している場合です。

クライアント ドライバーは、IoCancelIrp を呼び出して、アイドル IRP をキャンセルします。 次の表では、アイドル IRP をキャンセルするための 3 つのシナリオについて説明し、ドライバーが実行する必要があるアクションを示しています。

シナリオ アイドル要求キャンセルのメカニズム
クライアント ドライバーがアイドル IRP をキャンセルし、USB ドライバー スタックが「USB アイドル通知コールバック ルーチン」を呼び出していません。 USB ドライバー スタックはアイドル IRP を完了します。 デバイスが D0 から出ることがないため、ドライバーはデバイスの状態を変更しません。
クライアント ドライバーはアイドル IRP をキャンセルし、USB ドライバー スタックは USB アイドル通知コールバック ルーチンを呼び出しましたが、まだ制御は呼び出し元に戻っていません。 クライアント ドライバーが IRP のキャンセルを呼び出していても、USB アイドル通知コールバック ルーチンが呼び出される可能性があります。 この場合、クライアント ドライバーのコールバック ルーチンは、デバイスを同期的に低電力状態にすることで、デバイスの電源を切る必要があります。

デバイスが低電力状態にある場合、クライアント ドライバーは D0 要求を送信できます。

または、ドライバーは、USB ドライバー スタックがアイドル IRP を完了するのを待ってから、D0 IRP を送信することもできます。

コールバック ルーチンが、電力 IRP を割り当てるメモリの不足によりデバイスを低電力状態にできない場合は、アイドル IRP をキャンセルして直ちに終了する必要があります。 アイドル IRP は、コールバック ルーチンが制御を呼び出し元に戻すまで完了しません。したがって、アイドル IRP がキャンセルされた場合、コールバック ルーチンはアイドル IRP の完了を待ち続けないようにする必要があります。
デバイスは既に低電力状態にあります。 デバイスが既に低電力状態にある場合、クライアント ドライバーは D0 IRP を送信できます。 USB ドライバー スタックはアイドル要求 IRP を STATUS_SUCCESS で完了します。

または、ドライバーはアイドル IRP をキャンセルし、USB ドライバー スタックがアイドル IRP を完了するのを待ってから、D0 IRP を送信することもできます。

USB アイドル要求 IRP 完了ルーチン

多くの場合、バス ドライバーはドライバーのアイドル要求 IRP 完了ルーチンを呼び出すことがあります。 この状況になった場合、クライアント ドライバーは、バス ドライバーが IRP を完了した理由を検出する必要があります。 返されたステータス コードからこの情報が得られます。 ステータス コードが STATUS_POWER_STATE_INVALID でない場合、デバイスがまだ D0 になっていなければ、ドライバーは管理下のデバイスを D0 にする必要があります。 デバイスがまだアイドル状態である場合、ドライバーは別のアイドル要求 IRP を送信できます。

Note

アイドル要求 IRP 完了ルーチンは D0 電力要求の完了を待ち続けないようにする必要があります。 完了ルーチンは、ハブ ドライバーによって IRP のコンテキストで呼び出される可能性があり、完了ルーチンで別の電源 IRP を待ち続けると、デッドロックが発生する可能性があります。

次の一覧は、アイドル要求の完了ルーチンがいくつかの一般的なステータス コードをどのように解釈するかを示しています。

状態コード 説明
STATUS_SUCCESS デバイスをこれ以上サスペンドする必要がないことを示します。 ただし、ドライバーはデバイスに電力が供給されていることを確認し、まだ D0 になっていない場合は D0 にする必要があります。
STATUS_CANCELLED 次のいずれかの状況では、バス ドライバーはアイドル要求 IRP を STATUS_CANCELLED で完了します。
  • デバイス ドライバーが IRP をキャンセルしました。
  • システム電力状態の変更が必要です。
  • Windows XP では、接続された USB デバイスの 1 つのデバイス ドライバーが、アイドル要求コールバックルーチンを実行中に、管理下のデバイスを D2 にすることに失敗しました。 その結果、バス ドライバーは保留中のアイドル要求 IRP をすべて完了しました。
STATUS_POWER_STATE_INVALID デバイス ドライバーが管理下のデバイスに D3 電力状態を要求したことを示します。 この状況になった場合、バス ドライバーは STATUS_POWER_STATE_INVALID で保留中のアイドル IRP をすべて完了します。
STATUS_DEVICE_BUSY バス ドライバーがデバイスに対するアイドル要求 IRP を既に保留中にしていることを示します。 特定のデバイスに対して一度に保留中にできるアイドル IRP は 1 つのみです。 複数のアイドル要求 IRP の送信は、電力ポリシー所有者側のエラーであるため、ドライバー作成者が対処する必要があります。

次のコード例は、アイドル要求完了ルーチンのサンプル実装を示しています。

/*Routine Description:

  Completion routine for idle notification IRP

Arguments:

    DeviceObject - pointer to device object
    Irp - I/O request packet
    DeviceExtension - pointer to device extension

Return Value:

    NT status value

--*/

NTSTATUS
IdleNotificationRequestComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PDEVICE_EXTENSION DeviceExtension
    )
{
    NTSTATUS                ntStatus;
    POWER_STATE             powerState;
    PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;

    ntStatus = Irp->IoStatus.Status;

    if(!NT_SUCCESS(ntStatus) && ntStatus != STATUS_NOT_SUPPORTED)
    {

        //Idle IRP completes with error.

        switch(ntStatus)
        {

        case STATUS_INVALID_DEVICE_REQUEST:

            //Invalid request.

            break;

        case STATUS_CANCELLED:

            //1. The device driver canceled the IRP.
            //2. A system power state change is required.

            break;

        case STATUS_POWER_STATE_INVALID:

            // Device driver requested a D3 power state for its device
            // Release the allocated resources.

            goto IdleNotificationRequestComplete_Exit;

        case STATUS_DEVICE_BUSY:

            //The bus driver already holds an idle IRP pending for the device.

            break;

        default:
            break;

        }


        // If IRP completes with error, issue a SetD0

        //Increment the I/O count because
        //a new IRP is dispatched for the driver.
        //This call is not shown.

        powerState.DeviceState = PowerDeviceD0;

        // Issue a new IRP
        PoRequestPowerIrp (
            DeviceExtension->PhysicalDeviceObject,
            IRP_MN_SET_POWER,
            powerState,
            (PREQUEST_POWER_COMPLETE) PoIrpCompletionFunc,
            DeviceExtension,
            NULL);
    }

IdleNotificationRequestComplete_Exit:

    idleCallbackInfo = DeviceExtension->IdleCallbackInfo;

    DeviceExtension->IdleCallbackInfo = NULL;

    DeviceExtension->PendingIdleIrp = NULL;

    InterlockedExchange(&DeviceExtension->IdleReqPend, 0);

    if(idleCallbackInfo)
    {
        ExFreePool(idleCallbackInfo);
    }

    DeviceExtension->IdleState = IdleComplete;

    // Because the IRP was created using IoAllocateIrp,
    // the IRP needs to be released by calling IoFreeIrp.
    // Also return STATUS_MORE_PROCESSING_REQUIRED so that
    // the kernel does not reference this.

    IoFreeIrp(Irp);

    KeSetEvent(&DeviceExtension->IdleIrpCompleteEvent, IO_NO_INCREMENT, FALSE);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

USB アイドル通知コールバック ルーチン

バス ドライバー (ハブ ドライバーのインスタンス、または汎用親ドライバー) は、管理下のデバイスの子をいつサスペンドしても安全かを判断します。 安全であれば、各子のクライアント ドライバーによって提供されるアイドル通知コールバック ルーチンを呼び出します。

USB_IDLE_CALLBACK の関数プロトタイプは次のとおりです。

typedef VOID (*USB_IDLE_CALLBACK)(__in PVOID Context);

デバイス ドライバーは、アイドル通知コールバック ルーチンで次のアクションを実行する必要があります。

  • デバイスをリモート ウェイクアップ用に準備する必要がある場合は、デバイスに IRP_MN_WAIT_WAKE IRP を要求します。
  • すべての I/O をキャンセルし、デバイスを低電力状態に移行する準備をします。
  • デバイスを WDM スリープ状態にします。そのためには、PowerState パラメーターを列挙値 PowerDeviceD2(wdm.h、ntddk.h で定義) に設定して、PoRequestPowerIrp を呼び出します。 Windows XP では、デバイスがリモート ウェイクに対応していない場合でも、ドライバーが管理下のデバイスを PowerDeviceD3 にしないようにしてください。

Windows XP では、ドライバーはアイドル通知コールバック ルーチンに依存して、デバイスをセレクティブ サスペンドする必要があります。 Windows XP で動作するドライバーが、アイドル通知コールバック ルーチンを使用せずにデバイスを直接低電力状態にすると、USB デバイス ツリー内の他のデバイスがサスペンドされなくなる可能性があります。

ハブ ドライバーと USB 汎用親ドライバー (Usbccgp.sys) は両方とも、IRQL = PASSIVE_LEVEL でアイドル通知コールバック ルーチンを呼び出します。 これにより、コールバック ルーチンは電力状態の変更要求の完了を待ち続けることがなくなります。

コールバック ルーチンは、システムが S0 にあり、デバイスが D0 にあるときにのみ呼び出されます。

アイドル要求通知コールバック ルーチンには次の制限が適用されます。

  • デバイス ドライバーは、アイドル通知コールバック ルーチンで D0 から D2 へのデバイスの電力状態の遷移を開始できますが、他の電力状態の遷移は許可されません。 特に、ドライバーがコールバック ルーチンの実行中に管理下のデバイスを D0 に変更しようとすることがないようにしてください。
  • デバイス ドライバーがアイドル通知コールバック ルーチン内から複数の電力 IRP を要求しないようにしてください。

アイドル通知コールバック ルーチンでのウェイクアップのためのデバイスの準備

アイドル通知コールバック ルーチンは、管理下のデバイスに保留中の IRP_MN_WAIT_WAKE 要求があるかどうかを判断する必要があります。 保留中の IRP_MN_WAIT_WAKE 要求がない場合、コールバック ルーチンはデバイスを一時停止する前に IRP_MN_WAIT_WAKE 要求を送信する必要があります。 待機ウェイクメカニズムの詳細については、「ウェイクアップ機能を備えたデバイスのサポート」を参照してください。

USB グローバル サスペンド

USB 2.0 仕様では、グローバル サスペンドを、フレーム開始パケットを含むバス上のすべての USB トラフィックを停止することにより、USB ホスト コントローラーの背後のバス全体をサスペンドすることと定義しています。 まだサスペンドされていないダウンストリーム デバイスは、アップストリーム ポートのアイドル状態を検出し、自らサスペンド状態に入ります。 Windows では、この方法でグローバル サスペンドを実装しません。 Windows では、バス上のすべての USB トラフィックを停止する前に、USB ホスト コントローラーの背後にある各 USB デバイスを常にセレクティブ サスペンドします。

Windows 7 でのグローバル サスペンドの条件

Windows 7 は Windows Vista よりも USB ハブのセレクティブ サスペンドを積極的に利用しています。 Windows 7 の USB ハブドライバーは、接続されているすべてのデバイスが D1D2、または D3 のデバイス電力状態にあるハブをセレクティブ サスペンドします。 すべての USB ハブがセレクティブ サスペンドされると、バス全体がグローバル サスペンドに入ります。 Windows 7 USB ドライバー スタックは、デバイスが WDM デバイス状態 D1D2、または D3 にある場合は常に、そのデバイスをアイドルとして扱います。

Windows Vista でのグローバル サスペンドの条件

グローバル サスペンドを実行するための要件は、Windows XP よりも Windows Vista の方が柔軟です。

特に、Windows Vista では、デバイスが WDM デバイス状態 D1D2、または D3 にある場合は常に、USB スタックはそのデバイスをアイドルとして扱います。

次の図では、Windows Vista で発生する可能性のあるシナリオを示しています。

Diagram illustrating a global suspend in Windows Vista.

この図では、「Windows XP でのグローバル サスペンドの条件」で説明したものと同様の状況を示しています。 ただし、この場合、デバイス 3 はアイドル デバイスと見なされます。 すべてのデバイスがアイドル状態であるため、バス ドライバーは保留中のアイドル要求 IRP に関連するアイドル通知コールバック ルーチンを呼び出すことができます。 サスペンドが安全に実行できるようになるとすぐに、各デバイス ドライバーは管理下のデバイスをサスペンドし、バス ドライバーは USB ホスト コントローラーをサスペンドします。

Windows Vista では、グローバル サスペンドが開始される前に、ハブ以外のすべての USB デバイスが D1D2、または D3 にある必要があります。グローバル サスペンドが開始されると、ルート ハブを含むすべての USB ハブがサスペンドされます。 これは、セレクティブ サスペンドをサポートしていない USB クライアント ドライバーでは、バスがグローバル サスペンドに入ることができないということです。

Windows XP でのグローバル サスペンドの条件

Windows XP で最大限に電力を節約するには、すべてのデバイス ドライバーがアイドル要求 IRP を使用してデバイスをサスペンドすることが重要です。 1 つのドライバーがアイドル要求 IRP ではなく IRP_MN_SET_POWER 要求でデバイスをサスペンドした場合、他のデバイスがサスペンドできなくなる可能性があります。

次の図では、Windows XP で発生する可能性のあるシナリオを示しています。

Diagram illustrating a global suspend in Windows XP.

この図では、デバイス 3 は電力状態 D3 にあり、アイドル要求 IRP が保留中ではありません。 デバイス 3 は、親との間でアイドル要求 IRP が保留中でないため、Windows XP でグローバル サスペンドを行うためのアイドル デバイスとは見なされません。 これにより、バス ドライバーは、ツリー内の他のデバイスのドライバーに関連するアイドル要求コールバック ルーチンを呼び出すことができなくなります。

セレクティブ サスペンドの有効化

Microsoft Windows XP のアップグレード バージョンでは、セレクティブ サスペンドが無効になっています。 Windows XP、Windows Vista、およびそれ以降のバージョンの Windows のクリーン インストールでは有効になっています。

特定のルート ハブとその子デバイスのセレクティブ サスペンドを有効にするには、デバイス マネージャーで USB ルート ハブの [電源管理] タブでチェック ボックスをオンにします。

また、USB ポートドライバーのソフトウェア キー下で HcDisableSelectiveSuspend の値を設定することで、セレクティブ サスペンドを有効または無効にすることができます。 値に 1 を設定すると、セレクティブ サスペンドは無効になります。 値に 0 を設定すると、セレクティブ サスペンドが有効になります。

たとえば、Usbport.inf 内の次の行は、Hydra OHCI コントローラーのセレクティブ サスペンドを無効にします。

[OHCI_NOSS.AddReg.NT]
HKR,,"HcDisableSelectiveSuspend",0x00010001,1

クライアント ドライバーは、アイドル要求を送信する前に、セレクティブ サスペンドが有効かどうかを判断する必要はありません。 デバイスがアイドル状態のときは常に、アイドル要求を送信する必要があります。 アイドル要求が失敗した場合、クライアント ドライバーはアイドル タイマーをリセットして再試行する必要があります。