Verschieben der PnP-IRP-Verarbeitung, bis niedrigere Treiber fertig sind

Einige PnP- und Power-IRPs müssen zuerst vom übergeordneten Bustreiber für ein Gerät und dann von jedem nächsthöheren Treiber im Gerätestapel verarbeitet werden. Beispielsweise muss der übergeordnete Bustreiber der erste Treiber sein, der seine Startvorgänge für ein Gerät (IRP_MN_START_DEVICE) ausführt, gefolgt von jedem nächsthöheren Treiber. Für eine solche IRP müssen Funktions- und Filtertreiber eine E/A-Vervollständigungsroutine festlegen, den IRP an den nächstniedrigen Treiber übergeben und alle Aktivitäten verschieben, um die IRP zu verarbeiten, bis die niedrigeren Treiber mit dem IRP fertig sind.

Eine IoCompletion-Routine kann bei IRQL DISPATCH_LEVEL aufgerufen werden, aber möglicherweise muss ein Funktions- oder Filtertreiber den IRP unter IRQL = PASSIVE_LEVEL verarbeiten. Um zu PASSIVE_LEVEL aus einer IoCompletion-Routine zurückzukehren, kann ein Treiber ein Kernelereignis verwenden. Der Treiber registriert eine IoCompletion-Routine , die ein Kernelmodusereignis festlegt, und wartet dann auf das Ereignis in seiner DispatchPnP-Routine . Wenn das Ereignis festgelegt ist, haben niedrigere Treiber die IRP abgeschlossen, und der Treiber darf die IRP verarbeiten.

Beachten Sie, dass ein Treiber diese Technik nicht verwenden darf, um auf niedrigere Treiber zu warten, um eine Energie-IRP (IRP_MJ_POWER) zu beenden. Das Warten auf ein Ereignis in der DispatchPower-Routine , das in der IoCompletion-Routine festgelegt ist, kann zu einem Deadlock führen. Weitere Informationen finden Sie unter Übergeben von Power IRPs .

Die folgenden beiden Abbildungen zeigen ein Beispiel dafür, wie ein Treiber auf niedrigere Treiber wartet, um einen PnP-IRP abzuschließen. Das Beispiel zeigt, was die Funktions- und Bustreiber tun müssen, und wie sie mit dem PnP-Manager und dem E/A-Manager interagieren.

Diagramm, das die Verschiebung der Plug-and-Play-Irp-Handhabung veranschaulicht, Teil 1.

Die folgenden Hinweise entsprechen den eingekreisten Zahlen in der vorherigen Abbildung:

  1. Der PnP-Manager ruft den E/A-Manager auf, um einen IRP an den obersten Treiber im Gerätestapel zu senden.

  2. Der E/A-Manager ruft die DispatchPnP-Routine des obersten Treibers auf. In diesem Beispiel gibt es nur zwei Treiber im Gerätestapel (der Funktionstreiber und der übergeordnete Bustreiber), und der Funktionstreiber ist der oberste Treiber.

  3. Der Funktionstreiber deklariert und initialisiert ein Kernelmodusereignis, richtet den Stapelspeicherort für den nächstniedrigen Treiber ein und legt eine IoCompletion-Routine für dieses IRP fest.

    Der Funktionstreiber kann IoCopyCurrentIrpStackLocationToNext verwenden, um den Stapelspeicherort einzurichten.

    Beim Aufruf von IoSetCompletionRoutine legt der Funktionstreiber InvokeOnSuccess, InvokeOnError und InvokeOnCancel auf TRUE fest und übergibt das Kernelmodusereignis als Teil des Kontextparameters.

  4. Der Funktionstreiber übergibt den IRP im Gerätestapel mit IoCallDriver , bevor er Vorgänge zum Behandeln des IRP ausführt.

  5. Der E/A-Manager sendet den IRP an den nächstniedrigen Treiber im Gerätestapel, indem er die DispatchPnP-Routine dieses Treibers aufruft.

  6. Der nächstniedrige Treiber in diesem Beispiel ist der niedrigste Treiber im Gerätestapel, der übergeordnete Bustreiber. Der Bustreiber führt seine Vorgänge aus, um das Gerät zu starten. Der Bustreiber legt Irp-IoStatus.Status> fest, legt Irp-IoStatus.Information> fest, falls für diese IRP relevant, und schließt die IRP durch Aufrufen von IoCompleteRequest ab.

    Wenn der Bustreiber andere Treiberroutinen aufruft oder E/A an das Gerät sendet, um es zu starten, schließt der Bustreiber die PnP-IRP in seiner DispatchPnP-Routine nicht ab. Stattdessen muss die ausstehende IRP mit IoMarkIrpPending gekennzeichnet werden und STATUS_PENDING aus der DispatchPnP-Routine zurückgeben. Der Treiber ruft später IoCompleteRequest aus einer anderen Treiberroutine auf, möglicherweise eine DPC-Routine.

