Compartilhar via


Publicação da interface do dispositivo para uma porta serial gerenciada SerCx ou SerCx2

A partir do Windows 10 versão 1903 e posterior, o SerCx e o SerCx2 incluem suporte para publicar uma interface de GUID_DEVINTERFACE_COMPORT dispositivo. Aplicativos e serviços em um sistema podem usar essa interface de dispositivo para interagir com a porta serial.

Esse recurso pode ser habilitado em plataformas baseadas em SoC que apresentam um UART integrado com um driver de cliente SerCx/SerCx2, se o UART for exposto como uma porta física ou se aplicativos regulares (UWP ou Win32) precisarem se comunicar diretamente com um dispositivo conectado ao UART. Isso ocorre em oposição ao acesso ao controlador SerCx/SerCx2 por meio de uma ID de conexão, que permite exclusivamente o acesso ao UART a partir de um driver periférico dedicado.

Ao usar esse recurso para portas seriais gerenciadas SerCx/SerCx2, um número de porta COM não é atribuído para esses dispositivos, e nenhum link simbólico é criado. Portanto, os aplicativos devem usar a abordagem descrita neste documento para abrir a porta serial como uma interface de dispositivo.

Usar a interface do dispositivo (GUID_DEVINTERFACE_COMPORT) é a maneira recomendada de descobrir e acessar uma porta COM. O uso de nomes de porta COM herdados é propenso a colisões de nomes e não fornece notificações de alteração de estado a um cliente. O uso dos nomes de porta COM herdados não é recomendado e não é compatível com SerCx2 e SerCx.

Habilitar a criação da interface do dispositivo

Veja a seguir as instruções para habilitar a criação da interface do dispositivo. As portas seriais são exclusivas, ou seja, se a porta serial for acessível como uma interface de dispositivo, um recurso de conexão na ACPI não deve ser fornecido a nenhum outro dispositivo, por exemplo, nenhum UARTSerialBusV2 recurso deve ser fornecido a nenhum outro dispositivo no sistema; a porta deve ser acessível exclusivamente por meio da interface do dispositivo.

Configuração ACPI

Um fabricante ou integrador de sistema pode habilitar esse comportamento modificando a definição ACPI (ASL) do dispositivo existente SerCx/SerCx2 para adicionar uma _DSD definição para propriedades de dispositivo de valor-chave com UUID daffd814-6eba-4d8c-8a91-bc9bbf4aa301. Dentro dessa definição, a propriedade SerCx-FriendlyName é definida com uma descrição específica do sistema da porta serial, por exemplo, UART0, UART1 etc.

Exemplo de definição de dispositivo (excluindo informações específicas do fornecedor necessárias para definir o dispositivo):

    Device(URT0) {
        Name(_HID, ...)
        Name(_CID, ...)

        Name(_DSD, Package() {
            ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
            Package() {
                Package(2) {"SerCx-FriendlyName", "UART0"}
            }
        })
    }

O UUID especificado (daffd814-6eba-4d8c-8a91-bc9bbf4aa301) deve ser usado, e a entrada SerCx-FriendlyName deve ser definida para SerCx/SerCx2 para criar a interface do dispositivo.

Chave do Registro

Para fins de desenvolvimento, o SerCxFriendlyName também pode ser configurado como uma propriedade na chave de hardware do dispositivo no registro. O método CM_Open_DevNode_Key pode ser usado para acessar a chave de hardware do dispositivo e adicionar a propriedade SerCxFriendlyName ao dispositivo, que é usada pelo SerCx/SerCx2 para recuperar o nome amigável para a interface do dispositivo.

Não é recomendado definir essa chave por meio de uma extensão INF. Ela é fornecida principalmente para fins de teste e desenvolvimento. A abordagem recomendada é habilitar o recurso via ACPI, conforme documentado acima.

Interface de dispositivo

