USB KMDF 機能ドライバーにおけるセレクティブ サスペンド

この記事では、KMDF ファンクション ドライバーが USB セレクティブ サスペンドをサポートする方法について説明します。

USB ドライバーに、ユーザー モードで使用できない機能またはリソースが必要な場合、KMDF ファンクション ドライバーを指定する必要があります。 KMDF ドライバーは、KMDF 初期化構造に関連する値を設定し、適切なコールバック関数を指定することによって、セレクティブ サスペンドを実装します。 KMDF は、デバイスを一時停止および再開するため、下位ドライバーとの通信の詳細を処理します。

KMDF ドライバーでのセレクティブ サスペンドのガイドライン

セレクティブ サスペンドをサポートする KMDF ドライバーは、以下のガイドラインに従う必要があります。

  • KMDF ファンクション ドライバーは、デバイス スタックの PPO である必要があります。 既定では、KMDF ファンクション ドライバーは PPO です。
  • セレクティブ サスペンドをサポートする KMDF ファンクション ドライバーでは、電源管理されているキューまたは電源管理されていないキューを使用できます。 既定では、GPIO のキュー オブジェクトは電源管理されます。

電源ポリシーの所有権と KMDF USB ドライバー

既定では、USB デバイスの KMDF ファンクション ドライバーは、デバイス スタックの PPO です。 KMDF は、このドライバーの代わりにセレクティブ サスペンドと再開を管理します。

KMDF ドライバーでの I/O キュー構成

セレクティブ サスペンドをサポートする KMDF ファンクション ドライバーでは、電源管理されているキューまたは電源管理されていないキューを使用できます。 通常、ドライバーは、受信デバイスの I/O 制御要求を受け取るよう電源管理されていないキューを構成し、読み取り、書き込み、およびその他の電源依存の要求を受け取るよう 1 つ以上の電源管理キューを構成します。 要求が電源管理キューに到着すると、KMDF は、ドライバーに要求を提示する前に、デバイスが D0 にあることを確認します。

デバイス スタックの PPO の上に階層化された KMDF フィルター ドライバーを作成する場合、電源管理キューを使用しないでください。 理由は、UMDF ドライバーの場合と同じです。 デバイスが一時停止している間、フレームワークは電源管理キューからの要求を提示しないため、このようなキューを使用すると、デバイス スタックがストールする可能性があります。

KMDF ファンクション ドライバーのセレクティブ サスペンド メカニズム

KMDF は、USB セレクティブ サスペンドをサポートするために必要なほとんどの作業を処理します。 I/O アクティビティを追跡して、アイドル タイマーを管理し、親ドライバー (Usbhub.sys または Usbccgp.sys) がデバイスを一時停止して再開するデバイス I/O 制御要求を送信します。

KMDF ファンクション ドライバーがセレクティブ サスペンドをサポートしている場合、KMDF は、各デバイス オブジェクトが所有するすべての電源管理キューの I/O アクティビティを追跡します。 I/O カウントが 0 に達するたびに、フレームワークはアイドル タイマーを開始します。 タイムアウトの既定値は 5 秒です。

I/O 要求が、アイドル タイムアウト期間の経過前にデバイス オブジェクトに属する電源管理キューに到着した場合、フレームワークはアイドル タイマーを取り消し、デバイスを一時停止しません。

アイドル タイマーの有効期限が切れると、KMDF は USB デバイスを一時停止状態にするために必要な要求を発行します。 ファンクション ドライバーが USB エンドポイントで連続リーダーを使用する場合、リーダーの繰り返しポーリングは KMDF アイドル タイマーに対するアクティビティとしてカウントされません。 ただし、EvtDeviceD0Exit コールバック関数では、USB ドライバーは、デバイスが動作状態でない間にドライバーが I/O 要求を送信しないようにするため、電源管理されていないキューによって供給される継続的リーダーとその他の I/O ターゲットを手動で停止する必要があります。 ターゲットを停止するため、ドライバーは WdfIoTargetStop を呼び出し、ターゲット アクションとして WdfIoTargetWaitForSentIoToComplete を指定します。 応答として、フレームワークは、ターゲットの I/O キュー内のすべての I/O 要求が完了し、関連付けられている I/O 完了コールバックが実行された後のみ、I/O ターゲットを停止します。

既定では、KMDF はデバイスを D0 から移行し、アイドル設定でドライバーが指定したデバイスの電源状態に切り替えます。 KMDF は、移行の一環として、他の電源オフ シーケンスの場合と同じ方法でドライバーの電源コールバック関数を呼び出します。

