Implementieren der Funktionssperre in einem zusammengesetzten Treiber

Dieser Artikel bietet eine Übersicht über Funktionen zum Anhalten von Funktionen und Funktionen zur Remoteaktivierung für USB 3.0-Multifunktionsgeräte (Universal Serial Bus, USB) 3.0 (Zusammengesetzte Geräte). In diesem Artikel erfahren Sie, wie Sie diese Features in einem Treiber implementieren, der ein zusammengesetztes Gerät steuert. Der Artikel gilt für zusammengesetzte Treiber, die Usbccgp.sys ersetzen.

Die Usb 3.0-Spezifikation (Universal Serial Bus) definiert ein neues Feature namens Funktionssperre. Das Feature ermöglicht es einer einzelnen Funktion eines zusammengesetzten Geräts, unabhängig von anderen Funktionen in einen Zustand mit geringer Leistung zu gelangen. Betrachten Sie ein zusammengesetztes Gerät, das eine Funktion für die Tastatur und eine andere Funktion für die Maus definiert. Der Benutzer behält die Tastaturfunktion im Arbeitszustand, bewegt die Maus aber für einen bestimmten Zeitraum nicht. Der Clienttreiber für die Maus kann den Leerlaufzustand der Funktion erkennen und die Funktion an den Angehalten-Zustand senden, während die Tastaturfunktion im Betriebszustand bleibt.

Das gesamte Gerät kann unabhängig vom Energiezustand einer beliebigen Funktion innerhalb des Geräts in den Status "Anhalten" übergehen. Wenn eine bestimmte Funktion und das gesamte Gerät in den Suspend-Zustand gelangen, wird der Angehalten-Zustand der Funktion beibehalten, während sich das Gerät im Angehalten-Zustand befindet und während der gesamten Ein- und Aussetzungsprozesse des Geräts angehalten wird.

Ähnlich wie die Remote-Reaktivierungsfunktion eines USB 2.0-Geräts (siehe Remote Wake-up von USB-Geräten) kann eine einzelne Funktion in einem USB 3.0-Verbundgerät aus einem Zustand mit geringer Leistung aufwachen, ohne den Leistungszustand anderer Funktionen zu beeinträchtigen. Dieses Feature wird als Funktionsreaktivierung bezeichnet. Das Feature wird explizit vom Host aktiviert, indem eine Protokollanforderung gesendet wird, die die Remoteaktivierungsbits in der Firmware des Geräts festlegt. Dieser Prozess wird als Bewachung der Funktion für die Remoteaktivierung bezeichnet. Informationen zu den Remotereaktivierungsbits finden Sie in Abbildung 9-6 in der offiziellen USB-Spezifikation.

Wenn eine Funktion für das Remote-Reaktivieren aktiviert ist, behält die Funktion (im Zustand "Anhalten") genügend Strom, um ein Reaktivierungs-Fortsetzungssignal zu generieren, wenn ein Benutzerereignis auf dem physischen Gerät auftritt. Als Ergebnis dieses Fortsetzungssignals kann der Clienttreiber den Angehalten-Zustand der zugeordneten Funktion beenden. Im Beispiel für die Mausfunktion im zusammengesetzten Gerät sendet die Mausfunktion ein Fortsetzungssignal an den Host, wenn der Benutzer die Maus im Leerlaufzustand wischt. Auf dem Host erkennt der USB-Treiberstapel, welche Funktion aufgewacht ist, und verteilt die Benachrichtigung an den Clienttreiber der entsprechenden Funktion. Der Clienttreiber kann dann die Funktion aktivieren und in den Arbeitszustand gelangen.

Für den Clienttreiber ähneln die Schritte zum Senden einer Funktion zum Anhalten des Zustands und Zum Aufwecken der Funktion einem Gerätetreiber mit einer Funktion, der das gesamte Gerät in den Angehalten-Zustand sendet. Im folgenden Verfahren werden diese Schritte zusammengefasst.

  1. Erkennen Sie, wenn sich die zugeordnete Funktion im Leerlauf befindet.
  2. Senden sie ein E/A-Anforderungspaket (IRP) im Leerlauf.
  3. Senden Sie eine Anforderung, um ihre Funktion für die Remotereaktivierung zu rüsten, indem Sie ein E/A-Anforderungspaket (Wait-Wake-E/A-Anforderungspaket, IRP) senden.
  4. Überstellen Sie die Funktion in einen Energiesparzustand, indem Sie Dx-Energie-IRPs (D2 oder D3) senden.

