Partager via


Suspension sélective USB

Remarque

Cet article concerne les développeurs de pilotes de périphérique. Si vous rencontrez des difficultés avec un périphérique USB, consultez Résoudre les problèmes de USB-C dans Windows

La fonctionnalité de suspension sélective USB permet au pilote du hub de suspendre un port individuel sans affecter l’opération des autres ports sur le hub. Cette fonctionnalité est utile dans les ordinateurs portables, car elle permet d’économiser l’alimentation de la batterie. De nombreux appareils, tels que les scanneurs biométriques, nécessitent uniquement une alimentation intermittente. La suspension de ces appareils, lorsqu’ils ne sont pas en cours d’utilisation, réduit la consommation d’énergie globale. Plus important encore, tout appareil qui n’est pas suspendu de manière sélective peut empêcher le contrôleur hôte USB de désactiver sa planification de transfert, qui réside dans la mémoire système. Les transferts d’accès direct à la mémoire (DMA) par le contrôleur hôte vers le planificateur peuvent empêcher les processeurs du système d’entrer dans des états de veille plus profonds, tels que C3.

La suspension sélective est activée par défaut. Microsoft déconseille fortement de désactiver la suspension sélective.

Les pilotes clients ne doivent pas essayer de déterminer si la suspension sélective est activée avant d’envoyer des demandes inactives. Ils doivent envoyer des demandes inactives chaque fois que l’appareil est inactif. Si la demande inactive échoue, le pilote client doit réinitialiser le minuteur d’inactivité et réessayer.

Pour suspendre de manière sélective un périphérique USB, deux mécanismes différents existent : les IRP de requête inactives (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) et les IRP d’alimentation (IRP_MN_SET_POWER). Le mécanisme à utiliser dépend du type d’appareil : composite ou non conforme.

Sélection d’un mécanisme de suspension sélectif

Les pilotes clients d’une interface sur un appareil composite qui activent l’interface pour la mise en éveil à distance avec un IRP d’attente (IRP_MN_WAIT_WAKE), doivent utiliser le mécanisme IRP de demande inactive (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) pour suspendre de manière sélective un appareil.

Pour plus d’informations sur la mise en éveil à distance, consultez :

Cette section explique le mécanisme de suspension sélectif Windows.

Envoi d’une demande d’inactivité USB IRP

Lorsqu’un appareil est inactif, le pilote client informe le pilote de bus en envoyant un IRP de demande d’inactivité (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION). Une fois que le pilote de bus détermine qu’il est sûr de placer l’appareil dans un état de faible puissance, il appelle la routine de rappel que le pilote de périphérique client a transmis à la pile de protocole avec la requête IRP d'inactivité.

Dans la routine de rappel, le pilote client doit annuler toutes les opérations d’E/S en attente et attendre que tous les IRP USB d'E/S soient complétés. Il peut ensuite émettre une demande de IRP_MN_SET_POWER pour changer l’état d’alimentation de l’appareil WDM en D2. La routine de rappel doit attendre la fin de la requête D2 avant de retourner. Pour plus d'informations sur la routine de rappel de notification d'inactivité, consultez Implémentation d'une routine de rappel IRP pour la demande d'inactivité USB.

Le pilote de bus ne termine pas l'IRP de requête d'inactivité après avoir appelé la routine de rappel pour notification d'inactivité. Au lieu de cela, le conducteur de bus retient l’IRP de demande inactive en attente jusqu’à ce que l’une des conditions suivantes soit remplie :

  • Un IRP_MN_SURPRISE_REMOVAL ou IRP_MN_REMOVE_DEVICE IRP est reçu. Lorsque l’un de ces IRP est reçu, l’IRP de demande inactive est complétée avec le statut STATUS_CANCELLED.
  • Le pilote de bus reçoit une demande pour placer l’appareil dans un état d’alimentation opérationnel (D0). Lors de la réception de cette demande, le pilote de bus termine l’IRP de la demande inactive en attente avec STATUS_SUCCESS.

Les restrictions suivantes s’appliquent à l’utilisation des runtimes d’intégration des demandes inactives :

  • Les pilotes doivent être dans l’état de l’alimentation de l’appareil D0 lors de l’envoi d’un IRP de demande de mise en veille.
  • Les pilotes doivent envoyer un seul IRP de demande inactive par pile d’appareils.

