Écrire un pilote client UCSI

Un pilote UCSI (USB Type-C) Connecter or System Software Interface (UCSI) sert de pilote de contrôleur pour un système USB Type-C avec un contrôleur incorporé (EC).

Si votre système implémente Platform Policy Manager (PPM), comme décrit dans la spécification UCSI, dans une EC connectée au système sur :

  • Un transport ACPI, vous n’avez pas besoin d’écrire un pilote. Chargez le pilote intégré fourni par Microsoft (UcmUcsiCx.sys et UcmUcsiAcpiClient.sys). (Voir Pilote UCSI).

  • Un transport non ACPI, tel que USB, PCI, I2C ou UART, vous devez écrire un pilote client pour le contrôleur.

Remarque

Si votre matériel USB Type-C n’a pas la possibilité de gérer la machine d’état de distribution d’alimentation (PD), envisagez d’écrire un pilote de contrôleur de port USB Type-C. Pour plus d’informations, consultez Écrire un pilote de contrôleur de port USB Type-C.

À compter de Windows 10, version 1809, une nouvelle extension de classe pour UCSI (UcmUcsiCx.sys) a été ajoutée, qui implémente la spécification UCSI de manière indépendante du transport. Avec une quantité minimale de code, votre pilote, qui est un client à UcmUcsiCx, peut communiquer avec le matériel USB Type-C sur le transport non ACPI. Cette rubrique décrit les services fournis par l’extension de classe UCSI et le comportement attendu du pilote client.

Spécifications officielles

S’applique à :

  • Windows 10, version 1809

Version WDF

  • KMDF version 1.27

API importantes

Informations de référence sur les extensions de classe UcmUcsiCx

Exemple

Exemple de pilote client UcmUcsiCx

Remplacez les parties ACPI par votre implémentation pour le bus requis.

Architecture d’extension de classe UCSI

L’extension de classe UCSI, UcmUcsiCx, vous permet d’écrire un pilote qui communique avec son contrôleur incorporé à l’aide du transport non ACPI. Le pilote du contrôleur est un pilote client vers UcmUcsiCx. UcmUcsiCx est à son tour un client vers le gestionnaire de connecteur USB (UCM). Par conséquent, UcmUcsiCx ne prend aucune décision de politique propre. Au lieu de cela, il implémente des stratégies fournies par UCM. UcmUcsiCx implémente des ordinateurs d’état pour gérer les notifications Platform Policy Manager (PPM) du pilote client et envoie des commandes pour implémenter des décisions de stratégie UCM, ce qui permet une détection des problèmes et une gestion des erreurs plus fiables.

Architecture d’extension de classe UCSI.

Gestionnaire de stratégies de système d’exploitation (OPM)

OPM (OS Policy Manager) implémente la logique d’interaction avec PPM, comme décrit dans la spécification UCSI. OPM est responsable des opérations suivantes :

  • Conversion de stratégies UCM en commandes UCSI et notifications UCSI en notifications UCM.
  • Envoi de commandes UCSI requises pour initialiser ppm, détecter les erreurs et les mécanismes de récupération.

Gestion des commandes UCSI

Une opération classique implique plusieurs commandes à terminer par le matériel UCSI-complicant. Prenons l’exemple de la commande GET_CONNECTOR_STATUS.

  1. Le microprogramme PPM envoie une notification de modification de connexion au pilote UcmUcsiCx/client.
  2. En réponse, le pilote UcmUcsiCx/client envoie une commande GET_CONNECTOR_STATUS au microprogramme PPM.
  3. Le microprogramme PPM exécute GET_CONNECTOR_STATUS et envoie de manière asynchrone une notification complète de commande au pilote UcmUcsiCx/client. Cette notification contient des données sur l’état de connexion réel.
  4. Le pilote UcmUcsiCx/client traite ces informations d’état et envoie une ACK_CC_CI au microprogramme PPM.
  5. Le microprogramme PPM exécute ACK_CC_CI et envoie de manière asynchrone une notification complète de commande au pilote UcmUcsiCx/client.
  6. Le pilote UcmUcsiCx/client considère que la commande GET_CONNECTOR_STATUS doit être terminée.