Die folgende Abbildung zeigt den zweiten Teil des Beispiels, in dem die höheren Treiber im Gerätestapel ihre verzögerte IRP-Verarbeitung fortsetzen.

Diagramm, das das Verschieben der Plug-and-Play-Irp-Behandlung veranschaulicht, Teil 2.

Die folgenden Hinweise entsprechen den eingekreisten Zahlen in der vorherigen Abbildung:

  1. Wenn der Bustreiber IoCompleteRequest aufruft, untersucht der E/A-Manager die Stapelspeicherorte der höheren Treiber und ruft alle gefundenen IoCompletion-Routinen auf. In diesem Beispiel sucht der E/A-Manager die IoCompletion-Routine für den nächsthöheren Treiber, den Funktionstreiber, und ruft sie auf.

  2. Die IoCompletion-Routine des Funktionstreibers legt das im Kontextparameter angegebene Kernelmodusereignis fest und gibt STATUS_MORE_PROCESSING_REQUIRED zurück.

    Die IoCompletion-Routine muss STATUS_MORE_PROCESSING_REQUIRED zurückgeben, um zu verhindern, dass der E/A-Manager IoCompletion-Routinen aufruft, die zu diesem Zeitpunkt von höheren Treibern festgelegt wurden. Die IoCompletion-Routine verwendet diese status, um die Vervollständigung zu verschließen, damit die DispatchPnP-Routine des Treibers die Kontrolle wieder erlangen kann. Der E/A-Manager setzt den Aufruf der IoCompletion-Routinen höherer Treiber für dieses IRP fort, wenn die DispatchPnP-Routine dieses Treibers die IRP abgeschlossen hat.

  3. Der E/A-Manager beendet den Abschluss des IRP und gibt die Steuerung an die Routine zurück, die IoCompleteRequest genannt hat, die in diesem Beispiel die DispatchPnP-Routine des Busfahrers ist.

  4. Der Bustreiber kehrt von seiner DispatchPnP-Routine zurück, wobei status das Ergebnis seiner IRP-Verarbeitung angibt: entweder STATUS_SUCCESS oder ein Fehler status.

  5. IoCallDriver gibt die Steuerung an seinen Aufrufer zurück, was in diesem Beispiel die DispatchPnP-Routine des Funktionstreibers ist.

  6. Die DispatchPnP-Routine des Funktionstreibers setzt die Verarbeitung des IRP fort.

    Wenn IoCallDriver STATUS_PENDING zurückgibt, hat die DispatchPnP-Routine die Ausführung fortgesetzt, bevor die IoCompletion-Routine aufgerufen wurde. Die DispatchPnP-Routine muss daher warten, bis das Kernelereignis von seiner IoCompletion-Routine signalisiert wird. Dadurch wird sichergestellt, dass die DispatchPnP-Routine die IRP erst weiter verarbeitet, wenn sie von allen niedrigeren Treibern abgeschlossen wurde.

    Wenn Irp-IoStatus.Status> auf einen Fehler festgelegt ist, ist der IRP bei einem niedrigeren Treiber fehlgeschlagen, und der Funktionstreiber darf die IRP nicht weiter verarbeiten (mit Ausnahme der erforderlichen Bereinigung).

  7. Nachdem niedrigere Treiber die IRP erfolgreich abgeschlossen haben, verarbeitet der Funktionstreiber die IRP.

    Für IRPs, die zuerst vom übergeordneten Bustreiber verarbeitet werden, legt der Bustreiber in der Regel eine erfolgreiche status in Irp-IoStatus.Status> fest und legt optional einen Wert in Irp-IoStatus.Information> fest. Funktions- und Filtertreiber lassen die Werte in IoStatus unverändert, es sei denn, sie schlagen beim IRP fehl.

    Die DispatchPnP-Routine des Funktionstreibers ruft IoCompleteRequest auf, um die IRP abzuschließen. Der E/A-Manager setzt die E/A-Vervollständigungsverarbeitung fort. In diesem Beispiel gibt es keine Filtertreiber über dem Funktionstreiber und somit keine weiteren IoCompletion-Routinen , die aufgerufen werden können. Wenn IoCompleteRequest die Steuerung an die DispatchPnP-Routine des Funktionstreibers zurückgibt, gibt die DispatchPnP-Routine status zurück.

Wenn bei einigen IRPs ein Funktions- oder Filtertreiber die IRP auf dem Weg nach oben im Gerätestapel fehlschlägt, informiert der PnP-Manager die niedrigeren Treiber. Wenn beispielsweise ein Funktions- oder Filtertreiber einen IRP_MN_START_DEVICE ausfällt, sendet der PnP-Manager eine IRP_MN_REMOVE_DEVICE an den Gerätestapel.