Compartilhar 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. Essa funcionalidade é útil em computadores portáteis, pois ajuda a conservar a energia da bateria. Muitos dispositivos, como scanners biométricos, só exigem energia intermitentemente. Suspender esses dispositivos, quando eles não estão 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 hospedeiro para o agendador podem impedir que os processadores do sistema entrem em estados de suspensão mais profundos, como C3.

A suspensão seletiva está habilitada por padrão. A Microsoft recomenda fortemente não desabilitar a suspensão seletiva.

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

Para suspender seletivamente um dispositivo USB, existem dois mecanismos diferentes: IRPs de pedido de inatividade (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) e IRPs de ajuste de energia (IRP_MN_SET_POWER). O mecanismo a ser usado depende do tipo de dispositivo: composto ou não compatível.

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 de solicitação ociosa (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) para suspender seletivamente um dispositivo.

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

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 cliente informa o motorista do ônibus enviando uma solicitação IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION) ociosa. Depois que o motorista de ônibus determina que é seguro colocar o dispositivo em um estado de baixa potência, ele chama a rotina de retorno de chamada que o driver do dispositivo cliente passou pela 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 para notificação de inatividade, consulte Implementando uma rotina de retorno de chamada IRP de solicitação ociosa USB.

O motorista do ônibus 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 a solicitação ociosa IRP pendente até que uma das seguintes condições seja verdadeira:

  • Um IRP_MN_SURPRISE_REMOVAL ou IRP_MN_REMOVE_DEVICE IRP é recebido. Quando um desses IRPs é recebido, o IRP de requisição em estado ocioso é concluído com STATUS_CANCELLED.
  • O motorista do ônibus recebe uma solicitação para colocar o dispositivo em um estado de energia funcional (D0). Ao receber essa solicitação, o driver do ônibus conclui a requisiçã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 um IRP de solicitação ociosa.
  • Os drivers devem enviar apenas uma solicitação de estado ocioso IRP 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 pedido de inatividade USB. A verificação de erros é 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 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 Implementação de uma 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 ociosa para o driver pai.

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

Cancelando uma requisição ociosa de USB

Em determinadas circunstâncias, um driver de dispositivo pode precisar cancelar uma solicitação irp ociosa enviada ao motorista do ônibus. Essa situação poderá ocorrer se o dispositivo for removido, se tornar ativo após ficar ocioso e enviar a solicitação de inatividade, ou se todo o sistema estiver fazendo a transição para um estado de menor consumo de energia do sistema.

O driver cliente cancela o IRP ocioso ao chamar 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 cancela o IRP ocioso, e a rotina de callback de notificação ociosa USB não foi chamada. A pilha de 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 cancela o IRP ocioso, a pilha de drivers USB chama a rotina de callback de notificação de inatividade USB, e ela ainda não retornou. É possível que a função de callback de notificação ociosa USB seja invocada, mesmo que o driver cliente tenha solicitado o cancelamento no IRP. Nesse caso, a rotina de callback do driver cliente ainda deve desligar o dispositivo enviando-o para um estado de energia mais baixo sincronamente.

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

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

Se a função de callback não puder colocar o dispositivo em um estado de baixa potência devido à memória insuficiente para alocar um IRP de energia, deverá cancelar o IRP de inatividade e sair imediatamente. O IRP ocioso não é concluído até que a rotina de retorno de chamada retorne. 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 potência. Se o dispositivo já estiver em um estado de baixa potência, o driver cliente poderá enviar um IRP D0 . A pilha de driver USB conclui a requisição ociosa IRP com STATUS_SUCCESS.

Como alternativa, o driver pode cancelar o IRP ocioso, aguardar a pilha do driver USB concluir o IRP ocioso e então enviar um IRP D0.

Rotina de conclusão de IRP de solicitação de inatividade do USB

Em muitos casos, um motorista de ônibus pode chamar a rotina de conclusão de IRP referente à solicitação de inatividade de um motorista. Se essa situação ocorrer, um motorista 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 de status não for STATUS_POWER_STATE_INVALID, o driver deve colocar seu dispositivo em D0 caso o dispositivo ainda não esteja 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 bloquear 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 de status comuns:

Código de status Descrição
STATUS_SUCCESS Indica que o dispositivo não deve mais ser suspenso. No entanto, os motoristas ou responsáveis devem verificar se seus dispositivos estão ligados e colocá-los em D0 se eles ainda não estiverem em D0.
STATUS_CANCELADO 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.
STATUS_POWER_STATE_INVALID (Estado de energia inválido) Indica que o driver do dispositivo solicitou um estado de energia D3 para seu dispositivo. Quando essa solicitação ocorre, o motorista de ônibus finaliza todos os IRPs ociosos pendentes com STATUS_POWER_STATE_INVALID.
STATUS_DISPOSITIVO_OCUPADO Indica que o motorista do ônibus já contém uma IRP de solicitação ociosa pendente para o dispositivo. Somente um IRP ocioso pode estar pendente por vez para um dispositivo específico. Enviar vários IRPs de solicitação ociosa é um erro por parte do responsável pela política de energia. O autor do driver aborda o erro.

O exemplo de código a seguir mostra uma amostra de implementação para a rotina de conclusão da requisição inativa.

/*
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 motorista pai genérico) determina quando é seguro suspender os filhos de seu dispositivo. Se estiver, ele chamará a rotina de notificação de inatividade 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 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).

O driver do hub e o Driver Pai Genérico USB (Usbccgp.sys) chamam a rotina de retorno de notificação de inatividade em IRQL = PASSIVE_LEVEL. A rotina de retorno de chamada pode ser 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 seguintes restrições se aplicam a rotinas de retorno de chamada de notificação de requisição inativa:

  • 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 callback.
  • O driver de dispositivo não deve solicitar mais de um IRP de energia dentro da rotina de callback de notificação de inatividade.

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 IRP_MN_WAIT_WAKE pendente. Se nenhuma solicitação IRP_MN_WAIT_WAKE estiver pendente, a função de callback deve enviar uma solicitação IRP_MN_WAIT_WAKE antes de suspender o dispositivo. Para obter mais informações sobre o mecanismo de espera para ativação, consulte Suporte a Dispositivos com Capacidades de Ativação.

Suspensão global de USB

A Especificação USB 2.0 define a suspensão global como a suspensão de todo o ônibus atrás de um controlador de host USB, deixando de usar 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 inativo em sua porta upstream e entram no estado de suspensão automaticamente. 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

O driver do hub USB suspende seletivamente qualquer hub cujos 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 trata um dispositivo como ocioso sempre que o dispositivo está em um estado WDM D1, D2 ou D3.