Partilhar via


Como implementar a suspensão de função em um driver composto

Este artigo fornece uma visão geral dos recursos de suspensão de função e ativação remota de função para dispositivos multifuncionais Universal Serial Bus (USB) 3.0 (dispositivos compostos). Neste artigo, você aprenderá sobre como implementar esses recursos em um driver que controla um dispositivo composto. O artigo aplica-se a controladores compostos que substituem Usbccgp.sys.

A especificação Universal Serial Bus (USB) 3.0 define um novo recurso chamado suspensão de função. O recurso permite que uma função individual de um dispositivo composto entre em um estado de baixa energia, independentemente de outras funções. Considere um dispositivo composto que define uma função para o teclado e outra função para o mouse. O usuário mantém a função do teclado em estado de funcionamento, mas não move o mouse por um período de tempo. O driver do cliente para o mouse pode detetar o estado ocioso da função e enviar a função para o estado de suspensão enquanto a função do teclado permanece em estado de funcionamento.

Todo o dispositivo pode fazer a transição para o estado de suspensão, independentemente do estado de energia de qualquer função dentro do dispositivo. Se uma função específica e todo o dispositivo entrarem no estado de suspensão, o estado de suspensão da função será mantido enquanto o dispositivo estiver em estado de suspensão e durante todos os processos de entrada e saída de suspensão do dispositivo.

Semelhante ao recurso de despertar remoto de um dispositivo USB 2.0 (consulte Despertar remoto de dispositivos USB), uma função individual em um dispositivo composto USB 3.0 pode despertar de um estado de baixo consumo de energia sem afetar os estados de energia de outras funções. Esse recurso é chamado de ativação remota da função. O recurso é explicitamente ativado pelo host enviando uma solicitação de protocolo que define os bits de ativação remota no firmware do dispositivo. Este processo é chamado de armar a função para despertar remoto. Para obter informações sobre os bits relacionados à ativação remota, consulte a Figura 9-6 na especificação USB oficial.

Se uma função estiver armada para despertar remotamente, a função (quando em estado de suspensão) retém energia suficiente para gerar um sinal de retomada de ativação quando ocorre um evento de usuário no dispositivo físico. Como resultado desse sinal de retomada, o driver do cliente pode então sair do estado de suspensão da função associada. No exemplo para a função de mouse no dispositivo composto, quando o usuário mexe o mouse que está em estado ocioso, a função do mouse envia um sinal de retomada para o host. No host, a pilha de drivers USB deteta qual função acordou e propaga a notificação para o driver do cliente da função correspondente. O driver do cliente pode então ativar a função e entrar no estado de trabalho.

Para o driver do cliente, as etapas para enviar uma função para o estado de suspensão e despertar a função são semelhantes às de um driver de dispositivo de função única que envia o dispositivo inteiro para o estado de suspensão. O procedimento a seguir resume essas etapas.

  1. Detete quando a função associada está em estado ocioso.
  2. Envie um pacote de solicitação de E/S inativo (IRP).
  3. Envie uma solicitação para ativar a sua função para acordar remotamente, enviando um pacote de solicitação de E/S de espera e despertar (IRP).
  4. Transfira a função para um estado de baixa potência enviando IRPs de potência Dx (D2 ou D3).

Para obter mais informações sobre as etapas anteriores, consulte "Enviar um pedido IRP de inatividade USB" em Suspensão seletiva USB. Um driver composto cria um objeto de dispositivo físico (DOP) para cada função no dispositivo composto e lida com solicitações de energia enviadas pelo driver do cliente (o FDO da pilha de dispositivos de função). Para que um driver cliente entre e saia com êxito do estado de suspensão para sua função, o driver composto deve suportar recursos de suspensão de função e ativação remota e processar as solicitações de energia recebidas.

No Windows 8, a pilha de controladores USB para dispositivos USB 3.0 dá suporte a essas funcionalidades. Além disso, a função de suspensão e a função de ativação remota foram adicionadas ao driver pai genérico USB fornecido pela Microsoft (Usbccgp.sys), que é o driver composto padrão do Windows. Se você estiver escrevendo um driver composto personalizado, seu driver deverá lidar com solicitações relacionadas à suspensão de função e solicitações de ativação remota, de acordo com o procedimento a seguir.

