Présentation de la structure de code du pilote client USB (KMDF)

Dans cette rubrique, vous allez découvrir le code source d’un pilote client USB basé sur KMDF. Les exemples de code sont générés par le modèle de pilote de mode utilisateur USB inclus avec Microsoft Visual Studio 2019.

Ces sections fournissent des informations sur le code du modèle.

Pour obtenir des instructions sur la génération du code de modèle KMDF, consultez Comment écrire votre premier pilote client USB (KMDF).

Code source du pilote

L’objet driver représente le instance du pilote client après que Windows a chargé le pilote en mémoire. Le code source complet de l’objet pilote se trouve dans Driver.h et Driver.c.

Driver.h

Avant de discuter des détails du code du modèle, examinons certaines déclarations dans le fichier d’en-tête (Driver.h) qui sont pertinentes pour le développement de pilotes KMDF.

Driver.h, contient ces fichiers, inclus dans le Kit de pilotes Windows (WDK).

#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>

#include "device.h"
#include "queue.h"
#include "trace.h"

Les fichiers d’en-tête Ntddk.h et Wdf.h sont toujours inclus pour le développement de pilotes KMDF. Le fichier d’en-tête comprend différentes déclarations et définitions des méthodes et structures dont vous avez besoin pour compiler un pilote KMDF.

Usb.h et Usbdlib.h incluent des déclarations et des définitions de structures et de routines requises par un pilote client pour un périphérique USB.

Wdfusb.h inclut des déclarations et des définitions de structures et de méthodes requises pour communiquer avec les objets cibles d’E/S USB fournis par l’infrastructure.

Device.h, Queue.h et Trace.h ne sont pas inclus dans le WDK. Ces fichiers d’en-tête sont générés par le modèle et sont abordés plus loin dans cette rubrique.

Le bloc suivant dans Driver.h fournit des déclarations de type de rôle de fonction pour la routine DriverEntry et les routines de rappel d’événements EvtDriverDeviceAdd et EvtCleanupCallback . Toutes ces routines sont implémentées par le pilote. Les types de rôles aident le vérificateur de pilote statique (SDV) à analyser le code source d’un pilote. Pour plus d’informations sur les types de rôles, consultez Déclaration de fonctions à l’aide de types de rôles de fonction pour les pilotes KMDF.

DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;

Le fichier d’implémentation, Driver.c, contient le bloc de code suivant qui utilise alloc_text pragma pour spécifier si la fonction DriverEntry et les routines de rappel d’événements sont en mémoire paginable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif

Notez que DriverEntry est marqué comme INIT, tandis que les routines de rappel d’événement sont marquées comme PAGE. La section INIT indique que le code exécutable pour DriverEntry est paginable et ignoré dès que le pilote revient de son DriverEntry. La section PAGE indique que le code n’a pas besoin de rester en mémoire physique tout le temps ; il peut être écrit dans le fichier de page lorsqu’il n’est pas utilisé. Pour plus d’informations, consultez Verrouillage du code ou des données paginables.

Peu après le chargement de votre pilote, Windows alloue une structure DRIVER_OBJECT qui représente votre pilote. Il appelle ensuite la routine du point d’entrée de votre pilote, DriverEntry, et passe un pointeur vers la structure. Étant donné que Windows recherche la routine par son nom, chaque pilote doit implémenter une routine nommée DriverEntry. La routine effectue les tâches d’initialisation du pilote et spécifie les routines de rappel d’événements du pilote à l’infrastructure.

L’exemple de code suivant montre la routine DriverEntry générée par le modèle.

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
    WDF_DRIVER_CONFIG config;
    NTSTATUS status;
    WDF_OBJECT_ATTRIBUTES attributes;

    //
    // Initialize WPP Tracing
    //
    WPP_INIT_TRACING( DriverObject, RegistryPath );

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Register a cleanup callback so that we can call WPP_CLEANUP when
    // the framework driver object is deleted during driver unload.
    //
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.EvtCleanupCallback = MyUSBDriver_EvtDriverContextCleanup;

    WDF_DRIVER_CONFIG_INIT(&config,
                           MyUSBDriver_EvtDeviceAdd
                           );

    status = WdfDriverCreate(DriverObject,
                             RegistryPath,
                             &attributes,
                             &config,
                             WDF_NO_HANDLE
                             );

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
        WPP_CLEANUP(DriverObject);
        return status;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

