USB クライアント ドライバー コード構造について (KMDF)

このトピックでは、UMDF ベースの USB クライアント ドライバーのソース コードについて説明します。 コード例は、Microsoft Visual Studio 2019 に含まれている USB ユーザー モード ドライバー テンプレートによって生成されます。

これらのセクションでは、テンプレート コードに関する情報を提供します。

KMDF テンプレート コードの生成手順については、「最初の USB クライアント ドライバー (KMDF) を記述する方法」を参照してください。

ドライバー ソース コード

ドライバー オブジェクトは、Windows がドライバーをメモリに読み込んだ後のクライアント ドライバーのインスタンスを表します。 ドライバー オブジェクトの完全なソース コードは、Driver.h と Driver.c にあります。

Driver.h

テンプレート コードの詳細について説明する前に、UMDF ドライバー開発に関連するヘッダー ファイル (Driver.h) 内のいくつかの宣言を見てみましょう。

Driver.h には、Windows Driver Kit (WDK) に含まれる次のファイルが含まれています。

#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>

#include "device.h"
#include "queue.h"
#include "trace.h"

Ntddk.h および Wdf.h ヘッダー ファイルは、KMDF ドライバー開発向けに必ず含まれています。 ヘッダー ファイルには、UMDF ドライバーをコンパイルするために必要なメソッドと構造体のさまざまな宣言と定義が含まれています。

Usb.h と Usbdlib.h には、USB デバイスのクライアント ドライバーに必要な構造とルーチンの宣言と定義が含まれています。

Wdfusb.h には、フレームワークによって提供される USB I/O ターゲット オブジェクトと通信するために必要な構造とメソッドの宣言と定義が含まれています。

Device.h、Queue.h、Trace.h は WDK には含まれていません。 これらのヘッダー ファイルはテンプレートによって生成されます。これらのファイルについては、このトピックの後半で説明します。

Driver.h の次のブロックは、DriverEntry ルーチン、EvtDriverDeviceAdd イベント コールバック ルーチン、EvtCleanupCallback イベント コールバック ルーチンの関数ロール型宣言を提供します。 これらのルーチンはすべて、ドライバーによって実装されます。 ロール型は、静的ドライバー検証ツール (SDV) がドライバーのソース コードを分析するのに役立ちます。 ロールの種類について詳しくは、「KMDF ドライバーの関数ロール型を使用した関数の宣言」をご覧ください。

DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;

実装ファイル Driver.c には、DriverEntry 関数とイベント コールバック ルーチンがページング可能メモリ内にあるかどうかを指定するために alloc_text プラグマを使用する次のコード ブロックが含まれています。

#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif

DriverEntry が INIT としてマークされているのに対し、イベント コールバック ルーチンは PAGE としてマークされていることに注意してください。 INIT セクションは、DriverEntry の実行可能コードがページング可能であり、ドライバーが DriverEntry から戻るとすぐに破棄されることを示しています。 PAGE セクションは、コードが常に物理メモリ内に残る必要がないことを示します。使用されていないときは、ページ ファイルに書き込むことができます。 詳しくは、「ページング可能コードまたはデータのロック」をご覧ください。

ドライバーが読み込まれた直後、Windows はドライバーを表す DRIVER_OBJECT 構造を割り当てます。 次に、ドライバーのエントリ ポイント ルーチン DriverEntry を呼び出し、構造へのポインターを渡します。 Windows は名前でルーチンを検索するため、すべてのドライバーは DriverEntry という名前のルーチンを実装する必要があります。 このルーチンは、ドライバーの初期化タスクを実行し、フレームワークに対するドライバーのイベント コールバック ルーチンを指定します。

次のコード例は、テンプレートによって生成される DriverEntry ルーチンを示しています。

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
    WDF_DRIVER_CONFIG config;
    NTSTATUS status;
    WDF_OBJECT_ATTRIBUTES attributes;

    //
    // Initialize WPP Tracing
    //
    WPP_INIT_TRACING( DriverObject, RegistryPath );

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Register a cleanup callback so that we can call WPP_CLEANUP when
    // the framework driver object is deleted during driver unload.
    //
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.EvtCleanupCallback = MyUSBDriver_EvtDriverContextCleanup;

    WDF_DRIVER_CONFIG_INIT(&config,
                           MyUSBDriver_EvtDeviceAdd
                           );

    status = WdfDriverCreate(DriverObject,
                             RegistryPath,
                             &attributes,
                             &config,
                             WDF_NO_HANDLE
                             );

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
        WPP_CLEANUP(DriverObject);
        return status;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