デバイスが一時停止された後、次のいずれかのイベントが発生すると、フレームワークによってデバイスが自動的に再開されます。

  • ドライバーの電源管理キューのいずれかに対して I/O 要求が到着します。
  • ユーザーは、デバイス マネージャーを使用して USB セレクティブ サスペンドを無効にします。
  • ドライバーは、「USB デバイスの一時停止防止」の説明に従って、WdfDeviceStopIdle を呼び出します。

デバイスを再開するため、KMDF はデバイス スタックに電源投入要求を送信し、他の電源投入シーケンスの場合と同じ方法でドライバーのコールバック関数を呼び出します。

電源オフ シーケンスと電源投入シーケンスに関連するコールバックについて詳しくは、「WDF ドライバーのプラグ アンド プレイと電源管理」ホワイト ペーパーをご覧ください。

KMDF ファンクション ドライバーでの USB セレクティブ サスペンドのサポート

KMDF ファンクション ドライバーで USB セレクティブ サスペンドを実装するには、次のようにします。

  • アイドル タイムアウトなど、アイドル状態に関連する電源ポリシー設定を初期化します。
  • 開いているハンドル、またはデバイスの I/O キューに関連しないその他の理由により、デバイスを一時停止してはならないとドライバーが判断したとき、一時停止または再開操作を一時的に防止するロジックを必要に応じて含めます。
  • ヒューマン インターフェイス デバイス (HID) の USB ドライバーで、セレクティブ サスペンドをサポートしていることを INF で示します。

KMDF ファンクション ドライバーでの電源ポリシー設定の初期化

USB セレクティブ サスペンドのサポートを構成するには、KMDF ドライバーは、WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 構造を使用します。 ドライバーは、まず構造を初期化する必要があり、その後、ドライバーとそのデバイスの機能に関する詳細を提供するフィールドを設定できます。 通常、ドライバーは、EvtDriverDeviceAdd または EvtDevicePrepareHardware 関数でこの構造を埋めます。

WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 構造を初期化するには

ドライバーは、デバイス オブジェクトを作成した後、WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT 関数を使用して構造を初期化します。 この関数は、次の 2 つの引数を受け取ります。

  • 初期化する WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 構造へのポインター。
  • セレクティブ サスペンドのサポートを示す列挙値。 ドライバーは IdleUsbSelectiveSuspend を指定する必要があります。

ドライバーが IdleUsbSelectiveSuspend を指定した場合、関数は構造のメンバーを次のように初期化します。

  • IdleTimeoutIdleTimeoutDefaultValue (現在 5000 ミリ秒または 5 秒) に設定されます。
  • UserControlOfIdleSettingsIdleAllowUserControl に設定されます。
  • EnabledWdfUseDefault に設定されます。これは、セレクティブ サスペンドが有効になっていることを示しますが、UserControlOfIdleSettings メンバーにより許可されている場合は無効にできます。
  • DxStatePowerDeviceMaximum に設定され、デバイスの報告された電源機能を使用して、アイドル状態のデバイスを移行する状態を決定します。

USB セレクティブ サスペンドを構成するには

ドライバーが WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 構造を初期化した後、ドライバーは構造内の他のフィールドを設定し、WdfDeviceAssignS0IdleSettings を呼び出してこれらの設定をフレームワークに渡すことができます。 USB ファンクション ドライバーには、次のフィールドが適用されます。

  • IdleTimeout—フレームワークがデバイスをアイドル状態と見なす前に I/O 要求を受け取らずに経過する必要がある間隔 (ミリ秒単位)。 ドライバーは ULONG 値を指定することも、既定値をそのまま使用することもできます。

  • UserControlOfIdle設定—ユーザーがデバイスのアイドル設定を変更できるかどうかを示します。 指定できる値は IdleDoNotAllowUserControl と IdleAllowUserControl です。

  • DxState—フレームワークがデバイスを一時停止するデバイスの電源状態。 指定できる値は、PowerDeviceD1、PowerDeviceD2、PowerDeviceD3 です。

    USB ドライバーでは、この値の初期設定を変更しないでください。 WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT 関数は、この値を PowerDeviceMaximum に設定します。これにより、フレームワークがデバイスの機能に基づいて正しい値を選択できるようになります。

次のコード スニペットは、Osrusbfx2 サンプル ドライバーの Device.c ファイルからのものです。

WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings;
NTSTATUS    status = STATUS_SUCCESS;
//
// Initialize the idle policy structure.
//
WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, 
     IdleUsbSelectiveSuspend);
idleSettings.IdleTimeout = 10000; // 10 sec

status = WdfDeviceAssignS0IdleSettings(Device, &idleSettings);
if ( !NT_SUCCESS(status)) {
     TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
                 "WdfDeviceSetPowerPolicyS0IdlePolicy failed %x\n", 
                 status);
    return status;
}

