複合ドライバーに対する機能中断の実装方法
この記事では、ユニバーサル シリアル バス (USB) 3.0 多機能デバイス (複合デバイス) の機能中断と機能リモート ウェイクアップ機能の概要について説明します。 この記事では、複合デバイスを制御するドライバーにこれらの機能を実装する方法について説明します。 この記事は、Usbccgp.sys を置き換える複合ドライバーに適用されます。
ユニバーサル シリアル バス (USB) 3.0 仕様では、機能中断と呼ばれる新機能が定義されています。 この機能により、複合デバイスの個々の機能が、他の機能とは無関係に低電力状態に入ります。 キーボード用の機能とマウス用の別の機能を定義する複合デバイスについて考えてみましょう。 ユーザーはキーボード機能を動作状態に保ちますが、一定期間マウスを移動しません。 マウスのクライアント ドライバーは、キーボード機能が動作状態にある間に、機能のアイドル状態を検出し、その機能を中断状態に送信できます。
デバイス内の機能の電源状態に関係なく、デバイス全体が中断状態に移行できます。 特定の機能とデバイス全体が中断状態になると、デバイスが中断状態にある間、およびデバイスの中断開始プロセスおよび中断終了プロセス全体を通じて、機能の中断状態が保持されます。
USB 2.0 デバイスのリモート ウェイクアップ機能 (USB デバイスのリモート ウェイクアップを参照) と同様に、USB 3.0 複合デバイスの個々の機能は、他の機能の電源状態に影響を与えずに低電力状態から復帰できます。 この機能は、機能リモート ウェイクアップと呼ばれます。 この機能は、デバイスのファームウェアにリモート ウェイクアップ ビットを設定するプロトコル要求を送信することにより、ホストによって明示的に有効になります。 このプロセスは、リモート ウェイクアップ用の機能のアーミングと呼ばれます。 リモート ウェイク関連ビットの詳細については、公式 USB 仕様の図 9-6 を参照してください。
機能がリモート ウェイクアップ用に武装している場合、ユーザー イベントが物理デバイスで発生したときにウェイクアップ再開信号を生成するのに十分な電力が機能 (中断状態の場合) に保持されます。 その再開信号の結果として、クライアント ドライバーは関連付けられている機能の中断状態を終了できます。 複合デバイスのマウス機能の例では、ユーザーがアイドル状態のマウスを小刻みに動かすと、マウス機能はホストに再開信号を送信します。 ホスト上では、USB ドライバー スタックがどの機能が起動したかを検出し、対応する機能のクライアント ドライバーに通知を伝達します。 その後、クライアント ドライバーは機能をウェイクアップし、動作状態に入ることができます。
クライアント ドライバーの場合、機能を中断状態に送信し、その機能をウェイクアップする手順は、デバイス全体を中断状態に送信する単機能デバイス ドライバーと同様です。 次の手順は、これらの手順を要約したものです。
- 関連する機能がアイドル状態にあることを検出します。
- アイドル状態の I/O 要求パケット (IRP) を送信します。
- 待機ウェイク I/O 要求パケット (IRP) を送信して、リモート ウェイクアップの機能を準備する要求を送信します。
- Dx パワー IRP (D2 または D3) を送信して、機能を低電力状態に移行します。
上記の手順の詳細については、USB 選択的中断の「USB アイドル要求 IRP の送信」を参照してください。 複合ドライバーは、複合デバイス内の機能ごとに物理デバイス オブジェクト (PDO) を作成し、クライアント ドライバー (機能デバイス スタックの FDO) によって送信された電力要求を処理します。 クライアント ドライバーがその機能の中断状態を正常に開始および終了するには、複合ドライバーが機能の中断とリモート ウェイクアップ機能をサポートし、受信した電力要求を処理する必要があります。
Windows 8 では、USB 3.0 デバイス用の USB ドライバー スタックでこれらの機能がサポートされています。 さらに、Windows の既定の複合ドライバーである、マイクロソフトが提供する USB 汎用親ドライバー (Usbccgp.sys) に、機能の中断と機能のリモート ウェイクアップの実装が追加されました。 カスタム複合ドライバーを作成する場合、ドライバーは、次の手順に従って、機能の中断とリモート ウェイクアップ要求に関連する要求を処理する必要があります。
手順 1: USB ドライバー スタックで機能の中断がサポートされているかどうかを確認する
複合ドライバーの開始デバイス ルーチン (IRP_MN_START_DEVICE) で、次の手順を実行します。
- USBD_QueryUsbCapability ルーチンを呼び出して、基になる USB ドライバー スタックが機能の中断機能をサポートしているかどうかを判断します。 この呼び出しには、USBD_CreateHandle ルーチンへの以前の呼び出しで取得した有効な USBD ハンドルが必要です。
USBD_QueryUsbCapability の呼び出しに成功すると、基になる USB ドライバー スタックで機能の中断がサポートされているかどうかを判断します。 この呼び出しでは、USB ドライバー スタックが機能の中断をサポートしていないか、接続されているデバイスが USB 3.0 多機能デバイスではないことを示すエラー コードが返される場合があります。
- USBD_QueryUsbCapability 呼び出しで機能の中断がサポートされていることが示されている場合は、複合デバイスを基になる USB ドライバー スタックに登録します。 複合デバイスを登録するには、IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE I/O 制御要求を送信する必要があります。 この要求の詳細については、「複合デバイスの登録方法」を参照してください。
登録要求では、REGISTER_COMPOSITE_DEVICE 構造体を使用して、複合ドライバーに関する情報を指定します。 CapabilityFunctionSuspend を 1 に設定していることを確認して、複合ドライバーが機能の中断をサポートしていることを示します。
USB ドライバー スタックが機能の中断をサポートしているかどうかを確認する方法を示すコード例については、USBD_QueryUsbCapability を参照してください。
手順 2: アイドル状態の IRP を処理する
クライアント ドライバーは、アイドル状態の IRP を送信できます (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION を参照)。 要求は、クライアント ドライバーが機能のアイドル状態を検出した後に送信されます。 IRP には、クライアント ドライバーによって実装されるコールバック完了ルーチン (アイドル コールバックと呼ばれます) へのポインターが含まれています。 アイドル コールバック内では、クライアントは、中断状態に機能を送信する直前に、保留中の I/O 転送の取り消しなどのタスクを実行します。
Note
アイドル状態の IRP メカニズムは、USB 3.0 デバイスのクライアント ドライバーでは省略可能です。 ただし、ほとんどのクライアント ドライバーは、USB 2.0 デバイスと USB 3.0 デバイスの両方をサポートするように作成されています。 USB 2.0 デバイスをサポートするには、複合ドライバーはその IRP に依存して各機能の電源状態を追跡するため、ドライバーはアイドル状態の IRP を送信する必要があります。 すべての機能がアイドル状態の場合、複合ドライバーはデバイス全体を中断状態に送ります。
クライアント ドライバーからアイドル状態の IRP を受信すると、複合ドライバーは、クライアント ドライバーが状態を中断する機能を送信する可能性があることをクライアント ドライバーに通知するアイドル コールバックをすぐに呼び出す必要があります。
手順 3: リモート ウェイクアップ通知の要求を送信する
クライアント ドライバーは、IRP_MN_WAIT_WAKE (待機ウェイク IRP) に設定されたマイナー機能コードを持つ IRP_MJ_POWER IRP を送信することによって、リモート ウェイクアップの機能を準備ムする要求を送信できます。 クライアント ドライバーは、ドライバーがユーザー イベントの結果として動作状態を入力する場合にのみ、この要求を送信します。
待機ウェイク IRP を受信すると、複合ドライバーは USB ドライバー スタックに IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION I/O 制御要求を送信する必要があります。 要求により、USB ドライバー スタックは、スタックが再開信号に関する通知を受信したときに、複合ドライバーに通知します。 IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION では、REQUEST_REMOTE_WAKE_NOTIFICATION 構造体を使用して要求パラメーターを指定します。 複合ドライバーで指定する必要がある値の 1 つは、リモート ウェイクアップ用に武装している機能の機能ハンドルです。 複合ドライバーは、USB ドライバー スタックに複合デバイスを登録する前の要求でそのハンドルを取得します。 複合ドライバー登録要求の詳細については、「複合デバイスの登録方法」を参照してください。
要求の IRP では、複合ドライバーは、複合ドライバーによって実装される (リモート ウェイクアップ) 完了ルーチンへのポインターを提供します。
次のコード例は、リモート ウェイクアップ要求を送信する方法を示しています。
/*++
Description:
This routine sends a IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION request
to the USB driver stack. The IOCTL is completed by the USB driver stack
when the function wakes up from sleep.
Parameters:
parentFdoExt: The device context associated with the FDO for the
composite driver.
functionPdoExt: The device context associated with the PDO (created by
the composite driver) for the client driver.
--*/
VOID
SendRequestForRemoteWakeNotification(
__inout PPARENT_FDO_EXT parentFdoExt,
__inout PFUNCTION_PDO_EXT functionPdoExt
)
{
PIRP irp;
REQUEST_REMOTE_WAKE_NOTIFICATION remoteWake;
PIO_STACK_LOCATION nextStack;
NTSTATUS status;
// Allocate an IRP
irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);
if (irp)
{
//Initialize the USBDEVICE_REMOTE_WAKE_NOTIFICATION structure
remoteWake.Version = 0;
remoteWake.Size = sizeof(REQUEST_REMOTE_WAKE_NOTIFICATION);
remoteWake.UsbdFunctionHandle = functionPdoExt->functionHandle;
remoteWake.Interface = functionPdoExt->baseInterfaceNumber;
nextStack = IoGetNextIrpStackLocation(irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION;
nextStack->Parameters.Others.Argument1 = &remoteWake;
// Caller's completion routine will free the IRP when it completes.
SetCompletionRoutine(functionPdoExt->debugLog,
parentFdoExt->fdo,
irp,
CompletionRemoteWakeNotication,
(PVOID)functionPdoExt,
TRUE, TRUE, TRUE);
// Pass the IRP
IoCallDriver(parentFdoExt->topDevObj, irp);
}
return;
}
IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION 要求は、再開信号に関する通知を受け取ったときに、ウェイクアップ プロセス中に USB ドライバー スタックによって完了します。 その間、USB ドライバー スタックはリモート ウェイクアップ完了ルーチンも呼び出します。
複合ドライバーは、待機ウェイク IRP の保留中を維持し、後で処理するためにキューに配置する必要があります。 複合ドライバーは、ドライバーのリモート ウェイク アップ完了ルーチンが USB ドライバー スタックによって呼び出されたときに、その IRP を完了する必要があります。
手順 4: リモート ウェイクアップ用の機能をアームする要求を送信する
この機能を低電力状態に送信するために、クライアント ドライバーは、Windows ドライバー モデル (WDM) デバイスの電源状態を D2 または D3 に変更する要求を含む IRP_MN_SET_POWER IRP を送信します。 通常、クライアント ドライバーは、ドライバーがリモート ウェイク アップを要求する待機ウェイク IRP を送信した場合、D2 IRP を送信します。 それ以外の場合、クライアント ドライバーは D3 IRP を送信します。
D2 IRP を受信すると、複合ドライバーは、最初に待機ウェイク IRP がクライアント ドライバーによって送信された以前の要求から保留中かどうかを判断する必要があります。 その IRP が保留中の場合、複合ドライバーは、リモート ウェイク アップの機能を準備する必要があります。 これを行うには、複合ドライバーは SET_FEATURE 制御要求を機能の最初のインターフェイスに送信し、デバイスが再開信号を送信できるようにする必要があります。 制御要求を送信するには、USBD_UrbAllocate ルーチンを呼び出して URB 構造体を割り当て、UsbBuildFeatureRequest マクロを呼び出して、SET_FEATURE 要求の URB を書式設定します。 呼び出しでは、オペレーション コードとして URB_FUNCTION_SET_FEATURE_TO_INTERFACE を指定し、機能セレクターとして USB_FEATURE_FUNCTION_SUSPEND を指定します。 Index パラメーターで、最上位バイトのビット 1 を設定します。 この値は、転送のセットアップ パケットの wIndex フィールドにコピーされます。
次の例は、SET_FEATURE 制御要求を送信する方法を示しています。
/*++
Routine Description:
Sends a SET_FEATURE for REMOTE_WAKEUP to the device using a standard control request.
Parameters:
parentFdoExt: The device context associated with the FDO for the
composite driver.
functionPdoExt: The device context associated with the PDO (created by
the composite driver) for the client driver.
Returns:
NTSTATUS code.
--*/
VOID
NTSTATUS SendSetFeatureControlRequestToSuspend(
__inout PPARENT_FDO_EXT parentFdoExt,
__inout PFUNCTION_PDO_EXT functionPdoExt,
)
{
PURB urb
PIRP irp;
PIO_STACK_LOCATION nextStack;
NTSTATUS status;
status = USBD_UrbAllocate(parentFdoExt->usbdHandle, &urb);
if (!NT_SUCCESS(status))
{
//USBD_UrbAllocate failed.
goto Exit;
}
//Format the URB structure.
UsbBuildFeatureRequest (
urb,
URB_FUNCTION_SET_FEATURE_TO_INTERFACE, // Operation code
USB_FEATURE_FUNCTION_SUSPEND, // feature selector
functionPdoExt->firstInterface, // first interface of the function
NULL);
irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);
if (!irp)
{
// IoAllocateIrp failed.
status = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
nextStack = IoGetNextIrpStackLocation(irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
// Attach the URB to the IRP.
USBD_AssignUrbToIoStackLocation(nextStack, (PURB)urb);
// Caller's completion routine will free the IRP when it completes.
SetCompletionRoutine(functionPdoExt->debugLog,
parentFdoExt->fdo,
irp,
CompletionForSuspendControlRequest,
(PVOID)functionPdoExt,
TRUE, TRUE, TRUE);
// Pass the IRP
IoCallDriver(parentFdoExt->topDevObj, irp);
Exit:
if (urb)
{
USBD_UrbFree( parentFdoExt->usbdHandle, urb);
}
return status;
}
複合ドライバーは、USB ドライバー スタックに D2 IRP を送信します。 他のすべての機能が中断状態にある場合、USB ドライバー スタックはコントローラー上の特定のポート レジスタを操作してポートを中断します。
解説
マウス機能の例では、リモート ウェイクアップ機能が有効になっているため (ステップ 4 を参照)、ユーザーがマウスを小刻みに動かすと、マウス機能はホスト コントローラーへの上流のワイヤ上に再開信号を生成します。 次に、コントローラーは、起動した機能に関する情報を含む通知パケットを送信することにより、USB ドライバー スタックに通知します。 機能起動通知の詳細については、USB 3.0 仕様の図 8-17 を参照してください。
通知パケットを受信すると、USB ドライバー スタックは保留中の IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION 要求を完了し (手順 3 を参照)、要求で指定され、複合ドライバーによって実装された (リモート ウェイクアップ) 完了コールバック ルーチンを呼び出します。 通知が複合ドライバーに到達すると、クライアント ドライバーが以前に送信した wait-wake IRP が完了することにより、機能が動作状態に入ったことを、対応するクライアント ドライバーに通知します。
(リモート ウェイクアップ) 完了ルーチンでは、複合ドライバーは、保留中のウェイク待機 IRP を完了する作業項目をキューに配置する必要があります。 USB 3.0 デバイスの場合、複合ドライバーは再開信号を送信する機能のみをウェイクアップし、他の機能は中断状態のままにします。 作業項目をキューに入れることで、USB 2.0 デバイスの機能ドライバーの既存の実装との互換性が確保されます。 作業項目のキューの詳細については、「IoQueueWorkItem」を参照してください。
ワーカー スレッドは、待機ウェイク IRP を完了し、クライアント ドライバーの完了ルーチンを呼び出します。 その後、完了ルーチンは D0 IRP を送信し、機能が動作状態に入ります。 待機ウェイク IRP を完了する前に、複合ドライバーは PoSetSystemWake を呼び出して、待機ウェイク IRP を中断状態からシステムをウェイクアップする原因になったものとしてマークする必要があります。 電源マネージャーは、システムをウェイクアップしたデバイスに関する情報を含む Windows イベント トレーシング (ETW) イベント (グローバル システム チャネルで表示可能) をログに記録します。