DriverEntry ルーチンには、Windows によって割り当てられる DRIVER_OBJECT 構造へのポインターと、ドライバーのレジストリ パスという 2 つのパラメーターがあります。 RegistryPath パラメーターは、レジストリ内のドライバー固有のパスを表しています。

DriverEntry ルーチンでは、ドライバーは以下のタスクを実行します。

  • ドライバーの有効期間中に必要となるグローバル リソースを割り当てます。 たとえば、テンプレート コードでは、クライアント ドライバーは、WPP_INIT_TRACING マクロを呼び出すことにより、WPP ソフトウェア トレースに必要なリソースを割り当てます。

  • 特定のイベント コールバック ルーチンをフレームワークに登録します。

    イベント コールバックを登録するため、クライアント ドライバーはまず、特定の WDF 構造の EvtDriverXxx ルーチンの実装へのポインターを指定します。 その後、ドライバーは WdfDriverCreate メソッドを呼び出し、それらの構造を提供します (次の手順で説明します)。

  • WdfDriverCreate メソッドを呼び出し、フレームワーク ドライバー オブジェクトへのハンドルを取得します。

    クライアント ドライバーが WdfDriverCreate を呼び出した後、フレームワークは、クライアント ドライバーを表すフレームワーク ドライバー オブジェクトを作成します。 呼び出しが完了すると、クライアント ドライバーは WDFDRIVER ハンドルを受け取り、そのレジストリ パス、バージョン情報などのドライバーに関する情報を取得できます (「WDF ドライバー オブジェクト リファレンス」を参照)。

    フレームワーク ドライバー オブジェクトは、DRIVER_OBJECT で記述されている Windows ドライバー オブジェクトとは異なります。 クライアント ドライバーは、WDFDRIVER ハンドルを使用して WdfGetDriver メソッドを呼び出すことによって、いつでも WindowsDRIVER_OBJECT 構造へのポインターを取得できます。

WdfDriverCreate 呼び出しの後、フレームワークはクライアント ドライバーと連携して Windows と通信します。 フレームワークは、Windows とドライバーの間の抽象化レイヤーとして機能し、複雑なドライバー タスクの大部分を処理します。 クライアント ドライバーは、ドライバーが対象とするイベントのフレームワークに登録します。 特定のイベントが発生すると、Windows はフレームワークに通知します。 ドライバーが特定のイベントのイベント コールバックを登録した場合、フレームワークは登録されたイベント コールバックを呼び出すことによってドライバーに通知します。 こうすることにより、ドライバーには、必要に応じてイベントを処理する機会が与えられます。 ドライバーがイベント コールバックを登録しなかった場合、フレームワークはイベントの既定の処理を続行します。

ドライバーが登録する必要があるイベント コールバックの 1 つは EvtDriverDeviceAdd です。 フレームワークは、フレームワークがデバイス オブジェクトを作成する準備ができたとき、ドライバーの EvtDriverDeviceAdd 実装を呼び出します。 Windows では、デバイス オブジェクトは、クライアント ドライバーが読み込まれる物理デバイスの機能を論理的に表したものです (このトピックで後述します)。

ドライバーが登録できるその他のイベント コールバックは、EvtDriverUnloadEvtCleanupCallbackEvtDestroyCallback です。

テンプレート コードでは、クライアント ドライバーは、EvtDriverDeviceAddEvtCleanupCallback の 2 つのイベントに登録します。 ドライバーは、WDF_DRIVER_CONFIG 構造での EvtDriverDeviceAdd の実装と、WDF_OBJECT_ATTRIBUTES 構造における EvtCleanupCallback イベント コールバックへのポインターを指定します。

Windows が DRIVER_OBJECT 構造を解放してドライバーをアンロードする準備ができたら、フレームワークはドライバーの EvtCleanupCallback 実装を呼び出すことによって、そのイベントをクライアント ドライバーに報告します。 フレームワークは、フレームワーク ドライバー オブジェクトを削除する直前に、そのコールバックを呼び出します。 クライアント ドライバーは、DriverEntry に割り当てられたすべてのグローバル リソースを解放できます。 たとえば、テンプレート コードでは、クライアント ドライバーは DriverEntry でアクティブ化された WPP トレースを停止します。

