Escrever um driver de cliente UCSI

Um driver USB Type-C Connector System Software Interface (UCSI) serve como o driver do controlador para um sistema USB Type-C com um controlador incorporado (EC).

Se o sistema que implementa o Platform Policy Manager (PPM), conforme descrito na especificação UCSI, em um EC conectado ao sistema sobre:

  • Um transporte ACPI, você não precisa escrever um driver. Carregue o driver in-box fornecido pela Microsoft (UcmUcsiCx.sys e UcmUcsiAcpiClient.sys). (Veja Motorista UCSI).

  • Um transporte não-ACPI, como USB, PCI, I2C ou UART, você precisa escrever um driver cliente para o controlador.

Observação

Se o hardware USB Type-C não tiver a capacidade de lidar com a máquina de estado de entrega de energia (PD), considere escrever um driver de controlador de porta USB Type-C. Para obter mais informações, consulte Escrever um driver de controlador de porta USB Type-C.

A partir do Windows 10, versão 1809, uma nova extensão de classe para UCSI (UcmUcsiCx.sys) foi adicionada, que implementa a especificação UCSI de forma independente de transporte. Com uma quantidade mínima de código, seu driver, que é um cliente para UcmUcsiCx, pode se comunicar com o hardware USB Type-C por transporte não-ACPI. Este tópico descreve os serviços fornecidos pela extensão de classe UCSI e o comportamento esperado do driver cliente.

Especificações oficiais

Aplica-se a:

  • Windows 10, versão 1809

Versão do WDF

  • KMDF versão 1.27

APIs importantes

Referência de extensões de classe UcmUcsiCx

Amostra

Exemplo de driver de cliente UcmUcsiCx

Substitua as partes ACPI com sua implementação para o barramento necessário.

Arquitetura de extensão de classe UCSI

A extensão de classe UCSI, UcmUcsiCx, permite que você escreva um driver que se comunica com seu controlador incorporado usando transporte não-ACPI. O driver do controlador é um driver cliente para UcmUcsiCx. UcmUcsiCx é, por sua vez, um cliente para o gerenciador de conectores USB (UCM). Portanto, a UcmUcsiCx não toma nenhuma decisão política própria. Em vez disso, ele implementa políticas fornecidas pelo UCM. O UcmUcsiCx implementa máquinas de estado para lidar com notificações do Platform Policy Manager (PPM) do driver cliente e envia comandos para implementar decisões de política de UCM, permitindo uma detecção de problemas e tratamento de erros mais confiáveis.

Arquitetura de extensão de classe UCSI.

Gerenciador de políticas do sistema operacional (OPM)

O OS Policy Manager (OPM) implementa a lógica para interagir com o PPM, conforme descrito na especificação UCSI. A OPM é responsável por:

  • Converter políticas UCM em comandos UCSI e notificações UCSI em notificações UCM.
  • Envio de comandos UCSI necessários para inicializar o PPM, detectar erros e mecanismos de recuperação.

Manipulando comandos UCSI

Uma operação típica envolve vários comandos a serem concluídos pelo hardware compatível com UCSI. Por exemplo, vamos considerar o comando GET_CONNECTOR_STATUS.

  1. O firmware PPM envia uma notificação de alteração de conexão para o driver UcmUcsiCx/cliente.
  2. Em resposta, o driver UcmUcsiCx/client envia um comando GET_CONNECTOR_STATUS de volta para o firmware do PPM.
  3. O firmware PPM executa GET_CONNECTOR_STATUS e envia de forma assíncrona uma notificação de comando completo para o driver UcmUcsiCx/cliente. Essa notificação contém dados sobre o status real da conexão.
  4. O driver UcmUcsiCx/cliente processa essas informações de status e envia uma ACK_CC_CI para o firmware do PPM.
  5. O firmware PPM é executado ACK_CC_CI e envia de forma assíncrona uma notificação de comando completo para o driver UcmUcsiCx/cliente.
  6. O driver UcmUcsiCx/client considera o comando GET_CONNECTOR_STATUS como completo.