La routine DriverEntry a deux paramètres : un pointeur vers la structure DRIVER_OBJECT allouée par Windows et un chemin d’accès au Registre pour le pilote. Le paramètre RegistryPath représente le chemin d’accès spécifique au pilote dans le Registre.

Dans la routine DriverEntry , le pilote effectue les tâches suivantes :

  • Alloue les ressources globales requises pendant la durée de vie du pilote. Par exemple, dans le code du modèle, le pilote client alloue les ressources nécessaires au suivi logiciel WPP en appelant la macro WPP_INIT_TRACING .

  • Inscrit certaines routines de rappel d’événements auprès de l’infrastructure.

    Pour inscrire les rappels d’événements, le pilote client spécifie d’abord des pointeurs vers ses implémentations des routines EvtDriverXxx dans certaines structures WDF. Le pilote appelle ensuite la méthode WdfDriverCreate et fournit ces structures (décrites à l’étape suivante).

  • Appelle la méthode WdfDriverCreate et récupère un handle vers l’objet de pilote d’infrastructure.

    Une fois que le pilote client a appelé WdfDriverCreate, l’infrastructure crée un objet de pilote d’infrastructure pour représenter le pilote client. Une fois l’appel terminé, le pilote client reçoit un handle WDFDRIVER et peut récupérer des informations sur le pilote, telles que son chemin d’accès au Registre, les informations de version, etc. (voir Référence de l’objet du pilote WDF).

    Notez que l’objet de pilote d’infrastructure est différent de l’objet pilote Windows décrit par DRIVER_OBJECT. À tout moment, le pilote client peut obtenir un pointeur vers la structurewindows DRIVER_OBJECT en utilisant le handle WDFDRIVER et en appelant la méthode WdfGetDriver .

Après l’appel WdfDriverCreate , l’infrastructure s’associe au pilote client pour communiquer avec Windows. L’infrastructure agit comme une couche d’abstraction entre Windows et le pilote et gère la plupart des tâches complexes du pilote. Le pilote client s’inscrit auprès de l’infrastructure pour les événements qui intéressent le pilote. Lorsque certains événements se produisent, Windows avertit l’infrastructure. Si le pilote a inscrit un rappel d’événement pour un événement particulier, l’infrastructure avertit le pilote en appelant le rappel d’événement inscrit. Ce faisant, le pilote a la possibilité de gérer l’événement, si nécessaire. Si le pilote n’a pas inscrit son rappel d’événement, l’infrastructure continue avec sa gestion par défaut de l’événement.

L’un des rappels d’événements que le pilote doit inscrire est EvtDriverDeviceAdd. L’infrastructure appelle l’implémentation EvtDriverDeviceAdd du pilote lorsque l’infrastructure est prête à créer un objet d’appareil. Dans Windows, un objet d’appareil est une représentation logique de la fonction de l’appareil physique pour lequel le pilote client est chargé (décrit plus loin dans cette rubrique).

Les autres rappels d’événements que le pilote peut inscrire sont EvtDriverUnload, EvtCleanupCallback et EvtDestroyCallback.

Dans le code du modèle, le pilote client s’inscrit pour deux événements : EvtDriverDeviceAdd et EvtCleanupCallback. Le pilote spécifie un pointeur vers son implémentation d’EvtDriverDeviceAdd dans la structure WDF_DRIVER_CONFIG et le rappel de l’événement EvtCleanupCallback dans la structure WDF_OBJECT_ATTRIBUTES .

Lorsque Windows est prêt à libérer la structure DRIVER_OBJECT et à décharger le pilote, l’infrastructure signale cet événement au pilote client en appelant l’implémentation EvtCleanupCallback du pilote. L’infrastructure appelle ce rappel juste avant de supprimer l’objet de pilote d’infrastructure. Le pilote client peut libérer toutes les ressources globales qu’il a allouées dans son DriverEntry. Par exemple, dans le code du modèle, le pilote client arrête le suivi WPP activé dans DriverEntry.

