撰寫 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_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要求。
呼叫 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裝置電源狀態。
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 回呼函式中執行這些工作。
呼叫 UdecxUsbSimpleEndpointInitAllocate 以取得類別延伸模組所配置的初始化參數指標。
呼叫 UdecxUsbEndpointInitSetEndpointAddress 以在初始化參數中設定端點位址。
呼叫 UdecxUsbEndpointInitSetCallbacks 以註冊客戶端驅動程序實作的回呼函式。
用戶端驅動程式會實作這些函式,以處理端點上的佇列和要求。
呼叫 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 以註冊客戶端驅動程序實作的回呼函式。 與簡單的端點類似,驅動程式可以註冊這些回呼函式:
- EVT_UDECX_USB_ENDPOINT_RESET(必要)。
- EVT_UDECX_USB_ENDPOINT_START
- EVT_UDECX_USB_ENDPOINT_PURGE
呼叫 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 用戶端擴充功能狀態重設作業完成。
注意 :如果錯誤復原需要複雜的解決方案,用戶端驅動程式可以選擇重設主機控制器。 此邏輯可以在驅動程式在其 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 回呼。