Comunicação com o Platform Policy Manager (PPM)

UcmUcsiCx abstrai os detalhes do envio de comandos UCSI do OPM para o firmware do PPM e do recebimento de notificações do firmware do PPM. Ele converte comandos PPM em objetos WDFREQUEST e os encaminha para o driver cliente.

  • Notificações de PPM

    O driver cliente notifica UcmUcsiCx sobre notificações PPM do firmware. O driver fornece o bloco de dados UCSI que contém CCI. O UcmUcsiCx encaminha notificações para o OPM e outros componentes que tomam as ações apropriadas com base nos dados.

  • IOCTLs para o driver do cliente

    UcmUcsiCx envia comandos UCSI (através de solicitações IOCTL) para o driver cliente para enviar para o firmware PPM. O driver é responsável por concluir a solicitação depois de ter enviado o comando UCSI para o firmware.

Lidando com transições de energia

O driver do cliente é o proprietário da política de energia.

Se o driver cliente entrar em um estado Dx devido a S0-Idle, o WDF levará o driver para D0 quando o UcmUcsiCx enviar uma IOCTL contendo um comando UCSI para a fila gerenciada por energia do driver cliente. O driver cliente no S0-Idle deve reentrar em um estado de ativação quando houver uma notificação PPM do firmware porque no S0-Idle, as notificações do PPM ainda estão habilitadas.

Antes de começar

  • Determine o tipo de driver que você precisa gravar, dependendo se o hardware ou firmware implementa a máquina de estado PD e o transporte.

    Decisão para escolher a extensão de classe correta. Para obter mais informações, consulte Desenvolvendo drivers do Windows para conectores USB Type-C.

  • Instale o Windows 10 para edições de desktop (Home, Pro, Enterprise e Education).

  • Instale o WDK (Kit de Driver do Windows) mais recente no computador de desenvolvimento. O kit tem os arquivos de cabeçalho e bibliotecas necessários para escrever o driver do cliente, especificamente, você precisará:

    • A biblioteca de stub, (UcmUcsiCxStub.lib). A biblioteca converte as chamadas feitas pelo driver cliente e as passa para a extensão de classe.
    • O arquivo de cabeçalho, Ucmucsicx.h.
    • O driver cliente é executado no modo kernel e se vincula à biblioteca KMDF 1.27.
  • Familiarize-se com o Windows Driver Foundation (WDF). Leitura recomendada: Developing Drivers with Windows Driver Foundation escrito por Penny Orwick e Guy Smith.

1. Registre seu driver cliente com UcmUcsiCx

Em sua EVT_WDF_DRIVER_DEVICE_ADD implementação.

  1. Depois de definir as funções de retorno de chamada de eventos Plug and Play e de gerenciamento de energia (WdfDeviceInitSetPnpPowerEventCallbacks), chame UcmUcsiDeviceInitInitialize para inicializar a estrutura opaca WDFDEVICE_INIT. A chamada associa o driver do cliente à estrutura.

  2. Depois de criar o objeto de dispositivo de estrutura (WDFDEVICE), chame UcmUcsiDeviceInitialize para registrar o mergulhador cliente com UcmUcsiCx.

2. Criar o objeto PPM com UcmUcsiCx

