Écrire un pilote client UDE

Cet article décrit le comportement de l’extension de classe UDE (emulation de périphérique USB) et les tâches qu’un pilote client doit effectuer pour un contrôleur hôte émulé et les appareils qui y sont attachés. Il fournit des informations sur la façon dont le pilote de classe et l’extension de classe communiquent avec chacun par le biais d’un ensemble de routines et de fonctions de rappel. Il décrit également les fonctionnalités que le pilote client est censé implémenter.

Résumé

  • Objets et handles UDE utilisés par l’extension de classe et le pilote client.
  • Création d’un contrôleur hôte émulé avec des fonctionnalités permettant d’interroger les fonctionnalités du contrôleur et de réinitialiser le contrôleur.
  • Création d’un périphérique USB virtuel, configuration pour la gestion de l’alimentation et les transferts de données via des points de terminaison.

API importantes

Avant de commencer

  • Installez la dernière version du Kit de pilotes Windows (WDK) sur votre ordinateur de développement. Le kit contient les fichiers d’en-tête et les bibliothèques requis pour l’écriture d’un pilote client UDE. En particulier, vous aurez besoin des éléments suivants :
    • Bibliothèque stub, (Udecxstub.lib). La bibliothèque traduit les appels effectués par le pilote client et les transmet à UdeCx.
    • Fichier d’en-tête, Udecx.h.
  • Installez Windows 10 sur votre ordinateur cible.
  • Familiarisez-vous avec l’UDE. Consultez Architecture : Émulation de périphérique USB (UDE).
  • Familiarisez-vous avec Windows Driver Foundation (WDF). Lecture recommandée : Développement de pilotes avec Windows Driver Foundation, écrit par Penny Orwick et Guy Smith.

Objets et handles UDE

L’extension de classe UDE et le pilote client utilisent des objets WDF particuliers qui représentent le contrôleur hôte émulé et l’appareil virtuel, y compris ses points de terminaison et ses URB utilisés pour transférer des données entre l’appareil et l’hôte. Le pilote client demande la création des objets et la durée de vie de l’objet est gérée par l’extension de classe.

  • Objet de contrôleur d’hôte émulé (WDFDEVICE)

    Représente le contrôleur d’hôte émulé et est le main handle entre l’extension de classe UDE et le pilote client.

  • Objet d’appareil UDE (UDECXUSBDEVICE)

    Représente un périphérique USB virtuel connecté à un port sur le contrôleur hôte émulé.

  • Objet de point de terminaison UDE (UDECXUSBENDPOINT)

    Représente les canaux de données séquentiels des périphériques USB. Utilisé pour recevoir des demandes logicielles d’envoi ou de réception de données sur un point de terminaison.

Initialiser le contrôleur hôte émulé

Voici le résumé de la séquence dans laquelle le pilote client récupère un handle WDFDEVICE pour le contrôleur hôte émulé. Nous recommandons au pilote d’effectuer ces tâches dans sa fonction de rappel EvtDriverDeviceAdd .

  1. Appelez UdecxInitializeWdfDeviceInit en transmettant la référence à WDFDEVICE_INIT passée par l’infrastructure.

  2. Initialisez la structure WDFDEVICE_INIT avec des informations d’installation de sorte que cet appareil semble similaire à d’autres contrôleurs hôtes USB. Par exemple, attribuez un nom FDO et un lien symbolique, inscrivez une interface d’appareil avec le GUID d’GUID_DEVINTERFACE_USB_HOST_CONTROLLER fourni par Microsoft en tant que GUID d’interface de l’appareil afin que les applications puissent ouvrir un handle à l’appareil.

  3. Appelez WdfDeviceCreate pour créer l’objet d’appareil framework.

  4. Appelez UdecxWdfDeviceAddUsbDeviceEmulation et inscrivez les fonctions de rappel du pilote client.

    Voici les fonctions de rappel associées à l’objet contrôleur hôte, qui sont appelées par l’extension de classe UDE. Ces fonctions doivent être implémentées par le pilote 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.
    
    