L’exemple de code suivant montre l’implémentation du rappel de l’événement EvtCleanupCallback du pilote client.

VOID MyUSBDriver_EvtDriverContextCleanup(
    _In_ WDFDRIVER Driver
    )
{
    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE ();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Stop WPP Tracing
    //
    WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );

}

Une fois l’appareil reconnu par la pile de pilotes USB, le pilote de bus crée un objet de périphérique physique (PDO) pour l’appareil et associe l’AOP au nœud de l’appareil. Le nœud d’appareil se trouve dans une formation de pile, où l’AOP se trouve en bas. Chaque pile doit avoir un AOP et peut avoir des objets d’appareil de filtre (DO de filtre) et un objet de périphérique de fonction (FDO) au-dessus. Pour plus d’informations, consultez Nœuds d’appareil et piles d’appareils.

Cette illustration montre la pile d’appareils du pilote de modèle, MyUSBDriver_.sys.

pile d’appareils pour le pilote de modèle.

Notez la pile d’appareils nommée « Mon périphérique USB ». La pile de pilotes USB crée l’AOP pour la pile de périphériques. Dans l’exemple, le PDO est associé à Usbhub3.sys, qui est l’un des pilotes inclus avec la pile de pilotes USB. En tant que pilote de fonction pour l’appareil, le pilote client doit d’abord créer le FDO pour l’appareil, puis l’attacher au sommet de la pile des appareils.

Pour un pilote client basé sur KMDF, l’infrastructure effectue ces tâches pour le compte du pilote client. Pour représenter le FDO de l’appareil, l’infrastructure crée un objet d’appareil framework. Le pilote client peut toutefois spécifier certains paramètres d’initialisation que l’infrastructure utilise pour configurer le nouvel objet. Cette possibilité est donnée au pilote client lorsque l’infrastructure appelle l’implémentation EvtDriverDeviceAdd du pilote. Une fois l’objet créé et le FDO attaché au haut de la pile d’appareils, l’infrastructure fournit au pilote client un handle WDFDEVICE à l’objet d’appareil framework. À l’aide de ce handle, le pilote client peut effectuer différentes opérations liées à l’appareil.

L’exemple de code suivant montre l’implémentation du rappel d’événement EvtDriverDeviceAdd du pilote client.

NTSTATUS
MyUSBDriver_EvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = MyUSBDriver_CreateDevice(DeviceInit);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Pendant l’exécution, l’implémentation d’EvtDriverDeviceAdd utilise la macro PAGED_CODE pour case activée que la routine est appelée dans un environnement approprié pour du code paginable. Veillez à appeler la macro après avoir déclaré toutes vos variables ; sinon, la compilation échoue, car les fichiers sources générés sont des fichiers .c et non des fichiers .cpp.

L’implémentation EvtDriverDeviceAdd du pilote client appelle la fonction d’assistance MyUSBDriver_CreateDevice pour effectuer les tâches requises.

L’exemple de code suivant montre la fonction d’assistance MyUSBDriver_CreateDevice. MyUSBDriver_CreateDevice est défini dans Device.c.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
    WDF_OBJECT_ATTRIBUTES   deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    WDFDEVICE device;
    NTSTATUS status;

    PAGED_CODE();

    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    pnpPowerCallbacks.EvtDevicePrepareHardware = MyUSBDriver_EvtDevicePrepareHardware;
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);

    if (NT_SUCCESS(status)) {
        //
        // Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
        // inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
        // device.h header file. This function will do the type checking and return
        // the device context. If you pass a wrong object  handle
        // it will return NULL and assert if run under framework verifier mode.
        //
        deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
        deviceContext->PrivateDeviceData = 0;

        //
        // Create a device interface so that applications can find and talk
        // to us.
        //
        status = WdfDeviceCreateDeviceInterface(
            device,
            &GUID_DEVINTERFACE_MyUSBDriver_,
            NULL // ReferenceString
            );

        if (NT_SUCCESS(status)) {
            //
            // Initialize the I/O Package and any Queues
            //
            status = MyUSBDriver_QueueInitialize(device);
        }
    }

    return status;
}