Na implementação do EVT_WDF_DEVICE_PREPARE_HARDWARE, depois de receber a lista de recursos brutos e traduzidos, use os recursos para preparar o hardware. Por exemplo, se o transporte for I2C, leia os recursos de hardware para abrir um canal de comunicação. Em seguida, crie um objeto PPM. Para criar o objeto, você precisa definir determinadas opções de configuração.

  1. Forneça um identificador para a coleção de conectores no dispositivo.

    1. Crie a coleção de conectores chamando UcmUcsiConnectorCollectionCreate.

    2. Enumere os conectores no dispositivo e adicione-os à coleção chamando UcmUcsiConnectorCollectionAddConnector

      // Create the connector collection.
      
      UCMUCSI_CONNECTOR_COLLECTION* ConnectorCollectionHandle;
      
      status = UcmUcsiConnectorCollectionCreate(Device, //WDFDevice
               WDF_NO_OBJECT_ATTRIBUTES,
               ConnectorCollectionHandle);
      
      // Enumerate the connectors on the device.
      // ConnectorId of 0 is reserved for the parent device.
      // In this example, we assume the parent has no children connectors.
      
      UCMUCSI_CONNECTOR_INFO_INIT(&connectorInfo);
      connectorInfo.ConnectorId = 0;
      
      status = UcmUcsiConnectorCollectionAddConnector ( &ConnectorCollectionHandle,
                   &connectorInfo);
      
  2. Decida se deseja habilitar o controlador de dispositivo.

  3. Configure e crie o objeto PPM.

    1. Inicialize uma estrutura UCMUCSI_PPM_CONFIG fornecendo a alça do conector criada na etapa 1.

    2. Defina o membro UsbDeviceControllerEnabled como um valor booleano determinado na etapa 2.

    3. Defina os retornos de chamada do evento em WDF_OBJECT_ATTRIBUTES.

    4. Chame UcmUcsiPpmCreate passando todas as estruturas configuradas.

      UCMUCSIPPM ppmObject = WDF_NO_HANDLE;
      PUCMUCSI_PPM_CONFIG UcsiPpmConfig;
      WDF_OBJECT_ATTRIBUTES attrib;
      
      UCMUCSI_PPM_CONFIG_INIT(UcsiPpmConfig, ConnectorCollectionHandle);
      
      UcsiPpmConfig->UsbDeviceControllerEnabled = TRUE;
      
      WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attrib, Ppm);
      attrib->EvtDestroyCallback = &EvtObjectContextDestroy;
      
      status = UcmUcsiPpmCreate(wdfDevice, UcsiPpmConfig, &attrib, &ppmObject);
      

3. Configurar filas de E/S

UcmUcsiCx envia comandos UCSI para o driver cliente para enviar para o firmware PPM. Os comandos são enviados na forma dessas solicitações IOCTL em uma fila WDF.

O driver cliente é responsável por criar e registrar essa fila para UcmUcsiCx chamando UcmUcsiPpmSetUcsiCommandRequestQueue. A fila deve ser gerenciada por energia.

UcmUcsiCx garante que pode haver no máximo uma solicitação pendente na fila WDF. O driver cliente também é responsável por concluir a solicitação WDF depois de enviar o comando UCSI para o firmware.

Normalmente, o driver configura filas em sua implementação de EVT_WDF_DEVICE_PREPARE_HARDWARE.

WDFQUEUE UcsiCommandRequestQueue = WDF_NO_HANDLE;
WDF_OBJECT_ATTRIBUTES attrib;
WDF_IO_QUEUE_CONFIG queueConfig;

WDF_OBJECT_ATTRIBUTES_INIT(&attrib);
attrib.ParentObject = GetObjectHandle();

// In this example, even though the driver creates a sequential queue,
// UcmUcsiCx guarantees that will not send another request
// until the previous one has been completed.


WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);

// The queue must be power-managed.

queueConfig.PowerManaged = WdfTrue;
queueConfig.EvtIoDeviceControl = EvtIoDeviceControl;

status = WdfIoQueueCreate(device, &queueConfig, &attrib, &UcsiCommandRequestQueue);

UcmUcsiPpmSetUcsiCommandRequestQueue(ppmObject, UcsiCommandRequestQueue);