Gérer les requêtes IOCTL en mode utilisateur envoyées au contrôleur hôte

Pendant l’initialisation, le pilote client UDE expose le GUID d’interface de périphérique GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Cela permet au pilote de recevoir des requêtes IOCTL d’une application qui ouvre un handle d’appareil à l’aide de ce GUID. Pour obtenir la liste des codes de contrôle IOCTL, consultez IOCTL USB avec GUID d’interface d’appareil : GUID_DEVINTERFACE_USB_HOST_CONTROLLER.

Pour gérer ces demandes, le pilote client inscrit le rappel d’événement EvtIoDeviceControl . Dans l’implémentation, au lieu de gérer la demande, le pilote peut choisir de transférer la demande à l’extension de classe UDE pour traitement. Pour transférer la demande, le pilote doit appeler UdecxWdfDeviceTryHandleUserIoctl. Si le code de contrôle IOCTL reçu correspond à une requête standard, telle que la récupération des descripteurs d’appareil, l’extension de classe traite et termine la demande avec succès. Dans ce cas, UdecxWdfDeviceTryHandleUserIoctl se termine par TRUE comme valeur de retour. Dans le cas contraire, l’appel retourne FALSE et le pilote doit déterminer comment effectuer la demande. Dans une implémentation la plus simple, le pilote peut terminer la demande avec un code d’échec approprié en appelant 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;
}

Signaler les fonctionnalités du contrôleur hôte

Avant que les pilotes de couche supérieure puissent utiliser les fonctionnalités d’un contrôleur hôte USB, les pilotes doivent déterminer si ces fonctionnalités sont prises en charge par le contrôleur. Les pilotes effectuent de telles requêtes en appelant WdfUsbTargetDeviceQueryUsbCapability et USBD_QueryUsbCapability. Ces appels sont transférés vers l’extension de classe UDE (USB Device Emulation). Lors de l’obtention de la demande, l’extension de classe appelle l’implémentation EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY du pilote client. Cet appel est effectué uniquement une fois EvtDriverDeviceAdd terminé, généralement dans EvtDevicePrepareHardware et non après EvtDeviceReleaseHardware. Il s’agit d’une fonction de rappel obligatoire.

Dans l’implémentation, le pilote client doit indiquer s’il prend en charge la fonctionnalité demandée. Certaines fonctionnalités ne sont pas prises en charge par l’UDE, telles que les flux statiques.

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

Créer un périphérique USB virtuel

Un périphérique USB virtuel se comporte comme un périphérique USB. Il prend en charge une configuration avec plusieurs interfaces et chaque interface prend en charge d’autres paramètres. Chaque paramètre peut avoir un point de terminaison supplémentaire utilisé pour les transferts de données. Tous les descripteurs (appareil, configuration, interface, point de terminaison) sont définis par le pilote client UDE afin que l’appareil puisse signaler des informations semblable à un appareil USB réel.

Notes

Le pilote client UDE ne prend pas en charge les hubs externes