EvtDriverDeviceAdd a deux paramètres : un handle vers l’objet de pilote d’infrastructure créé lors de l’appel précédent à DriverEntry et un pointeur vers une structure WDFDEVICE_INIT . L’infrastructure alloue la structure WDFDEVICE_INIT et lui transmet un pointeur afin que le pilote client puisse remplir la structure avec des paramètres d’initialisation pour l’objet d’appareil framework à créer.

Dans l’implémentation EvtDriverDeviceAdd , le pilote client doit effectuer les tâches suivantes :

  • Appelez la méthode WdfDeviceCreate pour récupérer un handle WDFDEVICE dans le nouvel objet d’appareil.

    La méthode WdfDeviceCreate permet à l’infrastructure de créer un objet d’appareil framework pour le FDO et de l’attacher au sommet de la pile d’appareils. Dans l’appel WdfDeviceCreate , le pilote client doit effectuer les tâches suivantes :

    Les composants Windows, PnP et les gestionnaires de l’alimentation, envoient des demandes liées aux appareils aux pilotes en réponse aux modifications de l’état PnP (par exemple, démarré, arrêté et supprimé) et de l’état d’alimentation (comme le fonctionnement ou la suspension). Pour les pilotes basés sur KMDF, l’infrastructure intercepte ces requêtes. Le pilote client peut être averti des demandes en inscrivant des routines de rappel appelées rappels d’événements d’alimentation PnP auprès de l’infrastructure, à l’aide de l’appel WdfDeviceCreate . Lorsque les composants Windows envoient des requêtes, l’infrastructure les gère et appelle le rappel d’événement d’alimentation PnP correspondant, si le pilote client s’est inscrit.

    L’une des routines de rappel d’événements d’alimentation PnP que le pilote client doit implémenter est EvtDevicePrepareHardware. Ce rappel d’événement est appelé lorsque le gestionnaire PnP démarre l’appareil. L’implémentation d’EvtDevicePrepareHardware est décrite dans la section suivante.

    Un contexte d’appareil (parfois appelé extension de périphérique) est une structure de données (définie par le pilote client) permettant de stocker des informations sur un objet de périphérique spécifique. Le pilote client transmet un pointeur vers son contexte de périphérique à l’infrastructure. L’infrastructure alloue un bloc de mémoire en fonction de la taille de la structure et stocke un pointeur vers cet emplacement de mémoire dans l’objet d’appareil du framework. Le pilote client peut utiliser le pointeur pour accéder aux informations et les stocker dans les membres du contexte de l’appareil. Pour plus d’informations sur les contextes d’appareil, consultez Framework Object Context Space.

    Une fois l’appel WdfDeviceCreate terminé, le pilote client reçoit un handle vers le nouvel objet d’appareil d’infrastructure, qui stocke un pointeur vers le bloc de mémoire alloué par l’infrastructure pour le contexte de périphérique. Le pilote client peut maintenant obtenir un pointeur vers le contexte de l’appareil en appelant la macro WdfObjectGet_DEVICE_CONTEXT .

  • Inscrivez un GUID d’interface de périphérique pour le pilote client en appelant la méthode WdfDeviceCreateDeviceInterface . Les applications peuvent communiquer avec le pilote à l’aide de ce GUID. La constante GUID est déclarée dans l’en-tête, public.h.

  • Configurez des files d’attente pour les transferts d’E/S vers l’appareil. Le code du modèle définit MyUSBDriver_QueueInitialize, une routine d’assistance pour la configuration des files d’attente, qui est décrite dans la section Code source de file d’attente .

Code source de l’appareil

L’objet device représente le instance de l’appareil pour lequel le pilote client est chargé en mémoire. Le code source complet de l’objet d’appareil se trouve dans Device.h et Device.c.

Device.h

Le fichier d’en-tête Device.h inclut public.h, qui contient des déclarations communes utilisées par tous les fichiers du projet.

