Partager via


Publication de l’interface d’appareil pour un port série géré SerCx ou SerCx2

À compter de Windows 10 version 1903 et ultérieure, SerCx et SerCx2 incluent la prise en charge de la publication d’une interface d’appareil.GUID_DEVINTERFACE_COMPORT Les applications et les services sur un système peuvent utiliser cette interface d’appareil pour interagir avec le port série.

Cette fonctionnalité peut être activée sur les plateformes SoC qui présentent un UART intégré avec un pilote client SerCx/SerCx2, si l’UART est exposé en tant que port physique, ou si des applications régulières (UWP ou Win32) doivent communiquer directement avec un appareil attaché à l’UART. Ceci par opposition à l’accès au contrôleur SerCx/SerCx2 via un ID de connexion, qui permet exclusivement l’accès à l’UART à partir d’un pilote d’appareil dédié.

Lorsque vous utilisez cette fonctionnalité pour les ports série gérés SerCx/SerCx2, un numéro de port COM n’est pas attribué pour ces appareils et aucun lien symbolique n’est créé, ce qui signifie que les applications doivent utiliser l’approche décrite dans ce document pour ouvrir le port série en tant qu’interface d’appareil.

L’utilisation de l’interface de périphérique (GUID_DEVINTERFACE_COMPORT) est la méthode recommandée pour découvrir et accéder à un port COM. L’utilisation des anciens noms de ports COM est sujette à des collisions de noms et ne fournit pas de notifications de changement d’état à un client. L’utilisation des noms de ports COM hérités est déconseillée et n’est pas prise en charge avec SerCx2 et SerCx.

Activation de la création de l’interface d’appareil

Vous trouverez ci-dessous les instructions permettant d’activer la création de l’interface d’appareil. Il convient de noter que les ports série sont exclusifs, ce qui signifie que si le port série est accessible en tant qu’interface d’appareil, une ressource de connexion dans l’ACPI ne doit pas être fournie à d’autres appareils. Par exemple, aucune ressource UARTSerialBusV2 ne doit être fournie à d’autres appareils sur le système ; le port doit être rendu exclusivement accessible via l’interface d’appareil.

Configuration ACPI

Un fabricant ou un intégrateur de systèmes peut activer ce comportement en modifiant la définition ACPI (ASL) du dispositif SerCx/SerCx2 existant pour ajouter une définition _DSD pour les propriétés clé-valeur de l’appareil avec l’UUID daffd814-6eba-4d8c-8a91-bc9bbf4aa301. Dans cette définition, la propriété SerCx-FriendlyName est définie avec une description spécifique du système du port série, par exemple, UART0, UART1, etc.

Exemple de définition d’appareil (à l’exclusion des informations spécifiques du fournisseur nécessaires pour définir l’appareil) :

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

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

L’UUID (daffd814-6eba-4d8c-8a91-bc9bbf4aa301) spécifié doit être utilisé et l’entrée SerCx-FriendlyName doit être définie pour SerCx/SerCx2 pour créer l’interface de l’appareil.

Clé du Registre

À des fins de développement, le SerCxFriendlyName peut également être configuré en tant que propriété dans la clé matérielle de l’appareil dans le Registre. La méthode CM_Open_DevNode_Key peut être utilisée pour accéder à la clé matérielle de l’appareil et ajouter la propriété SerCxFriendlyName à ce dernier, qui est utilisée par SerCx/SerCx2 pour récupérer le nom convivial de l’interface d’appareil.

Il n’est pas recommandé de définir cette clé par le biais d’un INF d’extension, elle est fournie principalement à des fins de test et de développement. L’approche recommandée consiste à activer la fonctionnalité via l’ACPI comme indiqué ci-dessus.

Interface d’appareil

Si un FriendlyName paramètre est défini à l’aide des méthodes ci-dessus, SerCx/SerCx2 publie une GUID_DEVINTERFACE_COMPORT interface d’appareil pour le contrôleur. La propriété DEVPKEY_DeviceInterface_Serial_PortName de cette interface d’appareil sera définie sur le nom convivial spécifié, qui peut être utilisé par les applications pour localiser un contrôleur/port spécifique.

Activation de l’accès non privilégié

Par défaut, le contrôleur/port est uniquement accessible aux utilisateurs et applications privilégiés. Si l’accès à partir d’applications non privilégiées est requis, le client SerCx/SerCx2 doit remplacer le descripteur de sécurité par défaut après avoir appelé SerCx2InitializeDeviceInit() ou SerCxDeviceInitConfig(), mais avant d’appeler SerCx2InitializeDevice() ou SerCxInitialize(), moment auquel le descripteur de sécurité appliqué est propagé au contrôleur PDO.

Vous trouverez ci-dessous un exemple d’activation de l’accès non privilégié sur SerCx2 à partir du EvtDeviceAdd du pilote du contrôleur client 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)) {
        ...
    }

    ...
}

Changements de comportement lors de l’utilisation d’une interface d’appareil

L’activation de cette fonction entraîne les changements de comportement suivants dans SerCx/SerCx2 (par opposition à l’accès au contrôleur SerCx/SerCx2 par l’intermédiaire d’un ID de connexion) :

  • Aucune configuration par défaut n’est appliquée au port (vitesse, parité, etc.). Comme il n’existe aucune ressource de connexion dans l’ACPI pour le décrire, le port commence dans un état non initialisé. Les logiciels qui interagissent avec l’interface d’appareil sont nécessaires pour configurer le port à l’aide de l’interface IOCTL série définie.

  • Les appels du pilote client SerCx/SerCx2 pour interroger ou appliquer la configuration par défaut renvoient un état d’échec. En outre, IOCTL_SERIAL_APPLY_DEFAULT_CONFIGURATION les demandes adressées à l’interface d’appareil échouent, car aucune configuration par défaut n’est spécifiée.

Accès à l’interface d’appareil de port série

Pour les applications UWP, l’interface publiée est accessible à l’aide des API Windows.Devices.SerialCommunication d’espace de noms comme tout autre port série conforme.

Pour les applications Win32, l’interface de l’appareil est située et accessible à l’aide du processus suivant :

  1. L’application appelle CM_Get_Device_Interface_ListW pour obtenir la liste de toutes les interfaces d’appareil de classe GUID_DEVINTERFACE_COMPORT sur le système
  2. L’application appelle CM_Get_Device_Interface_PropertyW pour chaque interface renvoyée pour interroger la DEVPKEY_DeviceInterface_Serial_PortName de chaque interface découverte
  3. Lorsque le port souhaité est trouvé par son nom, l’application utilise la chaîne de liaison symbolique renvoyée dans (1) pour ouvrir un handle sur le port via CreateFile()

Exemple de code pour ce flux :

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

Il convient de noter qu’une application peut également s’abonner aux notifications d’arrivée de l’interface d’appareil et à la suppression de l’appareil, afin d’ouvrir ou de fermer un handle sur le contrôleur/le port lorsque l’appareil devient disponible ou indisponible.