Partager via


Interruption 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 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. La suspension sélective des périphériques USB est particulièrement utile dans 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écessitent uniquement une puissance intermittente. 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, tels que C3.

Il existe deux mécanismes différents pour suspendre de manière sélective un périphérique USB : les IRP de demande inactives (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) et définir des 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électif

Les pilotes clients, pour une interface sur un appareil composite, qui activent l’interface pour la mise en éveil à distance avec un IRP de veille 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 :

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 IRP de demande inactives (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) pour mettre hors tension leurs appareils. Les pilotes clients ne doivent pas utiliser les runtimes d’intégration d’alimentation WDM pour suspendre de manière sélective leurs appareils. Cela empêche les autres appareils de suspendre de manière sélective.
  • Windows Vista et versions ultérieures de Windows : les enregistreurs de pilotes ont plus 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 nécessaires 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 un appareil composite, armé pour le réveil Fonction sur un appareil composite, pas armé pour le réveil Périphérique USB d’interface unique
Windows 7 Utiliser l’IRP de la demande inactive Utiliser WDM power IRP Utiliser WDM power IRP
Windows Server 2008 Utiliser l’IRP de la demande inactive Utiliser WDM power IRP Utiliser WDM power IRP
Windows Vista Utiliser l’IRP de la demande inactive Utiliser WDM power IRP Utiliser WDM power IRP
Windows Server 2003 Utiliser l’IRP de la demande inactive Utiliser l’IRP de la demande inactive Utiliser l’IRP de la demande inactive
Windows XP Utiliser l’IRP de la demande inactive Utiliser l’IRP de la demande inactive Utiliser l’IRP de la demande inactive

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 alimentation, il appelle la routine de rappel que le pilote de périphérique client a transmis la pile avec l’IRP de la 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 E/S IRP USB se terminent. 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 inactive, consultez « Routine de rappel de notification inactive USB ».

Le pilote de bus ne termine pas l’IRP de la demande inactive après avoir appelé la routine de rappel de notification inactive. Au lieu de cela, le pilote de bus contient l’IRP de demande 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. Quand l’un de ces IRP est reçu, l’IRP de la demande inactive est terminée avec 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 ce pilote de bus de requête, l’IRP de la demande inactive en attente est terminée 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 inactive.
  • 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 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 des 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 sur l’exemple de code, consultez « 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. Envoyez la demande inactive 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 un IRP de demande inactive qui a été soumis au pilote de bus. Cela peut se produire si l’appareil est supprimé, devient actif après avoir été inactif et en envoyant la demande inactive, ou si l’ensemble du système passe à un état d’alimentation système 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 a annulé l’IRP inactif 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 l’IRP inactive. É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 retourné. 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 termine l’IRP inactive, puis envoie 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’a pas été 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 la demande inactive 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 de demande d’inactivité USB

Dans de nombreux cas, un pilote de bus peut appeler la routine de saisie semi-automatique IRP d’un pilote. Si cela se produit, un pilote client doit détecter la raison pour laquelle le pilote 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 envoyer un autre IRP de demande inactive.

Remarque

La routine d’achèvement IRP de la 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’un 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 d’achèvement pour une demande inactive doit interpréter certains codes d’état 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 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.
  • Sur Windows XP, le pilote de périphérique pour l’un des périphériques USB connectés n’a pas pu placer son appareil dans D2 lors de l’exécution de sa routine de rappel de demande inactive. Par conséquent, le pilote de bus a terminé toutes les irps de demande inactive 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 toutes les adresses IRP inactives en attente avec STATUS_POWER_STATE_INVALID.
STATUS_DEVICE_BUSY Indique que le pilote de bus contient 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 runtimes d’intégration des demandes inactives est une erreur dans la partie 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’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 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_MN_WAIT_WAKE IRP pour l’appareil si l’appareil doit être armé pour la mise en éveil à 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). Dans Windows XP, un pilote ne doit pas placer son appareil dans PowerDeviceD3, même si l’appareil n’est pas armé pour le réveil à distance.

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

Le pilote hub et le pilote parent générique USB (Usbccgp.sys) appellent la routine de rappel de notification inactive à IRQL = PASSIVE_LEVEL. Cela permet à la routine de rappel de 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 inactive.

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

Interruption globale USB

La spécification USB 2.0 définit la suspension globale en tant que 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émarrage d’images. Les appareils en aval qui ne sont pas déjà suspendus détectent l’état inactif sur leur port en amont et entrent l’état de suspension seul. 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 dans Windows 7

Windows 7 est plus agressif sur la suspension sélective des hubs USB que Windows Vista. Le pilote du hub USB Windows 7 interrompt de manière sélective tous les hubs où tous ses appareils attachés sont dans un état d’alimentation d’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 Windows 7 traite un appareil comme inactif chaque fois que l’appareil est dans un état d’appareil WDM de 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 illustrée dans la section « Conditions de suspension globale dans Windows XP ». Toutefois, dans ce cas, l’appareil 3 se qualifie 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 inactive associées aux irps de demande inactive en attente. Chaque pilote suspend son appareil et le pilote de bus suspend le contrôleur hôte USB dès qu’il est sûr de le faire.

Sur Windows Vista, tous les périphériques USB non hub doivent se trouver dans D1, D2 ou D3 avant que la suspension globale soit lancée, auquel cas tous les hubs USB, y compris le hub racine, sont suspendus. Cela signifie que tout pilote 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

Pour optimiser l’économie d’alimentation sur Windows XP, il est important que chaque pilote de périphérique utilise des IRP de demande inactives pour suspendre son appareil. Si un pilote suspend son appareil avec une demande de IRP_MN_SET_POWER au lieu d’un IRP de demande inactive, il peut empêcher d’autres appareils de 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 dans l’état d’alimentation D3 et n’a pas d’IRP de demande inactive en attente. L’appareil 3 ne se qualifie pas comme un appareil inactif à des fins de suspension globale dans Windows XP, car il n’a pas d’IRP de demande inactive en attente avec son parent. Cela empêche le pilote de bus d’appeler les routines de rappel de demande inactive 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 les installations propres de Windows XP, Windows Vista et versions ultérieures de Windows.

Pour activer la prise en charge sélective de la suspension d’un hub racine donné et de ses appareils enfants, cochez la case sous l’onglet Gestion de l’alimentation du 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 de 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 exemple, 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 inactive échoue, le pilote client doit réinitialiser le minuteur d’inactivité et réessayer.