Weitere Informationen zu den vorherigen Schritten finden Sie unter Senden einer USB-Idle Request-IRP unter Selektives Anhalten von USB. Ein zusammengesetzter Treiber erstellt ein physisches Geräteobjekt (PDO) für jede Funktion im zusammengesetzten Gerät und verarbeitet Energieanforderungen, die vom Clienttreiber (FDO des Funktionsgerätestapels) gesendet werden. Damit ein Clienttreiber erfolgreich in den Aussetzungszustand für seine Funktion ein- und aussteigen kann, muss der zusammengesetzte Treiber Funktionen zum Anhalten von Funktionen und Remoteaktivierung unterstützen und die empfangenen Energieanforderungen verarbeiten.

In Windows 8 unterstützt der USB-Treiberstapel für USB 3.0-Geräte diese Features. Darüber hinaus wurde dem von Microsoft bereitgestellten generischen übergeordneten USB-Treiber (Usbccgp.sys), dem standardmäßigen zusammengesetzten Windows-Treiber, die Implementierung von Funktionssperre und Funktionsreaktivierung hinzugefügt. Wenn Sie einen benutzerdefinierten zusammengesetzten Treiber schreiben, muss Ihr Treiber Anforderungen im Zusammenhang mit Funktionsansetzungs- und Remotereaktivierungsanforderungen gemäß dem folgenden Verfahren verarbeiten.

Schritt 1: Ermitteln, ob der USB-Treiberstapel das Anhalten der Funktion unterstützt

Führen Sie in der Startgeräteroutine (IRP_MN_START_DEVICE) Ihres zusammengesetzten Treibers die folgenden Schritte aus:

  1. Rufen Sie die USBD_QueryUsbCapability-Routine auf, um zu ermitteln, ob der zugrunde liegende USB-Treiberstapel die Funktion zum Anhalten der Funktion unterstützt. Für den Aufruf ist ein gültiges USBD-Handle erforderlich, das Sie beim vorherigen Aufruf der USBD_CreateHandle Routine erhalten haben.

Ein erfolgreicher Aufruf von USBD_QueryUsbCapability bestimmt, ob der zugrunde liegende USB-Treiberstapel funktionsangehalten wird. Der Aufruf kann einen Fehlercode zurückgeben, der angibt, dass der USB-Treiberstapel das Anhalten der Funktion nicht unterstützt oder das angeschlossene Gerät kein USB 3.0-Multifunktionsgerät ist.

  1. Wenn der USBD_QueryUsbCapability Aufruf angibt, dass funktionsangehalten wird, registrieren Sie das zusammengesetzte Gerät beim zugrunde liegenden USB-Treiberstapel. Zum Registrieren des zusammengesetzten Geräts müssen Sie eine IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE E/A-Steuerungsanforderung senden. Weitere Informationen zu dieser Anforderung finden Sie unter Registrieren eines zusammengesetzten Geräts.

Die Registrierungsanforderung verwendet die REGISTER_COMPOSITE_DEVICE-Struktur , um diese Informationen zum zusammengesetzten Treiber anzugeben. Stellen Sie sicher, dass Sie CapabilityFunctionSuspend auf 1 festlegen, um anzugeben, dass der zusammengesetzte Treiber funktionsangehalten unterstützt.

Ein Codebeispiel, das zeigt, wie Sie ermitteln können, ob der USB-Treiberstapel das Anhalten von Funktionen unterstützt, finden Sie unter USBD_QueryUsbCapability.

Schritt 2: Behandeln des Idle-IRP

Der Clienttreiber kann einen IRP im Leerlauf senden (siehe IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION). Die Anforderung wird gesendet, nachdem der Clienttreiber einen Leerlaufzustand für die Funktion erkannt hat. Die IRP enthält einen Zeiger auf die Rückrufabschlussroutine ( sogenannter Leerlaufrückruf), der vom Clienttreiber implementiert wird. Innerhalb des Leerlaufrückrufs führt der Client Aufgaben aus, z. B. das Abbrechen ausstehender E/A-Übertragungen, kurz bevor die Funktion in den Zustand angehalten wird.