Se a FriendlyName for definido usando os métodos acima, SerCx/SerCx2 publicará uma interface de GUID_DEVINTERFACE_COMPORT dispositivo para o controlador. Essa interface de dispositivo terá a propriedade DEVPKEY_DeviceInterface_Serial_PortName definida como o nome amigável especificado, que pode ser usado por aplicativos para localizar um controlador/porta específico.

Habilitar o acesso sem privilégios

Por padrão, o controlador/porta estará acessível apenas para usuários e aplicativos privilegiados. Se o acesso de aplicativos sem privilégios for necessário, o cliente SerCx/SerCx2 deverá substituir o descritor de segurança padrão após chamar SerCx2InitializeDeviceInit() ou SerCxDeviceInitConfig(), mas antes de chamar SerCx2InitializeDevice() ou SerCxInitialize(), momento em que o descritor de segurança aplicado é propagado para o PDO do controlador.

Veja a seguir um exemplo de como habilitar o acesso sem privilégios no SerCx2 de dentro do EvtDeviceAdd do driver do controlador do cliente SerCx2.

SampleControllerEvtDeviceAdd(
    WDFDRIVER WdfDriver,
    WDFDEVICE_INIT WdfDeviceInit
)
{
    ...

    NTSTATUS status = SerCx2InitializeDeviceInit(WdfDeviceInit);
    if (!NT_SUCCESS(status)) {
        ...
    }

    // Declare a security descriptor allowing access to all
    DECLARE_CONST_UNICODE_STRING(
        SDDL_DEVOBJ_SERCX_SYS_ALL_ADM_ALL_UMDF_ALL_USERS_RDWR,
        L"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;UD)(A;;GRGW;;;BU)");

    // Assign it to the device, overwriting the default SerCx2 security descriptor
    status = WdfDeviceInitAssignSDDLString(
                WdfDeviceInit,
                &SDDL_DEVOBJ_SERCX_SYS_ALL_ADM_ALL_UMDF_ALL_USERS_RDWR);

    if (!NT_SUCCESS(status)) {
        ...
    }

    ...
}

Alterações de comportamento ao usar uma interface de dispositivo

Aceitar esse recurso resulta nas seguintes alterações comportamentais no SerCx/SerCx2 (em vez de acessar o controlador SerCx/SerCx2 por meio de uma ID de conexão):

  • Nenhuma configuração padrão é aplicada à porta (velocidade, paridade etc). Como não há nenhum recurso de conexão na ACPI para descrever isso, a porta começa em um estado não inicializado. O software que interage com a interface do dispositivo é necessário para configurar a porta usando a interface IOCTL serial definida.

  • As chamadas do driver do cliente SerCx/SerCx2 para consultar ou aplicar a configuração padrão retornarão um status de falha. Além disso, as solicitações IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION para a interface do dispositivo terão falhas, pois não há nenhuma configuração padrão especificada para ser aplicada.

Acessar a interface de dispositivo da porta serial

Para aplicativos UWP, a interface publicada pode ser acessada usando as APIs de namespace Windows.Devices.SerialCommunication como qualquer outra porta serial compatível.

Para aplicativos Win32, a interface do dispositivo é localizada e acessada usando o seguinte processo:

  1. O aplicativo chama CM_Get_Device_Interface_ListW para obter uma lista de todas as interfaces de dispositivo de classe GUID_DEVINTERFACE_COMPORT no sistema
  2. O aplicativo chama CM_Get_Device_Interface_PropertyW para cada interface retornada para consultar o DEVPKEY_DeviceInterface_Serial_PortName de cada interface descoberta
  3. Quando a porta desejada é encontrada pelo nome, o aplicativo usa a cadeia de caracteres de link simbólico retornada em (1) para abrir um identificador da porta via CreateFile()

Exemplo de código para este fluxo:

#include <windows.h>
#include <cfgmgr32.h>
#include <initguid.h>
#include <devpropdef.h>
#include <devpkey.h>
#include <ntddser.h>

...

DWORD ret;
ULONG deviceInterfaceListBufferLength;

