Partager via


Comment implémenter l’interruption de fonction dans un pilote composite

Cet article fournit une vue d’ensemble des fonctionnalités de suspension de fonction et de mise en éveil à distance des fonctions pour les appareils multi-fonctions USB (Universal Serial Bus) 3.0 (appareils composites). Dans cet article, vous allez découvrir comment implémenter ces fonctionnalités dans un pilote qui contrôle un appareil composite. L’article s’applique aux pilotes composites qui remplacent Usbccgp.sys.

La spécification USB (Universal Serial Bus) 3.0 définit une nouvelle fonctionnalité appelée suspension de fonction. La fonctionnalité permet à une fonction individuelle d’un appareil composite d’entrer dans un état de faible consommation, indépendamment des autres fonctions. Considérez un appareil composite qui définit une fonction pour le clavier et une autre fonction pour la souris. L’utilisateur maintient la fonction clavier en état de fonctionnement, mais ne déplace pas la souris pendant un certain temps. Le pilote client de la souris peut détecter l’état d’inactivité de la fonction et envoyer la fonction à l’état de suspension pendant que la fonction clavier reste en état de fonctionnement.

L’ensemble de l’appareil peut passer à l’état de suspension, quel que soit l’état d’alimentation de n’importe quelle fonction au sein de l’appareil. Si une fonction particulière et l’ensemble de l’appareil entrent en état de suspension, l’état de suspension de la fonction est conservé pendant que l’appareil est à l’état de suspension, et tout au long des processus d’entrée et de sortie de suspension de l’appareil.

Comme pour la fonctionnalité de mise en éveil à distance d’un appareil USB 2.0 (voir Mise en éveil à distance des périphériques USB), une fonction individuelle d’un appareil composite USB 3.0 peut se réveiller à partir d’un état de faible consommation sans affecter les états d’alimentation d’autres fonctions. Cette fonctionnalité est appelée veille à distance de la fonction. La fonctionnalité est explicitement activée par l’hôte en envoyant une demande de protocole qui définit les bits de mise en éveil à distance dans le microprogramme de l’appareil. Ce processus est appelé arming the function for remote wake-up. Pour plus d’informations sur les bits liés à la veille à distance, consultez la figure 9-6 de la spécification USB officielle.

Si une fonction est armée pour la mise en éveil à distance, la fonction (en état de suspension) conserve suffisamment d’alimentation pour générer un signal de reprise de la mise en éveil lorsqu’un événement utilisateur se produit sur l’appareil physique. À la suite de ce signal de reprise, le pilote client peut ensuite quitter l’état de suspension de la fonction associée. Dans l’exemple de la fonction souris dans l’appareil composite, lorsque l’utilisateur agite la souris à l’état inactif, la fonction de souris envoie un signal de reprise à l’hôte. Sur l’hôte, la pile de pilotes USB détecte quelle fonction s’est réveillée et propage la notification au pilote client de la fonction correspondante. Le pilote client peut ensuite réveiller la fonction et entrer en état de fonctionnement.

Pour le pilote client, les étapes d’envoi d’une fonction à l’état de suspension et de réveil de la fonction sont similaires à celles d’un pilote de périphérique à fonction unique qui envoie l’ensemble de l’appareil à l’état de suspension. La procédure suivante récapitule ces étapes.

  1. Détectez quand la fonction associée est à l’état inactif.
  2. Envoyer un paquet de demande d’E/S inactif (IRP).
  3. Envoyez une demande pour armer sa fonction pour une mise en éveil à distance en envoyant un paquet de demande d’E/S d’attente (IRP).
  4. Faites passer la fonction à un état d’alimentation faible en envoyant des IRPs d’alimentation Dx (D2 ou D3).

Pour plus d’informations sur les étapes précédentes, consultez « Envoi d’un IRP de demande d’inactivité USB » dans La suspension sélective USB. Un pilote composite crée un objet de périphérique physique (PDO) pour chaque fonction de l’appareil composite et gère les demandes d’alimentation envoyées par le pilote client (le FDO de la pile de périphériques de fonction). Pour qu’un pilote client entre et quitte correctement l’état de suspension de sa fonction, le pilote composite doit prendre en charge les fonctionnalités d’interruption de fonction et de mise en éveil à distance, et traiter les demandes d’alimentation reçues.