Le bloc suivant dans Device.h déclare le contexte de l’appareil pour le pilote client.

typedef struct _DEVICE_CONTEXT
{
    WDFUSBDEVICE UsbDevice;
    ULONG PrivateDeviceData;  // just a placeholder

} DEVICE_CONTEXT, *PDEVICE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)

La structure DEVICE_CONTEXT est définie par le pilote client et stocke des informations sur un objet de périphérique d’infrastructure. Il est déclaré dans Device.h et contient deux membres : un handle pour l’objet périphérique cible USB d’une infrastructure (abordé plus loin) et un espace réservé. Cette structure sera développée dans les exercices ultérieurs.

Device.h inclut également la macro WDF_DECLARE_CONTEXT_TYPE , qui génère une fonction inline , WdfObjectGet_DEVICE_CONTEXT. Le pilote client peut appeler cette fonction pour récupérer un pointeur vers le bloc de mémoire à partir de l’objet de périphérique d’infrastructure.

La ligne de code suivante déclare MyUSBDriver_CreateDevice, une fonction d’assistance qui récupère un handle WDFUSBDEVICE dans l’objet périphérique cible USB.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    );

USBCreate prend un pointeur vers une structure WDFDEVICE_INIT comme paramètre. Il s’agit du même pointeur que celui qui a été passé par le framework quand il a appelé l’implémentation EvtDriverDeviceAdd du pilote client. Fondamentalement, MyUSBDriver_CreateDevice effectue les tâches de EvtDriverDeviceAdd. Le code source de l’implémentation d’EvtDriverDeviceAdd est décrit dans la section précédente.

La ligne suivante dans Device.h déclare une déclaration de type de rôle de fonction pour la routine de rappel d’événement EvtDevicePrepareHardware . Le rappel d’événement est implémenté par le pilote client et effectue des tâches telles que la configuration du périphérique USB.

EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;

Device.c

Le fichier d’implémentation Device.c contient le bloc de code suivant qui utilise alloc_text pragma pour spécifier que l’implémentation du pilote d’EvtDevicePrepareHardware est en mémoire paginable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif

Dans l’implémentation d’EvtDevicePrepareHardware, le pilote client effectue les tâches d’initialisation spécifiques à USB. Ces tâches incluent l’inscription du pilote client, l’initialisation d’objets cibles d’E/S spécifiques à USB et la sélection d’une configuration USB. Le tableau suivant présente les objets cibles d’E/S spécialisés fournis par le framework. Pour plus d’informations, consultez Cibles d’E/S USB.

Objet cible d’E/S USB (handle) Obtenir un handle en appelant... Description
Objet périphérique cible USB (WDFUSBDEVICE ) WdfUsbTargetDeviceCreateWithParameters Représente un périphérique USB et fournit des méthodes pour récupérer le descripteur d’appareil et envoyer des demandes de contrôle à l’appareil.
Objet d’interface cible USB (WDFUSBINTERFACE ) WdfUsbTargetDeviceGetInterface Représente une interface individuelle et fournit des méthodes qu’un pilote client peut appeler pour sélectionner un autre paramètre et récupérer des informations sur le paramètre.
Objet de canal cible USB (WDFUSBPIPE) WdfUsbInterfaceGetConfiguredPipe Représente un canal individuel pour un point de terminaison configuré dans le paramètre alternatif actuel pour une interface. La pile de pilotes USB sélectionne chaque interface dans la configuration sélectionnée et configure un canal de communication pour chaque point de terminaison au sein de l’interface. Dans la terminologie USB, ce canal de communication est appelé canal.

Cet exemple de code montre l’implémentation pour EvtDevicePrepareHardware.

