SerCx または SerCx2 が管理するシリアルポートのデバイス インターフェース パブリケーション

Windows 10 バージョン 1903 以降では、SerCx と SerCx2 には、 GUID_DEVINTERFACE_COMPORT device インターフェイスの発行のサポートが含まれています。 システム上のアプリケーションやサービスは、このデバイス インターフェースを使用してシリアル ポートとやりとりすることができます。

この機能は、SerCx/SerCx2 クライアント ドライバと統合された UART を特徴とする SoC ベースのプラットフォーム、UART が物理ポートとして公開されている場合、または通常のアプリケーション (UWP または Win32) が UART に接続されたデバイスと直接通信する必要がある場合に有効にすることができます。 これは、接続 ID を介して SerCx/SerCx2 コントローラーにアクセスするとは対照的です。これにより、専用のペリフェラル ドライバーから UART へのアクセスが排他的に有効になります。

SerCx/SerCx2 管理のシリアルポートをこの機能で使用する場合、これらのデバイスには COM ポート番号が割り当てられず、シンボリックリンクも作成されません。つまり、アプリケーションは、このドキュメントで説明されているアプローチを使用して、シリアルポートをデバイスインターフェースとして開く必要があります。

COM ポートを検出してアクセスするには、デバイス インターフェイス (GUID_DEVINTERFACE_COMPORT) を使用することをお勧めします。 レガシー COM ポート名を使用すると、名前の衝突が発生しやすく、クライアントへの状態変更の通知も提供されません。 SerCx2 および SerCx では、レガシー COM ポート名の使用は推奨されず、サポートされていません。

デバイスインターフェイスの作成を有効にする

以下は、デバイス インターフェイスの作成を有効にする手順です。 シリアルポートは排他的であることに注意してください。つまり、シリアルポートがデバイスインターフェースとしてアクセス可能な場合、ACPI の接続リソースは他のどのデバイスにも提供されるべきではありません。たとえば、システム上の他のどのデバイスにもUARTSerialBusV2リソースが提供されるべきではなく、ポートはデバイスインターフェースを通じて排他的にアクセス可能にされるべきです。

ACPI の構成

システムの製造元またはインテグレータは、既存のSerCx/SerCx2 デバイスの ACPI (ASL) 定義を変更して、UUIDdaffd814-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メソッドは、デバイスのハードウェア キーにアクセスし、プロパティSerCxFriendlyNameをデバイスに追加するために使用できます。これは、デバイス インターフェイスのフレンドリ名を取得するために SerCx/SerCx2 によって使用されます。

このキーを拡張 INF 経由で設定することはお勧めできません。これは主にテストと開発の目的で提供されています。 推奨される方法は、上で説明したように、ACPI を介して機能を有効にすることです。

デバイス インターフェイス

上記のメソッドを使用して FriendlyName が定義されている場合、SerCx/SerCx2 はコントローラーの GUID_DEVINTERFACE_COMPORT device インターフェイス を発行します。 このデバイス インターフェイスには、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 アプリケーションの場合、公開されたインターフェイスには、他の準拠シリアル ポートと同様に、Windows.Devices.SerialCommunication名前空間 API を使用してアクセスできます。

Win32 アプリケーションの場合、デバイス インターフェイスは次のプロセスを使用して特定され、アクセスされます。

  1. アプリケーションはCM_Get_Device_Interface_ListWを呼び出して、システム上のすべてのGUID_DEVINTERFACE_COMPORTクラスのデバイス インターフェイスのリストを取得します。
  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);

アプリケーションは、デバイスが使用可能になったとき、または使用できなくなったときに、コントローラ/ポートへのハンドルを開いたり閉じたりするために、デバイス インターフェイスの到着とデバイスの削除の通知をサブスクライブすることもできることに注意してください。