共用方式為


SerCx 或 SerCx2 受控序列埠的裝置介面發行集

從 Windows 10 版本 1903 和更新版本開始,SerCx 和 SerCx2 包含發行GUID_DEVINTERFACE_COMPORT裝置介面的支援。 系統上的應用程式和服務都能夠使用此裝置介面來與序列埠互動。

這項功能可以在SoC型平臺上啟用,如果UART公開為實體埠,或一般應用程式 (UWP 或 Win32) 需要直接與連結至UART的裝置通訊,則此功能與SerCx/SerCx/SerCx2 用戶端驅動程式搭配整合式 UART。 這與透過連線標識碼存取 SerCx/SerCx2 控制器相反,此識別元僅能從專用周邊驅動程式存取 UART。

針對 SerCx/SerCx2 受管理的序列埠使用這項功能時,不會為這些裝置指派 COM 埠號碼,而且不會建立符號連結,這表示應用程式必須使用本檔中所述的方法來開啟串行埠作為裝置介面。

使用裝置介面 (GUID_DEVINTERFACE_COMPORT) 是探索和存取 COM 埠的建議方式。 使用舊版 COM 埠名稱容易發生名稱衝突,且不會提供狀態變更通知給用戶端。 不建議使用舊版 COM 埠名稱,且不支援 SerCx2 和 SerCx。

啟用裝置介面建立

以下是啟用裝置介面建立的指示。 請注意,序列埠是獨佔的,這表示如果串行埠可做為裝置介面存取,則ACPI中的連線資源不應提供給任何其他裝置,例如,不應UARTSerialBusV2將資源提供給系統上的任何其他裝置;埠應該透過裝置介面獨佔存取。

ACPI 組態

系統製造商或整合者可以藉由修改現有 SerCx/SerCx2 裝置的 ACPI (ASL) 定義來啟用此行為,以使用 UUID daffd814-6eba-4d8c-8a91-bc9bbf4aa301新增_DSD索引鍵/值裝置屬性的定義。 在此定義內,屬性SerCx-FriendlyName是使用序列埠的系統特定描述來定義,例如、 UART0UART1等。

範例裝置定義(不包括定義裝置所需的廠商特定資訊):

    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()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 應用程式,您可以使用命名空間 API 來存取 Windows.Devices.SerialCommunication 已發佈的介面,就像任何其他相容串行埠一樣。

針對 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);

請注意,當裝置變成可用或無法使用時,應用程式也可以 訂閱裝置介面抵達和裝置移除 的通知,以開啟或關閉控制器/埠的句柄。