Hinweis

Der IRP-Mechanismus im Leerlauf ist optional für Clienttreiber von USB 3.0-Geräten. Die meisten Clienttreiber werden jedoch so geschrieben, dass sie sowohl USB 2.0- als auch USB 3.0-Geräte unterstützen. Um USB 2.0-Geräte zu unterstützen, muss der Treiber den IRP im Leerlauf senden, da der zusammengesetzte Treiber auf diesem IRP basiert, um den Energiezustand der einzelnen Funktionen nachzuverfolgen. Wenn sich alle Funktionen im Leerlauf befinden, sendet der zusammengesetzte Treiber das gesamte Gerät in den Ruhezustand.

Wenn der Idle-IRP vom Clienttreiber empfangen wird, muss der zusammengesetzte Treiber sofort den Rückruf im Leerlauf aufrufen, um den Clienttreiber darüber zu informieren, dass der Clienttreiber die Funktion zum Anhalten senden kann.

Schritt 3: Senden einer Anforderung für Remoteaktivierungsbenachrichtigungen

Der Clienttreiber kann eine Anforderung zum Ausrüsten seiner Funktion für die Remotereaktivierung senden, indem er eine IRP_MJ_POWER IRP mit untergeordnetem Funktionscode sendet, der auf IRP_MN_WAIT_WAKE festgelegt ist (Wait-Wake-IRP). Der Clienttreiber sendet diese Anforderung nur, wenn der Treiber als Ergebnis eines Benutzerereignisses in den Arbeitszustand wechseln möchte.

Beim Empfang des Warte-Reaktivierungs-IRP muss der zusammengesetzte Treiber die IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION E/A-Steuerungsanforderung an den USB-Treiberstapel senden. Die Anforderung ermöglicht es dem USB-Treiberstapel, den zusammengesetzten Treiber zu benachrichtigen, wenn der Stapel die Benachrichtigung über das Fortsetzungssignal empfängt. Die IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION verwendet die REQUEST_REMOTE_WAKE_NOTIFICATION-Struktur , um die Anforderungsparameter anzugeben. Einer der Werte, die der zusammengesetzte Treiber angeben muss, ist der Funktionshandle für die Funktion, die für die Remoteaktivierung bewaffnet ist. Der zusammengesetzte Treiber, der dieses Handle in einer früheren Anforderung erhalten hat, das zusammengesetzte Gerät beim USB-Treiberstapel zu registrieren. Weitere Informationen zu Registrierungsanforderungen für zusammengesetzte Treiber finden Sie unter Registrieren eines zusammengesetzten Geräts.

Im IRP für die Anforderung stellt der zusammengesetzte Treiber einen Zeiger auf eine Vervollständigungsroutine (Remote wake-up) bereit, die vom zusammengesetzten Treiber implementiert wird.

Der folgende Beispielcode zeigt, wie eine Remotereaktivierungsanforderung gesendet wird.

/*++

Description:
    This routine sends a IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION request
    to the USB driver stack. The IOCTL is completed by the USB driver stack
    when the function wakes up from sleep.

    Parameters:
    parentFdoExt: The device context associated with the FDO for the
    composite driver.

    functionPdoExt: The device context associated with the PDO (created by
    the composite driver) for the client driver.
--*/

VOID
SendRequestForRemoteWakeNotification(
    __inout PPARENT_FDO_EXT parentFdoExt,
    __inout PFUNCTION_PDO_EXT functionPdoExt
)

{
    PIRP                                irp;
    REQUEST_REMOTE_WAKE_NOTIFICATION    remoteWake;
    PIO_STACK_LOCATION                  nextStack;
    NTSTATUS                            status;

    // Allocate an IRP
    irp =  IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (irp)
    {

        //Initialize the USBDEVICE_REMOTE_WAKE_NOTIFICATION structure
        remoteWake.Version = 0;
        remoteWake.Size = sizeof(REQUEST_REMOTE_WAKE_NOTIFICATION);
        remoteWake.UsbdFunctionHandle = functionPdoExt->functionHandle;
        remoteWake.Interface = functionPdoExt->baseInterfaceNumber;

        nextStack = IoGetNextIrpStackLocation(irp);

        nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
        nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION;

        nextStack->Parameters.Others.Argument1 = &remoteWake;

        // Caller's completion routine will free the IRP when it completes.

        SetCompletionRoutine(functionPdoExt->debugLog,
                             parentFdoExt->fdo,
                             irp,
                             CompletionRemoteWakeNotication,
                             (PVOID)functionPdoExt,
                             TRUE, TRUE, TRUE);

        // Pass the IRP
        IoCallDriver(parentFdoExt->topDevObj, irp);

    }

    return;
}

