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, SerCx e SerCx2 incluem suporte para publicação de uma GUID_DEVINTERFACE_COMPORTinterface do dispositivo. Aplicativos e serviços em um sistema podem usar essa interface do 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 cliente SerCx/SerCx2, se o UART for exposto como uma porta física ou se os aplicativos regulares (UWP ou Win32) precisarem se comunicar diretamente com um dispositivo anexado ao UART. Em contraste, acessar o controlador SerCx/SerCx2 por meio de um ID de conexão permite exclusivamente o acesso ao UART por um driver periférico dedicado.

Ao usar esse recurso para portas serial gerenciadas serCx/SerCx2, um número de porta COM não é atribuído a esses dispositivos e nenhum link simbólico é criado – o que significa que os aplicativos devem usar a abordagem descrita neste documento para abrir a porta serial como uma interface do 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 nome e não fornece notificações de alteração de estado para um cliente. O uso dos nomes de porta COM herdados não é recomendado e não tem suporte com SerCx2 e SerCx.

Habilitando a criação da interface do dispositivo

Abaixo estão as instruções para habilitar a criação da interface do dispositivo. Observe que as portas serial são exclusivas, ou seja, se a porta serial estiver acessível como uma interface do dispositivo, um recurso de conexão no ACPI não deverá ser fornecido a nenhum outro dispositivo , por exemplo, nenhum UARTSerialBusV2 recurso deve ser fornecido a nenhum outro dispositivo no sistema; a porta deve ser tornada exclusivamente acessível por meio da interface do dispositivo.

Configuração de ACPI

Um fabricante ou integrador do sistema pode habilitar esse comportamento modificando a definição de ACPI (ASL) do dispositivo SerCx/SerCx2 existente para adicionar uma _DSD definição para propriedades de dispositivo chave-valor com UUID daffd814-6eba-4d8c-8a91-bc9bbf4aa301. Dentro dessa definição, a propriedade SerCx-FriendlyName é especificada com uma descrição específica do sistema para a 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 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 do sistema. O CM_Open_DevNode_Key método pode ser usado para acessar a chave de hardware do dispositivo e adicionar a propriedade SerCxFriendlyName ao dispositivo, que é usado pelo SerCx/SerCx2 para recuperar o nome amigável para a interface do dispositivo.

Não é recomendável 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 do dispositivo

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

Habilitando o acesso sem privilégios

Por padrão, o controlador/porta estará acessível somente para usuários e aplicativos privilegiados. Se o acesso de aplicativos não privilegiados for necessário, o cliente SerCx/SerCx2 deverá substituir o descritor de segurança padrão depois de 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.

Um exemplo de como habilitar o acesso desprivilegiado no SerCx2 a partir de dentro do driver de controlador do cliente SerCx2 está abaixo.

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 do 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 no 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 cliente SerCx/SerCx2 para consultar ou aplicar a configuração padrão retornarão um status de falha. Além disso, as solicitações para a interface do dispositivo falharão, IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION pois não há nenhuma configuração padrão especificada para aplicar.

Acessando a interface do dispositivo de porta serial

Para aplicativos UWP, a interface publicada pode ser acessada usando as APIs do Windows.Devices.SerialCommunication namespace, como qualquer outra porta serial em conformidade.

Para aplicativos Win32, a interface do dispositivo está 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 classe de dispositivo GUID_DEVINTERFACE_COMPORT no sistema.
  2. Chamadas de CM_Get_Device_Interface_PropertyW aplicativo para cada interface retornada para consultar cada DEVPKEY_DeviceInterface_Serial_PortName interface descoberta
  3. Quando a porta desejada é encontrada pelo nome, o aplicativo usa a string de link simbólico retornada em (1) para abrir um identificador para a porta por meio de CreateFile()

Código de exemplo 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);

Observe que um aplicativo também pode se inscrever para notificações de chegada e remoção de interfaces de dispositivos para abrir ou fechar um handle para o controlador/porta quando o dispositivo ficar disponível ou indisponível.