Voici le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBDEVICE pour un objet d’appareil UDE. Le pilote doit effectuer ces étapes après avoir récupéré le handle WDFDEVICE pour le contrôleur hôte émulé. Nous recommandons au pilote d’effectuer ces tâches dans sa fonction de rappel EvtDriverDeviceAdd .

  1. Appelez UdecxUsbDeviceInitAllocate pour obtenir un pointeur vers les paramètres d’initialisation requis pour créer l’appareil. Cette structure est allouée par l’extension de classe UDE.

  2. Inscrivez les fonctions de rappel d’événements en définissant les membres de UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS , puis en appelant UdecxUsbDeviceInitSetStateChangeCallbacks. Voici les fonctions de rappel associées à l’objet d’appareil UDE, qui sont appelées par l’extension de classe UDE.

    Ces fonctions sont implémentées par le pilote client pour créer ou configurer des points de terminaison.

  3. Appelez UdecxUsbDeviceInitSetSpeed pour définir la vitesse du périphérique USB et le type d’appareil, USB 2.0 ou un appareil SuperSpeed.

  4. Appelez UdecxUsbDeviceInitSetEndpointsType pour spécifier le type de points de terminaison pris en charge par l’appareil : simple ou dynamique. Si le pilote client choisit de créer des points de terminaison simples, il doit créer tous les objets de point de terminaison avant de brancher l’appareil. L’appareil ne doit avoir qu’une seule configuration et un seul paramètre d’interface par interface. Dans le cas de points de terminaison dynamiques, le pilote peut créer des points de terminaison à tout moment après avoir branché l’appareil lorsqu’il reçoit un rappel d’événement EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE . Consultez Créer des points de terminaison dynamiques.

  5. Appelez l’une de ces méthodes pour ajouter les descripteurs nécessaires à l’appareil.

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • UdecxUsbDeviceInitAddStringDescriptorRaw

      Si l’extension de classe UDE reçoit une demande pour un descripteur standard fourni par le pilote client lors de l’initialisation à l’aide de l’une des méthodes précédentes, l’extension de classe termine automatiquement la demande. L’extension de classe ne transfère pas cette demande au pilote client. Cette conception réduit le nombre de requêtes que le pilote doit traiter pour les demandes de contrôle. En outre, il élimine également la nécessité pour le pilote d’implémenter une logique de descripteur qui nécessite une analyse approfondie du paquet d’installation et la gestion correcte de wLength et TransferBufferLength . Cette liste inclut les demandes standard. Le pilote client n’a pas besoin de case activée pour ces requêtes (uniquement si les méthodes précédentes ont été appelées pour ajouter un descripteur) :

    • 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

      Toutefois, les demandes pour l’interface, le descripteur spécifique à la classe ou défini par le fournisseur, l’extension de classe UDE les transfère au pilote client. Le pilote doit gérer ces requêtes GET_DESCRIPTOR.

  6. Appelez UdecxUsbDeviceCreate pour créer l’objet d’appareil UDE et récupérer le handle UDECXUSBDEVICE.

  7. Créez des points de terminaison statiques en appelant UdecxUsbEndpointCreate. Consultez Créer des points de terminaison simples.

  8. Appelez UdecxUsbDevicePlugIn pour indiquer à l’extension de classe UDE que l’appareil est attaché et peut recevoir des demandes d’E/S sur les points de terminaison. Après cet appel, l’extension de classe peut également appeler des fonctions de rappel sur les points de terminaison et le périphérique USB. Note Si le périphérique USB doit être supprimé au moment de l’exécution, le pilote client peut appeler UdecxUsbDevicePlugOutAndDelete. Si le pilote souhaite utiliser l’appareil, il doit le créer en appelant UdecxUsbDeviceCreate.

Dans cet exemple, les déclarations de descripteur sont supposées être des variables globales, déclarées comme indiqué ici pour un appareil HID à titre d’exemple :

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

Voici un exemple dans lequel le pilote client spécifie des paramètres d’initialisation en inscrivant des fonctions de rappel, en définissant la vitesse de l’appareil, en indiquant le type de points de terminaison et enfin en définissant certains descripteurs d’appareil.


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

Gestion de l’alimentation du périphérique USB

L’extension de classe UDE appelle les fonctions de rappel du pilote client lorsqu’elle reçoit une demande d’envoi de l’appareil à un état d’alimentation faible ou de retour à l’état opérationnel. Ces fonctions de rappel sont requises pour les périphériques USB qui prennent en charge la veille. Le pilote client a enregistré son implémentation par lors de l’appel précédent à UdecxUsbDeviceInitSetStateChangeCallbacks.

Pour plus d’informations, consultez États d’alimentation des périphériques USB.

