다음을 통해 공유


SerCx 또는 SerCx2 관리 직렬 포트에 대한 디바이스 인터페이스 게시

Windows 10 버전 1903 이상부터 SerCx 및 SerCx2에는 디바이스 인터페이스 게시 GUID_DEVINTERFACE_COMPORT 에 대한 지원이 포함됩니다. 시스템의 애플리케이션 및 서비스는 이 디바이스 인터페이스를 사용하여 직렬 포트와 상호 작용할 수 있습니다.

이 기능은 SerCx/SerCx2 클라이언트 드라이버와 통합된 UART를 특징으로 하는 SoC 기반 플랫폼에서 사용할 수 있으며, UART가 물리적 포트로 노출되거나 일반 애플리케이션(UWP 또는 Win32)이 UART에 연결된 디바이스와 직접 통신해야 하는 경우 사용할 수 있습니다. 이는 전용 주변 드라이버에서 UART에 만 액세스할 수 있도록 하는 연결 ID 를 통해 SerCx/SerCx2 컨트롤러에 액세스하는 것과는 반대입니다.

SerCx/SerCx2 관리 직렬 포트에 이 기능을 사용하는 경우 이러한 디바이스에 대해 COM 포트 번호가 할당되지 않으며 기호 링크가 생성되지 않습니다. 즉, 애플리케이션은 이 문서에 설명된 접근 방식을 사용하여 직렬 포트를 디바이스 인터페이스로 열어야 합니다.

COM 포트를 검색하고 액세스하려면 디바이스 인터페이스(GUID_DEVINTERFACE_COMPORT)를 사용하는 것이 좋습니다. 레거시 COM 포트 이름을 사용하면 이름이 충돌하는 경향이 있으며 클라이언트에 상태 변경 알림을 제공하지 않습니다. 레거시 COM 포트 이름을 사용하는 것은 권장되지 않으며 SerCx2 및 SerCx에서는 지원되지 않습니다.

디바이스 인터페이스 만들기 사용

다음은 디바이스 인터페이스를 만들 수 있도록 설정하는 지침입니다. 직렬 포트는 배타적입니다. 즉, 직렬 포트가 디바이스 인터페이스로 액세스할 수 있는 경우 ACPI의 연결 리소스를 다른 디바이스에 제공하지 않아야 합니다. UARTSerialBusV2 예를 들어 시스템의 다른 디바이스에 리소스를 제공하지 않아야 합니다. 포트는 디바이스 인터페이스를 통해서만 액세스할 수 있도록 해야 합니다.

ACPI 구성

시스템 제조업체 또는 통합자는 UUIDdaffd814-6eba-4d8c-8a91-bc9bbf4aa301를 사용하여 키-값 디바이스 속성에 대한 정의를 추가 _DSD 하도록 기존 SerCx/SerCx2 디바이스의 ACPI(ASL) 정의를 수정하여 이 동작을 사용하도록 설정할 수 있습니다. 이 정의 내에서 속성 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/SerCx2에 대해 항목을 SerCx-FriendlyName 정의해야 합니다.

레지스트리 키

개발을 위해 레지스트리에서 SerCxFriendlyName 디바이스의 하드웨어 키에 속성으로 구성할 수도 있습니다. 이 메서드는 CM_Open_DevNode_Key 디바이스의 하드웨어 키 에 액세스하고 SerCx/SerCx2에서 디바이스 인터페이스의 친숙한 이름을 검색하는 데 사용되는 디바이스에 속성을 SerCxFriendlyName 추가하는 데 사용할 수 있습니다.

확장 INF를 통해 이 키를 설정하지 않는 것이 좋습니다. 이 키는 주로 테스트 및 개발 목적으로 제공됩니다. 위에서 설명한 대로 ACPI를 통해 기능을 사용하도록 설정하는 것이 좋습니다.

디바이스 인터페이스

위의 메서드를 FriendlyName 사용하여 정의된 경우 SerCx/SerCx2는 컨트롤러에 GUID_DEVINTERFACE_COMPORT 대한 디바이스 인터페이스 를 게시합니다. 이 디바이스 인터페이스에는 애플리케이션에서 DEVPKEY_DeviceInterface_Serial_PortName 특정 컨트롤러/포트를 찾는 데 사용할 수 있는 지정된 이름에 속성이 설정됩니다.

권한 없는 액세스 사용

기본적으로 컨트롤러/포트는 권한 있는 사용자 및 애플리케이션에서만 액세스할 수 있습니다. 권한 없는 애플리케이션에서 액세스해야 하는 경우 SerCx/SerCx2 클라이언트는 호출 후 또는 호출 SerCx2InitializeDeviceInit() SerCx2InitializeDevice() SerCxInitialize()하기 전에 또는 SerCxDeviceInitConfig()적용된 보안 설명자가 컨트롤러 PDO에 전파되는 기본 보안 설명자를 재정의해야 합니다.

SerCx2 클라이언트 컨트롤러 드라이버 내에서 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)) {
        ...
    }

    ...
}

디바이스 인터페이스를 사용할 때 동작이 변경됩니다.

이 기능을 옵트인하면 SerCx/SerCx2에서 다음과 같은 동작이 변경됩니다(연결 ID를 통해 SerCx/SerCx2 컨트롤러에 액세스하는 것과 반대).

  • 포트(속도, 패리티 등)에 기본 구성이 적용되지 않습니다. 이를 설명하는 ACPI에 연결 리소스가 없으므로 포트는 초기화되지 않은 상태로 시작됩니다. 정의된 직렬 IOCTL 인터페이스를 사용하여 포트를 구성하려면 디바이스 인터페이스와 상호 작용하는 소프트웨어가 필요합니다.

  • SerCx/SerCx2 클라이언트 드라이버에서 기본 구성을 쿼리하거나 적용하는 호출은 실패 상태를 반환합니다. IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION 또한 적용하도록 지정된 기본 구성이 없으므로 디바이스 인터페이스에 대한 요청이 실패합니다.

직렬 포트 디바이스 인터페이스 액세스

UWP 애플리케이션의 경우 게시된 인터페이스는 다른 호환 직렬 포트와 마찬가지로 네임스페이스 API를 사용하여 Windows.Devices.SerialCommunication 액세스할 수 있습니다.

Win32 애플리케이션의 경우 다음 프로세스를 사용하여 디바이스 인터페이스를 찾아 액세스합니다.

  1. 시스템의 모든 GUID_DEVINTERFACE_COMPORT 클래스 디바이스 인터페이스 목록을 가져오는 애플리케이션 호출 CM_Get_Device_Interface_ListW
  2. 애플리케이션은 검색된 각 인터페이스를 DEVPKEY_DeviceInterface_Serial_PortName 쿼리하기 위해 반환된 각 인터페이스에 대해 호출 CM_Get_Device_Interface_PropertyW 합니다.
  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);

디바이스 를 사용할 수 있거나 사용할 수 없게 될 때 컨트롤러/포트에 대한 핸들을 열거나 닫기 위해 애플리케이션에서 디바이스 인터페이스 도착 및 디바이스 제거 알림을 구독할 수도 있습니다.