Partager via


Comment enregistrer un appareil composite ?

Cet article décrit comment un pilote d'un appareil multifonction USB, appelé pilote composite, peut enregistrer et désenregistrer l'appareil composite avec la pile de pilotes USB sous-jacente. Windows charge le pilote fourni par Microsoft, Usbccgp.sys, comme pilote composite par défaut. La procédure décrite dans cet article s'applique à un pilote composite personnalisé basé sur le modèle de pilote Windows (WDM) qui remplace Usbccgp.sys.

Un appareil USB (Universal Serial Bus) peut offrir plusieurs fonctions actives simultanément. Ces appareils multifonctions sont également connus sous le nom d'appareils composites. Par exemple, un appareil composite peut définir une fonction pour le clavier et une autre pour la souris. Le pilote composite énumère les fonctions de l'appareil. Le pilote composite peut gérer lui-même ces fonctions dans un modèle monolithique ou créer des objets d'appareil physique (PDO) pour chacune des fonctions. Les pilotes de fonctions USB, tels que le pilote de clavier et le pilote de souris, gèrent leurs PDO individuels respectifs.

La spécification USB 3.0 définit la fonctionnalité de suspension de fonction et de réveil à distance permettant à des fonctions individuelles d'entrer et de sortir d'états de faible consommation sans affecter l'état d'alimentation d'autres fonctions ou de l'appareil entier. Pour plus d'informations sur cette fonctionnalité, voir Comment implémenter la suspension de fonction dans un pilote composite.

Pour utiliser cette fonctionnalité, le pilote composite doit enregistrer l'appareil auprès de la pile de pilotes USB sous-jacente. Comme la fonctionnalité s'applique aux appareils USB 3.0, le pilote composite doit s'assurer que la pile sous-jacente prend en charge la version USBD_INTERFACE_VERSION_602. Par le biais de la requête d'enregistrement, le pilote composite :

  • Informe la pile de pilotes USB sous-jacente que le pilote est responsable de l'envoi d'une requête pour armer une fonction de réveil à distance. La pile du pilote USB traite la requête de réveil à distance, qui envoie les requêtes de protocole nécessaires à l'appareil.
  • Il obtient une liste de poignées de fonction (une par fonction) attribuées par la pile du pilote USB. Le pilote composite peut alors utiliser un handle de fonction dans sa requête de réveil à distance de la fonction associée au handle.

Généralement, un pilote composite envoie la requête d'enregistrement dans la routine AddDevice ou start-device du pilote pour gérer IRP_MN_START_DEVICE. Le pilote composite libère alors les ressources allouées pour l'enregistrement dans les routines de déchargement du pilote, telles que la routine stop-device (IRP_MN_STOP_DEVICE) ou remove-device (IRP_MN_REMOVE_DEVICE).

Prérequis

Avant d'envoyer la requête d'enregistrement, assurez-vous que :

  • Vous disposez du nombre de fonctions de l'appareil. Ce nombre peut être dérivé des descripteurs récupérés par la requête get-configuration.
  • Vous avez obtenu une poignée USBD lors d'un appel précédent à USBD_CreateHandle.
  • La pile de pilotes USB sous-jacente prend en charge les appareils USB 3.0. Pour ce faire, appelez USBD_IsInterfaceVersionSupported et indiquez USBD_INTERFACE_VERSION_602 comme version à vérifier.

Pour un exemple de code, voir Comment implémenter la fonction Suspend dans un pilote composite.

Enregistrer un appareil composite

La procédure suivante décrit comment construire et envoyer une requête d'enregistrement pour associer un pilote composite à la pile de pilotes USB.

  1. Allouez une structure COMPOSITE_DEVICE_CAPABILITIES et initialisez-la en appelant la macro COMPOSITE_DEVICE_CAPABILITIES_INIT.

  2. Attribuez la valeur 1 au membre CapabilityFunctionSuspend de COMPOSITE_DEVICE_CAPABILITIES.

  3. Allouez une structure REGISTER_COMPOSITE_DEVICE et initialisez-la en appelant la routine USBD_BuildRegisterCompositeDevice. Dans l'appel, spécifiez la poignée USBD, la structure COMPOSITE_DEVICE_CAPABILITIES initialisée et le nombre de fonctions.

  4. Allouez un paquet de requêtes d'E/S (IRP) en appelant IoAllocateIrp et obtenez un pointeur sur le premier emplacement de la pile de l'IRP (IO_STACK_LOCATION) en appelant IoGetNextIrpStackLocation.

  5. Allouez de la mémoire pour un tampon suffisamment grand pour contenir un tableau de poignées de fonction (USBD_FUNCTION_HANDLE). Le nombre d'éléments du tableau doit correspondre au nombre de PDO.

  6. Construisez la requête en définissant les membres suivants de IO_STACK_LOCATION :

    • Spécifiez le type de requête en définissant Parameters.DeviceIoControl.IoControlCode sur IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE.
    • Spécifiez le paramètre d'entrée en fixant Parameters.Others.Argument1 à l'adresse de la structure REGISTER_COMPOSITE_DEVICE initialisée.
    • Spécifiez le paramètre de sortie en définissant AssociatedIrp.SystemBuffer sur le tampon alloué à l'étape 5.
  7. Appelez IoCallDriver pour envoyer la requête en passant l'IRP à l'emplacement suivant de la pile.

