Partager via


Écrire un pilote client UDE

Cet article décrit le comportement de l’extension de classe d’émulation d’un appareil USB (UDE) 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 manière dont le pilote de classe et l’extension de classe communiquent entre eux à travers 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 pour interroger les capacités du contrôleur et le réinitialiser.
  • Création d’un appareil USB virtuel, configuration de ce dernier pour la gestion de l’alimentation et les transferts de données via des points de terminaison.

API importantes

Avant de commencer

  • Installez le dernier kit de pilotes Windows (WDK) sur votre ordinateur de développement. Le kit contient les fichiers et bibliothèques d’en-tête requis pour écrire un pilote client UDE, en particulier :
    • La bibliothèque de stubs, (Udecxstub.lib). La bibliothèque convertit les appels effectués par le pilote client et les transmet à UdeCx.
    • Le fichier d’en-tête, Udecx.h.
  • Installez Windows 10 sur votre ordinateur cible.
  • Se familiariser avec l’UDE. Consultez Architecture : émulation d’appareils USB (UDE).
  • Familiarisez-vous avec Windows Driver Foundation (WDF). Lecture conseillée : Developing Drivers with Windows Driver Foundation écrit par Penny Orwick et Guy Smith.

Handles et objets 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 hôte émulé (WDFDEVICE)

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

  • Objet d’appareil UDE (UDECXUSBDEVICE)

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

  • Objet de point de terminaison UDE (UDECXUSBENDPOINT)

    Représente des canaux de données séquentiels d’appareils USB. Permet de recevoir des demandes logicielles pour envoyer ou recevoir des données sur un point de terminaison.

Initialiser le contrôleur hôte émulé

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é est présenté ci-dessous. Nous recommandons que le pilote effectue ces tâches dans sa fonction de rappel EvtDriverDeviceAdd.

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

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

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

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

    Voici les fonctions de rappel associées à l’objet du 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

Lors de l’initialisation, le pilote client UDE expose le GUID de l’interface de l’appareil GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Cela permet au pilote de recevoir des demandes 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 USB IOCTLs avec GUID de l’interface d’appareil : GUID_DEVINTERFACE_USB_HOST_CONTROLLER.

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

Informer des capacités du contrôleur hôte

Avant que les pilotes de la couche supérieure puissent utiliser les capacités d’un contrôleur hôte USB, ils doivent déterminer si ces dernières sont prises en charge par le contrôleur. Les pilotes effectuent de telles demandes en appelant WdfUsbTargetDeviceQueryUsbCapability a USBD_QueryUsbCapability. Ces appels sont transférés à l’extension de classe d’émulation d’appareil USB (UDE). Lorsque la demande des obtenue, 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. Cette fonction de rappel est requise.

Lors de l’implémentation, le pilote client doit indiquer s’il prend en charge la capacité demandée. Certaines capacités ne sont pas prises en charge par l’UDE, comme 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 appareil USB virtuel

Un appareil USB virtuel se comporte comme un appareil USB. Il prend en charge une configuration avec plusieurs interfaces et chaque interface prend en charge plusieurs configurations. Chaque configuration peut avoir un autre point de terminaison 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 fournir des informations comme un appareil USB réel.

Remarque

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

Le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBDEVICE pour un objet d’appareil UDE est présenté ci-dessous. Le pilote doit effectuer ces étapes après avoir récupéré le handle WDFDEVICE pour le contrôleur hôte émulé. Nous recommandons que le pilote effectue 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. Enregistrez les fonctions de rappel d’événement 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 de l’appareil USB ainsi que 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 décide de créer des points de terminaison simples, il doit créer tous les objets de point de terminaison avant de connecter l’appareil. L’appareil ne doit avoir qu’une seule configuration et une seule configuration 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 la connexion de 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 de descripteur standard que le pilote client a fourni lors de l’initialisation à l’aide de l’une des méthodes précédentes, l’extension de classe complète automatiquement la demande. L’extension de classe ne transfère pas cette demande au pilote client. Cette conception réduit le nombre de demande que le pilote doit traiter pour les demandes de contrôle. En outre, elle évite au pilote d’implémenter une logique de descripteur qui nécessite une analyse approfondie du paquet d’initialisation et une gestion correcte de wLength et TransferBufferLength. Cette liste inclut les demandes standard. Le pilote client n’a pas besoin de vérifier 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, la classe UDE transmet au pilote client les demandes de descripteurs d’interface, de descripteurs spécifiques à une classe ou de descripteurs définis par le fournisseur. Le pilote doit gérer ces demandes 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 l’appareil USB. Remarque Si l’appareil 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 enregistrant 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 de l’appareil USB

L’extension de classe UDE invoque les fonctions de rappel du pilote client lorsqu’elle reçoit une demande visant à mettre l’appareil en état de faible consommation ou à le remettre en état de fonctionnement. Ces fonctions de rappel sont requises pour les appareils USB qui prennent en charge la sortie de veille. Le pilote client a enregistré son implémentation dans l’appel précédent à UdecxUsbDeviceInitSetStateChangeCallbacks.

