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:
- La aplicación llama a
CM_Get_Device_Interface_ListW
para obtener una lista de todas las interfaces de dispositivo de claseGUID_DEVINTERFACE_COMPORT
en el sistema - La aplicación llama a
CM_Get_Device_Interface_PropertyW
para cada interfaz devuelta para consultarDEVPKEY_DeviceInterface_Serial_PortName
para cada interfaz detectada. - 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.