共用方式為


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

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

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

將此功能用於 SerCx/SerCx2 託管串行端口時,不會為這些設備分配 COM 端口號,並且不會創建任何符號鏈路 - 這意味著應用程序必須使用本文檔中描述的方法將串行端口打開為設備接口。

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

啟用裝置介面創建

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

ACPI 設定

系統製造商或整合商可以透過修改現有 SerCx/SerCx2 裝置的 ACPI(ASL)定義,增加具有 UUID 的鍵值式裝置屬性的定義,以啟用此行為。 在此定義內, 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 方法可用來存取裝置的 硬體金鑰 ,並將屬性 SerCxFriendlyName 新增至裝置,SerCx/SerCx2 會使用該屬性來擷取裝置介面的易記名稱。

不建議透過延伸模組 INF 設定此金鑰 - 它主要用於測試和開發目的。 建議的方法是透過 ACPI 啟用此功能,如上所述。

設備接口

如果使用上述方法定義FriendlyName,則 SerCx/SerCx2 將發布控制器的GUID_DEVINTERFACE_COMPORT裝置介面。 此裝置介面會將 DEVPKEY_DeviceInterface_Serial_PortName 屬性設定為指定的易記名稱,應用程式可能會使用該名稱來尋找特定的控制器/埠。

啟用非特殊許可權存取

預設情況下,控制器/連接埠僅可供特權使用者和應用程式存取。 如果需要從非特殊許可權應用程式存取,SerCx/SerCx2 用戶端必須在呼叫 SerCx2InitializeDeviceInit()SerCxDeviceInitConfig()之後覆寫預設安全性描述元,但在呼叫 SerCx2InitializeDevice()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 發生以下行為變更 (而不是 透過連線 ID 存取 SerCx/SerCx2 控制器):

  • 不會對埠應用預設配置(速度、奇偶校驗等)。 由於 ACPI 中沒有連線資源來描述此情況,因此埠會以未初始化的狀態開始。 需要與裝置介面互動的軟體,才能使用定義的 序列 IOCTL 介面來設定埠。

  • 從 SerCx/SerCx2 用戶端驅動程式呼叫查詢或套用預設組態會傳回失敗狀態。 此外, IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION 對裝置介面的請求將會失敗,因為沒有指定要套用的預設組態。

訪問串行埠設備接口

針對 UWP 應用程式,可以使用命名空間的 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);

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