次のコード例は、クライアント ドライバーの EvtCleanupCallback イベント コールバック実装を示しています。

VOID MyUSBDriver_EvtDriverContextCleanup(
    _In_ WDFDRIVER Driver
    )
{
    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE ();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Stop WPP Tracing
    //
    WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );

}

デバイスが USB ドライバー スタックによって認識されると、バス ドライバーはデバイスの物理デバイス オブジェクト (PDO) を作成し、PDO をデバイス ノードに関連付けます。 デバイス ノードはスタック構造内にあり、PDO は下部にあります。 スタックごとに 1 つの PDO が必要であり、その上にフィルター デバイス オブジェクト (フィルター DOs) とファンクション デバイス オブジェクト (FDO) を含めることができます。 詳細については、「デバイス ノードとデバイス スタック」を参照してください。

この図は、テンプレート ドライバー (MyUSBDriver_.sys) のデバイス スタックを示しています。

device stack for template driver.

"マイ USB デバイス" という名前のデバイス スタックに注目してください。 USB ドライバー スタックは、デバイス スタックの PDO を作成します。 この例では、PDO は USB ドライバー スタックに含まれるドライバーの 1 つである Usbhub3.sys に関連付けられています。 デバイスのファンクション ドライバーとして、クライアント ドライバーは、まずデバイスの FDO を作成し、デバイス スタックの先頭に接続する必要があります。

KMDF ベースのクライアント ドライバーの場合、フレームワークはクライアント ドライバーの代わりにこれらのタスクを実行します。 デバイスの FDO を表すため、フレームワークはフレームワーク デバイス オブジェクトを作成します。 ただし、クライアント ドライバーは、フレームワークが新しいオブジェクトを構成するために使用する特定の初期化パラメーターを指定できます。 この機会は、フレームワークがドライバーの EvtDriverDeviceAdd 実装を呼び出すときに、クライアント ドライバーに提供されます。 オブジェクトが作成され、FDO がデバイス スタックの上部にアタッチされると、フレームワークは、フレームワーク デバイス オブジェクトへの WDFDEVICE ハンドルをクライアント ドライバーに提供します。 このハンドルを使用すると、クライアント ドライバーは、さまざまなデバイス関連の操作を実行できます。

次のコード例は、クライアント ドライバーの EvtDriverDeviceAdd イベント コールバック実装を示しています。

NTSTATUS
MyUSBDriver_EvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = MyUSBDriver_CreateDevice(DeviceInit);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

実行時に、EvtDriverDeviceAdd の実装では、PAGED_CODE マクロを使用して、ページング可能なコードの適切な環境においてルーチンが呼び出されることがチェックされます。 すべての変数を宣言した後、必ずマクロを呼び出してください。生成されるソース ファイルは .c ファイルであり、.cpp ファイルではないため、呼び出さないとコンパイルは失敗します。

クライアント ドライバーの EvtDriverDeviceAdd 実装は、MyUSBDriver_CreateDevice ヘルパー関数を呼び出して、必要なタスクを実行します。

次のコード例は、MyUSBDriver_CreateDevice ヘルパー関数を示しています。 MyUSBDriver_CreateDevice は、Device.c で定義されています。

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
    WDF_OBJECT_ATTRIBUTES   deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    WDFDEVICE device;
    NTSTATUS status;

    PAGED_CODE();

    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    pnpPowerCallbacks.EvtDevicePrepareHardware = MyUSBDriver_EvtDevicePrepareHardware;
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);

    if (NT_SUCCESS(status)) {
        //
        // Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
        // inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
        // device.h header file. This function will do the type checking and return
        // the device context. If you pass a wrong object  handle
        // it will return NULL and assert if run under framework verifier mode.
        //
        deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
        deviceContext->PrivateDeviceData = 0;

        //
        // Create a device interface so that applications can find and talk
        // to us.
        //
        status = WdfDeviceCreateDeviceInterface(
            device,
            &GUID_DEVINTERFACE_MyUSBDriver_,
            NULL // ReferenceString
            );

        if (NT_SUCCESS(status)) {
            //
            // Initialize the I/O Package and any Queues
            //
            status = MyUSBDriver_QueueInitialize(device);
        }
    }

    return status;
}

