Поделиться через


Публикация интерфейса устройства для управляемого последовательного порта 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 интерфейс устройства расположен и доступен с помощью следующего процесса:

  1. Вызовы CM_Get_Device_Interface_ListW приложений для получения списка всех GUID_DEVINTERFACE_COMPORT интерфейсов устройств класса в системе
  2. Вызовы CM_Get_Device_Interface_PropertyW приложений для каждого возвращаемого интерфейса для запроса DEVPKEY_DeviceInterface_Serial_PortName для каждого обнаруженного интерфейса
  3. Когда нужный порт найден по имени, приложение использует символьную строку ссылки, возвращаемую в (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);

Обратите внимание, что приложение также может подписаться на уведомления о прибытии интерфейса устройства и удалении устройств, чтобы открыть или закрыть дескриптор контроллера или порта, когда устройство становится доступным или недоступным.