Dans Windows 8, la pile de pilotes USB pour les appareils USB 3.0 prend en charge ces fonctionnalités. En outre, l’implémentation de la suspension de fonction et de la mise en éveil à distance de la fonction a été ajoutée au pilote parent générique USB fourni par Microsoft (Usbccgp.sys), qui est le pilote composite par défaut De Windows. Si vous écrivez un pilote composite personnalisé, votre pilote doit gérer les requêtes liées à la suspension de fonction et aux demandes de mise en éveil à distance, conformément à la procédure suivante.

Étape 1 : Déterminer si la pile de pilotes USB prend en charge la suspension de la fonction

Dans la routine de périphérique de démarrage (IRP_MN_START_DEVICE) de votre pilote composite, effectuez les étapes suivantes :

  1. Appelez la routine USBD_QueryUsbCapability pour déterminer si la pile de pilotes USB sous-jacente prend en charge la fonctionnalité d’interruption de fonction. L’appel nécessite un handle USBD valide que vous avez obtenu lors de votre appel précédent au USBD_CreateHandle routine.

Un appel réussi à USBD_QueryUsbCapability détermine si la pile de pilotes USB sous-jacente prend en charge l’interruption de fonction. L’appel peut retourner un code d’erreur indiquant que la pile de pilotes USB ne prend pas en charge la suspension de la fonction ou que l’appareil attaché n’est pas un périphérique multi-fonction USB 3.0.

  1. Si l’appel USBD_QueryUsbCapability indique que la suspension de fonction est prise en charge, inscrivez le périphérique composite avec la pile de pilotes USB sous-jacente. Pour inscrire l’appareil composite, vous devez envoyer une demande de contrôle d’E /S IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE. Pour plus d’informations sur cette demande, consultez Comment inscrire un appareil composite.

La demande d’inscription utilise la structure REGISTER_COMPOSITE_DEVICE pour spécifier ces informations sur le pilote composite. Veillez à définir CapabilityFunctionSuspend sur 1 pour indiquer que le pilote composite prend en charge l’interruption de fonction.

Pour obtenir un exemple de code qui montre comment déterminer si la pile de pilotes USB prend en charge l’interruption de fonction, consultez USBD_QueryUsbCapability.

Étape 2 : Gérer l’IRP inactif

Le pilote client peut envoyer un IRP inactif (voir IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION). La demande est envoyée après que le pilote client a détecté un état d’inactivité pour la fonction. L’IRP contient un pointeur vers la routine de fin de rappel (appelée rappel inactif) implémenté par le pilote client. Dans le rappel inactif, le client effectue des tâches, telles que l’annulation des transferts d’E/S en attente, juste avant d’envoyer la fonction à l’état de suspension.

Notes

Le mécanisme IRP inactif est facultatif pour les pilotes clients d’appareils USB 3.0. Toutefois, la plupart des pilotes clients sont écrits pour prendre en charge les périphériques USB 2.0 et USB 3.0. Pour prendre en charge les périphériques USB 2.0, le pilote doit envoyer l’IRP inactif, car le pilote composite s’appuie sur cet IRP pour suivre l’état d’alimentation de chaque fonction. Si toutes les fonctions sont inactives, le pilote composite envoie l’ensemble de l’appareil à l’état de suspension.

Lors de la réception de l’IRP inactif du pilote client, le pilote composite doit appeler immédiatement le rappel d’inactivité pour informer le pilote client que le pilote client peut envoyer la fonction à l’état de suspension.

Étape 3 : Envoyer une demande de notification de mise en éveil à distance

Le pilote client peut envoyer une demande pour armer sa fonction pour une mise en éveil à distance en envoyant un IRP IRP_MJ_POWER avec le code de fonction mineur défini sur IRP_MN_WAIT_WAKE (IRP d’attente). Le pilote client envoie cette demande uniquement si le pilote souhaite entrer un état opérationnel à la suite d’un événement utilisateur.

À la réception de l’IRP de veille d’attente, le pilote composite doit envoyer la demande de contrôle d’E /S IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION à la pile de pilotes USB. La requête permet à la pile de pilotes USB d’avertir le pilote composite lorsque la pile reçoit la notification concernant le signal de reprise. Le IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION utilise la structure REQUEST_REMOTE_WAKE_NOTIFICATION pour spécifier les paramètres de requête. L’une des valeurs que le pilote composite doit spécifier est la poignée de fonction de la fonction qui est armée pour la mise en éveil à distance. Pilote composite obtenu qui gère dans une demande précédente d’inscription du périphérique composite auprès de la pile de pilotes USB. Pour plus d’informations sur les demandes d’inscription de pilotes composites, consultez Guide pratique pour inscrire un appareil composite.