Etapa 1: Verifique se a pilha de drivers USB suporta suspensão de função

Na rotina do dispositivo de inicialização (IRP_MN_START_DEVICE) do driver composto, execute as seguintes etapas:

  1. Chame a rotina USBD_QueryUsbCapability para determinar se a pilha de drivers USB subjacente suporta a capacidade de suspensão de funções. A chamada requer um identificador USBD válido que você obteve em sua chamada anterior para a rotina USBD_CreateHandle .

Uma chamada bem-sucedida para USBD_QueryUsbCapability determina se a pilha de driver USB subjacente suporta suspensão de função. A chamada pode devolver um código de erro indicando que o stack de drivers USB não suporta a suspensão de função ou que o dispositivo conectado não é um dispositivo multifuncional USB 3.0.

  1. Se a chamada USBD_QueryUsbCapability indicar que a suspensão de função é suportada, registe o dispositivo composto com a pilha de driver USB subjacente. Para registar o dispositivo composto, deve enviar um pedido IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE de controle de E/S. Para obter mais informações sobre essa solicitação, consulte Como registrar um dispositivo composto.

A solicitação de registro usa a estrutura REGISTER_COMPOSITE_DEVICE para especificar essas informações sobre o driver composto. Certifique-se de definir CapabilityFunctionSuspend como 1 para indicar que o driver composto suporta suspensão de função.

Para obter um exemplo de código que mostra como determinar se a pilha de drivers USB suporta suspensão de função, consulte USBD_QueryUsbCapability.

Etapa 2: Manipular o IRP ocioso

O driver do cliente pode enviar um IRP ocioso (consulte IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION). A solicitação é enviada depois que o driver do cliente detetou um estado ocioso para a função. O IRP contém um ponteiro para a rotina de conclusão de retorno de chamada (chamada retorno de chamada inativo) que é implementada pelo driver do cliente. Dentro do retorno de chamada ocioso, o cliente executa tarefas, como cancelar transferências de E/S pendentes, imediatamente antes de enviar a função para o estado de suspensão.

Observação

O mecanismo IRP ocioso é opcional para drivers de cliente de dispositivos USB 3.0. No entanto, a maioria dos drivers de cliente são escritos para suportar dispositivos USB 2.0 e USB 3.0. Para suportar dispositivos USB 2.0, o driver deve enviar o IRP ocioso, porque o driver composto depende desse IRP para rastrear o estado de energia de cada função. Se todas as funções estiverem ociosas, o driver composto enviará todo o dispositivo para o estado de suspensão.

Ao receber o IRP ocioso do driver do cliente, o driver composto deve invocar imediatamente o retorno de chamada ocioso para notificar o driver do cliente de que o driver do cliente pode enviar a função para o estado de suspensão.

Etapa 3: Enviar uma solicitação de notificação de ativação remota

O driver do cliente pode enviar uma solicitação para armar sua função para despertar remoto enviando um IRP IRP_MJ_POWER com código de função secundária definido como IRP_MN_WAIT_WAKE (IRP de espera-vigília). O driver do cliente envia essa solicitação somente se o driver deseja entrar no estado de trabalho como resultado de um evento de usuário.

Ao receber o IRP de espera-despertar, o driver composto deve enviar a solicitação de controle de E/S IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION para a pilha de drivers USB. A solicitação permite que a pilha de drivers USB notifique o driver composto quando a pilha receber a notificação sobre o sinal de retomada. O IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION usa a estrutura REQUEST_REMOTE_WAKE_NOTIFICATION para especificar os parâmetros da solicitação. Um dos valores que o driver composto deve especificar é o identificador de função para a função que está armada para despertar remoto. O driver composto obteve esse identificador em uma solicitação anterior para registrar o dispositivo composto com a pilha de driver USB. Para obter mais informações sobre solicitações de registro de driver composto, consulte Como registrar um dispositivo composto.

