123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914 |
- /*++
- Copyright (c) 2013 Minoca Corp. All Rights Reserved
- Module Name:
- devrem.c
- Abstract:
- This module implements device removal functionality.
- Author:
- Chris Stevens 17-Jun-2013
- Environment:
- Kernel
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/kernel/kernel.h>
- #include "iop.h"
- #include "pmp.h"
- #include "pagecach.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- KSTATUS
- IopSendRemovalIrp (
- PDEVICE Device
- );
- VOID
- IopDeviceDestroyDriverStack (
- PDEVICE Device
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- //
- // ------------------------------------------------------------------ Functions
- //
- VOID
- IopPrepareRemoveDevice (
- PDEVICE Device,
- PDEVICE_WORK_ENTRY Work
- )
- /*++
- Routine Description:
- This routine prepares a device for removal. It puts the device in the
- awaiting removal state. If it has no children, it queues the removal work
- item on itself. If this routine discovers that the device is already in the
- awaiting removal state, it exits.
- Arguments:
- Device - Supplies a pointer to the device that is preparing to be removed.
- Work - Supplies a pointer to the work request.
- Return Value:
- None.
- --*/
- {
- BOOL QueueRemoval;
- KSTATUS Status;
- PVOLUME Volume;
- ASSERT(Work->Action == DeviceActionPrepareRemove);
- ASSERT((Work->Flags & DEVICE_ACTION_SEND_TO_SUBTREE) != 0);
- //
- // This device should not already be marked removed. If it was removed,
- // then no additional work items should have been scheduled on its queue.
- //
- ASSERT(Device->State != DeviceRemoved);
- //
- // If the device is already awaiting removal, that means that some process
- // already scheduled this prepare removal work item on this device. It
- // should get signaled for removal by its children. In this case, this
- // work entry no longer needs to traverse this device's subtree.
- //
- if (Device->State == DeviceAwaitingRemoval) {
- Work->Flags &= ~DEVICE_ACTION_SEND_TO_SUBTREE;
- goto PrepareRemoveDeviceEnd;
- }
- //
- // Acquire the lock here to synchronize with child device creation. By the
- // time this lock is acquired, any concurrently active device creations
- // are finished and any future attempts at creation should fail because the
- // device is now awaiting removal. This also synchronizes with removal so
- // a device does not get two removal work items queued - one by this
- // routine and another when the last child gets removed.
- //
- KeAcquireSharedExclusiveLockExclusive(Device->Lock);
- //
- // The state check above is safe because a device's queue items are
- // processed sequentially and only this work item moves the state to
- // awaiting removal. Assert this though for good measure.
- //
- ASSERT(Device->State != DeviceAwaitingRemoval);
- //
- // Mark the device as awaiting removal.
- //
- IopSetDeviceState(Device, DeviceAwaitingRemoval);
- //
- // Unsignal the device so that anyone waiting on it will have to let the
- // removal finish.
- //
- ObSignalObject(Device, SignalOptionUnsignal);
- //
- // If this is a volume, make sure that it is marked that it is in the
- // process of being unmounted.
- //
- if (Device->Header.Type == ObjectVolume) {
- Volume = (PVOLUME)Device;
- Volume->Flags |= VOLUME_FLAG_UNMOUNTING;
- }
- //
- // Queue removal on the device if it has no active children.
- //
- QueueRemoval = FALSE;
- if (LIST_EMPTY(&(Device->ActiveChildListHead)) != FALSE) {
- QueueRemoval = TRUE;
- }
- KeReleaseSharedExclusiveLockExclusive(Device->Lock);
- //
- // Queue the removal work item on this device if necessary. If this fails,
- // handle the queue failure, which will roll back the state of any device
- // waiting on this device's removal process.
- //
- // N.B. There could be another prepare to remove work item in this device's
- // queue that could end up succeeding removal, making the rollback
- // unnecessary. Don't bother to check, however. A parent that gets
- // rolled back can attempt removal again.
- //
- if (QueueRemoval != FALSE) {
- Status = IopQueueDeviceWork(Device,
- DeviceActionRemove,
- NULL,
- DEVICE_ACTION_CLOSE_QUEUE);
- if (!KSUCCESS(Status)) {
- IopHandleDeviceQueueFailure(Device, DeviceActionRemove);
- }
- }
- PrepareRemoveDeviceEnd:
- return;
- }
- VOID
- IopRemoveDevice (
- PDEVICE Device,
- PDEVICE_WORK_ENTRY Work
- )
- /*++
- Routine Description:
- This routine removes a device by sending a removal IRP and then removing
- the device reference added during device creation. The removal IRP allows
- the driver to clean up any necessary state that cannot be cleaned up by the
- object manager's destruction call-back.
- Arguments:
- Device - Supplies a pointer to the device that is to be removed.
- Work - Supplies a pointer to the work request.
- Return Value:
- None.
- --*/
- {
- PDEVICE ParentDevice;
- BOOL RemoveParent;
- KSTATUS Status;
- ASSERT(Device->State == DeviceAwaitingRemoval);
- ASSERT((Work->Flags & DEVICE_ACTION_CLOSE_QUEUE) != 0);
- ASSERT(Device->QueueState == DeviceQueueActiveClosing);
- ASSERT(LIST_EMPTY(&(Device->ActiveChildListHead)) != FALSE);
- ASSERT(KeGetRunLevel() == RunLevelLow);
- //
- // Attempt to remove any device paths that belong to the device. Do this
- // before the remove IRP in case something fails. It will be done again
- // after the remove IRP, ingorning failures.
- //
- Status = IopRemoveDevicePaths(Device);
- if (!KSUCCESS(Status)) {
- IopAbortDeviceRemoval(Device, DeviceProblemFailedPathRemoval, TRUE);
- goto RemoveDeviceEnd;
- }
- //
- // Acquire the device lock exclusive for the remove IRP to synchronize with
- // I/O opens, system control IRPs, and user control IRPs.
- //
- KeAcquireSharedExclusiveLockExclusive(Device->Lock);
- //
- // Clean up the power management state.
- //
- PmpRemoveDevice(Device);
- //
- // Send the removal IRP to the device. If this fails, the removal process
- // must be rolled back for this branch of the device tree.
- //
- Status = IopSendRemovalIrp(Device);
- if (!KSUCCESS(Status)) {
- KeReleaseSharedExclusiveLockExclusive(Device->Lock);
- IopAbortDeviceRemoval(Device, DeviceProblemFailedToSendRemoveIrp, TRUE);
- goto RemoveDeviceEnd;
- }
- //
- // With the removal IRP sent, rolling back should not happen anymore. Thus,
- // without further ado, declare this device removed.
- //
- IopSetDeviceState(Device, DeviceRemoved);
- ObSignalObject(Device, SignalOptionSignalAll);
- //
- // Remove the device from the global list so that racy folks trying to
- // look up the device by device ID must finish now. It would be bad to get
- // all the way down to releasing the last reference only to have that
- // lookup function try to re-add one.
- //
- KeAcquireQueuedLock(IoDeviceListLock);
- LIST_REMOVE(&(Device->ListEntry));
- Device->ListEntry.Next = NULL;
- KeReleaseQueuedLock(IoDeviceListLock);
- //
- // Officially close the work queue.
- //
- KeAcquireQueuedLock(Device->QueueLock);
- Device->QueueState = DeviceQueueClosed;
- KeReleaseQueuedLock(Device->QueueLock);
- //
- // Release the device lock to let everyone else waiting on the state see
- // that it has now switched to removed.
- //
- KeReleaseSharedExclusiveLockExclusive(Device->Lock);
- //
- // Clean up the device paths again, ignoring failures this time.
- //
- IopRemoveDevicePaths(Device);
- //
- // Evict any lingering file object entries in the page cache. Clean removal
- // should have flushed block devices by this point.
- //
- IopEvictFileObjects(Device->DeviceId, PAGE_CACHE_EVICTION_FLAG_REMOVE);
- //
- // Flush the file objects for the device. Eviction should have removed all
- // the page cache entries, but the file object properties may be dirty and
- // keeping the file objects for this device in the dirty file objects list.
- // The writes will fail, but the flush should do the job to moving them all
- // out of the list.
- //
- IopFlushFileObjects(
- Device->DeviceId,
- IO_FLAG_DATA_SYNCHRONIZED | IO_FLAG_METADATA_SYNCHRONIZED,
- NULL);
- //
- // Release any lingering file objects that may be stuck open for this
- // device. Be nice, and do it for other devices as well.
- //
- IopCleanupFileObjects();
- //
- // Determine which device to consider the parent. A volume's effective
- // "parent" is the target device.
- //
- if (Device->Header.Type == ObjectVolume) {
- ParentDevice = Device->TargetDevice;
- } else {
- ParentDevice = Device->ParentDevice;
- }
- ASSERT(ParentDevice->Header.ReferenceCount >= 2);
- //
- // Acquire the parent's device lock exclusively to free its active child
- // list and its state. This needs to happen under the lock to synchronize
- // with the parent's own prepare remove work item which can schedule the
- // remove work item on a device with no children.
- //
- KeAcquireSharedExclusiveLockExclusive(ParentDevice->Lock);
- //
- // With the device officially removed, remove it from its parent's list of
- // active children.
- //
- LIST_REMOVE(&(Device->ActiveListEntry));
- Device->ActiveListEntry.Next = NULL;
- //
- // Handle the special case where the device is a volume and it's "parent"
- // is its target device.
- //
- if (Device->Header.Type == ObjectVolume) {
- //
- // If the parent has no more children, then nothing is mounted.
- //
- if (LIST_EMPTY(&(ParentDevice->ActiveChildListHead)) != FALSE) {
- ParentDevice->Flags &= ~DEVICE_FLAG_MOUNTED;
- }
- }
- //
- // If the parent device is awaiting removal, determine if the given device
- // is its last active child.
- //
- RemoveParent = FALSE;
- if ((ParentDevice->State == DeviceAwaitingRemoval) &&
- (LIST_EMPTY(&(ParentDevice->ActiveChildListHead)) != FALSE)) {
- ObAddReference(ParentDevice);
- RemoveParent = TRUE;
- }
- KeReleaseSharedExclusiveLockExclusive(ParentDevice->Lock);
- //
- // Release the initial volume reference.
- //
- if (Device->Header.Type == ObjectVolume) {
- IoVolumeReleaseReference((PVOLUME)Device);
- }
- //
- // Release the reference taken by the object manager. This is not
- // necessarily the device's last reference.
- //
- ObReleaseReference(Device);
- //
- // Queue the removal of the parent if it has no more children.
- //
- if (RemoveParent != FALSE) {
- Status = IopQueueDeviceWork(ParentDevice,
- DeviceActionRemove,
- NULL,
- DEVICE_ACTION_CLOSE_QUEUE);
- if (!KSUCCESS(Status)) {
- IopHandleDeviceQueueFailure(ParentDevice, DeviceActionRemove);
- }
- ObReleaseReference(ParentDevice);
- }
- RemoveDeviceEnd:
- return;
- }
- VOID
- IopAbortDeviceRemoval (
- PDEVICE Device,
- DEVICE_PROBLEM DeviceProblem,
- BOOL RollbackDevice
- )
- /*++
- Routine Description:
- This routine aborts the device removal process for the given device. It
- also walks back up the device tree reverting the removal process for any
- ancestor devices that were awaiting the given device's removal.
- Arguments:
- Device - Supplies a pointer to the device that failed the removal process
- and requires rollback.
- DeviceProblem - Supplies the devices problem (i.e. the reason for the
- abort).
- RollbackDevice - Supplies a boolean indicating whether or not the supplied
- device should be included in the rollback.
- Return Value:
- None.
- --*/
- {
- PDEVICE CurrentDevice;
- PDEVICE ParentDevice;
- DEVICE_STATE PreviousState;
- ULONG PreviousStateIndex;
- PVOLUME Volume;
- ASSERT(Device->Header.ReferenceCount >= 1);
- //
- // This routine could be called when the given device is not marked for
- // removal. In this case, just start with the parent device. In the case of
- // volumes, the "parent" device is the target device.
- //
- if (RollbackDevice == FALSE) {
- ASSERT(Device->State != DeviceAwaitingRemoval);
- if (Device->Header.Type == ObjectVolume) {
- ASSERT(Device->TargetDevice != NULL);
- CurrentDevice = Device->TargetDevice;
- } else {
- CurrentDevice = Device->ParentDevice;
- }
- } else {
- ASSERT(Device->State == DeviceAwaitingRemoval);
- CurrentDevice = Device;
- }
- //
- // Look back up the device tree reverting all the device's ancestors out of
- // the awaiting removal state. Since the caller must have a reference on
- // the supplied device, this routine does not need to worry about devices
- // disappearing; every device holds a reference to its parent (including
- // volumes and their target device).
- //
- KeAcquireSharedExclusiveLockExclusive(CurrentDevice->Lock);
- while (CurrentDevice->State == DeviceAwaitingRemoval) {
- ASSERT(CurrentDevice->Header.ReferenceCount >= 1);
- PreviousStateIndex = CurrentDevice->StateHistoryNextIndex - 1;
- if (PreviousStateIndex == MAX_ULONG) {
- PreviousStateIndex = DEVICE_STATE_HISTORY - 1;
- }
- PreviousState = CurrentDevice->StateHistory[PreviousStateIndex];
- IopSetDeviceState(CurrentDevice, PreviousState);
- //
- // Modify the device's queue back to the correct state. This depends on
- // the current queue state and the previous device state.
- //
- KeAcquireQueuedLock(Device->QueueLock);
- //
- // Devices with closed queues should never need to be rolled back.
- //
- ASSERT(CurrentDevice->QueueState != DeviceQueueClosed);
- //
- // The only queue state that needs rolling back is the active closing
- // state. All other device removal aborts come from failing to queue an
- // action, which already rolls back the queue state correctly.
- //
- if (CurrentDevice->QueueState == DeviceQueueActiveClosing) {
- ASSERT(LIST_EMPTY(&(CurrentDevice->WorkQueue)) != FALSE);
- //
- // If the previous state was unreported, then the queue should be
- // marked closed. Otherwise, it is open.
- //
- if (PreviousState == DeviceUnreported) {
- CurrentDevice->QueueState = DeviceQueueClosed;
- } else {
- CurrentDevice->QueueState = DeviceQueueOpen;
- }
- }
- KeReleaseQueuedLock(Device->QueueLock);
- //
- // Signal anyone waiting on this device's removal state. It will no
- // longer reach that signal.
- //
- ObSignalObject(CurrentDevice, SignalOptionSignalAll);
- //
- // Move backwards up the tree. For a volume, the effective parent is
- // the target device.
- //
- if (CurrentDevice->Header.Type == ObjectVolume) {
- Volume = (PVOLUME)CurrentDevice;
- //
- // Also make sure that the volume is no longer marked as
- // "unmounting".
- //
- Volume->Flags &= ~VOLUME_FLAG_UNMOUNTING;
- ASSERT(Device->TargetDevice != NULL);
- ParentDevice = Device->TargetDevice;
- } else {
- ParentDevice = CurrentDevice->ParentDevice;
- }
- //
- // Release the current device's lock before gettings the parent's lock.
- //
- KeReleaseSharedExclusiveLockExclusive(CurrentDevice->Lock);
- //
- // Move up to the parent device and acquire its lock.
- //
- CurrentDevice = ParentDevice;
- KeAcquireSharedExclusiveLockExclusive(CurrentDevice->Lock);
- }
- KeReleaseSharedExclusiveLockExclusive(CurrentDevice->Lock);
- //
- // Set the device problem state on the orignal device to record that this
- // device is the origin of the removal failure.
- //
- IopSetDeviceProblem(Device, DeviceProblem, STATUS_UNSUCCESSFUL);
- return;
- }
- VOID
- IopDestroyDevice (
- PVOID Object
- )
- /*++
- Routine Description:
- This routine destroys a device and its resources. The object manager will
- clean up the object header, leaving this routine to clean up the device
- specific elements of the object. This routine is meant only as a callback
- for the object manager.
- Arguments:
- Object - Supplies a pointer to the object to be destroyed.
- Return Value:
- None.
- --*/
- {
- PDEVICE Device;
- Device = (PDEVICE)Object;
- ASSERT(KeGetRunLevel() == RunLevelLow);
- ASSERT(Device != NULL);
- ASSERT((Device->State == DeviceRemoved) ||
- (Device->State == DeviceUnreported));
- //
- // Remove the device from the global list if not already done.
- //
- if (Device->ListEntry.Next != NULL) {
- ASSERT(Device->State == DeviceUnreported);
- KeAcquireQueuedLock(IoDeviceListLock);
- LIST_REMOVE(&(Device->ListEntry));
- Device->ListEntry.Next = NULL;
- KeReleaseQueuedLock(IoDeviceListLock);
- }
- //
- // If there's a target device, release the reference on it.
- //
- if (Device->TargetDevice != NULL) {
- ObReleaseReference(Device->TargetDevice);
- }
- //
- // The device's work queue should be empty.
- //
- ASSERT(LIST_EMPTY(&(Device->WorkQueue)) != FALSE);
- //
- // Detached the drivers from the device.
- //
- IopDeviceDestroyDriverStack(Device);
- //
- // Assert all the children are gone, there are no active children, and this
- // device is not an active child to anyone.
- //
- ASSERT(LIST_EMPTY(&(Device->Header.ChildListHead)) != FALSE);
- ASSERT(LIST_EMPTY(&(Device->ActiveChildListHead)) != FALSE);
- ASSERT(Device->ActiveListEntry.Next == NULL);
- //
- // Clean up the power management state.
- //
- PmpDestroyDevice(Device);
- //
- // Delete the arbiter list and the various resource lists.
- //
- IopDestroyArbiterList(Device);
- if (Device->ResourceRequirements != NULL) {
- IoDestroyResourceConfigurationList(Device->ResourceRequirements);
- }
- if (Device->SelectedConfiguration != NULL) {
- IoDestroyResourceRequirementList(Device->SelectedConfiguration);
- }
- if (Device->BusLocalResources != NULL) {
- IoDestroyResourceAllocationList(Device->BusLocalResources);
- }
- if (Device->ProcessorLocalResources != NULL) {
- IoDestroyResourceAllocationList(Device->ProcessorLocalResources);
- }
- if (Device->BootResources != NULL) {
- IoDestroyResourceAllocationList(Device->BootResources);
- }
- if (Device->Lock != NULL) {
- KeDestroySharedExclusiveLock(Device->Lock);
- }
- //
- // Deallocate the class ID, and compatible IDs. The object manager will
- // free the device ID (i.e. the name).
- //
- ASSERT((Device->Header.Flags & OBJECT_FLAG_USE_NAME_DIRECTLY) == 0);
- if (Device->ClassId != NULL) {
- MmFreePagedPool(Device->ClassId);
- } else if (Device->CompatibleIds != NULL) {
- MmFreePagedPool(Device->CompatibleIds);
- }
- RtlDebugPrint("Destroyed Device: %s, 0x%x\n", Device->Header.Name, Device);
- return;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- KSTATUS
- IopSendRemovalIrp (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine sends a removal IRP to a device, allowing device drivers to
- clean up any resources for the given device.
- Arguments:
- Device - Supplies a pointer to the device that is to receive a removal IRP.
- Return Value:
- Status code.
- --*/
- {
- PIRP RemovalIrp;
- KSTATUS Status;
- RemovalIrp = NULL;
- Status = STATUS_SUCCESS;
- //
- // The system should only send removal IRPs to devices awaiting removal.
- //
- ASSERT(Device->State == DeviceAwaitingRemoval);
- //
- // The device should have no active children.
- //
- ASSERT(LIST_EMPTY(&(Device->ActiveChildListHead)) != FALSE);
- //
- // The device's work queue should be closing and the work queue should be
- // empty.
- //
- ASSERT(Device->QueueState == DeviceQueueActiveClosing);
- ASSERT(LIST_EMPTY(&(Device->WorkQueue)) != FALSE);
- //
- // Skip to the end if there are no drivers.
- //
- if (Device->DriverStackSize == 0) {
- ASSERT(LIST_EMPTY(&(Device->DriverStackHead)) != FALSE);
- goto SendRemovalIrpEnd;
- }
- //
- // Allocate a removal IRP.
- //
- RemovalIrp = IoCreateIrp(Device, IrpMajorStateChange, 0);
- if (RemovalIrp == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto SendRemovalIrpEnd;
- }
- RemovalIrp->MinorCode = IrpMinorRemoveDevice;
- //
- // Send the removal IRP to the device. Release the topology lock while
- // calling the driver.
- //
- Status = IoSendSynchronousIrp(RemovalIrp);
- if (!KSUCCESS(Status)) {
- ASSERT(KSUCCESS(Status));
- goto SendRemovalIrpEnd;
- }
- Status = IoGetIrpStatus(RemovalIrp);
- if (!KSUCCESS(Status)) {
- RtlDebugPrint("IO: Remove IRP failed for device 0x%08x with status "
- "0x%08x\n",
- Device,
- Status);
- goto SendRemovalIrpEnd;
- }
- SendRemovalIrpEnd:
- if (RemovalIrp != NULL) {
- IoDestroyIrp(RemovalIrp);
- }
- return Status;
- }
- VOID
- IopDeviceDestroyDriverStack (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine destroys the driver stack for the given device.
- Arguments:
- Device - Supplies a pointer to the device whose driver stack is to be
- destroyed.
- Return Value:
- None.
- --*/
- {
- PLIST_ENTRY DriverListEntry;
- PDRIVER_STACK_ENTRY DriverStackEntry;
- PLIST_ENTRY RemovalEntry;
- ASSERT(Device != NULL);
- ASSERT((Device->State == DeviceRemoved) ||
- (Device->State == DeviceUnreported));
- //
- // Detached the drivers from the device.
- //
- DriverListEntry = Device->DriverStackHead.Next;
- while (DriverListEntry != &(Device->DriverStackHead)) {
- DriverStackEntry = (PDRIVER_STACK_ENTRY)LIST_VALUE(DriverListEntry,
- DRIVER_STACK_ENTRY,
- ListEntry);
- RemovalEntry = DriverListEntry;
- DriverListEntry = DriverListEntry->Next;
- LIST_REMOVE(RemovalEntry);
- IoDriverReleaseReference(DriverStackEntry->Driver);
- MmFreeNonPagedPool(DriverStackEntry);
- Device->DriverStackSize -= 1;
- }
- ASSERT(Device->DriverStackSize == 0);
- return;
- }
|