Dans l’IRP de la demande, le pilote composite fournit un pointeur vers une routine d’achèvement (mise en éveil à distance), qui est implémentée par le pilote composite.

L’exemple de code suivant montre comment envoyer une demande de mise en éveil à distance.

/*++

Description:
    This routine sends a IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION request
    to the USB driver stack. The IOCTL is completed by the USB driver stack
    when the function wakes up from sleep.

    Parameters:
    parentFdoExt: The device context associated with the FDO for the
    composite driver.

    functionPdoExt: The device context associated with the PDO (created by
    the composite driver) for the client driver.
--*/

VOID
SendRequestForRemoteWakeNotification(
    __inout PPARENT_FDO_EXT parentFdoExt,
    __inout PFUNCTION_PDO_EXT functionPdoExt
)

{
    PIRP                                irp;
    REQUEST_REMOTE_WAKE_NOTIFICATION    remoteWake;
    PIO_STACK_LOCATION                  nextStack;
    NTSTATUS                            status;

    // Allocate an IRP
    irp =  IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (irp)
    {

        //Initialize the USBDEVICE_REMOTE_WAKE_NOTIFICATION structure
        remoteWake.Version = 0;
        remoteWake.Size = sizeof(REQUEST_REMOTE_WAKE_NOTIFICATION);
        remoteWake.UsbdFunctionHandle = functionPdoExt->functionHandle;
        remoteWake.Interface = functionPdoExt->baseInterfaceNumber;

        nextStack = IoGetNextIrpStackLocation(irp);

        nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
        nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION;

        nextStack->Parameters.Others.Argument1 = &remoteWake;

        // Caller's completion routine will free the IRP when it completes.

        SetCompletionRoutine(functionPdoExt->debugLog,
                             parentFdoExt->fdo,
                             irp,
                             CompletionRemoteWakeNotication,
                             (PVOID)functionPdoExt,
                             TRUE, TRUE, TRUE);

        // Pass the IRP
        IoCallDriver(parentFdoExt->topDevObj, irp);

    }

    return;
}

La demande de IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION est effectuée par la pile de pilotes USB pendant le processus de mise en éveil lorsqu’elle reçoit une notification concernant le signal de reprise. Pendant ce temps, la pile de pilotes USB appelle également la routine de mise en éveil à distance.

Le pilote composite doit maintenir l’IRP de veille d’attente en attente et le mettre en file d’attente pour un traitement ultérieur. Le pilote composite doit effectuer cette IRP lorsque la routine de mise en éveil à distance du pilote est appelée par la pile de pilotes USB.

Étape 4 : Envoyer une demande pour armer la fonction pour une mise en éveil à distance

Pour envoyer la fonction à un état de faible consommation, le pilote client envoie une IRP_MN_SET_POWER IRP avec la demande de modification de l’état d’alimentation du périphérique WDM (Windows Driver Model) sur D2 ou D3. En règle générale, le pilote client envoie des IRP D2 si le pilote a envoyé un IRP de veille d’attente plus tôt pour demander une mise en éveil à distance. Sinon, le pilote client envoie des IRP D3 .

Lors de la réception de l’IRP D2 , le pilote composite doit d’abord déterminer si un IRP de veille d’attente est en attente à partir d’une demande précédente envoyée par le pilote client. Si cette IRP est en attente, le pilote composite doit armer la fonction pour le réveil à distance. Pour ce faire, le pilote composite doit envoyer une demande de contrôle SET_FEATURE à la première interface de la fonction, pour permettre à l’appareil d’envoyer un signal de reprise. Pour envoyer la demande de contrôle, allouez une structure URB en appelant la routine USBD_UrbAllocate et appelez la macro UsbBuildFeatureRequest pour mettre en forme l’URB pour une demande de SET_FEATURE. Dans l’appel, spécifiez URB_FUNCTION_SET_FEATURE_TO_INTERFACE comme code d’opération et le USB_FEATURE_FUNCTION_SUSPEND comme sélecteur de fonctionnalité. Dans le paramètre Index , définissez bit 1 de l’octet le plus significatif. Cette valeur est copiée dans le champ wIndex dans le paquet d’installation du transfert.

L’exemple suivant montre comment envoyer une demande de contrôle SET_FEATURE.