No IRP da solicitação, o driver composto fornece um ponteiro para uma rotina de conclusão (ativação remota), que é implementada pelo driver composto.

O código de exemplo a seguir mostra como enviar uma solicitação de ativação remota.

/*++

Description:
    This routine sends a IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION request
    to the USB driver stack. The IOCTL is completed by the USB driver stack
    when the function wakes up from sleep.

    Parameters:
    parentFdoExt: The device context associated with the FDO for the
    composite driver.

    functionPdoExt: The device context associated with the PDO (created by
    the composite driver) for the client driver.
--*/

VOID
SendRequestForRemoteWakeNotification(
    __inout PPARENT_FDO_EXT parentFdoExt,
    __inout PFUNCTION_PDO_EXT functionPdoExt
)

{
    PIRP                                irp;
    REQUEST_REMOTE_WAKE_NOTIFICATION    remoteWake;
    PIO_STACK_LOCATION                  nextStack;
    NTSTATUS                            status;

    // Allocate an IRP
    irp =  IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (irp)
    {

        //Initialize the USBDEVICE_REMOTE_WAKE_NOTIFICATION structure
        remoteWake.Version = 0;
        remoteWake.Size = sizeof(REQUEST_REMOTE_WAKE_NOTIFICATION);
        remoteWake.UsbdFunctionHandle = functionPdoExt->functionHandle;
        remoteWake.Interface = functionPdoExt->baseInterfaceNumber;

        nextStack = IoGetNextIrpStackLocation(irp);

        nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
        nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION;

        nextStack->Parameters.Others.Argument1 = &remoteWake;

        // Caller's completion routine will free the IRP when it completes.

        SetCompletionRoutine(functionPdoExt->debugLog,
                             parentFdoExt->fdo,
                             irp,
                             CompletionRemoteWakeNotication,
                             (PVOID)functionPdoExt,
                             TRUE, TRUE, TRUE);

        // Pass the IRP
        IoCallDriver(parentFdoExt->topDevObj, irp);

    }

    return;
}

A solicitação IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION é concluída pela pilha de drivers USB durante o processo de ativação, quando recebe uma notificação sobre o sinal de retomada. Durante esse tempo, a pilha de drivers USB também invoca a rotina de conclusão de ativação remota.

O driver composto deve manter o IRP wait-wake pendente e enfileirá-lo para processamento posterior. O driver composto deve concluir esse IRP quando a rotina de conclusão de ativação remota do driver for invocada pela pilha de drivers USB.

Passo 4: Envie um pedido para armar a função para despertar remoto

Para enviar a função para um estado de baixo consumo de energia, o driver do cliente envia um IRP IRP_MN_SET_POWER com a solicitação para alterar o estado de energia do dispositivo WDM (Windows Driver Model) para D2 ou D3. Normalmente, o driver do cliente envia IRP D2 se o driver enviou um IRP de espera-despertar mais cedo para solicitar a ativação remota. Caso contrário, o driver do cliente envia o IRP D3.

Ao receber o IRP D2 , o driver composto deve primeiro determinar se um IRP de espera-despertar está pendente de uma solicitação anterior enviada pelo driver do cliente. Se esse IRP estiver pendente, o driver composto deve preparar a função para o despertar remoto. Para fazer isso, o driver composto deve enviar uma solicitação de controle de SET_FEATURE para a primeira interface da função, para permitir que o dispositivo envie um sinal de retomada. Para enviar a solicitação de controle, aloque uma estrutura URB chamando a rotina USBD_UrbAllocate e chame a macro UsbBuildFeatureRequest para formatar a URB para uma solicitação SET_FEATURE. Na chamada, especifique URB_FUNCTION_SET_FEATURE_TO_INTERFACE como o código de operação e o USB_FEATURE_FUNCTION_SUSPEND como o seletor de recursos. No parâmetro Index , defina Bit 1 do byte mais significativo. Esse valor é copiado para o campo wIndex no pacote de configuração da transferência.

O exemplo a seguir mostra como enviar uma solicitação de controle SET_FEATURE.

