Partager via


Compréhension 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 en mode utilisateur USB inclus dans 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 pilote représente l’instance du pilote client après que Windows charge 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 de 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 de méthodes et de 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 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 EvtCleanupCallback et EvtDriverDeviceAdd. Toutes ces routines sont implémentées par le pilote. Les types de rôles aident static Driver Verifier (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énement 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 de 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 de point d’entrée de votre pilote, DriverEntry et transmet un pointeur à la structure. Étant donné que Windows recherche la routine par 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 dans 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 de Registre pour le pilote. Le paramètre RegistryPath représente le chemin spécifique au pilote dans le Registre.

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

  • Alloue des ressources globales requises pendant la durée de vie du pilote. Par exemple, dans le code du modèle, le pilote client alloue des ressources requises pour le 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 à l’objet de pilote d’infrastructure.

    Une fois que le pilote client appelle 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 pilote WDF).

    Notez que l’objet de pilote de framework est différent de l’objet pilote Windows décrit par DRIVER_OBJECT. À tout moment, le pilote client peut obtenir un pointeur vers la structure DRIVER_OBJECT Windowsà l’aide du handle WDFDRIVER et appeler la méthode WdfGetDriver.

Après l’appel WdfDriverCreate , le framework partenaires avec le 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. En procédant ainsi, 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 poursuit 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).

D’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 l’implémentation d’EvtDriverDeviceAdd dans la structure WDF_DRIVER_CONFIG et le rappel d’é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 qui a été activé dans DriverEntry.

L’exemple de code suivant montre l’implémentation de rappel d’é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 que l’appareil est 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’objet PDO au nœud d’appareil. Le nœud d’appareil se trouve dans une formation de pile, où l’objet PDO se trouve en bas. Chaque pile doit avoir une PDO et peut avoir des objets d’appareil de filtre (DOS de filtre) et un objet d’appareil de fonction (FDO) au-dessus de celui-ci. Pour plus d’informations, consultez Nœuds d’appareil et piles d’appareils.

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

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

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

Pour un pilote client basé sur KMDF, l’infrastructure effectue ces tâches pour le compte du pilote client. Pour représenter l’objet FDO de l’appareil, l’infrastructure crée un objet d’appareil framework. Toutefois, le pilote client peut spécifier certains paramètres d’initialisation utilisés par l’infrastructure pour configurer le nouvel objet. Cette opportunité 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 début de la pile d’appareils, l’infrastructure fournit au pilote client un handle WDFDEVICE à l’objet d’appareil de l’infrastructure. À 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 de 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 vérifier que la routine est appelée dans un environnement approprié pour le 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 .cpp fichiers.

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 de framework créé dans l’appel précédent à DriverEntry et un pointeur vers une structure WDFDEVICE_INIT. L’infrastructure alloue la structure WDFDEVICE_INIT et la transmet à un pointeur afin que le pilote client puisse remplir la structure avec des paramètres d’initialisation pour que l’objet d’appareil framework soit créé.

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 vers le nouvel objet d’appareil.

    La méthode WdfDeviceCreate provoque la création d’un objet d’appareil framework pour l’objet FDO et l’attache en haut de la pile d’appareils. Dans l’appel WdfDeviceCreate , le pilote client doit effectuer ces tâches :

    Composants Windows, PnP et gestionnaires d’alimentation, envoyez des demandes liées aux appareils aux pilotes en réponse aux modifications apportées à l’état PnP (par exemple, démarré, arrêté et supprimé) et à l’état d’alimentation (par exemple, 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 PnP power avec l’infrastructure, à l’aide de l’appel WdfDeviceCreate. Lorsque les composants Windows envoient des demandes, l’infrastructure les gère et appelle le rappel d’événement d’alimentation PnP correspondant, si le pilote client a enregistré.

    L’une des routines de rappel d’événements PnP power 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 d’appareil) est une structure de données (définie par le pilote client) pour stocker des informations sur un objet d’appareil spécifique. Le pilote client passe un pointeur vers son contexte d’appareil vers 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 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 framework, qui stocke un pointeur vers le bloc de mémoire alloué par l’infrastructure pour le contexte de l’appareil. 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 d’appareil 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 les files d’attente pour les transferts d’E/S vers l’appareil. Le code de 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 la file d’attente.

Code source de l’appareil

