Freigeben über


Veröffentlichung der Geräteschnittstelle für einen verwalteten seriellen Port von SerCx oder SerCx2

Ab Windows 10, Version 1903 und höher, unterstützen SerCx und SerCx2 die Veröffentlichung einer GUID_DEVINTERFACE_COMPORT Geräteschnittstelle. Anwendungen und Dienste auf einem System können diese Geräteschnittstelle verwenden, um mit dem seriellen Port zu interagieren.

Diese Funktion kann auf SoC-basierten Plattformen aktiviert werden, die über einen integrierten UART mit einem SerCx/SerCx2 Client-Treiber verfügen, wenn der UART als physischer Port exponiert ist, oder wenn reguläre Anwendungen (UWP oder Win32) direkt mit einem an den UART angeschlossenen Gerät kommunizieren müssen. Dies steht im Gegensatz zum Zugriff auf den SerCx/SerCx2-Controller über eine Verbindungs-ID – der ausschließlich den Zugriff auf den UART über einen dedizierten Peripherie-Treiber ermöglicht.

Wenn Sie diese Funktion für SerCx/SerCx2 verwaltete serielle Ports verwenden, wird für diese Geräte keine COM-Port-Nummer zugewiesen und es wird kein symbolischer Link erstellt. Das bedeutet, dass Anwendungen den in diesem Dokument beschriebenen Ansatz verwenden müssen, um den seriellen Port als Geräteschnittstelle zu öffnen.

Die Verwendung der Geräteschnittstelle (GUID_DEVINTERFACE_COMPORT) ist der empfohlene Weg, um einen COM-Port zu erkennen und darauf zuzugreifen. Die Verwendung von Legacy-COM-Portnamen ist anfällig für Namenskonflikte und stellt keine Statusänderungsbenachrichtigungen für einen Client bereit. Die Verwendung der veralteten COM-Port-Namen wird nicht empfohlen und von SerCx2 und SerCx nicht unterstützt.

Aktivieren der Erstellung von Geräteschnittstellen

Nachfolgend finden Sie die Anweisungen zur Aktivierung der Erstellung von Geräteschnittstellen. Beachten Sie, dass serielle Ports exklusiv sind, d. h. wenn der serielle Port als Geräteschnittstelle zugänglich ist, sollte eine Verbindungsressource in ACPI nicht für andere Geräte bereitgestellt werden – z. B. sollte keine UARTSerialBusV2Ressource für andere Geräte auf dem System bereitgestellt werden; der Port sollte exklusiv über die Geräteschnittstelle erreichbar gemacht werden.

ACPI-Konfiguration

Ein Systemhersteller oder -integrator kann dieses Verhalten aktivieren, indem er die ACPI (ASL)-Definition des vorhandenen SerCx/SerCx2-Geräts ändert und eine _DSDDefinition für Key-Value-Geräteeigenschaften mit der UUID daffd814-6eba-4d8c-8a91-bc9bbf4aa301 hinzufügt. Innerhalb dieser Definition wird die Eigenschaft SerCx-FriendlyName mit einer systemspezifischen Beschreibung des seriellen Ports definiert, z. B. UART0, UART1 usw.

Beispiel einer Gerätedefinition (ohne anbieterspezifische Informationen, die zur Definition des Geräts erforderlich sind):

    Device(URT0) {
        Name(_HID, ...)
        Name(_CID, ...)

        Name(_DSD, Package() {
            ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
            Package() {
                Package(2) {"SerCx-FriendlyName", "UART0"}
            }
        })
    }

Die angegebene UUID (daffd814-6eba-4d8c-8a91-bc9bbf4aa301) muss verwendet werden, und der Eintrag SerCx-FriendlyName muss für SerCx/SerCx2 definiert werden, um die Geräteschnittstelle zu erstellen.

Registrierungsschlüssel

Für Entwicklungszwecke kann die SerCxFriendlyName auch als Eigenschaft im Hardwareschlüssel des Geräts in der Registrierung konfiguriert werden. Die Methode CM_Open_DevNode_Key kann verwendet werden, um auf den Hardwareschlüssel des Geräts zuzugreifen und dem Gerät die Eigenschaft SerCxFriendlyName hinzuzufügen, die von SerCx/SerCx2 verwendet wird, um den Anzeigenamen für die Geräteschnittstelle abzurufen.