この例では、ドライバーは IdleUsbSelectiveSuspend を指定して WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT を呼び出します。 ドライバーは IdleTimeout を 10,000 ミリ秒 (10 秒) に設定し、DxStateUserControlOfIdleSettings のフレームワークの既定値を受け入れます。 その結果、フレームワークは、デバイスがアイドル状態のとき D3 状態に移行し、管理者特権を持つユーザーがデバイスのアイドル状態のサポートを有効または無効にできるようにするデバイス マネージャー プロパティ ページを作成します。 その後、ドライバーは WdfDeviceAssignS0IdleSettings を呼び出してアイドル状態のサポートを有効にし、これらの設定をフレームワークに登録します。

ドライバーは、デバイス オブジェクトを作成した後いつでも WdfDeviceAssignS0IdleSettings を呼び出すことができます。 ほとんどのドライバーは、最初は EvtDriverDeviceAdd コールバックからこのメソッドを呼び出しますが、これは常に可能であるとは限りません。ただし、望ましい場合もあります。 ドライバーが複数のデバイスまたはデバイス バージョンをサポートしている場合、ドライバーは、ハードウェアを照会するまで、すべてのデバイス機能を認識していない可能性があります。 このようなドライバーは、EvtDevicePrepareHardware コールバックまで WdfDeviceAssignS0IdleSettings の呼び出しを遅延させることができます。

WdfDeviceAssignS0IdleSettings への最初の呼び出し後、ドライバーは、アイドル タイムアウト値とデバイスがアイドル状態のデバイス状態を変更できます。 1 つ以上の設定を変更するには、ドライバーは、前に説明したように別の WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS 構造を初期化し、再度 WdfDeviceAssignS0IdleSettings を呼び出します。

USB デバイスの一時停止の防止

タイムアウト期間内に I/O 要求が存在しない場合 (通常、デバイスに対してハンドルがオープンになっている場合やデバイスが充電中の場合) は、USB デバイスの電源をオフにしなくてもかまいません。 USB ドライバーは、WdfDeviceStopIdle を呼び出し、デバイスの一時停止が再び許容される場合に WdfDeviceResumeIdle を呼び出すことによって、このような状況でフレームワークがアイドル状態のデバイスを一時停止するのを防ぐことができます。

WdfDeviceStopIdle はアイドル タイマーを停止します。 IdleTimeout 期間の有効期限が切れておらず、デバイスがまだ一時停止されていない場合、フレームワークはアイドル タイマーを取り消し、デバイスを一時停止しません。 デバイスが既に一時停止されている場合、フレームワークはデバイスを動作状態に戻します。 システムが Sx スリープ状態に変わったときにフレームワークがデバイスを一時停止するのを、WdfDeviceStopIdle が防ぐことはありません。 その唯一の効果は、システムが S0 動作状態にある間にデバイスの一時停止を防ぐことです。 WdfDeviceResumeIdle はアイドル タイマーを再起動します。 これら 2 つのメソッドはデバイスの参照カウントを管理するため、ドライバーが WdfDeviceStopIdle を複数回呼び出した場合、フレームワークは、ドライバーが WdfDeviceResumeIdle を同じ回数呼び出すまでデバイスを一時停止しません。 ドライバーは、WdfDeviceStopIdle を呼び出す前に WdfDeviceResumeIdle を呼び出してはなりません。

レジストリ キーを含む (HID ドライバーのみ)

USB HID デバイスの KMDF 上位フィルター ドライバーは、セレクティブ サスペンドをサポートする INF で示す必要があります。これにより、マイクロソフトによって提供される HIDClass.sys ポート ドライバーで HID スタックのセレクティブ サスペンドを有効にすることができます。 INF には、次の文字列に示すように、SelectiveSuspendEnabled キーを追加し、その値を 1 に設定する AddReg ディレクティブを含める必要があります。

HKR,,"SelectiveSuspendEnabled",0x00000001,0x1

例については、%WinDDK%\BuildNumber\Src\Hid\ Hidusbfx2\sys にある WDK の Hidusbfx2.inx をご覧ください。

KMDF ドライバーのリモート ウェイク サポート

セレクティブ サスペンドと同様、KMDF にはウェイクアップのサポートが組み込まれているため、USB デバイスは、デバイスがアイドル状態で、システムが動作状態 (S0) またはスリープ状態 (S1 ~ S4) のときにウェイクアップ信号をトリガーできます。 KMDF の用語では、これら 2 つの機能はそれぞれ "S0 からのウェイクアップ" と "Sx からのウェイクアップ" と呼ばれます。

USB デバイスの場合、ウェイクアップは、デバイス自体が低電力状態から動作状態への移行を開始できることを示すにすぎません。 したがって、USB の用語では、S0 からのウェイクアップと Sx からのウェイクアップは同じであり、"リモート ウェイク" と呼ばれます。