EvtDriverDeviceAdd には、DriverEntry への以前の呼び出しで作成されたフレームワーク ドライバー オブジェクトへのハンドルと、WDFDEVICE_INIT 構造へのポインターという 2 つのパラメーターがあります。 フレームワークは、WDFDEVICE_INIT 構造を割り当て、ポインターを渡すことにより、クライアント ドライバーが作成するフレームワーク デバイス オブジェクトの初期化パラメーターを構造に設定できるようにします。

EvtDriverDeviceAdd 実装では、クライアント ドライバーは以下のタスクを実行する必要があります。

  • WdfDeviceCreate メソッドを呼び出して、新しいデバイス オブジェクトに対する WDFDEVICE ハンドルを取得します。

    WdfDeviceCreate メソッドを使用すると、フレームワークは FDO のフレームワーク デバイス オブジェクトを作成し、デバイス スタックの先頭にアタッチします。 WdfDeviceCreate 呼び出しでは、クライアント ドライバーは以下のタスクを実行する必要があります。

    • フレームワークで指定された WDFDEVICE_INIT 構造で、クライアント ドライバーのプラグ アンド プレイ (PnP) 電源コールバック ルーチンへのポインターを指定します。 ルーチンは、まず WDF_PNPPOWER_EVENT_CALLBACKS 構造に設定され、次に WdfDeviceInitSetPnpPowerEventCallbacks メソッドを呼び出すことによって WDFDEVICE_INIT に関連付けられます。

    Windows コンポーネント、PnP、電源マネージャーは、PnP 状態 (開始、停止、削除など) と電源状態 (動作中や中断など) の変化に応じて、デバイス関連の要求をドライバーに送信します。 KMDF ベースのドライバーの場合、フレームワークはこれらの要求をインターセプトします。 クライアント ドライバーは、WdfDeviceCreate 呼び出しを使用して、PnP 電源イベント コールバックと呼ばれるコールバック ルーチンをフレームワークに登録することにより、要求に関する通知を受け取ることができます。 Windows コンポーネントが要求を送信すると、フレームワークは要求を処理し、クライアント ドライバーが登録されている場合は対応する PnP 電源イベント コールバックを呼び出します。

    クライアント ドライバーが実装する必要がある PnP 電源イベント コールバック ルーチンの 1 つは、EvtDevicePrepareHardware です。 このイベント コールバックは、PnP マネージャーがデバイスを起動したときに呼び出されます。 EvtDevicePrepareHardware の実装については、次のセクションで説明します。

    デバイス コンテキスト (デバイス拡張機能とも呼ばれます) は、特定のデバイス オブジェクトに関する情報を格納するためのデータ構造 (クライアント ドライバーによって定義されます) です。 クライアント ドライバーは、デバイス コンテキストへのポインターをフレームワークに渡します。 フレームワークは、構造のサイズに基づいてメモリ ブロックを割り当て、そのメモリ位置へのポインターをフレームワーク デバイス オブジェクトに格納します。 クライアント ドライバーは、ポインターを使用し、デバイス コンテキストのメンバーに情報にアクセスして格納できます。 デバイス コンテキストの詳細については、「フレームワーク オブジェクト コンテキスト空間」を参照してください。

    WdfDeviceCreate 呼び出しが完了すると、クライアント ドライバーは、デバイス コンテキストのフレームワークによって割り当てられたメモリ ブロックへのポインターを格納する新しいフレームワーク デバイス オブジェクトへのハンドルを受け取ります。 クライアント ドライバーは、WdfObjectGet_DEVICE_CONTEXT マクロを呼び出すことによって、デバイス コンテキストへのポインターを取得できるようになりました。

  • WdfDeviceCreateDeviceInterface メソッドを呼び出して、クライアント ドライバーのデバイス インターフェイス GUID を登録します。 アプリケーションは、この GUID を使用してドライバーと通信できます。 GUID 定数は、ヘッダー public.h で宣言されます。

  • デバイスへの I/O 転送のキューを設定します。 テンプレート コードでは、キューを設定するためのヘルパー ルーチンである MyUSBDriver_QueueInitialize が定義されます。これについては、「キューのソース コード」セクションで説明します。

デバイス ソース コード

デバイス オブジェクトは、クライアント ドライバーがメモリに読み込まれるデバイスのインスタンスを表します。 デバイス オブジェクトの完全なソース コードは、Device.h と Device.c にあります。

Device.h

Device.h ヘッダー ファイルには public.h が含まれています。public.h には、プロジェクト内のすべてのファイルで使用される一般的な宣言が含まれています。

