Scrivere un driver client UDE

Questo articolo descrive il comportamento dell'estensione e delle attività della classe e dell'emulazione del dispositivo USB (UDE) che un driver client deve eseguire per un controller host emulato e i dispositivi collegati. Fornisce informazioni sul modo in cui il driver della 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, configurandolo per il risparmio energia e i trasferimenti di dati tramite endpoint.

API importanti

Prima di iniziare

  • Installare l'ultimo 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 traduce 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).
  • 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)

    Rappresentare pipe di dati sequenziali dei dispositivi USB. Usato per ricevere richieste software per inviare o ricevere dati in 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 EvtDriverDeviceAdd callback.

  1. Chiamare UdecxInitializeWdfDeviceInit passando il riferimento a WDFDEVICE_INIT passato dal framework.

  2. 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 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.

  3. Chiamare WdfDeviceCreate per creare l'oggetto dispositivo framework.

  4. 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_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 di codici di controllo IOCTL, vedere IOCTLs USB con GUID dell'interfaccia dispositivo: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.

Per gestire queste 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 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. Queste chiamate vengono inoltrate all'estensione della classe USB Device Emulation(UDE). Dopo aver ottenuto la richiesta, l'estensione della classe richiama l'implementazione del driver client EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY . Questa chiamata viene effettuata 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 come 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 EvtDriverDeviceAdd callback.

  1. Chiamare UdecxUsbDeviceInitAllocate per ottenere un puntatore ai parametri di inizializzazione necessari per creare il dispositivo. Questa struttura viene allocata dall'estensione della classe UDE.

  2. 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.

  3. Chiamare UdecxUsbDeviceInitSetSpeed per impostare la velocità del dispositivo USB e anche il tipo di dispositivo, USB 2.0 o un dispositivo SuperSpeed.

  4. 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 un'unica impostazione dell'interfaccia per ogni interfaccia. Nel caso degli 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.

  5. Chiamare uno di questi metodi per aggiungere descrittori necessari al dispositivo.

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • UdecxUsbDeviceInitAddStringDescriptorRaw

      Se l'estensione della classe UDE riceve una richiesta per un descrittore standard fornito dal driver client durante l'inizializzazione tramite 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. Elimina inoltre la necessità del driver di implementare correttamente la logica del descrittore che richiede un'analisi completa del pacchetto di installazione e la gestione di wLength e TransferBufferLength . 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

    • 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

      Tuttavia, le richieste per l'interfaccia, il descrittore specifico della classe o il fornitore, l'estensione della classe UDE li inoltra al driver client. Il driver deve gestire tali richieste di GET_DESCRIPTOR.

  6. Chiamare UdecxUsbDeviceCreate per creare l'oggetto dispositivo UDE e recuperare l'handle UDECXUSBDEVICE.

  7. Creare endpoint statici chiamando UdecxUsbEndpointCreate. Vedere Creare endpoint semplici.

  8. 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
};

Ecco 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 per inviare il dispositivo a uno stato a 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 l'implementazione da nella chiamata precedente a UdecxUsbDeviceInitSetStateChangeCallbacks.

Per altre informazioni, vedere Stati di alimentazione del dispositivo USB.

Un dispositivo USB 3.0 consente a 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 di interfaccia della funzione che si sta svegliando.

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, sospensione della funzione o entrambi. Per un dispositivo USB 2.0, il driver deve chiamare UdecxUsbDeviceSignalWake, se il driver è abilitato per la riattivazione nel 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 di alimentazione insufficiente o se entra in 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 .

  1. Chiamare UdecxUsbSimpleEndpointInitAllocate per ottenere un puntatore ai parametri di inizializzazione allocati dall'estensione della classe.

  2. Chiamare UdecxUsbEndpointInitSetEndpointAddress per impostare l'indirizzo dell'endpoint nei parametri di inizializzazione.

  3. 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.

  4. Chiamare UdecxUsbEndpointCreate per creare l'oggetto endpoint e recuperare l'handle UDECXUSBENDPOINT.

  5. 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 del framework per gestire le richieste di trasferimento. Per ogni richiesta di trasferimento ricevuta dall'estensione della 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 un oggetto endpoint esistente deve essere rilasciato, 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 nell'implementazione della funzione di callback.

  1. Chiamare UdecxUsbEndpointInitSetEndpointAddress per impostare l'indirizzo dell'endpoint nei parametri di inizializzazione.

  2. Chiamare UdecxUsbEndpointInitSetCallbacks per registrare le funzioni di callback implementate dal driver client. Analogamente agli endpoint semplici, il driver può registrare queste funzioni di callback:

  3. Chiamare UdecxUsbEndpointCreate per creare l'oggetto endpoint e recuperare l'handle UDECXUSBENDPOINT.

  4. 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 avere esito negativo 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 fino a quando non viene cancellata la condizione di errore. 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 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_RESET 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_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 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 l'I/O appena inoltrato non riesce fino a quando non viene richiamato il EVT_UDECX_USB_ENDPOINT_START del driver client. Questi requisiti vengono soddisfatti chiamando UdecxUsbEndpointPurgeComplete, che assicurarsi che tutte le operazioni di I/O inoltrate vengano completate e che le operazioni di I/O inoltrate future non siano riuscite.

Operazione di avvio dell'endpoint

Nell'implementazione di EVT_UDECX_USB_ENDPOINT_START , è necessario che il driver client inizi a elaborare I/O nella coda dell'endpoint e su tutte le code che ricevono l'I/O inoltrato per l'endpoint. Dopo aver creato un endpoint, non riceve alcuna I/O finché non viene restituita la funzione di callback. Questo callback restituisce 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, intercettare il callback EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL nell'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 DICOM).

Metodi di gestione DI URB

Nell'ambito dell'elaborazione degli URL 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'ISTANZA contenuta 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 di elaborazione di I/O tipico per l'ISTANZA 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 DPC separato. Seguire queste procedure consigliate:

  • Per garantire la compatibilità con i driver USB esistenti, il client UDE deve chiamare WdfRequestComplete in DISPATCH_LEVEL.
  • Se IL DRIVER è stato aggiunto alla coda di un endpoint e il driver inizia a elaborarlo in modo sincrono sul thread del driver chiamante o DPC, la richiesta non deve essere completata in modo sincrono. Per 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'ISTANZA ricevuta in un DPC separato dal thread del chiamante o DPC. A tale scopo, il driver deve fornire un callback EvtIoCanceledOnQueue per le code DI RECA .