mirror of
https://github.com/reactos/reactos.git
synced 2025-01-05 22:12:46 +00:00
91fceab36e
During the boot process, it makes possible to initalize the driver's devices right after the driver is loaded. Moreover, this way one can be sure that all critical devices are initialized before the IopMarkBootPartition call (because we explicitly call the driver's AddDevice routine now, after each driver is loaded) CORE-7826
2650 lines
85 KiB
C
2650 lines
85 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
|
* PURPOSE: PnP manager device manipulation functions
|
|
* COPYRIGHT: Casper S. Hornstrup (chorns@users.sourceforge.net)
|
|
* 2007 Hervé Poussineau (hpoussin@reactos.org)
|
|
* 2014-2017 Thomas Faber (thomas.faber@reactos.org)
|
|
* 2020 Victor Perevertkin (victor.perevertkin@reactos.org)
|
|
*/
|
|
|
|
/* Device tree is a resource shared among all system services: hal, kernel, drivers etc.
|
|
* Thus all code which interacts with the tree needs to be synchronized.
|
|
* Here it's done via a list of DEVICE_ACTION_REQUEST structures, which represents
|
|
* the device action queue. It is being processed exclusively by the PipDeviceActionWorker.
|
|
*
|
|
* Operation queuing can be done with the PiQueueDeviceAction function or with
|
|
* the PiPerfomSyncDeviceAction for synchronous operations.
|
|
* All device manipulation like starting, removing, enumeration (see DEVICE_ACTION enum)
|
|
* have to be done with the PiQueueDeviceAction in order to avoid race conditions.
|
|
*
|
|
* Note: there is one special operation here - PiActionEnumRootDevices. It is meant to be done
|
|
* during initialization process (and be the first device tree operation executed) and
|
|
* is always executed synchronously.
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* GLOBALS *******************************************************************/
|
|
|
|
extern ERESOURCE IopDriverLoadResource;
|
|
extern BOOLEAN PnpSystemInit;
|
|
extern PDEVICE_NODE IopRootDeviceNode;
|
|
extern BOOLEAN PnPBootDriversLoaded;
|
|
extern BOOLEAN PnPBootDriversInitialized;
|
|
|
|
#define MAX_DEVICE_ID_LEN 200
|
|
#define MAX_SEPARATORS_INSTANCEID 0
|
|
#define MAX_SEPARATORS_DEVICEID 1
|
|
|
|
/* DATA **********************************************************************/
|
|
|
|
LIST_ENTRY IopDeviceActionRequestList;
|
|
WORK_QUEUE_ITEM IopDeviceActionWorkItem;
|
|
BOOLEAN IopDeviceActionInProgress;
|
|
KSPIN_LOCK IopDeviceActionLock;
|
|
KEVENT PiEnumerationFinished;
|
|
static const WCHAR ServicesKeyName[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\";
|
|
|
|
#define TAG_PNP_DEVACTION 'aDpP'
|
|
|
|
/* TYPES *********************************************************************/
|
|
|
|
typedef struct _DEVICE_ACTION_REQUEST
|
|
{
|
|
LIST_ENTRY RequestListEntry;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
PKEVENT CompletionEvent;
|
|
NTSTATUS *CompletionStatus;
|
|
DEVICE_ACTION Action;
|
|
} DEVICE_ACTION_REQUEST, *PDEVICE_ACTION_REQUEST;
|
|
|
|
typedef enum _ADD_DEV_DRIVER_TYPE
|
|
{
|
|
LowerFilter,
|
|
LowerClassFilter,
|
|
DeviceDriver,
|
|
UpperFilter,
|
|
UpperClassFilter
|
|
} ADD_DEV_DRIVER_TYPE;
|
|
|
|
typedef struct _ADD_DEV_DRIVERS_LIST
|
|
{
|
|
LIST_ENTRY ListEntry;
|
|
PDRIVER_OBJECT DriverObject;
|
|
ADD_DEV_DRIVER_TYPE DriverType;
|
|
} ADD_DEV_DRIVERS_LIST, *PADD_DEV_DRIVERS_LIST;
|
|
|
|
typedef struct _ATTACH_FILTER_DRIVERS_CONTEXT
|
|
{
|
|
ADD_DEV_DRIVER_TYPE DriverType;
|
|
PDEVICE_NODE DeviceNode;
|
|
PLIST_ENTRY DriversListHead;
|
|
} ATTACH_FILTER_DRIVERS_CONTEXT, *PATTACH_FILTER_DRIVERS_CONTEXT;
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
PDEVICE_OBJECT
|
|
IopGetDeviceObjectFromDeviceInstance(PUNICODE_STRING DeviceInstance);
|
|
|
|
NTSTATUS
|
|
IopGetParentIdPrefix(PDEVICE_NODE DeviceNode, PUNICODE_STRING ParentIdPrefix);
|
|
|
|
USHORT
|
|
NTAPI
|
|
IopGetBusTypeGuidIndex(LPGUID BusTypeGuid);
|
|
|
|
NTSTATUS
|
|
IopSetDeviceInstanceData(HANDLE InstanceKey, PDEVICE_NODE DeviceNode);
|
|
|
|
VOID
|
|
NTAPI
|
|
IopInstallCriticalDevice(PDEVICE_NODE DeviceNode);
|
|
|
|
static
|
|
VOID
|
|
IopCancelPrepareDeviceForRemoval(PDEVICE_OBJECT DeviceObject);
|
|
|
|
static
|
|
NTSTATUS
|
|
IopPrepareDeviceForRemoval(PDEVICE_OBJECT DeviceObject, BOOLEAN Force);
|
|
|
|
static
|
|
NTSTATUS
|
|
IopSetServiceEnumData(
|
|
_In_ PDEVICE_NODE DeviceNode,
|
|
_In_ HANDLE InstanceHandle);
|
|
|
|
static
|
|
BOOLEAN
|
|
IopValidateID(
|
|
_In_ PWCHAR Id,
|
|
_In_ BUS_QUERY_ID_TYPE QueryType)
|
|
{
|
|
PWCHAR PtrChar;
|
|
PWCHAR StringEnd;
|
|
WCHAR Char;
|
|
ULONG SeparatorsCount = 0;
|
|
PWCHAR PtrPrevChar = NULL;
|
|
ULONG MaxSeparators;
|
|
BOOLEAN IsMultiSz;
|
|
|
|
PAGED_CODE();
|
|
|
|
switch (QueryType)
|
|
{
|
|
case BusQueryDeviceID:
|
|
MaxSeparators = MAX_SEPARATORS_DEVICEID;
|
|
IsMultiSz = FALSE;
|
|
break;
|
|
case BusQueryInstanceID:
|
|
MaxSeparators = MAX_SEPARATORS_INSTANCEID;
|
|
IsMultiSz = FALSE;
|
|
break;
|
|
|
|
case BusQueryHardwareIDs:
|
|
case BusQueryCompatibleIDs:
|
|
MaxSeparators = MAX_SEPARATORS_DEVICEID;
|
|
IsMultiSz = TRUE;
|
|
break;
|
|
|
|
default:
|
|
DPRINT1("IopValidateID: Not handled QueryType - %x\n", QueryType);
|
|
return FALSE;
|
|
}
|
|
|
|
StringEnd = Id + MAX_DEVICE_ID_LEN;
|
|
|
|
for (PtrChar = Id; PtrChar < StringEnd; PtrChar++)
|
|
{
|
|
Char = *PtrChar;
|
|
|
|
if (Char == UNICODE_NULL)
|
|
{
|
|
if (!IsMultiSz || (PtrPrevChar && PtrChar == PtrPrevChar + 1))
|
|
{
|
|
if (MaxSeparators == SeparatorsCount || IsMultiSz)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
DPRINT1("IopValidateID: SeparatorsCount - %lu, MaxSeparators - %lu\n",
|
|
SeparatorsCount, MaxSeparators);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
StringEnd = PtrChar + MAX_DEVICE_ID_LEN + 1;
|
|
PtrPrevChar = PtrChar;
|
|
SeparatorsCount = 0;
|
|
}
|
|
else if (Char < ' ' || Char > 0x7F || Char == ',')
|
|
{
|
|
DPRINT1("IopValidateID: Invalid character - %04X\n", Char);
|
|
goto ErrorExit;
|
|
}
|
|
else if (Char == ' ')
|
|
{
|
|
*PtrChar = '_';
|
|
}
|
|
else if (Char == '\\')
|
|
{
|
|
SeparatorsCount++;
|
|
|
|
if (SeparatorsCount > MaxSeparators)
|
|
{
|
|
DPRINT1("IopValidateID: SeparatorsCount - %lu, MaxSeparators - %lu\n",
|
|
SeparatorsCount, MaxSeparators);
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
}
|
|
|
|
DPRINT1("IopValidateID: Not terminated ID\n");
|
|
|
|
ErrorExit:
|
|
// FIXME logging
|
|
return FALSE;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IopCreateDeviceInstancePath(
|
|
_In_ PDEVICE_NODE DeviceNode,
|
|
_Out_ PUNICODE_STRING InstancePath)
|
|
{
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
UNICODE_STRING DeviceId;
|
|
UNICODE_STRING InstanceId;
|
|
IO_STACK_LOCATION Stack;
|
|
NTSTATUS Status;
|
|
UNICODE_STRING ParentIdPrefix = { 0, 0, NULL };
|
|
DEVICE_CAPABILITIES DeviceCapabilities;
|
|
BOOLEAN IsValidID;
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_ID.BusQueryDeviceID to device stack\n");
|
|
|
|
Stack.Parameters.QueryId.IdType = BusQueryDeviceID;
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_ID,
|
|
&Stack);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("IopInitiatePnpIrp(BusQueryDeviceID) failed (Status %x)\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
IsValidID = IopValidateID((PWCHAR)IoStatusBlock.Information, BusQueryDeviceID);
|
|
|
|
if (!IsValidID)
|
|
{
|
|
DPRINT1("Invalid DeviceID. DeviceNode - %p\n", DeviceNode);
|
|
}
|
|
|
|
/* Save the device id string */
|
|
RtlInitUnicodeString(&DeviceId, (PWSTR)IoStatusBlock.Information);
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_CAPABILITIES to device stack (after enumeration)\n");
|
|
|
|
Status = IopQueryDeviceCapabilities(DeviceNode, &DeviceCapabilities);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("IopQueryDeviceCapabilities() failed (Status 0x%08lx)\n", Status);
|
|
RtlFreeUnicodeString(&DeviceId);
|
|
return Status;
|
|
}
|
|
|
|
/* This bit is only check after enumeration */
|
|
if (DeviceCapabilities.HardwareDisabled)
|
|
{
|
|
/* FIXME: Cleanup device */
|
|
RtlFreeUnicodeString(&DeviceId);
|
|
return STATUS_PLUGPLAY_NO_DEVICE;
|
|
}
|
|
|
|
if (!DeviceCapabilities.UniqueID)
|
|
{
|
|
/* Device has not a unique ID. We need to prepend parent bus unique identifier */
|
|
DPRINT("Instance ID is not unique\n");
|
|
Status = IopGetParentIdPrefix(DeviceNode, &ParentIdPrefix);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("IopGetParentIdPrefix() failed (Status 0x%08lx)\n", Status);
|
|
RtlFreeUnicodeString(&DeviceId);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_ID.BusQueryInstanceID to device stack\n");
|
|
|
|
Stack.Parameters.QueryId.IdType = BusQueryInstanceID;
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_ID,
|
|
&Stack);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT("IopInitiatePnpIrp(BusQueryInstanceID) failed (Status %lx)\n", Status);
|
|
ASSERT(IoStatusBlock.Information == 0);
|
|
}
|
|
|
|
if (IoStatusBlock.Information)
|
|
{
|
|
IsValidID = IopValidateID((PWCHAR)IoStatusBlock.Information, BusQueryInstanceID);
|
|
|
|
if (!IsValidID)
|
|
{
|
|
DPRINT1("Invalid InstanceID. DeviceNode - %p\n", DeviceNode);
|
|
}
|
|
}
|
|
|
|
RtlInitUnicodeString(&InstanceId,
|
|
(PWSTR)IoStatusBlock.Information);
|
|
|
|
InstancePath->Length = 0;
|
|
InstancePath->MaximumLength = DeviceId.Length + sizeof(WCHAR) +
|
|
ParentIdPrefix.Length +
|
|
InstanceId.Length +
|
|
sizeof(UNICODE_NULL);
|
|
if (ParentIdPrefix.Length && InstanceId.Length)
|
|
{
|
|
InstancePath->MaximumLength += sizeof(WCHAR);
|
|
}
|
|
|
|
InstancePath->Buffer = ExAllocatePoolWithTag(PagedPool,
|
|
InstancePath->MaximumLength,
|
|
TAG_IO);
|
|
if (!InstancePath->Buffer)
|
|
{
|
|
RtlFreeUnicodeString(&InstanceId);
|
|
RtlFreeUnicodeString(&ParentIdPrefix);
|
|
RtlFreeUnicodeString(&DeviceId);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* Start with the device id */
|
|
RtlCopyUnicodeString(InstancePath, &DeviceId);
|
|
RtlAppendUnicodeToString(InstancePath, L"\\");
|
|
|
|
/* Add information from parent bus device to InstancePath */
|
|
RtlAppendUnicodeStringToString(InstancePath, &ParentIdPrefix);
|
|
if (ParentIdPrefix.Length && InstanceId.Length)
|
|
{
|
|
RtlAppendUnicodeToString(InstancePath, L"&");
|
|
}
|
|
|
|
/* Finally, add the id returned by the driver stack */
|
|
RtlAppendUnicodeStringToString(InstancePath, &InstanceId);
|
|
|
|
/*
|
|
* FIXME: Check for valid characters, if there is invalid characters
|
|
* then bugcheck
|
|
*/
|
|
|
|
RtlFreeUnicodeString(&InstanceId);
|
|
RtlFreeUnicodeString(&DeviceId);
|
|
RtlFreeUnicodeString(&ParentIdPrefix);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief Loads and/or returns the driver associated with the registry entry if the driver
|
|
* is enabled. In case of an error, sets up a corresponding Problem to the DeviceNode
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
NTAPI
|
|
PiAttachFilterDriversCallback(
|
|
PWSTR ValueName,
|
|
ULONG ValueType,
|
|
PVOID ValueData,
|
|
ULONG ValueLength,
|
|
PVOID Ctx,
|
|
PVOID EntryContext)
|
|
{
|
|
PATTACH_FILTER_DRIVERS_CONTEXT context = Ctx;
|
|
PDRIVER_OBJECT DriverObject;
|
|
NTSTATUS Status;
|
|
BOOLEAN loadDrivers = (BOOLEAN)(ULONG_PTR)EntryContext;
|
|
|
|
PAGED_CODE();
|
|
|
|
// No filter value present
|
|
if (ValueType != REG_SZ)
|
|
return STATUS_SUCCESS;
|
|
|
|
if (ValueLength <= sizeof(WCHAR))
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
// open the service registry key
|
|
UNICODE_STRING serviceName = { .Length = 0 }, servicesKeyName;
|
|
RtlInitUnicodeString(&serviceName, ValueData);
|
|
RtlInitUnicodeString(&servicesKeyName, ServicesKeyName);
|
|
|
|
HANDLE ccsServicesHandle, serviceHandle = NULL;
|
|
|
|
Status = IopOpenRegistryKeyEx(&ccsServicesHandle, NULL, &servicesKeyName, KEY_READ);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to open a registry key for \"%wZ\" (status %x)\n", &serviceName, Status);
|
|
return Status;
|
|
}
|
|
|
|
Status = IopOpenRegistryKeyEx(&serviceHandle, ccsServicesHandle, &serviceName, KEY_READ);
|
|
ZwClose(ccsServicesHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to open a registry key for \"%wZ\" (status %x)\n", &serviceName, Status);
|
|
return Status;
|
|
}
|
|
|
|
PADD_DEV_DRIVERS_LIST driverEntry = ExAllocatePoolWithTag(PagedPool,
|
|
sizeof(*driverEntry),
|
|
TAG_PNP_DEVACTION);
|
|
|
|
if (!driverEntry)
|
|
{
|
|
DPRINT1("Failed to allocate driverEntry for \"%wZ\"\n", &serviceName);
|
|
ZwClose(serviceHandle);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// check if the driver is disabled
|
|
PKEY_VALUE_FULL_INFORMATION kvInfo;
|
|
SERVICE_LOAD_TYPE startType = DisableLoad;
|
|
|
|
Status = IopGetRegistryValue(serviceHandle, L"Start", &kvInfo);
|
|
if (NT_SUCCESS(Status) && kvInfo->Type == REG_DWORD)
|
|
{
|
|
RtlMoveMemory(&startType,
|
|
(PVOID)((ULONG_PTR)kvInfo + kvInfo->DataOffset),
|
|
sizeof(startType));
|
|
ExFreePool(kvInfo);
|
|
}
|
|
|
|
// TODO: take into account other start types (like SERVICE_DEMAND_START)
|
|
if (startType >= DisableLoad)
|
|
{
|
|
if (!(context->DeviceNode->Flags & DNF_HAS_PROBLEM))
|
|
{
|
|
PiSetDevNodeProblem(context->DeviceNode, CM_PROB_DISABLED_SERVICE);
|
|
}
|
|
|
|
DPRINT("Service \"%wZ\" is disabled (start type %u)\n", &serviceName, startType);
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// check if the driver is already loaded
|
|
UNICODE_STRING driverName;
|
|
Status = IopGetDriverNames(serviceHandle, &driverName, NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Unable to obtain the driver name for \"%wZ\"\n", &serviceName);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// try to open it
|
|
Status = ObReferenceObjectByName(&driverName,
|
|
OBJ_OPENIF | OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
|
|
NULL, /* PassedAccessState */
|
|
0, /* DesiredAccess */
|
|
IoDriverObjectType,
|
|
KernelMode,
|
|
NULL, /* ParseContext */
|
|
(PVOID*)&DriverObject);
|
|
RtlFreeUnicodeString(&driverName);
|
|
|
|
// the driver was not probably loaded, try to load
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (loadDrivers)
|
|
{
|
|
Status = IopLoadDriver(serviceHandle, &DriverObject);
|
|
}
|
|
else
|
|
{
|
|
DPRINT("Service \"%wZ\" will not be loaded now\n", &serviceName);
|
|
// return failure, the driver will be loaded later (in a subsequent call)
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
driverEntry->DriverObject = DriverObject;
|
|
driverEntry->DriverType = context->DriverType;
|
|
InsertTailList(context->DriversListHead, &driverEntry->ListEntry);
|
|
ZwClose(serviceHandle);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
if (!(context->DeviceNode->Flags & DNF_HAS_PROBLEM))
|
|
{
|
|
switch (Status)
|
|
{
|
|
case STATUS_INSUFFICIENT_RESOURCES:
|
|
PiSetDevNodeProblem(context->DeviceNode, CM_PROB_OUT_OF_MEMORY);
|
|
break;
|
|
case STATUS_FAILED_DRIVER_ENTRY:
|
|
PiSetDevNodeProblem(context->DeviceNode, CM_PROB_FAILED_DRIVER_ENTRY);
|
|
break;
|
|
case STATUS_ILL_FORMED_SERVICE_ENTRY:
|
|
PiSetDevNodeProblem(context->DeviceNode, CM_PROB_DRIVER_SERVICE_KEY_INVALID);
|
|
break;
|
|
default:
|
|
PiSetDevNodeProblem(context->DeviceNode, CM_PROB_DRIVER_FAILED_LOAD);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DPRINT1("Failed to load driver \"%wZ\" for %wZ (status %x)\n",
|
|
&serviceName, &context->DeviceNode->InstancePath, Status);
|
|
}
|
|
|
|
Cleanup:
|
|
ExFreePoolWithTag(driverEntry, TAG_PNP_DEVACTION);
|
|
if (serviceHandle)
|
|
{
|
|
ZwClose(serviceHandle);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Calls PiAttachFilterDriversCallback for filter drivers (if any)
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
PiAttachFilterDrivers(
|
|
PLIST_ENTRY DriversListHead,
|
|
PDEVICE_NODE DeviceNode,
|
|
HANDLE EnumSubKey,
|
|
HANDLE ClassKey,
|
|
BOOLEAN Lower,
|
|
BOOLEAN LoadDrivers)
|
|
{
|
|
RTL_QUERY_REGISTRY_TABLE QueryTable[2] = { { NULL, 0, NULL, NULL, 0, NULL, 0 }, };
|
|
ATTACH_FILTER_DRIVERS_CONTEXT routineContext;
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
routineContext.DriversListHead = DriversListHead;
|
|
routineContext.DeviceNode = DeviceNode;
|
|
|
|
// First add device filters
|
|
routineContext.DriverType = Lower ? LowerFilter : UpperFilter;
|
|
QueryTable[0] = (RTL_QUERY_REGISTRY_TABLE){
|
|
.QueryRoutine = PiAttachFilterDriversCallback,
|
|
.Name = Lower ? L"LowerFilters" : L"UpperFilters",
|
|
.DefaultType = REG_NONE,
|
|
.EntryContext = (PVOID)(ULONG_PTR)LoadDrivers
|
|
};
|
|
|
|
Status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
|
|
(PWSTR)EnumSubKey,
|
|
QueryTable,
|
|
&routineContext,
|
|
NULL);
|
|
if (ClassKey == NULL)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
// Then add device class filters
|
|
routineContext.DriverType = Lower ? LowerClassFilter : UpperClassFilter;
|
|
QueryTable[0] = (RTL_QUERY_REGISTRY_TABLE){
|
|
.QueryRoutine = PiAttachFilterDriversCallback,
|
|
.Name = Lower ? L"LowerFilters" : L"UpperFilters",
|
|
.DefaultType = REG_NONE,
|
|
.EntryContext = (PVOID)(ULONG_PTR)LoadDrivers
|
|
};
|
|
|
|
Status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
|
|
(PWSTR)ClassKey,
|
|
QueryTable,
|
|
&routineContext,
|
|
NULL);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @brief Loads all drivers for a device node (actual service and filters)
|
|
* and calls their AddDevice routine
|
|
*
|
|
* @param[in] DeviceNode The device node
|
|
* @param[in] LoadDrivers Whether to load drivers if they are not loaded yet
|
|
* (used when storage subsystem is not yet initialized)
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
PiCallDriverAddDevice(
|
|
_In_ PDEVICE_NODE DeviceNode,
|
|
_In_ BOOLEAN LoadDrivers)
|
|
{
|
|
NTSTATUS Status;
|
|
HANDLE EnumRootKey, SubKey;
|
|
HANDLE ClassKey = NULL;
|
|
UNICODE_STRING EnumRoot = RTL_CONSTANT_STRING(ENUM_ROOT);
|
|
static UNICODE_STRING ccsControlClass =
|
|
RTL_CONSTANT_STRING(L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Class");
|
|
PKEY_VALUE_FULL_INFORMATION kvInfo = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
// open the enumeration root key
|
|
Status = IopOpenRegistryKeyEx(&EnumRootKey, NULL, &EnumRoot, KEY_READ);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("IopOpenRegistryKeyEx() failed for \"%wZ\" (status %x)\n", &EnumRoot, Status);
|
|
return Status;
|
|
}
|
|
|
|
// open an instance subkey
|
|
Status = IopOpenRegistryKeyEx(&SubKey, EnumRootKey, &DeviceNode->InstancePath, KEY_READ);
|
|
ZwClose(EnumRootKey);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to open a devnode instance key for \"%wZ\" (status %x)\n",
|
|
&DeviceNode->InstancePath, Status);
|
|
return Status;
|
|
}
|
|
|
|
// try to get the class GUID of an instance and its registry key
|
|
Status = IopGetRegistryValue(SubKey, REGSTR_VAL_CLASSGUID, &kvInfo);
|
|
if (NT_SUCCESS(Status) && kvInfo->Type == REG_SZ && kvInfo->DataLength > sizeof(WCHAR))
|
|
{
|
|
UNICODE_STRING classGUID = {
|
|
.MaximumLength = kvInfo->DataLength,
|
|
.Length = kvInfo->DataLength - sizeof(UNICODE_NULL),
|
|
.Buffer = (PVOID)((ULONG_PTR)kvInfo + kvInfo->DataOffset)
|
|
};
|
|
HANDLE ccsControlHandle;
|
|
|
|
Status = IopOpenRegistryKeyEx(&ccsControlHandle, NULL, &ccsControlClass, KEY_READ);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("IopOpenRegistryKeyEx() failed for \"%wZ\" (status %x)\n",
|
|
&ccsControlClass, Status);
|
|
}
|
|
else
|
|
{
|
|
// open the CCS\Control\Class\<ClassGUID> key
|
|
Status = IopOpenRegistryKeyEx(&ClassKey, ccsControlHandle, &classGUID, KEY_READ);
|
|
ZwClose(ccsControlHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to open class key \"%wZ\" (status %x)\n", &classGUID, Status);
|
|
}
|
|
}
|
|
|
|
if (ClassKey)
|
|
{
|
|
// Check the Properties key of a class too
|
|
// Windows fills some device properties from this key (which is protected)
|
|
// TODO: add the device properties from this key
|
|
|
|
UNICODE_STRING properties = RTL_CONSTANT_STRING(REGSTR_KEY_DEVICE_PROPERTIES);
|
|
HANDLE propertiesHandle;
|
|
|
|
Status = IopOpenRegistryKeyEx(&propertiesHandle, ClassKey, &properties, KEY_READ);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT("Properties key failed to open for \"%wZ\" (status %x)\n",
|
|
&classGUID, Status);
|
|
}
|
|
else
|
|
{
|
|
ZwClose(propertiesHandle);
|
|
}
|
|
}
|
|
ExFreePool(kvInfo);
|
|
}
|
|
|
|
// the driver loading order:
|
|
// 1. LowerFilters
|
|
// 2. LowerClassFilters
|
|
// 3. Device driver (only one service!)
|
|
// 4. UpperFilters
|
|
// 5. UpperClassFilters
|
|
|
|
LIST_ENTRY drvListHead;
|
|
InitializeListHead(&drvListHead);
|
|
|
|
// lower (class) filters
|
|
Status = PiAttachFilterDrivers(&drvListHead, DeviceNode, SubKey, ClassKey, TRUE, LoadDrivers);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
ATTACH_FILTER_DRIVERS_CONTEXT routineContext = {
|
|
.DriversListHead = &drvListHead,
|
|
.DriverType = DeviceDriver,
|
|
.DeviceNode = DeviceNode
|
|
};
|
|
|
|
RTL_QUERY_REGISTRY_TABLE queryTable[2] = {{
|
|
.QueryRoutine = PiAttachFilterDriversCallback,
|
|
.Name = L"Service",
|
|
.Flags = RTL_QUERY_REGISTRY_REQUIRED,
|
|
.DefaultType = REG_SZ, // REG_MULTI_SZ is not allowed here
|
|
.DefaultData = L"",
|
|
.EntryContext = (PVOID)(ULONG_PTR)LoadDrivers
|
|
},};
|
|
|
|
// device driver
|
|
Status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
|
|
(PWSTR)SubKey,
|
|
queryTable,
|
|
&routineContext,
|
|
NULL);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
// do nothing
|
|
}
|
|
// if a driver is not found, but a device allows raw access -> proceed
|
|
else if (Status == STATUS_OBJECT_NAME_NOT_FOUND &&
|
|
(DeviceNode->CapabilityFlags & 0x00000040)) // CM_DEVCAP_RAWDEVICEOK
|
|
{
|
|
// add a dummy entry to the drivers list (need for later processing)
|
|
PADD_DEV_DRIVERS_LIST driverEntry = ExAllocatePoolZero(PagedPool,
|
|
sizeof(*driverEntry),
|
|
TAG_PNP_DEVACTION);
|
|
driverEntry->DriverType = DeviceDriver;
|
|
InsertTailList(&drvListHead, &driverEntry->ListEntry);
|
|
DPRINT("No service for \"%wZ\" (RawDeviceOK)\n", &DeviceNode->InstancePath);
|
|
}
|
|
else
|
|
{
|
|
if (Status == STATUS_OBJECT_TYPE_MISMATCH && !(DeviceNode->Flags & DNF_HAS_PROBLEM))
|
|
{
|
|
PiSetDevNodeProblem(DeviceNode, CM_PROB_REGISTRY);
|
|
}
|
|
DPRINT("No service for \"%wZ\" (loadDrv: %u)\n", &DeviceNode->InstancePath, LoadDrivers);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// upper (class) filters
|
|
Status = PiAttachFilterDrivers(&drvListHead, DeviceNode, SubKey, ClassKey, FALSE, LoadDrivers);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
// finally loop through the stack and call AddDevice for every driver
|
|
for (PLIST_ENTRY listEntry = drvListHead.Flink;
|
|
listEntry != &drvListHead;
|
|
listEntry = listEntry->Flink)
|
|
{
|
|
PADD_DEV_DRIVERS_LIST driverEntry;
|
|
driverEntry = CONTAINING_RECORD(listEntry, ADD_DEV_DRIVERS_LIST, ListEntry);
|
|
PDRIVER_OBJECT driverObject = driverEntry->DriverObject;
|
|
|
|
// FIXME: ReactOS is not quite ready for this assert
|
|
// (legacy drivers should not have AddDevice routine)
|
|
// ASSERT(!(DriverObject->Flags & DRVO_LEGACY_DRIVER));
|
|
|
|
if (driverObject && driverObject->DriverExtension->AddDevice)
|
|
{
|
|
Status = driverObject->DriverExtension->AddDevice(driverEntry->DriverObject,
|
|
DeviceNode->PhysicalDeviceObject);
|
|
}
|
|
else if (driverObject == NULL)
|
|
{
|
|
// valid only for DeviceDriver
|
|
ASSERT(driverEntry->DriverType == DeviceDriver);
|
|
ASSERT(DeviceNode->CapabilityFlags & 0x00000040); // CM_DEVCAP_RAWDEVICEOK
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
// HACK: the driver doesn't have a AddDevice routine. We shouldn't be here,
|
|
// but ReactOS' PnP stack is not that correct yet
|
|
DeviceNode->Flags |= DNF_LEGACY_DRIVER;
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
// for filter drivers we don't care about the AddDevice result
|
|
if (driverEntry->DriverType == DeviceDriver)
|
|
{
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
PDEVICE_OBJECT fdo = IoGetAttachedDeviceReference(DeviceNode->PhysicalDeviceObject);
|
|
|
|
// HACK: Check if we have a ACPI device (needed for power management)
|
|
if (fdo->DeviceType == FILE_DEVICE_ACPI)
|
|
{
|
|
static BOOLEAN SystemPowerDeviceNodeCreated = FALSE;
|
|
|
|
// There can be only one system power device
|
|
if (!SystemPowerDeviceNodeCreated)
|
|
{
|
|
PopSystemPowerDeviceNode = DeviceNode;
|
|
ObReferenceObject(PopSystemPowerDeviceNode->PhysicalDeviceObject);
|
|
SystemPowerDeviceNodeCreated = TRUE;
|
|
}
|
|
}
|
|
|
|
ObDereferenceObject(fdo);
|
|
PiSetDevNodeState(DeviceNode, DeviceNodeDriversAdded);
|
|
}
|
|
else
|
|
{
|
|
// lower filters (if already started) will be removed upon this request
|
|
PiSetDevNodeProblem(DeviceNode, CM_PROB_FAILED_ADD);
|
|
PiSetDevNodeState(DeviceNode, DeviceNodeAwaitingQueuedRemoval);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if DBG
|
|
PDEVICE_OBJECT attachedDO = IoGetAttachedDevice(DeviceNode->PhysicalDeviceObject);
|
|
if (attachedDO->Flags & DO_DEVICE_INITIALIZING)
|
|
{
|
|
DPRINT1("DO_DEVICE_INITIALIZING is not cleared on a device 0x%p!\n", attachedDO);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Cleanup:
|
|
while (!IsListEmpty(&drvListHead))
|
|
{
|
|
PLIST_ENTRY listEntry = RemoveHeadList(&drvListHead);
|
|
PADD_DEV_DRIVERS_LIST driverEntry;
|
|
driverEntry = CONTAINING_RECORD(listEntry, ADD_DEV_DRIVERS_LIST, ListEntry);
|
|
|
|
// drivers which don't have any devices (in case of failure) will be cleaned up
|
|
if (driverEntry->DriverObject)
|
|
{
|
|
ObDereferenceObject(driverEntry->DriverObject);
|
|
}
|
|
ExFreePoolWithTag(driverEntry, TAG_PNP_DEVACTION);
|
|
}
|
|
|
|
ZwClose(SubKey);
|
|
if (ClassKey != NULL)
|
|
{
|
|
ZwClose(ClassKey);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
IopQueryDeviceCapabilities(PDEVICE_NODE DeviceNode,
|
|
PDEVICE_CAPABILITIES DeviceCaps)
|
|
{
|
|
IO_STATUS_BLOCK StatusBlock;
|
|
IO_STACK_LOCATION Stack;
|
|
NTSTATUS Status;
|
|
HANDLE InstanceKey;
|
|
UNICODE_STRING ValueName;
|
|
|
|
/* Set up the Header */
|
|
RtlZeroMemory(DeviceCaps, sizeof(DEVICE_CAPABILITIES));
|
|
DeviceCaps->Size = sizeof(DEVICE_CAPABILITIES);
|
|
DeviceCaps->Version = 1;
|
|
DeviceCaps->Address = -1;
|
|
DeviceCaps->UINumber = -1;
|
|
|
|
/* Set up the Stack */
|
|
RtlZeroMemory(&Stack, sizeof(IO_STACK_LOCATION));
|
|
Stack.Parameters.DeviceCapabilities.Capabilities = DeviceCaps;
|
|
|
|
/* Send the IRP */
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&StatusBlock,
|
|
IRP_MN_QUERY_CAPABILITIES,
|
|
&Stack);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Status != STATUS_NOT_SUPPORTED)
|
|
{
|
|
DPRINT1("IRP_MN_QUERY_CAPABILITIES failed with status 0x%lx\n", Status);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
/* Map device capabilities to capability flags */
|
|
DeviceNode->CapabilityFlags = 0;
|
|
if (DeviceCaps->LockSupported)
|
|
DeviceNode->CapabilityFlags |= 0x00000001; // CM_DEVCAP_LOCKSUPPORTED
|
|
|
|
if (DeviceCaps->EjectSupported)
|
|
DeviceNode->CapabilityFlags |= 0x00000002; // CM_DEVCAP_EJECTSUPPORTED
|
|
|
|
if (DeviceCaps->Removable)
|
|
DeviceNode->CapabilityFlags |= 0x00000004; // CM_DEVCAP_REMOVABLE
|
|
|
|
if (DeviceCaps->DockDevice)
|
|
DeviceNode->CapabilityFlags |= 0x00000008; // CM_DEVCAP_DOCKDEVICE
|
|
|
|
if (DeviceCaps->UniqueID)
|
|
DeviceNode->CapabilityFlags |= 0x00000010; // CM_DEVCAP_UNIQUEID
|
|
|
|
if (DeviceCaps->SilentInstall)
|
|
DeviceNode->CapabilityFlags |= 0x00000020; // CM_DEVCAP_SILENTINSTALL
|
|
|
|
if (DeviceCaps->RawDeviceOK)
|
|
DeviceNode->CapabilityFlags |= 0x00000040; // CM_DEVCAP_RAWDEVICEOK
|
|
|
|
if (DeviceCaps->SurpriseRemovalOK)
|
|
DeviceNode->CapabilityFlags |= 0x00000080; // CM_DEVCAP_SURPRISEREMOVALOK
|
|
|
|
if (DeviceCaps->HardwareDisabled)
|
|
DeviceNode->CapabilityFlags |= 0x00000100; // CM_DEVCAP_HARDWAREDISABLED
|
|
|
|
if (DeviceCaps->NonDynamic)
|
|
DeviceNode->CapabilityFlags |= 0x00000200; // CM_DEVCAP_NONDYNAMIC
|
|
|
|
if (DeviceCaps->NoDisplayInUI)
|
|
DeviceNode->UserFlags |= DNUF_DONT_SHOW_IN_UI;
|
|
else
|
|
DeviceNode->UserFlags &= ~DNUF_DONT_SHOW_IN_UI;
|
|
|
|
Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, REG_OPTION_NON_VOLATILE, &InstanceKey);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Set 'Capabilities' value */
|
|
RtlInitUnicodeString(&ValueName, L"Capabilities");
|
|
Status = ZwSetValueKey(InstanceKey,
|
|
&ValueName,
|
|
0,
|
|
REG_DWORD,
|
|
&DeviceNode->CapabilityFlags,
|
|
sizeof(ULONG));
|
|
|
|
/* Set 'UINumber' value */
|
|
if (DeviceCaps->UINumber != MAXULONG)
|
|
{
|
|
RtlInitUnicodeString(&ValueName, L"UINumber");
|
|
Status = ZwSetValueKey(InstanceKey,
|
|
&ValueName,
|
|
0,
|
|
REG_DWORD,
|
|
&DeviceCaps->UINumber,
|
|
sizeof(ULONG));
|
|
}
|
|
|
|
ZwClose(InstanceKey);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IopQueryHardwareIds(PDEVICE_NODE DeviceNode,
|
|
HANDLE InstanceKey)
|
|
{
|
|
IO_STACK_LOCATION Stack;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
PWSTR Ptr;
|
|
UNICODE_STRING ValueName;
|
|
NTSTATUS Status;
|
|
ULONG Length, TotalLength;
|
|
BOOLEAN IsValidID;
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_ID.BusQueryHardwareIDs to device stack\n");
|
|
|
|
RtlZeroMemory(&Stack, sizeof(Stack));
|
|
Stack.Parameters.QueryId.IdType = BusQueryHardwareIDs;
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_ID,
|
|
&Stack);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
IsValidID = IopValidateID((PWCHAR)IoStatusBlock.Information, BusQueryHardwareIDs);
|
|
|
|
if (!IsValidID)
|
|
{
|
|
DPRINT1("Invalid HardwareIDs. DeviceNode - %p\n", DeviceNode);
|
|
}
|
|
|
|
TotalLength = 0;
|
|
|
|
Ptr = (PWSTR)IoStatusBlock.Information;
|
|
DPRINT("Hardware IDs:\n");
|
|
while (*Ptr)
|
|
{
|
|
DPRINT(" %S\n", Ptr);
|
|
Length = (ULONG)wcslen(Ptr) + 1;
|
|
|
|
Ptr += Length;
|
|
TotalLength += Length;
|
|
}
|
|
DPRINT("TotalLength: %hu\n", TotalLength);
|
|
DPRINT("\n");
|
|
|
|
RtlInitUnicodeString(&ValueName, L"HardwareID");
|
|
Status = ZwSetValueKey(InstanceKey,
|
|
&ValueName,
|
|
0,
|
|
REG_MULTI_SZ,
|
|
(PVOID)IoStatusBlock.Information,
|
|
(TotalLength + 1) * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ZwSetValueKey() failed (Status %lx)\n", Status);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status %x)\n", Status);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IopQueryCompatibleIds(PDEVICE_NODE DeviceNode,
|
|
HANDLE InstanceKey)
|
|
{
|
|
IO_STACK_LOCATION Stack;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
PWSTR Ptr;
|
|
UNICODE_STRING ValueName;
|
|
NTSTATUS Status;
|
|
ULONG Length, TotalLength;
|
|
BOOLEAN IsValidID;
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_ID.BusQueryCompatibleIDs to device stack\n");
|
|
|
|
RtlZeroMemory(&Stack, sizeof(Stack));
|
|
Stack.Parameters.QueryId.IdType = BusQueryCompatibleIDs;
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_ID,
|
|
&Stack);
|
|
if (NT_SUCCESS(Status) && IoStatusBlock.Information)
|
|
{
|
|
IsValidID = IopValidateID((PWCHAR)IoStatusBlock.Information, BusQueryCompatibleIDs);
|
|
|
|
if (!IsValidID)
|
|
{
|
|
DPRINT1("Invalid CompatibleIDs. DeviceNode - %p\n", DeviceNode);
|
|
}
|
|
|
|
TotalLength = 0;
|
|
|
|
Ptr = (PWSTR)IoStatusBlock.Information;
|
|
DPRINT("Compatible IDs:\n");
|
|
while (*Ptr)
|
|
{
|
|
DPRINT(" %S\n", Ptr);
|
|
Length = (ULONG)wcslen(Ptr) + 1;
|
|
|
|
Ptr += Length;
|
|
TotalLength += Length;
|
|
}
|
|
DPRINT("TotalLength: %hu\n", TotalLength);
|
|
DPRINT("\n");
|
|
|
|
RtlInitUnicodeString(&ValueName, L"CompatibleIDs");
|
|
Status = ZwSetValueKey(InstanceKey,
|
|
&ValueName,
|
|
0,
|
|
REG_MULTI_SZ,
|
|
(PVOID)IoStatusBlock.Information,
|
|
(TotalLength + 1) * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ZwSetValueKey() failed (Status %lx) or no Compatible ID returned\n", Status);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status %x)\n", Status);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
PiInitializeDevNode(
|
|
_In_ PDEVICE_NODE DeviceNode)
|
|
{
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
PWSTR DeviceDescription;
|
|
PWSTR LocationInformation;
|
|
IO_STACK_LOCATION Stack;
|
|
NTSTATUS Status;
|
|
ULONG RequiredLength;
|
|
LCID LocaleId;
|
|
HANDLE InstanceKey = NULL;
|
|
UNICODE_STRING ValueName;
|
|
UNICODE_STRING InstancePathU;
|
|
PDEVICE_OBJECT OldDeviceObject;
|
|
|
|
DPRINT("PiProcessNewDevNode(%p)\n", DeviceNode);
|
|
DPRINT("PDO 0x%p\n", DeviceNode->PhysicalDeviceObject);
|
|
|
|
/* Get Locale ID */
|
|
Status = ZwQueryDefaultLocale(FALSE, &LocaleId);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ZwQueryDefaultLocale() failed with status 0x%lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* FIXME: For critical errors, cleanup and disable device, but always
|
|
* return STATUS_SUCCESS.
|
|
*/
|
|
|
|
Status = IopCreateDeviceInstancePath(DeviceNode, &InstancePathU);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Status != STATUS_PLUGPLAY_NO_DEVICE)
|
|
{
|
|
DPRINT1("IopCreateDeviceInstancePath() failed with status 0x%lx\n", Status);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
/* Verify that this is not a duplicate */
|
|
OldDeviceObject = IopGetDeviceObjectFromDeviceInstance(&InstancePathU);
|
|
if (OldDeviceObject != NULL)
|
|
{
|
|
PDEVICE_NODE OldDeviceNode = IopGetDeviceNode(OldDeviceObject);
|
|
|
|
DPRINT1("Duplicate device instance '%wZ'\n", &InstancePathU);
|
|
DPRINT1("Current instance parent: '%wZ'\n", &DeviceNode->Parent->InstancePath);
|
|
DPRINT1("Old instance parent: '%wZ'\n", &OldDeviceNode->Parent->InstancePath);
|
|
|
|
KeBugCheckEx(PNP_DETECTED_FATAL_ERROR,
|
|
0x01,
|
|
(ULONG_PTR)DeviceNode->PhysicalDeviceObject,
|
|
(ULONG_PTR)OldDeviceObject,
|
|
0);
|
|
}
|
|
|
|
DeviceNode->InstancePath = InstancePathU;
|
|
|
|
DPRINT("InstancePath is %S\n", DeviceNode->InstancePath.Buffer);
|
|
|
|
/*
|
|
* Create registry key for the instance id, if it doesn't exist yet
|
|
*/
|
|
Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, REG_OPTION_NON_VOLATILE, &InstanceKey);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to create the instance key! (Status %lx)\n", Status);
|
|
|
|
/* We have to return success otherwise we abort the traverse operation */
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
IopQueryHardwareIds(DeviceNode, InstanceKey);
|
|
|
|
IopQueryCompatibleIds(DeviceNode, InstanceKey);
|
|
|
|
DeviceNode->Flags |= DNF_IDS_QUERIED;
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_DEVICE_TEXT.DeviceTextDescription to device stack\n");
|
|
|
|
Stack.Parameters.QueryDeviceText.DeviceTextType = DeviceTextDescription;
|
|
Stack.Parameters.QueryDeviceText.LocaleId = LocaleId;
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_DEVICE_TEXT,
|
|
&Stack);
|
|
DeviceDescription = NT_SUCCESS(Status) ? (PWSTR)IoStatusBlock.Information
|
|
: NULL;
|
|
/* This key is mandatory, so even if the Irp fails, we still write it */
|
|
RtlInitUnicodeString(&ValueName, L"DeviceDesc");
|
|
if (ZwQueryValueKey(InstanceKey, &ValueName, KeyValueBasicInformation, NULL, 0, &RequiredLength) == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
if (DeviceDescription &&
|
|
*DeviceDescription != UNICODE_NULL)
|
|
{
|
|
/* This key is overriden when a driver is installed. Don't write the
|
|
* new description if another one already exists */
|
|
Status = ZwSetValueKey(InstanceKey,
|
|
&ValueName,
|
|
0,
|
|
REG_SZ,
|
|
DeviceDescription,
|
|
((ULONG)wcslen(DeviceDescription) + 1) * sizeof(WCHAR));
|
|
}
|
|
else
|
|
{
|
|
UNICODE_STRING DeviceDesc = RTL_CONSTANT_STRING(L"Unknown device");
|
|
DPRINT("Driver didn't return DeviceDesc (Status 0x%08lx), so place unknown device there\n", Status);
|
|
|
|
Status = ZwSetValueKey(InstanceKey,
|
|
&ValueName,
|
|
0,
|
|
REG_SZ,
|
|
DeviceDesc.Buffer,
|
|
DeviceDesc.MaximumLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ZwSetValueKey() failed (Status 0x%lx)\n", Status);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (DeviceDescription)
|
|
{
|
|
ExFreePoolWithTag(DeviceDescription, 0);
|
|
}
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_DEVICE_TEXT.DeviceTextLocation to device stack\n");
|
|
|
|
Stack.Parameters.QueryDeviceText.DeviceTextType = DeviceTextLocationInformation;
|
|
Stack.Parameters.QueryDeviceText.LocaleId = LocaleId;
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_DEVICE_TEXT,
|
|
&Stack);
|
|
if (NT_SUCCESS(Status) && IoStatusBlock.Information)
|
|
{
|
|
LocationInformation = (PWSTR)IoStatusBlock.Information;
|
|
DPRINT("LocationInformation: %S\n", LocationInformation);
|
|
RtlInitUnicodeString(&ValueName, L"LocationInformation");
|
|
Status = ZwSetValueKey(InstanceKey,
|
|
&ValueName,
|
|
0,
|
|
REG_SZ,
|
|
LocationInformation,
|
|
((ULONG)wcslen(LocationInformation) + 1) * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("ZwSetValueKey() failed (Status %lx)\n", Status);
|
|
}
|
|
|
|
ExFreePoolWithTag(LocationInformation, 0);
|
|
}
|
|
else
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status %x) or IoStatusBlock.Information=NULL\n", Status);
|
|
}
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_BUS_INFORMATION to device stack\n");
|
|
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_BUS_INFORMATION,
|
|
NULL);
|
|
if (NT_SUCCESS(Status) && IoStatusBlock.Information)
|
|
{
|
|
PPNP_BUS_INFORMATION BusInformation = (PPNP_BUS_INFORMATION)IoStatusBlock.Information;
|
|
|
|
DeviceNode->ChildBusNumber = BusInformation->BusNumber;
|
|
DeviceNode->ChildInterfaceType = BusInformation->LegacyBusType;
|
|
DeviceNode->ChildBusTypeIndex = IopGetBusTypeGuidIndex(&BusInformation->BusTypeGuid);
|
|
ExFreePoolWithTag(BusInformation, 0);
|
|
}
|
|
else
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status %x) or IoStatusBlock.Information=NULL\n", Status);
|
|
|
|
DeviceNode->ChildBusNumber = 0xFFFFFFF0;
|
|
DeviceNode->ChildInterfaceType = InterfaceTypeUndefined;
|
|
DeviceNode->ChildBusTypeIndex = -1;
|
|
}
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_RESOURCES to device stack\n");
|
|
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_RESOURCES,
|
|
NULL);
|
|
if (NT_SUCCESS(Status) && IoStatusBlock.Information)
|
|
{
|
|
DeviceNode->BootResources = (PCM_RESOURCE_LIST)IoStatusBlock.Information;
|
|
IopDeviceNodeSetFlag(DeviceNode, DNF_HAS_BOOT_CONFIG);
|
|
}
|
|
else
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status %x) or IoStatusBlock.Information=NULL\n", Status);
|
|
DeviceNode->BootResources = NULL;
|
|
}
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_RESOURCE_REQUIREMENTS to device stack\n");
|
|
|
|
Status = IopInitiatePnpIrp(DeviceNode->PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_RESOURCE_REQUIREMENTS,
|
|
NULL);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
DeviceNode->ResourceRequirements = (PIO_RESOURCE_REQUIREMENTS_LIST)IoStatusBlock.Information;
|
|
}
|
|
else
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status %08lx)\n", Status);
|
|
DeviceNode->ResourceRequirements = NULL;
|
|
}
|
|
|
|
if (InstanceKey != NULL)
|
|
{
|
|
IopSetDeviceInstanceData(InstanceKey, DeviceNode);
|
|
}
|
|
|
|
// Try installing a critical device, so its Service key is populated
|
|
// then call IopSetServiceEnumData to populate service's Enum key.
|
|
// That allows us to start devices during an early boot
|
|
IopInstallCriticalDevice(DeviceNode);
|
|
IopSetServiceEnumData(DeviceNode, InstanceKey);
|
|
|
|
ZwClose(InstanceKey);
|
|
|
|
PiSetDevNodeState(DeviceNode, DeviceNodeInitialized);
|
|
|
|
if (!IopDeviceNodeHasFlag(DeviceNode, DNF_LEGACY_DRIVER))
|
|
{
|
|
/* Report the device to the user-mode pnp manager */
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_ENUMERATED,
|
|
&DeviceNode->InstancePath);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IopSetServiceEnumData(
|
|
_In_ PDEVICE_NODE DeviceNode,
|
|
_In_ HANDLE InstanceHandle)
|
|
{
|
|
UNICODE_STRING ServicesKeyPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\");
|
|
UNICODE_STRING ServiceKeyName;
|
|
UNICODE_STRING EnumKeyName;
|
|
UNICODE_STRING ValueName;
|
|
UNICODE_STRING ServiceName;
|
|
PKEY_VALUE_FULL_INFORMATION KeyValueInformation, kvInfo2;
|
|
HANDLE ServiceKey = NULL, ServiceEnumKey = NULL;
|
|
ULONG Disposition;
|
|
ULONG Count = 0, NextInstance = 0;
|
|
WCHAR ValueBuffer[6];
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
// obtain the device node's ServiceName
|
|
Status = IopGetRegistryValue(InstanceHandle, L"Service", &kvInfo2);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if (kvInfo2->Type != REG_SZ || kvInfo2->DataLength <= sizeof(WCHAR))
|
|
{
|
|
ExFreePool(kvInfo2);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
ServiceName.MaximumLength = kvInfo2->DataLength;
|
|
ServiceName.Length = kvInfo2->DataLength - sizeof(UNICODE_NULL);
|
|
ServiceName.Buffer = (PVOID)((ULONG_PTR)kvInfo2 + kvInfo2->DataOffset);
|
|
|
|
DPRINT("IopSetServiceEnumData(%p)\n", DeviceNode);
|
|
DPRINT("Instance: %wZ\n", &DeviceNode->InstancePath);
|
|
DPRINT("Service: %wZ\n", &ServiceName);
|
|
|
|
ServiceKeyName.MaximumLength = ServicesKeyPath.Length + ServiceName.Length + sizeof(UNICODE_NULL);
|
|
ServiceKeyName.Length = 0;
|
|
ServiceKeyName.Buffer = ExAllocatePool(PagedPool, ServiceKeyName.MaximumLength);
|
|
if (ServiceKeyName.Buffer == NULL)
|
|
{
|
|
DPRINT1("No ServiceKeyName.Buffer!\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlAppendUnicodeStringToString(&ServiceKeyName, &ServicesKeyPath);
|
|
RtlAppendUnicodeStringToString(&ServiceKeyName, &ServiceName);
|
|
|
|
DPRINT("ServiceKeyName: %wZ\n", &ServiceKeyName);
|
|
|
|
Status = IopOpenRegistryKeyEx(&ServiceKey, NULL, &ServiceKeyName, KEY_CREATE_SUB_KEY);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
RtlInitUnicodeString(&EnumKeyName, L"Enum");
|
|
Status = IopCreateRegistryKeyEx(&ServiceEnumKey,
|
|
ServiceKey,
|
|
&EnumKeyName,
|
|
KEY_SET_VALUE,
|
|
REG_OPTION_VOLATILE,
|
|
&Disposition);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (Disposition == REG_OPENED_EXISTING_KEY)
|
|
{
|
|
/* Read the NextInstance value */
|
|
Status = IopGetRegistryValue(ServiceEnumKey,
|
|
L"Count",
|
|
&KeyValueInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
goto done;
|
|
|
|
if ((KeyValueInformation->Type == REG_DWORD) &&
|
|
(KeyValueInformation->DataLength))
|
|
{
|
|
/* Read it */
|
|
Count = *(PULONG)((ULONG_PTR)KeyValueInformation +
|
|
KeyValueInformation->DataOffset);
|
|
}
|
|
|
|
ExFreePool(KeyValueInformation);
|
|
KeyValueInformation = NULL;
|
|
|
|
/* Read the NextInstance value */
|
|
Status = IopGetRegistryValue(ServiceEnumKey,
|
|
L"NextInstance",
|
|
&KeyValueInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
goto done;
|
|
|
|
if ((KeyValueInformation->Type == REG_DWORD) &&
|
|
(KeyValueInformation->DataLength))
|
|
{
|
|
NextInstance = *(PULONG)((ULONG_PTR)KeyValueInformation +
|
|
KeyValueInformation->DataOffset);
|
|
}
|
|
|
|
ExFreePool(KeyValueInformation);
|
|
KeyValueInformation = NULL;
|
|
}
|
|
|
|
/* Set the instance path */
|
|
swprintf(ValueBuffer, L"%lu", NextInstance);
|
|
RtlInitUnicodeString(&ValueName, ValueBuffer);
|
|
Status = ZwSetValueKey(ServiceEnumKey,
|
|
&ValueName,
|
|
0,
|
|
REG_SZ,
|
|
DeviceNode->InstancePath.Buffer,
|
|
DeviceNode->InstancePath.MaximumLength);
|
|
if (!NT_SUCCESS(Status))
|
|
goto done;
|
|
|
|
/* Increment Count and NextInstance */
|
|
Count++;
|
|
NextInstance++;
|
|
|
|
/* Set the new Count value */
|
|
RtlInitUnicodeString(&ValueName, L"Count");
|
|
Status = ZwSetValueKey(ServiceEnumKey,
|
|
&ValueName,
|
|
0,
|
|
REG_DWORD,
|
|
&Count,
|
|
sizeof(Count));
|
|
if (!NT_SUCCESS(Status))
|
|
goto done;
|
|
|
|
/* Set the new NextInstance value */
|
|
RtlInitUnicodeString(&ValueName, L"NextInstance");
|
|
Status = ZwSetValueKey(ServiceEnumKey,
|
|
&ValueName,
|
|
0,
|
|
REG_DWORD,
|
|
&NextInstance,
|
|
sizeof(NextInstance));
|
|
}
|
|
|
|
RtlDuplicateUnicodeString(RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING,
|
|
&ServiceName,
|
|
&DeviceNode->ServiceName);
|
|
|
|
done:
|
|
if (ServiceEnumKey != NULL)
|
|
ZwClose(ServiceEnumKey);
|
|
|
|
if (ServiceKey != NULL)
|
|
ZwClose(ServiceKey);
|
|
|
|
ExFreePool(ServiceKeyName.Buffer);
|
|
ExFreePool(kvInfo2);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
PiStartDeviceFinal(
|
|
_In_ PDEVICE_NODE DeviceNode)
|
|
{
|
|
DEVICE_CAPABILITIES DeviceCapabilities;
|
|
NTSTATUS Status;
|
|
|
|
if (!(DeviceNode->Flags & DNF_IDS_QUERIED))
|
|
{
|
|
// query ids (for reported devices)
|
|
UNICODE_STRING enumRoot = RTL_CONSTANT_STRING(ENUM_ROOT);
|
|
HANDLE enumRootHandle, instanceHandle;
|
|
|
|
// open the enumeration root key
|
|
Status = IopOpenRegistryKeyEx(&enumRootHandle, NULL, &enumRoot, KEY_READ);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("IopOpenRegistryKeyEx() failed for \"%wZ\" (status %x)\n", &enumRoot, Status);
|
|
return Status;
|
|
}
|
|
|
|
// open an instance subkey
|
|
Status = IopOpenRegistryKeyEx(&instanceHandle, enumRootHandle, &DeviceNode->InstancePath, KEY_READ);
|
|
ZwClose(enumRootHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to open a devnode instance key for \"%wZ\" (status %x)\n",
|
|
&DeviceNode->InstancePath, Status);
|
|
return Status;
|
|
}
|
|
|
|
IopQueryHardwareIds(DeviceNode, instanceHandle);
|
|
IopQueryCompatibleIds(DeviceNode, instanceHandle);
|
|
|
|
DeviceNode->Flags |= DNF_IDS_QUERIED;
|
|
ZwClose(instanceHandle);
|
|
}
|
|
|
|
// we're about to start - needs enumeration
|
|
DeviceNode->Flags |= DNF_REENUMERATE;
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_CAPABILITIES to device stack (after start)\n");
|
|
|
|
Status = IopQueryDeviceCapabilities(DeviceNode, &DeviceCapabilities);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status 0x%08lx)\n", Status);
|
|
}
|
|
|
|
/* Invalidate device state so IRP_MN_QUERY_PNP_DEVICE_STATE is sent */
|
|
IoInvalidateDeviceState(DeviceNode->PhysicalDeviceObject);
|
|
|
|
DPRINT("Sending GUID_DEVICE_ARRIVAL %wZ\n", &DeviceNode->InstancePath);
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_ARRIVAL, &DeviceNode->InstancePath);
|
|
|
|
PiSetDevNodeState(DeviceNode, DeviceNodeStarted);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS **********************************************************/
|
|
|
|
/**
|
|
* @brief Sends one of the remove IRPs to the device stack
|
|
*
|
|
* If there is a mounted VPB attached to a one of the stack devices, the IRP
|
|
* should be send to a VPB's DeviceObject first (which belongs to a FS driver).
|
|
* FS driver will then forward it down to the volume device.
|
|
* While walking the device stack, the function sets (or unsets) VPB_REMOVE_PENDING flag
|
|
* thus blocking all further mounts on a soon-to-be-removed devices
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
PiIrpSendRemoveCheckVpb(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ UCHAR MinorFunction)
|
|
{
|
|
KIRQL oldIrql;
|
|
|
|
ASSERT(MinorFunction == IRP_MN_QUERY_REMOVE_DEVICE ||
|
|
MinorFunction == IRP_MN_CANCEL_REMOVE_DEVICE ||
|
|
MinorFunction == IRP_MN_SURPRISE_REMOVAL ||
|
|
MinorFunction == IRP_MN_REMOVE_DEVICE);
|
|
|
|
PDEVICE_OBJECT vpbDevObj = DeviceObject, targetDevice = DeviceObject;
|
|
|
|
// walk the device stack down, stop on a first mounted device
|
|
do
|
|
{
|
|
if (vpbDevObj->Vpb)
|
|
{
|
|
// two locks are needed here
|
|
KeWaitForSingleObject(&vpbDevObj->DeviceLock, Executive, KernelMode, FALSE, NULL);
|
|
IoAcquireVpbSpinLock(&oldIrql);
|
|
|
|
if (MinorFunction == IRP_MN_CANCEL_REMOVE_DEVICE)
|
|
{
|
|
vpbDevObj->Vpb->Flags &= ~VPB_REMOVE_PENDING;
|
|
}
|
|
else
|
|
{
|
|
vpbDevObj->Vpb->Flags |= VPB_REMOVE_PENDING;
|
|
}
|
|
|
|
BOOLEAN isMounted = (_Bool)(vpbDevObj->Vpb->Flags & VPB_MOUNTED);
|
|
|
|
if (isMounted)
|
|
{
|
|
targetDevice = vpbDevObj->Vpb->DeviceObject;
|
|
}
|
|
|
|
IoReleaseVpbSpinLock(oldIrql);
|
|
KeSetEvent(&vpbDevObj->DeviceLock, IO_NO_INCREMENT, FALSE);
|
|
|
|
if (isMounted)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
oldIrql = KeAcquireQueuedSpinLock(LockQueueIoDatabaseLock);
|
|
vpbDevObj = vpbDevObj->AttachedDevice;
|
|
KeReleaseQueuedSpinLock(LockQueueIoDatabaseLock, oldIrql);
|
|
} while (vpbDevObj);
|
|
|
|
ASSERT(targetDevice);
|
|
|
|
PVOID info;
|
|
IO_STACK_LOCATION stack = {.MajorFunction = IRP_MJ_PNP, .MinorFunction = MinorFunction};
|
|
|
|
return IopSynchronousCall(targetDevice, &stack, &info);
|
|
}
|
|
|
|
static
|
|
VOID
|
|
NTAPI
|
|
IopSendRemoveDevice(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
PDEVICE_NODE DeviceNode = IopGetDeviceNode(DeviceObject);
|
|
|
|
ASSERT(DeviceNode->State == DeviceNodeAwaitingQueuedRemoval);
|
|
|
|
/* Drivers should never fail a IRP_MN_REMOVE_DEVICE request */
|
|
PiIrpSendRemoveCheckVpb(DeviceObject, IRP_MN_REMOVE_DEVICE);
|
|
|
|
PiSetDevNodeState(DeviceNode, DeviceNodeRemoved);
|
|
PiNotifyTargetDeviceChange(&GUID_TARGET_DEVICE_REMOVE_COMPLETE, DeviceObject, NULL);
|
|
LONG_PTR refCount = ObDereferenceObject(DeviceObject);
|
|
if (refCount != 0)
|
|
{
|
|
DPRINT1("Leaking device %wZ, refCount = %d\n", &DeviceNode->InstancePath, (INT32)refCount);
|
|
}
|
|
}
|
|
|
|
static
|
|
VOID
|
|
IopSendRemoveDeviceRelations(PDEVICE_RELATIONS DeviceRelations)
|
|
{
|
|
/* This function DOES dereference the device objects in all cases */
|
|
|
|
ULONG i;
|
|
|
|
for (i = 0; i < DeviceRelations->Count; i++)
|
|
{
|
|
IopSendRemoveDevice(DeviceRelations->Objects[i]);
|
|
DeviceRelations->Objects[i] = NULL;
|
|
}
|
|
|
|
ExFreePool(DeviceRelations);
|
|
}
|
|
|
|
static
|
|
VOID
|
|
IopSendRemoveChildDevices(PDEVICE_NODE ParentDeviceNode)
|
|
{
|
|
PDEVICE_NODE ChildDeviceNode, NextDeviceNode;
|
|
KIRQL OldIrql;
|
|
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
ChildDeviceNode = ParentDeviceNode->Child;
|
|
while (ChildDeviceNode != NULL)
|
|
{
|
|
NextDeviceNode = ChildDeviceNode->Sibling;
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
|
|
IopSendRemoveDevice(ChildDeviceNode->PhysicalDeviceObject);
|
|
|
|
ChildDeviceNode = NextDeviceNode;
|
|
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
}
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
}
|
|
|
|
static
|
|
VOID
|
|
NTAPI
|
|
IopSendSurpriseRemoval(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
ASSERT(IopGetDeviceNode(DeviceObject)->State == DeviceNodeAwaitingQueuedRemoval);
|
|
/* Drivers should never fail a IRP_MN_SURPRISE_REMOVAL request */
|
|
PiIrpSendRemoveCheckVpb(DeviceObject, IRP_MN_SURPRISE_REMOVAL);
|
|
}
|
|
|
|
static
|
|
VOID
|
|
NTAPI
|
|
IopCancelRemoveDevice(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
/* Drivers should never fail a IRP_MN_CANCEL_REMOVE_DEVICE request */
|
|
PiIrpSendRemoveCheckVpb(DeviceObject, IRP_MN_CANCEL_REMOVE_DEVICE);
|
|
|
|
PiNotifyTargetDeviceChange(&GUID_TARGET_DEVICE_REMOVE_CANCELLED, DeviceObject, NULL);
|
|
}
|
|
|
|
static
|
|
VOID
|
|
IopCancelRemoveChildDevices(PDEVICE_NODE ParentDeviceNode)
|
|
{
|
|
PDEVICE_NODE ChildDeviceNode, NextDeviceNode;
|
|
KIRQL OldIrql;
|
|
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
ChildDeviceNode = ParentDeviceNode->Child;
|
|
while (ChildDeviceNode != NULL)
|
|
{
|
|
NextDeviceNode = ChildDeviceNode->Sibling;
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
|
|
IopCancelPrepareDeviceForRemoval(ChildDeviceNode->PhysicalDeviceObject);
|
|
|
|
ChildDeviceNode = NextDeviceNode;
|
|
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
}
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
}
|
|
|
|
static
|
|
VOID
|
|
IopCancelRemoveDeviceRelations(PDEVICE_RELATIONS DeviceRelations)
|
|
{
|
|
/* This function DOES dereference the device objects in all cases */
|
|
|
|
ULONG i;
|
|
|
|
for (i = 0; i < DeviceRelations->Count; i++)
|
|
{
|
|
IopCancelPrepareDeviceForRemoval(DeviceRelations->Objects[i]);
|
|
ObDereferenceObject(DeviceRelations->Objects[i]);
|
|
DeviceRelations->Objects[i] = NULL;
|
|
}
|
|
|
|
ExFreePool(DeviceRelations);
|
|
}
|
|
|
|
static
|
|
VOID
|
|
IopCancelPrepareDeviceForRemoval(PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
IO_STACK_LOCATION Stack;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
PDEVICE_RELATIONS DeviceRelations;
|
|
NTSTATUS Status;
|
|
|
|
IopCancelRemoveDevice(DeviceObject);
|
|
|
|
Stack.Parameters.QueryDeviceRelations.Type = RemovalRelations;
|
|
|
|
Status = IopInitiatePnpIrp(DeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_DEVICE_RELATIONS,
|
|
&Stack);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed with status 0x%08lx\n", Status);
|
|
DeviceRelations = NULL;
|
|
}
|
|
else
|
|
{
|
|
DeviceRelations = (PDEVICE_RELATIONS)IoStatusBlock.Information;
|
|
}
|
|
|
|
if (DeviceRelations)
|
|
IopCancelRemoveDeviceRelations(DeviceRelations);
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
NTAPI
|
|
IopQueryRemoveDevice(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
PDEVICE_NODE DeviceNode = IopGetDeviceNode(DeviceObject);
|
|
NTSTATUS Status;
|
|
|
|
ASSERT(DeviceNode);
|
|
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_REMOVE_PENDING,
|
|
&DeviceNode->InstancePath);
|
|
|
|
Status = PiIrpSendRemoveCheckVpb(DeviceObject, IRP_MN_QUERY_REMOVE_DEVICE);
|
|
|
|
PiNotifyTargetDeviceChange(&GUID_TARGET_DEVICE_QUERY_REMOVE, DeviceObject, NULL);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Removal vetoed by %wZ\n", &DeviceNode->InstancePath);
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_REMOVAL_VETOED,
|
|
&DeviceNode->InstancePath);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IopQueryRemoveChildDevices(PDEVICE_NODE ParentDeviceNode, BOOLEAN Force)
|
|
{
|
|
PDEVICE_NODE ChildDeviceNode, NextDeviceNode, FailedRemoveDevice;
|
|
NTSTATUS Status;
|
|
KIRQL OldIrql;
|
|
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
ChildDeviceNode = ParentDeviceNode->Child;
|
|
while (ChildDeviceNode != NULL)
|
|
{
|
|
NextDeviceNode = ChildDeviceNode->Sibling;
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
PiSetDevNodeState(ChildDeviceNode, DeviceNodeAwaitingQueuedRemoval);
|
|
|
|
Status = IopPrepareDeviceForRemoval(ChildDeviceNode->PhysicalDeviceObject, Force);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FailedRemoveDevice = ChildDeviceNode;
|
|
goto cleanup;
|
|
}
|
|
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
ChildDeviceNode = NextDeviceNode;
|
|
}
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
cleanup:
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
ChildDeviceNode = ParentDeviceNode->Child;
|
|
while (ChildDeviceNode != NULL)
|
|
{
|
|
NextDeviceNode = ChildDeviceNode->Sibling;
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
|
|
IopCancelPrepareDeviceForRemoval(ChildDeviceNode->PhysicalDeviceObject);
|
|
|
|
/* IRP_MN_CANCEL_REMOVE_DEVICE is also sent to the device
|
|
* that failed the IRP_MN_QUERY_REMOVE_DEVICE request */
|
|
if (ChildDeviceNode == FailedRemoveDevice)
|
|
return Status;
|
|
|
|
ChildDeviceNode = NextDeviceNode;
|
|
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
}
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IopQueryRemoveDeviceRelations(PDEVICE_RELATIONS DeviceRelations, BOOLEAN Force)
|
|
{
|
|
/* This function DOES NOT dereference the device objects on SUCCESS
|
|
* but it DOES dereference device objects on FAILURE */
|
|
|
|
ULONG i, j;
|
|
NTSTATUS Status;
|
|
|
|
for (i = 0; i < DeviceRelations->Count; i++)
|
|
{
|
|
Status = IopPrepareDeviceForRemoval(DeviceRelations->Objects[i], Force);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
j = i;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
cleanup:
|
|
/* IRP_MN_CANCEL_REMOVE_DEVICE is also sent to the device
|
|
* that failed the IRP_MN_QUERY_REMOVE_DEVICE request */
|
|
for (i = 0; i <= j; i++)
|
|
{
|
|
IopCancelPrepareDeviceForRemoval(DeviceRelations->Objects[i]);
|
|
ObDereferenceObject(DeviceRelations->Objects[i]);
|
|
DeviceRelations->Objects[i] = NULL;
|
|
}
|
|
for (; i < DeviceRelations->Count; i++)
|
|
{
|
|
ObDereferenceObject(DeviceRelations->Objects[i]);
|
|
DeviceRelations->Objects[i] = NULL;
|
|
}
|
|
ExFreePool(DeviceRelations);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IopPrepareDeviceForRemoval(IN PDEVICE_OBJECT DeviceObject, BOOLEAN Force)
|
|
{
|
|
PDEVICE_NODE DeviceNode = IopGetDeviceNode(DeviceObject);
|
|
IO_STACK_LOCATION Stack;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
PDEVICE_RELATIONS DeviceRelations;
|
|
NTSTATUS Status;
|
|
|
|
if ((DeviceNode->UserFlags & DNUF_NOT_DISABLEABLE) && !Force)
|
|
{
|
|
DPRINT1("Removal not allowed for %wZ\n", &DeviceNode->InstancePath);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if (!Force && IopQueryRemoveDevice(DeviceObject) != STATUS_SUCCESS)
|
|
{
|
|
DPRINT1("Removal vetoed by failing the query remove request\n");
|
|
|
|
IopCancelRemoveDevice(DeviceObject);
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
Stack.Parameters.QueryDeviceRelations.Type = RemovalRelations;
|
|
|
|
Status = IopInitiatePnpIrp(DeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_DEVICE_RELATIONS,
|
|
&Stack);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed with status 0x%08lx\n", Status);
|
|
DeviceRelations = NULL;
|
|
}
|
|
else
|
|
{
|
|
DeviceRelations = (PDEVICE_RELATIONS)IoStatusBlock.Information;
|
|
}
|
|
|
|
if (DeviceRelations)
|
|
{
|
|
Status = IopQueryRemoveDeviceRelations(DeviceRelations, Force);
|
|
if (!NT_SUCCESS(Status))
|
|
return Status;
|
|
}
|
|
|
|
Status = IopQueryRemoveChildDevices(DeviceNode, Force);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (DeviceRelations)
|
|
IopCancelRemoveDeviceRelations(DeviceRelations);
|
|
return Status;
|
|
}
|
|
|
|
if (DeviceRelations)
|
|
IopSendRemoveDeviceRelations(DeviceRelations);
|
|
IopSendRemoveChildDevices(DeviceNode);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
IopRemoveDevice(PDEVICE_NODE DeviceNode)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
// This function removes the device subtree, with the root in DeviceNode
|
|
// atm everyting is in fact done inside this function, which is completely wrong.
|
|
// The right implementation should have a separate removal worker thread and
|
|
// properly do device node state transitions
|
|
|
|
DPRINT("Removing device: %wZ\n", &DeviceNode->InstancePath);
|
|
|
|
BOOLEAN surpriseRemoval = (_Bool)(DeviceNode->Flags & DNF_DEVICE_GONE);
|
|
|
|
Status = IopPrepareDeviceForRemoval(DeviceNode->PhysicalDeviceObject, surpriseRemoval);
|
|
|
|
if (surpriseRemoval)
|
|
{
|
|
IopSendSurpriseRemoval(DeviceNode->PhysicalDeviceObject);
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_SURPRISE_REMOVAL, &DeviceNode->InstancePath);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
IopSendRemoveDevice(DeviceNode->PhysicalDeviceObject);
|
|
if (surpriseRemoval)
|
|
{
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_SAFE_REMOVAL, &DeviceNode->InstancePath);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
IoInvalidateDeviceState(IN PDEVICE_OBJECT PhysicalDeviceObject)
|
|
{
|
|
PDEVICE_NODE DeviceNode = IopGetDeviceNode(PhysicalDeviceObject);
|
|
PNP_DEVICE_STATE PnPFlags;
|
|
NTSTATUS Status;
|
|
|
|
Status = PiIrpQueryPnPDeviceState(DeviceNode, &PnPFlags);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Status != STATUS_NOT_SUPPORTED)
|
|
{
|
|
DPRINT1("IRP_MN_QUERY_PNP_DEVICE_STATE failed with status 0x%lx\n", Status);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (PnPFlags & PNP_DEVICE_NOT_DISABLEABLE)
|
|
DeviceNode->UserFlags |= DNUF_NOT_DISABLEABLE;
|
|
else
|
|
DeviceNode->UserFlags &= ~DNUF_NOT_DISABLEABLE;
|
|
|
|
if (PnPFlags & PNP_DEVICE_DONT_DISPLAY_IN_UI)
|
|
DeviceNode->UserFlags |= DNUF_DONT_SHOW_IN_UI;
|
|
else
|
|
DeviceNode->UserFlags &= ~DNUF_DONT_SHOW_IN_UI;
|
|
|
|
if ((PnPFlags & PNP_DEVICE_REMOVED) ||
|
|
((PnPFlags & PNP_DEVICE_FAILED) && !(PnPFlags & PNP_DEVICE_RESOURCE_REQUIREMENTS_CHANGED)))
|
|
{
|
|
/* Flag it if it's failed */
|
|
if (PnPFlags & PNP_DEVICE_FAILED)
|
|
{
|
|
PiSetDevNodeProblem(DeviceNode, CM_PROB_FAILED_POST_START);
|
|
}
|
|
|
|
DeviceNode->Flags |= DNF_DEVICE_GONE;
|
|
PiSetDevNodeState(DeviceNode, DeviceNodeAwaitingQueuedRemoval);
|
|
}
|
|
// it doesn't work anyway. A real resource rebalancing should be implemented
|
|
#if 0
|
|
else if ((PnPFlags & PNP_DEVICE_FAILED) && (PnPFlags & PNP_DEVICE_RESOURCE_REQUIREMENTS_CHANGED))
|
|
{
|
|
/* Stop for resource rebalance */
|
|
Status = IopStopDevice(DeviceNode);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Failed to stop device for rebalancing\n");
|
|
|
|
/* Stop failed so don't rebalance */
|
|
PnPFlags &= ~PNP_DEVICE_RESOURCE_REQUIREMENTS_CHANGED;
|
|
}
|
|
}
|
|
|
|
/* Resource rebalance */
|
|
if (PnPFlags & PNP_DEVICE_RESOURCE_REQUIREMENTS_CHANGED)
|
|
{
|
|
DPRINT("Sending IRP_MN_QUERY_RESOURCES to device stack\n");
|
|
|
|
Status = IopInitiatePnpIrp(PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_RESOURCES,
|
|
NULL);
|
|
if (NT_SUCCESS(Status) && IoStatusBlock.Information)
|
|
{
|
|
DeviceNode->BootResources =
|
|
(PCM_RESOURCE_LIST)IoStatusBlock.Information;
|
|
IopDeviceNodeSetFlag(DeviceNode, DNF_HAS_BOOT_CONFIG);
|
|
}
|
|
else
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status %x) or IoStatusBlock.Information=NULL\n", Status);
|
|
DeviceNode->BootResources = NULL;
|
|
}
|
|
|
|
DPRINT("Sending IRP_MN_QUERY_RESOURCE_REQUIREMENTS to device stack\n");
|
|
|
|
Status = IopInitiatePnpIrp(PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_RESOURCE_REQUIREMENTS,
|
|
NULL);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
DeviceNode->ResourceRequirements =
|
|
(PIO_RESOURCE_REQUIREMENTS_LIST)IoStatusBlock.Information;
|
|
}
|
|
else
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed (Status %08lx)\n", Status);
|
|
DeviceNode->ResourceRequirements = NULL;
|
|
}
|
|
|
|
/* IRP_MN_FILTER_RESOURCE_REQUIREMENTS is called indirectly by IopStartDevice */
|
|
if (IopStartDevice(DeviceNode) != STATUS_SUCCESS)
|
|
{
|
|
DPRINT1("Restart after resource rebalance failed\n");
|
|
|
|
DeviceNode->Flags &= ~(DNF_STARTED | DNF_START_REQUEST_PENDING);
|
|
DeviceNode->Flags |= DNF_START_FAILED;
|
|
|
|
IopRemoveDevice(DeviceNode);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
PiEnumerateDevice(
|
|
_In_ PDEVICE_NODE DeviceNode)
|
|
{
|
|
PDEVICE_OBJECT ChildDeviceObject;
|
|
PDEVICE_NODE ChildDeviceNode;
|
|
ULONG i;
|
|
|
|
// bus relations are already obtained for this device node
|
|
|
|
if (!NT_SUCCESS(DeviceNode->CompletionStatus))
|
|
{
|
|
DPRINT("QDR request failed for %wZ, status %x\n",
|
|
&DeviceNode->InstancePath, DeviceNode->CompletionStatus);
|
|
// treat as if there are no child objects
|
|
}
|
|
|
|
PDEVICE_RELATIONS DeviceRelations = DeviceNode->OverUsed1.PendingDeviceRelations;
|
|
DeviceNode->OverUsed1.PendingDeviceRelations = NULL;
|
|
|
|
// it's acceptable not to have PDOs
|
|
if (!DeviceRelations)
|
|
{
|
|
PiSetDevNodeState(DeviceNode, DeviceNodeStarted);
|
|
DPRINT("No PDOs\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// mark children nodes as non-present (those not returned in DR request will be removed)
|
|
for (PDEVICE_NODE child = DeviceNode->Child; child != NULL; child = child->Sibling)
|
|
{
|
|
child->Flags &= ~DNF_ENUMERATED;
|
|
}
|
|
|
|
DPRINT("PiEnumerateDevice: enumerating %u children\n", DeviceRelations->Count);
|
|
|
|
// create device nodes for all new children and set DNF_ENUMERATED back for old ones
|
|
for (i = 0; i < DeviceRelations->Count; i++)
|
|
{
|
|
ChildDeviceObject = DeviceRelations->Objects[i];
|
|
ASSERT((ChildDeviceObject->Flags & DO_DEVICE_INITIALIZING) == 0);
|
|
|
|
ChildDeviceNode = IopGetDeviceNode(ChildDeviceObject);
|
|
if (!ChildDeviceNode)
|
|
{
|
|
/* One doesn't exist, create it */
|
|
ChildDeviceNode = PipAllocateDeviceNode(ChildDeviceObject);
|
|
if (ChildDeviceNode)
|
|
{
|
|
PiInsertDevNode(ChildDeviceNode, DeviceNode);
|
|
|
|
/* Mark the node as enumerated */
|
|
ChildDeviceNode->Flags |= DNF_ENUMERATED;
|
|
|
|
/* Mark the DO as bus enumerated */
|
|
ChildDeviceObject->Flags |= DO_BUS_ENUMERATED_DEVICE;
|
|
}
|
|
else
|
|
{
|
|
/* Ignore this DO */
|
|
DPRINT1("PipAllocateDeviceNode() failed. Skipping PDO %u\n", i);
|
|
ObDereferenceObject(ChildDeviceObject);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Mark it as enumerated */
|
|
ChildDeviceNode->Flags |= DNF_ENUMERATED;
|
|
ObDereferenceObject(ChildDeviceObject);
|
|
}
|
|
}
|
|
ExFreePool(DeviceRelations);
|
|
|
|
// time to remove non-reported devices
|
|
for (PDEVICE_NODE child = DeviceNode->Child; child != NULL; child = child->Sibling)
|
|
{
|
|
if (!(child->Flags & (DNF_ENUMERATED|DNF_DEVICE_GONE)))
|
|
{
|
|
// this flag indicates that this is a surprise removal
|
|
child->Flags |= DNF_DEVICE_GONE;
|
|
PiSetDevNodeState(child, DeviceNodeAwaitingQueuedRemoval);
|
|
}
|
|
}
|
|
|
|
PiSetDevNodeState(DeviceNode, DeviceNodeStarted);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
NTAPI
|
|
IopSendEject(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
IO_STACK_LOCATION Stack;
|
|
PVOID Dummy;
|
|
|
|
RtlZeroMemory(&Stack, sizeof(IO_STACK_LOCATION));
|
|
Stack.MajorFunction = IRP_MJ_PNP;
|
|
Stack.MinorFunction = IRP_MN_EJECT;
|
|
|
|
return IopSynchronousCall(DeviceObject, &Stack, &Dummy);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
IoRequestDeviceEject(IN PDEVICE_OBJECT PhysicalDeviceObject)
|
|
{
|
|
PDEVICE_NODE DeviceNode = IopGetDeviceNode(PhysicalDeviceObject);
|
|
PDEVICE_RELATIONS DeviceRelations;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
IO_STACK_LOCATION Stack;
|
|
DEVICE_CAPABILITIES Capabilities;
|
|
NTSTATUS Status;
|
|
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_KERNEL_INITIATED_EJECT,
|
|
&DeviceNode->InstancePath);
|
|
|
|
if (IopQueryDeviceCapabilities(DeviceNode, &Capabilities) != STATUS_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
Stack.Parameters.QueryDeviceRelations.Type = EjectionRelations;
|
|
|
|
Status = IopInitiatePnpIrp(PhysicalDeviceObject,
|
|
&IoStatusBlock,
|
|
IRP_MN_QUERY_DEVICE_RELATIONS,
|
|
&Stack);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT("IopInitiatePnpIrp() failed with status 0x%08lx\n", Status);
|
|
DeviceRelations = NULL;
|
|
}
|
|
else
|
|
{
|
|
DeviceRelations = (PDEVICE_RELATIONS)IoStatusBlock.Information;
|
|
}
|
|
|
|
if (DeviceRelations)
|
|
{
|
|
Status = IopQueryRemoveDeviceRelations(DeviceRelations, FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
goto cleanup;
|
|
}
|
|
|
|
Status = IopQueryRemoveChildDevices(DeviceNode, FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (DeviceRelations)
|
|
IopCancelRemoveDeviceRelations(DeviceRelations);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (IopPrepareDeviceForRemoval(PhysicalDeviceObject, FALSE) != STATUS_SUCCESS)
|
|
{
|
|
if (DeviceRelations)
|
|
IopCancelRemoveDeviceRelations(DeviceRelations);
|
|
IopCancelRemoveChildDevices(DeviceNode);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (DeviceRelations)
|
|
IopSendRemoveDeviceRelations(DeviceRelations);
|
|
IopSendRemoveChildDevices(DeviceNode);
|
|
|
|
DeviceNode->Problem = CM_PROB_HELD_FOR_EJECT;
|
|
if (Capabilities.EjectSupported)
|
|
{
|
|
if (IopSendEject(PhysicalDeviceObject) != STATUS_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// DeviceNode->Flags |= DNF_DISABLED;
|
|
}
|
|
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_EJECT,
|
|
&DeviceNode->InstancePath);
|
|
|
|
return;
|
|
|
|
cleanup:
|
|
IopQueueTargetDeviceEvent(&GUID_DEVICE_EJECT_VETOED,
|
|
&DeviceNode->InstancePath);
|
|
}
|
|
|
|
static
|
|
VOID
|
|
PiDevNodeStateMachine(
|
|
_In_ PDEVICE_NODE RootNode)
|
|
{
|
|
NTSTATUS status;
|
|
BOOLEAN doProcessAgain;
|
|
PDEVICE_NODE currentNode = RootNode;
|
|
PDEVICE_OBJECT referencedObject;
|
|
|
|
do
|
|
{
|
|
doProcessAgain = FALSE;
|
|
|
|
// The device can be removed during processing, but we still need its Parent and Sibling
|
|
// links to continue the tree traversal. So keep the link till the and of a cycle
|
|
referencedObject = currentNode->PhysicalDeviceObject;
|
|
ObReferenceObject(referencedObject);
|
|
|
|
// Devices with problems are skipped (unless they are not being removed)
|
|
if (currentNode->Flags & DNF_HAS_PROBLEM &&
|
|
currentNode->State != DeviceNodeAwaitingQueuedRemoval)
|
|
{
|
|
goto skipEnum;
|
|
}
|
|
|
|
switch (currentNode->State)
|
|
{
|
|
case DeviceNodeUnspecified: // this state is not used
|
|
break;
|
|
case DeviceNodeUninitialized:
|
|
DPRINT("DeviceNodeUninitialized %wZ\n", ¤tNode->InstancePath);
|
|
status = PiInitializeDevNode(currentNode);
|
|
doProcessAgain = NT_SUCCESS(status);
|
|
break;
|
|
case DeviceNodeInitialized:
|
|
DPRINT("DeviceNodeInitialized %wZ\n", ¤tNode->InstancePath);
|
|
status = PiCallDriverAddDevice(currentNode, PnPBootDriversInitialized);
|
|
doProcessAgain = NT_SUCCESS(status);
|
|
break;
|
|
case DeviceNodeDriversAdded:
|
|
DPRINT("DeviceNodeDriversAdded %wZ\n", ¤tNode->InstancePath);
|
|
status = IopAssignDeviceResources(currentNode);
|
|
doProcessAgain = NT_SUCCESS(status);
|
|
break;
|
|
case DeviceNodeResourcesAssigned:
|
|
DPRINT("DeviceNodeResourcesAssigned %wZ\n", ¤tNode->InstancePath);
|
|
// send IRP_MN_START_DEVICE
|
|
PiIrpStartDevice(currentNode);
|
|
|
|
// skip DeviceNodeStartPending, it is probably used for an async IRP_MN_START_DEVICE
|
|
PiSetDevNodeState(currentNode, DeviceNodeStartCompletion);
|
|
doProcessAgain = TRUE;
|
|
break;
|
|
case DeviceNodeStartPending: // skipped on XP/2003
|
|
break;
|
|
case DeviceNodeStartCompletion:
|
|
DPRINT("DeviceNodeStartCompletion %wZ\n", ¤tNode->InstancePath);
|
|
status = currentNode->CompletionStatus;
|
|
doProcessAgain = TRUE;
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
UINT32 problem = (status == STATUS_PNP_REBOOT_REQUIRED)
|
|
? CM_PROB_NEED_RESTART
|
|
: CM_PROB_FAILED_START;
|
|
|
|
PiSetDevNodeProblem(currentNode, problem);
|
|
PiSetDevNodeState(currentNode, DeviceNodeAwaitingQueuedRemoval);
|
|
}
|
|
else
|
|
{
|
|
// TODO: IopDoDeferredSetInterfaceState and IopAllocateLegacyBootResources
|
|
// are called here too
|
|
|
|
PiSetDevNodeState(currentNode, DeviceNodeStartPostWork);
|
|
}
|
|
break;
|
|
case DeviceNodeStartPostWork:
|
|
DPRINT("DeviceNodeStartPostWork %wZ\n", ¤tNode->InstancePath);
|
|
status = PiStartDeviceFinal(currentNode);
|
|
doProcessAgain = TRUE;
|
|
break;
|
|
case DeviceNodeStarted:
|
|
if (currentNode->Flags & DNF_REENUMERATE)
|
|
{
|
|
DPRINT("DeviceNodeStarted REENUMERATE %wZ\n", ¤tNode->InstancePath);
|
|
currentNode->Flags &= ~DNF_REENUMERATE;
|
|
status = PiIrpQueryDeviceRelations(currentNode, BusRelations);
|
|
|
|
// again, skip DeviceNodeEnumeratePending as with the starting sequence
|
|
PiSetDevNodeState(currentNode, DeviceNodeEnumerateCompletion);
|
|
doProcessAgain = TRUE;
|
|
}
|
|
break;
|
|
case DeviceNodeQueryStopped:
|
|
// we're here after sending IRP_MN_QUERY_STOP_DEVICE
|
|
status = currentNode->CompletionStatus;
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
PiSetDevNodeState(currentNode, DeviceNodeStopped);
|
|
}
|
|
else
|
|
{
|
|
PiIrpCancelStopDevice(currentNode);
|
|
PiSetDevNodeState(currentNode, DeviceNodeStarted);
|
|
}
|
|
break;
|
|
case DeviceNodeStopped:
|
|
// TODO: do resource rebalance (not implemented)
|
|
ASSERT(FALSE);
|
|
break;
|
|
case DeviceNodeRestartCompletion:
|
|
break;
|
|
case DeviceNodeEnumeratePending: // skipped on XP/2003
|
|
break;
|
|
case DeviceNodeEnumerateCompletion:
|
|
DPRINT("DeviceNodeEnumerateCompletion %wZ\n", ¤tNode->InstancePath);
|
|
status = PiEnumerateDevice(currentNode);
|
|
doProcessAgain = TRUE;
|
|
break;
|
|
case DeviceNodeAwaitingQueuedDeletion:
|
|
break;
|
|
case DeviceNodeAwaitingQueuedRemoval:
|
|
DPRINT("DeviceNodeAwaitingQueuedRemoval %wZ\n", ¤tNode->InstancePath);
|
|
status = IopRemoveDevice(currentNode);
|
|
break;
|
|
case DeviceNodeQueryRemoved:
|
|
break;
|
|
case DeviceNodeRemovePendingCloses:
|
|
break;
|
|
case DeviceNodeRemoved:
|
|
break;
|
|
case DeviceNodeDeletePendingCloses:
|
|
break;
|
|
case DeviceNodeDeleted:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
skipEnum:
|
|
if (!doProcessAgain)
|
|
{
|
|
KIRQL OldIrql;
|
|
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
|
|
/* If we have a child, simply go down the tree */
|
|
if (currentNode->State != DeviceNodeRemoved && currentNode->Child != NULL)
|
|
{
|
|
ASSERT(currentNode->Child->Parent == currentNode);
|
|
currentNode = currentNode->Child;
|
|
}
|
|
else
|
|
{
|
|
while (currentNode != RootNode)
|
|
{
|
|
/* All children processed -- go sideways */
|
|
if (currentNode->Sibling != NULL)
|
|
{
|
|
ASSERT(currentNode->Sibling->Parent == currentNode->Parent);
|
|
currentNode = currentNode->Sibling;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* We're the last sibling -- go back up */
|
|
ASSERT(currentNode->Parent->LastChild == currentNode);
|
|
currentNode = currentNode->Parent;
|
|
}
|
|
/* We already visited the parent and all its children, so keep looking */
|
|
}
|
|
}
|
|
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
|
|
}
|
|
ObDereferenceObject(referencedObject);
|
|
} while (doProcessAgain || currentNode != RootNode);
|
|
}
|
|
|
|
#ifdef DBG
|
|
static
|
|
PCSTR
|
|
ActionToStr(
|
|
_In_ DEVICE_ACTION Action)
|
|
{
|
|
switch (Action)
|
|
{
|
|
case PiActionEnumDeviceTree:
|
|
return "PiActionEnumDeviceTree";
|
|
case PiActionEnumRootDevices:
|
|
return "PiActionEnumRootDevices";
|
|
case PiActionResetDevice:
|
|
return "PiActionResetDevice";
|
|
case PiActionAddBootDevices:
|
|
return "PiActionAddBootDevices";
|
|
default:
|
|
return "(request unknown)";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static
|
|
VOID
|
|
NTAPI
|
|
PipDeviceActionWorker(
|
|
_In_opt_ PVOID Context)
|
|
{
|
|
PLIST_ENTRY ListEntry;
|
|
PDEVICE_ACTION_REQUEST Request;
|
|
KIRQL OldIrql;
|
|
PDEVICE_NODE deviceNode;
|
|
NTSTATUS status;
|
|
|
|
KeAcquireSpinLock(&IopDeviceActionLock, &OldIrql);
|
|
while (!IsListEmpty(&IopDeviceActionRequestList))
|
|
{
|
|
ListEntry = RemoveHeadList(&IopDeviceActionRequestList);
|
|
KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
|
|
Request = CONTAINING_RECORD(ListEntry, DEVICE_ACTION_REQUEST, RequestListEntry);
|
|
|
|
ASSERT(Request->DeviceObject);
|
|
|
|
deviceNode = IopGetDeviceNode(Request->DeviceObject);
|
|
ASSERT(deviceNode);
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
DPRINT("Processing PnP request %p: DeviceObject - %p, Action - %s\n",
|
|
Request, Request->DeviceObject, ActionToStr(Request->Action));
|
|
|
|
switch (Request->Action)
|
|
{
|
|
case PiActionAddBootDevices:
|
|
{
|
|
if (deviceNode->State == DeviceNodeInitialized &&
|
|
!(deviceNode->Flags & DNF_HAS_PROBLEM))
|
|
{
|
|
status = PiCallDriverAddDevice(deviceNode, PnPBootDriversInitialized);
|
|
}
|
|
break;
|
|
}
|
|
case PiActionEnumRootDevices:
|
|
case PiActionEnumDeviceTree:
|
|
deviceNode->Flags |= DNF_REENUMERATE;
|
|
PiDevNodeStateMachine(deviceNode);
|
|
break;
|
|
|
|
case PiActionResetDevice:
|
|
// TODO: the operation is a no-op for everything except removed nodes
|
|
// for removed nodes, it returns them back to DeviceNodeUninitialized
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
default:
|
|
DPRINT1("Unimplemented device action %u\n", Request->Action);
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
break;
|
|
}
|
|
|
|
if (Request->CompletionStatus)
|
|
{
|
|
*Request->CompletionStatus = status;
|
|
}
|
|
|
|
if (Request->CompletionEvent)
|
|
{
|
|
KeSetEvent(Request->CompletionEvent, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
|
|
DPRINT("Finished processing PnP request %p\n", Request);
|
|
ObDereferenceObject(Request->DeviceObject);
|
|
ExFreePoolWithTag(Request, TAG_IO);
|
|
KeAcquireSpinLock(&IopDeviceActionLock, &OldIrql);
|
|
}
|
|
IopDeviceActionInProgress = FALSE;
|
|
KeSetEvent(&PiEnumerationFinished, IO_NO_INCREMENT, FALSE);
|
|
KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
|
|
}
|
|
|
|
/**
|
|
* @brief Queue a device operation to a worker thread.
|
|
*
|
|
* @param[in] DeviceObject The device object
|
|
* @param[in] Action The action
|
|
* @param[in] CompletionEvent The completion event object (optional)
|
|
* @param[out] CompletionStatus Status returned be the action will be written here
|
|
*/
|
|
|
|
VOID
|
|
PiQueueDeviceAction(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ DEVICE_ACTION Action,
|
|
_In_opt_ PKEVENT CompletionEvent,
|
|
_Out_opt_ NTSTATUS *CompletionStatus)
|
|
{
|
|
PDEVICE_ACTION_REQUEST Request;
|
|
KIRQL OldIrql;
|
|
|
|
Request = ExAllocatePoolWithTag(NonPagedPoolMustSucceed, sizeof(*Request), TAG_IO);
|
|
|
|
DPRINT("PiQueueDeviceAction: DeviceObject - %p, Request - %p, Action - %s\n",
|
|
DeviceObject, Request, ActionToStr(Action));
|
|
|
|
ObReferenceObject(DeviceObject);
|
|
|
|
Request->DeviceObject = DeviceObject;
|
|
Request->Action = Action;
|
|
Request->CompletionEvent = CompletionEvent;
|
|
Request->CompletionStatus = CompletionStatus;
|
|
|
|
KeAcquireSpinLock(&IopDeviceActionLock, &OldIrql);
|
|
InsertTailList(&IopDeviceActionRequestList, &Request->RequestListEntry);
|
|
|
|
if (Action == PiActionEnumRootDevices || Action == PiActionAddBootDevices)
|
|
{
|
|
ASSERT(!IopDeviceActionInProgress);
|
|
|
|
IopDeviceActionInProgress = TRUE;
|
|
KeClearEvent(&PiEnumerationFinished);
|
|
KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
|
|
|
|
PipDeviceActionWorker(NULL);
|
|
return;
|
|
}
|
|
|
|
if (IopDeviceActionInProgress || !PnPBootDriversLoaded)
|
|
{
|
|
KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
|
|
return;
|
|
}
|
|
IopDeviceActionInProgress = TRUE;
|
|
KeClearEvent(&PiEnumerationFinished);
|
|
KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
|
|
|
|
ExInitializeWorkItem(&IopDeviceActionWorkItem, PipDeviceActionWorker, NULL);
|
|
ExQueueWorkItem(&IopDeviceActionWorkItem, DelayedWorkQueue);
|
|
}
|
|
|
|
/**
|
|
* @brief Perfom a device operation synchronously via PiQueueDeviceAction
|
|
*
|
|
* @param[in] DeviceObject The device object
|
|
* @param[in] Action The action
|
|
*
|
|
* @return Status of the operation
|
|
*/
|
|
|
|
NTSTATUS
|
|
PiPerformSyncDeviceAction(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ DEVICE_ACTION Action)
|
|
{
|
|
KEVENT opFinished;
|
|
NTSTATUS status;
|
|
|
|
KeInitializeEvent(&opFinished, SynchronizationEvent, FALSE);
|
|
PiQueueDeviceAction(DeviceObject, Action, &opFinished, &status);
|
|
KeWaitForSingleObject(&opFinished, Executive, KernelMode, FALSE, NULL);
|
|
|
|
return status;
|
|
}
|