Além disso, o driver cliente também deve chamar UcmUcsiPpmStart para notificar UcmUcsiCx que o driver está pronto para receber as solicitações IOCTL. Recomendamos que você faça essa chamada em seu EVT_WDF_DEVICE_PREPARE_HARDWARE depois de criar o identificador WDFQUEUE para receber comandos UCSI, por meio de UcmUcsiPpmSetUcsiCommandRequestQueue. Por outro lado, quando o driver não deseja processar mais solicitações, ele deve chamar UcmUcsiPpmStop. Faça isso está em sua EVT_WDF_DEVICE_RELEASE_HARDWARE implementação.

4. Lidar com as solicitações IOCTL

Considere este exemplo de sequência dos eventos que ocorre quando um parceiro USB Type-C é conectado a um conector.

  1. O firmware do PPM determina um evento de anexação e envia uma notificação ao driver do cliente.
  2. O driver cliente chama UcmUcsiPpmNotification para enviar essa notificação para UcmUcsiCx.
  3. UcmUcsiCx notifica a máquina de estado do OPM e envia um comando Get Connector Status para UcmUcsiCx.
  4. UcmUcsiCx cria uma solicitação e envia IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK para o driver cliente.
  5. O driver cliente processa essa solicitação e envia o comando para o firmware do PPM. O driver conclui essa solicitação de forma assíncrona e envia outra notificação para UcmUcsiCx.
  6. Na notificação de conclusão de comando bem-sucedida, a máquina de estado do OPM lê a carga útil (contendo informações de status do conector) e notifica o UCM sobre o evento de anexação Type-C.

Neste exemplo, a carga útil também indicou que uma alteração no status de negociação de entrega de energia entre o firmware e o parceiro de porta foi bem-sucedida. A máquina de estado do OPM envia outro comando UCSI: Obter PDOs. Semelhante ao comando Get Connector Status, quando o comando Get PDOs é concluído com êxito, a máquina de estado do OPM notifica o UCM sobre esse evento.

O manipulador do driver do cliente para EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL é semelhante a este código de exemplo. Para obter informações sobre como lidar com solicitações, consulte Manipuladores de solicitações

void EvtIoDeviceControl(
    _In_ WDFREQUEST Request,
    _In_ ULONG IoControlCode
    )
{
...
    switch (IoControlCode)
    {
    case IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK:
        EvtSendData(Request);
        break;

    case IOCTL_UCMUCSI_PPM_GET_UCSI_DATA_BLOCK:
        EvtReceiveData(Request);
        break;

    default:
        status = STATUS_NOT_SUPPORTED;
        goto Exit;
    }

    status = STATUS_SUCCESS;

Exit:

    if (!NT_SUCCESS(status))
    {
        WdfRequestComplete(Request, status);
    }

}

VOID EvtSendData(
    WDFREQUEST Request
    )
{
    NTSTATUS status;
    PUCMUCSI_PPM_SEND_UCSI_DATA_BLOCK_IN_PARAMS inParams;

    status = WdfRequestRetrieveInputBuffer(Request, sizeof(*inParams),
        reinterpret_cast<PVOID*>(&inParams), nullptr);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    // Build a UCSI command request and send to the PPM firmware.

Exit:
    WdfRequestComplete(Request, status);
}

VOID EvtReceiveData(
    WDFREQUEST Request
    )
{

    NTSTATUS status;

    PUCMUCSI_PPM_GET_UCSI_DATA_BLOCK_IN_PARAMS inParams;
    PUCMUCSI_PPM_GET_UCSI_DATA_BLOCK_OUT_PARAMS outParams;

    status = WdfRequestRetrieveInputBuffer(Request, sizeof(*inParams),
        reinterpret_cast<PVOID*>(&inParams), nullptr);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(*outParams),
        reinterpret_cast<PVOID*>(&outParams), nullptr);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    // Receive data from the PPM firmware.
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }
    WdfRequestSetInformation(Request, sizeof(*outParams));

Exit:
    WdfRequestComplete(Request, status);
}