Partilhar via


Suspensão seletiva USB

Observação

Este artigo é para desenvolvedores de driver de dispositivo. Se você estiver enfrentando dificuldades com um dispositivo USB, consulte Corrigir problemas USB-C no Windows

O recurso de suspensão seletiva USB permite que o driver do hub suspenda uma porta individual sem afetar a operação das outras portas no hub. A suspensão seletiva de dispositivos USB é especialmente útil em computadores portáteis, pois ajuda a conservar a energia da bateria. Muitos dispositivos, como leitores de impressões digitais e outros tipos de scanners biométricos, só precisam de energia intermitentemente. Suspender esses dispositivos, quando o dispositivo não está em uso, reduz o consumo geral de energia. Mais importante, qualquer dispositivo que não esteja suspenso seletivamente pode impedir que o controlador host USB desabilite sua programação de transferência, que reside na memória do sistema. As transferências de acesso direto à memória (DMA) pelo controlador host para o agendador podem impedir que os processadores do sistema entrem em estados de suspensão mais profundos, como C3.

Existem dois mecanismos diferentes para suspender seletivamente um dispositivo USB: IRPs de solicitação ociosa (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) e IRPs de energia definida (IRP_MN_SET_POWER). O mecanismo a ser usado depende do sistema operacional e do tipo de dispositivo: composto ou não composto.

Selecionando um mecanismo de suspensão seletiva

Os drivers cliente, para uma interface em um dispositivo composto, que habilitam a interface para ativação remota com um IRP de despertar de espera (IRP_MN_WAIT_WAKE), devem usar o mecanismo de IRP de solicitação ociosa (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) para suspender seletivamente um dispositivo.

Para obter informações sobre o despertar remoto, consulte:

A versão do sistema operacional Windows determina a maneira como os drivers para dispositivos não compostos habilitam a suspensão seletiva.

  • Windows XP: No Windows XP, todos os drivers de cliente devem usar IRPs de solicitação ociosa (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) para desligar seus dispositivos. Os drivers cliente não devem usar IRPs de energia WDM para suspender seletivamente seus dispositivos. Isso evita que outros dispositivos suspendam seletivamente.
  • Windows Vista e versões posteriores do Windows: os criadores de drivers têm mais opções para desligar dispositivos no Windows Vista e nas versões posteriores do Windows. Embora o Windows Vista ofereça suporte ao mecanismo IRP de solicitação ociosa do Windows, os drivers não são necessários para usá-lo.

A tabela a seguir mostra os cenários que exigem o uso do IRP de solicitação ociosa e os que podem usar um IRP de energia WDM para suspender um dispositivo USB:

Versão do Windows Função no dispositivo composto, armado para despertar Função no dispositivo composto, não armado para despertar Dispositivo USB de interface única
Windows 7 Usar IRP de solicitação ociosa Usar o IRP de energia WDM Usar o IRP de energia WDM
Windows Server 2008 Usar IRP de solicitação ociosa Usar o IRP de energia WDM Usar o IRP de energia WDM
Windows Vista Usar IRP de solicitação ociosa Usar o IRP de energia WDM Usar o IRP de energia WDM
Windows Server 2003 Usar IRP de solicitação ociosa Usar IRP de solicitação ociosa Usar IRP de solicitação ociosa
Windows XP Usar IRP de solicitação ociosa Usar IRP de solicitação ociosa Usar IRP de solicitação ociosa

Esta seção explica o mecanismo de suspensão seletiva do Windows.

Enviando uma solicitação de ociosidade USB IRP

Quando um dispositivo fica ocioso, o motorista cliente informa o motorista do ônibus enviando uma solicitação ociosa IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION). Depois que o driver de barramento determina que é seguro colocar o dispositivo em um estado de baixa energia, ele chama a rotina de retorno de chamada que o driver de dispositivo cliente transmitiu pela pilha com o IRP de solicitação ociosa.