Un appareil USB 3.0 permet à des fonctions individuelles d’entrer dans un état d’alimentation inférieur. Chaque fonction est également capable d’envoyer un signal de veille. L’extension de classe UDE avertit le pilote client en appelant EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. Cet événement indique un changement d’état d’alimentation de la fonction et indique au pilote client si la fonction peut sortir du nouvel état. Dans la fonction, l’extension de classe transmet le numéro d’interface de la fonction qui se réveille.

Le pilote client peut simuler l’action d’un périphérique USB virtuel qui lance son propre réveil à partir d’un état d’alimentation faible, d’une interruption de fonction ou des deux. Pour un périphérique USB 2.0, le pilote doit appeler UdecxUsbDeviceSignalWake, si le pilote a activé la veille sur l’appareil dans la EVT_UDECX_USB_DEVICE_D0_EXIT la plus récente. Pour un périphérique USB 3.0, le pilote doit appeler UdecxUsbDeviceSignalFunctionWake , car la fonctionnalité de veille USB 3.0 est par fonction. Si l’ensemble de l’appareil est dans un état de faible consommation ou en entrant un tel état, UdecxUsbDeviceSignalFunctionWake réveille l’appareil.

Créer des points de terminaison simples

Le pilote client crée des objets de point de terminaison UDE pour gérer les transferts de données vers et depuis le périphérique USB. Le pilote crée des points de terminaison simples après avoir créé l’appareil UDE et avant de signaler l’appareil comme connecté.

Voici le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBENDPOINT pour un objet de point de terminaison UDE. Le pilote doit effectuer ces étapes après avoir récupéré le handle UDECXUSBDEVICE pour le périphérique USB virtuel. Nous recommandons au pilote d’effectuer ces tâches dans sa fonction de rappel EvtDriverDeviceAdd .

  1. Appelez UdecxUsbSimpleEndpointInitAllocate pour obtenir un pointeur vers les paramètres d’initialisation alloués par l’extension de classe.

  2. Appelez UdecxUsbEndpointInitSetEndpointAddress pour définir l’adresse du point de terminaison dans les paramètres d’initialisation.

  3. Appelez UdecxUsbEndpointInitSetCallbacks pour inscrire les fonctions de rappel implémentées par le pilote client.

    Ces fonctions sont implémentées par le pilote client pour gérer les files d’attente et les requêtes sur un point de terminaison.

  4. Appelez UdecxUsbEndpointCreate pour créer l’objet de point de terminaison et récupérer le handle UDECXUSBENDPOINT.

  5. Appelez UdecxUsbEndpointSetWdfIoQueue pour associer un objet de file d’attente d’infrastructure au point de terminaison. Le cas échéant, il peut définir l’objet point de terminaison sur l’objet parent WDF de la file d’attente en définissant les attributs appropriés.

    Chaque objet de point de terminaison a un objet de file d’attente framework pour gérer les demandes de transfert. Pour chaque demande de transfert que l’extension de classe reçoit, elle met en file d’attente un objet de demande d’infrastructure. L’état de la file d’attente (démarré, vidé) est géré par l’extension de classe UDE et le pilote client ne doit pas modifier cet état. Chaque objet de requête contient un bloc de requête USB (URB) qui contient les détails du transfert.

Dans cet exemple, le pilote client crée le point de terminaison de contrôle par défaut.

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

Créer des points de terminaison dynamiques

Le pilote client peut créer des points de terminaison dynamiques à la demande de l’extension de classe UDE (pour le compte du pilote hub et des pilotes clients). L’extension de classe effectue la requête en appelant l’une des fonctions de rappel suivantes :

*EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD Le pilote client crée le point de terminaison de contrôle par défaut (Point de terminaison 0)

*EVT_UDECX_USB_DEVICE_ENDPOINT_ADD Le pilote client crée un point de terminaison dynamique.

*EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Le pilote client modifie la configuration en sélectionnant un autre paramètre, en désactivant les points de terminaison actuels ou en ajoutant des points de terminaison dynamiques.