NTSTATUS
MyUSBDriver_EvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourceList,
    _In_ WDFCMRESLIST ResourceListTranslated
    )
{
    NTSTATUS status;
    PDEVICE_CONTEXT pDeviceContext;
    WDF_USB_DEVICE_CREATE_CONFIG createParams;
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;

    UNREFERENCED_PARAMETER(ResourceList);
    UNREFERENCED_PARAMETER(ResourceListTranslated);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = STATUS_SUCCESS;
    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);

    if (pDeviceContext->UsbDevice == NULL) {

        //
        // Specifying a client contract version of 602 enables us to query for
        // and use the new capabilities of the USB driver stack for Windows 8.
        // It also implies that we conform to rules mentioned in the documentation
        // documentation for WdfUsbTargetDeviceCreateWithParameters.
        //
        WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createParams,
                                         USBD_CLIENT_CONTRACT_VERSION_602
                                         );

        status = WdfUsbTargetDeviceCreateWithParameters(Device,
                                                    &createParams,
                                                    WDF_NO_OBJECT_ATTRIBUTES,
                                                    &pDeviceContext->UsbDevice
                                                    );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceCreateWithParameters failed 0x%x", status);
            return status;
        }

        //
        // Select the first configuration of the device, using the first alternate
        // setting of each interface
        //
        WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&configParams,
                                                                     0,
                                                                     NULL
                                                                     );
        status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
                                                WDF_NO_OBJECT_ATTRIBUTES,
                                                &configParams
                                                );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceSelectConfig failed 0x%x", status);
            return status;
        }
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Voici un aperçu plus approfondi des tâches du pilote client implémentées par le code du modèle :

  1. Spécifie la version du contrat du pilote client en préparation de l’inscription auprès de la pile de pilotes USB sous-jacente, chargée par Windows.

    Windows peut charger la pile de pilotes USB 3.0 ou USB 2.0, selon le contrôleur hôte auquel le périphérique USB est attaché. La pile de pilotes USB 3.0 est une nouveauté dans Windows 8 et prend en charge plusieurs nouvelles fonctionnalités définies par la spécification USB 3.0, telles que la fonctionnalité de flux. La nouvelle pile de pilotes implémente également plusieurs améliorations, telles qu’un meilleur suivi et traitement des blocs de requête USB (URB), qui sont disponibles via un nouvel ensemble de routines URB. Un pilote client qui a l’intention d’utiliser ces fonctionnalités ou d’appeler les nouvelles routines doit spécifier la version du contrat USBD_CLIENT_CONTRACT_VERSION_602. Un pilote client USBD_CLIENT_CONTRACT_VERSION_602 doit respecter un certain ensemble de règles. Pour plus d’informations sur ces règles, consultez Meilleures pratiques : utilisation d’URBs.

    Pour spécifier la version du contrat, le pilote client doit initialiser une structure de WDF_USB_DEVICE_CREATE_CONFIG avec la version du contrat en appelant la macro WDF_USB_DEVICE_CREATE_CONFIG_INIT .

  2. Appelle la méthode WdfUsbTargetDeviceCreateWithParameters . La méthode nécessite un handle pour l’objet de périphérique d’infrastructure que le pilote client a obtenu précédemment en appelant WdfDeviceCreate dans l’implémentation du pilote d’EvtDriverDeviceAdd. La méthode WdfUsbTargetDeviceCreateWithParameters :

    • Inscrit le pilote client auprès de la pile de pilotes USB sous-jacente.
    • Récupère un handle WDFUSBDEVICE dans l’objet périphérique cible USB créé par le framework. Le code de modèle stocke le handle dans l’objet périphérique cible USB dans son contexte d’appareil. En utilisant ce handle, le pilote client peut obtenir des informations spécifiques à USB sur le périphérique.

    Vous devez appeler WdfUsbTargetDeviceCreate au lieu de WdfUsbTargetDeviceCreateWithParameters si :

    Ces pilotes ne sont pas tenus de spécifier une version de contrat client et doivent donc ignorer l’étape 1.

  3. Sélectionne une configuration USB.

    Dans le code du modèle, le pilote client sélectionne la configuration par défaut dans le périphérique USB. La configuration par défaut inclut la configuration 0 de l’appareil et le paramètre de remplacement 0 de chaque interface au sein de cette configuration.

    Pour sélectionner la configuration par défaut, le pilote client configure la structure WDF_USB_DEVICE_SELECT_CONFIG_PARAMS en appelant la fonction WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES . La fonction initialise le membre Type sur WdfUsbTargetDeviceSelectConfigTypeMultiInterface pour indiquer que si plusieurs interfaces sont disponibles, un autre paramètre doit être sélectionné dans chacune de ces interfaces. Étant donné que l’appel doit sélectionner la configuration par défaut, le pilote client spécifie NULL dans le paramètre SettingPairs et 0 dans le paramètre NumberInterfaces . Une fois l’opération terminée, le membre MultiInterface.NumberOfConfiguredInterfaces de WDF_USB_DEVICE_SELECT_CONFIG_PARAMS indique le nombre d’interfaces pour lesquelles l’autre paramètre 0 a été sélectionné. Les autres membres ne sont pas modifiés.

    Note Si le pilote client souhaite sélectionner d’autres paramètres que le paramètre par défaut, il doit créer un tableau de structures WDF_USB_INTERFACE_SETTING_PAIR . Chaque élément du tableau spécifie le numéro d’interface défini par l’appareil et l’index de l’autre paramètre à sélectionner. Ces informations sont stockées dans les descripteurs de configuration et d’interface de l’appareil qui peuvent être obtenus en appelant la méthode WdfUsbTargetDeviceRetrieveConfigDescriptor . Le pilote client doit ensuite appeler WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES et passer le tableau WDF_USB_INTERFACE_SETTING_PAIR au framework.

