Compartir a través de


Publicación de la interfaz de dispositivo para un puerto serie gestionado por 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_COMPORTinterfaz 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 puerto 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 para la creación de interfaces de dispositivos

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 UARTSerialBusV2 se debe proporcionar ningún recurso 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 _DSD definición 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, SerCxFriendlyName también se puede configurar como una propiedad en la clave de hardware del dispositivo en el Registro. El CM_Open_DevNode_Key método 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 de dispositivo

Si un FriendlyName se define mediante los métodos anteriores, SerCx/SerCx2 publicará una GUID_DEVINTERFACE_COMPORTinterfaz de dispositivo para el controlador. Esta interfaz de dispositivo tendrá la DEVPKEY_DeviceInterface_Serial_PortName propiedad 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 a 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(), momento en el cual el descriptor de seguridad aplicado se propaga al PDO del controlador.

A continuación se muestra un ejemplo de cómo habilitar el acceso sin privilegios en SerCx2 desde el controlador de cliente de SerCx2 EvtDeviceAdd.

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 participación en 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. Se necesita software que interactúe con la interfaz del dispositivo 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, IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION se producirá un error en las solicitudes 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 del 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 clase de dispositivo en el sistema
  2. La aplicación llama CM_Get_Device_Interface_PropertyW a cada interfaz devuelta para consultar 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.