//
// Determine the size (in characters) of buffer required for to fetch a list of
// all GUID_DEVINTERFACE_COMPORT device interfaces present on the system.
//
ret = CM_Get_Device_Interface_List_SizeW(
        &deviceInterfaceListBufferLength,
        (LPGUID) &GUID_DEVINTERFACE_COMPORT,
        NULL,
        CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
if (ret != CR_SUCCESS) {
    // Handle error
    ...
}

//
// Allocate buffer of the determined size.
//
PWCHAR deviceInterfaceListBuffer = (PWCHAR) malloc(deviceInterfaceListBufferLength * sizeof(WCHAR));
if (deviceInterfaceListBuffer == NULL) {
    // Handle error
    ...
}

//
// Fetch the list of all GUID_DEVINTERFACE_COMPORT device interfaces present
// on the system.
//
ret = CM_Get_Device_Interface_ListW(
        (LPGUID) &GUID_DEVINTERFACE_COMPORT,
        NULL,
        deviceInterfaceListBuffer,
        deviceInterfaceListBufferLength,
        CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
if (ret != CR_SUCCESS) {
    // Handle error
    ...
}

//
// Iterate through the list, examining one interface at a time
//
PWCHAR currentInterface = deviceInterfaceListBuffer;
while (*currentInterface) {
    //
    // Fetch the DEVPKEY_DeviceInterface_Serial_PortName for this interface
    //
    CONFIGRET configRet;
    DEVPROPTYPE devPropType;
    PWCHAR devPropBuffer;
    ULONG devPropSize = 0;

    // First, get the size of buffer required
    configRet = CM_Get_Device_Interface_PropertyW(
        currentInterface,
        &DEVPKEY_DeviceInterface_Serial_PortName,
        &devPropType,
        NULL,
        &devPropSize,
        0);
    if (configRet != CR_BUFFER_SMALL) {
        // Handle error
        ...
    }

    // Allocate the buffer
    devPropBuffer = malloc(devPropSize);
    if (devPropBuffer == NULL) {
        // Handle error
        free(devPropBuffer);
        ...
    }

    configRet = CM_Get_Device_Interface_PropertyW(
        currentInterface,
        &DEVPKEY_DeviceInterface_Serial_PortName,
        &devPropType,
        (PBYTE) devPropBuffer,
        &devPropSize,
        0);
    if (configRet != CR_SUCCESS) {
        // Handle error
        free(devPropBuffer);
        ...
    }

    // Verify the value is the correct type and size
    if ((devPropType != DEVPROP_TYPE_STRING) ||
        (devPropSize < sizeof(WCHAR))) {
        // Handle error
        free(devPropBuffer);
        ...
    }

    // Now, check if the interface is the one we are interested in
    if (wcscmp(devPropBuffer, L"UART0") == 0) {
        free(devPropBuffer);
        break;
    }

    // Advance to the next string (past the terminating NULL)
    currentInterface += wcslen(currentInterface) + 1;
    free(devPropBuffer);
}

//
// currentInterface now either points to NULL (there was no match and we iterated
// over all interfaces without a match) - or, it points to the interface with
// the friendly name UART0, in which case we can open it.
//
if (*currentInterface == L'\0') {
    // Handle interface not found error
    ...
}

//
// Now open the device interface as we would a COMx style serial port.
//
HANDLE portHandle = CreateFileW(
                        currentInterface,
                        GENERIC_READ | GENERIC_WRITE,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        0,
                        NULL);
if (portHandle == INVALID_HANDLE_VALUE) {
    // Handle error
    ...
}

free(deviceInterfaceListBuffer);
deviceInterfaceListBuffer = NULL;
currentInterface = NULL;

//
// We are now able to send IO requests to the device.
//
... = ReadFile(portHandle, ..., ..., ..., NULL);

Um aplicativo também pode se inscrever para receber notificações de chegada da interface do dispositivo e remoção do dispositivo para abrir ou fechar um identificador do controlador/porta quando o dispositivo se torna disponível ou indisponível.