Schreiben eines UCSI-Clienttreibers

Ein USB Type-C Verbinden or System Software Interface (UCSI)-Treiber dient als Controllertreiber für ein USB Type-C-System mit einem eingebetteten Controller (EC).

Wenn Ihr System, das Platform Policy Manager (PPM) implementiert, wie in der UCSI-Spezifikation beschrieben, in einer EC, die mit dem System verbunden ist, über:

  • Ein ACPI-Transport ist nicht erforderlich, um einen Treiber zu schreiben. Laden Sie den von Microsoft bereitgestellten In-Box-Treiber (UcmUcsiCx.sys und UcmUcsiAcpiClient.sys). (Siehe UCSI-Treiber).

  • Ein Nicht-ACPI-Transport, z. B. USB, PCI, I2C oder UART, müssen Sie einen Clienttreiber für den Controller schreiben.

Hinweis

Wenn Ihre USB-Type-C-Hardware nicht über die Möglichkeit verfügt, den Pd-Zustandsautomaten (Power Delivery) zu verarbeiten, sollten Sie einen USB-Type-C-Portcontrollertreiber schreiben. Weitere Informationen finden Sie unter Schreiben eines USB Type-C-Anschlusscontrollertreibers.

Ab Windows 10, Version 1809, wurde eine neue Klassenerweiterung für UCSI (UcmUcsiCx.sys) hinzugefügt, die die UCSI-Spezifikation auf transportagnostische Weise implementiert. Mit minimalem Codeumfang kann Ihr Treiber, der ein Client für UcmUcsiCx ist, mit der USB-Typ-C-Hardware über den Nicht-ACPI-Transport kommunizieren. In diesem Thema werden die dienste beschrieben, die von der UCSI-Klassenerweiterung und dem erwarteten Verhalten des Clienttreibers bereitgestellt werden.

Offizielle Spezifikationen

Gilt für:

  • Windows 10, Version 1809

WDF-Version

  • KMDF, Version 1.27

Wichtige APIs

Referenz zu UcmUcsiCx-Klassenerweiterungen

Beispiel

Beispiel für ucmUcsiCx-Clienttreiber

Ersetzen Sie die ACPI-Teile durch Ihre Implementierung für den erforderlichen Bus.

UCSI-Klassenerweiterungsarchitektur

Die UCSI-Klassenerweiterung UcmUcsiCx ermöglicht es Ihnen, einen Treiber zu schreiben, der mit seinem eingebetteten Controller kommuniziert, indem Sie nicht-ACPI-Transport verwenden. Der Controllertreiber ist ein Clienttreiber für UcmUcsiCx. UcmUcsiCx ist wiederum ein Client zum USB-Connector-Manager (UCM). Daher trifft UcmUcsiCx keine eigenen politischen Entscheidungen. Stattdessen werden richtlinien implementiert, die von UCM bereitgestellt werden. UcmUcsiCx implementiert Zustandsautomaten für die Behandlung von PPM-Benachrichtigungen (Platform Policy Manager) vom Clienttreiber und sendet Befehle zum Implementieren von UCM-Richtlinienentscheidungen, sodass eine zuverlässigere Problemerkennung und Fehlerbehandlung möglich ist.

UCSI-Klassenerweiterungsarchitektur.

Betriebssystemrichtlinien-Manager (OPM)

Os Policy Manager (OPM) implementiert die Logik für die Interaktion mit PPM, wie in der UCSI-Spezifikation beschrieben. OPM ist für:

  • Konvertieren von UCM-Richtlinien in UCSI-Befehle und UCSI-Benachrichtigungen in UCM-Benachrichtigungen.
  • Senden von UCSI-Befehlen, die zum Initialisieren von PPM, zum Erkennen von Fehlern und wiederherstellungsmechanismen erforderlich sind.

Behandeln von UCSI-Befehlen

Ein typischer Vorgang umfasst mehrere Befehle, die von der UCSI-komplizenten Hardware abgeschlossen werden. Betrachten wir beispielsweise den Befehl GET_CONNECTOR_STATUS.

  1. Die PPM-Firmware sendet eine Verbindungsänderungsbenachrichtigung an den UcmUcsiCx/Clienttreiber.
  2. Als Reaktion sendet der UcmUcsiCx/Clienttreiber einen GET_CONNECTOR_STATUS Befehl zurück an die PPM-Firmware.
  3. Die PPM-Firmware wird GET_CONNECTOR_STATUS ausgeführt und sendet asynchron eine Befehlsvervollständigen-Benachrichtigung an den UcmUcsiCx/Clienttreiber. Diese Benachrichtigung enthält Daten zum tatsächlichen Verbindungsstatus.
  4. Der UcmUcsiCx/Clienttreiber verarbeitet diese Statusinformationen und sendet eine ACK_CC_CI an die PPM-Firmware.
  5. Die PPM-Firmware führt ACK_CC_CI aus und sendet asynchron eine Befehlsvervollständigen-Benachrichtigung an den UcmUcsiCx/Clienttreiber.
  6. Der UcmUcsiCx/Clienttreiber betrachtet den GET_CONNECTOR_STATUS Befehl als abgeschlossen.