Die IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION Anforderung wird vom USB-Treiberstapel während des Aktivierungsprozesses abgeschlossen, wenn sie eine Benachrichtigung über das Fortsetzungssignal empfängt. Während dieser Zeit ruft der USB-Treiberstapel auch die Remotereaktivierungsvervollständigungsroutine auf.

Der zusammengesetzte Treiber muss die Warte-Reaktivierungs-IRP ausstehen und für die spätere Verarbeitung in die Warteschlange stellen. Der zusammengesetzte Treiber muss diese IRP abschließen, wenn die Remotereaktivierungs-Vervollständigungsroutine des Treibers vom USB-Treiberstapel aufgerufen wird.

Schritt 4: Senden einer Anforderung zum Ausrüsten der Funktion für die Remoteaktivierung

Um die Funktion in einen Energiesparzustand zu senden, sendet der Clienttreiber eine IRP_MN_SET_POWER IRP mit der Anforderung, den Gerätestromzustand des Windows-Treibermodells (WDM) in D2 oder D3 zu ändern. In der Regel sendet der Clienttreiber D2-IRP , wenn der Treiber zuvor eine Warte-Reaktivierungs-IRP gesendet hat, um die Remotereaktivierung anzufordern. Andernfalls sendet der Clienttreiber D3 IRP.

Beim Empfang des D2-IRP muss der zusammengesetzte Treiber zunächst ermitteln, ob eine Warte-Reaktivierungs-IRP aus einer vorherigen Anforderung aussteht, die vom Clienttreiber gesendet wurde. Wenn diese IRP aussteht, muss der zusammengesetzte Treiber die Funktion für die Remoteaktivierung ausrüsten. Dazu muss der zusammengesetzte Treiber eine SET_FEATURE-Steuerungsanforderung an die erste Schnittstelle der Funktion senden, damit das Gerät ein Fortsetzungssignal senden kann. Um die Steuerelementanforderung zu senden, ordnen Sie eine URB-Struktur zu, indem Sie die USBD_UrbAllocate Routine aufrufen, und rufen Sie das Makro UsbBuildFeatureRequest auf, um die URB für eine SET_FEATURE-Anforderung zu formatieren. Geben Sie im Aufruf URB_FUNCTION_SET_FEATURE_TO_INTERFACE als Vorgangscode und den USB_FEATURE_FUNCTION_SUSPEND als Featureauswahl an. Legen Sie im Index-ParameterBit 1 des bedeutendsten Byte fest. Dieser Wert wird in das Feld wIndex im Setuppaket der Übertragung kopiert.

Das folgende Beispiel zeigt, wie eine SET_FEATURE-Steuerelementanforderung gesendet wird.

/*++

Routine Description:

Sends a SET_FEATURE for REMOTE_WAKEUP to the device using a standard control request.

Parameters:
parentFdoExt: The device context associated with the FDO for the
composite driver.

functionPdoExt: The device context associated with the PDO (created by
the composite driver) for the client driver.

Returns:

NTSTATUS code.

--*/
VOID
    NTSTATUS SendSetFeatureControlRequestToSuspend(
    __inout PPARENT_FDO_EXT parentFdoExt,
    __inout PFUNCTION_PDO_EXT functionPdoExt,
    )

