撰寫 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 回呼函式中執行這些工作。
藉由將架構所傳遞的參考傳遞到 WDFDEVICE_INIT,來呼叫 UdecxInitializeWdfDeviceInit。
使用設定資訊初始化 WDFDEVICE_INIT 結構,讓此裝置看起來與其他USB主機控制器類似。 例如,指派 FDO 名稱和符號連結,使用Microsoft提供的 GUID_DEVINTERFACE_USB_HOST_CONTROLLER GUID 註冊裝置介面,作為裝置介面 GUID,讓應用程式可以開啟裝置的句柄。
呼叫 WdfDeviceCreate 來建立架構裝置物件。
呼叫 UdecxWdfDeviceAddUsbDeviceEmulation 並註冊用戶端驅動程式的回呼函數。
以下是與主機控制器對象相關聯的回呼函式,由UDE類別延伸模組叫用。 用戶端驅動程序必須實作這些函式。
EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY
判斷客戶端驅動程式必須向類別延伸模組報告之主控制器所支援的功能。
-
選擇性。 重設主機控制器和/或連接的裝置。
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主機控制器的功能之前,驅動程式必須判斷控制器是否支持這些功能。 驅動程式藉由呼叫 WdfUsbTargetDeviceQueryUsbCapability 和 USBD_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 回呼函式中執行這些工作。
呼叫 UdecxUsbDeviceInitAllocate,以取得建立裝置所需的初始化參數指標。 此結構是由 UDE 類別延伸模組所配置。
藉由設定 UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS 的成員,然後呼叫 UdecxUsbDeviceInitSetStateChangeCallbacks來註冊事件回呼函式。 以下是與 UDE 裝置物件相關聯的回呼函式,由 UDE 類別延伸模組叫用。
這些函式是由客戶端驅動程序實作,以建立或設定端點。
呼叫 UdecxUsbDeviceInitSetSpeed 來設定 USB 裝置速度,以及裝置類型、USB 2.0 或 SuperSpeed 裝置。
呼叫 UdecxUsbDeviceInitSetEndpointsType 來指定裝置支援的端點類型:簡單或動態。 如果客戶端驅動程式選擇建立簡單的端點,驅動程式必須先建立所有端點物件,才能插入裝置。 裝置必須只有一個組態,而且每個介面只能有一個介面設定。 在動態端點的情況下,驅動程式在裝置插入後,只要收到 EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE 事件回呼,就可以隨時建立端點。 請參閱 建立動態端點。
呼叫上述任何方法,將必要的描述元新增至裝置。
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 功能暫停功能
USB 功能遠端喚醒
USB_REQUEST_清除功能
USB_FEATURE_ENDPOINT_STALL
USB_REQUEST_SET_SEL
USB_REQUEST_ISOCH_DELAY
不過,針對介面、類別特定或廠商定義描述元的要求,UDE 類別擴充會將它們轉送至客戶端驅動程式。 驅動程式必須處理這些GET_DESCRIPTOR要求。
呼叫 UdecxUsbDeviceCreate,以建立 UDE 裝置物件並擷取 UDECXUSBDEVICE 句柄。
呼叫 UdecxUsbEndpointCreate來建立靜態端點。 請參閱 建立簡單端點。
呼叫 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 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 回呼函式中執行這些工作。
呼叫 UdecxUsbSimpleEndpointInitAllocate,以取得類別延伸模組所配置的初始化參數指標。
呼叫 UdecxUsbEndpointInitSetEndpointAddress,以在初始化參數中設定端點位址。
呼叫 UdecxUsbEndpointInitSetCallbacks,以註冊由用戶端驅動程式實現的回呼函式。
用戶端驅動程式會實作這些函式,以處理端點上的佇列和要求。
EVT_UDECX_USB_ENDPOINT_RESET:重設虛擬USB裝置的端點。
EVT_UDECX_USB_ENDPOINT_START:選擇性。 開始處理 I/O 要求
EVT_UDECX_USB_ENDPOINT_PURGE:選擇性。 停止傳送至端點佇列的 I/O 要求,並取消尚未處理的要求。
呼叫 UdecxUsbEndpointCreate,以建立端點物件並擷取 UDECXUSBENDPOINT 句柄。
呼叫 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 句柄的順序摘要。
呼叫 UdecxUsbEndpointInitSetEndpointAddress,以在初始化參數中設定端點位址。
呼叫 UdecxUsbEndpointInitSetCallbacks 來註冊用戶端驅動程式所實現的回呼函數。 與簡單的端點類似,驅動程式可以註冊這些回呼函式:
呼叫 UdecxUsbEndpointCreate,以建立端點物件並擷取 UDECXUSBENDPOINT 句柄。
呼叫 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 用戶端驅動程式可以依據如下範例實作 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 處理 URBs 時,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 回呼。