L’exemple de code WDM suivant illustre les étapes à suivre par un pilote de périphérique pour envoyer un IRP de demande d’inactivité USB. La vérification des erreurs est omise dans l’exemple de code suivant.

  1. Allouer et initialiser le IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION IRP

    irp = IoAllocateIrp (DeviceContext->TopOfStackDeviceObject->StackSize, FALSE);
    nextStack = IoGetNextIrpStackLocation (irp);
    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION;
    nextStack->Parameters.DeviceIoControl.InputBufferLength =
    sizeof(struct _USB_IDLE_CALLBACK_INFO);
    
  2. Allouez et initialisez la structure des informations de demande d'inactivité (USB_IDLE_CALLBACK_INFO).

    idleCallbackInfo = ExAllocatePool (NonPagedPool,
    sizeof(struct _USB_IDLE_CALLBACK_INFO));
    idleCallbackInfo->IdleCallback = IdleNotificationCallback;
    // Put a pointer to the device extension in member IdleContext
    idleCallbackInfo->IdleContext = (PVOID) DeviceExtension;
    nextStack->Parameters.DeviceIoControl.Type3InputBuffer = idleCallbackInfo;
    
  3. Définissez une routine d’achèvement.

    Le pilote client doit associer une routine d’achèvement à l’IRP de la requête inactive. Pour plus d'informations sur la routine d'achèvement des notifications d'inactivité et sur l'exemple de code, consultez Implémentation d'une routine d'achèvement de demande d'inactivité USB.

    IoSetCompletionRoutine (irp,
        IdleNotificationRequestComplete,
        DeviceContext,
        TRUE,
        TRUE,
        TRUE);
    
  4. Stockez la demande inactive dans l’extension de l’appareil.

    deviceExtension->PendingIdleIrp = irp;
    
  5. Envoyer la demande de mise en veille au pilote parent.

    ntStatus = IoCallDriver (DeviceContext->TopOfStackDeviceObject, irp);
    

Annulation d’une requête inactive USB

Dans certaines circonstances, un pilote de périphérique peut avoir besoin d'annuler une demande IRP d'inactivité soumise au pilote de bus. Cette situation peut se produire si l'appareil est retiré, devient actif après avoir été inactif et avoir envoyé la demande d'inactivité, ou si l'ensemble du système passe à un état de consommation d'énergie inférieur.

Le pilote client annule l’IRP inactive en appelant IoCancelIrp. Le tableau suivant décrit trois scénarios d’annulation d’un IRP inactif et spécifie l’action que le pilote doit entreprendre :

Scénario Mécanisme d’annulation de demande inactive
Le pilote client annule l’IRP inactif et la routine de rappel de notification d’inactivité USB n’a pas été appelée. La pile de pilotes USB termine l'IRP en veille. Étant donné que l’appareil n’a jamais quitté le D0, le pilote ne modifie pas l’état de l’appareil.
Le pilote client annule l’IRP inactif, la pile de pilotes USB appelle la routine de rappel de notification d’inactivité USB, et celle-ci n’a pas encore terminé. Il est possible que la routine de rappel de notification inactive USB soit appelée même si le pilote client a appelé l’annulation sur l’IRP. Dans ce cas, la routine de rappel du pilote client doit toujours mettre hors tension l’appareil en envoyant l’appareil à un état d’alimentation inférieur de manière synchrone.

Lorsque l’appareil est dans un état d’alimentation inférieur, le pilote client peut ensuite envoyer une demande D0 .

Sinon, le pilote peut attendre que la pile des pilotes USB achève l’IRP inactive avant d’envoyer l’IRP D0.

Si la routine de rappel ne parvient pas à placer l’appareil dans un état d’alimentation faible en raison d’une mémoire insuffisante pour allouer un IRP d’alimentation, il doit annuler l’IRP inactif et quitter immédiatement. L’IRP inactif n’est pas terminé tant que la routine de rappel n’est pas retournée. Par conséquent, la routine de rappel ne doit pas bloquer l’attente de la fin de l’IRP inactive annulée.
L’appareil est déjà dans un état de faible alimentation. Si l’appareil est déjà dans un état de faible alimentation, le pilote client peut envoyer un IRP D0 . La pile de pilotes USB termine l’IRP de requête d'inactivité avec STATUS_SUCCESS.

Sinon, le pilote peut annuler l’IRP inactif, attendre que la pile des pilotes USB termine l’IRP inactive, puis envoyer un IRP D0 .