{
    PURB                            urb
    PIRP                            irp;
    PIO_STACK_LOCATION              nextStack;
    NTSTATUS                        status;

    status = USBD_UrbAllocate(parentFdoExt->usbdHandle, &urb);

    if (!NT_SUCCESS(status))
    {
        //USBD_UrbAllocate failed.
        goto Exit;
    }

    //Format the URB structure.
    UsbBuildFeatureRequest (
        urb,
        URB_FUNCTION_SET_FEATURE_TO_INTERFACE, // Operation code
        USB_FEATURE_FUNCTION_SUSPEND,          // feature selector
        functionPdoExt->firstInterface,           // first interface of the function
        NULL);

    irp =  IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (!irp)
    {
        // IoAllocateIrp failed.
        status = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;
    }

    nextStack = IoGetNextIrpStackLocation(irp);

    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;

    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;

    //  Attach the URB to the IRP.
    USBD_AssignUrbToIoStackLocation(nextStack, (PURB)urb);

    // Caller's completion routine will free the IRP when it completes.
    SetCompletionRoutine(functionPdoExt->debugLog,
        parentFdoExt->fdo,
        irp,
        CompletionForSuspendControlRequest,
        (PVOID)functionPdoExt,
        TRUE, TRUE, TRUE);


    // Pass the IRP
    IoCallDriver(parentFdoExt->topDevObj, irp);


Exit:
    if (urb)
    {
        USBD_UrbFree( parentFdoExt->usbdHandle, urb);
    }

    return status;

}

Der zusammengesetzte Treiber sendet dann den D2-IRP an den USB-Treiberstapel. Wenn sich alle anderen Funktionen in einem Angehalten-Zustand befinden, hält der USB-Treiberstapel den Port an, indem bestimmte Portregister auf dem Controller manipuliert werden.

Hinweise

Im Mausfunktionsbeispiel generiert die Mausfunktion, da die Remoteaktivierungsfunktion aktiviert ist (siehe Schritt 4), ein Fortsetzungssignal auf dem Draht Upstream an den Hostcontroller, wenn der Benutzer die Maus wischt. Der Controller benachrichtigt dann den USB-Treiberstapel, indem er ein Benachrichtigungspaket sendet, das Informationen über die Funktion enthält, die aufgewacht ist. Informationen zur Funktionsreaktivierungsbenachrichtigung finden Sie in Abbildung 8-17 in der USB 3.0-Spezifikation.

Beim Empfang des Benachrichtigungspakets schließt der USB-Treiberstapel die ausstehende IOCTL_INTERNAL_USB_REQUEST_REMOTE_WAKE_NOTIFICATION Anforderung ab (siehe Schritt 3) und ruft die Rückrufroutine (Remotereaktivierung) auf, die in der Anforderung angegeben und vom zusammengesetzten Treiber implementiert wurde. Wenn die Benachrichtigung den zusammengesetzten Treiber erreicht, benachrichtigt sie den entsprechenden Clienttreiber darüber, dass die Funktion in den Arbeitszustand versetzt wurde, indem sie die warte-Reaktivierungs-IRP abschließt, die der Clienttreiber zuvor gesendet hatte.

In der Vervollständigungsroutine (Remote wake-up) sollte der zusammengesetzte Treiber eine Arbeitsaufgabe in die Warteschlange stellen, um die ausstehende Warte-Reaktivierungs-IRP abzuschließen. Bei USB 3.0-Geräten aktiviert der zusammengesetzte Treiber nur die Funktion, die das Fortsetzungssignal sendet, und lässt andere Funktionen im Ruhezustand zurück. Das Anstehen des Arbeitselements stellt die Kompatibilität mit der vorhandenen Implementierung für Funktionstreiber von USB 2.0-Geräten sicher. Informationen zum Anstehen eines Arbeitselements finden Sie unter IoQueueWorkItem.

Der Workerthread schließt die Warte-Reaktivierungs-IRP ab und ruft die Vervollständigungsroutine des Clienttreibers auf. Die Vervollständigungsroutine sendet dann einen D0-IRP , um die Funktion in den Arbeitszustand zu versetzen. Vor Abschluss der Warte-Reaktivierungs-IRP sollte der zusammengesetzte Treiber PoSetSystemWake aufrufen, um die Warte-Reaktivierungs-IRP als diejenige zu markieren, die dazu beigetragen hat, dass das System aus dem Ruhezustand aktiviert wurde. Der Power Manager protokolliert ein ETW-Ereignis (Event Tracing for Windows) (sichtbar im globalen Systemkanal), das Informationen zu Geräten enthält, die das System aktiviert haben.