Pour en savoir plus, consultez États d’alimentation des appareils USB.

Un appareil USB 3.0 permet aux fonctions individuelles d’entrer dans un état de faible alimentation. Chaque fonction est également capable d’envoyer un signal de sortie 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 du 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 sort de veille.

Le pilote client peut simuler l’action d’un appareil USB virtuel qui lance sa propre sortie de veille à partir d’un faible état d’alimentation de lien, d’une suspension de fonction ou des deux. Pour un appareil USB 2.0, le pilote doit appeler UdecxUsbDeviceSignalWake, si le pilote a activé la sortie de veille de l’appareil dans la EVT_UDECX_USB_DEVICE_D0_EXIT la plus récente. Pour un appareils USB 3.0, le pilote doit appeler UdecxUsbDeviceSignalFunctionWake , car la fonctionnalité de sortie de veille USB 3.0 est par fonction. Si l’ensemble de l’appareil est dans un état de faible alimentation ou en train d’entrer dans un tel état, UdecxUsbDeviceSignalFunctionWake sort l’appareil de veille.

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 l’appareil USB. Le pilote crée des points de terminaison simples après avoir créé l’appareil UDE et avant d’indiquer que l’appareil est connecté.

Le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBDENDPOINT pour un objet de point de terminaison UDE est présenté ci-dessous. Le pilote doit effectuer ces étapes après avoir récupéré le handle UDECXUSBDEVICE pour l’appareil USB virtuel. Nous recommandons que le pilote effectue 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 enregistrer 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 demandes 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 de point de terminaison comme 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 d’infrastructure pour gérer les demandes de transfert. Pour chaque demande de transfert reçue par l’extension de classe, 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 demande contient un bloc de demandes 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 de hub et des pilotes clients). L’extension de classe effectue la demande en appelant l’une de ces fonctions de rappel :

*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 pendant son appel à UdecxUsbDeviceInitSetStateChangeCallbacks. Consultez Créer l’appareil virtuel USB. Ce mécanisme permet au pilote client de modifier dynamiquement les paramètres de configuration et d’interface USB 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 enregistrer les fonctions de rappel implémentées par le pilote client. Comme pour les points de terminaison simples, le pilote peut enregistrer les fonctions de rappel suivantes :

  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 la récupération d’erreurs en réinitialisant un point de terminaison

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

Ces appel est asynchrone. Une fois que le client a terminé l’opération de réinitialisation, le pilote doit achever la demande avec un code d’échec approprié en appelant WdfRequestComplete. Cet appel informe l’extension de client UDE de l’achèvement de l’opération de réinitialisation avec l’état.

Remarque 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 enregistrée 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 appareils en aval, il doit spécifier UdeWdfDeviceResetActionResetEachUsbDevice dans les paramètres de configuration pendant l’enregistrement. 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 d’infrastructure 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 demandes des files d’attente de points de terminaison vers d’autres files d’attente internes, le client doit implémenter la logique pour gérer les modifications dans le flux d’E/S du point de terminaison. Ces fonctions de rappel sont enregistrées auprès d’UdecxUsbEndpointInitSetCallbacks.

Opération de vidage 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 abouties, et que les E/S nouvellement transférées échouent également jusqu’à ce que le EVT_UDECX_USB_ENDPOINT_START du pilote client est appelé. 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 ultérieures transférées échouent.

Opération de démarrage du point de terminaison

Dans l’implémentation EVT_UDECX_USB_ENDPOINT_START , le pilote client est requis pour 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 ce dernier. Après la création d’un point d’extrémité, celui-ci ne reçoit aucune entrée/sortie avant le renvoi de cette fonction de rappel. Ce rappel renvoie le point de terminaison à un état de traitement des E/S 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 de 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 CioControlCode IOCTL_INTERNAL_USB_SUBMIT_URB (consultez l’exemple de code sous méthodes de gestion des URB).

Méthodes de gestion des URB

Dans le cadre du traitement des URB via l’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 demandes sur un point de terminaison.

UdecxUrbRetrieveControlSetupPacket Récupère un paquet de configuration 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 demande URB avec un code d’état d’achèvement spécifique pour l’USB.

UdecxUrbCompleteWithNtStatus Termine la demande URB avec un code NTSTATUS.

Vous trouverez ci-dessous le flux de traitement d’E/S standard 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 séparément avec un DPC. Suivez ces meilleures pratiques :

  • Pour garantir la compatibilité avec les pilotes USB existants, le client UDE doit appeler WdfRequestComplete dans 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 DPC du pilote appelant, la demande ne doit pas être effectuée de manière synchrone. Un DPC distinct est nécessaire à cette fin, 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 terminer 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.