Na rotina de retorno de chamada, o driver do cliente deve cancelar todas as operações de E/S pendentes e aguardar a conclusão de todos os IRPs de E/S USB. Em seguida, ele pode emitir uma solicitação de IRP_MN_SET_POWER para alterar o estado de energia do dispositivo WDM para D2. A rotina de retorno de chamada deve aguardar a conclusão da solicitação D2 antes de retornar. Para obter mais informações sobre a rotina de retorno de chamada de notificação ociosa, consulte "Rotina de retorno de chamada de notificação ociosa USB".

O motorista do ônibus não conclui a IRP de solicitação ociosa depois de chamar a rotina de retorno de chamada de notificação ociosa. Em vez disso, o motorista do ônibus mantém o IRP de solicitação ocioso pendente até que uma das seguintes condições seja verdadeira:

  • Um IRP_MN_SUPRISE_REMOVAL ou IRP_MN_REMOVE_DEVICE IRP é recebido. Quando um desses IRPs é recebido, o IRP de solicitação ociosa é concluído com STATUS_CANCELLED.
  • O motorista do ônibus recebe uma solicitação para colocar o dispositivo em um estado de potência de trabalho (D0). Ao receber essa solicitação, o motorista de ônibus conclui a solicitação ociosa pendente IRP com STATUS_SUCCESS.

As seguintes restrições se aplicam ao uso de IRPs de solicitação ociosa:

  • Os drivers devem estar no estado de energia do dispositivo D0 ao enviar uma solicitação ociosa IRP.
  • Os drivers devem enviar apenas um IRP de solicitação ociosa por pilha de dispositivos.