Device.h の次のブロックは、クライアント ドライバーのデバイス コンテキストを宣言します。

typedef struct _DEVICE_CONTEXT
{
    WDFUSBDEVICE UsbDevice;
    ULONG PrivateDeviceData;  // just a placeholder

} DEVICE_CONTEXT, *PDEVICE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)

DEVICE_CONTEXT 構造は、クライアント ドライバーによって定義され、フレームワーク デバイス オブジェクトに関する情報を格納します。 これは Device.h で宣言され、フレームワークの USB ターゲット デバイス オブジェクトへのハンドル (後述) とプレースホルダーの 2 つのメンバーが含まれています。 この構造は、後の演習で拡張されます。

Device.h には、インライン関数 WdfObjectGet_DEVICE_CONTEXT を生成する WDF_DECLARE_CONTEXT_TYPE マクロも含まれています。 クライアント ドライバーは、その関数を呼び出して、フレームワーク デバイス オブジェクトからメモリ ブロックへのポインターを取得できます。

次のコード行では、USB ターゲット デバイス オブジェクトへの WDFUSBDEVICE ハンドルを取得するヘルパー関数である MyUSBDriver_CreateDevice を宣言します。

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    );

USBCreate は、パラメーターとして WDFDEVICE_INIT 構造へのポインターを受け取ります。 これは、クライアント ドライバーの EvtDriverDeviceAdd 実装を呼び出したときにフレームワークによって渡されたポインターと同じです。 基本的に、MyUSBDriver_CreateDevice は EvtDriverDeviceAdd のタスクを実行します。 EvtDriverDeviceAdd 実装のソース コードについては、前のセクションで説明します。

Device.h の次の行は、EvtDevicePrepareHardware イベント コールバック ルーチンの関数ロール型宣言を宣言します。 イベント コールバックはクライアント ドライバーによって実装され、USB デバイスの構成などのタスクを実行します。

EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;

Device.c

Device.c 実装ファイルには、alloc_text プラグマを使用して EvtDevicePrepareHardware のドライバーの実装がページング可能メモリ内にあることを指定する次のコード ブロックが含まれています。

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif

EvtDevicePrepareHardware の実装では、クライアント ドライバーは USB 固有の初期化タスクを実行します。 これらのタスクには、クライアント ドライバーの登録、USB 固有の I/O ターゲット オブジェクトの初期化、USB 構成の選択などがあります。 次の表は、フレームワークによって提供される特殊な I/O ターゲット オブジェクトを示しています。 詳しくは、「USB I/O ターゲット」をご覧ください。

USB I/O ターゲット オブジェクト (ハンドル) ...を呼び出してハンドルを取得 説明
USB ターゲット デバイス オブジェクト (WDFUSBDEVICE) WdfUsbTargetDeviceCreateWithParameters USB デバイスを表し、デバイス記述子を取得し、デバイスに制御リクエストを送信するためのメソッドを提供します。
USB ターゲット インターフェイス オブジェクト (WDFUSBINTERFACE) WdfUsbTargetDeviceGetInterface 個別のインターフェイスを表し、クライアント ドライバーが代替設定を選択し、設定に関する情報を取得するために呼び出すことができるメソッドを提供します。
USB ターゲット パイプ オブジェクト (WDFUSBPIPE) WdfUsbInterfaceGetConfiguredPipe インターフェイスの現在の代替設定で構成されているエンドポイントの個別のパイプを表します。 USB ドライバー スタックは、選択された構成内の各インターフェイスを選択し、インターフェイス内の各エンドポイントへのコミュニケーション チャネルを設定します。 USB の用語では、その通信チャネルはパイプと呼ばれます。

このコード例は、EvtDevicePrepareHardware の実装を示しています。