Kommunikation mit Platform Policy Manager (PPM)

UcmUcsiCx abstrahiert die Details zum Senden von UCSI-Befehlen von OPM an die PPM-Firmware und empfangen Benachrichtigungen von der PPM-Firmware. Er konvertiert PPM-Befehle in WDFREQUEST-Objekte und leitet sie an den Clienttreiber weiter.

  • PPM-Benachrichtigungen

    Der Clienttreiber benachrichtigt UcmUcsiCx über PPM-Benachrichtigungen von der Firmware. Der Treiber stellt den UCSI-Datenblock bereit, der CCI enthält. UcmUcsiCx leitet Benachrichtigungen an OPM und andere Komponenten weiter, die basierend auf den Daten geeignete Maßnahmen ergreifen.

  • IOCTLs für den Clienttreiber

    UcmUcsiCx sendet UCSI-Befehle (über IOCTL-Anforderungen) an den Clienttreiber, um an die PPM-Firmware zu senden. Der Treiber ist dafür verantwortlich, die Anforderung abzuschließen, nachdem er den UCSI-Befehl an die Firmware gesendet hat.

Behandeln von Stromübergängen

Der Clienttreiber ist der Besitzer der Energierichtlinie.

Wenn der Clienttreiber aufgrund von S0-Idle in einen Dx-Zustand wechselt, bringt WDF den Treiber an D0, wenn der UcmUcsiCx eine IOCTL mit einem UCSI-Befehl an die stromverwaltete Warteschlange des Clienttreibers sendet. Der Clienttreiber in S0-Idle sollte einen unterstützten Zustand erneut eingeben, wenn eine PPM-Benachrichtigung von der Firmware vorhanden ist, da in S0-Idle PPM-Benachrichtigungen weiterhin aktiviert sind.

Voraussetzungen

  • Ermitteln Sie den Typ des Treibers, den Sie schreiben müssen, je nachdem, ob Ihre Hardware oder Firmware PD-Zustandscomputer und den Transport implementiert.

    Entscheidung für die Auswahl der richtigen Klassenerweiterung. Weitere Informationen finden Sie unter Entwickeln von Windows-Treibern für USB-Typ-C-Connectors.

  • Installieren Sie Windows 10 für Desktopeditionen (Home, Pro, Enterprise und Education).

  • Installieren Sie das neueste Windows Driver Kit (WDK) auf Ihrem Entwicklungscomputer. Das Kit verfügt über die erforderlichen Headerdateien und Bibliotheken zum Schreiben des Clienttreibers, insbesondere:

    • Die Stubbibliothek (UcmUcsiCxStub.lib). Die Bibliothek übersetzt Aufrufe des Clienttreibers und übergibt sie an die Klassenerweiterung.
    • Die Headerdatei Ucmucsicx.h.
    • Der Clienttreiber wird im Kernelmodus ausgeführt und bindet an die KMDF 1.27-Bibliothek.
  • Machen Sie sich mit Windows Driver Foundation (WDF) vertraut. Empfohlene Lektüre: Entwickeln von Drivers with Windows Driver Foundation , geschrieben von Cent Orwick und Guy Smith.

1. Registrieren Sie Ihren Clienttreiber bei UcmUcsiCx

In Ihrer EVT_WDF_DRIVER_DEVICE_ADD Implementierung.

  1. Nachdem Sie die Plug & Play- und Power Management-Ereignisrückruffunktionen (WdfDeviceInitSetPnpPowerEventCallbacks) festgelegt haben, rufen Sie ucmUcsiDeviceInitInitialize auf, um die WDFDEVICE_INIT undurchsichtige Struktur zu initialisieren. Der Aufruf ordnet den Clienttreiber dem Framework zu.

  2. Rufen Sie nach dem Erstellen des Framework-Geräteobjekts (WDFDEVICE) UcmUcsiDeviceInitialize auf, um den Client-Diver bei UcmUcsiCx zu registrieren.

2. Erstellen des PPM-Objekts mit UcmUcsiCx