Le pilote client a enregistré le rappel précédent lors de son appel à UdecxUsbDeviceInitSetStateChangeCallbacks. Consultez Créer un périphérique USB virtuel. Ce mécanisme permet au pilote client de modifier dynamiquement la configuration USB et les paramètres d’interface sur l’appareil. Par exemple, lorsqu’un objet de point de terminaison est nécessaire ou qu’un objet de point de terminaison existant doit être libéré, l’extension de classe appelle le EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE.

Voici le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBENDPOINT pour un objet de point de terminaison dans son implémentation de la fonction de rappel.

  1. Appelez UdecxUsbEndpointInitSetEndpointAddress pour définir l’adresse du point de terminaison dans les paramètres d’initialisation.

  2. Appelez UdecxUsbEndpointInitSetCallbacks pour inscrire les fonctions de rappel implémentées par le pilote client. Comme pour les points de terminaison simples, le pilote peut inscrire ces fonctions de rappel :

  3. Appelez UdecxUsbEndpointCreate pour créer l’objet de point de terminaison et récupérer le handle UDECXUSBENDPOINT.

  4. Appelez UdecxUsbEndpointSetWdfIoQueue pour associer un objet de file d’attente d’infrastructure au point de terminaison.

Dans cet exemple d’implémentation, le pilote client crée un point de terminaison de contrôle par défaut dynamique.

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

Effectuer une récupération d’erreur en réinitialisant un point de terminaison

Parfois, les transferts de données peuvent échouer pour diverses raisons, telles qu’une condition de blocage dans le point de terminaison. En cas d’échec des transferts, le point de terminaison ne peut pas traiter les demandes tant que la condition d’erreur n’a pas été effacée. Lorsque l’extension de classe UDE rencontre des échecs de transfert de données, elle appelle la fonction de rappel EVT_UDECX_USB_ENDPOINT_RESET du pilote client, que le pilote a inscrite lors de l’appel précédent à UdecxUsbEndpointInitSetCallbacks. Dans l’implémentation, le pilote peut choisir d’effacer l’état HALT du canal et de prendre d’autres mesures nécessaires pour effacer la condition d’erreur.

Cet appel est asynchrone. Une fois que le client a terminé l’opération de réinitialisation, le pilote doit terminer la demande avec un code d’échec approprié en appelant WdfRequestComplete. Cet appel avertit l’extension cliente UDE de la fin de l’opération de réinitialisation avec status.

Note Si une solution complexe est requise pour la récupération d’erreurs, le pilote client a la possibilité de réinitialiser le contrôleur hôte. Cette logique peut être implémentée dans la fonction de rappel EVT_UDECX_WDF_DEVICE_RESET que le pilote a inscrite dans son appel UdecxWdfDeviceAddUsbDeviceEmulation . Le cas échéant, le pilote peut réinitialiser le contrôleur hôte et tous les appareils en aval. Si le pilote client n’a pas besoin de réinitialiser le contrôleur, mais de réinitialiser tous les périphériques en aval, le pilote doit spécifier UdeWdfDeviceResetActionResetEachUsbDevice dans les paramètres de configuration lors de l’inscription. Dans ce cas, l’extension de classe appelle EVT_UDECX_WDF_DEVICE_RESET pour chaque appareil connecté.

Implémenter la gestion de l’état de la file d’attente

L’état de l’objet de file d’attente de framework associé à un objet de point de terminaison UDE est géré par l’extension de classe UDE. Toutefois, si le pilote client transfère les requêtes des files d’attente de point de terminaison vers d’autres files d’attente internes, le client doit implémenter la logique pour gérer les modifications apportées au flux d’E/S du point de terminaison. Ces fonctions de rappel sont inscrites auprès de UdecxUsbEndpointInitSetCallbacks.

Opération de purge du point de terminaison

