Публикация интерфейса устройства для управляемого последовательного порта SerCx или SerCx2
Начиная с Windows 10 версии 1903 и более поздних версий, SerCx и SerCx2 включают поддержку публикации GUID_DEVINTERFACE_COMPORT
интерфейса устройства. Приложения и службы в системе могут использовать этот интерфейс устройства для взаимодействия с последовательным портом.
Эта функция может быть включена на платформах на основе SoC, которые имеют интегрированный UART с драйвером клиента SerCx/SerCx2, если UART предоставляется как физический порт, или если обычные приложения (UWP или Win32) должны взаимодействовать напрямую с устройством, подключенным к UART. Это отличается от доступа к контроллеру SerCx/SerCx2 через идентификатор подключения, который исключительно обеспечивает доступ к UART из выделенного периферийного драйвера.
При использовании этой функции для управляемых последовательных портов SerCx/SerCx2 номер COM-порта не назначается для этих устройств, а символьная ссылка не создается. Это означает, что приложения должны использовать подход, описанный в этом документе, чтобы открыть последовательный порт в качестве интерфейса устройства.
Использование интерфейса устройства (GUID_DEVINTERFACE_COMPORT
) — это рекомендуемый способ обнаружения и доступа к com-порту. Использование устаревших имен COM-портов подвержено конфликтам имен и не предоставляет уведомления об изменении состояния клиенту. Использование устаревших имен COM-портов не рекомендуется и не поддерживается в SerCx2 и SerCx.
Включение создания интерфейса устройства
Ниже приведены инструкции по включению создания интерфейса устройства. Обратите внимание, что последовательные порты являются эксклюзивными, то есть если последовательный порт доступен как интерфейс устройства, ресурс подключения в ACPI не должен предоставляться другим устройствам, например, ни UARTSerialBusV2
одному ресурсу в системе. Порт должен быть доступен исключительно через интерфейс устройства.
Конфигурация ACPI
Системный производитель или интегратор могут включить это поведение, изменив определение ACPI (ASL) существующего устройства SerCx/SerCx2, чтобы добавить _DSD
определение для свойств устройства key-value с помощью UUIDdaffd814-6eba-4d8c-8a91-bc9bbf4aa301
. Внутри этого определения свойство SerCx-FriendlyName
определяется с помощью системного описания последовательного порта, например UART0
, UART1
и т. д.
Пример определения устройства (за исключением сведений о поставщике, необходимых для определения устройства):
Device(URT0) {
Name(_HID, ...)
Name(_CID, ...)
Name(_DSD, Package() {
ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
Package() {
Package(2) {"SerCx-FriendlyName", "UART0"}
}
})
}
Необходимо использовать указанный UUID (daffd814-6eba-4d8c-8a91-bc9bbf4aa301
), а запись SerCx-FriendlyName
должна быть определена для SerCx/SerCx2, чтобы создать интерфейс устройства.
Раздел реестра
Для целей разработки SerCxFriendlyName
также можно настроить как свойство в аппаратном разделе устройства в реестре. Метод CM_Open_DevNode_Key
может использоваться для доступа к аппаратному ключу устройства и добавлению свойства SerCxFriendlyName
на устройство, которое используется SerCx/SerCx2 для получения понятного имени интерфейса устройства.
Не рекомендуется задавать этот ключ с помощью расширения INF- он предоставляется в первую очередь для тестирования и разработки. Рекомендуемый подход — включить функцию с помощью ACPI, как описано выше.
Интерфейс устройства
Если определение FriendlyName
определено с помощью описанных выше методов, SerCx/SerCx2 опубликует GUID_DEVINTERFACE_COMPORT
интерфейс устройства для контроллера. В этом интерфейсе устройства будет задано DEVPKEY_DeviceInterface_Serial_PortName
понятное имя свойства, которое может использоваться приложениями для поиска определенного контроллера или порта.
Включение непривилегированного доступа
По умолчанию контроллер или порт будет доступен только привилегированным пользователям и приложениям. Если требуется доступ из непривилегированных приложений, клиент SerCx/SerCx2 должен переопределить дескриптор безопасности по умолчанию после вызова SerCx2InitializeDeviceInit()
или перед вызовом SerCx2InitializeDevice()
или SerCxDeviceInitConfig()
SerCxInitialize()
, в то время как примененный дескриптор безопасности распространяется на PDO контроллера.
Ниже приведен пример включения непривилегированного доступа к SerCx2 из драйвера клиентского контроллера EvtDeviceAdd
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)) {
...
}
...
}
Изменения поведения при использовании интерфейса устройства
Включение этой функции приводит к следующим изменениям поведения в SerCx/SerCx2 (в отличие от доступа к контроллеру SerCx/SerCx2 через идентификатор подключения):
Конфигурация по умолчанию не применяется к порту (скорость, четность и т. д.). Так как в ACPI нет ресурса подключения, порт начинается в неинициализированном состоянии. Программное обеспечение, взаимодействующее с интерфейсом устройства, требуется для настройки порта с помощью определенного последовательного интерфейса IOCTL.
Вызовы из драйвера клиента SerCx/SerCx2 для запроса или применения конфигурации по умолчанию возвращают состояние сбоя. Кроме того, запросы к интерфейсу устройства завершаются ошибкой,
IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION
так как для применения не указана конфигурация по умолчанию.
Доступ к интерфейсу устройства последовательного порта
Для приложений UWP доступ к опубликованному интерфейсу можно получить с помощью Windows.Devices.SerialCommunication
API пространства имен, таких как любой другой последовательный порт.
Для приложений Win32 интерфейс устройства расположен и доступен с помощью следующего процесса:
- Вызовы
CM_Get_Device_Interface_ListW
приложений для получения списка всехGUID_DEVINTERFACE_COMPORT
интерфейсов устройств класса в системе - Вызовы
CM_Get_Device_Interface_PropertyW
приложений для каждого возвращаемого интерфейса для запросаDEVPKEY_DeviceInterface_Serial_PortName
для каждого обнаруженного интерфейса - Когда нужный порт найден по имени, приложение использует символьную строку ссылки, возвращаемую в (1), чтобы открыть дескриптор через порт.
CreateFile()
Пример кода для этого потока:
#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);
Обратите внимание, что приложение также может подписаться на уведомления о прибытии интерфейса устройства и удалении устройств, чтобы открыть или закрыть дескриптор контроллера или порта, когда устройство становится доступным или недоступным.