Verwenden Sie in Ihrer Implementierung von EVT_WDF_DEVICE_PREPARE_HARDWARE, nachdem Sie die Liste der rohen und übersetzten Ressourcen erhalten haben, die Ressourcen, um die Hardware vorzubereiten. Wenn Ihr Transport z. B. I2C ist, lesen Sie die Hardwareressourcen, um einen Kommunikationskanal zu öffnen. Erstellen Sie als Nächstes ein PPM-Objekt. Zum Erstellen des Objekts müssen Sie bestimmte Konfigurationsoptionen festlegen.

  1. Stellen Sie einen Handle für die Verbindersammlung auf dem Gerät bereit.

    1. Erstellen Sie die Connectorauflistung, indem Sie UcmUcsi Verbinden orCollectionCreate aufrufen.

    2. Aufzählen der Verbinder auf dem Gerät und Hinzufügen der Verbinder zur Sammlung durch Aufrufen von UcmUcsi Verbinden orCollectionAdd Verbinden 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. Entscheiden Sie, ob Sie den Gerätecontroller aktivieren möchten.

  3. Konfigurieren und erstellen Sie das PPM-Objekt.

    1. Initialisieren Sie eine UCMUCSI_PPM_CONFIG Struktur, indem Sie den verbinderhandle bereitstellen, den Sie in Schritt 1 erstellt haben.

    2. Legen Sie usbDeviceControllerEnabled auf einen booleschen Wert fest, der in Schritt 2 ermittelt wird.

    3. Legen Sie Ihre Ereignisrückrufe in WDF_OBJECT_ATTRIBUTES fest.

    4. Rufen Sie UcmUcsiPpmCreate auf , indem Sie alle konfigurierten Strukturen übergeben.

      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. Einrichten von E/A-Warteschlangen

UcmUcsiCx sendet UCSI-Befehle an den Clienttreiber, um an die PPM-Firmware zu senden. Die Befehle werden in Form dieser IOCTL-Anforderungen in einer WDF-Warteschlange gesendet.

Der Clienttreiber ist für das Erstellen und Registrieren dieser Warteschlange bei UcmUcsiCx durch Aufrufen von UcmUcsiPpmSetUcsiCommandRequestQueue verantwortlich. Die Warteschlange muss energieverwaltet werden.

UcmUcsiCx garantiert, dass es höchstens eine ausstehende Anforderung in der WDF-Warteschlange geben kann. Der Clienttreiber ist auch dafür verantwortlich, die WDF-Anforderung abzuschließen, nachdem er den UCSI-Befehl an die Firmware gesendet hat.

In der Regel richtet der Treiber Warteschlangen in seiner Implementierung von EVT_WDF_DEVICE_PREPARE_HARDWARE ein.

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

Außerdem muss der Clienttreiber ucmUcsiPpmStart aufrufen, um UcmUcsiCx zu benachrichtigen, dass der Treiber bereit ist, die IOCTL-Anforderungen zu empfangen. Es wird empfohlen, diesen Aufruf in Ihrer EVT_WDF_DEVICE_PREPARE_HARDWARE nach dem Erstellen des WDFQUEUE-Handles für den Empfang von UCSI-Befehlen über UcmUcsiPpmSetUcsiCommandRequestQueue vorzunehmen. Wenn der Treiber dagegen keine weiteren Anforderungen verarbeiten möchte, muss er UcmUcsiPpmStop aufrufen. Dies geschieht in Ihrer EVT_WDF_DEVICE_RELEASE_HARDWARE Implementierung.

4. Behandeln der IOCTL-Anforderungen

Betrachten Sie diese Beispielsequenz der Ereignisse, die auftreten, wenn ein USB-Typ-C-Partner an einen Verbinder angeschlossen ist.

  1. PPM-Firmware bestimmt ein Attach-Ereignis und sendet eine Benachrichtigung an den Clienttreiber.
  2. Clienttreiber ruft UcmUcsiPpmNotification auf, um diese Benachrichtigung an UcmUcsiCx zu senden.
  3. UcmUcsiCx benachrichtigt den OPM-Zustandscomputer und sendet einen Befehl "Get Verbinden or Status" an UcmUcsiCx.
  4. UcmUcsiCx erstellt eine Anforderung und sendet IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK an den Clienttreiber.
  5. Der Clienttreiber verarbeitet diese Anforderung und sendet den Befehl an die PPM-Firmware. Der Treiber schließt diese Anforderung asynchron ab und sendet eine weitere Benachrichtigung an UcmUcsiCx.
  6. Bei erfolgreicher Befehlsvervollständigen-Benachrichtigung liest der OPM-Zustandscomputer die Nutzlast (mit Connectorstatusinformationen) und benachrichtigt UCM über das Type-C Attach-Ereignis.

In diesem Beispiel hat die Nutzlast auch angegeben, dass eine Änderung des Leistungsübermittlungsstatus zwischen der Firmware und dem Portpartner erfolgreich war. Der OPM-Zustandscomputer sendet einen anderen UCSI-Befehl: Abrufen von PDOs. Ähnlich wie der Befehl "Get Verbinden or Status" benachrichtigt der OPM-Zustandscomputer die UCM dieses Ereignisses, wenn der Befehl "PDOs abrufen" erfolgreich abgeschlossen wird.

Der Handler des Clienttreibers für EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL ähnelt diesem Beispielcode. Informationen zum Behandeln von Anforderungen finden Sie unter Anforderungshandler

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