Communication avec Platform Policy Manager (PPM)

UcmUcsiCx extrait les détails de l’envoi de commandes UCSI d’OPM au microprogramme PPM et de la réception de notifications du microprogramme PPM. Il convertit les commandes PPM en objets WDFREQUEST et les transfère au pilote client.

  • PPM Notifications

    Le pilote client informe UcmUcsiCx des notifications PPM du microprogramme. Le pilote fournit le bloc de données UCSI contenant cci. UcmUcsiCx transfère les notifications à OPM et à d’autres composants qui prennent les mesures appropriées en fonction des données.

  • IOCTLs au pilote client

    UcmUcsiCx envoie des commandes UCSI (via des demandes IOCTL) au pilote client pour envoyer au microprogramme PPM. Le pilote est responsable de la fin de la demande après avoir envoyé la commande UCSI au microprogramme.

Gestion des transitions d’alimentation

Le pilote client est le propriétaire de la stratégie d’alimentation.

Si le pilote client entre dans un état Dx en raison de S0-Idle, WDF l’amène à D0 lorsque l’UcmUcsiCx envoie une durée de vie IOCTL contenant une commande UCSI à la file d’attente gérée par le pilote client. Le pilote client dans S0-Idle doit réentérer un état alimenté lorsqu’il existe une notification PPM à partir du microprogramme, car dans S0-Idle, les notifications PPM sont toujours activées.

Avant de commencer

  • Déterminez le type de pilote que vous devez écrire selon que votre matériel ou microprogramme implémente la machine d’état PD et le transport.

    Décision de choisir l’extension de classe appropriée. Pour plus d’informations, consultez Développement de pilotes Windows pour les connecteurs USB Type-C.

  • Installez Windows 10 pour les éditions de bureau (Famille, Professionnel, Entreprise et Éducation).

  • Installez le dernier kit de pilotes Windows (WDK) sur votre ordinateur de développement. Le kit contient les fichiers et bibliothèques d’en-tête requis pour écrire le pilote client, en particulier :

    • Bibliothèque stub, (UcmUcsiCxStub.lib). La bibliothèque traduit les appels effectués par le pilote client et les transmet à l’extension de classe.
    • Fichier d’en-tête, Ucmucsicx.h.
    • Le pilote client s’exécute en mode noyau et lie à la bibliothèque KMDF 1.27.
  • Familiarisez-vous avec Windows Driver Foundation (WDF). Lecture recommandée : Développement de pilotes avec Windows Driver Foundation écrit par Penny Orwick et Guy Smith.

1. Inscrire votre pilote client auprès d’UcmUcsiCx

Dans votre implémentation EVT_WDF_DRIVER_DEVICE_ADD.

  1. Après avoir défini les fonctions de rappel d’événements de Plug-and-Play et de gestion de l’alimentation (WdfDeviceInitSetPnpPowerEventCallbacks), appelez UcmUcsiDeviceInitialize pour initialiser la structure opaque WDFDEVICE_INIT. L’appel associe le pilote client à l’infrastructure.

  2. Après avoir créé l’objet d’appareil framework (WDFDEVICE), appelez UcmUcsiDeviceInitialize pour inscrire le plongeur client auprès d’UcmUcsiCx.

2. Créer l’objet PPM avec UcmUcsiCx