Routine d'achèvement d'IRP pour demande d'inactivité USB

Dans de nombreux cas, un chauffeur de bus peut appeler la routine de finalisation de la requête IRP d'inactivité d'un conducteur. Si cette situation se produit, un pilote client doit détecter pourquoi le conducteur de bus a terminé l’IRP. Le code d’état retourné peut fournir ces informations. Si le code d’état n’est pas STATUS_POWER_STATE_INVALID, le pilote doit placer son appareil en D0 si l’appareil n’est pas déjà dans D0. Si l'appareil est toujours inactif, le pilote peut soumettre une autre requête d'inactivité IRP.

Remarque

La routine d'achèvement IRP de la demande inactive ne doit pas bloquer en attendant l'achèvement d'une demande d'alimentation D0. La routine d’achèvement peut être appelée par le pilote hub dans le contexte d’un IRP d’alimentation électrique, et le blocage sur un autre IRP d’alimentation électrique dans cette routine peut entraîner une impasse.

La liste suivante indique comment une routine d’achèvement pour une demande inactive doit interpréter certains codes d’état courants :

Code de statut Descriptif
STATUT_RÉUSSI Indique que l’appareil ne doit plus être suspendu. Toutefois, les pilotes doivent vérifier que leurs appareils sont alimentés et les placer dans D0 s’ils ne sont pas déjà dans D0.
STATUT_ANNULÉ Le pilote de bus termine l’IRP de demande d’inactivité avec STATUS_CANCELLED dans l’une des circonstances suivantes :
  • Le pilote de périphérique a annulé l’IRP.
  • Une modification de l’état de l’alimentation du système est requise.
ÉTAT_D'ALIMENTATION_INVALID Indique que le pilote de périphérique a demandé un état d’alimentation D3 pour son appareil. Lorsque cette requête se produit, le pilote de bus clôture tous les IRP inactifs en attente avec STATUS_POWER_STATE_INVALID.
ÉTAT_APPAREIL_OCCUPÉ Indique que le chauffeur de bus a déjà un IRP de demande inactive en attente pour l’appareil. Un seul IRP inactif peut être en attente à la fois pour un appareil donné. L’envoi de plusieurs IRP de demande inactives est une erreur de la part du responsable de la politique d’alimentation. L’enregistreur de pilotes résout l’erreur.

L’exemple de code suivant montre un exemple d’implémentation pour la routine d’achèvement de la demande inactive.

/*
Routine Description:
  Completion routine for idle notification IRP

Arguments:
    DeviceObject - pointer to device object
    Irp - I/O request packet
    DeviceExtension - pointer to device extension

Return Value:
    NT status value
--*/

NTSTATUS
IdleNotificationRequestComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PDEVICE_EXTENSION DeviceExtension
    )
{
    NTSTATUS                ntStatus;
    POWER_STATE             powerState;
    PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;

    ntStatus = Irp->IoStatus.Status;

    if(!NT_SUCCESS(ntStatus) && ntStatus != STATUS_NOT_SUPPORTED)
    {

        //Idle IRP completes with error.
        switch(ntStatus)
        {
        case STATUS_INVALID_DEVICE_REQUEST:
            //Invalid request.
            break;

        case STATUS_CANCELLED:
            //1. The device driver canceled the IRP.
            //2. A system power state change is required.
            break;

        case STATUS_POWER_STATE_INVALID:
            // Device driver requested a D3 power state for its device
            // Release the allocated resources.
            goto IdleNotificationRequestComplete_Exit;

        case STATUS_DEVICE_BUSY:
            //The bus driver already holds an idle IRP pending for the device.
            break;

        default:
            break;
        }

        // If IRP completes with error, issue a SetD0
        //Increment the I/O count because
        //a new IRP is dispatched for the driver.
        //This call is not shown.
        powerState.DeviceState = PowerDeviceD0;

        // Issue a new IRP
        PoRequestPowerIrp (
            DeviceExtension->PhysicalDeviceObject,
            IRP_MN_SET_POWER,
            powerState,
            (PREQUEST_POWER_COMPLETE) PoIrpCompletionFunc,
            DeviceExtension,
            NULL);
    }

IdleNotificationRequestComplete_Exit:

    idleCallbackInfo = DeviceExtension->IdleCallbackInfo;
    DeviceExtension->IdleCallbackInfo = NULL;
    DeviceExtension->PendingIdleIrp = NULL;
    InterlockedExchange(&DeviceExtension->IdleReqPend, 0);

    if(idleCallbackInfo)
    {
        ExFreePool(idleCallbackInfo);
    }

    DeviceExtension->IdleState = IdleComplete;

    // Because the IRP was created using IoAllocateIrp,
    // the IRP needs to be released by calling IoFreeIrp.
    // Also return STATUS_MORE_PROCESSING_REQUIRED so that
    // the kernel does not reference this.
    IoFreeIrp(Irp);
    KeSetEvent(&DeviceExtension->IdleIrpCompleteEvent, IO_NO_INCREMENT, FALSE);
    return STATUS_MORE_PROCESSING_REQUIRED;
}

