Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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.
Sommario
- Oggetti e handle UDE utilizzati dall'estensione di 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
Prima di iniziare
-
Installare la versione più recente di Windows Driver Kit (WDK) sul computer di sviluppo. Il kit include i file di intestazione e le librerie necessari per sviluppare un driver client UDE, in particolare, è necessario:
- La libreria stub, (Udecxstub.lib). La libreria converte le chiamate effettuate dal driver client e le passa a UdeCx.
- Il file di intestazione Udecx.h
- Installare Windows 10 nel computer di destinazione.
- Acquisire familiarità con UDE. Fare riferimento all'architettura : 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 maniglie 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 la loro durata viene gestita dall'estensione della classe.
Oggetto controller host emulato (WDFDEVICE)
Rappresenta il controller host emulato ed è il collegamento principale tra l'estensione della classe UDE e il driver del client.
l'oggetto dispositivo UDE (UDECXUSBDEVICE)
Rappresenta un dispositivo USB virtuale connesso a una porta nel controller host emulato.
oggetto endpoint UDE (UDECXUSBENDPOINT)
Rappresenta i canali di dati sequenziali dei 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 sua 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 sia simile ad altri controller host USB. Ad esempio, assegnare un nome al FDO, creare un collegamento simbolico, registrare un'interfaccia del dispositivo con il GUID fornito da Microsoft GUID_DEVINTERFACE_USB_HOST_CONTROLLER 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.
-
Opzionale. 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 del 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 restituisce TRUE come valore di ritorno. In caso contrario, la chiamata restituisce FALSE 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 tali 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 eseguita solo dopo il completamento di EvtDriverDeviceAdd, in genere in EvtDevicePrepareHardware e non dopo EvtDeviceReleaseHardware. Questa 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 funzione di callback relativa a 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 richiamo dell'evento EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE. Vedi 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 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_SET_CONFIGURATION
Richiesta USB: Imposta Interfaccia
USB_REQUEST_SET_ADDRESS
RICHIESTA_USB_IMPOSTA_CARATTERISTICA
Funzione di sospensione della caratteristica USB
USB_FEATURE_REMOTE_WAKEUP
RICHIESTA_USB_CANCELLA_FUNZIONE
USB_FEATURE_ENDPOINT_STALL
USB_REQUEST_SET_SEL
Richiesta di Ritardo ISOCH USB
Tuttavia, le richieste per l'interfaccia, il descrittore specifico della classe o il descrittore definito dal fornitore vengono inoltrate al driver client dall'estensione della classe UDE. 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. Vedi Crea 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 ne ha registrato l'implementazione nella chiamata precedente a UdecxUsbDeviceInitSetStateChangeCallbacks.
Per altre informazioni, vedere USB Device Power States.
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_SET_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_SET_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 dell'interfaccia della funzione che si sta riattivando.
Il driver client può simulare l'azione di un dispositivo USB virtuale che avvia la propria riattivazione da uno stato di bassa potenza del collegamento, dalla sospensione della funzione, o da entrambi. Per un dispositivo USB 2.0, il driver deve chiamare UdecxUsbDeviceSignalWake, se il driver ha abilitato la riattivazione sul dispositivo nell'EVT_UDECX_USB_DEVICE_D0_EXITpiù recente. Per un dispositivo USB 3.0, il driver deve chiamare UdecxUsbDeviceSignalFunctionWake perché la funzionalità di riattivazione USB 3.0 è specifica per ciascuna funzione. Se l'intero dispositivo si trova in uno stato a basso consumo o sta entrando in tale stato, UdecxUsbDeviceSignalFunctionWake riattiva il dispositivo.
Creazione di 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à nel relativo EvtDriverDeviceAdd funzione di callback.
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 su un endpoint.
EVT_UDECX_USB_ENDPOINT_RESET: 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. Interrompere la messa in coda delle richieste di I/O alla coda delle richieste dell'endpoint e annullare le richieste non elaborate.
Chiamare UdecxUsbEndpointCreare per creare l'oggetto endpoint e recuperare l'handle UDECXUSBENDPOINT.
Chiamare UdecxUsbEndpointSetWdfIoQueue per associare un oggetto coda del framework all'endpoint. Se applicabile, è possibile configurare l'oggetto endpoint come oggetto padre WDF della coda impostando gli attributi appropriati.
Ogni oggetto endpoint ha un oggetto di coda del framework per gestire le richieste di trasferimento. Per ogni richiesta di trasferimento ricevuta dall'estensione di classe, accoda un oggetto di richiesta del 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 su 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. Vedi Creare il 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 UdecxUsbEndpointCreare 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 tramite la reimpostazione di 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 EVT_UDECX_USB_ENDPOINT_RESET del driver client, 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 indicando lo 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_RESET che il driver ha registrato nella relativa UdecxWdfDeviceAddUsbDeviceEmulation chiamata. 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_RESET per ogni dispositivo connesso.
Implementare la gestione dello stato della coda
Lo stato dell'oggetto coda del framework associato a un oggetto endpoint UDE è gestito dall'estensione di 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 del 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 l'I/O appena inoltrato abbia 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 siano completate e le operazioni di I/O inoltrate future vengano bloccate.
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 riporta l'endpoint a uno stato di elaborazione di I/O 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, è necessario intercettare la callback EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL sull'oggetto coda utilizzato con UdecxUsbEndpointInitSetCallbacks al momento di associare la coda all'endpoint. In tale callback, gestire l'I/O per l'IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (vedere il codice di esempio nei metodi di gestione ).
Metodi di gestione URB
Nell'ambito dell'elaborazione degli URB tramite IOCTL_INTERNAL_USB_SUBMIT_URB di una coda associata a un endpoint su un dispositivo virtuale, un driver client UDE può ottenere un puntatore al buffer di trasferimento di una richiesta di I/O utilizzando 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 dall'oggetto di richiesta del framework specificato.
UdecxUrbRetrieveBuffer Recupera il buffer di trasferimento di un oggetto di richiesta framework specificato inviato alla coda dell'endpoint.
UdecxUrbSetBytesCompleted Imposta il numero di byte trasferiti per l'oggetto RESOURCE FRAMEWORK contenuto in un oggetto richiesta framework.
UdecxUrbComplete Completa la richiesta URB con un codice di stato di completamento specifico dell'USB.
UdecxUrbCompleteWithNtStatus Completa la richiesta URB con un codice NTSTATUS.
Di seguito è riportato il flusso dell'elaborazione I/O tipica per l'URB 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 separatamente 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'URB è stato aggiunto alla coda di un endpoint e il driver inizia a elaborarlo sincronicamente sul thread o sul DPC del driver chiamante, la richiesta non deve essere completata in modo sincrono. Per tale scopo, è necessario un DPC separato, che il driver mette in coda chiamando WdfDpcEnqueue.
- Quando l'estensione della classe UDE richiama EvtIoCanceledOnQueue o EvtRequestCancel, il driver client deve completare l'URB ricevuto in un DPC separato dal thread o DPC del chiamante. A tale scopo, il driver deve fornire un callback EvtIoCanceledOnQueue per le code URB.