L’objet appareil représente l’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 les déclarations courantes 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 d’appareil framework. Il est déclaré dans Device.h et contient deux membres : un handle à l’objet périphérique cible USB d’un framework (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 de l’objet d’appareil framework.

La ligne de code suivante déclare MyUSBDriver_CreateDevice, une fonction d’assistance qui récupère un handle WDFUSBDEVICE sur 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 passé par l’infrastructure lorsqu’il a appelé l’implémentation EvtDriverDeviceAdd du pilote client. En fait, MyUSBDriver_CreateDevice effectue les tâches d’EvtDriverDeviceAdd. Le code source de l’implémentation EvtDriverDeviceAdd est abordé 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 des objets cibles d’E/S spécifiques à USB et la sélection d’une configuration USB. Le tableau suivant montre les objets cibles d’E/S spécialisés fournis par l’infrastructure. 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 d’une interface. La pile de pilotes USB sélectionne chaque interface dans la configuration sélectionnée et configure un canal de communication sur 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 d’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, comme implémentées par le code du modèle :

  1. Spécifie la version du contrat du pilote client en préparation pour s’inscrire 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 nouvelle 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 que le suivi et le 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’URB.

    Pour spécifier la version du contrat, le pilote client doit initialiser une structure 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 d’appareil framework que le pilote client obtenu précédemment en appelant WdfDeviceCreate dans l’implémentation du pilote d’EvtDriverDeviceAdd. Méthode WdfUsbTargetDeviceCreateWithParameters :

    • Inscrit le pilote client avec la pile de pilotes USB sous-jacente.
    • Récupère un handle WDFUSBDEVICE sur l’objet d’appareil cible USB créé par l’infrastructure. Le code de modèle stocke le handle sur 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 l’appareil.

    Vous devez appeler WdfUsbTargetDeviceCreate au lieu de WdfUsbTargetDeviceCreateWithParameters si :

    Ces pilotes ne sont pas requis pour 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 Configuration 0 de l’appareil et le paramètre 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 à WdfUsbTargetDeviceSelectConfigTypeMultiInterface pour indiquer que si plusieurs interfaces sont disponibles, un autre paramètre dans chacune de ces interfaces doit être sélectionné. É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 un autre paramètre 0 a été sélectionné. Les autres membres ne sont pas modifiés.

    Notez que si le pilote client souhaite sélectionner d’autres paramètres que le paramètre par défaut, le pilote 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 du paramètre de remplacement à 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 à l’infrastructure.

Code source de file d’attente

L’objet file d’attente d’infrastructure représente la file d’attente d’E/S d’un objet d’appareil framework 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 du framework.

Le premier bloc dans 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)

Comme pour 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 du framework 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 incluent si la file d’attente est gérée par l’alimentation, autorise les requêtes de longueur nulle ou est la file d’attente par défaut pour le pilote. Un objet file d’attente d’infrastructure unique peut gérer plusieurs types de requêtes, tels que le contrôle d’E/S de lecture, d’écriture et 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 distribution. Le type de répartition d’un objet file d’attente détermine la façon dont l’infrastructure remet des 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 requête 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 demande 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 de l’infrastructure lorsque le pilote est prêt à le traiter.

En règle générale, le pilote client doit configurer des files d’attente dans le rappel d’événement EvtDriverDeviceAdd du pilote. Le code de 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 de 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 file d’attente par défaut, est géré par l’alimentation et reçoit des demandes 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 vers l’objet de file d’attente d’infrastructure 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 à 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 mémoires 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 demande (IRP) et le transfère au pilote client. L’infrastructure intercepte la requête, crée un objet de requête d’infrastructure et l’ajoute à l’objet de file d’attente de l’infrastructure. 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 file d’attente a été créé avec l’indicateur WdfIoQueueDispatchParallel, le rappel est appelé dès que la requête 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énement du pilote client, elle transmet un handle à l’objet de requête d’infrastructure qui contient la requête (et ses mémoires tampons d’entrée et de sortie) envoyées par l’application. En outre, il envoie un handle à l’objet de file d’attente framework qui contient la requête. Dans le rappel d’événement, le pilote client traite la requête en fonction des besoins. Le code du modèle termine simplement la requête. Le pilote client peut effectuer des tâches plus impliquées. Par exemple, si une application demande certaines informations d’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 demandées sur l’appareil. Les demandes de contrôle USB sont abordées dans le transfert de contrôle USB.