[NTOS:IO] Move device manipulation functions from pnpmgr/pnpmgr.c to pnpmgr/devaction.c
[reactos.git] / ntoskrnl / io / pnpmgr / devaction.c
diff --git a/ntoskrnl/io/pnpmgr/devaction.c b/ntoskrnl/io/pnpmgr/devaction.c
new file mode 100644 (file)
index 0000000..1687709
--- /dev/null
@@ -0,0 +1,2375 @@
+/*
+ * 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)
+ */
+
+/* INCLUDES ******************************************************************/
+
+#include <ntoskrnl.h>
+#define NDEBUG
+#include <debug.h>
+
+/* GLOBALS *******************************************************************/
+
+extern ERESOURCE IopDriverLoadResource;
+extern BOOLEAN PnpSystemInit;
+extern PDEVICE_NODE IopRootDeviceNode;
+
+#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;
+
+/* 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
+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 */
+        DeviceNode->Flags |= DNF_DISABLED;
+        RtlFreeUnicodeString(&DeviceId);
+        return STATUS_PLUGPLAY_NO_DEVICE;
+    }
+    else
+    {
+        DeviceNode->Flags &= ~DNF_DISABLED;
+    }
+
+    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;
+}
+
+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;
+}
+
+/*
+ * IopActionInterrogateDeviceStack
+ *
+ * Retrieve information for all (direct) child nodes of a parent node.
+ *
+ * Parameters
+ *    DeviceNode
+ *       Pointer to device node.
+ *    Context
+ *       Pointer to parent node to retrieve child node information for.
+ *
+ * Remarks
+ *    Any errors that occur are logged instead so that all child services have a chance
+ *    of being interrogated.
+ */
+
+NTSTATUS
+IopActionInterrogateDeviceStack(PDEVICE_NODE DeviceNode,
+                                PVOID Context)
+{
+    IO_STATUS_BLOCK IoStatusBlock;
+    PWSTR DeviceDescription;
+    PWSTR LocationInformation;
+    PDEVICE_NODE ParentDeviceNode;
+    IO_STACK_LOCATION Stack;
+    NTSTATUS Status;
+    ULONG RequiredLength;
+    LCID LocaleId;
+    HANDLE InstanceKey = NULL;
+    UNICODE_STRING ValueName;
+    UNICODE_STRING InstancePathU;
+    PDEVICE_OBJECT OldDeviceObject;
+
+    DPRINT("IopActionInterrogateDeviceStack(%p, %p)\n", DeviceNode, Context);
+    DPRINT("PDO 0x%p\n", DeviceNode->PhysicalDeviceObject);
+
+    ParentDeviceNode = (PDEVICE_NODE)Context;
+
+    /*
+     * We are called for the parent too, but we don't need to do special
+     * handling for this node
+     */
+    if (DeviceNode == ParentDeviceNode)
+    {
+        DPRINT("Success\n");
+        return STATUS_SUCCESS;
+    }
+
+    /*
+     * Make sure this device node is a direct child of the parent device node
+     * that is given as an argument
+     */
+    if (DeviceNode->Parent != ParentDeviceNode)
+    {
+        DPRINT("Skipping 2+ level child\n");
+        return STATUS_SUCCESS;
+    }
+
+    /* Skip processing if it was already completed before */
+    if (DeviceNode->Flags & DNF_PROCESSED)
+    {
+        /* Nothing to do */
+        return STATUS_SUCCESS;
+    }
+
+    /* 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);
+        }
+
+        /* We have to return success otherwise we abort the traverse operation */
+        return STATUS_SUCCESS;
+    }
+
+    /* 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);
+
+    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);
+    }
+
+    ZwClose(InstanceKey);
+
+    IopDeviceNodeSetFlag(DeviceNode, DNF_PROCESSED);
+
+    if (!IopDeviceNodeHasFlag(DeviceNode, DNF_LEGACY_DRIVER))
+    {
+        /* Report the device to the user-mode pnp manager */
+        IopQueueTargetDeviceEvent(&GUID_DEVICE_ENUMERATED,
+                                  &DeviceNode->InstancePath);
+    }
+
+    return STATUS_SUCCESS;
+}
+
+/*
+ * IopActionConfigureChildServices
+ *
+ * Retrieve configuration for all (direct) child nodes of a parent node.
+ *
+ * Parameters
+ *    DeviceNode
+ *       Pointer to device node.
+ *    Context
+ *       Pointer to parent node to retrieve child node configuration for.
+ *
+ * Remarks
+ *    Any errors that occur are logged instead so that all child services have a chance of beeing
+ *    configured.
+ */
+
+NTSTATUS
+IopActionConfigureChildServices(PDEVICE_NODE DeviceNode,
+                                PVOID Context)
+{
+    RTL_QUERY_REGISTRY_TABLE QueryTable[3];
+    PDEVICE_NODE ParentDeviceNode;
+    PUNICODE_STRING Service;
+    UNICODE_STRING ClassGUID;
+    NTSTATUS Status;
+    DEVICE_CAPABILITIES DeviceCaps;
+
+    DPRINT("IopActionConfigureChildServices(%p, %p)\n", DeviceNode, Context);
+
+    ParentDeviceNode = (PDEVICE_NODE)Context;
+
+    /*
+     * We are called for the parent too, but we don't need to do special
+     * handling for this node
+     */
+    if (DeviceNode == ParentDeviceNode)
+    {
+        DPRINT("Success\n");
+        return STATUS_SUCCESS;
+    }
+
+    /*
+     * Make sure this device node is a direct child of the parent device node
+     * that is given as an argument
+     */
+
+    if (DeviceNode->Parent != ParentDeviceNode)
+    {
+        DPRINT("Skipping 2+ level child\n");
+        return STATUS_SUCCESS;
+    }
+
+    if (!(DeviceNode->Flags & DNF_PROCESSED))
+    {
+        DPRINT1("Child not ready to be configured\n");
+        return STATUS_SUCCESS;
+    }
+
+    if (!(DeviceNode->Flags & (DNF_DISABLED | DNF_STARTED | DNF_ADDED)))
+    {
+        UNICODE_STRING RegKey;
+
+        /* Install the service for this if it's in the CDDB */
+        IopInstallCriticalDevice(DeviceNode);
+
+        /*
+         * Retrieve configuration from Enum key
+         */
+
+        Service = &DeviceNode->ServiceName;
+
+        RtlZeroMemory(QueryTable, sizeof(QueryTable));
+        RtlInitUnicodeString(Service, NULL);
+        RtlInitUnicodeString(&ClassGUID, NULL);
+
+        QueryTable[0].Name = L"Service";
+        QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
+        QueryTable[0].EntryContext = Service;
+
+        QueryTable[1].Name = L"ClassGUID";
+        QueryTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT;
+        QueryTable[1].EntryContext = &ClassGUID;
+        QueryTable[1].DefaultType = REG_SZ;
+        QueryTable[1].DefaultData = L"";
+        QueryTable[1].DefaultLength = 0;
+
+        RegKey.Length = 0;
+        RegKey.MaximumLength = sizeof(ENUM_ROOT) + sizeof(WCHAR) + DeviceNode->InstancePath.Length;
+        RegKey.Buffer = ExAllocatePoolWithTag(PagedPool,
+                                              RegKey.MaximumLength,
+                                              TAG_IO);
+        if (RegKey.Buffer == NULL)
+        {
+            IopDeviceNodeSetFlag(DeviceNode, DNF_DISABLED);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+
+        RtlAppendUnicodeToString(&RegKey, ENUM_ROOT);
+        RtlAppendUnicodeToString(&RegKey, L"\\");
+        RtlAppendUnicodeStringToString(&RegKey, &DeviceNode->InstancePath);
+
+        Status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
+            RegKey.Buffer, QueryTable, NULL, NULL);
+        ExFreePoolWithTag(RegKey.Buffer, TAG_IO);
+
+        if (!NT_SUCCESS(Status))
+        {
+            /* FIXME: Log the error */
+            DPRINT("Could not retrieve configuration for device %wZ (Status 0x%08x)\n",
+                   &DeviceNode->InstancePath, Status);
+            IopDeviceNodeSetFlag(DeviceNode, DNF_DISABLED);
+            return STATUS_SUCCESS;
+        }
+
+        if (Service->Buffer == NULL)
+        {
+            if (NT_SUCCESS(IopQueryDeviceCapabilities(DeviceNode, &DeviceCaps)) &&
+                DeviceCaps.RawDeviceOK)
+            {
+                DPRINT("%wZ is using parent bus driver (%wZ)\n", &DeviceNode->InstancePath, &ParentDeviceNode->ServiceName);
+                RtlInitEmptyUnicodeString(&DeviceNode->ServiceName, NULL, 0);
+            }
+            else if (ClassGUID.Length != 0)
+            {
+                /* Device has a ClassGUID value, but no Service value.
+                 * Suppose it is using the NULL driver, so state the
+                 * device is started */
+                DPRINT("%wZ is using NULL driver\n", &DeviceNode->InstancePath);
+                IopDeviceNodeSetFlag(DeviceNode, DNF_STARTED);
+            }
+            else
+            {
+                DeviceNode->Problem = CM_PROB_FAILED_INSTALL;
+                IopDeviceNodeSetFlag(DeviceNode, DNF_DISABLED);
+            }
+            return STATUS_SUCCESS;
+        }
+
+        DPRINT("Got Service %S\n", Service->Buffer);
+    }
+
+    return STATUS_SUCCESS;
+}
+
+/*
+ * IopActionInitChildServices
+ *
+ * Initialize the service for all (direct) child nodes of a parent node
+ *
+ * Parameters
+ *    DeviceNode
+ *       Pointer to device node.
+ *    Context
+ *       Pointer to parent node to initialize child node services for.
+ *
+ * Remarks
+ *    If the driver image for a service is not loaded and initialized
+ *    it is done here too. Any errors that occur are logged instead so
+ *    that all child services have a chance of being initialized.
+ */
+
+NTSTATUS
+IopActionInitChildServices(PDEVICE_NODE DeviceNode,
+                           PVOID Context)
+{
+    PDEVICE_NODE ParentDeviceNode;
+    NTSTATUS Status;
+    BOOLEAN BootDrivers = !PnpSystemInit;
+
+    DPRINT("IopActionInitChildServices(%p, %p)\n", DeviceNode, Context);
+
+    ParentDeviceNode = Context;
+
+    /*
+     * We are called for the parent too, but we don't need to do special
+     * handling for this node
+     */
+    if (DeviceNode == ParentDeviceNode)
+    {
+        DPRINT("Success\n");
+        return STATUS_SUCCESS;
+    }
+
+    /*
+     * We don't want to check for a direct child because
+     * this function is called during boot to reinitialize
+     * devices with drivers that couldn't load yet due to
+     * stage 0 limitations (ie can't load from disk yet).
+     */
+
+    if (!(DeviceNode->Flags & DNF_PROCESSED))
+    {
+        DPRINT1("Child not ready to be added\n");
+        return STATUS_SUCCESS;
+    }
+
+    if (IopDeviceNodeHasFlag(DeviceNode, DNF_STARTED) ||
+        IopDeviceNodeHasFlag(DeviceNode, DNF_ADDED) ||
+        IopDeviceNodeHasFlag(DeviceNode, DNF_DISABLED))
+        return STATUS_SUCCESS;
+
+    if (DeviceNode->ServiceName.Buffer == NULL)
+    {
+        /* We don't need to worry about loading the driver because we're
+         * being driven in raw mode so our parent must be loaded to get here */
+        Status = IopInitializeDevice(DeviceNode, NULL);
+        if (NT_SUCCESS(Status))
+        {
+            Status = IopStartDevice(DeviceNode);
+            if (!NT_SUCCESS(Status))
+            {
+                DPRINT1("IopStartDevice(%wZ) failed with status 0x%08x\n",
+                        &DeviceNode->InstancePath, Status);
+            }
+        }
+    }
+    else
+    {
+        PLDR_DATA_TABLE_ENTRY ModuleObject;
+        PDRIVER_OBJECT DriverObject;
+
+        KeEnterCriticalRegion();
+        ExAcquireResourceExclusiveLite(&IopDriverLoadResource, TRUE);
+        /* Get existing DriverObject pointer (in case the driver has
+           already been loaded and initialized) */
+        Status = IopGetDriverObject(
+            &DriverObject,
+            &DeviceNode->ServiceName,
+            FALSE);
+
+        if (!NT_SUCCESS(Status))
+        {
+            /* Driver is not initialized, try to load it */
+            Status = IopLoadServiceModule(&DeviceNode->ServiceName, &ModuleObject);
+
+            if (NT_SUCCESS(Status) || Status == STATUS_IMAGE_ALREADY_LOADED)
+            {
+                /* Initialize the driver */
+                Status = IopInitializeDriverModule(DeviceNode, ModuleObject,
+                    &DeviceNode->ServiceName, FALSE, &DriverObject);
+                if (!NT_SUCCESS(Status))
+                    DeviceNode->Problem = CM_PROB_FAILED_DRIVER_ENTRY;
+            }
+            else if (Status == STATUS_DRIVER_UNABLE_TO_LOAD)
+            {
+                DPRINT1("Service '%wZ' is disabled\n", &DeviceNode->ServiceName);
+                DeviceNode->Problem = CM_PROB_DISABLED_SERVICE;
+            }
+            else
+            {
+                DPRINT("IopLoadServiceModule(%wZ) failed with status 0x%08x\n",
+                       &DeviceNode->ServiceName, Status);
+                if (!BootDrivers)
+                    DeviceNode->Problem = CM_PROB_DRIVER_FAILED_LOAD;
+            }
+        }
+        ExReleaseResourceLite(&IopDriverLoadResource);
+        KeLeaveCriticalRegion();
+
+        /* Driver is loaded and initialized at this point */
+        if (NT_SUCCESS(Status))
+        {
+            /* Initialize the device, including all filters */
+            Status = PipCallDriverAddDevice(DeviceNode, FALSE, DriverObject);
+
+            /* Remove the extra reference */
+            ObDereferenceObject(DriverObject);
+        }
+        else
+        {
+            /*
+             * Don't disable when trying to load only boot drivers
+             */
+            if (!BootDrivers)
+            {
+                IopDeviceNodeSetFlag(DeviceNode, DNF_DISABLED);
+            }
+        }
+    }
+
+    return STATUS_SUCCESS;
+}
+
+static
+NTSTATUS
+IopSetServiceEnumData(PDEVICE_NODE DeviceNode)
+{
+    UNICODE_STRING ServicesKeyPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\");
+    UNICODE_STRING ServiceKeyName;
+    UNICODE_STRING EnumKeyName;
+    UNICODE_STRING ValueName;
+    PKEY_VALUE_FULL_INFORMATION KeyValueInformation;
+    HANDLE ServiceKey = NULL, ServiceEnumKey = NULL;
+    ULONG Disposition;
+    ULONG Count = 0, NextInstance = 0;
+    WCHAR ValueBuffer[6];
+    NTSTATUS Status = STATUS_SUCCESS;
+
+    DPRINT("IopSetServiceEnumData(%p)\n", DeviceNode);
+    DPRINT("Instance: %wZ\n", &DeviceNode->InstancePath);
+    DPRINT("Service: %wZ\n", &DeviceNode->ServiceName);
+
+    if (DeviceNode->ServiceName.Buffer == NULL)
+    {
+        DPRINT1("No service!\n");
+        return STATUS_SUCCESS;
+    }
+
+    ServiceKeyName.MaximumLength = ServicesKeyPath.Length + DeviceNode->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, &DeviceNode->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));
+    }
+
+done:
+    if (ServiceEnumKey != NULL)
+        ZwClose(ServiceEnumKey);
+
+    if (ServiceKey != NULL)
+        ZwClose(ServiceKey);
+
+    ExFreePool(ServiceKeyName.Buffer);
+
+    return Status;
+}
+
+static
+VOID
+NTAPI
+IopStartDevice2(IN PDEVICE_OBJECT DeviceObject)
+{
+    IO_STACK_LOCATION Stack;
+    PDEVICE_NODE DeviceNode;
+    NTSTATUS Status;
+    PVOID Dummy;
+    DEVICE_CAPABILITIES DeviceCapabilities;
+
+    /* Get the device node */
+    DeviceNode = IopGetDeviceNode(DeviceObject);
+
+    ASSERT(!(DeviceNode->Flags & DNF_DISABLED));
+
+    /* Build the I/O stack location */
+    RtlZeroMemory(&Stack, sizeof(IO_STACK_LOCATION));
+    Stack.MajorFunction = IRP_MJ_PNP;
+    Stack.MinorFunction = IRP_MN_START_DEVICE;
+
+    Stack.Parameters.StartDevice.AllocatedResources =
+         DeviceNode->ResourceList;
+    Stack.Parameters.StartDevice.AllocatedResourcesTranslated =
+         DeviceNode->ResourceListTranslated;
+
+    /* Do the call */
+    Status = IopSynchronousCall(DeviceObject, &Stack, &Dummy);
+    if (!NT_SUCCESS(Status))
+    {
+        /* Send an IRP_MN_REMOVE_DEVICE request */
+        IopRemoveDevice(DeviceNode);
+
+        /* Set the appropriate flag */
+        DeviceNode->Flags |= DNF_START_FAILED;
+        DeviceNode->Problem = CM_PROB_FAILED_START;
+
+        DPRINT1("Warning: PnP Start failed (%wZ) [Status: 0x%x]\n", &DeviceNode->InstancePath, Status);
+        return;
+    }
+
+    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(DeviceObject);
+
+    /* Otherwise, mark us as started */
+    DeviceNode->Flags |= DNF_STARTED;
+    DeviceNode->Flags &= ~DNF_STOPPED;
+
+    /* We now need enumeration */
+    DeviceNode->Flags |= DNF_NEED_ENUMERATION_ONLY;
+}
+
+static
+NTSTATUS
+NTAPI
+IopStartAndEnumerateDevice(IN PDEVICE_NODE DeviceNode)
+{
+    PDEVICE_OBJECT DeviceObject;
+    NTSTATUS Status;
+    PAGED_CODE();
+
+    /* Sanity check */
+    ASSERT((DeviceNode->Flags & DNF_ADDED));
+    ASSERT((DeviceNode->Flags & (DNF_RESOURCE_ASSIGNED |
+                                 DNF_RESOURCE_REPORTED |
+                                 DNF_NO_RESOURCE_REQUIRED)));
+
+    /* Get the device object */
+    DeviceObject = DeviceNode->PhysicalDeviceObject;
+
+    /* Check if we're not started yet */
+    if (!(DeviceNode->Flags & DNF_STARTED))
+    {
+        /* Start us */
+        IopStartDevice2(DeviceObject);
+    }
+
+    /* Do we need to query IDs? This happens in the case of manual reporting */
+#if 0
+    if (DeviceNode->Flags & DNF_NEED_QUERY_IDS)
+    {
+        DPRINT1("Warning: Device node has DNF_NEED_QUERY_IDS\n");
+        /* And that case shouldn't happen yet */
+        ASSERT(FALSE);
+    }
+#endif
+
+    IopSetServiceEnumData(DeviceNode);
+
+    /* Make sure we're started, and check if we need enumeration */
+    if ((DeviceNode->Flags & DNF_STARTED) &&
+        (DeviceNode->Flags & DNF_NEED_ENUMERATION_ONLY))
+    {
+        /* Enumerate us */
+        IoSynchronousInvalidateDeviceRelations(DeviceObject, BusRelations);
+        Status = STATUS_SUCCESS;
+    }
+    else
+    {
+        /* Nothing to do */
+        Status = STATUS_SUCCESS;
+    }
+
+    /* Return */
+    return Status;
+}
+
+NTSTATUS
+IopStartDevice(
+    PDEVICE_NODE DeviceNode)
+{
+    NTSTATUS Status;
+    HANDLE InstanceHandle = NULL, ControlHandle = NULL;
+    UNICODE_STRING KeyName, ValueString;
+    OBJECT_ATTRIBUTES ObjectAttributes;
+
+    if (DeviceNode->Flags & DNF_DISABLED)
+        return STATUS_SUCCESS;
+
+    Status = IopAssignDeviceResources(DeviceNode);
+    if (!NT_SUCCESS(Status))
+        goto ByeBye;
+
+    /* New PnP ABI */
+    IopStartAndEnumerateDevice(DeviceNode);
+
+    /* FIX: Should be done in new device instance code */
+    Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, REG_OPTION_NON_VOLATILE, &InstanceHandle);
+    if (!NT_SUCCESS(Status))
+        goto ByeBye;
+
+    /* FIX: Should be done in IoXxxPrepareDriverLoading */
+    // {
+    RtlInitUnicodeString(&KeyName, L"Control");
+    InitializeObjectAttributes(&ObjectAttributes,
+                               &KeyName,
+                               OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+                               InstanceHandle,
+                               NULL);
+    Status = ZwCreateKey(&ControlHandle,
+                         KEY_SET_VALUE,
+                         &ObjectAttributes,
+                         0,
+                         NULL,
+                         REG_OPTION_VOLATILE,
+                         NULL);
+    if (!NT_SUCCESS(Status))
+        goto ByeBye;
+
+    RtlInitUnicodeString(&KeyName, L"ActiveService");
+    ValueString = DeviceNode->ServiceName;
+    if (!ValueString.Buffer)
+        RtlInitUnicodeString(&ValueString, L"");
+    Status = ZwSetValueKey(ControlHandle, &KeyName, 0, REG_SZ, ValueString.Buffer, ValueString.Length + sizeof(UNICODE_NULL));
+    // }
+
+ByeBye:
+    if (ControlHandle != NULL)
+        ZwClose(ControlHandle);
+
+    if (InstanceHandle != NULL)
+        ZwClose(InstanceHandle);
+
+    return Status;
+}
+
+static
+NTSTATUS
+NTAPI
+IopQueryStopDevice(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_QUERY_STOP_DEVICE;
+
+    return IopSynchronousCall(DeviceObject, &Stack, &Dummy);
+}
+
+static
+VOID
+NTAPI
+IopSendStopDevice(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_STOP_DEVICE;
+
+    /* Drivers should never fail a IRP_MN_STOP_DEVICE request */
+    IopSynchronousCall(DeviceObject, &Stack, &Dummy);
+}
+
+NTSTATUS
+IopStopDevice(
+    PDEVICE_NODE DeviceNode)
+{
+    NTSTATUS Status;
+
+    DPRINT("Stopping device: %wZ\n", &DeviceNode->InstancePath);
+
+    Status = IopQueryStopDevice(DeviceNode->PhysicalDeviceObject);
+    if (NT_SUCCESS(Status))
+    {
+        IopSendStopDevice(DeviceNode->PhysicalDeviceObject);
+
+        DeviceNode->Flags &= ~(DNF_STARTED | DNF_START_REQUEST_PENDING);
+        DeviceNode->Flags |= DNF_STOPPED;
+
+        return STATUS_SUCCESS;
+    }
+
+    return Status;
+}
+
+/* PUBLIC FUNCTIONS **********************************************************/
+
+static
+VOID
+NTAPI
+IopSendRemoveDevice(IN PDEVICE_OBJECT DeviceObject)
+{
+    IO_STACK_LOCATION Stack;
+    PVOID Dummy;
+    PDEVICE_NODE DeviceNode = IopGetDeviceNode(DeviceObject);
+
+    /* Drop all our state for this device in case it isn't really going away */
+    DeviceNode->Flags &= DNF_ENUMERATED | DNF_PROCESSED;
+
+    RtlZeroMemory(&Stack, sizeof(IO_STACK_LOCATION));
+    Stack.MajorFunction = IRP_MJ_PNP;
+    Stack.MinorFunction = IRP_MN_REMOVE_DEVICE;
+
+    /* Drivers should never fail a IRP_MN_REMOVE_DEVICE request */
+    IopSynchronousCall(DeviceObject, &Stack, &Dummy);
+
+    IopNotifyPlugPlayNotification(DeviceObject,
+                                  EventCategoryTargetDeviceChange,
+                                  &GUID_TARGET_DEVICE_REMOVE_COMPLETE,
+                                  NULL,
+                                  NULL);
+    ObDereferenceObject(DeviceObject);
+}
+
+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)
+{
+    IO_STACK_LOCATION Stack;
+    PVOID Dummy;
+
+    RtlZeroMemory(&Stack, sizeof(IO_STACK_LOCATION));
+    Stack.MajorFunction = IRP_MJ_PNP;
+    Stack.MinorFunction = IRP_MN_SURPRISE_REMOVAL;
+
+    /* Drivers should never fail a IRP_MN_SURPRISE_REMOVAL request */
+    IopSynchronousCall(DeviceObject, &Stack, &Dummy);
+}
+
+static
+VOID
+NTAPI
+IopCancelRemoveDevice(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_CANCEL_REMOVE_DEVICE;
+
+    /* Drivers should never fail a IRP_MN_CANCEL_REMOVE_DEVICE request */
+    IopSynchronousCall(DeviceObject, &Stack, &Dummy);
+
+    IopNotifyPlugPlayNotification(DeviceObject,
+                                  EventCategoryTargetDeviceChange,
+                                  &GUID_TARGET_DEVICE_REMOVE_CANCELLED,
+                                  NULL,
+                                  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);
+    IO_STACK_LOCATION Stack;
+    PVOID Dummy;
+    NTSTATUS Status;
+
+    ASSERT(DeviceNode);
+
+    IopQueueTargetDeviceEvent(&GUID_DEVICE_REMOVE_PENDING,
+                              &DeviceNode->InstancePath);
+
+    RtlZeroMemory(&Stack, sizeof(IO_STACK_LOCATION));
+    Stack.MajorFunction = IRP_MJ_PNP;
+    Stack.MinorFunction = IRP_MN_QUERY_REMOVE_DEVICE;
+
+    Status = IopSynchronousCall(DeviceObject, &Stack, &Dummy);
+
+    IopNotifyPlugPlayNotification(DeviceObject,
+                                  EventCategoryTargetDeviceChange,
+                                  &GUID_TARGET_DEVICE_QUERY_REMOVE,
+                                  NULL,
+                                  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);
+
+        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
+VOID
+IopHandleDeviceRemoval(
+    IN PDEVICE_NODE DeviceNode,
+    IN PDEVICE_RELATIONS DeviceRelations)
+{
+    PDEVICE_NODE Child = DeviceNode->Child, NextChild;
+    ULONG i;
+    BOOLEAN Found;
+
+    if (DeviceNode == IopRootDeviceNode)
+        return;
+
+    while (Child != NULL)
+    {
+        NextChild = Child->Sibling;
+        Found = FALSE;
+
+        for (i = 0; DeviceRelations && i < DeviceRelations->Count; i++)
+        {
+            if (IopGetDeviceNode(DeviceRelations->Objects[i]) == Child)
+            {
+                Found = TRUE;
+                break;
+            }
+        }
+
+        if (!Found && !(Child->Flags & DNF_WILL_BE_REMOVED))
+        {
+            /* Send removal IRPs to all of its children */
+            IopPrepareDeviceForRemoval(Child->PhysicalDeviceObject, TRUE);
+
+            /* Send the surprise removal IRP */
+            IopSendSurpriseRemoval(Child->PhysicalDeviceObject);
+
+            /* Tell the user-mode PnP manager that a device was removed */
+            IopQueueTargetDeviceEvent(&GUID_DEVICE_SURPRISE_REMOVAL,
+                                      &Child->InstancePath);
+
+            /* Send the remove device IRP */
+            IopSendRemoveDevice(Child->PhysicalDeviceObject);
+        }
+
+        Child = NextChild;
+    }
+}
+
+NTSTATUS
+IopRemoveDevice(PDEVICE_NODE DeviceNode)
+{
+    NTSTATUS Status;
+
+    DPRINT("Removing device: %wZ\n", &DeviceNode->InstancePath);
+
+    Status = IopPrepareDeviceForRemoval(DeviceNode->PhysicalDeviceObject, FALSE);
+    if (NT_SUCCESS(Status))
+    {
+        IopSendRemoveDevice(DeviceNode->PhysicalDeviceObject);
+        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);
+    IO_STACK_LOCATION Stack;
+    ULONG_PTR PnPFlags;
+    NTSTATUS Status;
+    IO_STATUS_BLOCK IoStatusBlock;
+
+    RtlZeroMemory(&Stack, sizeof(IO_STACK_LOCATION));
+    Stack.MajorFunction = IRP_MJ_PNP;
+    Stack.MinorFunction = IRP_MN_QUERY_PNP_DEVICE_STATE;
+
+    Status = IopSynchronousCall(PhysicalDeviceObject, &Stack, (PVOID*)&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) DeviceNode->Problem = CM_PROB_FAILED_POST_START;
+
+        /* Send removal IRPs to all of its children */
+        IopPrepareDeviceForRemoval(PhysicalDeviceObject, TRUE);
+
+        /* Send surprise removal */
+        IopSendSurpriseRemoval(PhysicalDeviceObject);
+
+        /* Tell the user-mode PnP manager that a device was removed */
+        IopQueueTargetDeviceEvent(&GUID_DEVICE_SURPRISE_REMOVAL,
+                                  &DeviceNode->InstancePath);
+
+        IopSendRemoveDevice(PhysicalDeviceObject);
+    }
+    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);
+        }
+    }
+}
+
+/*
+ * IopInitializePnpServices
+ *
+ * Initialize services for discovered children
+ *
+ * Parameters
+ *    DeviceNode
+ *       Top device node to start initializing services.
+ *
+ * Return Value
+ *    Status
+ */
+NTSTATUS
+IopInitializePnpServices(IN PDEVICE_NODE DeviceNode)
+{
+    DEVICETREE_TRAVERSE_CONTEXT Context;
+
+    DPRINT("IopInitializePnpServices(%p)\n", DeviceNode);
+
+    IopInitDeviceTreeTraverseContext(
+        &Context,
+        DeviceNode,
+        IopActionInitChildServices,
+        DeviceNode);
+
+    return IopTraverseDeviceTree(&Context);
+}
+
+
+NTSTATUS
+IopEnumerateDevice(
+    IN PDEVICE_OBJECT DeviceObject)
+{
+    PDEVICE_NODE DeviceNode = IopGetDeviceNode(DeviceObject);
+    DEVICETREE_TRAVERSE_CONTEXT Context;
+    PDEVICE_RELATIONS DeviceRelations;
+    PDEVICE_OBJECT ChildDeviceObject;
+    IO_STATUS_BLOCK IoStatusBlock;
+    PDEVICE_NODE ChildDeviceNode;
+    IO_STACK_LOCATION Stack;
+    NTSTATUS Status;
+    ULONG i;
+
+    DPRINT("DeviceObject 0x%p\n", DeviceObject);
+
+    if (DeviceNode->Flags & DNF_NEED_ENUMERATION_ONLY)
+    {
+        DeviceNode->Flags &= ~DNF_NEED_ENUMERATION_ONLY;
+
+        DPRINT("Sending GUID_DEVICE_ARRIVAL\n");
+        IopQueueTargetDeviceEvent(&GUID_DEVICE_ARRIVAL,
+                                  &DeviceNode->InstancePath);
+    }
+
+    DPRINT("Sending IRP_MN_QUERY_DEVICE_RELATIONS to device stack\n");
+
+    Stack.Parameters.QueryDeviceRelations.Type = BusRelations;
+
+    Status = IopInitiatePnpIrp(
+        DeviceObject,
+        &IoStatusBlock,
+        IRP_MN_QUERY_DEVICE_RELATIONS,
+        &Stack);
+    if (!NT_SUCCESS(Status) || Status == STATUS_PENDING)
+    {
+        DPRINT("IopInitiatePnpIrp() failed with status 0x%08lx\n", Status);
+        return Status;
+    }
+
+    DeviceRelations = (PDEVICE_RELATIONS)IoStatusBlock.Information;
+
+    /*
+     * Send removal IRPs for devices that have disappeared
+     * NOTE: This code handles the case where no relations are specified
+     */
+    IopHandleDeviceRemoval(DeviceNode, DeviceRelations);
+
+    /* Now we bail if nothing was returned */
+    if (!DeviceRelations)
+    {
+        /* We're all done */
+        DPRINT("No PDOs\n");
+        return STATUS_SUCCESS;
+    }
+
+    DPRINT("Got %u PDOs\n", DeviceRelations->Count);
+
+    /*
+     * Create device nodes for all discovered devices
+     */
+    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 */
+            Status = IopCreateDeviceNode(
+                DeviceNode,
+                ChildDeviceObject,
+                NULL,
+                &ChildDeviceNode);
+            if (NT_SUCCESS(Status))
+            {
+                /* 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("IopCreateDeviceNode() failed with status 0x%08x. Skipping PDO %u\n", Status, i);
+                ObDereferenceObject(ChildDeviceObject);
+            }
+        }
+        else
+        {
+            /* Mark it as enumerated */
+            ChildDeviceNode->Flags |= DNF_ENUMERATED;
+            ObDereferenceObject(ChildDeviceObject);
+        }
+    }
+    ExFreePool(DeviceRelations);
+
+    /*
+     * Retrieve information about all discovered children from the bus driver
+     */
+    IopInitDeviceTreeTraverseContext(
+        &Context,
+        DeviceNode,
+        IopActionInterrogateDeviceStack,
+        DeviceNode);
+
+    Status = IopTraverseDeviceTree(&Context);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT("IopTraverseDeviceTree() failed with status 0x%08lx\n", Status);
+        return Status;
+    }
+
+    /*
+     * Retrieve configuration from the registry for discovered children
+     */
+    IopInitDeviceTreeTraverseContext(
+        &Context,
+        DeviceNode,
+        IopActionConfigureChildServices,
+        DeviceNode);
+
+    Status = IopTraverseDeviceTree(&Context);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT("IopTraverseDeviceTree() failed with status 0x%08lx\n", Status);
+        return Status;
+    }
+
+    /*
+     * Initialize services for discovered children.
+     */
+    Status = IopInitializePnpServices(DeviceNode);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT("IopInitializePnpServices() failed with status 0x%08lx\n", Status);
+        return Status;
+    }
+
+    DPRINT("IopEnumerateDevice() finished\n");
+    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
+NTAPI
+IopDeviceActionWorker(
+    _In_ PVOID Context)
+{
+    PLIST_ENTRY ListEntry;
+    PDEVICE_ACTION_DATA Data;
+    KIRQL OldIrql;
+
+    KeAcquireSpinLock(&IopDeviceActionLock, &OldIrql);
+    while (!IsListEmpty(&IopDeviceActionRequestList))
+    {
+        ListEntry = RemoveHeadList(&IopDeviceActionRequestList);
+        KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
+        Data = CONTAINING_RECORD(ListEntry,
+                                 DEVICE_ACTION_DATA,
+                                 RequestListEntry);
+
+        switch (Data->Action)
+        {
+            case DeviceActionInvalidateDeviceRelations:
+                IoSynchronousInvalidateDeviceRelations(Data->DeviceObject,
+                                                       Data->InvalidateDeviceRelations.Type);
+                break;
+
+            default:
+                DPRINT1("Unimplemented device action %u\n", Data->Action);
+                break;
+        }
+
+        ObDereferenceObject(Data->DeviceObject);
+        ExFreePoolWithTag(Data, TAG_IO);
+        KeAcquireSpinLock(&IopDeviceActionLock, &OldIrql);
+    }
+    IopDeviceActionInProgress = FALSE;
+    KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
+}
+
+VOID
+IopQueueDeviceAction(
+    _In_ PDEVICE_ACTION_DATA ActionData)
+{
+    PDEVICE_ACTION_DATA Data;
+    KIRQL OldIrql;
+
+    DPRINT("IopQueueDeviceAction(%p)\n", ActionData);
+
+    Data = ExAllocatePoolWithTag(NonPagedPool,
+                                 sizeof(DEVICE_ACTION_DATA),
+                                 TAG_IO);
+    if (!Data)
+        return;
+
+    ObReferenceObject(ActionData->DeviceObject);
+    RtlCopyMemory(Data, ActionData, sizeof(DEVICE_ACTION_DATA));
+
+    DPRINT("Action %u\n", Data->Action);
+
+    KeAcquireSpinLock(&IopDeviceActionLock, &OldIrql);
+    InsertTailList(&IopDeviceActionRequestList, &Data->RequestListEntry);
+    if (IopDeviceActionInProgress)
+    {
+        KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
+        return;
+    }
+    IopDeviceActionInProgress = TRUE;
+    KeReleaseSpinLock(&IopDeviceActionLock, OldIrql);
+
+    ExInitializeWorkItem(&IopDeviceActionWorkItem,
+                         IopDeviceActionWorker,
+                         NULL);
+    ExQueueWorkItem(&IopDeviceActionWorkItem,
+                    DelayedWorkQueue);
+}