KMDF USB ファンクション ドライバーは、セレクティブ サスペンド メカニズムの一部としてこの機能を提供するため、S0 からのウェイクアップをサポートするコードは必要ありません。 ただし、システムが Sx 内にある場合にリモート ウェイクをサポートするには、ファンクション ドライバーで次の操作を行う必要があります。

KMDF ドライバーは通常、EvtDriverDeviceAdd または EvtDevicePrepareHardware 関数で USB セレクティブ サスペンドのサポートを構成すると同時にウェイクアップ サポートを構成します。

デバイス機能の確認

KMDF USB ファンクション ドライバーは、アイドル状態とスリープ解除の電源ポリシー設定を初期化する前に、デバイスがリモート ウェイクをサポートしていることを確認する必要があります。 デバイスのハードウェア機能に関する情報を取得するため、ドライバーは WDF_USB_DEVICE_INFORMATION 構造を初期化し、(通常は EvtDriverDeviceAdd または EvtDevicePrepareHardware コールバックで) WdfUsbTargetDeviceRetrieveInformation を呼び出します。

WdfUsbTargetDeviceRetrieveInformation の呼び出しでは、ドライバーは、デバイス オブジェクトと、初期化された WDF_USB_DEVICE_INFORMATION 構造へのポインターにハンドルを渡します。 関数から正常に返されると、構造の Traits フィールドには、デバイスがセルフパワー型かつ高速で動作しており、リモート ウェイクをサポートしているかどうかを示すフラグが含められます。

Osrusbfx2 KMDF サンプルの次の例は、このメソッドを呼び出して、デバイスがリモート ウェイクをサポートしているかどうかを判断する方法を示しています。 これらのコード行が実行された後、waitWakeEnable 変数には、デバイスがリモート ウェイクをサポートしている場合は TRUE、サポートしていない場合は FALSE が含められます。

    WDF_USB_DEVICE_INFORMATION          deviceInfo;
// Retrieve USBD version information, port driver capabilities and device
// capabilites such as speed, power, etc.
//

WDF_USB_DEVICE_INFORMATION_INIT(&deviceInfo);

status = WdfUsbTargetDeviceRetrieveInformation(
                            pDeviceContext->UsbDevice,
                            &deviceInfo);
waitWakeEnable = deviceInfo.Traits & WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE;

リモート ウェイクアップの有効化

USB の用語では、USB デバイスは、その DEVICE_REMOTE_WAKEUP 機能が設定されている場合、リモート ウェイクアップが有効になります。 USB 仕様に従って、ホスト ソフトウェアは、デバイスをスリープ状態にする "直前" にデバイスにリモート ウェイクアップ機能を設定する必要があります。 KMDF ファンクション ドライバーは、ウェイクアップ設定を初期化するためにのみ必要です。 KMDF とマイクロソフトによって提供される USB バス ドライバーは、I/O 要求を発行し、リモート ウェイクアップを有効にするために必要なハードウェア操作を処理します。

ウェイク設定を初期化するには

  1. WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT を呼び出して、WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS 構造を初期化します。 この関数は、構造の Enabled メンバーを WdfUseDefault に設定し、DxState メンバーを PowerDeviceMaximum に設定し、UserControlOfWakeSettings メンバーを WakeAllowUserControl に設定します。
  2. 初期化された構造を使用して WdfDeviceAssignSxWakeSettings を呼び出します。 その結果、デバイスは D3 状態から復帰でき、ユーザーはデバイス マネージャーのデバイス プロパティ ページからのウェイク シグナルを有効または無効にできます。

Osrusbfx2 サンプルの次のコード スニペットは、ウェイク設定を既定値に初期化する方法を示しています。

WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings;

WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT(&wakeSettings);
status = WdfDeviceAssignSxWakeSettings(Device, &wakeSettings);
if (!NT_SUCCESS(status)) {
    return status;
}

セレクティブ サスペンドをサポートする USB デバイスの場合、基になるバス ドライバーは、スリープ解除するデバイス ハードウェアを準備します。 そのため、USB ファンクション ドライバーで EvtDeviceArmWakeFromS0 コールバックが必要になることはほとんどありません。 フレームワークは、アイドルタイムアウトの有効期限が切れると、USB バス ドライバーにセレクティブ サスペンド要求を送信します。

同じ理由から、USB ファンクション ドライバーで EvtDeviceWakeFromS0Triggered コールバックまたは EvtDeviceWakeFromSxTriggered コールバックが必要になることはほとんどありません。 代わりに、フレームワークと基になるバス ドライバーは、デバイスを動作状態に戻すすべての要件を処理します。