Compartilhar via


Suspensão seletiva de USB

Observação

Este artigo é para desenvolvedores de driver de dispositivo. Se você estiver enfrentando dificuldades com um dispositivo USB, confira Solucionar problemas comuns de USB

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ão digital e outros tipos de scanners biométricos, só exigem energia intermitente. A suspensão desses dispositivos, quando o dispositivo não está em uso, reduz o consumo geral de energia. Mais importante, qualquer dispositivo que não seja suspenso seletivamente pode impedir que o controlador de host USB desabilite seu agendamento de transferência, que reside na memória do sistema. As transferências de DMA (acesso direto à memória) pelo controlador de host para o agendador podem impedir que os processadores do sistema insiram estados de suspensão mais profundos, como C3.

Há dois mecanismos diferentes para suspender seletivamente um dispositivo USB: IRPs de solicitação ociosa (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) e definir IRPs de energia (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 de cliente, para uma interface em um dispositivo composto, que habilitam a interface para ativação remota com um IRP de ativação de espera (IRP_MN_WAIT_WAKE), devem usar o mecanismo IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) de solicitação ociosa para suspender seletivamente um dispositivo.

Para obter informações sobre a ativação remota, consulte:

A versão do sistema operacional Windows determina a maneira como os drivers para dispositivos não compostos permitem 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 de cliente não devem usar IRPs de energia WDM para suspender seletivamente seus dispositivos. Isso impede que outros dispositivos suspendam seletivamente.
  • Windows Vista e versões posteriores do Windows: os gravadores de driver têm mais opções para desligar dispositivos no Windows Vista e nas versões posteriores do Windows. Embora o Windows Vista dê 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 ativação Função no dispositivo composto, não armada para ativação Dispositivo USB de interface única
Windows 7 Usar IRP de solicitação ociosa Usar o IRP de energia do WDM Usar o IRP de energia do WDM
Windows Server 2008 Usar IRP de solicitação ociosa Usar o IRP de energia do WDM Usar o IRP de energia do WDM
Windows Vista Usar IRP de solicitação ociosa Usar o IRP de energia do WDM Usar o IRP de energia do 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 ociosa USB IRP

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

Na rotina de retorno de chamada, o driver 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 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 barramento não conclui o 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 ociosa pendente até que uma das seguintes condições seja verdadeira:

  • Um IRP de IRP_MN_SUPRISE_REMOVAL ou IRP_MN_REMOVE_DEVICE é 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 energia em funcionamento (D0). Ao receber essa solicitação, o motorista do barramento conclui o IRP de solicitação ociosa pendente 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 um IRP de solicitação ociosa.
  • Os drivers devem enviar apenas um IRP de solicitação ociosa por pilha de dispositivos.

O código de exemplo do WDM a seguir ilustra as etapas que um driver de dispositivo executa para enviar um IRP de solicitação ociosa USB. 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. Alocar e inicializar 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 do cliente deve associar uma rotina de conclusão ao 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 "Rotina de conclusão de IRP de solicitação ociosa usb".

    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 Idle para o driver pai.

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

Cancelando uma solicitação ociosa USB

Em determinadas circunstâncias, um driver de dispositivo pode precisar cancelar uma solicitação ociosa IRP que foi enviada ao motorista do ônibus. Isso pode ocorrer se o dispositivo for removido, ficar ativo após 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 inferior.

O driver do 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 executar:

Cenário Mecanismo de cancelamento de solicitação ociosa
O driver cliente cancelou o IRP ocioso e a pilha do driver USB não chamou a "Rotina de Retorno de Chamada de Notificação Ociosa USB". A pilha do driver USB conclui 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 inferior, o driver do cliente pode enviar uma solicitação D0 .

Como alternativa, o driver pode aguardar a pilha do driver USB concluir o IRP ocioso e, em seguida, enviar o IRP D0 .

Se a rotina de retorno de chamada não puder colocar o dispositivo em um estado de baixa potência 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 seja retornada; portanto, a rotina de retorno de chamada não deve bloquear a espera da 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 baixa potência, o driver do cliente poderá enviar um D0 IRP. A pilha de driver USB conclui o IRP de solicitação ociosa com STATUS_SUCCESS.

