Suspension sélective USB

Notes

Cet article est destiné aux 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 usb courants

La fonctionnalité de suspension sélective USB permet au pilote de hub de suspendre un port individuel sans affecter le fonctionnement des autres ports sur le hub. La suspension sélective des périphériques USB est particulièrement utile sur les ordinateurs portables, car elle permet d’économiser l’alimentation de la batterie. De nombreux appareils, tels que les lecteurs d’empreintes digitales et d’autres types de scanneurs biométriques, n’ont besoin d’alimentation que par intermittence. La suspension de ces appareils, lorsque l’appareil n’est 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, comme C3.

Il existe deux mécanismes différents pour suspendre sélectivement un périphérique USB : les irps de demande inactive (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) et les irps d’alimentation (IRP_MN_SET_POWER). Le mécanisme à utiliser dépend du système d’exploitation et du type d’appareil : composite ou non composite.

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

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

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

La version du système d’exploitation Windows détermine la façon dont les pilotes pour les appareils non composites activent la suspension sélective.

  • Windows XP : sur Windows XP, tous les pilotes clients doivent utiliser des irps de demande inactive (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) pour mettre leurs appareils hors tension. Les pilotes clients ne doivent pas utiliser les IRP d’alimentation WDM pour suspendre sélectivement leurs appareils. Cela empêche d’autres appareils de suspendre sélectivement.
  • Windows Vista et versions ultérieures de Windows : les enregistreurs de pilotes ont davantage de choix pour la mise hors tension des appareils dans Windows Vista et dans les versions ultérieures de Windows. Bien que Windows Vista prenne en charge le mécanisme IRP des demandes inactives Windows, les pilotes ne sont pas requis pour l’utiliser.

Le tableau suivant présente les scénarios qui nécessitent l’utilisation de l’IRP de demande inactive et ceux qui peuvent utiliser un IRP d’alimentation WDM pour suspendre un périphérique USB :

Version de Windows Fonction sur l’appareil composite, armé pour le réveil Fonction sur l’appareil composite, non armé pour l’éveil Périphérique USB à interface unique
Windows 7 Utiliser l’IRP de requête inactive Utiliser l’IRP d’alimentation WDM Utiliser l’IRP d’alimentation WDM
Windows Server 2008 Utiliser l’IRP de requête inactive Utiliser l’IRP d’alimentation WDM Utiliser l’IRP d’alimentation WDM
Windows Vista Utiliser l’IRP de requête inactive Utiliser l’IRP d’alimentation WDM Utiliser l’IRP d’alimentation WDM
Windows Server 2003 Utiliser l’IRP de requête inactive Utiliser l’IRP de requête inactive Utiliser l’IRP de requête inactive
Windows XP Utiliser l’IRP de requête inactive Utiliser l’IRP de requête inactive Utiliser l’IRP de requête inactive

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

Envoi d’un IRP de requête inactive USB