O código de exemplo WDM a seguir ilustra as etapas que um driver de dispositivo executa para enviar uma solicitação ociosa USB IRP. A verificação de erros foi omitida no exemplo de código a seguir.

  1. Alocar e inicializar o 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. Aloque e inicialize a estrutura de informações de solicitação ociosa (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. Defina uma rotina de conclusão.

    O driver cliente deve associar uma rotina de conclusão à IRP de solicitação ociosa. Para obter mais informações sobre a rotina de conclusão de notificação ociosa e o código de exemplo, consulte "USB Idle Request IRP Completion Routine".

    IoSetCompletionRoutine (irp,
        IdleNotificationRequestComplete,
        DeviceContext,
        TRUE,
        TRUE,
        TRUE);
    
  4. Armazene a solicitação ociosa na extensão do dispositivo.

    deviceExtension->PendingIdleIrp = irp;
    
    
  5. Envie a solicitação Ociosa para o driver pai.

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

Cancelando uma solicitação de ociosidade USB

Em determinadas circunstâncias, um driver de dispositivo pode precisar cancelar uma solicitação ociosa IRP que foi enviada ao driver do ônibus. Isso pode ocorrer se o dispositivo for removido, ficar ativo depois de ficar ocioso e enviar a solicitação ociosa ou se todo o sistema estiver fazendo a transição para um estado de energia do sistema mais baixo.

O driver cliente cancela o IRP ocioso chamando IoCancelIrp. A tabela a seguir descreve três cenários para cancelar um IRP ocioso e especifica a ação que o driver deve tomar:

Cenário Mecanismo de cancelamento de solicitação ociosa
O driver cliente cancelou o IRP ocioso e a pilha de driver USB não chamou a "Rotina de retorno de chamada de notificação ociosa USB". A pilha de drivers USB completa o IRP ocioso. Como o dispositivo nunca saiu do D0, o driver não altera o estado do dispositivo.
O driver cliente cancelou o IRP ocioso, a pilha de driver USB chamou a rotina de retorno de chamada de notificação ociosa USB e ainda não retornou. É possível que a rotina de retorno de chamada de notificação ociosa USB seja invocada mesmo que o driver cliente tenha invocado o cancelamento no IRP. Nesse caso, a rotina de retorno de chamada do driver cliente ainda deve desligar o dispositivo enviando o dispositivo para um estado de energia mais baixo de forma síncrona.

Quando o dispositivo está no estado de energia mais baixa, o driver cliente pode enviar uma solicitação D0 .

Como alternativa, o driver pode esperar que a pilha de drivers USB conclua o IRP ocioso e, em seguida, enviar o IRP D0 .

Se a rotina de retorno de chamada não conseguir colocar o dispositivo em um estado de baixo consumo de energia devido à memória insuficiente para alocar um IRP de energia, ele deverá cancelar o IRP ocioso e sair imediatamente. O IRP ocioso não será concluído até que a rotina de retorno de chamada tenha retornado; portanto, a rotina de retorno de chamada não deve bloquear a espera pela conclusão do IRP ocioso cancelado.
O dispositivo já está em um estado de baixa energia. Se o dispositivo já estiver em um estado de baixo consumo de energia, o driver cliente pode enviar um IRP D0 . A pilha de drivers USB conclui a solicitação ociosa IRP com STATUS_SUCCESS.

Como alternativa, o driver pode cancelar o IRP ocioso, aguardar que a pilha de drivers USB conclua o IRP ocioso e, em seguida, enviar um IRP D0 .

Rotina de conclusão de IRP de solicitação ociosa USB

Em muitos casos, um motorista de ônibus pode chamar a rotina de conclusão de IRP de solicitação ociosa de um motorista. Se isso ocorrer, um driver cliente deve detectar por que o motorista de ônibus concluiu o IRP. O código de status retornado pode fornecer essas informações. Se o código de status não estiver STATUS_POWER_STATE_INVALID, o driver deverá colocar seu dispositivo em D0 se o dispositivo ainda não estiver em D0. Se o dispositivo ainda estiver ocioso, o driver poderá enviar outra solicitação ociosa IRP.

Observação

A rotina de conclusão de IRP de solicitação ociosa não deve bloquear a espera pela conclusão de uma solicitação de energia D0 . A rotina de conclusão pode ser chamada no contexto de uma IRP de energia pelo driver do hub, e o bloqueio de outra IRP de energia na rotina de conclusão pode levar a um impasse.

A lista a seguir indica como uma rotina de conclusão para uma solicitação ociosa deve interpretar alguns códigos de status comuns:

Código de status Descrição
STATUS_SUCCESS Indica que o dispositivo não deve mais ser suspenso. No entanto, os drivers devem verificar se seus dispositivos estão ligados e colocá-los em D0 se ainda não estiverem em D0.
STATUS_CANCELLED O motorista do ônibus conclui a solicitação ociosa IRP com STATUS_CANCELLED em qualquer uma das seguintes circunstâncias:
  • O driver de dispositivo cancelou o IRP.
  • É necessária uma alteração do estado de energia do sistema.
  • No Windows XP, o driver de dispositivo para um dos dispositivos USB conectados falhou ao colocar seu dispositivo em D2 durante a execução de sua rotina de retorno de chamada de solicitação ociosa. Como resultado, o motorista do ônibus completou todos os IRPs de solicitação ociosos pendentes.
STATUS_POWER_STATE_INVALID Indica que o driver de dispositivo solicitou um estado de energia D3 para seu dispositivo. Quando isso ocorre, o motorista do barramento conclui todos os IRPs ociosos pendentes com STATUS_POWER_STATE_INVALID.
STATUS_DEVICE_BUSY Indica que o motorista do barramento já mantém uma solicitação ociosa IRP pendente para o dispositivo. Apenas um IRP ocioso pode estar pendente de cada vez para um determinado dispositivo. O envio de vários IRPs de solicitação ociosa é um erro por parte do proprietário da política de energia e deve ser resolvido pelo gravador do driver.

O exemplo de código a seguir mostra uma implementação de exemplo para a rotina de conclusão de solicitação ociosa.

/*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;
}

Rotina de retorno de chamada de notificação ociosa USB

O driver de barramento (uma instância do driver de hub ou o driver pai genérico) determina quando é seguro suspender os filhos de seu dispositivo. Se estiver, ele chama a rotina de retorno de chamada de notificação ociosa fornecida pelo driver cliente de cada filho.

O protótipo de função para USB_IDLE_CALLBACK é o seguinte:

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

Um driver de dispositivo deve executar as seguintes ações em sua rotina de retorno de chamada de notificação ociosa:

  • Solicite um IRP IRP_MN_WAIT_WAKE para o dispositivo se o dispositivo precisar estar armado para despertar remoto.
  • Cancele toda a E/S e prepare o dispositivo para ir para um estado de energia mais baixo.
  • Coloque o dispositivo em um estado de suspensão WDM chamando PoRequestPowerIrp com o parâmetro PowerState definido como o valor do enumerador PowerDeviceD2 (definido em wdm.h; ntddk.h). No Windows XP, um driver não deve colocar seu dispositivo no PowerDeviceD3, mesmo se o dispositivo não estiver armado para despertar remoto.

No Windows XP, um driver deve contar com uma rotina de retorno de chamada de notificação ociosa para suspender seletivamente um dispositivo. Se um driver em execução no Windows XP colocar um dispositivo em um estado de menor consumo de energia diretamente sem usar uma rotina de retorno de chamada de notificação ociosa, isso pode impedir que outros dispositivos na árvore de dispositivos USB sejam suspensos.

O driver de hub e o USB Generic Parent Driver (Usbccgp.sys) chamam a rotina de retorno de chamada de notificação ociosa em IRQL = PASSIVE_LEVEL. Isso permite que a rotina de retorno de chamada seja bloqueada enquanto aguarda a conclusão da solicitação de alteração de estado de energia.

A rotina de retorno de chamada é invocada somente enquanto o sistema está em S0 e o dispositivo está em D0.

As restrições a seguir se aplicam a rotinas de retorno de chamada de notificação de solicitação ociosa:

  • Os drivers de dispositivo podem iniciar uma transição de estado de energia do dispositivo de D0 para D2 na rotina de retorno de chamada de notificação ociosa, mas nenhuma outra transição de estado de energia é permitida. Em particular, um driver não deve tentar alterar seu dispositivo para D0 durante a execução de sua rotina de retorno de chamada.
  • Os drivers de dispositivo não devem solicitar mais de um IRP de energia de dentro da rotina de retorno de chamada de notificação ociosa.

Armando dispositivos para despertar na rotina de retorno de chamada de notificação ociosa

A rotina de retorno de chamada de notificação ociosa deve determinar se seu dispositivo tem uma solicitação de IRP_MN_WAIT_WAKE pendente. Se nenhuma solicitação IRP_MN_WAIT_WAKE estiver pendente, a rotina de retorno de chamada deve enviar uma solicitação de IRP_MN_WAIT_WAKE antes de suspender o dispositivo. Para obter mais informações sobre o mecanismo de ativação de espera, consulte Suportando dispositivos que têm recursos de ativação.

Suspensão global USB

A especificação USB 2.0 define suspensão global como a suspensão de todo o barramento atrás de um controlador de host USB, interrompendo todo o tráfego USB no barramento, incluindo pacotes de início de quadro. Os dispositivos downstream que ainda não estão suspensos detectam o estado Ocioso em sua porta upstream e entram no estado de suspensão por conta própria. O Windows não implementa a suspensão global dessa maneira. O Windows sempre suspende seletivamente cada dispositivo USB atrás de um controlador de host USB antes que ele cesse todo o tráfego USB no barramento.

Condições para suspensão global no Windows 7

O Windows 7 é mais agressivo em suspender seletivamente hubs USB do que o Windows Vista. O driver de hub USB do Windows 7 suspenderá seletivamente qualquer hub em que todos os dispositivos conectados estejam no estado de energia do dispositivo D1, D2 ou D3 . Todo o barramento entra em suspensão global assim que todos os hubs USB são suspensos seletivamente. A pilha de drivers USB do Windows 7 trata um dispositivo como ocioso sempre que o dispositivo estiver em um estado de dispositivo WDM de D1, D2 ou D3.

Condições para suspensão global no Windows Vista

Os requisitos para fazer uma suspensão global são mais flexíveis no Windows Vista do que no Windows XP.

Em particular, a pilha USB trata um dispositivo como ocioso no Windows Vista sempre que o dispositivo está em um estado de dispositivo WDM de D1, D2 ou D3.

O diagrama a seguir ilustra um cenário que pode ocorrer no Windows Vista.

Diagrama ilustrando uma suspensão global no Windows Vista.

Este diagrama ilustra uma situação semelhante à descrita na seção "Condições para suspensão global no Windows XP". No entanto, neste caso, o Dispositivo 3 qualifica-se como um dispositivo ocioso. Como todos os dispositivos estão ociosos, o motorista do barramento pode chamar as rotinas de retorno de chamada de notificação ociosa associadas aos IRPs de solicitação ociosa pendentes. Cada driver suspende seu dispositivo e o driver do barramento suspende o controlador de host USB assim que for seguro fazê-lo.

No Windows Vista, todos os dispositivos USB que não sejam de hub devem estar em D1, D2 ou D3 antes que a suspensão global seja iniciada, momento em que todos os hubs USB, incluindo o hub raiz, são suspensos. Isso significa que qualquer driver de cliente USB que não ofereça suporte à suspensão seletiva impede que o barramento entre em suspensão global.

Condições para suspensão global no Windows XP

Para maximizar a economia de energia no Windows XP, é importante que cada driver de dispositivo use IRPs de solicitação ociosa para suspender seu dispositivo. Se um driver suspender seu dispositivo com uma solicitação de IRP_MN_SET_POWER em vez de uma solicitação ociosa IRP, ele poderá impedir que outros dispositivos suspendam.

O diagrama a seguir ilustra um cenário que pode ocorrer no Windows XP.

Diagrama ilustrando uma suspensão global no Windows XP.

Nesta figura, o dispositivo 3 está no estado de energia D3 e não tem uma solicitação ociosa IRP pendente. O dispositivo 3 não se qualifica como um dispositivo ocioso para fins de suspensão global no Windows XP, porque ele não tem um IRP de solicitação ocioso pendente com seu pai. Isso impede que o motorista do barramento chame as rotinas de retorno de chamada de solicitação ociosa associadas aos drivers de outros dispositivos na árvore.

Habilitando a suspensão seletiva

A suspensão seletiva está desabilitada para versões de atualização do Microsoft Windows XP. Ele está habilitado para instalações limpas do Windows XP, Windows Vista e versões posteriores do Windows.

Para habilitar o suporte à suspensão seletiva para um determinado hub raiz e seus dispositivos filho, marque a caixa de seleção na guia Gerenciamento de Energia do hub raiz USB no Gerenciador de Dispositivos.

Como alternativa, você pode habilitar ou desabilitar a suspensão seletiva definindo o valor de HcDisableSelectiveSuspend na chave de software do driver da porta USB. Um valor de 1 desabilita a suspensão seletiva. Um valor de 0 permite a suspensão seletiva.

Por exemplo, as seguintes linhas em Usbport.inf desativam a suspensão seletiva para um controlador Hydra OHCI:

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

Os drivers de cliente não devem tentar determinar se a suspensão seletiva está habilitada antes de enviar solicitações ociosas. Eles devem enviar solicitações ociosas sempre que o dispositivo estiver ocioso. Se a solicitação ociosa falhar, o driver cliente deve redefinir o temporizador ocioso e tentar novamente.