Code source de la file d’attente

L’objet de file d’attente d’infrastructure représente la file d’attente d’E/S d’un objet d’appareil d’infrastructure spécifique. Le code source complet de l’objet file d’attente se trouve dans Queue.h et Queue.c.

Queue.h

Déclare une routine de rappel d’événement pour l’événement déclenché par l’objet file d’attente de l’infrastructure.

Le premier bloc de Queue.h déclare un contexte de file d’attente.

typedef struct _QUEUE_CONTEXT {

    ULONG PrivateDeviceData;  // just a placeholder

} QUEUE_CONTEXT, *PQUEUE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)

Semblable à un contexte d’appareil, un contexte de file d’attente est une structure de données définie par le client pour stocker des informations sur une file d’attente particulière.

La ligne de code suivante déclare MyUSBDriver_QueueInitialize fonction, la fonction d’assistance qui crée et initialise l’objet de file d’attente du framework.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    );

L’exemple de code suivant déclare une déclaration de type de rôle de fonction pour la routine de rappel d’événement EvtIoDeviceControl . Le rappel d’événement est implémenté par le pilote client et est appelé lorsque l’infrastructure traite une demande de contrôle d’E/S d’appareil.

EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;

Queue.c

Le fichier d’implémentation, Queue.c, contient le bloc de code suivant qui utilise alloc_text pragma pour spécifier que l’implémentation du pilote de MyUSBDriver_QueueInitialize est en mémoire paginable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif

WDF fournit l’objet de file d’attente d’infrastructure pour gérer le flux de requête vers le pilote client. L’infrastructure crée un objet de file d’attente d’infrastructure lorsque le pilote client appelle la méthode WdfIoQueueCreate . Dans cet appel, le pilote client peut spécifier certaines options de configuration avant que l’infrastructure crée des files d’attente. Ces options indiquent si la file d’attente est gérée par l’alimentation, autorise les demandes de longueur nulle ou s’il s’agit de la file d’attente par défaut pour le pilote. Un objet de file d’attente d’infrastructure unique peut gérer plusieurs types de requêtes, tels que la lecture, l’écriture et le contrôle d’E/S d’appareil. Le pilote client peut spécifier des rappels d’événements pour chacune de ces demandes.

Le pilote client doit également spécifier le type de répartition. Le type de répartition d’un objet de file d’attente détermine la façon dont l’infrastructure envoie les requêtes au pilote client. Le mécanisme de remise peut être séquentiel, en parallèle ou par un mécanisme personnalisé défini par le pilote client. Pour une file d’attente séquentielle, une requête n’est pas remise tant que le pilote client n’a pas terminé la demande précédente. En mode de répartition parallèle, l’infrastructure transfère les demandes dès qu’elles arrivent du gestionnaire d’E/S. Cela signifie que le pilote client peut recevoir une requête lors du traitement d’une autre. Dans le mécanisme personnalisé, le client extrait manuellement la requête suivante de l’objet de file d’attente d’infrastructure lorsque le pilote est prêt à la traiter.

