共用方式為


撰寫 UDE 用戶端驅動程式

本文說明客戶端驅動程式必須針對所連結的模擬主機控制器和裝置執行之 USB 裝置模擬(UDE)類別延伸模組和工作的行為。 它提供類別驅動程式和類別延伸模組如何透過一組例程和回呼函式來與每個類別通訊的相關信息。 它也會描述客戶端驅動程式預期要實作的功能。

摘要

  • 類別延伸模組和客戶端驅動程式所使用的UDE物件和句柄。
  • 建立具有查詢控制器功能並重設控制器功能的模擬主控制器。
  • 建立虛擬 USB 裝置,並將其設定為透過端點進行電源管理和資料傳輸。

重要 API

開始之前

  • 安裝 您開發電腦的最新 Windows 驅動程式套件 (WDK)。 套件具有撰寫 UDE 用戶端驅動程式所需的頭檔與連結庫,特別是您需要:
    • 存根連結庫 (Udecxstub.lib)。 連結庫會轉譯客戶端驅動程式所進行的呼叫,並將其傳遞至UdeCx。
    • 頭檔 Udecx.h。
  • 在您的目標電腦上安裝 Windows 10。
  • 熟悉UDE。 請參閱 架構:USB 裝置模擬(UDE)
  • 熟悉 Windows Driver Foundation (WDF)。 推薦閱讀: 使用 Windows Driver Foundation 開發驅動程式,由佩妮·奧里克和蓋伊·史密斯撰寫。

UDE 物件和句柄

UDE 類別延伸模組和用戶端驅動程式會使用代表模擬主機控制器和虛擬設備的特定 WDF 物件,包括用來在裝置與主機之間傳輸數據的端點和 URL。 用戶端驅動程式會要求建立 物件,而物件的存留期是由類別延伸模組所管理。

  • 模擬主機控制器物件 (WDFDEVICE)

    表示仿真的主控制器,是UDE類別延伸模組與客戶端驅動程序之間的主要句柄。

  • UDE 裝置物件 (UDECXUSBDEVICE)

    表示連線到模擬主機控制器上埠的虛擬USB裝置。

  • UDE 端點物件 (UDECXUSBENDPOINT)

    代表USB裝置的循序數據管道。 用來接收軟體要求,以在端點上傳送或接收數據。

初始化仿真的主控制器

以下是客戶端驅動程式針對模擬主機控制器擷取 WDFDEVICE 句柄的順序摘要。 我們建議驅動程式在其 EvtDriverDeviceAdd 回呼函式中執行這些工作。

  1. 藉由傳遞架構所傳遞WDFDEVICE_INIT的參考,呼叫 UdecxInitializeWdfDeviceInit。

  2. 使用 設定資訊初始化WDFDEVICE_INIT 結構,讓此裝置看起來與其他USB主機控制器類似。 例如,指派 FDO 名稱和符號連結,向 Microsoft 提供的 GUID_DEVINTERFACE_USB_HOST_CONTROLLER GUID 註冊裝置介面做為裝置介面 GUID,讓應用程式可以開啟裝置的句柄。

  3. 呼叫 WdfDeviceCreate 以建立架構裝置物件。

  4. 呼叫 UdecxWdfDeviceAddUsbDeviceEmulation 並註冊客戶端驅動程式的回呼函式。

    以下是與主機控制器對象相關聯的回呼函式,由UDE類別延伸模組叫用。 用戶端驅動程序必須實作這些函式。

    
    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控制代碼的清單,請參閱 USB IOCTLs 與裝置介面 GUID:GUID_DEVINTERFACE_USB_HOST_CONTROLLER。

為了處理這些要求,客戶端驅動程序會 註冊 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 裝置。 它支援具有多個介面的組態,而且每個介面都支援替代設定。 每個設定可以有一個用於數據傳輸的端點。 所有描述項(裝置、組態、介面、連接點)都是由UDE用戶端驅動程式設定,讓裝置可以報告資訊,就像真正的USB裝置一樣。

注意

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 以指定裝置支援的端點類型:簡單或動態。 如果客戶端驅動程式選擇建立簡單的端點,驅動程式必須先建立所有端點物件,才能插入裝置。 裝置必須只有一個組態,而且每個介面只能有一個介面設定。 如果是動態端點,驅動程式可以在裝置收到 EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE 事件回呼時,隨時建立端點。 請參閱 建立動態端點

  5. 呼叫上述任何方法,將必要的描述元新增至裝置。

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • UdecxUsbDeviceInitAddStringDescriptorRaw

      如果UDE類別延伸模組收到客戶端驅動程式在使用上述其中一種方法進行初始化期間所提供的標準描述項要求,則類別延伸模組會自動完成要求。 類別延伸模組不會將該要求轉送至客戶端驅動程式。 此設計可減少驅動程式需要處理控制要求的要求數目。 此外,它也不需要驅動程序實作描述元邏輯,而此邏輯需要大量剖析設定封包並處理 wLengthTransferBufferLength 。 此清單包含標準要求。 用戶端驅動程式不需要檢查這些要求(只有在呼叫上述方法以新增描述元時):

    • 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 ,向 UDE 類別延伸模組指出裝置已連結,而且可以在端點上接收 I/O 要求。 在此呼叫之後,類別延伸模組也可以在端點和 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裝置電源狀態

  • EVT_UDECX_USB_DEVICE_D0_ENTRY:客戶端驅動程式會將裝置從 Dx 狀態轉換為 D0 狀態。

  • EVT_UDECX_USB_DEVICE_D0_EXIT:客戶端驅動程式會將裝置從 D0 狀態轉換為 Dx 狀態。

  • EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE:用戶端驅動程式會變更虛擬 USB 3.0 裝置指定介面的函式狀態。