Une fois l'opération terminée, inspectez le tableau des poignées de fonction renvoyées par la pile du pilote USB. Vous pouvez stocker le tableau dans le contexte de l'appareil du pilote pour une utilisation ultérieure.

L'exemple de code suivant montre comment créer et envoyer une requête d'enregistrement. L'exemple suppose que le pilote composite stocke le nombre de fonctions obtenu précédemment et le handle USBD dans le contexte de l'appareil du pilote.

VOID  RegisterCompositeDriver(PPARENT_FDO_EXT parentFdoExt)
{
    PIRP                            irp;
    REGISTER_COMPOSITE_DRIVER       registerInfo;
    COMPOSITE_DRIVER_CAPABILITIES   capabilities;
    NTSTATUS                        status;
    PVOID                           buffer;
    ULONG                           bufSize;
    PIO_STACK_LOCATION              nextSp;

    buffer = NULL;

    COMPOSITE_DRIVER_CAPABILITIES_INIT(&capabilities);
    capabilities.CapabilityFunctionSuspend = 1;

    USBD_BuildRegisterCompositeDriver(parentFdoExt->usbdHandle,
        capabilities,
        parentFdoExt->numFunctions,
        &registerInfo);

    irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (irp == NULL)
    {
        //IoAllocateIrp failed.
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto ExitRegisterCompositeDriver;
    }

    nextSp = IoGetNextIrpStackLocation(irp);

    bufSize = parentFdoExt->numFunctions * sizeof(USBD_FUNCTION_HANDLE);

    buffer = ExAllocatePoolWithTag (NonPagedPool, bufSize, POOL_TAG);

    if (buffer == NULL)
    {
        // Memory alloc for function-handles failed.
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto ExitRegisterCompositeDriver;
    }

    nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DRIVER;

    //Set the input buffer in Argument1
    nextSp->Parameters.Others.Argument1 = &registerInfo;

    //Set the output buffer in SystemBuffer field for USBD_FUNCTION_HANDLE.
    irp->AssociatedIrp.SystemBuffer = buffer;

    // Pass the IRP down to the next device object in the stack. Not shown.
    status = CallNextDriverSync(parentFdoExt, irp, FALSE);

    if (!NT_SUCCESS(status))
    {
        //Failed to register the composite driver.
        goto ExitRegisterCompositeDriver;
    }

    parentFdoExt->compositeDriverRegistered = TRUE;

    parentFdoExt->functionHandleArray = (PUSBD_FUNCTION_HANDLE) buffer;

End:
    if (!NT_SUCCESS(status))
    {
        if (buffer != NULL)
        {
            ExFreePoolWithTag (buffer, POOL_TAG);
            buffer = NULL;
        }
    }

    if (irp != NULL)
    {
        IoFreeIrp(irp);
        irp = NULL;
    }

    return;
}

Annuler l'enregistrement d'un appareil composite

  1. Allouez une IRP en appelant IoAllocateIrp et obtenez un pointeur sur le premier emplacement de la pile de l'IRP (IO_STACK_LOCATION) en appelant IoGetNextIrpStackLocation.
  2. Construisez la requête en définissant le membre Parameters.DeviceIoControl.IoControlCode de IO_STACK_LOCATION à IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DEVICE.
  3. Appelez IoCallDriver pour envoyer la requête en passant l'IRP à l'emplacement suivant de la pile.

La requête IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DEVICE est envoyée une fois par le pilote composite dans le contexte de la routine remove-device. L'objectif de la requête est de supprimer l'association entre la pile du pilote USB, le pilote composite et sa fonction énumérée. La requête nettoie également toutes les ressources qui ont été créées pour maintenir cette association et tous les handles de fonction qui ont été renvoyés dans la requête d'enregistrement précédente.

L'exemple de code suivant montre comment créer et envoyer une requête de désenregistrement de l'appareil composite. L'exemple suppose que le pilote composite a été précédemment enregistré par le biais d'une requête d'enregistrement comme décrit précédemment dans cet article.

VOID  UnregisterCompositeDriver(
    PPARENT_FDO_EXT parentFdoExt )
{
    PIRP                irp;
    PIO_STACK_LOCATION  nextSp;
    NTSTATUS            status;

    PAGED_CODE();

    irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (irp == NULL)
    {
        //IoAllocateIrp failed.
        status = STATUS_INSUFFICIENT_RESOURCES;
        return;
    }

    nextSp = IoGetNextIrpStackLocation(irp);

    nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DRIVER;

    // Pass the IRP down to the next device object in the stack. Not shown.
    status = CallNextDriverSync(parentFdoExt, irp, FALSE);

    if (NT_SUCCESS(status))
    {
        parentFdoExt->compositeDriverRegistered = FALSE;
    }

    IoFreeIrp(irp);

    return;
}