You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2577 lines
80 KiB
2577 lines
80 KiB
/*++
|
|
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
pnpdel.c
|
|
|
|
Abstract:
|
|
|
|
This module contains routines to perform device removal
|
|
|
|
Author:
|
|
|
|
Robert B. Nelson (RobertN) Jun 1, 1998.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "pnpmgrp.h"
|
|
#include "wdmguid.h"
|
|
|
|
#ifdef POOL_TAGGING
|
|
#undef ExAllocatePool
|
|
#define ExAllocatePool(a,b) ExAllocatePoolWithTag(a,b,'edpP')
|
|
#endif
|
|
|
|
//
|
|
// Kernel mode PNP specific routines.
|
|
//
|
|
|
|
VOID
|
|
IopDelayedRemoveWorker(
|
|
IN PVOID Context
|
|
);
|
|
|
|
BOOLEAN
|
|
IopDeleteLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
IN PRELATION_LIST RelationsList,
|
|
IN ULONG Problem,
|
|
OUT PNP_VETO_TYPE *VetoType OPTIONAL,
|
|
OUT PUNICODE_STRING VetoName OPTIONAL
|
|
);
|
|
|
|
NTSTATUS
|
|
IopProcessRelation(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
IN BOOLEAN IsDirectDescendant,
|
|
OUT PNP_VETO_TYPE *VetoType,
|
|
OUT PUNICODE_STRING VetoName,
|
|
IN OUT PRELATION_LIST RelationsList
|
|
);
|
|
|
|
VOID
|
|
IopSurpriseRemoveLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN OUT PRELATION_LIST RelationsList
|
|
);
|
|
|
|
BOOLEAN
|
|
IopQueryRemoveLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
OUT PNP_VETO_TYPE *VetoType,
|
|
OUT PUNICODE_STRING VetoName
|
|
);
|
|
|
|
VOID
|
|
IopCancelRemoveLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode
|
|
);
|
|
|
|
VOID
|
|
IopRemoveLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN ULONG Problem,
|
|
IN OUT PRELATION_LIST RelationsList
|
|
);
|
|
|
|
typedef struct {
|
|
|
|
BOOLEAN TreeDeletion;
|
|
BOOLEAN DescendantNode;
|
|
|
|
} REMOVAL_WALK_CONTEXT, *PREMOVAL_WALK_CONTEXT;
|
|
|
|
NTSTATUS
|
|
PipRequestDeviceRemovalWorker(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PVOID Context
|
|
);
|
|
|
|
NTSTATUS
|
|
PiProcessBusRelations(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
IN BOOLEAN IsDirectDescendant,
|
|
OUT PNP_VETO_TYPE *VetoType,
|
|
OUT PUNICODE_STRING VetoName,
|
|
IN OUT PRELATION_LIST RelationsList
|
|
);
|
|
|
|
WORK_QUEUE_ITEM IopDeviceRemovalWorkItem;
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, IopChainDereferenceComplete)
|
|
#pragma alloc_text(PAGE, IopDelayedRemoveWorker)
|
|
#pragma alloc_text(PAGE, IopDeleteLockedDeviceNode)
|
|
#pragma alloc_text(PAGE, IopSurpriseRemoveLockedDeviceNode)
|
|
#pragma alloc_text(PAGE, IopQueryRemoveLockedDeviceNode)
|
|
#pragma alloc_text(PAGE, IopCancelRemoveLockedDeviceNode)
|
|
#pragma alloc_text(PAGE, IopDeleteLockedDeviceNodes)
|
|
#pragma alloc_text(PAGE, IopInvalidateRelationsInList)
|
|
#pragma alloc_text(PAGE, IopBuildRemovalRelationList)
|
|
#pragma alloc_text(PAGE, IopProcessCompletedEject)
|
|
#pragma alloc_text(PAGE, IopProcessRelation)
|
|
#pragma alloc_text(PAGE, IopQueuePendingEject)
|
|
#pragma alloc_text(PAGE, IopQueuePendingSurpriseRemoval)
|
|
#pragma alloc_text(PAGE, IopUnloadAttachedDriver)
|
|
#pragma alloc_text(PAGE, IopUnlinkDeviceRemovalRelations)
|
|
#pragma alloc_text(PAGE, PipRequestDeviceRemoval)
|
|
#pragma alloc_text(PAGE, PipRequestDeviceRemovalWorker)
|
|
#pragma alloc_text(PAGE, PipIsBeingRemovedSafely)
|
|
#pragma alloc_text(PAGE, PiProcessBusRelations)
|
|
#endif
|
|
|
|
VOID
|
|
IopChainDereferenceComplete(
|
|
IN PDEVICE_OBJECT PhysicalDeviceObject,
|
|
IN BOOLEAN OnCleanStack
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is invoked when the reference count on a PDO and all its
|
|
attached devices transitions to a zero. It tags the devnode as ready for
|
|
removal. If all the devnodes are tagged then IopDelayedRemoveWorker is
|
|
called to actually send the remove IRPs.
|
|
|
|
Arguments:
|
|
|
|
PhysicalDeviceObject - Supplies a pointer to the PDO whose references just
|
|
went to zero.
|
|
|
|
OnCleanStack - Indicates whether the current thread is in the middle a
|
|
driver operation.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPENDING_RELATIONS_LIST_ENTRY entry;
|
|
PLIST_ENTRY link;
|
|
ULONG count;
|
|
ULONG taggedCount;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
KeEnterCriticalRegion();
|
|
ExAcquireResourceExclusiveLite(&IopSurpriseRemoveListLock, TRUE);
|
|
|
|
//
|
|
// Find the relation list this devnode is a member of.
|
|
//
|
|
for (link = IopPendingSurpriseRemovals.Flink;
|
|
link != &IopPendingSurpriseRemovals;
|
|
link = link->Flink) {
|
|
|
|
entry = CONTAINING_RECORD(link, PENDING_RELATIONS_LIST_ENTRY, Link);
|
|
|
|
//
|
|
// Tag the devnode as ready for remove. If it isn't in this list
|
|
//
|
|
status = IopSetRelationsTag( entry->RelationsList, PhysicalDeviceObject, TRUE );
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
taggedCount = IopGetRelationsTaggedCount( entry->RelationsList );
|
|
count = IopGetRelationsCount( entry->RelationsList );
|
|
|
|
if (taggedCount == count) {
|
|
//
|
|
// Remove relations list from list of pending surprise removals.
|
|
//
|
|
RemoveEntryList( link );
|
|
|
|
ExReleaseResourceLite(&IopSurpriseRemoveListLock);
|
|
KeLeaveCriticalRegion();
|
|
|
|
if ((!OnCleanStack) ||
|
|
(PsGetCurrentProcess() != PsInitialSystemProcess)) {
|
|
|
|
//
|
|
// Queue a work item to do the removal so we call the driver
|
|
// in the system process context rather than the random one
|
|
// we're in now.
|
|
//
|
|
ExInitializeWorkItem( &entry->WorkItem,
|
|
IopDelayedRemoveWorker,
|
|
entry);
|
|
|
|
ExQueueWorkItem(&entry->WorkItem, DelayedWorkQueue);
|
|
|
|
} else {
|
|
|
|
//
|
|
// We are already in the system process and not in some
|
|
// random ObDeref call, so call the worker inline.
|
|
//
|
|
IopDelayedRemoveWorker( entry );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(link != &IopPendingSurpriseRemovals);
|
|
|
|
ExReleaseResourceLite(&IopSurpriseRemoveListLock);
|
|
KeLeaveCriticalRegion();
|
|
}
|
|
|
|
VOID
|
|
IopDelayedRemoveWorker(
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is usually called from a worker thread to actually send the
|
|
remove IRPs once the reference count on a PDO and all its attached devices
|
|
transitions to a zero.
|
|
|
|
Arguments:
|
|
|
|
Context - Supplies a pointer to the pending relations list entry which has
|
|
the relations list of PDOs we need to remove.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPENDING_RELATIONS_LIST_ENTRY entry = (PPENDING_RELATIONS_LIST_ENTRY)Context;
|
|
|
|
PAGED_CODE();
|
|
|
|
PpDevNodeLockTree(PPL_TREEOP_ALLOW_READS);
|
|
|
|
IopDeleteLockedDeviceNodes( entry->DeviceObject,
|
|
entry->RelationsList,
|
|
RemoveDevice, // OperationCode
|
|
FALSE, // ProcessIndirectDescendants
|
|
entry->Problem, // Problem
|
|
NULL, // VetoType
|
|
NULL); // VetoName
|
|
|
|
//
|
|
// The final reference on DeviceNodes in the DeviceNodeDeletePendingCloses
|
|
// state is dropped here.
|
|
//
|
|
IopFreeRelationList( entry->RelationsList );
|
|
|
|
ExFreePool( entry );
|
|
PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS);
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
IopDeleteLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
IN PRELATION_LIST RelationsList,
|
|
IN ULONG Problem,
|
|
OUT PNP_VETO_TYPE *VetoType OPTIONAL,
|
|
OUT PUNICODE_STRING VetoName OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function assumes that the specified device is a bus and will
|
|
recursively remove all its children.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node to be removed.
|
|
|
|
VetoType - Pointer to address that receives the veto type if the operation
|
|
failed.
|
|
|
|
VetoName - Pointer to a unicode string that will receive data appropriate
|
|
to the veto type.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
{
|
|
BOOLEAN success;
|
|
|
|
PAGED_CODE();
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopDeleteLockedDeviceNode: Entered\n DeviceNode = 0x%p\n OperationCode = 0x%08X\n RelationsList = 0x%p\n Problem = %d\n",
|
|
DeviceNode,
|
|
OperationCode,
|
|
RelationsList,
|
|
Problem));
|
|
|
|
success = TRUE;
|
|
switch(OperationCode) {
|
|
|
|
case SurpriseRemoveDevice:
|
|
|
|
IopSurpriseRemoveLockedDeviceNode(DeviceNode, RelationsList);
|
|
break;
|
|
|
|
case RemoveDevice:
|
|
|
|
IopRemoveLockedDeviceNode(DeviceNode, Problem, RelationsList);
|
|
break;
|
|
|
|
case QueryRemoveDevice:
|
|
|
|
ASSERT(VetoType && VetoName);
|
|
|
|
success = IopQueryRemoveLockedDeviceNode(
|
|
DeviceNode,
|
|
VetoType,
|
|
VetoName
|
|
);
|
|
|
|
break;
|
|
|
|
case CancelRemoveDevice:
|
|
|
|
IopCancelRemoveLockedDeviceNode(DeviceNode);
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
VOID
|
|
IopSurpriseRemoveLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN OUT PRELATION_LIST RelationsList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function sends a surprise remove IRP to a devnode and processes the
|
|
results.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node to be surprise removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PNP_DEVNODE_STATE devnodeState, schedulerState;
|
|
PDEVICE_OBJECT deviceObject;
|
|
PDEVICE_NODE child, nextChild;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER (RelationsList);
|
|
|
|
schedulerState = DeviceNode->State;
|
|
|
|
ASSERT((schedulerState == DeviceNodeAwaitingQueuedDeletion) ||
|
|
(schedulerState == DeviceNodeAwaitingQueuedRemoval));
|
|
|
|
//
|
|
// Clear the scheduling state (DeviceNodeAwaitingQueuedDeletion) off
|
|
// the state stack.
|
|
//
|
|
PipRestoreDevNodeState(DeviceNode);
|
|
|
|
devnodeState = DeviceNode->State;
|
|
|
|
//
|
|
// Do our state updates.
|
|
//
|
|
PpHotSwapInitRemovalPolicy(DeviceNode);
|
|
|
|
if (devnodeState == DeviceNodeRemovePendingCloses) {
|
|
|
|
//
|
|
// If the state is DeviceNodeRemovePendingCloses, we should have got
|
|
// here via DeviceNodeAwaitingQueuedDeletion. We're probably surprise
|
|
// removing a device that was already surprise failed.
|
|
//
|
|
ASSERT(schedulerState == DeviceNodeAwaitingQueuedDeletion);
|
|
|
|
//ASSERT(DeviceNode->Child == NULL);
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeDeletePendingCloses, NULL);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Detach any children from the tree here. If they needed SurpriseRemove
|
|
// IRPs, they already will have received them.
|
|
//
|
|
for(child = DeviceNode->Child; child; child = nextChild) {
|
|
|
|
//
|
|
// Grab a copy of the next sibling before we blow away this devnode.
|
|
//
|
|
nextChild = child->Sibling;
|
|
|
|
if (child->Flags & DNF_ENUMERATED) {
|
|
child->Flags &= ~DNF_ENUMERATED;
|
|
}
|
|
|
|
//
|
|
// If the child has resources and we are wiping out the parent, we need
|
|
// to drop the resources (the parent will lose them when his arbiter is
|
|
// nuked with the upcoming SurpriseRemoveDevice.)
|
|
//
|
|
if (PipDoesDevNodeHaveResources(child)) {
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopSurpriseRemoveLockedDeviceNode: Releasing resources for child device = 0x%p\n",
|
|
child->PhysicalDeviceObject));
|
|
|
|
//
|
|
// ADRIAO N.B. 2000/08/21 -
|
|
// Note that if the child stack has no drivers then a Remove
|
|
// IRP could be sent here. The stack would be unable to distinguish
|
|
// this from AddDevice cleanup.
|
|
//
|
|
/*
|
|
if ((child->State == DeviceNodeUninitialized) ||
|
|
(child->State == DeviceNodeInitialized)) {
|
|
|
|
IopRemoveDevice(child->PhysicalDeviceObject, IRP_MN_REMOVE_DEVICE);
|
|
}
|
|
*/
|
|
IopReleaseDeviceResources(child, FALSE);
|
|
}
|
|
|
|
//
|
|
// The devnode will be removed from the tree in
|
|
// IopUnlinkDeviceRemovalRelations. We don't remove it here as we want
|
|
// the tree structure in place for the upcoming broadcast down to user
|
|
// mode.
|
|
//
|
|
// Note - Children in the Uninitialized/Initialized states are not
|
|
// put directly into DeviceNodeDeleted today. This could be
|
|
// done but we'd have to verify what happens to API calls in
|
|
// response to SurpriseRemoval notifications. (Actually, those
|
|
// API's are blocked in ppcontrol.c, hotplug cannot in fact
|
|
// walk the tree!)
|
|
//
|
|
PipSetDevNodeState(child, DeviceNodeDeletePendingCloses, NULL);
|
|
}
|
|
|
|
//
|
|
// Only send surprise removes where neccessary.
|
|
//
|
|
// ISSUE - 2000/08/24 - ADRIAO: Maintaining noncorrect Win2K behavior
|
|
// Win2K erroneously sent SR's to nonstarted nodes.
|
|
//
|
|
deviceObject = DeviceNode->PhysicalDeviceObject;
|
|
|
|
status = IopRemoveDevice(deviceObject, IRP_MN_SURPRISE_REMOVAL);
|
|
|
|
if ((devnodeState == DeviceNodeStarted) ||
|
|
(devnodeState == DeviceNodeStopped) ||
|
|
(devnodeState == DeviceNodeStartPostWork) ||
|
|
(devnodeState == DeviceNodeRestartCompletion)) {
|
|
|
|
//deviceObject = DeviceNode->PhysicalDeviceObject;
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopSurpriseRemoveLockedDeviceNode: Sending surprise remove irp to device = 0x%p\n",
|
|
deviceObject));
|
|
|
|
//status = IopRemoveDevice(deviceObject, IRP_MN_SURPRISE_REMOVAL);
|
|
|
|
//
|
|
// Disable any device interfaces that may still be enabled for this
|
|
// device after the removal.
|
|
//
|
|
IopDisableDeviceInterfaces(&DeviceNode->InstancePath);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopSurpriseRemoveLockedDeviceNode: Releasing devices resources\n"));
|
|
|
|
IopReleaseDeviceResources(DeviceNode, FALSE);
|
|
}
|
|
|
|
if (DeviceNode->Flags & DNF_ENUMERATED) {
|
|
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeRemovePendingCloses, NULL);
|
|
|
|
} else {
|
|
|
|
ASSERT(schedulerState == DeviceNodeAwaitingQueuedDeletion);
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeDeletePendingCloses, NULL);
|
|
}
|
|
}
|
|
|
|
ASSERT(DeviceNode->DockInfo.DockStatus != DOCK_ARRIVING);
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
IopQueryRemoveLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
OUT PNP_VETO_TYPE *VetoType,
|
|
OUT PUNICODE_STRING VetoName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function sends a query remove IRP to a devnode and processes the
|
|
results.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node to be query removed.
|
|
|
|
VetoType - Pointer to address that receives the veto type if the operation
|
|
failed.
|
|
|
|
VetoName - Pointer to a unicode string that will receive data appropriate
|
|
to the veto type.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN (success/failure).
|
|
|
|
--*/
|
|
{
|
|
PNP_DEVNODE_STATE devnodeState;
|
|
PDEVICE_OBJECT deviceObject;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
devnodeState = DeviceNode->State;
|
|
|
|
switch(devnodeState) {
|
|
case DeviceNodeUninitialized:
|
|
case DeviceNodeInitialized:
|
|
case DeviceNodeRemoved:
|
|
//
|
|
// Don't send Queries to devices that haven't been started.
|
|
//
|
|
ASSERT(DeviceNode->Child == NULL);
|
|
return TRUE;
|
|
|
|
case DeviceNodeDriversAdded:
|
|
case DeviceNodeResourcesAssigned:
|
|
case DeviceNodeStartCompletion:
|
|
case DeviceNodeStartPostWork:
|
|
//
|
|
// ISSUE - 2000/08/24 - ADRIAO: Maintaining noncorrect Win2K behavior
|
|
// Win2K erroneously sent QR's to all nodes.
|
|
//
|
|
break;
|
|
|
|
case DeviceNodeStarted:
|
|
//
|
|
// This guy needs to be queried
|
|
//
|
|
break;
|
|
|
|
case DeviceNodeAwaitingQueuedRemoval:
|
|
case DeviceNodeAwaitingQueuedDeletion:
|
|
case DeviceNodeRemovePendingCloses:
|
|
case DeviceNodeStopped:
|
|
case DeviceNodeRestartCompletion:
|
|
//
|
|
// These states should have been culled by IopProcessRelation
|
|
//
|
|
ASSERT(0);
|
|
return TRUE;
|
|
|
|
case DeviceNodeQueryStopped:
|
|
case DeviceNodeEnumeratePending:
|
|
case DeviceNodeStartPending:
|
|
case DeviceNodeEnumerateCompletion:
|
|
case DeviceNodeQueryRemoved:
|
|
case DeviceNodeDeletePendingCloses:
|
|
case DeviceNodeDeleted:
|
|
case DeviceNodeUnspecified:
|
|
default:
|
|
//
|
|
// None of these should be seen here.
|
|
//
|
|
ASSERT(0);
|
|
return TRUE;
|
|
}
|
|
|
|
ASSERT(PipAreDriversLoaded(DeviceNode));
|
|
|
|
deviceObject = DeviceNode->PhysicalDeviceObject;
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopQueryRemoveLockedDeviceNode: Sending QueryRemove irp to device = 0x%p\n",
|
|
deviceObject));
|
|
|
|
status = IopRemoveDevice(deviceObject, IRP_MN_QUERY_REMOVE_DEVICE);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopQueryRemoveLockedDeviceNode: QueryRemove vetoed by device = 0x%p, sending CancelRemove\n",
|
|
deviceObject));
|
|
|
|
IopRemoveDevice(deviceObject, IRP_MN_CANCEL_REMOVE_DEVICE);
|
|
|
|
*VetoType = PNP_VetoDevice;
|
|
RtlCopyUnicodeString(VetoName, &DeviceNode->InstancePath);
|
|
return FALSE;
|
|
}
|
|
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeQueryRemoved, NULL);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
IopCancelRemoveLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function sends a cancel remove IRP to a devnode and processes the
|
|
results.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node to be cancel removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PDEVICE_OBJECT deviceObject;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (DeviceNode->State != DeviceNodeQueryRemoved) {
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// ISSUE - 2000/08/24 - ADRIAO: Maintaining noncorrect Win2K behavior
|
|
// Win2K erroneously sent QR's to all nodes.
|
|
//
|
|
//ASSERT(DeviceNode->PreviousState == DeviceNodeStarted);
|
|
|
|
deviceObject = DeviceNode->PhysicalDeviceObject;
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopCancelRemoveLockedDeviceNode: Sending CancelRemove irp to device = 0x%p\n",
|
|
deviceObject));
|
|
|
|
IopRemoveDevice(deviceObject, IRP_MN_CANCEL_REMOVE_DEVICE);
|
|
|
|
PipRestoreDevNodeState(DeviceNode);
|
|
}
|
|
|
|
|
|
VOID
|
|
IopRemoveLockedDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN ULONG Problem,
|
|
IN OUT PRELATION_LIST RelationsList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function sends a remove IRP to a devnode and processes the
|
|
results.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node to be removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PDEVICE_OBJECT deviceObject = DeviceNode->PhysicalDeviceObject;
|
|
PDEVICE_OBJECT *attachedDevices, device1, *device2;
|
|
PDRIVER_OBJECT *attachedDrivers, *driver;
|
|
ULONG length = 0;
|
|
PDEVICE_NODE child, nextChild;
|
|
BOOLEAN removeIrpNeeded;
|
|
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER (RelationsList);
|
|
|
|
//
|
|
// Do our state updates.
|
|
//
|
|
PpHotSwapInitRemovalPolicy(DeviceNode);
|
|
|
|
//
|
|
// Make sure we WILL drop our references to its children.
|
|
//
|
|
for(child = DeviceNode->Child; child; child = nextChild) {
|
|
|
|
//
|
|
// Grab a copy of the next sibling before we blow away this devnode.
|
|
//
|
|
nextChild = child->Sibling;
|
|
|
|
if (child->Flags & DNF_ENUMERATED) {
|
|
child->Flags &= ~DNF_ENUMERATED;
|
|
}
|
|
|
|
ASSERT(child->State == DeviceNodeRemoved);
|
|
ASSERT(!PipAreDriversLoaded(child));
|
|
|
|
//
|
|
// If the child has resources and we are wiping out the parent, we need
|
|
// to drop the resources (the parent will lose them when his arbiter is
|
|
// nuked with the upcoming RemoveDevice.)
|
|
//
|
|
if (PipDoesDevNodeHaveResources(child)) {
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopRemoveLockedDeviceNode: Releasing resources for child device = 0x%p\n",
|
|
child->PhysicalDeviceObject));
|
|
|
|
//
|
|
// ADRIAO N.B. 2000/08/21 -
|
|
// Note that the child stack has no drivers and as such a
|
|
// Remove IRP could be sent here. The stack would be unable to
|
|
// distinguish this from AddDevice cleanup.
|
|
//
|
|
IopRemoveDevice(child->PhysicalDeviceObject, IRP_MN_REMOVE_DEVICE);
|
|
|
|
IopReleaseDeviceResources(child, FALSE);
|
|
}
|
|
|
|
//
|
|
// The devnode will be removed from the tree in
|
|
// IopUnlinkDeviceRemovalRelations. We don't remove it here as we want
|
|
// the tree structure in place for the upcoming broadcast down to user
|
|
// mode.
|
|
//
|
|
PipSetDevNodeState(child, DeviceNodeDeleted, NULL);
|
|
}
|
|
|
|
if ((DeviceNode->State == DeviceNodeAwaitingQueuedDeletion) ||
|
|
(DeviceNode->State == DeviceNodeAwaitingQueuedRemoval)) {
|
|
|
|
if (!(DeviceNode->Flags & DNF_ENUMERATED)) {
|
|
|
|
ASSERT(DeviceNode->State == DeviceNodeAwaitingQueuedDeletion);
|
|
//
|
|
// This happens when pnpevent shortcircuits the surprise remove path
|
|
// upon discovering a nonstarted device has been removed from the
|
|
// system. This devnode will need a final remove if it alone has been
|
|
// pulled from the tree (we don't here know if the parent is going to
|
|
// get pulled too, which would make this remove IRP unneccessary.)
|
|
//
|
|
//PipRestoreDevNodeState(DeviceNode);
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeDeletePendingCloses, NULL);
|
|
|
|
} else {
|
|
|
|
ASSERT(DeviceNode->State == DeviceNodeAwaitingQueuedRemoval);
|
|
PipRestoreDevNodeState(DeviceNode);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do the final remove cleanup on the device...
|
|
//
|
|
switch(DeviceNode->State) {
|
|
|
|
case DeviceNodeUninitialized:
|
|
case DeviceNodeInitialized:
|
|
case DeviceNodeRemoved:
|
|
//
|
|
// ISSUE - 2000/08/24 - ADRIAO: Maintaining noncorrect Win2K behavior
|
|
// Win2K erroneously sent SR's and R's to all
|
|
// nodes. Those bugs must be fixed in tandem.
|
|
//
|
|
//removeIrpNeeded = FALSE;
|
|
removeIrpNeeded = TRUE;
|
|
break;
|
|
|
|
case DeviceNodeDriversAdded:
|
|
case DeviceNodeResourcesAssigned:
|
|
case DeviceNodeStartCompletion:
|
|
case DeviceNodeStartPostWork:
|
|
case DeviceNodeQueryRemoved:
|
|
case DeviceNodeRemovePendingCloses:
|
|
case DeviceNodeDeletePendingCloses:
|
|
//
|
|
// Expected.
|
|
//
|
|
removeIrpNeeded = TRUE;
|
|
break;
|
|
|
|
case DeviceNodeStarted:
|
|
case DeviceNodeStopped:
|
|
case DeviceNodeRestartCompletion:
|
|
case DeviceNodeQueryStopped:
|
|
case DeviceNodeEnumeratePending:
|
|
case DeviceNodeStartPending:
|
|
case DeviceNodeEnumerateCompletion:
|
|
case DeviceNodeAwaitingQueuedRemoval:
|
|
case DeviceNodeAwaitingQueuedDeletion:
|
|
case DeviceNodeDeleted:
|
|
case DeviceNodeUnspecified:
|
|
default:
|
|
//
|
|
// None of these should be seen here.
|
|
//
|
|
ASSERT(0);
|
|
removeIrpNeeded = TRUE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Add a reference to each FDO attached to the PDO such that the FDOs won't
|
|
// actually go away until the removal operation is completed.
|
|
// Note we need to make a copy of all the attached devices because we won't be
|
|
// able to traverse the attached chain when the removal operation is done.
|
|
//
|
|
// ISSUE - 2000/08/21 - ADRIAO: Low resource path
|
|
// The allocation failure cases here are quite broken, and now that
|
|
// IofCallDriver and IofCompleteRequest reference things appropriately, all
|
|
// this is strictly unneccessary.
|
|
//
|
|
device1 = deviceObject->AttachedDevice;
|
|
while (device1) {
|
|
length++;
|
|
device1 = device1->AttachedDevice;
|
|
}
|
|
|
|
attachedDevices = NULL;
|
|
attachedDrivers = NULL;
|
|
if (length != 0) {
|
|
|
|
length = (length + 2) * sizeof(PDEVICE_OBJECT);
|
|
|
|
attachedDevices = (PDEVICE_OBJECT *) ExAllocatePool(PagedPool, length);
|
|
if (attachedDevices) {
|
|
|
|
attachedDrivers = (PDRIVER_OBJECT *) ExAllocatePool(PagedPool, length);
|
|
if (attachedDrivers) {
|
|
|
|
RtlZeroMemory(attachedDevices, length);
|
|
RtlZeroMemory(attachedDrivers, length);
|
|
device1 = deviceObject->AttachedDevice;
|
|
device2 = attachedDevices;
|
|
driver = attachedDrivers;
|
|
|
|
while (device1) {
|
|
ObReferenceObject(device1);
|
|
*device2++ = device1;
|
|
*driver++ = device1->DriverObject;
|
|
device1 = device1->AttachedDevice;
|
|
}
|
|
|
|
} else {
|
|
|
|
ExFreePool(attachedDevices);
|
|
attachedDevices = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (removeIrpNeeded) {
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopRemoveLockedDeviceNode: Sending remove irp to device = 0x%p\n",
|
|
deviceObject));
|
|
|
|
IopRemoveDevice(deviceObject, IRP_MN_REMOVE_DEVICE);
|
|
|
|
if (DeviceNode->State == DeviceNodeQueryRemoved) {
|
|
//
|
|
// Disable any device interfaces that may still be enabled for this
|
|
// device after the removal.
|
|
//
|
|
IopDisableDeviceInterfaces(&DeviceNode->InstancePath);
|
|
}
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopRemoveLockedDeviceNode: Releasing devices resources\n"));
|
|
|
|
//
|
|
// ISSUE - 2000/3/8 - RobertN - This doesn't take into account the
|
|
// cleanup of surprise removed devices. We will query for boot configs
|
|
// unnecessarily. We should probably also check if the parent is NULL.
|
|
//
|
|
IopReleaseDeviceResources(
|
|
DeviceNode,
|
|
(BOOLEAN) ((DeviceNode->Flags & DNF_ENUMERATED) != 0)
|
|
);
|
|
}
|
|
|
|
if (!(DeviceNode->Flags & DNF_ENUMERATED)) {
|
|
//
|
|
// If the device is a dock, remove it from the list of dock devices
|
|
// and change the current Hardware Profile, if necessary.
|
|
//
|
|
ASSERT(DeviceNode->DockInfo.DockStatus != DOCK_ARRIVING) ;
|
|
if ((DeviceNode->DockInfo.DockStatus == DOCK_DEPARTING)||
|
|
(DeviceNode->DockInfo.DockStatus == DOCK_EJECTIRP_COMPLETED)) {
|
|
|
|
PpProfileCommitTransitioningDock(DeviceNode, DOCK_DEPARTING);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remove the reference to the attached FDOs to allow them to be actually
|
|
// deleted.
|
|
//
|
|
device2 = attachedDevices;
|
|
if (device2 != NULL) {
|
|
driver = attachedDrivers;
|
|
while (*device2) {
|
|
(*device2)->DeviceObjectExtension->ExtensionFlags &= ~(DOE_REMOVE_PENDING | DOE_REMOVE_PROCESSED);
|
|
(*device2)->DeviceObjectExtension->ExtensionFlags |= DOE_START_PENDING;
|
|
IopUnloadAttachedDriver(*driver);
|
|
ObDereferenceObject(*device2);
|
|
device2++;
|
|
driver++;
|
|
}
|
|
ExFreePool(attachedDevices);
|
|
ExFreePool(attachedDrivers);
|
|
}
|
|
|
|
deviceObject->DeviceObjectExtension->ExtensionFlags &= ~(DOE_REMOVE_PENDING | DOE_REMOVE_PROCESSED);
|
|
deviceObject->DeviceObjectExtension->ExtensionFlags |= DOE_START_PENDING;
|
|
|
|
//
|
|
// Now mark this one removed if it's still in the tree.
|
|
//
|
|
if (DeviceNode->Flags & DNF_ENUMERATED) {
|
|
|
|
ASSERT(DeviceNode->Parent);
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeRemoved, NULL);
|
|
|
|
} else if (DeviceNode->Parent != NULL) {
|
|
|
|
//
|
|
// The devnode will be removed from the tree in
|
|
// IopUnlinkDeviceRemovalRelations.
|
|
//
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeDeleted, NULL);
|
|
|
|
} else {
|
|
|
|
ASSERT(DeviceNode->State == DeviceNodeDeletePendingCloses);
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeDeleted, NULL);
|
|
}
|
|
|
|
//
|
|
// Set the problem codes appropriatly. We don't change the problem codes
|
|
// on a devnode unless:
|
|
// a) It disappeared.
|
|
// b) We're disabling it.
|
|
//
|
|
if ((!PipDoesDevNodeHaveProblem(DeviceNode)) ||
|
|
(Problem == CM_PROB_DEVICE_NOT_THERE) ||
|
|
(Problem == CM_PROB_DISABLED)) {
|
|
|
|
PipClearDevNodeProblem(DeviceNode);
|
|
PipSetDevNodeProblem(DeviceNode, Problem);
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
IopDeleteLockedDeviceNodes(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PRELATION_LIST RelationsList,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
IN BOOLEAN ProcessIndirectDescendants,
|
|
IN ULONG Problem,
|
|
OUT PNP_VETO_TYPE *VetoType OPTIONAL,
|
|
OUT PUNICODE_STRING VetoName OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs requested operation on the DeviceObject and
|
|
the device objects specified in the DeviceRelations.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies a pointer to the device object.
|
|
|
|
DeviceRelations - supplies a pointer to the device's removal relations.
|
|
|
|
OperationCode - Operation code, i.e., QueryRemove, CancelRemove, Remove...
|
|
|
|
VetoType - Pointer to address that receives the veto type if the operation
|
|
failed.
|
|
|
|
VetoName - Pointer to a unicode string that will receive data appropriate
|
|
to the veto type.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PDEVICE_NODE deviceNode;
|
|
PDEVICE_OBJECT relatedDeviceObject;
|
|
ULONG marker;
|
|
BOOLEAN directDescendant;
|
|
|
|
PAGED_CODE();
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopDeleteLockedDeviceNodes: Entered\n DeviceObject = 0x%p\n RelationsList = 0x%p\n OperationCode = %d\n",
|
|
DeviceObject,
|
|
RelationsList,
|
|
OperationCode));
|
|
|
|
deviceNode = (PDEVICE_NODE) DeviceObject->DeviceObjectExtension->DeviceNode;
|
|
|
|
marker = 0;
|
|
while (IopEnumerateRelations( RelationsList,
|
|
&marker,
|
|
&relatedDeviceObject,
|
|
&directDescendant,
|
|
NULL,
|
|
TRUE)) {
|
|
|
|
//
|
|
// Depending on the operation we need to do different things.
|
|
//
|
|
// QueryRemoveDevice / CancelRemoveDevice
|
|
// Process both direct and indirect descendants
|
|
//
|
|
// SurpriseRemoveDevice / RemoveDevice
|
|
// Ignore indirect descendants
|
|
//
|
|
if (directDescendant || ProcessIndirectDescendants) {
|
|
|
|
deviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode;
|
|
|
|
if (!IopDeleteLockedDeviceNode( deviceNode,
|
|
OperationCode,
|
|
RelationsList,
|
|
Problem,
|
|
VetoType,
|
|
VetoName)) {
|
|
|
|
ASSERT(OperationCode == QueryRemoveDevice);
|
|
|
|
while (IopEnumerateRelations( RelationsList,
|
|
&marker,
|
|
&relatedDeviceObject,
|
|
NULL,
|
|
NULL,
|
|
FALSE)) {
|
|
|
|
deviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode;
|
|
|
|
IopDeleteLockedDeviceNode( deviceNode,
|
|
CancelRemoveDevice,
|
|
RelationsList,
|
|
Problem,
|
|
VetoType,
|
|
VetoName);
|
|
}
|
|
|
|
status = STATUS_UNSUCCESSFUL;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
IopBuildRemovalRelationList(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
OUT PNP_VETO_TYPE *VetoType,
|
|
OUT PUNICODE_STRING VetoName,
|
|
OUT PRELATION_LIST *RelationsList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine locks the device subtrees for removal operation and returns
|
|
a list of device objects which need to be removed with the specified
|
|
DeviceObject.
|
|
|
|
Caller must hold a reference to the DeviceObject.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies a pointer to the device object to be removed.
|
|
|
|
OperationCode - Operation code, i.e., QueryEject, CancelEject, Eject...
|
|
|
|
VetoType - Pointer to address that receives the veto type if the operation
|
|
failed.
|
|
|
|
VetoName - Pointer to a unicode string that will receive data appropriate
|
|
to the veto type.
|
|
|
|
RelationList - supplies a pointer to a variable to receive the device's
|
|
relations.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS status;
|
|
PDEVICE_NODE deviceNode;
|
|
PRELATION_LIST newRelationsList;
|
|
|
|
PAGED_CODE();
|
|
|
|
*RelationsList = NULL;
|
|
|
|
deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode;
|
|
|
|
//
|
|
// Obviously no one should try to delete the whole device node tree.
|
|
//
|
|
ASSERT(DeviceObject != IopRootDeviceNode->PhysicalDeviceObject);
|
|
|
|
if ((newRelationsList = IopAllocateRelationList(OperationCode)) == NULL) {
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// First process the object itself
|
|
//
|
|
status = IopProcessRelation(
|
|
deviceNode,
|
|
OperationCode,
|
|
TRUE,
|
|
VetoType,
|
|
VetoName,
|
|
newRelationsList
|
|
);
|
|
|
|
ASSERT(status != STATUS_INVALID_DEVICE_REQUEST);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
IopCompressRelationList(&newRelationsList);
|
|
*RelationsList = newRelationsList;
|
|
|
|
//
|
|
// At this point we have a list of all the relations, those that are
|
|
// direct descendants of the original device we are ejecting or
|
|
// removing have the DirectDescendant bit set.
|
|
//
|
|
// Relations which were merged from an existing eject have the tagged
|
|
// bit set.
|
|
//
|
|
// All of the relations and their parents are locked.
|
|
//
|
|
// There is a reference on each device object by virtue of it being in
|
|
// the list. There is another one on each device object because it is
|
|
// locked and the lock count is >= 1.
|
|
//
|
|
// There is also a reference on each relation's parent and it's lock
|
|
// count is >= 1.
|
|
//
|
|
} else {
|
|
|
|
IopFreeRelationList(newRelationsList);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
PiProcessBusRelations(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
IN BOOLEAN IsDirectDescendant,
|
|
OUT PNP_VETO_TYPE *VetoType,
|
|
OUT PUNICODE_STRING VetoName,
|
|
IN OUT PRELATION_LIST RelationsList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine processes the BusRelations for the specified devnode.
|
|
Caller must hold the device tree lock.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device object to be collected.
|
|
|
|
OperationCode - Operation code, i.e., QueryRemove, QueryEject, ...
|
|
|
|
IsDirectDescendant - TRUE if the device object is a direct descendant
|
|
of the node the operation is being performed upon.
|
|
|
|
VetoType - Pointer to address that receives the veto type if the operation
|
|
failed.
|
|
|
|
VetoName - Pointer to a unicode string that will receive data appropriate
|
|
to the veto type.
|
|
|
|
RelationList - supplies a pointer to a variable to receive the device's
|
|
removal relations.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
{
|
|
PDEVICE_NODE child;
|
|
PDEVICE_OBJECT childDeviceObject;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
for(child = DeviceNode->Child;
|
|
child != NULL;
|
|
child = child->Sibling) {
|
|
|
|
childDeviceObject = child->PhysicalDeviceObject;
|
|
|
|
status = IopProcessRelation(
|
|
child,
|
|
OperationCode,
|
|
IsDirectDescendant,
|
|
VetoType,
|
|
VetoName,
|
|
RelationsList
|
|
);
|
|
|
|
ASSERT(status == STATUS_SUCCESS || status == STATUS_UNSUCCESSFUL);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
return status;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
IopProcessRelation(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
IN BOOLEAN IsDirectDescendant,
|
|
OUT PNP_VETO_TYPE *VetoType,
|
|
OUT PUNICODE_STRING VetoName,
|
|
IN OUT PRELATION_LIST RelationsList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds the list of device objects that need to be removed or
|
|
examined when the passed in device object is torn down.
|
|
|
|
Caller must hold the device tree lock.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device object to be collected.
|
|
|
|
OperationCode - Operation code, i.e., QueryRemove, QueryEject, ...
|
|
|
|
IsDirectDescendant - TRUE if the device object is a direct descendant
|
|
of the node the operation is being performed upon.
|
|
|
|
VetoType - Pointer to address that receives the veto type if the operation
|
|
failed.
|
|
|
|
VetoName - Pointer to a unicode string that will receive data appropriate
|
|
to the veto type.
|
|
|
|
RelationList - supplies a pointer to a variable to receive the device's
|
|
removal relations.
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
{
|
|
PDEVICE_NODE relatedDeviceNode;
|
|
PDEVICE_OBJECT relatedDeviceObject;
|
|
PDEVICE_RELATIONS deviceRelations;
|
|
PLIST_ENTRY ejectLink;
|
|
PPENDING_RELATIONS_LIST_ENTRY ejectEntry;
|
|
PRELATION_LIST pendingRelationList;
|
|
NTSTATUS status;
|
|
ULONG i;
|
|
PNP_DEVNODE_STATE devnodeState;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (OperationCode == QueryRemoveDevice || OperationCode == EjectDevice) {
|
|
|
|
if (DeviceNode->State == DeviceNodeDeleted) {
|
|
|
|
//
|
|
// The device has already been removed, fail the attempt.
|
|
//
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if ((DeviceNode->State == DeviceNodeAwaitingQueuedRemoval) ||
|
|
(DeviceNode->State == DeviceNodeAwaitingQueuedDeletion)) {
|
|
|
|
//
|
|
// The device has failed or is going away. Let the queued
|
|
// remove deal with it.
|
|
//
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if ((DeviceNode->State == DeviceNodeRemovePendingCloses) ||
|
|
(DeviceNode->State == DeviceNodeDeletePendingCloses)) {
|
|
|
|
//
|
|
// The device is in the process of being surprise removed, let it finish
|
|
//
|
|
*VetoType = PNP_VetoOutstandingOpen;
|
|
RtlCopyUnicodeString(VetoName, &DeviceNode->InstancePath);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if ((DeviceNode->State == DeviceNodeStopped) ||
|
|
(DeviceNode->State == DeviceNodeRestartCompletion)) {
|
|
|
|
//
|
|
// We are recovering from a rebalance. This should never happen and
|
|
// this return code will cause us to ASSERT.
|
|
//
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
} else if (DeviceNode->State == DeviceNodeDeleted) {
|
|
|
|
//
|
|
// The device has already been removed, ignore it. We should only have
|
|
// seen such a thing if it got handed to us in a Removal or Ejection
|
|
// relation.
|
|
//
|
|
ASSERT(!IsDirectDescendant);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
status = IopAddRelationToList( RelationsList,
|
|
DeviceNode->PhysicalDeviceObject,
|
|
IsDirectDescendant,
|
|
FALSE);
|
|
|
|
if (status == STATUS_SUCCESS) {
|
|
|
|
if (!(DeviceNode->Flags & DNF_LOCKED_FOR_EJECT)) {
|
|
|
|
//
|
|
// Then process the bus relations
|
|
//
|
|
status = PiProcessBusRelations(
|
|
DeviceNode,
|
|
OperationCode,
|
|
IsDirectDescendant,
|
|
VetoType,
|
|
VetoName,
|
|
RelationsList
|
|
);
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
return status;
|
|
}
|
|
//
|
|
// Retrieve the state of the devnode when it failed.
|
|
//
|
|
devnodeState = DeviceNode->State;
|
|
if ((devnodeState == DeviceNodeAwaitingQueuedRemoval) ||
|
|
(devnodeState == DeviceNodeAwaitingQueuedDeletion)) {
|
|
|
|
devnodeState = DeviceNode->PreviousState;
|
|
}
|
|
|
|
//
|
|
// Next the removal relations
|
|
//
|
|
if ((devnodeState == DeviceNodeStarted) ||
|
|
(devnodeState == DeviceNodeStopped) ||
|
|
(devnodeState == DeviceNodeStartPostWork) ||
|
|
(devnodeState == DeviceNodeRestartCompletion)) {
|
|
|
|
status = IopQueryDeviceRelations( RemovalRelations,
|
|
DeviceNode->PhysicalDeviceObject,
|
|
TRUE,
|
|
&deviceRelations);
|
|
|
|
if (NT_SUCCESS(status) && deviceRelations) {
|
|
|
|
for (i = 0; i < deviceRelations->Count; i++) {
|
|
|
|
relatedDeviceObject = deviceRelations->Objects[i];
|
|
|
|
relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode;
|
|
|
|
ASSERT(relatedDeviceNode);
|
|
|
|
if (relatedDeviceNode) {
|
|
|
|
status = IopProcessRelation(
|
|
relatedDeviceNode,
|
|
OperationCode,
|
|
FALSE,
|
|
VetoType,
|
|
VetoName,
|
|
RelationsList
|
|
);
|
|
}
|
|
|
|
ObDereferenceObject( relatedDeviceObject );
|
|
|
|
ASSERT(status == STATUS_SUCCESS ||
|
|
status == STATUS_UNSUCCESSFUL);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
ExFreePool(deviceRelations);
|
|
|
|
return status;
|
|
}
|
|
}
|
|
|
|
ExFreePool(deviceRelations);
|
|
} else {
|
|
if (status != STATUS_NOT_SUPPORTED) {
|
|
IopDbgPrint((IOP_LOADUNLOAD_WARNING_LEVEL,
|
|
"IopProcessRelation: IopQueryDeviceRelations failed, DeviceObject = 0x%p, status = 0x%08X\n",
|
|
DeviceNode->PhysicalDeviceObject, status));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Finally the eject relations if we are doing an eject operation
|
|
//
|
|
if (OperationCode != QueryRemoveDevice &&
|
|
OperationCode != RemoveFailedDevice &&
|
|
OperationCode != RemoveUnstartedFailedDevice) {
|
|
status = IopQueryDeviceRelations( EjectionRelations,
|
|
DeviceNode->PhysicalDeviceObject,
|
|
TRUE,
|
|
&deviceRelations);
|
|
|
|
if (NT_SUCCESS(status) && deviceRelations) {
|
|
|
|
for (i = 0; i < deviceRelations->Count; i++) {
|
|
|
|
relatedDeviceObject = deviceRelations->Objects[i];
|
|
|
|
relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode;
|
|
|
|
ASSERT(relatedDeviceNode);
|
|
|
|
if (relatedDeviceNode) {
|
|
|
|
status = IopProcessRelation(
|
|
relatedDeviceNode,
|
|
OperationCode,
|
|
FALSE,
|
|
VetoType,
|
|
VetoName,
|
|
RelationsList
|
|
);
|
|
}
|
|
|
|
ObDereferenceObject( relatedDeviceObject );
|
|
|
|
ASSERT(status == STATUS_SUCCESS ||
|
|
status == STATUS_UNSUCCESSFUL);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
ExFreePool(deviceRelations);
|
|
|
|
return status;
|
|
}
|
|
}
|
|
|
|
ExFreePool(deviceRelations);
|
|
} else {
|
|
if (status != STATUS_NOT_SUPPORTED) {
|
|
IopDbgPrint((IOP_LOADUNLOAD_WARNING_LEVEL,
|
|
"IopProcessRelation: IopQueryDeviceRelations failed, DeviceObject = 0x%p, status = 0x%08X\n",
|
|
DeviceNode->PhysicalDeviceObject,
|
|
status));
|
|
}
|
|
}
|
|
}
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Look to see if this device is already part of a pending ejection.
|
|
// If it is and we are doing an ejection then we will subsume it
|
|
// within the larger ejection. If we aren't doing an ejection then
|
|
// we better be processing the removal of one of the ejected devices.
|
|
//
|
|
for(ejectLink = IopPendingEjects.Flink;
|
|
ejectLink != &IopPendingEjects;
|
|
ejectLink = ejectLink->Flink) {
|
|
|
|
ejectEntry = CONTAINING_RECORD( ejectLink,
|
|
PENDING_RELATIONS_LIST_ENTRY,
|
|
Link);
|
|
|
|
if (ejectEntry->RelationsList != NULL &&
|
|
IopIsRelationInList(ejectEntry->RelationsList, DeviceNode->PhysicalDeviceObject)) {
|
|
|
|
|
|
if (OperationCode == EjectDevice) {
|
|
|
|
status = IopRemoveRelationFromList(RelationsList, DeviceNode->PhysicalDeviceObject);
|
|
|
|
ASSERT(NT_SUCCESS(status));
|
|
|
|
IopCancelPendingEject(ejectEntry);
|
|
pendingRelationList = ejectEntry->RelationsList;
|
|
ejectEntry->RelationsList = NULL;
|
|
|
|
//
|
|
// If a parent fails eject and it has a child that is
|
|
// infinitely pending an eject, this means the child now
|
|
// wakes up. One suggestion brought up that does not involve
|
|
// a code change is to amend the WDM spec to say if driver
|
|
// gets a start IRP for a device pending eject, it should
|
|
// cancel the eject IRP automatically.
|
|
//
|
|
IopMergeRelationLists(RelationsList, pendingRelationList, FALSE);
|
|
|
|
IopFreeRelationList(pendingRelationList);
|
|
|
|
if (IsDirectDescendant) {
|
|
//
|
|
// If IsDirectDescendant is specified then we need to
|
|
// get that bit set on the relation that caused us to
|
|
// do the merge. IopAddRelationToList will fail with
|
|
// STATUS_OBJECT_NAME_COLLISION but the bit will still
|
|
// be set as a side effect.
|
|
//
|
|
IopAddRelationToList( RelationsList,
|
|
DeviceNode->PhysicalDeviceObject,
|
|
TRUE,
|
|
FALSE);
|
|
}
|
|
} else if (OperationCode != QueryRemoveDevice) {
|
|
|
|
//
|
|
// Either the device itself disappeared or an ancestor
|
|
// of this device failed in some way. In both cases this
|
|
// happened before we completed the eject IRP. We'll
|
|
// remove it from the list in the pending ejection and
|
|
// return it.
|
|
//
|
|
|
|
status = IopRemoveRelationFromList( ejectEntry->RelationsList,
|
|
DeviceNode->PhysicalDeviceObject);
|
|
|
|
DeviceNode->Flags &= ~DNF_LOCKED_FOR_EJECT;
|
|
|
|
ASSERT(NT_SUCCESS(status));
|
|
|
|
} else {
|
|
|
|
//
|
|
// Someone is trying to take offline a supertree of this
|
|
// device which happens to be prepared for ejection.
|
|
// Whistler like Win2K won't let this happen (doing so
|
|
// isn't too hard, it involves writing code to cancel
|
|
// the outstanding eject and free the relation list.)
|
|
//
|
|
ASSERT(0);
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(ejectLink != &IopPendingEjects);
|
|
|
|
if (ejectLink == &IopPendingEjects) {
|
|
|
|
PP_SAVE_DEVICEOBJECT_TO_TRIAGE_DUMP(DeviceNode->PhysicalDeviceObject);
|
|
KeBugCheckEx( PNP_DETECTED_FATAL_ERROR,
|
|
PNP_ERR_DEVICE_MISSING_FROM_EJECT_LIST,
|
|
(ULONG_PTR)DeviceNode->PhysicalDeviceObject,
|
|
0,
|
|
0);
|
|
}
|
|
}
|
|
} else if (status == STATUS_OBJECT_NAME_COLLISION) {
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopProcessRelation: Duplicate relation, DeviceObject = 0x%p\n",
|
|
DeviceNode->PhysicalDeviceObject));
|
|
|
|
status = PiProcessBusRelations(
|
|
DeviceNode,
|
|
OperationCode,
|
|
IsDirectDescendant,
|
|
VetoType,
|
|
VetoName,
|
|
RelationsList
|
|
);
|
|
|
|
} else if (status != STATUS_INSUFFICIENT_RESOURCES) {
|
|
|
|
PP_SAVE_DEVICEOBJECT_TO_TRIAGE_DUMP(DeviceNode->PhysicalDeviceObject);
|
|
KeBugCheckEx( PNP_DETECTED_FATAL_ERROR,
|
|
PNP_ERR_UNEXPECTED_ADD_RELATION_ERR,
|
|
(ULONG_PTR)DeviceNode->PhysicalDeviceObject,
|
|
(ULONG_PTR)RelationsList,
|
|
status);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
BOOLEAN
|
|
IopQueuePendingEject(
|
|
PPENDING_RELATIONS_LIST_ENTRY Entry
|
|
)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PpDevNodeLockTree(PPL_TREEOP_ALLOW_READS);
|
|
|
|
InsertTailList(&IopPendingEjects, &Entry->Link);
|
|
|
|
PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
NTSTATUS
|
|
IopInvalidateRelationsInList(
|
|
IN PRELATION_LIST RelationsList,
|
|
IN PLUGPLAY_DEVICE_DELETE_TYPE OperationCode,
|
|
IN BOOLEAN OnlyIndirectDescendants,
|
|
IN BOOLEAN RestartDevNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Iterate over the relations in the list creating a second list containing the
|
|
parent of each entry skipping parents which are also in the list. In other
|
|
words, if the list contains node P and node C where node C is a child of node
|
|
P then the parent of node P would be added but not node P itself.
|
|
|
|
|
|
Arguments:
|
|
|
|
RelationsList - List of relations
|
|
|
|
OperationCode - Type of operation the invalidation is associated
|
|
with.
|
|
|
|
OnlyIndirectDescendants - Indirect relations are those which aren't direct
|
|
descendants (bus relations) of the PDO originally
|
|
targetted for the operation or its direct
|
|
descendants. This would include Removal or
|
|
Eject relations.
|
|
|
|
RestartDevNode - If true then any node who's parent was invalidated
|
|
is restarted. This flag requires that all the
|
|
relations in the list have been previously
|
|
sent a remove IRP.
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
{
|
|
PRELATION_LIST parentsList;
|
|
PDEVICE_OBJECT deviceObject, parentObject;
|
|
PDEVICE_NODE deviceNode;
|
|
ULONG marker;
|
|
BOOLEAN directDescendant, tagged;
|
|
|
|
PAGED_CODE();
|
|
|
|
parentsList = IopAllocateRelationList(OperationCode);
|
|
|
|
if (parentsList == NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
IopSetAllRelationsTags( RelationsList, FALSE );
|
|
|
|
//
|
|
// Traverse the list creating a new list with the topmost parents of
|
|
// each sublist contained in RelationsList.
|
|
//
|
|
|
|
marker = 0;
|
|
|
|
while (IopEnumerateRelations( RelationsList,
|
|
&marker,
|
|
&deviceObject,
|
|
&directDescendant,
|
|
&tagged,
|
|
TRUE)) {
|
|
|
|
if (!OnlyIndirectDescendants || !directDescendant) {
|
|
|
|
if (!tagged) {
|
|
|
|
parentObject = deviceObject;
|
|
|
|
while (IopSetRelationsTag( RelationsList, parentObject, TRUE ) == STATUS_SUCCESS) {
|
|
|
|
deviceNode = parentObject->DeviceObjectExtension->DeviceNode;
|
|
|
|
if (RestartDevNode) {
|
|
|
|
deviceNode->Flags &= ~DNF_LOCKED_FOR_EJECT;
|
|
|
|
//
|
|
// Bring the devnode back online if it:
|
|
// a) It is still physically present
|
|
// b) It was held for an eject
|
|
//
|
|
if ((deviceNode->Flags & DNF_ENUMERATED) &&
|
|
PipIsDevNodeProblem(deviceNode, CM_PROB_HELD_FOR_EJECT)) {
|
|
|
|
ASSERT(deviceNode->Child == NULL);
|
|
ASSERT(!PipAreDriversLoaded(deviceNode));
|
|
|
|
//
|
|
// This operation is a reorder barrier. This keeps
|
|
// our subsequent enumeration from draining prior
|
|
// to our problem clearing.
|
|
//
|
|
PipRequestDeviceAction( parentObject,
|
|
ClearEjectProblem,
|
|
TRUE,
|
|
0,
|
|
NULL,
|
|
NULL );
|
|
}
|
|
}
|
|
|
|
if (deviceNode->Parent != NULL) {
|
|
|
|
parentObject = deviceNode->Parent->PhysicalDeviceObject;
|
|
|
|
} else {
|
|
parentObject = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (parentObject != NULL) {
|
|
IopAddRelationToList( parentsList, parentObject, FALSE, FALSE );
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Reenumerate each of the parents
|
|
//
|
|
|
|
marker = 0;
|
|
|
|
while (IopEnumerateRelations( parentsList,
|
|
&marker,
|
|
&deviceObject,
|
|
NULL,
|
|
NULL,
|
|
FALSE)) {
|
|
|
|
PipRequestDeviceAction( deviceObject,
|
|
ReenumerateDeviceTree,
|
|
FALSE,
|
|
0,
|
|
NULL,
|
|
NULL );
|
|
}
|
|
|
|
//
|
|
// Free the parents list
|
|
//
|
|
|
|
IopFreeRelationList( parentsList );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
IopProcessCompletedEject(
|
|
IN PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called at passive level from a worker thread that was queued
|
|
either when an eject IRP completed (see io\pnpirp.c - IopDeviceEjectComplete
|
|
or io\pnpirp.c - IopEjectDevice), or when a warm eject needs to be performed.
|
|
We also may need to fire off any enumerations of parents of ejected devices
|
|
to verify they have indeed left.
|
|
|
|
Arguments:
|
|
|
|
Context - Pointer to the pending relations list which contains the device
|
|
to eject (warm) and the list of parents to reenumerate.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PPENDING_RELATIONS_LIST_ENTRY entry = (PPENDING_RELATIONS_LIST_ENTRY)Context;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PAGED_CODE();
|
|
|
|
if ((entry->LightestSleepState != PowerSystemWorking) &&
|
|
(entry->LightestSleepState != PowerSystemUnspecified)) {
|
|
|
|
//
|
|
// For docks, WinLogon gets to do the honors. For other devices, the
|
|
// user must infer when it's safe to remove the device (if we've powered
|
|
// up, it may not be safe now!)
|
|
//
|
|
entry->DisplaySafeRemovalDialog = FALSE;
|
|
|
|
//
|
|
// This is a warm eject request, initiate it here.
|
|
//
|
|
status = IopWarmEjectDevice(entry->DeviceObject, entry->LightestSleepState);
|
|
|
|
//
|
|
// We're back and we either succeeded or failed. Either way...
|
|
//
|
|
}
|
|
|
|
if (entry->DockInterface) {
|
|
|
|
entry->DockInterface->ProfileDepartureSetMode(
|
|
entry->DockInterface->Context,
|
|
PDS_UPDATE_DEFAULT
|
|
);
|
|
|
|
entry->DockInterface->InterfaceDereference(
|
|
entry->DockInterface->Context
|
|
);
|
|
}
|
|
|
|
PpDevNodeLockTree(PPL_TREEOP_ALLOW_READS);
|
|
|
|
RemoveEntryList( &entry->Link );
|
|
|
|
//
|
|
// Check if the RelationsList pointer in the context structure is NULL. If
|
|
// so, this means we were cancelled because this eject is part of a new
|
|
// larger eject. In that case all we want to do is unlink and free the
|
|
// context structure.
|
|
//
|
|
|
|
//
|
|
// Two interesting points about such code.
|
|
//
|
|
// 1) If you wait forever to complete an eject of a dock, we *wait* forever
|
|
// in the Query profile change state. No sneaky adding another dock. You
|
|
// must finish what you started...
|
|
// 2) Let's say you are ejecting a dock, and it is taking a long time. If
|
|
// you try to eject the parent, that eject will *not* grab this lower
|
|
// eject as we will block on the profile change semaphore. Again, finish
|
|
// what you started...
|
|
//
|
|
|
|
if (entry->RelationsList != NULL) {
|
|
|
|
if (entry->ProfileChangingEject) {
|
|
|
|
PpProfileMarkAllTransitioningDocksEjected();
|
|
}
|
|
|
|
IopInvalidateRelationsInList(
|
|
entry->RelationsList,
|
|
EjectDevice,
|
|
FALSE,
|
|
TRUE
|
|
);
|
|
|
|
//
|
|
// Free the relations list
|
|
//
|
|
|
|
IopFreeRelationList( entry->RelationsList );
|
|
|
|
} else {
|
|
|
|
entry->DisplaySafeRemovalDialog = FALSE;
|
|
}
|
|
|
|
PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS);
|
|
|
|
//
|
|
// Complete the event
|
|
//
|
|
if (entry->DeviceEvent != NULL ) {
|
|
|
|
PpCompleteDeviceEvent( entry->DeviceEvent, status );
|
|
}
|
|
|
|
if (entry->DisplaySafeRemovalDialog) {
|
|
|
|
PpSetDeviceRemovalSafe(entry->DeviceObject, NULL, NULL);
|
|
}
|
|
|
|
ObDereferenceObject(entry->DeviceObject);
|
|
ExFreePool( entry );
|
|
}
|
|
|
|
VOID
|
|
IopQueuePendingSurpriseRemoval(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PRELATION_LIST List,
|
|
IN ULONG Problem
|
|
)
|
|
{
|
|
PPENDING_RELATIONS_LIST_ENTRY entry;
|
|
|
|
PAGED_CODE();
|
|
|
|
entry = (PPENDING_RELATIONS_LIST_ENTRY) PiAllocateCriticalMemory(
|
|
SurpriseRemoveDevice,
|
|
NonPagedPool,
|
|
sizeof(PENDING_RELATIONS_LIST_ENTRY),
|
|
'rcpP'
|
|
);
|
|
|
|
ASSERT(entry != NULL);
|
|
|
|
entry->DeviceObject = DeviceObject;
|
|
entry->RelationsList = List;
|
|
entry->Problem = Problem;
|
|
entry->ProfileChangingEject = FALSE ;
|
|
|
|
KeEnterCriticalRegion();
|
|
ExAcquireResourceExclusiveLite(&IopSurpriseRemoveListLock, TRUE);
|
|
|
|
InsertTailList(&IopPendingSurpriseRemovals, &entry->Link);
|
|
|
|
ExReleaseResourceLite(&IopSurpriseRemoveListLock);
|
|
KeLeaveCriticalRegion();
|
|
}
|
|
|
|
VOID
|
|
IopUnlinkDeviceRemovalRelations(
|
|
IN PDEVICE_OBJECT RemovedDeviceObject,
|
|
IN OUT PRELATION_LIST RelationsList,
|
|
IN UNLOCK_UNLINK_ACTION UnlinkAction
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine unlocks the device tree deletion operation.
|
|
If there is any pending kernel deletion, this routine initiates
|
|
a worker thread to perform the work.
|
|
|
|
Arguments:
|
|
|
|
RemovedDeviceObject - Supplies a pointer to the device object to which the
|
|
remove was originally targetted (as opposed to one of the relations).
|
|
|
|
DeviceRelations - supplies a pointer to the device's removal relations.
|
|
|
|
UnlinkAction - Specifies which devnodes will be unlinked from the devnode
|
|
tree.
|
|
|
|
UnLinkRemovedDeviceNodes - Devnodes which are no longer enumerated and
|
|
have been sent a REMOVE_DEVICE IRP are unlinked.
|
|
|
|
UnlinkAllDeviceNodesPendingClose - This is used when a device is
|
|
surprise removed. Devnodes in RelationsList are unlinked from the
|
|
tree if they don't have children and aren't consuming any resources.
|
|
|
|
UnlinkOnlyChildDeviceNodesPendingClose - This is used when a device fails
|
|
while started. We unlink any child devnodes of the device which
|
|
failed but not the failed device's devnode.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
{
|
|
|
|
PDEVICE_NODE deviceNode;
|
|
PDEVICE_OBJECT deviceObject;
|
|
ULONG marker;
|
|
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER (RemovedDeviceObject);
|
|
|
|
PpDevNodeLockTree(PPL_TREEOP_BLOCK_READS_FROM_ALLOW);
|
|
|
|
if (ARGUMENT_PRESENT(RelationsList)) {
|
|
marker = 0;
|
|
while (IopEnumerateRelations( RelationsList,
|
|
&marker,
|
|
&deviceObject,
|
|
NULL,
|
|
NULL,
|
|
TRUE)) {
|
|
|
|
deviceNode = (PDEVICE_NODE)deviceObject->DeviceObjectExtension->DeviceNode;
|
|
|
|
//
|
|
// There are three different scenarios in which we want to unlink a
|
|
// devnode from the tree.
|
|
//
|
|
// 1) A devnode is no longer enumerated and has been sent a
|
|
// remove IRP.
|
|
//
|
|
// 2) A devnode has been surprise removed, has no children, has
|
|
// no resources or they've been freed. UnlinkAction will be
|
|
// UnlinkAllDeviceNodesPendingClose.
|
|
//
|
|
// 3) A devnode has failed and a surprise remove IRP has been sent.
|
|
// Then we want to remove children without resources but not the
|
|
// failed devnode itself. UnlinkAction will be
|
|
// UnlinkOnlyChildDeviceNodesPendingClose.
|
|
//
|
|
switch(UnlinkAction) {
|
|
|
|
case UnlinkRemovedDeviceNodes:
|
|
|
|
//
|
|
// Removes have been sent to every devnode in this relation
|
|
// list. Deconstruct the tree appropriately.
|
|
//
|
|
ASSERT(deviceNode->State != DeviceNodeDeletePendingCloses);
|
|
break;
|
|
|
|
case UnlinkAllDeviceNodesPendingClose:
|
|
|
|
ASSERT((deviceNode->State == DeviceNodeDeletePendingCloses) ||
|
|
(deviceNode->State == DeviceNodeDeleted));
|
|
break;
|
|
|
|
case UnlinkOnlyChildDeviceNodesPendingClose:
|
|
|
|
#if DBG
|
|
if (RemovedDeviceObject != deviceObject) {
|
|
|
|
ASSERT((deviceNode->State == DeviceNodeDeletePendingCloses) ||
|
|
(deviceNode->State == DeviceNodeDeleted));
|
|
} else {
|
|
|
|
ASSERT(deviceNode->State == DeviceNodeRemovePendingCloses);
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Deconstruct the tree appropriately.
|
|
//
|
|
if ((deviceNode->State == DeviceNodeDeletePendingCloses) ||
|
|
(deviceNode->State == DeviceNodeDeleted)) {
|
|
|
|
ASSERT(!(deviceNode->Flags & DNF_ENUMERATED));
|
|
|
|
//
|
|
// Remove the devnode from the tree.
|
|
//
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopUnlinkDeviceRemovalRelations: Cleaning up registry values, instance = %wZ\n",
|
|
&deviceNode->InstancePath));
|
|
|
|
PiLockPnpRegistry(TRUE);
|
|
|
|
IopCleanupDeviceRegistryValues(&deviceNode->InstancePath);
|
|
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"IopUnlinkDeviceRemovalRelations: Removing DevNode tree, DevNode = 0x%p\n",
|
|
deviceNode));
|
|
|
|
PpDevNodeRemoveFromTree(deviceNode);
|
|
|
|
PiUnlockPnpRegistry();
|
|
|
|
if (deviceNode->State == DeviceNodeDeleted) {
|
|
|
|
ASSERT(PipDoesDevNodeHaveProblem(deviceNode));
|
|
IopRemoveRelationFromList(RelationsList, deviceObject);
|
|
|
|
//
|
|
// Ashes to ashes
|
|
// Memory to freelist
|
|
//
|
|
ObDereferenceObject(deviceObject); // Added during Enum
|
|
} else {
|
|
|
|
//
|
|
// There is still one more ref on the device object, one
|
|
// holding it to the relation list. Once the final removes
|
|
// are sent the relationlist will be freed and then the
|
|
// final ref will be dropped.
|
|
//
|
|
ObDereferenceObject(deviceObject); // Added during Enum
|
|
}
|
|
|
|
} else {
|
|
|
|
ASSERT(deviceNode->Flags & DNF_ENUMERATED);
|
|
}
|
|
}
|
|
}
|
|
|
|
PpDevNodeUnlockTree(PPL_TREEOP_BLOCK_READS_FROM_ALLOW);
|
|
}
|
|
|
|
//
|
|
// The routines below are specific to kernel mode PnP configMgr.
|
|
//
|
|
NTSTATUS
|
|
IopUnloadAttachedDriver(
|
|
IN PDRIVER_OBJECT DriverObject
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function unloads the driver for the specified device object if it does not
|
|
control any other device object.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies a pointer to a device object
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS status;
|
|
PWCHAR buffer;
|
|
UNICODE_STRING unicodeName;
|
|
ULONG bufferSize;
|
|
PUNICODE_STRING serviceName = &DriverObject->DriverExtension->ServiceKeyName;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (DriverObject->DriverSection != NULL) {
|
|
|
|
if (DriverObject->DeviceObject == NULL) {
|
|
|
|
bufferSize = CmRegistryMachineSystemCurrentControlSetServices.Length +
|
|
serviceName->Length + sizeof(WCHAR) +
|
|
sizeof(L"\\");
|
|
buffer = (PWCHAR) ExAllocatePool(
|
|
PagedPool,
|
|
bufferSize);
|
|
if (!buffer) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
StringCbPrintfW(
|
|
buffer,
|
|
bufferSize,
|
|
L"%s\\%s",
|
|
CmRegistryMachineSystemCurrentControlSetServices.Buffer,
|
|
serviceName->Buffer);
|
|
RtlInitUnicodeString(&unicodeName, buffer);
|
|
status = IopUnloadDriver(&unicodeName, TRUE);
|
|
if (NT_SUCCESS(status)) {
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"****** Unloaded driver (%wZ)\n",
|
|
serviceName));
|
|
|
|
} else {
|
|
IopDbgPrint((IOP_LOADUNLOAD_WARNING_LEVEL,
|
|
"****** Error unloading driver (%wZ), status = 0x%08X\n",
|
|
serviceName,
|
|
status));
|
|
|
|
}
|
|
ExFreePool(unicodeName.Buffer);
|
|
}
|
|
else {
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"****** Skipping unload of driver (%wZ), DriverObject->DeviceObject != NULL\n",
|
|
serviceName));
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// This is a boot driver, can't be unloaded just return SUCCESS
|
|
//
|
|
IopDbgPrint((IOP_LOADUNLOAD_INFO_LEVEL,
|
|
"****** Skipping unload of boot driver (%wZ)\n",
|
|
serviceName));
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
PipRequestDeviceRemoval(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN BOOLEAN TreeDeletion,
|
|
IN ULONG Problem
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queues a work item to remove or delete a device.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device object to be cleaned up.
|
|
|
|
TreeDeletion - If TRUE, the devnode is physically missing and should
|
|
eventually end up in the deleted state. If FALSE, the
|
|
stack just needs to be torn down.
|
|
|
|
Problem - Problem code to assign to the removed stack.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
REMOVAL_WALK_CONTEXT removalWalkContext;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(DeviceNode != NULL);
|
|
|
|
if (DeviceNode) {
|
|
|
|
if (DeviceNode->InstancePath.Length == 0) {
|
|
|
|
IopDbgPrint((IOP_ERROR_LEVEL, "Driver %wZ reported child %p missing right after enumerating it!\n", &DeviceNode->Parent->ServiceName, DeviceNode));
|
|
ASSERT(DeviceNode->InstancePath.Length != 0);
|
|
}
|
|
|
|
PPDEVNODE_ASSERT_LOCK_HELD(PPL_TREEOP_ALLOW_READS);
|
|
|
|
removalWalkContext.TreeDeletion = TreeDeletion;
|
|
removalWalkContext.DescendantNode = FALSE;
|
|
|
|
status = PipRequestDeviceRemovalWorker(
|
|
DeviceNode,
|
|
(PVOID) &removalWalkContext
|
|
);
|
|
|
|
ASSERT(NT_SUCCESS(status));
|
|
|
|
//
|
|
// Queue the event, we'll return immediately after it's queued.
|
|
//
|
|
PpSetTargetDeviceRemove(
|
|
DeviceNode->PhysicalDeviceObject,
|
|
TRUE,
|
|
TRUE,
|
|
FALSE,
|
|
FALSE,
|
|
Problem,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
PipRequestDeviceRemovalWorker(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is a worker routine for PipRequestDeviceRemoval routine. It
|
|
is used to mark an entire subtree for removal.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node to mark.
|
|
|
|
Context - Points to a boolean that indicates whether the removal is
|
|
physical or stack specific.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS value.
|
|
|
|
--*/
|
|
{
|
|
PREMOVAL_WALK_CONTEXT removalWalkContext;
|
|
PNP_DEVNODE_STATE sentinelState;
|
|
|
|
PAGED_CODE();
|
|
|
|
removalWalkContext = (PREMOVAL_WALK_CONTEXT) Context;
|
|
|
|
switch(DeviceNode->State) {
|
|
|
|
case DeviceNodeUninitialized:
|
|
ASSERT(removalWalkContext->TreeDeletion);
|
|
break;
|
|
|
|
case DeviceNodeInitialized:
|
|
//
|
|
// This can happen on a non-descendant node if it fails AddDevice.
|
|
//
|
|
break;
|
|
|
|
case DeviceNodeDriversAdded:
|
|
//
|
|
// Happens when a parent stops enumerating a kid who had a
|
|
// resource conflict. This can also happen if AddDevice fails when
|
|
// a lower filter is attached but the service fails.
|
|
//
|
|
break;
|
|
|
|
case DeviceNodeResourcesAssigned:
|
|
//
|
|
// Happens when a parent stops enumerating a kid who has been
|
|
// assigned resources but hadn't yet been started.
|
|
//
|
|
ASSERT(removalWalkContext->TreeDeletion);
|
|
break;
|
|
|
|
case DeviceNodeStartPending:
|
|
//
|
|
// Not implemented yet.
|
|
//
|
|
ASSERT(0);
|
|
break;
|
|
|
|
case DeviceNodeStartCompletion:
|
|
case DeviceNodeStartPostWork:
|
|
//
|
|
// These are operational states for taking Added to Started. No
|
|
// descendant should be in this state as the engine currently
|
|
// finishes these before progressing to the next node.
|
|
//
|
|
// Note that DeviceNodeStartPostWork can occur on a legacy added
|
|
// root enumerated devnode. Since the root itself cannot disappear
|
|
// or be removed the below asserts still hold true.
|
|
//
|
|
// ISSUE - 2000/08/12 - ADRIAO: IoReportResourceUsage sync problems
|
|
//
|
|
ASSERT(!removalWalkContext->DescendantNode);
|
|
ASSERT(!removalWalkContext->TreeDeletion);
|
|
break;
|
|
|
|
case DeviceNodeStarted:
|
|
break;
|
|
|
|
case DeviceNodeQueryStopped:
|
|
//
|
|
// Internal rebalance engine state, should never be seen.
|
|
//
|
|
ASSERT(0);
|
|
break;
|
|
|
|
case DeviceNodeStopped:
|
|
ASSERT(removalWalkContext->DescendantNode);
|
|
ASSERT(removalWalkContext->TreeDeletion);
|
|
break;
|
|
|
|
case DeviceNodeRestartCompletion:
|
|
//
|
|
// This is an operational state for taking Stopped to Started. No
|
|
// descendant should be in this state as the engine currently
|
|
// finishes these before progressing to the next node.
|
|
//
|
|
ASSERT(!removalWalkContext->DescendantNode);
|
|
ASSERT(!removalWalkContext->TreeDeletion);
|
|
break;
|
|
|
|
case DeviceNodeEnumeratePending:
|
|
//
|
|
// Not implemented yet.
|
|
//
|
|
ASSERT(0);
|
|
break;
|
|
|
|
case DeviceNodeAwaitingQueuedRemoval:
|
|
case DeviceNodeAwaitingQueuedDeletion:
|
|
|
|
//
|
|
// ISSUE - 2000/08/30 - ADRIAO: Excessive reenum race
|
|
// Here we hit a case where we didn't flush the removes in the
|
|
// queue due to excessive enumeration. Flushing the last removes
|
|
// is problematic as they themselves will queue up enums! Until a
|
|
// better solution is found, we convert the state here. Bleargh!!!
|
|
// Note that this can also happen because PipDeviceActionWorker
|
|
// doesn't flush enums in the case of failed
|
|
// PipProcessQueryDeviceState or PipCallDriverAddDevice!
|
|
//
|
|
ASSERT(removalWalkContext->TreeDeletion);
|
|
//ASSERT(0);
|
|
PipRestoreDevNodeState(DeviceNode);
|
|
PipSetDevNodeState(DeviceNode, DeviceNodeAwaitingQueuedDeletion, NULL);
|
|
return STATUS_SUCCESS;
|
|
|
|
case DeviceNodeRemovePendingCloses:
|
|
case DeviceNodeRemoved:
|
|
ASSERT(removalWalkContext->TreeDeletion);
|
|
break;
|
|
|
|
case DeviceNodeEnumerateCompletion:
|
|
case DeviceNodeQueryRemoved:
|
|
case DeviceNodeDeletePendingCloses:
|
|
case DeviceNodeDeleted:
|
|
case DeviceNodeUnspecified:
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Give the devnode a sentinel state that will keep the start/enum engine
|
|
// at bay until the removal engine processes the tree.
|
|
//
|
|
sentinelState = (removalWalkContext->TreeDeletion) ?
|
|
DeviceNodeAwaitingQueuedDeletion :
|
|
DeviceNodeAwaitingQueuedRemoval;
|
|
|
|
PipSetDevNodeState(DeviceNode, sentinelState, NULL);
|
|
|
|
//
|
|
// All subsequent nodes are descendants, and all subsequent removals are
|
|
// deletions.
|
|
//
|
|
removalWalkContext->DescendantNode = TRUE;
|
|
removalWalkContext->TreeDeletion = TRUE;
|
|
|
|
return PipForAllChildDeviceNodes(
|
|
DeviceNode,
|
|
PipRequestDeviceRemovalWorker,
|
|
(PVOID) removalWalkContext
|
|
);
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
PipIsBeingRemovedSafely(
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function looks at a device with a physical remove queued against it
|
|
and indicates whether it is safe to remove.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node to examine. The devnode
|
|
should be in the DeviceNodeAwaitingQueuedDeletion state.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE iff the devnode is safe to be removed.
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ASSERT(DeviceNode->State == DeviceNodeAwaitingQueuedDeletion);
|
|
|
|
if (IopDeviceNodeFlagsToCapabilities(DeviceNode)->SurpriseRemovalOK) {
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if ((DeviceNode->PreviousState != DeviceNodeStarted) &&
|
|
(DeviceNode->PreviousState != DeviceNodeStopped) &&
|
|
(DeviceNode->PreviousState != DeviceNodeStartPostWork) &&
|
|
(DeviceNode->PreviousState != DeviceNodeRestartCompletion)) {
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|