Dans votre implémentation de EVT_WDF_DEVICE_PREPARE_HARDWARE, après avoir reçu la liste des ressources brutes et traduites, utilisez les ressources pour préparer le matériel. Par exemple, si votre transport est I2C, lisez les ressources matérielles pour ouvrir un canal de communication. Ensuite, créez un objet PPM. Pour créer l’objet, vous devez définir certaines options de configuration.

  1. Fournissez un handle à la collection de connecteurs sur l’appareil.

    1. Créez la collection de connecteurs en appelant UcmUcsi Connecter orCollectionCreate.

    2. Énumérer les connecteurs sur l’appareil et les ajouter à la collection en appelant UcmUcsi Connecter orCollectionAdd Connecter or

      // Create the connector collection.
      
      UCMUCSI_CONNECTOR_COLLECTION* ConnectorCollectionHandle;
      
      status = UcmUcsiConnectorCollectionCreate(Device, //WDFDevice
               WDF_NO_OBJECT_ATTRIBUTES,
               ConnectorCollectionHandle);
      
      // Enumerate the connectors on the device.
      // ConnectorId of 0 is reserved for the parent device.
      // In this example, we assume the parent has no children connectors.
      
      UCMUCSI_CONNECTOR_INFO_INIT(&connectorInfo);
      connectorInfo.ConnectorId = 0;
      
      status = UcmUcsiConnectorCollectionAddConnector ( &ConnectorCollectionHandle,
                   &connectorInfo);
      
  2. Déterminez si vous souhaitez activer le contrôleur d’appareil.

  3. Configurez et créez l’objet PPM.

    1. Initialisez une structure UCMUCSI_PPM_CONFIG en fournissant le handle de connecteur que vous avez créé à l’étape 1.

    2. Définissez le membre UsbDeviceControllerEnabled sur une valeur booléenne déterminée à l’étape 2.

    3. Définissez vos rappels d’événements dans WDF_OBJECT_ATTRIBUTES.

    4. Appelez UcmUcsiPpmCreate en passant toutes les structures configurées.

      UCMUCSIPPM ppmObject = WDF_NO_HANDLE;
      PUCMUCSI_PPM_CONFIG UcsiPpmConfig;
      WDF_OBJECT_ATTRIBUTES attrib;
      
      UCMUCSI_PPM_CONFIG_INIT(UcsiPpmConfig, ConnectorCollectionHandle);
      
      UcsiPpmConfig->UsbDeviceControllerEnabled = TRUE;
      
      WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attrib, Ppm);
      attrib->EvtDestroyCallback = &EvtObjectContextDestroy;
      
      status = UcmUcsiPpmCreate(wdfDevice, UcsiPpmConfig, &attrib, &ppmObject);
      

3. Configurer des files d’attente d’E/S

UcmUcsiCx envoie des commandes UCSI au pilote client pour l’envoyer au microprogramme PPM. Les commandes sont envoyées sous la forme de ces demandes IOCTL dans une file d’attente WDF.

Le pilote client est chargé de créer et d’inscrire cette file d’attente dans UcmUcsiCx en appelant UcmUcsiPpmSetUcsiCommandRequestQueue. La file d’attente doit être gérée par l’alimentation.

UcmUcsiCx garantit qu’il peut y avoir au plus une demande en attente dans la file d’attente WDF. Le pilote client est également responsable de la fin de la requête WDF après avoir envoyé la commande UCSI au microprogramme.

En règle générale, le pilote configure des files d’attente dans son implémentation de EVT_WDF_DEVICE_PREPARE_HARDWARE.

WDFQUEUE UcsiCommandRequestQueue = WDF_NO_HANDLE;
WDF_OBJECT_ATTRIBUTES attrib;
WDF_IO_QUEUE_CONFIG queueConfig;

WDF_OBJECT_ATTRIBUTES_INIT(&attrib);
attrib.ParentObject = GetObjectHandle();

// In this example, even though the driver creates a sequential queue,
// UcmUcsiCx guarantees that will not send another request
// until the previous one has been completed.


WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);

// The queue must be power-managed.

queueConfig.PowerManaged = WdfTrue;
queueConfig.EvtIoDeviceControl = EvtIoDeviceControl;

status = WdfIoQueueCreate(device, &queueConfig, &attrib, &UcsiCommandRequestQueue);

UcmUcsiPpmSetUcsiCommandRequestQueue(ppmObject, UcsiCommandRequestQueue);

