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ções para dispositivos de várias funções do Barramento Serial Universal (USB) 3.0 (dispositivos compostos). Neste artigo, você aprenderá a implementar esses recursos em um driver que controla um dispositivo composto. O artigo se aplica a drivers compostos que substituem Usbccgp.sys.

A especificação do Barramento Serial Universal (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 potência, 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 de teclado em estado de trabalho, mas não move o mouse por um período de tempo. O driver do cliente para o mouse pode detectar o estado ocioso da função e enviar a função para suspender o estado enquanto a função de teclado permanece no estado de trabalho.

Todo o dispositivo pode fazer a transição para suspender o estado, 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 em todos os processos de entrada e saída de suspensão do dispositivo.

Semelhante ao recurso de ativação remota de um dispositivo USB 2.0 (consulte Ativação remota de dispositivos USB), uma função individual em um dispositivo composto USB 3.0 pode acordar de um estado de baixa energia sem afetar os estados de energia de outras funções. Esse recurso é chamado de ativação remota de função. O recurso é explicitamente habilitado pelo host enviando uma solicitação de protocolo que define os bits de ativação remota no firmware do dispositivo. Esse processo é chamado de armar a função para ativação remota. 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 ativação remota, a função (quando estiver no estado de suspensão) reterá energia suficiente para gerar um sinal de retomada de ativação quando um evento de usuário ocorrer no dispositivo físico. Como resultado desse sinal de retomada, o driver cliente pode sair do estado de suspensão da função associada. No exemplo da função do mouse no dispositivo composto, quando o usuário move 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 driver USB detecta qual função acordou e propaga a notificação para o driver cliente da função correspondente. O driver do cliente pode ativar a função e entrar no estado de trabalho.

Para o driver cliente, as etapas para enviar uma função para suspender o estado e ativar a função são semelhantes a um driver de dispositivo de função única enviando todo o dispositivo para suspender o estado. O procedimento a seguir resume essas etapas.

  1. Detectar quando a função associada está em estado ocioso.
  2. Enviar um IRP (pacote de solicitação de E/S) ocioso.
  3. Envie uma solicitação para armar sua função para ativação remota enviando um IRP (pacote de solicitação de E/S) de espera.
  4. Faça a transição da função para um estado de baixa potência enviando IRPs de energia Dx (D2 ou D3).

Para obter mais informações sobre as etapas anteriores, confira "Como enviar um IRP de solicitação ociosa por USB" em Suspensão Seletiva por USB. Um driver composto cria um PDO (objeto de dispositivo físico) para cada função no dispositivo composto e manipula as solicitações de energia enviadas pelo driver cliente (o FDO da pilha do dispositivo de função). Para que um driver de cliente entre e saia com êxito do estado de suspensão para sua função, o driver composto deve dar suporte a recursos de suspensão de função e ativação remota e processar as solicitações de energia recebidas.

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

Etapa 1: Determinar se a pilha de driver USB dá suporte à suspensão da função

Na rotina do dispositivo inicial (IRP_MN_START_DEVICE) do driver composto, execute as seguintes etapas:

  1. Chame a rotina de USBD_QueryUsbCapability para determinar se a pilha de driver USB subjacente dá suporte à funcionalidade de suspensão de função. A chamada requer um identificador USBD válido que você obteve em sua chamada anterior para a rotina de USBD_CreateHandle .

Uma chamada bem-sucedida para USBD_QueryUsbCapability determina se a pilha de driver USB subjacente dá suporte à suspensão da função. A chamada pode retornar um código de erro indicando que a pilha de driver USB não dá suporte à suspensão de função ou que o dispositivo anexado não é um dispositivo usb 3.0 de várias funções.

  1. Se a chamada USBD_QueryUsbCapability indicar que há suporte para suspensão de função, registre o dispositivo composto com a pilha de driver USB subjacente. Para registrar o dispositivo composto, você deve enviar uma solicitação de controle de E/S IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE. 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. Defina CapabilityFunctionSuspend como 1 para indicar que o driver composto dá suporte à suspensão da função.

Para obter um exemplo de código que mostra como determinar se a pilha de driver USB dá suporte à suspensão da função, consulte USBD_QueryUsbCapability.

Etapa 2: manipular o IRP ocioso

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

Observação

O mecanismo IRP ocioso é opcional para drivers cliente de dispositivos USB 3.0. No entanto, a maioria dos drivers de cliente é gravada para dar suporte a dispositivos USB 2.0 e USB 3.0. Para dar suporte a dispositivos USB 2.0, o driver deve enviar o IRP ocioso, pois o driver composto depende desse IRP para acompanhar o estado de energia de cada função. Se todas as funções estiverem ociosas, o driver composto enviará todo o dispositivo para suspender o estado.

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

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

O driver cliente pode enviar uma solicitação para armar sua função para ativação remota enviando um IRP IRP_MJ_POWER com código de função secundário definido como IRP_MN_WAIT_WAKE (IRP de ativação de espera). O driver cliente envia essa solicitação somente se o driver quiser entrar no estado de trabalho como resultado de um evento de usuário.

Ao receber o IRP de espera, o driver composto deve enviar o IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION solicitação de controle de E/S para a pilha de driver USB. A solicitação permite que a pilha de driver 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 de solicitação. Um dos valores que o driver composto deve especificar é o identificador de função para a função que está armada para ativação remota. 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 para a 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 driver USB durante o processo de ativação quando recebe uma notificação sobre o sinal de retomada. Durante esse tempo, a pilha de driver USB também invoca a rotina de conclusão de ativação remota.

O driver composto deve manter o IRP de espera pendente e enfileira-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 driver USB.

Etapa 4: Enviar uma solicitação para armar a função para ativação remota

Para enviar a função para um estado de baixa potência, o driver cliente envia um IRP IRP_MN_SET_POWER com a solicitação para alterar o estado de energia do dispositivo WDM (Modelo de Driver do Windows) para D2 ou D3. Normalmente, o driver cliente envia D2 IRP se o driver enviou um IRP de ativação de espera anteriormente para solicitar a ativação remota. Caso contrário, o driver cliente enviará o IRP D3 .

Ao receber o IRP D2 , o driver composto deve primeiro determinar se um IRP de espera está pendente de uma solicitação anterior enviada pelo driver cliente. Se esse IRP estiver pendente, o driver composto deverá armar a função para ativação remota. Para fazer isso, o driver composto deve enviar uma solicitação de controle 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 o URB para uma solicitação de 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 instalaçã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;

}

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

Comentários

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

Ao receber o pacote de notificação, a pilha de driver 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) que foi especificada na solicitação e implementada pelo driver composto. Quando a notificação atinge o driver composto, ela notifica o driver cliente correspondente de que a função entrou no estado de trabalho concluindo o IRP de ativação de espera que o driver cliente havia enviado anteriormente.

Na rotina de conclusão (ativação remota), o driver composto deve enfileirar um item de trabalho para concluir o IRP de ativação de espera pendente. Para dispositivos USB 3.0, o driver composto ativa apenas a função que envia o sinal de retomada e deixa outras funções no estado de suspensão. O enfileiramento do 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 de espera e invoca a rotina de conclusão do driver cliente. Em seguida, a rotina de conclusão envia um IRP D0 para inserir a função no estado de trabalho. Antes de concluir o IRP de espera, o driver composto deve chamar PoSetSystemWake para marcar o IRP de espera como aquele que contribuiu para acordar o sistema do estado de suspensão. O power manager registra um evento ETW (Rastreamento de Eventos para Windows) (acessível no canal do sistema global) que inclui informações sobre dispositivos que acordaram o sistema.