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.
1326 lines
36 KiB
1326 lines
36 KiB
/*++
|
|
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
devnode.c
|
|
|
|
Abstract:
|
|
|
|
This file contains routines to maintain our private device node list.
|
|
|
|
Author:
|
|
|
|
Forrest Foltz (forrestf) 27-Mar-1996
|
|
|
|
Revision History:
|
|
|
|
Modified for nt kernel.
|
|
|
|
--*/
|
|
|
|
#include "pnpmgrp.h"
|
|
|
|
//
|
|
// Internal definitions
|
|
//
|
|
|
|
typedef struct _ENUM_CONTEXT{
|
|
PENUM_CALLBACK CallersCallback;
|
|
PVOID CallersContext;
|
|
} ENUM_CONTEXT, *PENUM_CONTEXT;
|
|
|
|
//
|
|
// Internal References
|
|
//
|
|
|
|
NTSTATUS
|
|
PipForAllDeviceNodesCallback(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PVOID Context
|
|
);
|
|
|
|
BOOLEAN
|
|
PipAreDriversLoadedWorker(
|
|
IN PNP_DEVNODE_STATE CurrentNodeState,
|
|
IN PNP_DEVNODE_STATE PreviousNodeState
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, PipAreDriversLoaded)
|
|
#pragma alloc_text(PAGE, PipAreDriversLoadedWorker)
|
|
#pragma alloc_text(PAGE, PipAllocateDeviceNode)
|
|
#pragma alloc_text(PAGE, PipForAllDeviceNodes)
|
|
#pragma alloc_text(PAGE, PipForDeviceNodeSubtree)
|
|
#pragma alloc_text(PAGE, PipForAllChildDeviceNodes)
|
|
#pragma alloc_text(PAGE, PipForAllDeviceNodesCallback)
|
|
#pragma alloc_text(PAGE, IopDestroyDeviceNode)
|
|
#pragma alloc_text(PAGE, PpDevNodeLockTree)
|
|
#pragma alloc_text(PAGE, PpDevNodeUnlockTree)
|
|
#pragma alloc_text(PAGE, PipIsProblemReadonly)
|
|
#pragma alloc_text(PAGE, PipIsDevNodeDNStarted)
|
|
#pragma alloc_text(PAGE, PipSetDevNodeProblem)
|
|
#pragma alloc_text(PAGE, PipClearDevNodeProblem)
|
|
//#pragma alloc_text(NONPAGE, PpDevNodeInsertIntoTree)
|
|
//#pragma alloc_text(NONPAGE, PpDevNodeRemoveFromTree)
|
|
//#pragma alloc_text(NONPAGE, PipRestoreDevNodeState)
|
|
//#pragma alloc_text(NONPAGE, PipSetDevNodeState)
|
|
#if DBG
|
|
#pragma alloc_text(PAGE, PpDevNodeAssertLockLevel)
|
|
#endif // DBG
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
|
|
BOOLEAN
|
|
PipAreDriversLoaded(
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines whether a devnode should be treated as if it has
|
|
drivers attached to the PDO's stack (ie it's been added.)
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Device node to examine.
|
|
|
|
Return Value:
|
|
|
|
TRUE if drivers are loaded, FALSE otherwise.
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
return PipAreDriversLoadedWorker(
|
|
DeviceNode->State,
|
|
DeviceNode->PreviousState
|
|
);
|
|
}
|
|
|
|
BOOLEAN
|
|
PipAreDriversLoadedWorker(
|
|
IN PNP_DEVNODE_STATE CurrentNodeState,
|
|
IN PNP_DEVNODE_STATE PreviousNodeState
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines whether a devnode should be treated as if it has
|
|
drivers attached to the PDO's stack (ie it's been added.)
|
|
|
|
Arguments:
|
|
|
|
CurrentNodeState - Current state of device node to examine.
|
|
|
|
PreviousNodeState - Previous state of device node to examine.
|
|
|
|
Return Value:
|
|
|
|
TRUE if drivers are loaded, FALSE otherwise.
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
switch(CurrentNodeState) {
|
|
|
|
case DeviceNodeDriversAdded:
|
|
case DeviceNodeResourcesAssigned:
|
|
case DeviceNodeStartCompletion:
|
|
case DeviceNodeStartPostWork:
|
|
case DeviceNodeStarted:
|
|
case DeviceNodeQueryStopped:
|
|
case DeviceNodeStopped:
|
|
case DeviceNodeRestartCompletion:
|
|
case DeviceNodeEnumerateCompletion:
|
|
case DeviceNodeQueryRemoved:
|
|
case DeviceNodeRemovePendingCloses:
|
|
case DeviceNodeDeletePendingCloses:
|
|
case DeviceNodeAwaitingQueuedRemoval:
|
|
return TRUE;
|
|
|
|
case DeviceNodeAwaitingQueuedDeletion:
|
|
return PipAreDriversLoadedWorker(
|
|
PreviousNodeState,
|
|
DeviceNodeUnspecified
|
|
);
|
|
|
|
case DeviceNodeUninitialized:
|
|
case DeviceNodeInitialized:
|
|
case DeviceNodeRemoved:
|
|
return FALSE;
|
|
|
|
case DeviceNodeDeleted:
|
|
//
|
|
// This can be seen by user mode because we defer delinking devices
|
|
// from the tree during removal.
|
|
//
|
|
return FALSE;
|
|
|
|
case DeviceNodeStartPending:
|
|
case DeviceNodeEnumeratePending:
|
|
case DeviceNodeUnspecified:
|
|
default:
|
|
ASSERT(0);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOLEAN
|
|
PipIsDevNodeDNStarted(
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine takes a devnode and determines whether the devnode should
|
|
have the user mode DN_STARTED bit set.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Device node to examine.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the devnode should be considered started, FALSE otherwise.
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
switch (DeviceNode->State) {
|
|
|
|
case DeviceNodeStartPending:
|
|
case DeviceNodeStartCompletion:
|
|
case DeviceNodeStartPostWork:
|
|
case DeviceNodeStarted:
|
|
case DeviceNodeQueryStopped:
|
|
case DeviceNodeEnumeratePending:
|
|
case DeviceNodeEnumerateCompletion:
|
|
case DeviceNodeStopped:
|
|
case DeviceNodeRestartCompletion:
|
|
return TRUE;
|
|
|
|
case DeviceNodeUninitialized:
|
|
case DeviceNodeInitialized:
|
|
case DeviceNodeDriversAdded:
|
|
case DeviceNodeResourcesAssigned:
|
|
case DeviceNodeRemoved:
|
|
case DeviceNodeQueryRemoved:
|
|
case DeviceNodeRemovePendingCloses:
|
|
case DeviceNodeDeletePendingCloses:
|
|
case DeviceNodeAwaitingQueuedRemoval:
|
|
case DeviceNodeAwaitingQueuedDeletion:
|
|
return FALSE;
|
|
|
|
case DeviceNodeDeleted:
|
|
//
|
|
// This can be seen by user mode because we defer delinking devices
|
|
// from the tree during removal.
|
|
//
|
|
return FALSE;
|
|
|
|
case DeviceNodeUnspecified:
|
|
default:
|
|
ASSERT(0);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
PipClearDevNodeProblem(
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DeviceNode->Flags &= ~DNF_HAS_PROBLEM;
|
|
DeviceNode->Problem = 0;
|
|
}
|
|
|
|
VOID
|
|
PipSetDevNodeProblem(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN ULONG Problem
|
|
)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ASSERT(DeviceNode->State != DeviceNodeUninitialized || !(DeviceNode->Flags & DNF_ENUMERATED) || Problem == CM_PROB_INVALID_DATA);
|
|
ASSERT(DeviceNode->State != DeviceNodeStarted);
|
|
ASSERT(Problem != 0);
|
|
DeviceNode->Flags |= DNF_HAS_PROBLEM; \
|
|
DeviceNode->Problem = Problem;
|
|
}
|
|
|
|
VOID
|
|
PipSetDevNodeState(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PNP_DEVNODE_STATE State,
|
|
OUT PNP_DEVNODE_STATE *OldState OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets a devnodes state and optional returns the prior state.
|
|
The prior state is saved and can be restored via PipRestoreDevNodeState.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Device node to update state.
|
|
|
|
State - State to place devnode in.
|
|
|
|
OldState - Optionally receives prior state of devnode.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PNP_DEVNODE_STATE previousState;
|
|
KIRQL oldIrql;
|
|
|
|
ASSERT(State != DeviceNodeQueryStopped || DeviceNode->State == DeviceNodeStarted);
|
|
|
|
#if DBG
|
|
if ((State == DeviceNodeDeleted) ||
|
|
(State == DeviceNodeDeletePendingCloses)) {
|
|
|
|
ASSERT(!(DeviceNode->Flags & DNF_ENUMERATED));
|
|
}
|
|
#endif
|
|
|
|
KeAcquireSpinLock(&IopPnPSpinLock, &oldIrql);
|
|
|
|
previousState = DeviceNode->State;
|
|
if (DeviceNode->State != State) {
|
|
|
|
//
|
|
// Update the devnode's current and previous state.
|
|
//
|
|
DeviceNode->State = State;
|
|
DeviceNode->PreviousState = previousState;
|
|
|
|
//
|
|
// Push prior state onto the history stack.
|
|
//
|
|
DeviceNode->StateHistory[DeviceNode->StateHistoryEntry] = previousState;
|
|
DeviceNode->StateHistoryEntry++;
|
|
DeviceNode->StateHistoryEntry %= STATE_HISTORY_SIZE;
|
|
}
|
|
|
|
KeReleaseSpinLock(&IopPnPSpinLock, oldIrql);
|
|
|
|
IopDbgPrint((IOP_INFO_LEVEL,
|
|
"%wZ: %s => %s\n",
|
|
&DeviceNode->InstancePath,
|
|
PP_DEVNODESTATE_NAME(previousState),
|
|
PP_DEVNODESTATE_NAME(State)));
|
|
|
|
if (ARGUMENT_PRESENT(OldState)) {
|
|
|
|
*OldState = previousState;
|
|
}
|
|
if (State == DeviceNodeDeleted) {
|
|
|
|
PpRemoveDeviceActionRequests(DeviceNode->PhysicalDeviceObject);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
PipRestoreDevNodeState(
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine restores a devnodes state to the state pushed by the last
|
|
PipSetDevNodeState call. This function can only be called once for each
|
|
call to PipSetDevNodeState.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Device node to restore state.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PNP_DEVNODE_STATE previousState, newState;
|
|
KIRQL oldIrql;
|
|
|
|
ASSERT((DeviceNode->State == DeviceNodeQueryRemoved) ||
|
|
(DeviceNode->State == DeviceNodeQueryStopped) ||
|
|
(DeviceNode->State == DeviceNodeAwaitingQueuedRemoval) ||
|
|
(DeviceNode->State == DeviceNodeAwaitingQueuedDeletion));
|
|
|
|
KeAcquireSpinLock(&IopPnPSpinLock, &oldIrql);
|
|
|
|
//
|
|
// Update the devnode's state.
|
|
//
|
|
previousState = DeviceNode->State;
|
|
newState = DeviceNode->State = DeviceNode->PreviousState;
|
|
|
|
//
|
|
// Push the old state onto the history stack.
|
|
//
|
|
DeviceNode->StateHistory[DeviceNode->StateHistoryEntry] = previousState;
|
|
DeviceNode->StateHistoryEntry++;
|
|
DeviceNode->StateHistoryEntry %= STATE_HISTORY_SIZE;
|
|
|
|
#if DBG
|
|
//
|
|
// Put a sentinel on the stack - restoring twice is a bug.
|
|
//
|
|
DeviceNode->PreviousState = DeviceNodeUnspecified;
|
|
#endif
|
|
|
|
KeReleaseSpinLock(&IopPnPSpinLock, oldIrql);
|
|
|
|
IopDbgPrint((IOP_INFO_LEVEL,
|
|
"%wZ: %s => %s\n",
|
|
&DeviceNode->InstancePath,
|
|
PP_DEVNODESTATE_NAME(previousState),
|
|
PP_DEVNODESTATE_NAME(newState)));
|
|
}
|
|
|
|
BOOLEAN
|
|
PipIsProblemReadonly(
|
|
IN ULONG Problem
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns TRUE if the specified CM_PROB code cannot be cleared
|
|
by user mode, FALSE otherwise.
|
|
|
|
Arguments:
|
|
|
|
Problem - CM_PROB_...
|
|
|
|
Return Value:
|
|
|
|
TRUE/FALSE.
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
switch(Problem) {
|
|
|
|
case CM_PROB_OUT_OF_MEMORY: // Nonresettable due to IoReportResourceUsage path.
|
|
case CM_PROB_NORMAL_CONFLICT:
|
|
case CM_PROB_PARTIAL_LOG_CONF:
|
|
case CM_PROB_DEVICE_NOT_THERE:
|
|
case CM_PROB_HARDWARE_DISABLED:
|
|
case CM_PROB_DISABLED_SERVICE:
|
|
case CM_PROB_TRANSLATION_FAILED:
|
|
case CM_PROB_NO_SOFTCONFIG:
|
|
case CM_PROB_BIOS_TABLE:
|
|
case CM_PROB_IRQ_TRANSLATION_FAILED:
|
|
case CM_PROB_DUPLICATE_DEVICE:
|
|
case CM_PROB_SYSTEM_SHUTDOWN:
|
|
case CM_PROB_HELD_FOR_EJECT:
|
|
case CM_PROB_REGISTRY_TOO_LARGE:
|
|
case CM_PROB_INVALID_DATA:
|
|
case CM_PROB_SETPROPERTIES_FAILED:
|
|
|
|
return TRUE;
|
|
|
|
case CM_PROB_FAILED_INSTALL:
|
|
case CM_PROB_FAILED_ADD:
|
|
case CM_PROB_FAILED_START:
|
|
case CM_PROB_NOT_CONFIGURED:
|
|
case CM_PROB_NEED_RESTART:
|
|
case CM_PROB_REINSTALL:
|
|
case CM_PROB_REGISTRY:
|
|
case CM_PROB_DISABLED:
|
|
case CM_PROB_FAILED_DRIVER_ENTRY:
|
|
case CM_PROB_DRIVER_FAILED_PRIOR_UNLOAD:
|
|
case CM_PROB_DRIVER_FAILED_LOAD:
|
|
case CM_PROB_DRIVER_SERVICE_KEY_INVALID:
|
|
case CM_PROB_LEGACY_SERVICE_NO_DEVICES:
|
|
case CM_PROB_HALTED:
|
|
case CM_PROB_FAILED_POST_START:
|
|
case CM_PROB_WILL_BE_REMOVED:
|
|
case CM_PROB_DRIVER_BLOCKED:
|
|
|
|
return FALSE;
|
|
|
|
case CM_PROB_PHANTOM:
|
|
|
|
//
|
|
// Should never see in kernel mode
|
|
//
|
|
|
|
case CM_PROB_DEVLOADER_FAILED:
|
|
case CM_PROB_DEVLOADER_NOT_FOUND:
|
|
case CM_PROB_REENUMERATION:
|
|
case CM_PROB_VXDLDR:
|
|
case CM_PROB_NOT_VERIFIED:
|
|
case CM_PROB_LIAR:
|
|
case CM_PROB_FAILED_FILTER:
|
|
case CM_PROB_MOVED:
|
|
case CM_PROB_TOO_EARLY:
|
|
case CM_PROB_NO_VALID_LOG_CONF:
|
|
case CM_PROB_UNKNOWN_RESOURCE:
|
|
case CM_PROB_ENTRY_IS_WRONG_TYPE:
|
|
case CM_PROB_LACKED_ARBITRATOR:
|
|
case CM_PROB_BOOT_CONFIG_CONFLICT:
|
|
case CM_PROB_DEVLOADER_NOT_READY:
|
|
case CM_PROB_CANT_SHARE_IRQ:
|
|
|
|
//
|
|
// Win9x specific
|
|
//
|
|
|
|
default:
|
|
ASSERT(0);
|
|
|
|
//
|
|
// We return TRUE in this path because that prevents these problems
|
|
// from being set on devnodes (SetDeviceProblem won't allow usage
|
|
// of ReadOnly problems)
|
|
//
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
PipAllocateDeviceNode(
|
|
IN PDEVICE_OBJECT PhysicalDeviceObject,
|
|
OUT PDEVICE_NODE *DeviceNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function allocates a device node from nonpaged pool and initializes
|
|
the fields which do not require to hold lock to do so. Since adding
|
|
the device node to pnp mgr's device node tree requires acquiring lock,
|
|
this routine does not add the device node to device node tree.
|
|
|
|
Arguments:
|
|
|
|
PhysicalDeviceObject - Supplies a pointer to its corresponding physical device
|
|
object.
|
|
|
|
Return Value:
|
|
|
|
a pointer to the newly created device node. Null is returned if failed.
|
|
|
|
--*/
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
*DeviceNode = ExAllocatePoolWithTag(
|
|
NonPagedPool,
|
|
sizeof(DEVICE_NODE),
|
|
IOP_DNOD_TAG
|
|
);
|
|
|
|
if (*DeviceNode == NULL ){
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
InterlockedIncrement((LONG *)&IopNumberDeviceNodes);
|
|
|
|
RtlZeroMemory(*DeviceNode, sizeof(DEVICE_NODE));
|
|
|
|
(*DeviceNode)->InterfaceType = InterfaceTypeUndefined;
|
|
(*DeviceNode)->BusNumber = (ULONG)-1;
|
|
(*DeviceNode)->ChildInterfaceType = InterfaceTypeUndefined;
|
|
(*DeviceNode)->ChildBusNumber = (ULONG)-1;
|
|
(*DeviceNode)->ChildBusTypeIndex = (USHORT)-1;
|
|
(*DeviceNode)->State = DeviceNodeUninitialized;
|
|
(*DeviceNode)->DisableableDepends = 0;
|
|
|
|
PpHotSwapInitRemovalPolicy(*DeviceNode);
|
|
|
|
InitializeListHead(&(*DeviceNode)->DeviceArbiterList);
|
|
InitializeListHead(&(*DeviceNode)->DeviceTranslatorList);
|
|
|
|
if (PhysicalDeviceObject){
|
|
|
|
(*DeviceNode)->PhysicalDeviceObject = PhysicalDeviceObject;
|
|
PhysicalDeviceObject->DeviceObjectExtension->DeviceNode = (PVOID)*DeviceNode;
|
|
PhysicalDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
|
|
}
|
|
|
|
InitializeListHead(&(*DeviceNode)->TargetDeviceNotify);
|
|
InitializeListHead(&(*DeviceNode)->DockInfo.ListEntry);
|
|
InitializeListHead(&(*DeviceNode)->PendedSetInterfaceState);
|
|
InitializeListHead(&(*DeviceNode)->LegacyBusListEntry);
|
|
|
|
if (PpSystemHiveTooLarge) {
|
|
|
|
//
|
|
// FUTURE: Make this an informational status, as that's how it is used.
|
|
//
|
|
return STATUS_SYSTEM_HIVE_TOO_LARGE;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
PipForAllDeviceNodes(
|
|
IN PENUM_CALLBACK Callback,
|
|
IN PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function walks the device node tree and invokes the caller specified
|
|
'Callback' function for each device node.
|
|
|
|
Note, this routine (or its worker routine) traverses the tree in a top
|
|
down manner.
|
|
|
|
Arguments:
|
|
|
|
Callback - Supplies the call back routine for each device node.
|
|
|
|
Context - Supplies a parameter/context for the callback function.
|
|
|
|
Return Value:
|
|
|
|
Status returned from Callback, if not successfull then the tree walking stops.
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
return PipForDeviceNodeSubtree(IopRootDeviceNode, Callback, Context);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
PipForDeviceNodeSubtree(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PENUM_CALLBACK Callback,
|
|
IN PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function walks the device node tree under but not including the passed
|
|
in device node and perform caller specified 'Callback' function for each
|
|
device node.
|
|
|
|
Note, this routine (or its worker routine) traverses the tree in a top
|
|
down manner.
|
|
|
|
Arguments:
|
|
|
|
Callback - Supplies the call back routine for each device node.
|
|
|
|
Context - Supplies a parameter/context for the callback function.
|
|
|
|
Return Value:
|
|
|
|
Status returned from Callback, if not successfull then the tree walking stops.
|
|
|
|
--*/
|
|
{
|
|
ENUM_CONTEXT enumContext;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
enumContext.CallersCallback = Callback;
|
|
enumContext.CallersContext = Context;
|
|
|
|
//
|
|
// Start with a pointer to the root device node, recursively examine all the
|
|
// children until we the callback function says stop or we've looked at all
|
|
// of them.
|
|
//
|
|
PpDevNodeLockTree(PPL_SIMPLE_READ);
|
|
|
|
status = PipForAllChildDeviceNodes(DeviceNode,
|
|
PipForAllDeviceNodesCallback,
|
|
(PVOID)&enumContext );
|
|
|
|
|
|
PpDevNodeUnlockTree(PPL_SIMPLE_READ);
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
PipForAllChildDeviceNodes(
|
|
IN PDEVICE_NODE Parent,
|
|
IN PENUM_CALLBACK Callback,
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function walks the Parent's device node subtree and perform caller specified
|
|
'Callback' function for each device node under Parent.
|
|
|
|
Note, befor calling this rotuine, callers must acquire the enumeration mutex
|
|
of the 'Parent' device node to make sure its children won't go away unless the
|
|
call tells them to.
|
|
|
|
Arguments:
|
|
|
|
Parent - Supplies a pointer to the device node whose subtree is to be walked.
|
|
|
|
Callback - Supplies the call back routine for each device node.
|
|
|
|
Context - Supplies a parameter/context for the callback function.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS value.
|
|
|
|
--*/
|
|
|
|
{
|
|
PDEVICE_NODE nextChild = Parent->Child;
|
|
PDEVICE_NODE child;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Process siblings until we find the end of the sibling list or
|
|
// the Callback() returns FALSE. Set result = TRUE at the top of
|
|
// the loop so that if there are no siblings we will return TRUE,
|
|
// e.g. Keep Enumerating.
|
|
//
|
|
// Note, we need to find next child before calling Callback function
|
|
// in case the current child is deleted by the Callback function.
|
|
//
|
|
|
|
while (nextChild && NT_SUCCESS(status)) {
|
|
child = nextChild;
|
|
nextChild = child->Sibling;
|
|
status = Callback(child, Context);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
PipForAllDeviceNodesCallback(
|
|
IN PDEVICE_NODE DeviceNode,
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is the worker routine for PipForAllChildDeviceNodes routine.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node whose subtree is to be walked.
|
|
|
|
Context - Supplies a context which contains the caller specified call back
|
|
function and parameter.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS value.
|
|
|
|
--*/
|
|
|
|
{
|
|
PENUM_CONTEXT enumContext;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
enumContext = (PENUM_CONTEXT)Context;
|
|
|
|
//
|
|
// First call the caller's callback for this devnode
|
|
//
|
|
|
|
status =
|
|
enumContext->CallersCallback(DeviceNode, enumContext->CallersContext);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// Now enumerate the children, if any.
|
|
//
|
|
if (DeviceNode->Child) {
|
|
|
|
status = PipForAllChildDeviceNodes(
|
|
DeviceNode,
|
|
PipForAllDeviceNodesCallback,
|
|
Context);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
VOID
|
|
IopDestroyDeviceNode(
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is invoked by IopDeleteDevice to clean up the device object's
|
|
device node structure.
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Supplies a pointer to the device node whose subtree is to be walked.
|
|
|
|
Context - Supplies a context which contains the caller specified call back
|
|
function and parameter.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS value.
|
|
|
|
--*/
|
|
|
|
{
|
|
#if DBG
|
|
PDEVICE_OBJECT dbgDeviceObject;
|
|
#endif
|
|
|
|
PAGED_CODE();
|
|
|
|
if (DeviceNode) {
|
|
|
|
if ((DeviceNode->PhysicalDeviceObject->Flags & DO_BUS_ENUMERATED_DEVICE) &&
|
|
DeviceNode->Parent != NULL) {
|
|
|
|
PP_SAVE_DEVNODE_TO_TRIAGE_DUMP(DeviceNode);
|
|
KeBugCheckEx( PNP_DETECTED_FATAL_ERROR,
|
|
PNP_ERR_ACTIVE_PDO_FREED,
|
|
(ULONG_PTR)DeviceNode->PhysicalDeviceObject,
|
|
0,
|
|
0);
|
|
}
|
|
if (DeviceNode->Flags & DNF_LEGACY_RESOURCE_DEVICENODE) {
|
|
//
|
|
// Release the resources this device consumes (the devicenode will
|
|
// get deleted after the release). Basically cleanup after bad
|
|
// (legacy) drivers.
|
|
//
|
|
IopLegacyResourceAllocation( ArbiterRequestUndefined,
|
|
IoPnpDriverObject,
|
|
DeviceNode->PhysicalDeviceObject,
|
|
NULL,
|
|
NULL);
|
|
return;
|
|
}
|
|
|
|
#if DBG
|
|
|
|
//
|
|
// If Only Parent is NOT NULL, most likely the driver forgot to
|
|
// release resources before deleting its FDO. (The driver previously
|
|
// call legacy assign resource interface.)
|
|
//
|
|
|
|
ASSERT(DeviceNode->Child == NULL &&
|
|
DeviceNode->Sibling == NULL &&
|
|
DeviceNode->LastChild == NULL
|
|
);
|
|
|
|
ASSERT(DeviceNode->DockInfo.SerialNumber == NULL &&
|
|
IsListEmpty(&DeviceNode->DockInfo.ListEntry));
|
|
|
|
if (DeviceNode->PhysicalDeviceObject->Flags & DO_BUS_ENUMERATED_DEVICE) {
|
|
ASSERT (DeviceNode->Parent == 0);
|
|
}
|
|
|
|
if (DeviceNode->PreviousResourceList) {
|
|
ExFreePool(DeviceNode->PreviousResourceList);
|
|
}
|
|
if (DeviceNode->PreviousResourceRequirements) {
|
|
ExFreePool(DeviceNode->PreviousResourceRequirements);
|
|
}
|
|
|
|
//
|
|
// device should not appear to be not-disableable if/when we get here
|
|
// if either of these two lines ASSERT, email: jamiehun
|
|
//
|
|
|
|
ASSERT((DeviceNode->UserFlags & DNUF_NOT_DISABLEABLE) == 0);
|
|
ASSERT(DeviceNode->DisableableDepends == 0);
|
|
|
|
if (DeviceNode->InstancePath.Length) {
|
|
|
|
dbgDeviceObject = IopDeviceObjectFromDeviceInstance(&DeviceNode->InstancePath);
|
|
|
|
if (dbgDeviceObject) {
|
|
|
|
ASSERT(dbgDeviceObject != DeviceNode->PhysicalDeviceObject);
|
|
ObDereferenceObject(dbgDeviceObject);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
if (DeviceNode->DuplicatePDO) {
|
|
ObDereferenceObject(DeviceNode->DuplicatePDO);
|
|
}
|
|
if (DeviceNode->ServiceName.Length != 0) {
|
|
ExFreePool(DeviceNode->ServiceName.Buffer);
|
|
}
|
|
if (DeviceNode->InstancePath.Length != 0) {
|
|
ExFreePool(DeviceNode->InstancePath.Buffer);
|
|
}
|
|
if (DeviceNode->ResourceRequirements) {
|
|
ExFreePool(DeviceNode->ResourceRequirements);
|
|
}
|
|
//
|
|
// Dereference all the arbiters and translators on this PDO.
|
|
//
|
|
IopUncacheInterfaceInformation(DeviceNode->PhysicalDeviceObject) ;
|
|
|
|
//
|
|
// Release any pended IoSetDeviceInterface structures
|
|
//
|
|
|
|
while (!IsListEmpty(&DeviceNode->PendedSetInterfaceState)) {
|
|
|
|
PPENDING_SET_INTERFACE_STATE entry;
|
|
|
|
entry = (PPENDING_SET_INTERFACE_STATE)RemoveHeadList(&DeviceNode->PendedSetInterfaceState);
|
|
|
|
ExFreePool(entry->LinkName.Buffer);
|
|
|
|
ExFreePool(entry);
|
|
}
|
|
|
|
DeviceNode->PhysicalDeviceObject->DeviceObjectExtension->DeviceNode = NULL;
|
|
ExFreePool(DeviceNode);
|
|
InterlockedDecrement((LONG *)&IopNumberDeviceNodes);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
PpDevNodeInsertIntoTree(
|
|
IN PDEVICE_NODE ParentNode,
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called to insert a new devnode into the device tree.
|
|
|
|
Note that there are two classes of callers:
|
|
PnP callers
|
|
Legacy callers
|
|
|
|
All PnP callers hold the device tree lock. Legacy callers however come in
|
|
with no locks, as they might be brought into being due to a PnP event. To
|
|
deal with the later case, inserts are atomic and legacy callers can never
|
|
remove themselves from the tree.
|
|
|
|
Arguments:
|
|
|
|
ParentNode - Supplies a pointer to the device node's parent
|
|
|
|
DeviceNode - Supplies a pointer to the device node which needs to be
|
|
inserted into the tree.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG depth;
|
|
KIRQL oldIrql;
|
|
|
|
//
|
|
// Acquire spinlock to deal with legacy/PnP synchronization.
|
|
//
|
|
KeAcquireSpinLock(&IopPnPSpinLock, &oldIrql);
|
|
|
|
//
|
|
// Determine the depth of the devnode.
|
|
//
|
|
depth = ParentNode->Level + 1;
|
|
DeviceNode->Level = depth;
|
|
|
|
//
|
|
// Update the maximum depth of the tree.
|
|
//
|
|
if (depth > IopMaxDeviceNodeLevel) {
|
|
IopMaxDeviceNodeLevel = depth;
|
|
}
|
|
|
|
//
|
|
// Put this devnode at the end of the parent's list of children. Note that
|
|
// the Child/Sibling fields are really the last things to be updated. This
|
|
// has to be done as walkers of the tree hold no locks that protect the
|
|
// tree from legacy inserts.
|
|
//
|
|
DeviceNode->Parent = ParentNode;
|
|
KeMemoryBarrier();
|
|
if (ParentNode->LastChild) {
|
|
ASSERT(ParentNode->LastChild->Sibling == NULL);
|
|
ParentNode->LastChild->Sibling = DeviceNode;
|
|
ParentNode->LastChild = DeviceNode;
|
|
} else {
|
|
ASSERT(ParentNode->Child == NULL);
|
|
ParentNode->Child = ParentNode->LastChild = DeviceNode;
|
|
}
|
|
|
|
KeReleaseSpinLock(&IopPnPSpinLock, oldIrql);
|
|
|
|
//
|
|
// Tree has changed
|
|
//
|
|
IoDeviceNodeTreeSequence += 1;
|
|
}
|
|
|
|
VOID
|
|
PpDevNodeRemoveFromTree(
|
|
IN PDEVICE_NODE DeviceNode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function removes the device node from the device node tree
|
|
|
|
Arguments:
|
|
|
|
DeviceNode - Device node to remove
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
PDEVICE_NODE *node;
|
|
KIRQL oldIrql;
|
|
|
|
//
|
|
// Acquire spinlock to deal with legacy/PnP synchronization.
|
|
//
|
|
KeAcquireSpinLock(&IopPnPSpinLock, &oldIrql);
|
|
|
|
//
|
|
// Unlink the pointer to this device node. (If this is the
|
|
// first entry, unlink it from the parents child pointer, else
|
|
// remove it from the sibling list)
|
|
//
|
|
|
|
node = &DeviceNode->Parent->Child;
|
|
while (*node != DeviceNode) {
|
|
node = &(*node)->Sibling;
|
|
}
|
|
*node = DeviceNode->Sibling;
|
|
|
|
if (DeviceNode->Parent->Child == NULL) {
|
|
DeviceNode->Parent->LastChild = NULL;
|
|
} else {
|
|
while (*node) {
|
|
node = &(*node)->Sibling;
|
|
}
|
|
DeviceNode->Parent->LastChild = CONTAINING_RECORD(node, DEVICE_NODE, Sibling);
|
|
}
|
|
|
|
KeReleaseSpinLock(&IopPnPSpinLock, oldIrql);
|
|
|
|
//
|
|
// Remove this device node from Legacy Bus information table.
|
|
//
|
|
IopRemoveLegacyBusDeviceNode(DeviceNode);
|
|
|
|
//
|
|
// Orphan any outstanding device change notifications on these nodes.
|
|
//
|
|
IopOrphanNotification(DeviceNode);
|
|
|
|
//
|
|
// No longer linked
|
|
//
|
|
DeviceNode->Parent = NULL;
|
|
DeviceNode->Child = NULL;
|
|
DeviceNode->Sibling = NULL;
|
|
DeviceNode->LastChild = NULL;
|
|
}
|
|
|
|
VOID
|
|
PpDevNodeLockTree(
|
|
IN PNP_LOCK_LEVEL LockLevel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function acquires the tree lock with the appropriate level of
|
|
restrictions.
|
|
|
|
Arguments:
|
|
|
|
LockLevel:
|
|
PPL_SIMPLE_READ - Allows simple examination of the tree.
|
|
|
|
PPL_TREEOP_ALLOW_READS - Called as part of a StartEnum/Remove/Power
|
|
operation, blocks other such operations.
|
|
Simple reads can go through however.
|
|
|
|
PPL_TREEOP_BLOCK_READS - Called as part of a StartEnum/Remove/Power
|
|
operation, blocks other such operations.
|
|
Simple reads are also blocked.
|
|
|
|
PPL_TREEOP_BLOCK_READS_FROM_ALLOW - Switch to PPL_TREEOP_BLOCK_READS
|
|
when already in
|
|
PPL_TREEOP_BLOCK_READS. Note that
|
|
PpDevNodeUnlockTree must be
|
|
subsequently called on both to
|
|
release.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG refCount, remainingCount;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Block any attempt to suspend the thread via user mode.
|
|
//
|
|
KeEnterCriticalRegion();
|
|
|
|
switch(LockLevel) {
|
|
|
|
case PPL_SIMPLE_READ:
|
|
ExAcquireSharedWaitForExclusive(&IopDeviceTreeLock, TRUE);
|
|
break;
|
|
|
|
case PPL_TREEOP_ALLOW_READS:
|
|
ExAcquireResourceExclusiveLite(&PiEngineLock, TRUE);
|
|
ExAcquireSharedWaitForExclusive(&IopDeviceTreeLock, TRUE);
|
|
break;
|
|
|
|
case PPL_TREEOP_BLOCK_READS:
|
|
ExAcquireResourceExclusiveLite(&PiEngineLock, TRUE);
|
|
ExAcquireResourceExclusiveLite(&IopDeviceTreeLock, TRUE);
|
|
break;
|
|
|
|
case PPL_TREEOP_BLOCK_READS_FROM_ALLOW:
|
|
|
|
//
|
|
// Drop the tree lock and require exclusive.
|
|
//
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&PiEngineLock));
|
|
|
|
//
|
|
// "Shared" is a subset of exclusive. ExIsResourceAcquiredShared
|
|
// will return nonzero if it's owned exclusive. We flush out that
|
|
// case here.
|
|
//
|
|
ASSERT(ExIsResourceAcquiredSharedLite(&IopDeviceTreeLock) &&
|
|
(!ExIsResourceAcquiredExclusiveLite(&IopDeviceTreeLock)));
|
|
|
|
//
|
|
// Drop the tree lock entirely.
|
|
//
|
|
refCount = ExIsResourceAcquiredSharedLite(&IopDeviceTreeLock);
|
|
for(remainingCount = refCount; remainingCount; remainingCount--) {
|
|
|
|
ExReleaseResourceLite(&IopDeviceTreeLock);
|
|
}
|
|
|
|
//
|
|
// Grab it exclusively while keeping the original count.
|
|
//
|
|
for(remainingCount = refCount; remainingCount; remainingCount--) {
|
|
|
|
ExAcquireResourceExclusiveLite(&IopDeviceTreeLock, TRUE);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
PpDevNodeUnlockTree(
|
|
IN PNP_LOCK_LEVEL LockLevel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function releases the tree lock with the appropriate level of
|
|
restrictions.
|
|
|
|
Arguments:
|
|
|
|
LockLevel:
|
|
PPL_SIMPLE_READ - Allows simple examination of the tree.
|
|
|
|
PPL_TREEOP_ALLOW_READS - Called as part of a StartEnum/Remove/Power
|
|
operation, blocks other such operations.
|
|
Simple reads can go through however.
|
|
|
|
PPL_TREEOP_BLOCK_READS - Called as part of a StartEnum/Remove/Power
|
|
operation, blocks other such operations.
|
|
Simple reads are also blocked.
|
|
|
|
PPL_TREEOP_BLOCK_READS_FROM_ALLOW - Switch to PPL_TREEOP_BLOCK_READS
|
|
when already in
|
|
PPL_TREEOP_BLOCK_READS. Note that
|
|
PpDevNodeUnlockTree must be
|
|
subsequently called on both to
|
|
release.
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PPDEVNODE_ASSERT_LOCK_HELD(LockLevel);
|
|
switch(LockLevel) {
|
|
|
|
case PPL_SIMPLE_READ:
|
|
ExReleaseResourceLite(&IopDeviceTreeLock);
|
|
break;
|
|
|
|
case PPL_TREEOP_ALLOW_READS:
|
|
ExReleaseResourceLite(&IopDeviceTreeLock);
|
|
ExReleaseResourceLite(&PiEngineLock);
|
|
break;
|
|
|
|
case PPL_TREEOP_BLOCK_READS:
|
|
ExReleaseResourceLite(&IopDeviceTreeLock);
|
|
ExReleaseResourceLite(&PiEngineLock);
|
|
break;
|
|
|
|
case PPL_TREEOP_BLOCK_READS_FROM_ALLOW:
|
|
//
|
|
// The engine lock should still be held here. Now we adjust the
|
|
// tree lock. Go back to allow by converting the exclusive lock to
|
|
// shared. Note that this doesn't chance the acquisition count.
|
|
//
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&IopDeviceTreeLock));
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&PiEngineLock));
|
|
ExConvertExclusiveToSharedLite(&IopDeviceTreeLock);
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
KeLeaveCriticalRegion();
|
|
}
|
|
|
|
#if DBG
|
|
VOID
|
|
PpDevNodeAssertLockLevel(
|
|
IN PNP_LOCK_LEVEL LockLevel,
|
|
IN PCSTR File,
|
|
IN ULONG Line
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This asserts the lock is currently held at the appropriate level.
|
|
|
|
Arguments:
|
|
|
|
LockLevel:
|
|
PPL_SIMPLE_READ - Allows simple examination of the tree.
|
|
|
|
PPL_TREEOP_ALLOW_READS - Called as part of a StartEnum/Remove/Power
|
|
operation, blocks other such operations.
|
|
Simple reads can go through however.
|
|
|
|
PPL_TREEOP_BLOCK_READS - Called as part of a StartEnum/Remove/Power
|
|
operation, blocks other such operations.
|
|
Simple reads are also blocked.
|
|
|
|
PPL_TREEOP_BLOCK_READS_FROM_ALLOW - Switch to PPL_TREEOP_BLOCK_READS
|
|
when already in
|
|
PPL_TREEOP_BLOCK_READS. Note that
|
|
PpDevNodeUnlockTree must be
|
|
subsequently called on both to
|
|
release.
|
|
|
|
File: Name of c-file asserting the lock is held.
|
|
|
|
Line: Line number in above c-file.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER (File);
|
|
UNREFERENCED_PARAMETER (Line);
|
|
|
|
switch(LockLevel) {
|
|
|
|
case PPL_SIMPLE_READ:
|
|
ASSERT(ExIsResourceAcquiredSharedLite(&IopDeviceTreeLock));
|
|
break;
|
|
|
|
case PPL_TREEOP_ALLOW_READS:
|
|
ASSERT(ExIsResourceAcquiredSharedLite(&IopDeviceTreeLock));
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&PiEngineLock));
|
|
break;
|
|
|
|
case PPL_TREEOP_BLOCK_READS_FROM_ALLOW:
|
|
//
|
|
// This isn't really a lock level, but this assert-o-matic function
|
|
// is called from Unlock, in which case this level means "drop back
|
|
// to PPL_TREEOP_ALLOW_READS *from* PPL_TREEOP_BLOCK_READS." So...
|
|
//
|
|
// Fall through
|
|
//
|
|
|
|
case PPL_TREEOP_BLOCK_READS:
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&IopDeviceTreeLock));
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&PiEngineLock));
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
#endif // DBG
|