NTSTATUS
MyUSBDriver_EvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourceList,
    _In_ WDFCMRESLIST ResourceListTranslated
    )
{
    NTSTATUS status;
    PDEVICE_CONTEXT pDeviceContext;
    WDF_USB_DEVICE_CREATE_CONFIG createParams;
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;

    UNREFERENCED_PARAMETER(ResourceList);
    UNREFERENCED_PARAMETER(ResourceListTranslated);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = STATUS_SUCCESS;
    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);

    if (pDeviceContext->UsbDevice == NULL) {

        //
        // Specifying a client contract version of 602 enables us to query for
        // and use the new capabilities of the USB driver stack for Windows 8.
        // It also implies that we conform to rules mentioned in the documentation
        // documentation for WdfUsbTargetDeviceCreateWithParameters.
        //
        WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createParams,
                                         USBD_CLIENT_CONTRACT_VERSION_602
                                         );

        status = WdfUsbTargetDeviceCreateWithParameters(Device,
                                                    &createParams,
                                                    WDF_NO_OBJECT_ATTRIBUTES,
                                                    &pDeviceContext->UsbDevice
                                                    );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceCreateWithParameters failed 0x%x", status);
            return status;
        }

        //
        // Select the first configuration of the device, using the first alternate
        // setting of each interface
        //
        WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&configParams,
                                                                     0,
                                                                     NULL
                                                                     );
        status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
                                                WDF_NO_OBJECT_ATTRIBUTES,
                                                &configParams
                                                );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceSelectConfig failed 0x%x", status);
            return status;
        }
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

テンプレート コードによって実装されるクライアント ドライバーのタスクの詳細を次に示します。

  1. Windows によって読み込まれた基になる USB ドライバー スタックに自身を登録する準備として、クライアント ドライバーのコントラクト バージョンを指定します。

    Windows では、USB デバイスがアタッチされているホスト コントローラーに応じて、USB 3.0 または USB 2.0 ドライバー スタックを読み込むことができます。 USB 3.0 ドライバー スタックは Windows 8 の新機能であり、ストリーム機能など、USB 3.0 仕様で定義されたいくつかの新機能をサポートしています。 新しいドライバー スタックには、新しい URB ルーチンのセットを通じて使用できる USB 要求ブロック (URB) の追跡と処理の向上など、いくつかの機能強化も実装されています。 これらの機能を使用するクライアント ドライバーや新しいルーチンを呼び出すクライアント ドライバーは、USBD_CLIENT_CONTRACT_VERSION_602 コントラクト バージョンを指定する必要があります。 USBD_CLIENT_CONTRACT_VERSION_602 クライアント ドライバーは、特定の規則セットに従う必要があります。 これらの規則の詳細については、「ベスト プラクティス: URB の使用」を参照してください。

    コントラクト バージョンを指定するには、クライアント ドライバーは、WDF_USB_DEVICE_CREATE_CONFIG_INIT マクロを呼び出すことによって、コントラクト バージョンを持つ WDF_USB_DEVICE_CREATE_CONFIG 構造を初期化する必要があります。

  2. WdfUsbTargetDeviceCreateWithParameters メソッドを呼び出します。 このメソッドには、ドライバーの EvtDriverDeviceAdd 実装で WdfDeviceCreate を呼び出すことによって、クライアント ドライバーが以前に取得したフレームワーク デバイス オブジェクトへのハンドルが必要です。 WdfUsbTargetDeviceCreateWithParameters メソッド:

    • クライアント ドライバーを、基になる USB ドライバー スタックに登録します。
    • フレームワークによって作成された USB ターゲット デバイス オブジェクトに対する WDFUSBDEVICE ハンドルを取得します。 テンプレート コードは、USB ターゲット デバイス オブジェクトへのハンドルをデバイス コンテキストに格納します。 クライアント ドライバーは、そのハンドルを使用することにより、デバイスに関する USB 固有の情報を取得できます。

    次の場合、WdfUsbTargetDeviceCreateWithParameters の代わりに WdfUsbTargetDeviceCreate を呼び出す必要があります。

    • クライアント ドライバーは、WDK の Windows 8 バージョンで使用可能な URB ルーチンの新しいセットを呼び出しません。

      クライアント ドライバーが WdfUsbTargetDeviceCreateWithParameters を呼び出した場合、USB ドライバー スタックは、WdfUsbTargetDeviceCreateUrb または WdfUsbTargetDeviceCreateIsochUrb を呼び出すことによって、すべての URB が割り当てられていることを前提としています。 これらのメソッドによって割り当てられる URB には、高速処理のために USB ドライバー スタックによって使用される不透明な URB コンテキスト ブロックがあります。 クライアント ドライバーが、これらのメソッドによって割り当てられない URB を使用する場合、USB ドライバーはバグチェックを生成します。

      URB 割り当てについて詳しくは、「URB の割り当てと構築」をご覧ください。

    • クライアント ドライバーに、「ベスト プラクティス: URL の使用」で説明されている一連の規則に従う意図がありません。

    そのようなドライバーは、クライアント コントラクトのバージョンを指定する必要がないため、手順 1 をスキップする必要があります。

  3. USB 構成を選択します。

    テンプレート コードでは、クライアント ドライバーは USB デバイスの既定の構成を選択します。 既定の構成には、デバイスの構成 0 と、その構成内の各インターフェイスの代替設定 0 が含まれています。

    既定の構成を選択するため、クライアント ドライバーは、WDF_USB_DEVICE_Standard Edition LECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES 関数を呼び出すことによって、WDF_USB_DEVICE_SELECT_CONFIG_PARAMS 構造を構成します。 この関数は、Type メンバーを WdfUsbTargetDeviceSelectConfigTypeMultiInterface に初期化し、複数のインターフェイスが使用可能な場合、各インターフェイスの代替設定を選択する必要があることを示しています。 呼び出しで既定の構成を選択する必要があるため、クライアント ドライバーは SettingPairs パラメーターに NULL、NumberInterfaces パラメーターに 0 を指定します。 完了すると、WDF_USB_DEVICE_SELECT_CONFIG_PARAMSMultiInterface.NumberOfConfiguredInterfaces メンバーは、代替設定 0 が選択されたインターフェイスの数を示します。 他のメンバーは変更されません。

    クライアント ドライバーが既定の設定以外の代替設定を選択することを望んでいる場合、ドライバーは、WDF_USB_INTERFACE_SETTING_PAIR 構造の配列を作成する必要があります。 配列内の各要素は、デバイス定義のインターフェイス番号と、選択する代替設定のインデックスを指定します。 この情報は、WdfUsbTargetDeviceRetrieveConfigDescriptor メソッドを呼び出すことによって取得できるデバイスの構成およびインターフェイス記述子に格納されます。 クライアント ドライバーは、WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES を呼び出し、WDF_USB_INTERFACE_SETTING_PAIR 配列をフレームワークに渡す必要があります。