/*++

Routine Description:

Sends a SET_FEATURE for REMOTE_WAKEUP to the device using a standard control request.

Parameters:
parentFdoExt: The device context associated with the FDO for the
composite driver.

functionPdoExt: The device context associated with the PDO (created by
the composite driver) for the client driver.

Returns:

NTSTATUS code.

--*/
VOID
    NTSTATUS SendSetFeatureControlRequestToSuspend(
    __inout PPARENT_FDO_EXT parentFdoExt,
    __inout PFUNCTION_PDO_EXT functionPdoExt,
    )

{
    PURB                            urb
    PIRP                            irp;
    PIO_STACK_LOCATION              nextStack;
    NTSTATUS                        status;

    status = USBD_UrbAllocate(parentFdoExt->usbdHandle, &urb);

    if (!NT_SUCCESS(status))
    {
        //USBD_UrbAllocate failed.
        goto Exit;
    }

    //Format the URB structure.
    UsbBuildFeatureRequest (
        urb,
        URB_FUNCTION_SET_FEATURE_TO_INTERFACE, // Operation code
        USB_FEATURE_FUNCTION_SUSPEND,          // feature selector
        functionPdoExt->firstInterface,           // first interface of the function
        NULL);

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

    if (!irp)
    {
        // IoAllocateIrp failed.
        status = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;
    }

    nextStack = IoGetNextIrpStackLocation(irp);

    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;

    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;

    //  Attach the URB to the IRP.
    USBD_AssignUrbToIoStackLocation(nextStack, (PURB)urb);

    // Caller's completion routine will free the IRP when it completes.
    SetCompletionRoutine(functionPdoExt->debugLog,
        parentFdoExt->fdo,
        irp,
        CompletionForSuspendControlRequest,
        (PVOID)functionPdoExt,
        TRUE, TRUE, TRUE);


    // Pass the IRP
    IoCallDriver(parentFdoExt->topDevObj, irp);


Exit:
    if (urb)
    {
        USBD_UrbFree( parentFdoExt->usbdHandle, urb);
    }

    return status;

}

Le pilote composite envoie ensuite l’IRP D2 à la pile de pilotes USB. Si toutes les autres fonctions sont à l’état de suspension, la pile de pilotes USB suspend le port en manipulant certains registres de ports sur le contrôleur.

Remarques

Dans l’exemple de fonction de souris, étant donné que la fonctionnalité de mise en éveil à distance est activée (voir l’étape 4), la fonction de souris génère un signal de reprise sur le câble amont au contrôleur hôte lorsque l’utilisateur agite la souris. Le contrôleur avertit ensuite la pile de pilotes USB en envoyant un paquet de notification contenant des informations sur la fonction qui s’est réveillée. Pour plus d’informations sur la notification d’éveil de la fonction, consultez la figure 8-17 de la spécification USB 3.0.

À la réception du paquet de notification, la pile de pilotes USB termine la demande de IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION en attente (voir l’étape 3) et appelle la routine de rappel d’achèvement (mise en éveil à distance) spécifiée dans la demande et implémentée par le pilote composite. Lorsque la notification atteint le pilote composite, elle avertit le pilote client correspondant que la fonction est entrée en état de fonctionnement en effectuant l’IRP de veille d’attente que le pilote client avait envoyé précédemment.

Dans la routine d’achèvement (mise en éveil à distance), le pilote composite doit mettre en file d’attente un élément de travail pour terminer l’IRP de veille d’attente en attente. Pour les périphériques USB 3.0, le pilote composite réveille uniquement la fonction qui envoie le signal de reprise et laisse les autres fonctions en état de suspension. La mise en file d’attente de l’élément de travail garantit la compatibilité avec l’implémentation existante pour les pilotes de fonction des périphériques USB 2.0. Pour plus d’informations sur la mise en file d’attente d’un élément de travail, consultez IoQueueWorkItem.

Le thread worker termine l’IRP de veille d’attente et appelle la routine d’achèvement du pilote client. La routine d’achèvement envoie ensuite un IRP D0 pour entrer la fonction en état de fonctionnement. Avant de terminer l’IRP de veille d’attente, le pilote composite doit appeler PoSetSystemWake pour marquer l’IRP wait-wake comme étant celui qui a contribué à réveiller le système de l’état de suspension. Le gestionnaire d’alimentation consigne un événement de suivi d’événements pour Windows (ETW) (visible dans le canal système global) qui inclut des informations sur les appareils qui ont réveillé le système.