USB 3.0 裝置可讓個別功能進入較低的電源狀態。 每個函式也都能夠傳送喚醒訊號。 UDE 類別延伸模組會叫 用EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE來通知客戶端驅動程式。 此事件指出函式電源狀態變更,並通知用戶端驅動程式函式是否可以從新狀態喚醒。 在函式中,類別延伸會傳遞正在喚醒之函式的介面編號。

用戶端驅動程式可以模擬虛擬 USB 裝置從低鏈接電源狀態、函式暫停或兩者起始喚醒的虛擬 USB 裝置動作。 如果是USB 2.0裝置,如果驅動程式在最近EVT_UDECX_USB_DEVICE_D0_EXIT中啟用喚醒,驅動程序必須呼叫UdecxUsbDeviceSignalWake。 針對USB 3.0裝置,驅動程式必須呼叫 UdecxUsbDeviceSignalFunctionWake ,因為USB 3.0喚醒功能是個別函式。 如果整個裝置處於低電源狀態或進入這類狀態, UdecxUsbDeviceSignalFunctionWake 會喚醒裝置。

建立簡單的端點

用戶端驅動程式會建立 UDE 端點物件,以處理往返 USB 裝置的數據傳輸。 驅動程式會在建立 UDE 裝置之後,以及在將裝置回報為插入之前,建立簡單的端點。

以下是客戶端驅動程式為 UDE 端點物件建立 UDECXUSBENDPOINT 句柄的順序摘要。 驅動程式在擷取虛擬 USB 裝置的 UDECXUSBDEVICE 句柄之後,必須執行這些步驟。 我們建議驅動程式在其 EvtDriverDeviceAdd 回呼函式中執行這些工作。

  1. 呼叫 UdecxUsbSimpleEndpointInitAllocate 以取得類別延伸模組所配置的初始化參數指標。

  2. 呼叫 UdecxUsbEndpointInitSetEndpointAddress 以在初始化參數中設定端點位址。

  3. 呼叫 UdecxUsbEndpointInitSetCallbacks 以註冊客戶端驅動程序實作的回呼函式。

    用戶端驅動程式會實作這些函式,以處理端點上的佇列和要求。

    • EVT_UDECX_USB_ENDPOINT_RESET:重設虛擬USB裝置的端點。

    • EVT_UDECX_USB_ENDPOINT_START:選擇性。 開始處理 I/O 要求

    • EVT_UDECX_USB_ENDPOINT_PURGE:選擇性。 停止佇列端點佇列的 I/O 要求,並取消未處理的要求。

  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 用戶端擴充功能狀態重設作業完成。

注意 :如果錯誤復原需要複雜的解決方案,用戶端驅動程式可以選擇重設主機控制器。 此邏輯可以在驅動程式在其 UdecxWdfDeviceAddUsbDeviceEmulation 呼叫中註冊的EVT_UDECX_WDF_DEVICE_RESET回呼函式中作。 如果適用,驅動程式可以重設主機控制器和所有下游裝置。 如果客戶端驅動程式不需要重設控制器,但重設所有下游裝置,驅動程式必須在註冊期間在組態參數中指定 UdeWdfDeviceResetEachUsbDevice 。 在此情況下,類別延伸模組會 針對每個連線的裝置叫用EVT_UDECX_WDF_DEVICE_RESET

實作佇列狀態管理

與UDE端點對象相關聯的架構佇列物件狀態是由UDE類別延伸模組所管理。 不過,如果客戶端驅動程式將要求從端點佇列轉送至其他內部佇列,則客戶端必須實作邏輯來處理端點 I/O 流程中的變更。 這些回呼函式會向 UdecxUsbEndpointInitSetCallbacks註冊。

端點清除作業

每個端點有一個佇列的 UDE 用戶端驅動程式可以實 作EVT_UDECX_USB_ENDPOINT_PURGE ,如下列範例所示:

EVT_UDECX_USB_ENDPOINT_PURGE實作中,用戶端驅動程式必須確定從端點佇列轉送的所有 I/O 都已完成,而且新轉送的 I/O 也會失敗,直到叫用用戶端驅動程式的EVT_UDECX_USB_ENDPOINT_START為止。 呼叫 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 處理 URL 時,UDE 用戶端驅動程式可以使用下列方法來取得 I/O 要求的傳輸緩衝區指標:

用戶端驅動程式會實作這些函式,以處理端點上的佇列和要求。

UdecxUrbRetrieveControlSetupPacket 從指定的架構要求物件擷取 USB 控制項設定封包。

UdecxUrbRetrieveBuffer 從傳送至端點佇列的指定架構要求物件擷取 URB 的傳輸緩衝區。

UdecxUrbSetBytesCompleted 設定針對 Framework 要求物件內所含 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 上以同步方式開始處理它,則要求不得同步完成。 為了達到該目的,需要個別的 DPC,驅動程式會藉由呼叫 WdfDpcEnqueue 來排入佇列。
  • 當 UDE 類別延伸模組叫用 EvtIoCanceledOnQueue EvtRequestCancel 時,用戶端驅動程式必須在與呼叫端線程或 DPC 不同的 DPC 上完成收到的 URB。 若要這樣做,驅動程式必須為其 URB 佇列提供 EvtIoCanceledOnQueue 回呼。