Scrivere un driver client UDE
Questo articolo descrive il comportamento dell'estensione e delle attività della classe UDE (Device Emulation) USB che un driver client deve eseguire per un controller host emulato e i dispositivi collegati. Fornisce informazioni sul modo in cui il driver di classe e l'estensione della classe comunicano con ognuno tramite un set di routine e funzioni di callback. Descrive anche le funzionalità che il driver client deve implementare.
Riepilogo
- Oggetti UDE e handle usati dall'estensione della classe e dal driver client.
- Creazione di un controller host emulato con funzionalità per eseguire query sulle funzionalità del controller e reimpostare il controller.
- Creazione di un dispositivo USB virtuale, impostandolo per il risparmio energia e i trasferimenti di dati tramite endpoint.
API importanti
Operazioni preliminari
- Installare la versione più recente di Windows Driver Kit (WDK) nel computer di sviluppo. Il kit include i file di intestazione e le librerie necessari per la scrittura di un driver client UDE, in particolare, è necessario:
- Libreria stub, (Udecxstub.lib). La libreria converte le chiamate effettuate dal driver client e le passa a UdeCx.
- File di intestazione, Udecx.h.
- Installare Windows 10 nel computer di destinazione.
- Acquisire familiarità con UDE. Vedere Architettura: Emulazione dispositivo USB(UDE).See Architecture: USB Device Emulation(UDE).
- Acquisire familiarità con Windows Driver Foundation (WDF). Lettura consigliata: Sviluppo di driver con Windows Driver Foundation, scritto da Penny Orwick e Guy Smith.
Oggetti e handle UDE
L'estensione della classe UDE e il driver client usano oggetti WDF specifici che rappresentano il controller host emulato e il dispositivo virtuale, inclusi gli endpoint e gli URL usati per trasferire i dati tra il dispositivo e l'host. Il driver client richiede la creazione degli oggetti e della durata dell'oggetto viene gestito dall'estensione della classe .
Oggetto controller host emulato (WDFDEVICE)
Rappresenta il controller host emulato ed è l'handle principale tra l'estensione della classe UDE e il driver client.
Oggetto dispositivo UDE (UDECXUSBDEVICE)
Rappresenta un dispositivo USB virtuale connesso a una porta nel controller host emulato.
Oggetto endpoint UDE (UDECXUSBENDPOINT)
Rappresenta pipe di dati sequenziali di dispositivi USB. Usato per ricevere richieste software per inviare o ricevere dati su un endpoint.
Inizializzare il controller host emulato
Ecco il riepilogo della sequenza in cui il driver client recupera un handle WDFDEVICE per il controller host emulato. È consigliabile che il driver esegua queste attività nella relativa funzione di callback EvtDriverDeviceAdd.
Chiamare UdecxInitializeWdfDeviceInit passando il riferimento a WDFDEVICE_INIT passato dal framework.
Inizializzare la struttura WDFDEVICE_INIT con informazioni di configurazione in modo che questo dispositivo risulti simile ad altri controller host USB. Ad esempio, assegnare un nome FDO e un collegamento simbolico, registrare un'interfaccia del dispositivo con il GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER fornito da Microsoft come GUID dell'interfaccia del dispositivo in modo che le applicazioni possano aprire un handle al dispositivo.
Chiamare WdfDeviceCreate per creare l'oggetto dispositivo framework.
Chiamare UdecxWdfDeviceAddUsbDeviceEmulation e registrare le funzioni di callback del driver client.
Ecco le funzioni di callback associate all'oggetto controller host, richiamate dall'estensione della classe UDE. Queste funzioni devono essere implementate dal driver client.
EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY
Determina le funzionalità supportate dal controller host che il driver client deve segnalare all'estensione della classe.
EVT_UDECX_WDF_DEVICE_RE edizione Standard T
Facoltativo. Reimposta il controller host e/o i dispositivi connessi.
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.
Gestire le richieste IOCTL in modalità utente inviate al controller host
Durante l'inizializzazione, il driver client UDE espone il GUID dell'interfaccia del dispositivo GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Ciò consente al driver di ricevere richieste IOCTL da un'applicazione che apre un handle di dispositivo usando tale GUID. Per un elenco dei codici di controllo IOCTL, vedi IOCTLs USB con GUID dell'interfaccia del dispositivo: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.
Per gestire tali richieste, il driver client registra il callback dell'evento EvtIoDeviceControl . Nell'implementazione, invece di gestire la richiesta, il driver può scegliere di inoltrare la richiesta all'estensione della classe UDE per l'elaborazione. Per inoltrare la richiesta, il driver deve chiamare UdecxWdfDeviceTryHandleUserIoctl. Se il codice di controllo IOCTL ricevuto corrisponde a una richiesta standard, ad esempio il recupero dei descrittori del dispositivo, l'estensione della classe elabora e completa correttamente la richiesta. In questo caso, UdecxWdfDeviceTryHandleUserIoctl viene completato con TRUE come valore restituito. In caso contrario, la chiamata restituisce FAL edizione Standard e il driver deve determinare come completare la richiesta. In un'implementazione più semplice, il driver può completare la richiesta con un codice di errore appropriato chiamando 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;
}
Segnalare le funzionalità del controller host
Prima che i driver di livello superiore possano usare le funzionalità di un controller host USB, i driver devono determinare se tali funzionalità sono supportate dal controller. I driver eseguono queste query chiamando WdfUsbTargetDeviceQueryUsbCapability e USBD_QueryUsbCapability. Tali chiamate vengono inoltrate all'estensione della classe USB Device Emulation(UDE). Dopo aver ottenuto la richiesta, l'estensione della classe richiama l'implementazione EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY del driver client. Questa chiamata viene effettuata solo dopo il completamento di EvtDriverDeviceAdd, in genere in EvtDevicePrepareHardware e non dopo EvtDeviceReleaseHardware. Questa è la funzione di callback necessaria.
Nell'implementazione, il driver client deve segnalare se supporta la funzionalità richiesta. Alcune funzionalità non sono supportate da UDE, ad esempio flussi statici.
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;
}
Creare un dispositivo USB virtuale
Un dispositivo USB virtuale si comporta in modo simile a un dispositivo USB. Supporta una configurazione con più interfacce e ogni interfaccia supporta impostazioni alternative. Ogni impostazione può avere un altro endpoint usato per i trasferimenti di dati. Tutti i descrittori (dispositivo, configurazione, interfaccia, endpoint) vengono impostati dal driver client UDE in modo che il dispositivo possa segnalare informazioni molto simili a un dispositivo USB reale.
Nota
Il driver client UDE non supporta hub esterni
Ecco il riepilogo della sequenza in cui il driver client crea un handle UDECXUSBDEVICE per un oggetto dispositivo UDE. Il driver deve eseguire questi passaggi dopo aver recuperato l'handle WDFDEVICE per il controller host emulato. È consigliabile che il driver esegua queste attività nella relativa funzione di callback EvtDriverDeviceAdd.
Chiamare UdecxUsbDeviceInitAllocate per ottenere un puntatore ai parametri di inizializzazione necessari per creare il dispositivo. Questa struttura viene allocata dall'estensione della classe UDE.
Registrare le funzioni di callback degli eventi impostando i membri di UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS e quindi chiamando UdecxUsbDeviceInitSetStateChangeCallbacks. Ecco le funzioni di callback associate all'oggetto dispositivo UDE, richiamate dall'estensione della classe UDE.
Queste funzioni vengono implementate dal driver client per creare o configurare gli endpoint.
Chiama UdecxUsbDeviceInitSetSpeed per impostare la velocità del dispositivo USB e anche il tipo di dispositivo, USB 2.0 o un dispositivo SuperSpeed.
Chiamare UdecxUsbDeviceInitSetEndpointsType per specificare il tipo di endpoint supportati dal dispositivo: semplice o dinamico. Se il driver client sceglie di creare endpoint semplici, il driver deve creare tutti gli oggetti endpoint prima di collegare il dispositivo. Il dispositivo deve avere una sola configurazione e una sola impostazione di interfaccia per ogni interfaccia. Nel caso di endpoint dinamici, il driver può creare endpoint in qualsiasi momento dopo aver collegato il dispositivo quando riceve un callback di eventi EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE . Vedere Creare endpoint dinamici.
Chiamare uno di questi metodi per aggiungere i descrittori necessari al dispositivo.
UdecxUsbDeviceInitAddStringDescriptorRaw
Se l'estensione della classe UDE riceve una richiesta per un descrittore standard fornito dal driver client durante l'inizializzazione usando uno dei metodi precedenti, l'estensione della classe completa automaticamente la richiesta. L'estensione della classe non inoltra tale richiesta al driver client. Questa progettazione riduce il numero di richieste che il driver deve elaborare per le richieste di controllo. Inoltre, elimina anche la necessità del driver di implementare la logica del descrittore che richiede un'analisi completa del pacchetto di installazione e la gestione di wLength e TransferBufferLength correttamente. Questo elenco include le richieste standard. Il driver client non deve verificare la presenza di queste richieste (solo se sono stati chiamati i metodi precedenti per aggiungere il descrittore):
USB_REQUEST_GET_DESCRIPTOR
USB_REQUEST_edizione Standard T_CONFIGURATION
USB_REQUEST_edizione Standard T_INTERFACE
USB_REQUEST_edizione Standard T_ADDRESS
USB_REQUEST_edizione Standard T_FEATURE
USB_FEATURE_FUNCTION_SUSPEND
USB_FEATURE_REMOTE_WAKEUP
USB_REQUEST_CLEAR_FEATURE
USB_FEATURE_ENDPOINT_STALL
USB_REQUEST_edizione Standard T_edizione Standard L
USB_REQUEST_ISOCH_DELAY
Tuttavia, le richieste per l'interfaccia, il descrittore specifico della classe o il descrittore definito dal fornitore, l'estensione della classe UDE li inoltra al driver client. Il driver deve gestire tali richieste di GET_DESCRIPTOR.
Chiamare UdecxUsbDeviceCreate per creare l'oggetto dispositivo UDE e recuperare l'handle UDECXUSBDEVICE.
Creare endpoint statici chiamando UdecxUsbEndpointCreate. Vedere Creare endpoint semplici.
Chiamare UdecxUsbDevicePlugIn per indicare all'estensione della classe UDE che il dispositivo è collegato e può ricevere richieste di I/O negli endpoint. Dopo questa chiamata, l'estensione della classe può anche richiamare funzioni di callback negli endpoint e nel dispositivo USB. Nota Se il dispositivo USB deve essere rimosso in fase di esecuzione, il driver client può chiamare UdecxUsbDevicePlugOutAndDelete. Se il driver vuole usare il dispositivo, deve crearlo chiamando UdecxUsbDeviceCreate.
In questo esempio si presuppone che le dichiarazioni del descrittore siano variabili globali, dichiarate come illustrato di seguito per un dispositivo HID come esempio:
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
};
Di seguito è riportato un esempio in cui il driver client specifica i parametri di inizializzazione registrando le funzioni di callback, impostando la velocità del dispositivo, indicando il tipo di endpoint e infine impostando alcuni descrittori di dispositivo.
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;
}
Risparmio energia del dispositivo USB
L'estensione della classe UDE richiama le funzioni di callback del driver client quando riceve una richiesta di inviare il dispositivo a uno stato di basso consumo o riportarlo allo stato di lavoro. Queste funzioni di callback sono necessarie per i dispositivi USB che supportano la riattivazione. Il driver client ha registrato la sua implementazione da nella chiamata precedente a UdecxUsbDeviceInitSetStateChangeCallbacks.
Per altre informazioni, vedere Stati di alimentazione dei dispositivi USB.
EVT_UDECX_USB_DEVICE_D0_ENTRY: il driver client passa il dispositivo da uno stato Dx allo stato D0.
EVT_UDECX_USB_DEVICE_D0_EXIT: il driver client esegue la transizione del dispositivo dallo stato D0 a uno stato Dx.
EVT_UDECX_USB_DEVICE_edizione Standard T_FUNCTION_SUSPEND_AND_WAKE: il driver client modifica lo stato della funzione dell'interfaccia specificata del dispositivo USB 3.0 virtuale.
Un dispositivo USB 3.0 consente alle singole funzioni di entrare in uno stato di alimentazione inferiore. Ogni funzione è anche in grado di inviare un segnale di riattivazione. L'estensione della classe UDE notifica al driver client richiamando EVT_UDECX_USB_DEVICE_edizione Standard T_FUNCTION_SUSPEND_AND_WAKE. Questo evento indica una modifica dello stato di alimentazione della funzione e informa il driver client di se la funzione può riattivarsi dal nuovo stato. Nella funzione l'estensione della classe passa il numero di interfaccia della funzione che viene riattivata.
Il driver client può simulare l'azione di un dispositivo USB virtuale che avvia la propria riattivazione da uno stato di alimentazione a basso collegamento, la sospensione della funzione o entrambi. Per un dispositivo USB 2.0, il driver deve chiamare UdecxUsbDeviceSignalWake, se il driver ha abilitato la riattivazione del dispositivo nell'EVT_UDECX_USB_DEVICE_D0_EXIT più recente. Per un dispositivo USB 3.0, il driver deve chiamare UdecxUsbDeviceSignalFunctionWake perché la funzionalità di riattivazione USB 3.0 è per funzione. Se l'intero dispositivo si trova in uno stato a basso consumo o immettendo tale stato, UdecxUsbDeviceSignalFunctionWake riattiva il dispositivo.
Creare endpoint semplici
Il driver client crea oggetti endpoint UDE per gestire i trasferimenti di dati da e verso il dispositivo USB. Il driver crea endpoint semplici dopo aver creato il dispositivo UDE e prima di segnalare il dispositivo come collegato.
Ecco il riepilogo della sequenza in cui il driver client crea un handle UDECXUSBENDPOINT per un oggetto endpoint UDE. Il driver deve eseguire questi passaggi dopo aver recuperato l'handle UDECXUSBDEVICE per il dispositivo USB virtuale. È consigliabile che il driver esegua queste attività nella relativa funzione di callback EvtDriverDeviceAdd.
Chiamare UdecxUsbSimpleEndpointInitAllocate per ottenere un puntatore ai parametri di inizializzazione allocati dall'estensione della classe.
Chiamare UdecxUsbEndpointInitSetEndpointAddress per impostare l'indirizzo dell'endpoint nei parametri di inizializzazione.
Chiamare UdecxUsbEndpointInitSetCallbacks per registrare le funzioni di callback implementate dal driver client.
Queste funzioni vengono implementate dal driver client per gestire code e richieste in un endpoint.
EVT_UDECX_USB_ENDPOINT_RE edizione Standard T: reimposta un endpoint del dispositivo USB virtuale.
EVT_UDECX_USB_ENDPOINT_START: facoltativo. Avvia l'elaborazione delle richieste di I/O
EVT_UDECX_USB_ENDPOINT_PURGE: facoltativo. Arrestare l'accodamento delle richieste di I/O alla coda dell'endpoint e annullare le richieste non elaborate.
Chiamare UdecxUsbEndpointCreate per creare l'oggetto endpoint e recuperare l'handle UDECXUSBENDPOINT.
Chiamare UdecxUsbEndpointSetWdfIoQueue per associare un oggetto coda del framework all'endpoint. Se applicabile, può impostare l'oggetto endpoint come oggetto padre WDF della coda impostando gli attributi appropriati.
Ogni oggetto endpoint ha un oggetto coda framework per gestire le richieste di trasferimento. Per ogni richiesta di trasferimento ricevuta dall'estensione di classe, accoda un oggetto richiesta framework. Lo stato della coda (avviato, eliminato) viene gestito dall'estensione della classe UDE e il driver client non deve modificare tale stato. Ogni oggetto richiesta contiene un blocco di richieste USB che contiene i dettagli del trasferimento.
In questo esempio, il driver client crea l'endpoint di controllo predefinito.
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;
}
Creare endpoint dinamici
Il driver client può creare endpoint dinamici alla richiesta dell'estensione della classe UDE (per conto del driver hub e dei driver client). L'estensione della classe effettua la richiesta richiamando una di queste funzioni di callback:
* EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD Il driver client crea l'endpoint di controllo predefinito (endpoint 0)
* EVT_UDECX_USB_DEVICE_ENDPOINT_ADD Il driver client crea un endpoint dinamico.
* EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Il driver client modifica la configurazione selezionando un'impostazione alternativa, disabilitando gli endpoint correnti o aggiungendo endpoint dinamici.
Il driver client ha registrato il callback precedente durante la chiamata a UdecxUsbDeviceInitSetStateChangeCallbacks. Vedere Creare un dispositivo USB virtuale. Questo meccanismo consente al driver client di modificare dinamicamente le impostazioni di configurazione e interfaccia USB nel dispositivo. Ad esempio, quando è necessario un oggetto endpoint o è necessario rilasciare un oggetto endpoint esistente, l'estensione della classe chiama il EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE.
Ecco il riepilogo della sequenza in cui il driver client crea un handle UDECXUSBENDPOINT per un oggetto endpoint nella relativa implementazione della funzione di callback.
Chiamare UdecxUsbEndpointInitSetEndpointAddress per impostare l'indirizzo dell'endpoint nei parametri di inizializzazione.
Chiamare UdecxUsbEndpointInitSetCallbacks per registrare le funzioni di callback implementate dal driver client. Analogamente agli endpoint semplici, il driver può registrare queste funzioni di callback:
Chiamare UdecxUsbEndpointCreate per creare l'oggetto endpoint e recuperare l'handle UDECXUSBENDPOINT.
Chiamare UdecxUsbEndpointSetWdfIoQueue per associare un oggetto coda del framework all'endpoint.
In questa implementazione di esempio, il driver client crea un endpoint di controllo predefinito dinamico.
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;
}
Eseguire il ripristino degli errori reimpostando un endpoint
A volte, i trasferimenti di dati possono non riuscire a causa di vari motivi, ad esempio una condizione di stallo nell'endpoint. Nel caso di trasferimenti non riusciti, l'endpoint non può elaborare le richieste finché la condizione di errore non viene cancellata. Quando l'estensione della classe UDE non riesce a trasferire i dati, richiama la funzione di callback del driver client EVT_UDECX_USB_ENDPOINT_RE edizione Standard T, che il driver registrato nella chiamata precedente a UdecxUsbEndpointInitSetCallbacks. Nell'implementazione, il driver può scegliere di cancellare lo stato HALT della pipe ed eseguire altri passaggi necessari per cancellare la condizione di errore.
Questa chiamata è asincrona. Al termine dell'operazione di reimpostazione del client, il driver deve completare la richiesta con un codice di errore appropriato chiamando WdfRequestComplete. Tale chiamata notifica all'estensione client UDE il completamento dell'operazione di reimpostazione con stato.
Nota Se è necessaria una soluzione complessa per il ripristino degli errori, il driver client ha la possibilità di reimpostare il controller host. Questa logica può essere implementata nella funzione di callback EVT_UDECX_WDF_DEVICE_RE edizione Standard T registrata dal driver nella chiamata UdecxWdfDeviceAddUsbDeviceEmulation. Se applicabile, il driver può reimpostare il controller host e tutti i dispositivi downstream. Se il driver client non deve reimpostare il controller ma reimpostare tutti i dispositivi downstream, il driver deve specificare UdeWdfDeviceResetActionResetEachUsbDevice nei parametri di configurazione durante la registrazione. In tal caso, l'estensione della classe richiama EVT_UDECX_WDF_DEVICE_RE edizione Standard T per ogni dispositivo connesso.
Implementare la gestione dello stato della coda
Lo stato dell'oggetto coda del framework associato a un oggetto endpoint UDE viene gestito dall'estensione della classe UDE. Tuttavia, se il driver client inoltra le richieste dalle code degli endpoint ad altre code interne, il client deve implementare la logica per gestire le modifiche nel flusso di I/O dell'endpoint. Queste funzioni di callback vengono registrate con UdecxUsbEndpointInitSetCallbacks.
Operazione di eliminazione dell'endpoint
Un driver client UDE con una coda per endpoint può implementare EVT_UDECX_USB_ENDPOINT_PURGE come illustrato in questo esempio:
Nell'implementazione EVT_UDECX_USB_ENDPOINT_PURGE, il driver client è necessario per assicurarsi che tutte le operazioni di I/O inoltrate dalla coda dell'endpoint siano state completate e che le operazioni di I/O appena inoltrate hanno esito negativo fino a quando non viene richiamato il EVT_UDECX_USB_ENDPOINT_START del driver client. Questi requisiti vengono soddisfatti chiamando UdecxUsbEndpointPurgeComplete, che assicurano che tutte le operazioni di I/O inoltrate inoltrate siano completate e che le operazioni di I/O inoltrate future non siano riuscite.
Operazione di avvio dell'endpoint
Nell'implementazione EVT_UDECX_USB_ENDPOINT_START , il driver client deve iniziare l'elaborazione di I/O nella coda dell'endpoint e in tutte le code che ricevono operazioni di I/O inoltrate per l'endpoint. Dopo aver creato un endpoint, non riceve alcun I/O fino a quando questa funzione di callback non viene restituita. Questo callback restituisce l'endpoint a uno stato di I/O di elaborazione dopo il completamento di EVT_UDECX_USB_ENDPOINT_PURGE .
Gestione delle richieste di trasferimento dei dati (URB)
Per elaborare le richieste di I/O USB inviate agli endpoint del dispositivo client, intercettare il callback EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL sull'oggetto coda usato con UdecxUsbEndpointInitSetCallbacks quando si associa la coda all'endpoint. In tale callback, elaborare I/O per il IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (vedere codice di esempio nei metodi di gestione XAML).
Metodi di gestione DISI
Nell'ambito dell'elaborazione degli URI tramite IOCTL_INTERNAL_USB_SUBMIT_URB di una coda associata a un endpoint in un dispositivo virtuale, un driver client UDE può ottenere un puntatore al buffer di trasferimento di una richiesta di I/O usando questi metodi:
Queste funzioni vengono implementate dal driver client per gestire code e richieste in un endpoint.
UdecxUrbRetrieveControlSetupPacket Recupera un pacchetto di installazione del controllo USB da un oggetto richiesta framework specificato.
UdecxUrbRetrieveBuffer Recupera il buffer di trasferimento di un OGGETTO RICHIESTA FRAMEWORK specificato inviato alla coda dell'endpoint.
UdecxUrbSetBytesCompleted Imposta il numero di byte trasferiti per l'elemento KUB contenuto all'interno di un oggetto richiesta framework.
UdecxUrbComplete completa la richiesta KUBerneta con un codice di stato di completamento specifico dell'USB.
UdecxUrbCompleteWithNtStatus Completa la richiesta IIS con un codice NTSTATUS.
Di seguito è riportato il flusso dell'elaborazione di I/O tipica per l'istruzione SSH di un trasferimento USB OUT.
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;
}
Il driver client può completare una richiesta di I/O in un oggetto separato con un DPC. Seguire queste procedure consigliate:
- Per garantire la compatibilità con i driver USB esistenti, il client UDE deve chiamare WdfRequestComplete in DISPATCH_LEVEL.
- Se l'UTILITÀ è stata aggiunta alla coda di un endpoint e il driver inizia a elaborarlo in modo sincrono sul thread o DPC del driver chiamante, la richiesta non deve essere completata in modo sincrono. A tale scopo, è necessario un DPC separato, che la coda del driver chiamando WdfDpcEnqueue.
- Quando l'estensione della classe UDE richiama EvtIoCanceledOnQueue o EvtRequestCancel, il driver client deve completare l'operazione ricevuta IN un DPC separato dal thread o DPC del chiamante. A tale scopo, il driver deve fornire un callback EvtIoCanceledOnQueue per le code ODBC .