Es wird nicht empfohlen, diesen Schlüssel über eine Erweiterungs-INF festzulegen – er ist in erster Linie für Test- und Entwicklungszwecke vorgesehen. Es wird empfohlen, die Funktion wie oben beschrieben über ACPI zu aktivieren.

Geräteschnittstelle

Wenn eine FriendlyName mithilfe der oben genannten Methoden definiert ist, veröffentlicht SerCx/SerCx2 eine GUID_DEVINTERFACE_COMPORT Geräteschnittstelle für den Controller. Diese Geräteschnittstelle hat die Eigenschaft DEVPKEY_DeviceInterface_Serial_PortName, die auf den angegebenen Anzeigenamen festgelegt ist, der von Anwendungen verwendet werden kann, um einen bestimmten Controller/Port zu finden.

Aktivieren des unprivilegierten Zugriffs

Standardmäßig ist der Zugriff auf den Controller/Port nur für privilegierte Benutzer*innen und Anwendungen möglich. Wenn der Zugriff von nicht privilegierten Anwendungen erforderlich ist, muss der SerCx/SerCx2-Client den Standard-Sicherheitsdeskriptor nach dem Aufruf von SerCx2InitializeDeviceInit() oder SerCxDeviceInitConfig(), aber vor dem Aufruf von SerCx2InitializeDevice() oder SerCxInitialize() überschreiben, wobei der angewandte Sicherheitsdeskriptor an das Controller-PDO weitergegeben wird.

Nachfolgend finden Sie ein Beispiel für die Aktivierung des unprivilegierten Zugriffs auf SerCx2 aus dem EvtDeviceAdd des Treibers des SerCx2-Client-Controllers.

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)) {
        ...
    }

    ...
}

Verhaltensänderungen bei der Verwendung einer Geräte-Schnittstelle

Die Aktivierung dieser Funktion führt zu den folgenden Verhaltensänderungen in SerCx/SerCx2 (im Gegensatz zum Zugriff auf den SerCx/SerCx2-Controller über eine Verbindungs-ID):

  • Es wird keine Standardkonfiguration auf den Port angewendet (Geschwindigkeit, Parität usw.). Da es keine Verbindungsressource in ACPI gibt, die dies beschreibt, beginnt der Port in einem nicht initialisierten Zustand. Software, die mit der Schnittstelle des Geräts interagiert, muss den Port über die definierte serielle IOCTL-Schnittstelle konfigurieren.

  • Aufrufe des SerCx/SerCx2-Client-Treibers zum Abfragen oder Anwenden der Standardkonfiguration geben einen Fehlerstatus zurück. Außerdem schlagen IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION-Anfragen an die Geräteschnittstelle fehl, da keine Standardkonfiguration angegeben ist, die übernommen werden kann.

Zugriff auf die Geräteschnittstelle des seriellen Ports

Bei UWP-Anwendungen kann auf die veröffentlichte Schnittstelle wie auf jeden anderen konformen seriellen Port über die Windows.Devices.SerialCommunication-Namespace-APIs zugegriffen werden.

Bei Win32-Anwendungen wird die Geräteschnittstelle über den folgenden Prozess gefunden und darauf zugegriffen:

  1. Die Anwendung ruft CM_Get_Device_Interface_ListW auf, um eine Liste aller Geräteschnittstellen der Klasse GUID_DEVINTERFACE_COMPORT auf dem System zu erhalten
  2. Die Anwendung ruft CM_Get_Device_Interface_PropertyW für jede zurückgegebene Schnittstelle auf, um die DEVPKEY_DeviceInterface_Serial_PortName für jede gefundene Schnittstelle abzufragen
  3. Wenn der gewünschte Port über den Namen gefunden wird, verwendet die Anwendung die in (1) zurückgegebene symbolische Link-Zeichenfolge, um über CreateFile() ein Handle auf den Port zu öffnen.

Beispielhafter Code für diesen Flow:

#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);

Beachten Sie, dass eine Anwendung auch Benachrichtigungen über das Auftauchen von Geräteschnittstellen und das Entfernen von Geräten abonnieren kann, um ein Handle auf den Controller/Port zu öffnen oder zu schließen, wenn das Gerät verfügbar oder nicht verfügbar ist.