Routine de rappel de notification inactive USB

Le pilote de bus (une instance du pilote hub ou le pilote parent générique) détermine quand il est sûr de suspendre les enfants de son appareil. Si c’est le cas, il appelle la routine de rappel de notification d'inactivité fournie par chaque pilote client enfant.

Le prototype de fonction pour USB_IDLE_CALLBACK est le suivant :

typedef VOID (*USB_IDLE_CALLBACK)(__in PVOID Context);

Un pilote de périphérique doit effectuer les actions suivantes dans sa routine de rappel de notification inactive :

  • Demandez une IRP_MN_WAIT_WAKE IRP pour l’appareil si l’appareil doit être préparé pour l’activation à distance.
  • Annulez toutes les E/S et préparez l’appareil à atteindre un état d’alimentation inférieur.
  • Placez l’appareil dans un état de veille WDM en appelant PoRequestPowerIrp avec le paramètre PowerState défini sur la valeur d’énumérateur PowerDeviceD2 (définie dans wdm.h ; ntddk.h).

Le pilote hub et le pilote parent générique USB (Usbccgp.sys) appellent la routine de rappel de notification inactive à IRQL = PASSIVE_LEVEL. La routine de rappel peut ensuite bloquer pendant qu’elle attend la fin de la demande de modification de l’état d’alimentation.

La routine de rappel est appelée uniquement pendant que le système se trouve dans S0 et que l’appareil est en D0.

Les restrictions suivantes s’appliquent aux routines de rappel de notification de demande inactive :

  • Les pilotes de périphérique peuvent lancer une transition de l’état d’alimentation de l’appareil de D0 à D2 dans la routine de rappel de notification inactive, mais aucune autre transition d’état d’alimentation n’est autorisée. En particulier, un pilote ne doit pas tenter de remplacer son appareil par D0 lors de l’exécution de sa routine de rappel.
  • Les pilotes de périphérique ne doivent pas demander plusieurs IRP d’alimentation à partir de la routine de rappel de notification d'inactivité.

Armement des dispositifs pour le réveil dans la routine de rappel de notification inactif

La routine de rappel de notification inactive doit déterminer si son appareil a une demande de IRP_MN_WAIT_WAKE en attente. Si aucune demande de IRP_MN_WAIT_WAKE n’est en attente, la routine de rappel doit envoyer une demande de IRP_MN_WAIT_WAKE avant de suspendre l’appareil. Pour plus d’informations sur le mécanisme d’attente de réveil, consultez Prise en charge des appareils dotés de fonctionnalités de réveil.

Suspension globale USB

La spécification USB 2.0 définit la suspension globale comme l'interruption de l'ensemble du bus se trouvant derrière un contrôleur hôte USB, en arrêtant tout le trafic USB sur le bus, y compris les paquets de début de trame. Les appareils en aval qui ne sont pas déjà suspendus détectent l’état inactif sur leur port en amont et entrent dans l'état de suspension par eux-mêmes. Windows n’implémente pas la suspension globale de cette façon. Windows suspend toujours de manière sélective chaque périphérique USB derrière un contrôleur hôte USB avant de cesser tout le trafic USB sur le bus.

Conditions de suspension globale

Le pilote du hub USB suspend de manière sélective un hub lorsque tous ses appareils connectés sont dans l’état d’alimentation de l’appareil D1, D2 ou D3. L’ensemble du bus entre en suspension globale une fois que tous les hubs USB sont suspendus de manière sélective. La pile de pilotes USB considère un appareil comme inactif dès que l'appareil est dans un état de périphérique WDM D1, D2 ou D3.