Compartir a través de


Publicación de la interfaz de dispositivo para un puerto serie administrado SerCx o SerCx2

A partir de La versión 1903 y posteriores de Windows 10, SerCx y SerCx2 incluyen compatibilidad con la publicación de una GUID_DEVINTERFACE_COMPORT interfaz de dispositivo. Las aplicaciones y los servicios de un sistema pueden usar esta interfaz de dispositivo para interactuar con el puerto serie.

Esta característica se puede habilitar en plataformas basadas en SoC que incluyen un UART integrado con un controlador cliente SerCx/SerCx2, si el UART se expone como un puerto físico o si las aplicaciones normales (UWP o Win32) necesitan comunicarse directamente con un dispositivo conectado al UART. Esto se opone a acceder al controlador SerCx/SerCx2 a través de un identificador de conexión, que permite exclusivamente el acceso al UART desde un controlador periférico dedicado.

Al usar esta característica para los puertos serie administrados SerCx/SerCx2, no se asigna un número de puerto COM para estos dispositivos y no se crea ningún vínculo simbólico, lo que significa que las aplicaciones deben usar el enfoque descrito en este documento para abrir el puerto serie como una interfaz de dispositivo.

El uso de la interfaz de dispositivo (GUID_DEVINTERFACE_COMPORT) es la manera recomendada de detectar y acceder a un puerto COM. El uso de nombres de puertos COM heredados es propenso a colisiones de nombres y no proporciona notificaciones de cambio de estado a un cliente. No se recomienda usar los nombres de puerto COM heredados y no se admite con SerCx2 y SerCx.

Habilitación de la creación de la interfaz de dispositivo

A continuación se muestran las instrucciones para habilitar la creación de la interfaz de dispositivo. Tenga en cuenta que los puertos serie son exclusivos, lo que significa que si el puerto serie es accesible como una interfaz de dispositivo, no se debe proporcionar un recurso en ACPI a ningún otro dispositivo; por ejemplo, no se debe proporcionar ningún recurso UARTSerialBusV2 a ningún otro dispositivo del sistema; el puerto debe ser accesible exclusivamente a través de la interfaz del dispositivo.

Configuración de ACPI

Un fabricante o integrador del sistema puede habilitar este comportamiento modificando la definición ACPI (ASL) del dispositivo SerCx/SerCx2 existente para agregar una definición _DSD para las propiedades del dispositivo clave-valor con UUID daffd814-6eba-4d8c-8a91-bc9bbf4aa301. Dentro de esta definición, la propiedad SerCx-FriendlyName se define con una descripción específica del sistema del puerto serie, por ejemplo, UART0, UART1, etc.

Definición de dispositivo de ejemplo (excepto la información específica del proveedor necesaria para definir el dispositivo):

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

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

Se debe usar el UUID especificado (daffd814-6eba-4d8c-8a91-bc9bbf4aa301) y la entrada SerCx-FriendlyName debe definirse para SerCx/SerCx2 para crear la interfaz del dispositivo.

Clave del Registro

Con fines de desarrollo, también se puede configurar SerCxFriendlyName como una propiedad en la clave de hardware del dispositivo en el Registro. El método CM_Open_DevNode_Key se puede usar para acceder a la clave de hardware del dispositivo y agregar la propiedad SerCxFriendlyName al dispositivo, que se usa en SerCx/SerCx2 para recuperar el nombre descriptivo de la interfaz del dispositivo.

No se recomienda establecer esta clave a través de una extensión INF: se proporciona principalmente con fines de prueba y desarrollo. El enfoque recomendado es habilitar la característica a través de ACPI como se documentó anteriormente.

Interfaz del dispositivo

FriendlyName Si se define mediante los métodos anteriores, SerCx/SerCx2 publicará una GUID_DEVINTERFACE_COMPORT interfaz de dispositivo para el controlador. Esta interfaz de dispositivo tendrá la propiedad DEVPKEY_DeviceInterface_Serial_PortName establecida en el nombre descriptivo especificado, que las aplicaciones pueden usar para localizar un controlador o puerto específico.

Habilitación del acceso sin privilegios

De forma predeterminada, solo se podrá acceder al controlador o puerto por parte de los usuarios y aplicaciones con privilegios. Si se requiere acceso desde aplicaciones sin privilegios, el cliente SerCx/SerCx2 debe invalidar el descriptor de seguridad predeterminado después de llamar a SerCx2InitializeDeviceInit() o SerCxDeviceInitConfig(), pero antes de llamar a SerCx2InitializeDevice() o SerCxInitialize(), en cuyo momento se propaga el descriptor de seguridad aplicado al PDO del controlador.

A continuación se muestra un ejemplo de cómo habilitar el acceso sin privilegios en SerCx2 desde EvtDeviceAdd del controlador 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)) {
        ...
    }

    ...
}

Cambios de comportamiento al usar una interfaz de dispositivo

La inclusión de esta característica da como resultado los siguientes cambios de comportamiento en SerCx/SerCx2 (en lugar de acceder al controlador SerCx/SerCx2 a través de un identificador de conexión):

  • No se aplica ninguna configuración predeterminada al puerto (velocidad, paridad, etc.). Como no hay ningún recurso de conexión en ACPI para describirlo, el puerto comienza en un estado no inicializado. El software que interactúa con la interfaz de dispositivo es necesario para configurar el puerto mediante la interfaz IOCTL serie definida.

  • Las llamadas desde el controlador cliente SerCx/SerCx2 para consultar o aplicar la configuración predeterminada devolverán un estado de error. Además, se producirá un error en las solicitudes IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION a la interfaz del dispositivo, ya que no hay ninguna configuración predeterminada especificada para aplicar.

Acceso a la interfaz de dispositivo de puerto serie

En el caso de las aplicaciones para UWP, se puede acceder a la interfaz publicada mediante las API de espacio de nombres Windows.Devices.SerialCommunication como cualquier otro puerto serie compatible.

En el caso de las aplicaciones Win32, la interfaz del dispositivo se encuentra y se accede a ella mediante el siguiente proceso:

  1. La aplicación llama a CM_Get_Device_Interface_ListW para obtener una lista de todas las interfaces de dispositivo de clase GUID_DEVINTERFACE_COMPORT en el sistema
  2. La aplicación llama a CM_Get_Device_Interface_PropertyW para cada interfaz devuelta para consultar DEVPKEY_DeviceInterface_Serial_PortName para cada interfaz detectada.
  3. Cuando el puerto deseado se encuentra por nombre, la aplicación usa la cadena de vínculo simbólico devuelta en (1) para abrir un identificador al puerto a través de CreateFile()

Código de ejemplo para este flujo:

#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);

Tenga en cuenta que una aplicación también puede suscribirse a las notificaciones de llegada de la interfaz de dispositivo y eliminación de dispositivos para abrir o cerrar un identificador al controlador o puerto cuando el dispositivo esté disponible o no disponible.