Un pilote client UDE avec une file d’attente par point de terminaison peut implémenter EVT_UDECX_USB_ENDPOINT_PURGE comme illustré dans cet exemple :

Dans l’implémentation EVT_UDECX_USB_ENDPOINT_PURGE , le pilote client est requis pour s’assurer que toutes les E/S transférées à partir de la file d’attente du point de terminaison ont été terminées et que les E/S nouvellement transférées échouent également jusqu’à ce que la EVT_UDECX_USB_ENDPOINT_START du pilote client soit appelée. Ces exigences sont remplies en appelant UdecxUsbEndpointPurgeComplete, ce qui garantit que toutes les E/S transférées sont terminées et que les E/S transférées ultérieures échouent.

Opération de démarrage du point de terminaison

Dans l’implémentation EVT_UDECX_USB_ENDPOINT_START , le pilote client doit commencer à traiter les E/S sur la file d’attente du point de terminaison et sur toutes les files d’attente qui reçoivent des E/S transférées pour le point de terminaison. Une fois qu’un point de terminaison est créé, il ne reçoit aucune E/S tant que cette fonction de rappel n’est pas retournée. Ce rappel retourne le point de terminaison à un état d’E/S de traitement une fois EVT_UDECX_USB_ENDPOINT_PURGE terminé.

Gestion des demandes de transfert de données (URB)

Pour traiter les demandes d’E/S USB envoyées aux points de terminaison de l’appareil client, interceptez le rappel EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL sur l’objet file d’attente utilisé avec UdecxUsbEndpointInitSetCallbacks lors de l’association de la file d’attente au point de terminaison. Dans ce rappel, traitez les E/S pour le IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (consultez l’exemple de code sous Méthodes de gestion URB).

Méthodes de gestion URB

Dans le cadre du traitement des URB via IOCTL_INTERNAL_USB_SUBMIT_URB d’une file d’attente associée à un point de terminaison sur un appareil virtuel, un pilote client UDE peut obtenir un pointeur vers la mémoire tampon de transfert d’une demande d’E/S à l’aide des méthodes suivantes :

Ces fonctions sont implémentées par le pilote client pour gérer les files d’attente et les requêtes sur un point de terminaison.

UdecxUrbRetrieveControlSetupPacket Récupère un paquet d’installation de contrôle USB à partir d’un objet de demande d’infrastructure spécifié.

UdecxUrbRetrieveBuffer Récupère la mémoire tampon de transfert d’un URB à partir de l’objet de demande d’infrastructure spécifié envoyé à la file d’attente du point de terminaison.

UdecxUrbSetBytesCompleted Définit le nombre d’octets transférés pour l’URB contenu dans un objet de demande d’infrastructure.

UdecxUrbComplete Termine la requête URB avec un code d’achèvement spécifique status à USB.

UdecxUrbCompleteWithNtStatus Termine la requête URB avec un code NTSTATUS.

Vous trouverez ci-dessous le flux de traitement d’E/S classique pour l’URB d’un transfert 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;
}

Le pilote client peut effectuer une demande d’E/S sur un autre avec un DPC. Suivez ces bonnes pratiques :

  • Pour garantir la compatibilité avec les pilotes USB existants, le client UDE doit appeler WdfRequestComplete à DISPATCH_LEVEL.
  • Si l’URB a été ajouté à la file d’attente d’un point de terminaison et que le pilote commence à le traiter de manière synchrone sur le thread ou la DPC du pilote appelant, la demande ne doit pas être effectuée de manière synchrone. Un DPC distinct est requis à cet effet, que le pilote met en file d’attente en appelant WdfDpcEnqueue.
  • Lorsque l’extension de classe UDE appelle EvtIoCanceledOnQueue ou EvtRequestCancel, le pilote client doit effectuer l’URB reçue sur un DPC distinct du thread ou du DPC de l’appelant. Pour ce faire, le pilote doit fournir un rappel EvtIoCanceledOnQueue pour ses files d’attente URB .