UDE クライアント ドライバーを作成する

この記事では、USB デバイス エミュレーション (UDE) クラス拡張機能の振る舞いと、エミュレートされたホスト コントローラーおよびそれに接続されているデバイスに対してクライアント ドライバーが実行する必要があるタスクについて説明します。 また、クラス ドライバーとクラス拡張機能が一連のルーチンとコールバック関数を通じてどのようにやり取りするかについての情報を提供します。 さらに、クライアント ドライバーが実装する必要がある機能についても説明します。

まとめ

  • クラス拡張機能とクライアント ドライバーによって使用される UDE オブジェクトとハンドル。
  • コントローラーの機能を照会し、コントローラーをリセットする機能を備えるエミュレートされたホスト コントローラーの作成。
  • 仮想 USB デバイスの作成、電源管理用のセットアップ、エンドポイントを介したデータ転送。

重要な API

開始する前に

  • 最新の Windows Driver Kit (WDK) を開発用コンピューターにインストールします。 このキットには、UDE クライアント ドライバーを作成するために必要なヘッダー ファイルとライブラリが含まれています。具体的には、以下が必要になります。
    • スタブ ライブラリ (Udecxstub.lib)。 このライブラリはクライアント ドライバーによる呼び出しを変換し、UdeCx に渡します。
    • ヘッダー ファイル Udecx.h。
  • ターゲット コンピューターに Windows 10 をインストールします。
  • UDE についてよく理解します。 「アーキテクチャ: USB デバイス エミュレーション (UDE)」を参照してください。
  • Windows Driver Foundation (WDF) についてよく理解します。 推奨資料: Developing Drivers with Windows Driver Foundation Guy Smith、Penny Orwick 共著

UDE オブジェクトおよびハンドル

UDE クラス拡張機能とクライアント ドライバーは、エミュレートされたホスト コントローラーと仮想デバイスを表す特定の WDF オブジェクト (デバイスとホストの間でデータを転送するために使用されるエンドポイントや URB など) を使用します。 クライアント ドライバーはオブジェクトの作成を要求し、オブジェクトの有効期間はクラス拡張機能によって管理されます。

  • エミュレートされたホスト コントローラー オブジェクト (WDFDEVICE)

    エミュレートされたホスト コントローラーを表し、UDE クラス拡張機能とクライアント ドライバー間のメイン ハンドルです。

  • UDE デバイス オブジェクト (UDECXUSBDEVICE)

    エミュレートされたホスト コントローラーのポートに接続されている仮想 USB デバイスを表します。

  • UDE エンドポイント オブジェクト (UDECXUSBENDPOINT)

    USB デバイスのシーケンシャル データ パイプを表します。 エンドポイントでデータを送受信するソフトウェア要求を受信するために使用されます。

エミュレートされたホスト コントローラーを初期化する