En outre, le pilote client doit également appeler UcmUcsiPpmStart pour informer UcmUcsiCx que le pilote est prêt à recevoir les demandes IOCTL. Nous vous recommandons d’effectuer cet appel dans votre EVT_WDF_DEVICE_PREPARE_HARDWARE après avoir créé le handle WDFQUEUE pour recevoir des commandes UCSI, via UcmUcsiPpmSetUcsiCommandRequestQueue. À l’inverse, lorsque le pilote ne souhaite pas traiter plus de requêtes, il doit appeler UcmUcsiPpmStop. Procédez comme suit dans votre implémentation EVT_WDF_DEVICE_RELEASE_HARDWARE.

4. Gérer les demandes IOCTL

Prenons cet exemple de séquence des événements qui se produisent lorsqu’un partenaire USB Type-C est attaché à un connecteur.

  1. Le microprogramme PPM détermine un événement d’attachement et envoie une notification au pilote client.
  2. Le pilote client appelle UcmUcsiPpmNotification pour envoyer cette notification à UcmUcsiCx.
  3. UcmUcsiCx avertit la machine d’état OPM et envoie une commande Get Connecter or Status à UcmUcsiCx.
  4. UcmUcsiCx crée une requête et envoie IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK au pilote client.
  5. Le pilote client traite cette requête et envoie la commande au microprogramme PPM. Le pilote termine cette requête de façon asynchrone et envoie une autre notification à UcmUcsiCx.
  6. Lors de la notification complète de la commande réussie, l’ordinateur d’état OPM lit la charge utile (contenant les informations d’état du connecteur) et avertit UCM de l’événement d’attachement Type-C.

Dans cet exemple, la charge utile a également indiqué qu’une modification de l’état de négociation de la distribution de l’alimentation entre le microprogramme et le partenaire de port a réussi. La machine d’état OPM envoie une autre commande UCSI : Obtenir des PDO. Comme pour obtenir la commande Get Connecter or Status, lorsque la commande Get PDOs s’exécute correctement, l’ordinateur d’état OPM informe UCM de cet événement.

Le gestionnaire du pilote client pour EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL est similaire à cet exemple de code. Pour plus d’informations sur la gestion des demandes, consultez Gestionnaires de demandes

void EvtIoDeviceControl(
    _In_ WDFREQUEST Request,
    _In_ ULONG IoControlCode
    )
{
...
    switch (IoControlCode)
    {
    case IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK:
        EvtSendData(Request);
        break;

    case IOCTL_UCMUCSI_PPM_GET_UCSI_DATA_BLOCK:
        EvtReceiveData(Request);
        break;

    default:
        status = STATUS_NOT_SUPPORTED;
        goto Exit;
    }

    status = STATUS_SUCCESS;

Exit:

    if (!NT_SUCCESS(status))
    {
        WdfRequestComplete(Request, status);
    }

}

VOID EvtSendData(
    WDFREQUEST Request
    )
{
    NTSTATUS status;
    PUCMUCSI_PPM_SEND_UCSI_DATA_BLOCK_IN_PARAMS inParams;

    status = WdfRequestRetrieveInputBuffer(Request, sizeof(*inParams),
        reinterpret_cast<PVOID*>(&inParams), nullptr);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    // Build a UCSI command request and send to the PPM firmware.

Exit:
    WdfRequestComplete(Request, status);
}

VOID EvtReceiveData(
    WDFREQUEST Request
    )
{

    NTSTATUS status;

    PUCMUCSI_PPM_GET_UCSI_DATA_BLOCK_IN_PARAMS inParams;
    PUCMUCSI_PPM_GET_UCSI_DATA_BLOCK_OUT_PARAMS outParams;

    status = WdfRequestRetrieveInputBuffer(Request, sizeof(*inParams),
        reinterpret_cast<PVOID*>(&inParams), nullptr);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(*outParams),
        reinterpret_cast<PVOID*>(&outParams), nullptr);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    // Receive data from the PPM firmware.
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }
    WdfRequestSetInformation(Request, sizeof(*outParams));

Exit:
    WdfRequestComplete(Request, status);
}