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.
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.
- O firmware PPM envia uma notificação de alteração de conexão para o driver UcmUcsiCx/cliente.
- Em resposta, o driver UcmUcsiCx/client envia um comando GET_CONNECTOR_STATUS de volta para o firmware do PPM.
- 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.
- O driver UcmUcsiCx/cliente processa essas informações de status e envia uma ACK_CC_CI para o firmware do PPM.
- O firmware PPM é executado ACK_CC_CI e envia de forma assíncrona uma notificação de comando completo para o driver UcmUcsiCx/cliente.
- 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.
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.
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.
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.
Forneça um identificador para a coleção de conectores no dispositivo.
Crie a coleção de conectores chamando UcmUcsiConnectorCollectionCreate.
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);
Decida se deseja habilitar o controlador de dispositivo.
Configure e crie o objeto PPM.
Inicialize uma estrutura UCMUCSI_PPM_CONFIG fornecendo a alça do conector criada na etapa 1.
Defina o membro UsbDeviceControllerEnabled como um valor booleano determinado na etapa 2.
Defina os retornos de chamada do evento em WDF_OBJECT_ATTRIBUTES.
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.
- O firmware do PPM determina um evento de anexação e envia uma notificação ao driver do cliente.
- O driver cliente chama UcmUcsiPpmNotification para enviar essa notificação para UcmUcsiCx.
- UcmUcsiCx notifica a máquina de estado do OPM e envia um comando Get Connector Status para UcmUcsiCx.
- UcmUcsiCx cria uma solicitação e envia IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK para o driver cliente.
- 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.
- 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);
}