ここでは、クライアント ドライバーがエミュレートされたホスト コントローラーの WDFDEVICE ハンドルを取得する手順の概要を示します。 ドライバーがこれらのタスクを EvtDriverDeviceAdd コールバック関数で実行することをお勧めします。

  1. フレームワークから渡された WDFDEVICE_INIT への参照を渡して UdecxInitializeWdfDeviceInit を呼び出します。

  2. このデバイスが他の USB ホスト コントローラーと同様に見えるように、セットアップ情報を使用して WDFDEVICE_INIT 構造体を初期化します。 たとえば、FDO 名とシンボリック リンクを割り当て、Microsoft 提供の GUID_DEVINTERFACE_USB_HOST_CONTROLLER GUID をデバイス インターフェイス GUID として登録して、アプリケーションがデバイスへのハンドルを開くことができるようにします。

  3. WdfDeviceCreate を呼び出して、フレームワーク デバイス オブジェクトを作成します。

  4. UdecxWdfDeviceAddUsbDeviceEmulation を呼び出して、クライアント ドライバーのコールバック関数を登録します。

    ここでは、ホスト コントローラー オブジェクトに関連付けられたコールバック関数を示します。これらの関数は UDE クラス拡張機能によって呼び出されます。 これらの関数はクライアント ドライバーで実装する必要があります。

    • EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY

      ホスト コントローラーによってサポートされる機能のうち、クライアント ドライバーがクラス拡張機能にレポートする必要があるものを指定します。

    • EVT_UDECX_WDF_DEVICE_RESET

      省略可能。 ホスト コントローラーや接続されたデバイスをリセットします。

    
    EVT_WDF_DRIVER_DEVICE_ADD                 Controller_WdfEvtDeviceAdd;
    
    #define BASE_DEVICE_NAME                  L"\\Device\\USBFDO-"
    #define BASE_SYMBOLIC_LINK_NAME           L"\\DosDevices\\HCD"
    
    #define DeviceNameSize                    sizeof(BASE_DEVICE_NAME)+MAX_SUFFIX_SIZE
    #define SymLinkNameSize                   sizeof(BASE_SYMBOLIC_LINK_NAME)+MAX_SUFFIX_SIZE
    
    NTSTATUS
    Controller_WdfEvtDeviceAdd(
        _In_
            WDFDRIVER Driver,
        _Inout_
            PWDFDEVICE_INIT WdfDeviceInit
        )
    {
        NTSTATUS                            status;
        WDFDEVICE                           wdfDevice;
        WDF_PNPPOWER_EVENT_CALLBACKS        wdfPnpPowerCallbacks;
        WDF_OBJECT_ATTRIBUTES               wdfDeviceAttributes;
        WDF_OBJECT_ATTRIBUTES               wdfRequestAttributes;
        UDECX_WDF_DEVICE_CONFIG             controllerConfig;
        WDF_FILEOBJECT_CONFIG               fileConfig;
        PWDFDEVICE_CONTEXT                  pControllerContext;
        WDF_IO_QUEUE_CONFIG                 defaultQueueConfig;
        WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS
                                            idleSettings;
        UNICODE_STRING                      refString;
        ULONG instanceNumber;
        BOOLEAN isCreated;
    
        DECLARE_UNICODE_STRING_SIZE(uniDeviceName, DeviceNameSize);
        DECLARE_UNICODE_STRING_SIZE(uniSymLinkName, SymLinkNameSize);
    
        UNREFERENCED_PARAMETER(Driver);
    
        ...
    
        WdfDeviceInitSetPnpPowerEventCallbacks(WdfDeviceInit, &wdfPnpPowerCallbacks);
    
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfRequestAttributes, REQUEST_CONTEXT);
        WdfDeviceInitSetRequestAttributes(WdfDeviceInit, &wdfRequestAttributes);
    
    // To distinguish I/O sent to GUID_DEVINTERFACE_USB_HOST_CONTROLLER, we will enable
    // enable interface reference strings by calling WdfDeviceInitSetFileObjectConfig
    // with FileObjectClass WdfFileObjectWdfXxx.
    
    WDF_FILEOBJECT_CONFIG_INIT(&fileConfig,
                                WDF_NO_EVENT_CALLBACK,
                                WDF_NO_EVENT_CALLBACK,
                                WDF_NO_EVENT_CALLBACK // No cleanup callback function
                                );
    
    ...
    
    WdfDeviceInitSetFileObjectConfig(WdfDeviceInit,
                                        &fileConfig,
                                        WDF_NO_OBJECT_ATTRIBUTES);
    
    ...
    
    // Do additional setup required for USB controllers.
    
    status = UdecxInitializeWdfDeviceInit(WdfDeviceInit);
    
    ...
    
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfDeviceAttributes, WDFDEVICE_CONTEXT);
    wdfDeviceAttributes.EvtCleanupCallback = _ControllerWdfEvtCleanupCallback;
    
    // Call WdfDeviceCreate with a few extra compatibility steps to ensure this device looks
    // exactly like other USB host controllers.
    
    isCreated = FALSE;
    
    for (instanceNumber = 0; instanceNumber < ULONG_MAX; instanceNumber++) {
    
        status = RtlUnicodeStringPrintf(&uniDeviceName,
                                        L"%ws%d",
                                        BASE_DEVICE_NAME,
                                        instanceNumber);
    
        ...
    
        status = WdfDeviceInitAssignName(*WdfDeviceInit, &uniDeviceName);
    
        ...
    
        status = WdfDeviceCreate(WdfDeviceInit, WdfDeviceAttributes, WdfDevice);
    
        if (status == STATUS_OBJECT_NAME_COLLISION) {
    
            // This is expected to happen at least once when another USB host controller
            // already exists on the system.
    
        ...
    
        } else if (!NT_SUCCESS(status)) {
    
        ...
    
        } else {
    
            isCreated = TRUE;
            break;
        }
    }
    
    if (!isCreated) {
    
        ...
    }
    
    // Create the symbolic link (also for compatibility).
    status = RtlUnicodeStringPrintf(&uniSymLinkName,
                                    L"%ws%d",
                                    BASE_SYMBOLIC_LINK_NAME,
                                    instanceNumber);
    ...
    
    status = WdfDeviceCreateSymbolicLink(*WdfDevice, &uniSymLinkName);
    
    ...
    
    // Create the device interface.
    
    RtlInitUnicodeString(&refString,
                         USB_HOST_DEVINTERFACE_REF_STRING);
    
    status = WdfDeviceCreateDeviceInterface(wdfDevice,
                                            (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER,
                                            &refString);
    
    ...
    
    UDECX_WDF_DEVICE_CONFIG_INIT(&controllerConfig, Controller_EvtUdecxWdfDeviceQueryUsbCapability);
    
    status = UdecxWdfDeviceAddUsbDeviceEmulation(wdfDevice,
                                               &controllerConfig);
    
    // Create default queue. It only supports USB controller IOCTLs. (USB I/O will come through
    // in separate USB device queues.)
    // Shown later in this topic.
    
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&defaultQueueConfig, WdfIoQueueDispatchSequential);
    defaultQueueConfig.EvtIoDeviceControl = ControllerEvtIoDeviceControl;
    defaultQueueConfig.PowerManaged = WdfFalse;
    
    status = WdfIoQueueCreate(wdfDevice,
                              &defaultQueueConfig,
                              WDF_NO_OBJECT_ATTRIBUTES,
                              &pControllerContext->DefaultQueue);
    
    ...
    
    // Initialize virtual USB device software objects.
    // Shown later in this topic.
    
    status = Usb_Initialize(wdfDevice);
    
    ...
    
    exit:
    
        return status;
    }
    ```1.
    
    

ホスト コントローラーに送信されたユーザー モード IOCTL 要求を処理する

初期化中、UDE クライアント ドライバーは GUID_DEVINTERFACE_USB_HOST_CONTROLLER デバイス インターフェイス GUID を公開します。 これにより、ドライバーはその GUID を使用して、デバイス ハンドルを開くアプリケーションから IOCTL 要求を受信できるようになります。 IOCTL 制御コードの一覧については、「デバイス インターフェイス GUID が GUID_DEVINTERFACE_USB_HOST_CONTROLLER の USB IOCTL」を参照してください。

これらの要求を処理するために、クライアント ドライバーは EvtIoDeviceControl イベント コールバックを登録します。 実装では、ドライバーが要求を処理する代わりに、UDE クラス拡張機能に処理のために転送できるようにします。 要求を転送するために、ドライバーは UdecxWdfDeviceTryHandleUserIoctl を呼び出す必要があります。 受信した IOCTL 制御コードがデバイス記述子の取得などの標準要求に対応する場合、クラス拡張機能は正常にその要求を処理して完了します。 この場合、UdecxWdfDeviceTryHandleUserIoctl は戻り値として TRUE を返して完了します。 それ以外の場合、呼び出しは FALSE を返し、ドライバーは要求を完了する方法を決定する必要があります。 最も単純な実装では、ドライバーは WdfRequestComplete を呼び出して、該当する失敗コードで要求を完了できます。


EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL        Controller_EvtIoDeviceControl;

VOID
Controller_EvtIoDeviceControl(
    _In_
        WDFQUEUE Queue,
    _In_
        WDFREQUEST Request,
    _In_
        size_t OutputBufferLength,
    _In_
        size_t InputBufferLength,
    _In_
        ULONG IoControlCode
)
{
    BOOLEAN handled;
    NTSTATUS status;
    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(InputBufferLength);

    handled = UdecxWdfDeviceTryHandleUserIoctl(WdfIoQueueGetDevice(Queue),
                                                Request);

    if (handled) {

        goto exit;
    }

    // Unexpected control code.
    // Fail the request.

    status = STATUS_INVALID_DEVICE_REQUEST;

    WdfRequestComplete(Request, status);

exit:

    return;
}

ホスト コントローラーの機能をレポートする

上位レイヤーのドライバーが USB ホスト コントローラーの機能を使用する前に、ドライバーはそれらの機能がコントローラーでサポートされているかどうかを判断する必要があります。 ドライバーは、WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability を呼び出すことで、このようなクエリを行います。 これらの呼び出しは USB デバイス エミュレーション (UDE) クラス拡張機能に転送されます。 要求を取得すると、クラス拡張機能はクライアント ドライバーの EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY 実装を呼び出します。 この呼び出しは、EvtDriverDeviceAdd が完了した後でのみ、通常は EvtDevicePrepareHardware で行われ、EvtDeviceReleaseHardware の後では行われません。 このコールバック関数は必須です。

実装では、クライアント ドライバーは、要求された機能をサポートしているかどうかをレポートする必要があります。 静的ストリームなどの特定の機能は UDE ではサポートされていません。

NTSTATUS
Controller_EvtControllerQueryUsbCapability(
    WDFDEVICE     UdeWdfDevice,
    PGUID         CapabilityType,
    ULONG         OutputBufferLength,
    PVOID         OutputBuffer,
    PULONG        ResultLength
)

{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(UdeWdfDevice);
    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(OutputBuffer);

    *ResultLength = 0;

    if (RtlCompareMemory(CapabilityType,
                         &GUID_USB_CAPABILITY_CHAINED_MDLS,
                         sizeof(GUID)) == sizeof(GUID)) {

        //
        // TODO: Is GUID_USB_CAPABILITY_CHAINED_MDLS supported?
        // If supported, status = STATUS_SUCCESS
        // Otherwise, status = STATUS_NOT_SUPPORTED
    }

    else {

        status = STATUS_NOT_IMPLEMENTED;
    }

    return status;
}

仮想 USB デバイスを作成する

仮想 USB デバイスは USB デバイスと同様に振る舞います。 複数のインターフェイスを備える構成に対応し、各インターフェイスは代替設定をサポートします。 各設定には、データ転送に使用されるエンドポイントを 1 つ以上含めることができます。 すべての記述子 (デバイス、構成、インターフェイス、エンドポイント) は、デバイスが実際の USB デバイスと同様に情報をレポートできるように、UDE クライアント ドライバーによって設定されます。

Note

UDE クライアント ドライバーは外付けハブをサポートしていません。

ここでは、クライアント ドライバーが UDE デバイス オブジェクトの UDECXUSBDEVICE ハンドルを作成する手順の概要を示します。 ドライバーは、エミュレートされたホスト コントローラーの WDFDEVICE ハンドルを取得した後、これらの手順を実行する必要があります。 ドライバーがこれらのタスクを EvtDriverDeviceAdd コールバック関数で実行することをお勧めします。

  1. UdecxUsbDeviceInitAllocate を呼び出して、デバイスの作成に必要な初期化パラメーターへのポインターを取得します。 この構造体は UDE クラス拡張機能によって割り当てられます。

  2. UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS のメンバーを設定し、UdecxUsbDeviceInitSetStateChangeCallbacks を呼び出して、イベント コールバック関数を登録します。 ここでは、UDE デバイス オブジェクトに関連付けられたコールバック関数を示します。これらの関数は UDE クラス拡張機能によって呼び出されます。

    また、エンドポイントを作成または構成するためにクライアント ドライバーによって実装されます。

  3. UdecxUsbDeviceInitSetSpeed を呼び出して、USB デバイスの速度とデバイスのタイプ (USB 2.0 または SuperSpeed デバイス) を設定します。

  4. UdecxUsbDeviceInitSetEndpointsType を呼び出して、デバイスがサポートするエンドポイントのタイプ (単純または動的) を指定します。 クライアント ドライバーが単純なエンドポイントの作成を選択した場合、ドライバーはデバイスを接続する前にすべてのエンドポイント オブジェクトを作成する必要があります。 デバイスには、インターフェイスごとに 1 つの構成と 1 つのインターフェイス設定のみが必要です。 動的エンドポイントの場合、ドライバーはデバイスを接続した後、EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE イベント コールバックの受信時に、任意のタイミングでエンドポイントを作成できます。 「動的エンドポイントを作成する」を参照してください。

  5. 次のメソッドのいずれかを呼び出して、必要な記述子をデバイスに追加します。

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • UdecxUsbDeviceInitAddStringDescriptorRaw

      クライアント ドライバーが初期化時に上記のメソッドのいずれかを使用して渡した標準記述子に対する要求を UDE クラス拡張機能が受け取った場合、その拡張機能はその要求を自動的に完了します。 その要求をクライアント ドライバーに転送しません。 この設計により、ドライバーが制御要求のために処理する必要のある要求の数が減ります。 さらに、セットアップ パケットの広範な解析と、wLength および TransferBufferLength の正しい処理を必要とする記述子ロジックを、ドライバーに実装する必要もなくなります。 次の一覧には標準要求が含まれています。 クライアント ドライバーはこれらの要求をチェックする必要はありません (記述子を追加するために前述のメソッドが呼び出された場合のみ)。

    • USB_REQUEST_GET_DESCRIPTOR

    • USB_REQUEST_SET_CONFIGURATION

    • USB_REQUEST_SET_INTERFACE

    • USB_REQUEST_SET_ADDRESS

    • USB_REQUEST_SET_FEATURE

    • USB_FEATURE_FUNCTION_SUSPEND

    • USB_FEATURE_REMOTE_WAKEUP

    • USB_REQUEST_CLEAR_FEATURE

    • USB_FEATURE_ENDPOINT_STALL

    • USB_REQUEST_SET_SEL

    • USB_REQUEST_ISOCH_DELAY

      ただし、インターフェイス、クラス固有、またはベンダー定義の記述子に対する要求は、UDE クラス拡張機能によってクライアント ドライバーに転送されます。 ドライバーはこれらの GET_DESCRIPTOR 要求を処理する必要があります。

  6. UdecxUsbDeviceCreate を呼び出して UDE デバイス オブジェクトを作成し、UDECXUSBDEVICE ハンドルを取得します。

  7. UdecxUsbEndpointCreate を呼び出して静的エンドポイントを作成します。 「単純なエンドポイントを作成する」を参照してください。

  8. UdecxUsbDevicePlugIn を呼び出して、デバイスが接続されておりエンドポイントで I/O 要求を受信できることを、UDE クラス拡張機能に示します。 この呼び出しの後、クラス拡張機能はエンドポイントおよび USB デバイスに関連付けられたコールバック関数を呼び出すこともできます。 注意 実行時に USB デバイスを取り外す必要がある場合、クライアント ドライバーは UdecxUsbDevicePlugOutAndDelete を呼び出すことができます。 ドライバーがデバイスを使用する場合は、UdecxUsbDeviceCreate を呼び出してデバイスを作成する必要があります。

この例では、記述子の宣言はグローバル変数であると想定しており、HID デバイスに対して次のように宣言しています。

const UCHAR g_UsbDeviceDescriptor[] = {
    // Device Descriptor
    0x12, // Descriptor Size
    0x01, // Device Descriptor Type
    0x00, 0x03, // USB 3.0
    0x00, // Device class
    0x00, // Device sub-class
    0x00, // Device protocol
    0x09, // Maxpacket size for EP0 : 2^9
    0x5E, 0x04, // Vendor ID
    0x39, 0x00, // Product ID
    0x00, // LSB of firmware version
    0x03, // MSB of firmware version
    0x01, // Manufacture string index
    0x03, // Product string index
    0x00, // Serial number string index
    0x01 // Number of configurations
};

ここでは、クライアント ドライバーがコールバック関数を登録し、デバイス速度を設定し、エンドポイントのタイプを指定し、最後にいくつかのデバイス記述子を設定することで初期化パラメーターを指定する例を示します。


NTSTATUS
Usb_Initialize(
    _In_
        WDFDEVICE WdfDevice
    )
{
    NTSTATUS                                status;
    PUSB_CONTEXT                            usbContext;    //Client driver declared context for the host controller object
    PUDECX_USBDEVICE_CONTEXT                deviceContext; //Client driver declared context for the UDE device object
    UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
    WDF_OBJECT_ATTRIBUTES                   attributes;

    UDECX_USB_DEVICE_PLUG_IN_OPTIONS        pluginOptions;

    usbContext = WdfDeviceGetUsbContext(WdfDevice);

    usbContext->UdecxUsbDeviceInit = UdecxUsbDeviceInitAllocate(WdfDevice);

    if (usbContext->UdecxUsbDeviceInit == NULL) {

        ...
        goto exit;
    }

    // State changed callbacks

    UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
#ifndef SIMPLEENDPOINTS
    callbacks.EvtUsbDeviceDefaultEndpointAdd = UsbDevice_EvtUsbDeviceDefaultEndpointAdd;
    callbacks.EvtUsbDeviceEndpointAdd = UsbDevice_EvtUsbDeviceEndpointAdd;
    callbacks.EvtUsbDeviceEndpointsConfigure = UsbDevice_EvtUsbDeviceEndpointsConfigure;
#endif
    callbacks.EvtUsbDeviceLinkPowerEntry = UsbDevice_EvtUsbDeviceLinkPowerEntry;
    callbacks.EvtUsbDeviceLinkPowerExit = UsbDevice_EvtUsbDeviceLinkPowerExit;
    callbacks.EvtUsbDeviceSetFunctionSuspendAndWake = UsbDevice_EvtUsbDeviceSetFunctionSuspendAndWake;

    UdecxUsbDeviceInitSetStateChangeCallbacks(usbContext->UdecxUsbDeviceInit, &callbacks);

    // Set required attributes.

    UdecxUsbDeviceInitSetSpeed(usbContext->UdecxUsbDeviceInit, UdecxUsbLowSpeed);

#ifdef SIMPLEENDPOINTS
    UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeSimple);
#else
    UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeDynamic);
#endif

    // Add device descriptor
    //
    status = UdecxUsbDeviceInitAddDescriptor(usbContext->UdecxUsbDeviceInit,
                                           (PUCHAR)g_UsbDeviceDescriptor,
                                           sizeof(g_UsbDeviceDescriptor));

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#ifdef USB30

    // Add BOS descriptor for a SuperSpeed device

    status = UdecxUsbDeviceInitAddDescriptor(pUsbContext->UdecxUsbDeviceInit,
                                           (PUCHAR)g_UsbBOSDescriptor,
                                           sizeof(g_UsbBOSDescriptor));

    if (!NT_SUCCESS(status)) {

        goto exit;
    }
#endif

    // String descriptors

    status = UdecxUsbDeviceInitAddDescriptorWithIndex(usbContext->UdecxUsbDeviceInit,
                                                    (PUCHAR)g_LanguageDescriptor,
                                                    sizeof(g_LanguageDescriptor),
                                                    0);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    status = UdecxUsbDeviceInitAddStringDescriptor(usbContext->UdecxUsbDeviceInit,
                                                 &g_ManufacturerStringEnUs,
                                                 g_ManufacturerIndex,
                                                 US_ENGLISH);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, UDECX_USBDEVICE_CONTEXT);

    status = UdecxUsbDeviceCreate(&usbContext->UdecxUsbDeviceInit,
                                &attributes,
                                &usbContext->UdecxUsbDevice);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#ifdef SIMPLEENDPOINTS
   // Create the default control endpoint
   // Shown later in this topic.

    status = UsbCreateControlEndpoint(WdfDevice);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#endif

    UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&pluginOptions);
#ifdef USB30
    pluginOptions.Usb30PortNumber = 2;
#else
    pluginOptions.Usb20PortNumber = 1;
#endif
    status = UdecxUsbDevicePlugIn(usbContext->UdecxUsbDevice, &pluginOptions);

exit:

    if (!NT_SUCCESS(status)) {

        UdecxUsbDeviceInitFree(usbContext->UdecxUsbDeviceInit);
        usbContext->UdecxUsbDeviceInit = NULL;

    }

    return status;
}

USB デバイスの電源管理

UDE クラス拡張は、デバイスを低電力状態に移行したり動作状態に復帰させたりする要求を受け取ると、クライアント ドライバーのコールバック関数を呼び出します。 これらのコールバック関数は、ウェイクをサポートする USB デバイスに必要です。 クライアント ドライバーは UdecxUsbDeviceInitSetStateChangeCallbacks の前回の呼び出し時に、その実装を登録しました。

詳細については、「USB デバイスの電力状態」を参照してください。

USB 3.0 デバイスでは個々の機能を低電力状態に移行できます。 各機能はウェイク信号を送信することもできます。 UDE クラス拡張機能は、EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE を呼び出してクライアント ドライバーに通知します。 このイベントは、機能の電力状態の変更を示し、機能が新しい状態から復帰できるかどうかをクライアント ドライバーに通知します。 この関数内で、クラス拡張機能はウェイクアップする機能のインターフェイス番号を渡します。

クライアント ドライバーは、仮想 USB デバイスの低リンク電力状態、機能のサスペンド、またはその両方からの復帰アクションをシミュレートできます。 USB 2.0 デバイスの場合、ドライバーが直近の EVT_UDECX_USB_DEVICE_D0_EXIT でデバイスのウェイクアップを有効にしていれば、ドライバーは UdecxUsbDeviceSignalWake を呼び出す必要があります。 USB 3.0 デバイスの場合、ドライバーは UdecxUsbDeviceSignalFunctionWake を呼び出す必要があります。なぜなら、USB 3.0 のウェイク機能は機能単位であるためです。 デバイス全体が低電力状態にある場合、またはそのような状態に移行しようとしている場合、UdecxUsbDeviceSignalFunctionWake はデバイスをウェイクアップします。

単純なエンドポイントを作成する

クライアント ドライバーは、USB デバイスとの間のデータ転送を処理する UDE エンドポイント オブジェクトを作成します。 ドライバーは、UDE デバイスを作成した後、デバイスが接続されたことをレポートする前に、単純なエンドポイントを作成します。

ここでは、クライアント ドライバーが UDE エンドポイント オブジェクトの UDECXUSBENDPOINT ハンドルを作成する手順の概要を示します。 ドライバーは、仮想 USB デバイスの UDECXUSBDEVICE ハンドルを取得した後、これらの手順を実行する必要があります。 ドライバーがこれらのタスクを EvtDriverDeviceAdd コールバック関数で実行することをお勧めします。

  1. UdecxUsbSimpleEndpointInitAllocate を呼び出して、クラス拡張機能によって割り当てられた初期化パラメーターへのポインターを取得します。

  2. UdecxUsbEndpointInitSetEndpointAddress を呼び出して、初期化パラメーターにエンドポイント アドレスを設定します。

  3. UdecxUsbEndpointInitSetCallbacks を呼び出して、クライアント ドライバーに実装されたコールバック関数を登録します。

    これらの関数は、エンドポイント上のキューと要求を処理するためにクライアント ドライバーによって実装されます。

  4. UdecxUsbEndpointCreate を呼び出してエンドポイント オブジェクトを作成し、UDECXUSBENDPOINT ハンドルを取得します。

  5. UdecxUsbEndpointSetWdfIoQueue を呼び出して、フレームワーク キュー オブジェクトをエンドポイントに関連付けます。 必要に応じて、適切な属性を設定することで、エンドポイント オブジェクトをキューの WDF 親オブジェクトに設定できます。

    すべてのエンドポイント オブジェクトには、転送要求を処理するためのフレームワーク キュー オブジェクトがあります。 クラス拡張機能は、受信する転送要求ごとに、フレームワーク要求オブジェクトをキューに入れます。 キューの状態 (開始、消去) は UDE クラス拡張機能によって管理され、クライアント ドライバーはその状態を変更できません。 各要求オブジェクトには、転送の詳細を含む USB 要求ブロック (URB) が含まれています。

この例では、クライアント ドライバーが既定の制御エンドポイントを作成します。

EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL IoEvtControlUrb;
EVT_UDECX_USB_ENDPOINT_RESET UsbEndpointReset;
EVT_UDECX_USB_ENDPOINT_PURGE UsEndpointEvtPurge;
EVT_UDECX_USB_ENDPOINT_START UsbEndpointEvtStart;

NTSTATUS
UsbCreateControlEndpoint(
    _In_
        WDFDEVICE WdfDevice
    )
{
    NTSTATUS                      status;
    PUSB_CONTEXT                  pUsbContext;
    WDF_IO_QUEUE_CONFIG           queueConfig;
    WDFQUEUE                      controlQueue;
    UDECX_USB_ENDPOINT_CALLBACKS  callbacks;
    PUDECXUSBENDPOINT_INIT        endpointInit;

    pUsbContext = WdfDeviceGetUsbContext(WdfDevice);
    endpointInit = NULL;

    WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);

    queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;

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

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    endpointInit = UdecxUsbSimpleEndpointInitAllocate(pUsbContext->UdecxUsbDevice);

    if (endpointInit == NULL) {

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }

    UdecxUsbEndpointInitSetEndpointAddress(endpointInit, USB_DEFAULT_ENDPOINT_ADDRESS);

    UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
    UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);

    callbacks.EvtUsbEndpointStart = UsbEndpointEvtStart;
    callbacks.EvtUsbEndpointPurge = UsEndpointEvtPurge;

    status = UdecxUsbEndpointCreate(&endpointInit,
        WDF_NO_OBJECT_ATTRIBUTES,
        &pUsbContext->UdecxUsbControlEndpoint);

    if (!NT_SUCCESS(status)) {
        goto exit;
    }

    UdecxUsbEndpointSetWdfIoQueue(pUsbContext->UdecxUsbControlEndpoint,
        controlQueue);

exit:

    if (endpointInit != NULL) {

        NT_ASSERT(!NT_SUCCESS(status));
        UdecxUsbEndpointInitFree(endpointInit);
        endpointInit = NULL;
    }

    return status;
}

動的エンドポイントを作成する

クライアント ドライバーは、UDE クラス拡張機能の要求に応じて (ハブ ドライバーとクライアント ドライバーに代わって) 動的エンドポイントを作成できます。 クラス拡張機能は、次のコールバック関数のいずれかを呼び出して要求を作成します。

*EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD クライアント ドライバーは既定の制御エンドポイント (エンドポイント 0) を作成します。

*EVT_UDECX_USB_DEVICE_ENDPOINT_ADD クライアント ドライバーは動的エンドポイントを作成します。

*EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE クライアント ドライバーは代替設定を選択すること、現在のエンドポイントを無効にすること、または動的エンドポイントを追加することで、構成を変更します。

クライアント ドライバーは UdecxUsbDeviceInitSetStateChangeCallbacks の呼び出し中に、前述のコールバックを登録しました。 「仮想 USB デバイスを作成する」を参照してください。 このメカニズムにより、クライアント ドライバーはデバイス上の USB 構成とインターフェイス設定を動的に変更できます。 たとえば、エンドポイント オブジェクトが必要な場合、または既存のエンドポイント オブジェクトを解放する必要がある場合、クラス拡張機能は EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE を呼び出します。

ここでは、クライアント ドライバーがコールバック関数の実装でエンドポイント オブジェクトの UDECXUSBENDPOINT ハンドルを作成する手順の概要を示します。

  1. UdecxUsbEndpointInitSetEndpointAddress を呼び出して、初期化パラメーターにエンドポイント アドレスを設定します。

  2. UdecxUsbEndpointInitSetCallbacks を呼び出して、クライアント ドライバーに実装されたコールバック関数を登録します。 単純なエンドポイントと同様に、ドライバーは次のコールバック関数を登録できます。

  3. UdecxUsbEndpointCreate を呼び出してエンドポイント オブジェクトを作成し、UDECXUSBENDPOINT ハンドルを取得します。

  4. UdecxUsbEndpointSetWdfIoQueue を呼び出して、フレームワーク キュー オブジェクトをエンドポイントに関連付けます。

この実装例では、クライアント ドライバーは動的な既定の制御エンドポイントを作成します。

NTSTATUS
UsbDevice_EvtUsbDeviceDefaultEndpointAdd(
    _In_
        UDECXUSBDEVICE            UdecxUsbDevice,
    _In_
        PUDECXUSBENDPOINT_INIT    UdecxUsbEndpointInit
)
{
    NTSTATUS                    status;
    PUDECX_USBDEVICE_CONTEXT    deviceContext;
    WDFQUEUE                    controlQueue;
    WDF_IO_QUEUE_CONFIG         queueConfig;
    UDECX_USB_ENDPOINT_CALLBACKS  callbacks;

    deviceContext = UdecxDeviceGetContext(UdecxUsbDevice);

    WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);

    queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;

    status = WdfIoQueueCreate (deviceContext->WdfDevice,
                               &queueConfig,
                               WDF_NO_OBJECT_ATTRIBUTES,
                               &controlQueue);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    UdecxUsbEndpointInitSetEndpointAddress(UdecxUsbEndpointInit, USB_DEFAULT_DEVICE_ADDRESS);

    UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
    UdecxUsbEndpointInitSetCallbacks(UdecxUsbEndpointInit, &callbacks);

    status = UdecxUsbEndpointCreate(UdecxUsbEndpointInit,
        WDF_NO_OBJECT_ATTRIBUTES,
        &deviceContext->UdecxUsbControlEndpoint);

    if (!NT_SUCCESS(status)) {
        goto exit;
    }

    UdecxUsbEndpointSetWdfIoQueue(deviceContext->UdecxUsbControlEndpoint,
        controlQueue);

exit:

    return status;
}

エンドポイントをリセットすることでエラー回復を実行する

場合によっては、エンドポイントでのストール状態などのさまざまな理由により、データ転送が失敗することがあります。 転送が失敗した場合、エンドポイントはエラー状態が解消されるまで要求を処理できません。 UDE クラス拡張機能がデータ転送に失敗すると、EVT_UDECX_USB_ENDPOINT_RESET コールバック関数を呼び出します。この関数は、ドライバーが前回の UdecxUsbEndpointInitSetCallbacks の呼び出し時に登録したものです。 実装では、ドライバーはパイプの HALT 状態をクリアし、エラー状態をクリアするために必要な他の手順を実行することを選択できます。

この呼び出しは非同期です。 クライアントがリセット操作を完了した後、ドライバーは WdfRequestComplete を呼び出して、該当する失敗コードで要求を完了する必要があります。 この呼び出しは、UDE クライアント拡張機能にリセット操作の完了をステータスと共に通知します。

注意 エラー回復に複雑な解決策が必要な場合は、クライアント ドライバーがホスト コントローラーをリセットするという選択肢があります。 このロジックは EVT_UDECX_WDF_DEVICE_RESET コールバック関数で実装できます。この関数は、ドライバーが UdecxWdfDeviceAddUsbDeviceEmulation の呼び出し時に登録したものです。 必要に応じて、ドライバーはホスト コントローラーとすべてのダウンストリーム デバイスをリセットできます。 クライアント ドライバーがコントローラーをリセットする必要はないが、すべてのダウンストリーム デバイスをリセットする必要がある場合、ドライバーは登録時の構成パラメーターに UdeWdfDeviceResetActionResetEachUsbDevice を指定する必要があります。 その場合、クラス拡張機能は接続されたデバイスごとに EVT_UDECX_WDF_DEVICE_RESET を呼び出します。

キューの状態管理を実装する

UDE エンドポイント オブジェクトに関連付けられたフレームワーク キュー オブジェクトの状態は、UDE クラス拡張機能によって管理されます。 ただし、クライアント ドライバーが要求をエンドポイント キューから他の内部キューに転送する場合、クライアントはエンドポイントの I/O フローの変更を処理するロジックを実装する必要があります。 これらのコールバック関数は UdecxUsbEndpointInitSetCallbacks に登録されます。

エンドポイントの消去操作

UDE クライアント ドライバーは、エンドポイントごとに 1 つのキューが割り当てられ、次の例に示すように EVT_UDECX_USB_ENDPOINT_PURGE を実装できます。

EVT_UDECX_USB_ENDPOINT_PURGE 実装では、クライアント ドライバーは、エンドポイントのキューから転送されたすべての I/O が完了していることを確認する必要があり、また、クライアント ドライバーの EVT_UDECX_USB_ENDPOINT_START が呼び出されるまで、新しく転送された I/O は失敗します。 これらの要件は、UdecxUsbEndpointPurgeComplete を呼び出すことで満たされます。これにより、すべての転送 I/O が完了し、今後の転送 I/O が失敗するようになります。

エンドポイントの開始操作

EVT_UDECX_USB_ENDPOINT_START 実装では、クライアント ドライバーは、エンドポイントのキューで、およびエンドポイントに転送された I/O を受信するキューで I/O の処理を開始する必要があります。 エンドポイントが作成された後、このコールバック関数から制御が戻るまで、エンドポイントは I/O を受け取りません。 このコールバックは、EVT_UDECX_USB_ENDPOINT_PURGE が完了した後、エンドポイントを I/O 処理可能な状態に戻します。

データ転送要求 (URB) の処理

クライアント デバイスのエンドポイントに送信された USB I/O 要求を処理するには、キューをエンドポイントに関連付けるときに、UdecxUsbEndpointInitSetCallbacks で使用されるキュー オブジェクトの EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL コールバックをキャッチします。 そのコールバックで、IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode の I/O を処理します (「URB 処理メソッドのサンプル コード」を参照)。

URB 処理メソッド

仮想デバイスのエンドポイントに関連付けられたキューの IOCTL_INTERNAL_USB_SUBMIT_URB を介した URB の処理の一環として、UDE クライアント ドライバーは、次のメソッドを使用して I/O 要求の転送バッファーへのポインターを取得できます。

これらの関数は、エンドポイント上のキューと要求を処理するためにクライアント ドライバーによって実装されます。

UdecxUrbRetrieveControlSetupPacket 指定されたフレームワーク要求オブジェクトから USB 制御セットアップ パケットを取得します。

UdecxUrbRetrieveBuffer 指定したフレームワーク要求オブジェクトからエンドポイント キューに送信された URB の転送バッファーを取得します。

UdecxUrbSetBytesCompleted フレームワーク要求オブジェクトに含まれる URB に転送されたデータのバイト数を設定します。

UdecxUrbComplete USB 固有の完了ステータス コードで URB 要求を完了します。

UdecxUrbCompleteWithNtStatus NTSTATUS コードで URB 要求を完了します。

USB OUT 転送の URB に対する一般的な I/O 処理の流れを次に示します。

static VOID
IoEvtSampleOutUrb(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
)
{
    PENDPOINTQUEUE_CONTEXT pEpQContext;
    NTSTATUS status = STATUS_SUCCESS;
    PUCHAR transferBuffer;
    ULONG transferBufferLength = 0;

    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(InputBufferLength);

    // one possible way to get context info
    pEpQContext = GetEndpointQueueContext(Queue);

    if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB)
    {
        LogError(TRACE_DEVICE, "WdfRequest %p Incorrect IOCTL %x, %!STATUS!",
            Request, IoControlCode, status);
        status = STATUS_INVALID_PARAMETER;
        goto exit;
    }

    status = UdecxUrbRetrieveBuffer(Request, &transferBuffer, &transferBufferLength);
    if (!NT_SUCCESS(status))
    {
        LogError(TRACE_DEVICE, "WdfRequest %p unable to retrieve buffer %!STATUS!",
            Request, status);
        goto exit;
    }

    if (transferBufferLength >= 1)
    {
        //consume one byte of output data
        pEpQContext->global_storage = transferBuffer[0];
    }

exit:
    // writes never pended, always completed
    UdecxUrbSetBytesCompleted(Request, transferBufferLength);
    UdecxUrbCompleteWithNtStatus(Request, status);
    return;
}

クライアント ドライバーは、DPC を使用して別のスレッドで I/O 要求を完了できます。 次のベスト プラクティスに従ってください。

  • 既存の USB ドライバーとの互換性を確保するために、UDE クライアントは DISPATCH_LEVEL で WdfRequestComplete を呼び出す必要があります。
  • URB がエンドポイントのキューに追加され、ドライバーが呼び出し側ドライバーのスレッドまたは DPC で URB の処理を同期的に開始した場合、要求は同期的に完了しないようにする必要があります。 そのためには、別の DPC が必要です。ドライバーは、WdfDpcEnqueue を呼び出してその DPC をキューに入れます。
  • UDE クラス拡張機能が、EvtIoCanceledOnQueue または EvtRequestCancel を呼び出すとき、クライアント ドライバーは、呼び出し元のスレッドまたは DPC とは別の DPC で受信した URB を完了する必要があります。 そのためには、ドライバーは URB キュー用の EvtIoCanceledOnQueue コールバックを用意する必要があります。