Lorsqu’un appareil est inactif, le pilote client l’informe en envoyant un IRP de demande inactive (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION). Une fois que le pilote de bus a déterminé qu’il est sûr de placer l’appareil dans un état de faible consommation d’énergie, il appelle la routine de rappel que le pilote de périphérique client a transmise à la pile avec l’IRP de demande inactive.

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 d’E/S USB se terminent. Il peut ensuite émettre une demande IRP_MN_SET_POWER pour changer l’état d’alimentation de l’appareil WDM en D2. La routine de rappel doit attendre que la requête D2 se termine avant de retourner. Pour plus d’informations sur la routine de rappel de notification inactive, consultez « Routine de rappel de notification d’inactivité USB ».

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

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

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

  • Les pilotes doivent être à l’état d’alimentation du périphérique D0 lors de l’envoi d’une IRP de demande inactive.
  • Les pilotes doivent envoyer une seule IRP de requête inactive par pile de périphériques.

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

  1. Allouer et initialiser l’IRP IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION

    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 d’informations de requête inactive (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 demande inactive. Pour plus d’informations sur la routine d’achèvement des notifications inactives et un exemple de code, consultez « Routine d’achèvement IRP de requête inactive USB ».

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

    deviceExtension->PendingIdleIrp = irp;
    
    
  5. Envoyez la demande d’inactivité au pilote parent.

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

Annulation d’une demande d’inactivité USB

Dans certaines circonstances, un pilote de périphérique peut avoir besoin d’annuler un IRP de demande inactive qui a été envoyé au pilote de bus. Cela peut se produire si l’appareil est supprimé, devient actif après avoir été inactif et envoyé la demande inactive, ou si l’ensemble du système passe à un état d’alimentation inférieur.

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

Scénario Mécanisme d’annulation des demandes inactives
Le pilote client a annulé l’IRP inactive et la pile de pilotes USB n’a pas appelé la « routine de rappel de notification d’inactivité USB ». La pile de pilotes USB termine le IRP inactif. Étant donné que l’appareil n’a jamais quitté le D0, le pilote ne modifie pas l’état de l’appareil.
Le pilote client a annulé l’IRP inactif, la pile de pilotes USB a appelé la routine de rappel de notification d’inactivité USB et elle n’a pas encore été retournée. Il est possible que la routine de rappel de notification d’inactivité 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 façon synchrone.

Lorsque l’appareil est dans l’état d’alimentation inférieure, le pilote client peut alors envoyer une requête D0 .

Le pilote peut également attendre que la pile de pilotes USB termine l’IRP inactif, puis envoyer l’IRP D0 .

Si la routine de rappel ne parvient pas à placer l’appareil dans un état de faible consommation d’énergie en raison d’une mémoire insuffisante pour allouer un IRP d’alimentation, elle 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 consommation. Si l’appareil est déjà dans un état de faible consommation, le pilote client peut envoyer un IRP D0 . La pile de pilotes USB termine l’IRP de la requête inactive avec STATUS_SUCCESS.

Le pilote peut également annuler l’IRP inactif, attendre que la pile de pilotes USB termine l’IRP inactive, puis envoyer un IRP D0 .

Routine de saisie semi-automatique des demandes inactives USB

Dans de nombreux cas, un pilote de bus peut appeler la routine d’achèvement de la demande inactive d’un pilote. Si cela se produit, un pilote client doit détecter la raison pour laquelle le pilote de bus a effectué le IRP. Le code status retourné peut fournir ces informations. Si le code status n’est pas STATUS_POWER_STATE_INVALID, le pilote doit placer son appareil dans D0 si l’appareil n’est pas déjà en D0. Si l’appareil est toujours inactif, le pilote peut envoyer un autre IRP de demande d’inactivité.

Notes

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

La liste suivante indique comment une routine de saisie semi-automatique pour une demande inactive doit interpréter certains codes status courants :

Code d’état Description
STATUS_SUCCESS 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.
STATUS_CANCELLED Le pilote de bus effectue 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 d’alimentation du système est requise.
  • Sur Windows XP, le pilote de périphérique de l’un des périphériques USB connectés n’a pas pu placer son appareil en D2 lors de l’exécution de sa routine de rappel de demande inactive. Par conséquent, le pilote de bus a terminé tous les IRP de demande d’inactivité en attente.
STATUS_POWER_STATE_INVALID Indique que le pilote de périphérique a demandé un état d’alimentation D3 pour son appareil. Lorsque cela se produit, le pilote de bus termine tous les IRP inactifs en attente avec STATUS_POWER_STATE_INVALID.
STATUS_DEVICE_BUSY Indique que le pilote de bus contient déjà une demande IRP inactive en attente pour l’appareil. Un seul IRP inactif peut être en attente à la fois pour un appareil donné. L’envoi de plusieurs irps de demande inactive est une erreur de la part du propriétaire de la stratégie d’alimentation et doit être traitée par l’enregistreur de pilotes.

L’exemple de code suivant montre un exemple d’implémentation pour la routine d’exécution des demandes inactives.

/*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 d’inactivité USB

Le pilote de bus (soit une instance du pilote hub, soit le pilote parent générique) détermine quand il est possible de suspendre les enfants de son appareil en toute sécurité. Si tel est le cas, il appelle la routine de rappel de notification inactive fournie par le pilote client de chaque 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 IRP_MN_WAIT_WAKE pour l’appareil si l’appareil doit être armé pour une mise en éveil à distance.
  • Annulez toutes les E/S et préparez l’appareil à passer à 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 énumératrice PowerDeviceD2 (définie dans wdm.h ; ntddk.h). Dans Windows XP, un pilote ne doit pas placer son appareil dans PowerDeviceD3, même si l’appareil n’est pas armé pour la veille à distance.

Dans Windows XP, un pilote doit s’appuyer sur une routine de rappel de notification inactive pour suspendre de manière sélective un appareil. Si un pilote s’exécutant dans Windows XP place un appareil dans un état d’alimentation inférieur directement sans utiliser une routine de rappel de notification inactive, cela peut empêcher d’autres appareils de l’arborescence de périphériques USB de se suspendre.

Le pilote hub et le pilote parent générique (Usbccgp.sys) usb appellent la routine de rappel de notification d’inactivité à l’adresse IRQL = PASSIVE_LEVEL. Cela permet à la routine de rappel de se 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 lorsque le système est dans S0 et que l’appareil est en D0.

Les restrictions suivantes s’appliquent aux routines de rappel de notification des demandes inactives :

  • 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 d’inactivité, 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é.

Arming devices for wakeup in the idle notification callback routine

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 requête 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 de veille d’attente, consultez Prise en charge des appareils dotés de fonctionnalités wakeUp.

Interruption globale USB

La spécification USB 2.0 définit la suspension globale comme la suspension de l’ensemble du bus 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 d’image. Les appareils en aval qui ne sont pas encore suspendus détectent l’état d’inactivité sur leur port amont et entrent eux-mêmes l’état de suspension. Windows n’implémente pas la suspension globale de cette manière. 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 dans Windows 7

Windows 7 est plus agressif en ce qui concerne la suspension sélective des hubs USB que Windows Vista. Le pilote du hub USB Windows 7 interrompt de manière sélective tout hub où tous ses appareils connectés sont à l’état d’alimentation 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 Windows 7 traite un appareil comme inactif chaque fois que l’appareil est dans un état d’appareil WDM D1, D2 ou D3.

Conditions de suspension globale dans Windows Vista

Les conditions requises pour effectuer une suspension globale sont plus flexibles dans Windows Vista que dans Windows XP.

En particulier, la pile USB traite un appareil comme inactif dans Windows Vista chaque fois que l’appareil est dans un état d’appareil WDM de D1, D2 ou D3.

Le diagramme suivant illustre un scénario qui peut se produire dans Windows Vista.

Diagramme illustrant une suspension globale dans Windows Vista.

Ce diagramme illustre une situation similaire à celle décrite dans la section « Conditions de suspension globale dans Windows XP ». Toutefois, dans ce cas, l’appareil 3 est considéré comme un appareil inactif. Étant donné que tous les appareils sont inactifs, le pilote de bus est en mesure d’appeler les routines de rappel de notification d’inactivité associées aux IRPs de demande inactive en attente. Chaque pilote interrompt son appareil et le pilote de bus interrompt le contrôleur hôte USB dès qu’il peut le faire en toute sécurité.

Sur Windows Vista, tous les périphériques USB autres que le hub doivent être en D1, D2 ou D3 avant que la suspension globale ne soit lancée, auquel cas tous les hubs USB, y compris le hub racine, sont suspendus. Cela signifie que tout pilote de client USB qui ne prend pas en charge la suspension sélective empêche le bus d’entrer en suspension globale.

Conditions de suspension globale dans Windows XP

Afin d’optimiser les économies d’énergie sur Windows XP, il est important que chaque pilote de périphérique utilise des IRP de demande inactive pour suspendre son appareil. Si un pilote interrompt son appareil avec une demande de IRP_MN_SET_POWER au lieu d’un IRP de requête inactif, cela peut empêcher d’autres appareils de se suspendre.

Le diagramme suivant illustre un scénario qui peut se produire dans Windows XP.

Diagramme illustrant une suspension globale dans Windows XP.

Dans cette figure, l’appareil 3 est à l’état d’alimentation D3 et n’a pas d’IRP de requête inactive en attente. L’appareil 3 n’est pas considéré comme un appareil inactif aux fins d’une suspension globale dans Windows XP, car il n’a pas d’IRP de requête inactive en attente avec son parent. Cela empêche le pilote de bus d’appeler les routines de rappel des demandes inactives associées aux pilotes d’autres appareils de l’arborescence.

Activation de la suspension sélective

La suspension sélective est désactivée pour les versions de mise à niveau de Microsoft Windows XP. Il est activé pour propre installations de Windows XP, Windows Vista et versions ultérieures de Windows.

Pour activer la prise en charge de la suspension sélective pour un hub racine donné et ses appareils enfants, cochez la case sous l’onglet Gestion de l’alimentation pour le hub racine USB dans Gestionnaire de périphériques.

Vous pouvez également activer ou désactiver la suspension sélective en définissant la valeur hcDisableSelectiveSuspend sous la clé logicielle du pilote de port USB. La valeur 1 désactive la suspension sélective. La valeur 0 active la suspension sélective.

Par instance, les lignes suivantes dans Usbport.inf désactivent la suspension sélective pour un contrôleur Hydra OHCI :

[OHCI_NOSS.AddReg.NT]
HKR,,"HcDisableSelectiveSuspend",0x00010001,1

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 d’inactivité échoue, le pilote client doit réinitialiser le minuteur d’inactivité et réessayer.