/*++

Routine Description:

Sends a SET_FEATURE for REMOTE_WAKEUP to the device using a standard control request.

Parameters:
parentFdoExt: The device context associated with the FDO for the
composite driver.

functionPdoExt: The device context associated with the PDO (created by
the composite driver) for the client driver.

Returns:

NTSTATUS code.

--*/
VOID
    NTSTATUS SendSetFeatureControlRequestToSuspend(
    __inout PPARENT_FDO_EXT parentFdoExt,
    __inout PFUNCTION_PDO_EXT functionPdoExt,
    )

{
    PURB                            urb
    PIRP                            irp;
    PIO_STACK_LOCATION              nextStack;
    NTSTATUS                        status;

    status = USBD_UrbAllocate(parentFdoExt->usbdHandle, &urb);

    if (!NT_SUCCESS(status))
    {
        //USBD_UrbAllocate failed.
        goto Exit;
    }

    //Format the URB structure.
    UsbBuildFeatureRequest (
        urb,
        URB_FUNCTION_SET_FEATURE_TO_INTERFACE, // Operation code
        USB_FEATURE_FUNCTION_SUSPEND,          // feature selector
        functionPdoExt->firstInterface,           // first interface of the function
        NULL);

    irp =  IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (!irp)
    {
        // IoAllocateIrp failed.
        status = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;
    }

    nextStack = IoGetNextIrpStackLocation(irp);

    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;

    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;

    //  Attach the URB to the IRP.
    USBD_AssignUrbToIoStackLocation(nextStack, (PURB)urb);

    // Caller's completion routine will free the IRP when it completes.
    SetCompletionRoutine(functionPdoExt->debugLog,
        parentFdoExt->fdo,
        irp,
        CompletionForSuspendControlRequest,
        (PVOID)functionPdoExt,
        TRUE, TRUE, TRUE);


    // Pass the IRP
    IoCallDriver(parentFdoExt->topDevObj, irp);


Exit:
    if (urb)
    {
        USBD_UrbFree( parentFdoExt->usbdHandle, urb);
    }

    return status;

}

O driver composto envia então o IRP D2 para a pilha de drivers USB. Se todas as outras funções estiverem em estado de suspensão, a pilha de drivers USB suspenderá a porta manipulando determinados registros de porta no controlador.

Observações

No exemplo da função do mouse, como o recurso de ativação remota está ativado (consulte a etapa 4), a função do mouse gera um sinal de retomada no fio a montante para o controlador host quando o usuário mexe o mouse. Em seguida, o controlador notifica a pilha de drivers USB enviando um pacote de notificação que contém informações sobre a função que despertou. Para obter informações sobre a notificação de ativação de função, consulte a Figura 8-17 na especificação USB 3.0.

Ao receber o pacote de notificação, a pilha de drivers USB conclui a solicitação de IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION pendente (consulte a etapa 3) e invoca a rotina de retorno de chamada de conclusão (ativação remota) especificada na solicitação e implementada pelo driver composto. Quando a notificação chega ao controlador composto, ele notifica o controlador cliente correspondente de que a função entrou no estado operacional, completando o IRP de espera-despertar que o controlador cliente tinha enviado anteriormente.

Na rotina de conclusão (ativação remota), o driver composto deve enfileirar um item de trabalho para concluir o IRP pendente de vigília-espera. Para dispositivos USB 3.0, o driver composto desperta apenas a função que envia o sinal de retomada e deixa outras funções em estado de suspensão. Enfileirar o item de trabalho garante a compatibilidade com a implementação existente para drivers de função de dispositivos USB 2.0. Para obter informações sobre como enfileirar um item de trabalho, consulte IoQueueWorkItem.

O thread de trabalho conclui o IRP wait-wake e invoca a rotina de conclusão do driver do cliente. A rotina de conclusão envia um IRP D0 para inserir a função no estado de trabalho. Antes de concluir o IRP wait-wake, o driver composto deve chamar PoSetSystemWake para marcar o IRP wait-wake como aquele que contribuiu para despertar o sistema do estado de suspensão. O gerenciador de energia registra um evento ETW (Event Tracing for Windows) (visível no canal global do sistema) que inclui informações sobre dispositivos que acordaram o sistema.