Como alternativa, o driver pode cancelar o IRP ocioso, aguardar a pilha do driver USB concluir 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 deverá detectar por que o motorista do ônibus concluiu o IRP. O código de status retornado pode fornecer essas informações. Se o código status não for 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 do IRP de solicitação ociosa não deve bloquear a espera da conclusão de uma solicitação de energia D0 . A rotina de conclusão pode ser chamada no contexto de um IRP de energia pelo driver do hub e o bloqueio em outro IRP de energia na rotina de conclusão pode levar a um deadlock.

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

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 eles 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 do dispositivo cancelou o IRP.
  • Uma alteração de estado de energia do sistema é necessária.
  • No Windows XP, o driver de dispositivo de um dos dispositivos USB conectados não pôde colocar seu dispositivo em D2 enquanto executava sua rotina de retorno de chamada de solicitação ociosa. Como resultado, o motorista do ônibus concluiu todos os IRPs de solicitação ociosa pendentes.
STATUS_POWER_STATE_INVALID Indica que o driver do dispositivo solicitou um estado de energia D3 para seu dispositivo. Quando isso ocorre, o motorista do ônibus conclui todos os IRPs ociosos pendentes com STATUS_POWER_STATE_INVALID.
STATUS_DEVICE_BUSY Indica que o motorista do barramento já mantém um IRP de solicitação ociosa pendente para o dispositivo. Somente um IRP ocioso pode estar pendente por vez para um determinado dispositivo. Enviar vários IRPs de solicitação ociosa é um erro por parte do proprietário da política de energia e deve ser abordado pelo gravador de 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 motorista do ônibus (uma instância do driver do hub ou o driver pai genérico) determina quando é seguro suspender os filhos de seu dispositivo. Se for, ele chamará a rotina de retorno de chamada de notificação ociosa fornecida pelo driver cliente de cada criança.

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 uma IRP_MN_WAIT_WAKE IRP para o dispositivo se o dispositivo precisar estar armado para ativação remota.
  • 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 do WDM chamando PoRequestPowerIrp com o parâmetro PowerState definido como o valor enumerador PowerDeviceD2 (definido em wdm.h; ntddk.h). No Windows XP, um driver não deve colocar seu dispositivo no PowerDeviceD3, mesmo que o dispositivo não esteja armado para ativação remota.

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 energia inferior diretamente sem usar uma rotina de retorno de chamada de notificação ociosa, isso poderá impedir que outros dispositivos na árvore de dispositivos USB sejam suspensos.

O driver do hub e o driver pai genérico USB (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 do estado de energia.

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

As seguintes restrições 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 dentro da rotina de retorno de chamada de notificação ociosa.

Armar dispositivos para ativação 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 de IRP_MN_WAIT_WAKE estiver pendente, a rotina de retorno de chamada deverá enviar uma solicitação IRP_MN_WAIT_WAKE antes de suspender o dispositivo. Para obter mais informações sobre o mecanismo de ativação de espera, consulte Suporte a dispositivos que têm recursos de ativação.

Suspensão global usb

A Especificação USB 2.0 define a suspensão global como a suspensão de todo o barramento atrás de um controlador de host USB, encerrando 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 na 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 do hub USB do Windows 7 suspenderá seletivamente qualquer hub em que todos os seus dispositivos anexados estejam no estado de energia do dispositivo D1, D2 ou D3 . O barramento inteiro entra em suspensão global depois que todos os hubs USB são suspensos seletivamente. A pilha de driver USB do Windows 7 trata um dispositivo como ocioso sempre que o dispositivo está 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 de suspensão global no Windows XP". No entanto, nesse caso, o Dispositivo 3 se qualifica 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 motorista do ônibus suspende o controlador de host USB assim que é seguro fazê-lo.

No Windows Vista, todos os dispositivos USB não 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, estão suspensos. Isso significa que qualquer driver cliente USB que não dá suporte à suspensão seletiva impede que o ônibus 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 todo driver de dispositivo use IRPs de solicitação ociosa para suspender seu dispositivo. Se um driver suspender seu dispositivo com uma solicitação IRP_MN_SET_POWER em vez de um IRP de solicitação ociosa, ele poderá impedir que outros dispositivos sejam suspensos.

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

Diagrama ilustrando uma suspensão global no Windows XP.

Nessa figura, o dispositivo 3 está no estado de energia D3 e não tem um IRP de solicitação ocioso pendente. O dispositivo 3 não se qualifica como um dispositivo ocioso para fins de uma suspensão global no Windows XP, pois não tem uma solicitação ociosa IRP pendente com seu pai. Isso impede que o motorista do ônibus 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 limpo do Windows XP, Windows Vista e versões posteriores do Windows.

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

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

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

[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 do cliente deverá redefinir o temporizador ocioso e tentar novamente.