En règle générale, le pilote client doit configurer des files d’attente dans le rappel de l’événement EvtDriverDeviceAdd du pilote. Le code du modèle fournit la routine d’assistance, MyUSBDriver_QueueInitialize, qui initialise l’objet de file d’attente du framework.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    )
{
    WDFQUEUE queue;
    NTSTATUS status;
    WDF_IO_QUEUE_CONFIG    queueConfig;

    PAGED_CODE();
    
    //
    // Configure a default queue so that requests that are not
    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
    // other queues get dispatched here.
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
         &queueConfig,
        WdfIoQueueDispatchParallel
        );

    queueConfig.EvtIoDeviceControl = MyUSBDriver_EvtIoDeviceControl;

    status = WdfIoQueueCreate(
                 Device,
                 &queueConfig,
                 WDF_NO_OBJECT_ATTRIBUTES,
                 &queue
                 );

    if( !NT_SUCCESS(status) ) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
        return status;
    }

    return status;
}

Pour configurer des files d’attente, le pilote client effectue les tâches suivantes :

  1. Spécifie les options de configuration de la file d’attente dans une structure WDF_IO_QUEUE_CONFIG . Le code du modèle utilise la fonction WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE pour initialiser la structure. La fonction spécifie l’objet file d’attente comme objet de file d’attente par défaut, est gérée par l’alimentation et reçoit les requêtes en parallèle.
  2. Ajoute les rappels d’événements du pilote client pour les demandes d’E/S pour la file d’attente. Dans le modèle, le pilote client spécifie un pointeur vers son rappel d’événement pour une demande de contrôle d’E/S d’appareil.
  3. Appelle WdfIoQueueCreate pour récupérer un handle WDFQUEUE dans l’objet de file d’attente du framework créé par l’infrastructure.

Voici comment fonctionne le mécanisme de file d’attente. Pour communiquer avec l’appareil USB, une application ouvre d’abord un handle sur l’appareil en appelant les routines SetDixxx et CreateHandle. À l’aide de ce handle, l’application appelle la fonction DeviceIoControl avec un code de contrôle spécifique. Selon le type de code de contrôle, l’application peut spécifier des tampons d’entrée et de sortie dans cet appel. L’appel est finalement reçu par le Gestionnaire d’E/S, qui crée ensuite une requête (IRP) et la transfère au pilote client. L’infrastructure intercepte la requête, crée un objet de demande d’infrastructure et l’ajoute à l’objet de file d’attente du framework. Dans ce cas, étant donné que le pilote client a inscrit son rappel d’événement pour la demande de contrôle d’E/S de l’appareil, l’infrastructure appelle le rappel. En outre, étant donné que l’objet queue a été créé avec l’indicateur WdfIoQueueDispatchParallel, le rappel est appelé dès que la demande est ajoutée à la file d’attente.

VOID
MyUSBDriver_EvtIoDeviceControl(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, 
                TRACE_QUEUE, 
                "!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d", 
                Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode);

    WdfRequestComplete(Request, STATUS_SUCCESS);

    return;
}

Lorsque l’infrastructure appelle le rappel d’événements du pilote client, elle transmet un handle à l’objet de demande d’infrastructure qui contient la requête (et ses tampons d’entrée et de sortie) envoyées par l’application. En outre, il envoie un handle à l’objet de file d’attente du framework qui contient la demande. Dans le rappel d’événement, le pilote client traite la demande en fonction des besoins. Le code du modèle termine simplement la demande. Le pilote client peut effectuer des tâches plus impliquées. Par instance, si une application demande certaines informations sur l’appareil, dans le rappel d’événement, le pilote client peut créer une demande de contrôle USB et l’envoyer à la pile de pilotes USB pour récupérer les informations d’appareil demandées. Les demandes de contrôle USB sont abordées dans Transfert de contrôle USB.