キューのソース コード

フレームワーク キュー オブジェクトは、特定のフレームワーク デバイス オブジェクトの I/O キューを表します。 キュー オブジェクトの完全なソース コードは、Queue.h と Queue.c にあります。

Queue.h

フレームワークのキュー オブジェクトによって発生するイベントのイベント コールバック ルーチンを宣言します。

Queue.h の最初のブロックは、キュー コンテキストを宣言します。

typedef struct _QUEUE_CONTEXT {

    ULONG PrivateDeviceData;  // just a placeholder

} QUEUE_CONTEXT, *PQUEUE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)

デバイス コンテキストと同様、キュー コンテキストは、特定のキューに関する情報を格納するためにクライアントによって定義されるデータ構造です。

次のコード行では、フレームワーク キュー オブジェクトを作成して初期化するヘルパー関数である MyUSBDriver_QueueInitialize 関数を宣言します。

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    );

次のコード例では、EvtIoDeviceControl イベント コールバック ルーチンの関数ロール型宣言を宣言します。 イベント コールバックはクライアント ドライバーによって実装され、フレームワークがデバイス I/O 制御要求を処理するときに呼び出されます。

EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;

Queue.c

実装ファイル Queue.c には、alloc_text プラグマを使用して、ドライバーの MyUSBDriver_QueueInitialize の実装がページング可能メモリ内にあることを指定する次のコード ブロックが含まれています。

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif

WDF は、クライアント ドライバーへの要求フローを処理するフレームワーク キュー オブジェクトを提供します。 フレームワークは、クライアント ドライバーが WdfIoQueueCreate メソッドを呼び出すときにフレームワーク キュー オブジェクトを作成します。 この呼び出しでは、フレームワークがキューを作成する前に、クライアント ドライバーにおいて特定の構成オプションを指定できます。 これらのオプションには、キューが電源管理されているか、長さ 0 の要求が許可されているか、ドライバーの既定のキューであるかが含まれています。 単一のフレームワーク キュー オブジェクトは、読み取り、書き込み、デバイス I/O 制御など、いくつかの種類の要求を処理できます。 クライアント ドライバーは、これらの要求ごとにイベント コールバックを指定できます。

クライアント ドライバーでは、ディスパッチの種類も指定する必要があります。 キュー オブジェクトのディスパッチの種類は、フレームワークがクライアント ドライバーに要求を配信する方法を決定します。 配信メカニズムは、順次、並列、またはクライアント ドライバーによって定義されたカスタム メカニズムによって行うことができます。 順次キューの場合、クライアント ドライバーが前の要求を完了するまで、要求は配信されません。 並列ディスパッチ モードでは、フレームワークは I/O マネージャーから要求が到着すると、すぐに要求を転送します。 つまり、クライアント ドライバーは、別の要求の処理中に 1 件の要求を受信できます。 カスタム メカニズムでは、ドライバーが次の要求を処理する準備ができたら、クライアントはフレームワーク キュー オブジェクトから次の要求を手動で引き出します。

通常、クライアント ドライバーは、ドライバーの EvtDriverDeviceAdd イベント コールバックでキューを設定する必要があります。 テンプレート コードは、フレームワーク キュー オブジェクトを初期化するヘルパー ルーチン (MyUSBDriver_QueueInitialize) を提供します。

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    )
{
    WDFQUEUE queue;
    NTSTATUS status;
    WDF_IO_QUEUE_CONFIG    queueConfig;

    PAGED_CODE();
    
    //
    // Configure a default queue so that requests that are not
    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
    // other queues get dispatched here.
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
         &queueConfig,
        WdfIoQueueDispatchParallel
        );

    queueConfig.EvtIoDeviceControl = MyUSBDriver_EvtIoDeviceControl;

    status = WdfIoQueueCreate(
                 Device,
                 &queueConfig,
                 WDF_NO_OBJECT_ATTRIBUTES,
                 &queue
                 );

    if( !NT_SUCCESS(status) ) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
        return status;
    }

    return status;
}

キューを設定するため、クライアント ドライバーは次のタスクを実行します。

  1. WDF_IO_QUEUE_CONFIG 構造内のキューの構成オプションを指定します。 テンプレート コードでは、WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE 関数を使用して構造を初期化します。 この関数は、キュー オブジェクトを既定のキュー オブジェクトとして指定し、電源管理されており、要求を並行して受信します。
  2. キューの I/O 要求に対するクライアント ドライバーのイベント コールバックを追加します。 テンプレートでは、クライアント ドライバーは、デバイス I/O 制御要求のイベント コールバックへのポインターを指定します。
  3. WdfIoQueueCreate を呼び出して、フレームワークによって作成されたフレームワーク キュー オブジェクトに対する WDFQUEUE ハンドルを取得します。

キュー メカニズムのしくみを次に示します。 USB デバイスと通信するため、アプリケーションはまず SetDixxx ルーチンと CreateHandle を呼び出して、デバイスへのハンドルを開きます。 このハンドルを使用すると、アプリケーションは特定のコントロール コードを使用して DeviceIoControl 関数を呼び出します。 制御コードのタイプに応じて、アプリケーションはその呼び出しで入力バッファーと出力バッファーを指定できます。 呼び出しは最終的に I/O マネージャーによって受け取られます。その後、要求 (IRP) が作成され、クライアント ドライバーに転送されます。 フレームワークは要求をインターセプトし、フレームワーク要求オブジェクトを作成し、それをフレームワーク キュー オブジェクトに追加します。 この場合、クライアント ドライバーはデバイス I/O 制御要求のイベント コールバックを登録したため、フレームワークはコールバックを呼び出します。 さらに、キュー オブジェクトが WdfIoQueueDispatchParallel フラグを使用して作成されているため、リクエストがキューに追加されるとすぐにコールバックが呼び出されます。

VOID
MyUSBDriver_EvtIoDeviceControl(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, 
                TRACE_QUEUE, 
                "!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d", 
                Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode);

    WdfRequestComplete(Request, STATUS_SUCCESS);

    return;
}

フレームワークは、クライアント ドライバーのイベント コールバックを呼び出すと、アプリケーションによって送信された要求 (およびその入力バッファーと出力バッファー) を保持するフレームワーク要求オブジェクトにハンドルを渡します。 さらに、要求を含むフレームワーク キュー オブジェクトにハンドルを送信します。 イベント コールバックでは、クライアント ドライバーは必要に応じて要求を処理します。 テンプレート コードは、要求を完了するだけです。 クライアント ドライバーは、より複雑なタスクを実行できます。 たとえば、アプリケーションがイベント コールバックで特定のデバイス情報を要求した場合、クライアント ドライバーは USB 制御要求を作成し、それを USB ドライバー スタックに送信して、要求されたデバイス情報を取得できます。 USB コントロールの要求については、「USB コントロール転送」で説明します。