/*
 * PROJECT:         ReactOS Kernel
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            ntoskrnl/io/iomgr/device.c
 * PURPOSE:         Device Object Management, including Notifications and Queues.
 * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
 *                  Filip Navara (navaraf@reactos.org)
 *                  Hervé Poussineau (hpoussin@reactos.org)
 */

/* INCLUDES *******************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>

/* GLOBALS ********************************************************************/

ULONG IopDeviceObjectNumber = 0;
LIST_ENTRY ShutdownListHead, LastChanceShutdownListHead;
KSPIN_LOCK ShutdownListLock;
extern LIST_ENTRY IopDiskFileSystemQueueHead;
extern LIST_ENTRY IopCdRomFileSystemQueueHead;
extern LIST_ENTRY IopTapeFileSystemQueueHead;
extern ERESOURCE IopDatabaseResource;

/* PRIVATE FUNCTIONS **********************************************************/

VOID
NTAPI
IopReadyDeviceObjects(IN PDRIVER_OBJECT Driver)
{
    PDEVICE_OBJECT DeviceObject;
    PAGED_CODE();

    /* Set the driver as initialized */
    Driver->Flags |= DRVO_INITIALIZED;
    DeviceObject = Driver->DeviceObject;
    while (DeviceObject)
    {
        /* Set every device as initialized too */
        DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
        DeviceObject = DeviceObject->NextDevice;
    }
}

VOID
NTAPI
IopDeleteDevice(IN PVOID ObjectBody)
{
    PDEVICE_OBJECT DeviceObject = ObjectBody;
    PDEVICE_NODE DeviceNode = IopGetDeviceNode(DeviceObject);
    PAGED_CODE();

    /* Cleanup and free the device node */
    if (DeviceNode)
        IopFreeDeviceNode(DeviceNode);

    /* Dereference the driver object, referenced in IoCreateDevice */
    if (DeviceObject->DriverObject)
        ObDereferenceObject(DeviceObject->DriverObject);
}


PDEVICE_OBJECT
NTAPI
IopAttachDeviceToDeviceStackSafe(IN PDEVICE_OBJECT SourceDevice,
                                 IN PDEVICE_OBJECT TargetDevice,
                                 OUT PDEVICE_OBJECT *AttachedToDeviceObject OPTIONAL)
{
    PDEVICE_OBJECT AttachedDevice;
    PEXTENDED_DEVOBJ_EXTENSION SourceDeviceExtension;

    /* Get the Attached Device and source extension */
    AttachedDevice = IoGetAttachedDevice(TargetDevice);
    SourceDeviceExtension = IoGetDevObjExtension(SourceDevice);
    ASSERT(SourceDeviceExtension->AttachedTo == NULL);

    /* Make sure that it's in a correct state */
    if ((AttachedDevice->Flags & DO_DEVICE_INITIALIZING) ||
        (IoGetDevObjExtension(AttachedDevice)->ExtensionFlags &
         (DOE_UNLOAD_PENDING |
          DOE_DELETE_PENDING |
          DOE_REMOVE_PENDING |
          DOE_REMOVE_PROCESSED)))
    {
        /* Device was unloading or being removed */
        AttachedDevice = NULL;
    }
    else
    {
        /* Update atached device fields */
        AttachedDevice->AttachedDevice = SourceDevice;
        AttachedDevice->Spare1++;

        /* Update the source with the attached data */
        SourceDevice->StackSize = AttachedDevice->StackSize + 1;
        SourceDevice->AlignmentRequirement = AttachedDevice->
                                             AlignmentRequirement;
        SourceDevice->SectorSize = AttachedDevice->SectorSize;

        /* Check for pending start flag */
        if (IoGetDevObjExtension(AttachedDevice)->ExtensionFlags &
            DOE_START_PENDING)
        {
            /* Propagate */
            IoGetDevObjExtension(SourceDevice)->ExtensionFlags |=
                DOE_START_PENDING;
        }

        /* Set the attachment in the device extension */
        SourceDeviceExtension->AttachedTo = AttachedDevice;
    }

    /* Return the attached device */
    if (AttachedToDeviceObject) *AttachedToDeviceObject = AttachedDevice;
    return AttachedDevice;
}

VOID
NTAPI
IoShutdownPnpDevices(VOID)
{
    /* This routine is only used by Driver Verifier to validate shutdown */
    return;
}

VOID
NTAPI
IoShutdownSystem(IN ULONG Phase)
{
    PLIST_ENTRY ListEntry;
    PDEVICE_OBJECT DeviceObject;
    PSHUTDOWN_ENTRY ShutdownEntry;
    IO_STATUS_BLOCK StatusBlock;
    PIRP Irp;
    KEVENT Event;
    NTSTATUS Status;

    /* Initialize an event to wait on */
    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    /* What phase? */
    if (Phase == 0)
    {
        /* Shutdown PnP */
        IoShutdownPnpDevices();

        /* Loop first-chance shutdown notifications */
        ListEntry = ExInterlockedRemoveHeadList(&ShutdownListHead,
                                                &ShutdownListLock);
        while (ListEntry)
        {
            /* Get the shutdown entry */
            ShutdownEntry = CONTAINING_RECORD(ListEntry,
                                              SHUTDOWN_ENTRY,
                                              ShutdownList);

            /* Get the attached device */
            DeviceObject = IoGetAttachedDevice(ShutdownEntry->DeviceObject);

            /* Build the shutdown IRP and call the driver */
            Irp = IoBuildSynchronousFsdRequest(IRP_MJ_SHUTDOWN,
                                               DeviceObject,
                                               NULL,
                                               0,
                                               NULL,
                                               &Event,
                                               &StatusBlock);
            if (Irp)
            {
                Status = IoCallDriver(DeviceObject, Irp);
                if (Status == STATUS_PENDING)
                {
                    /* Wait on the driver */
                    KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
                }
            }

            /* Remove the flag */
            ShutdownEntry->DeviceObject->Flags &= ~DO_SHUTDOWN_REGISTERED;

            /* Get rid of our reference to it */
            ObDereferenceObject(ShutdownEntry->DeviceObject);

            /* Free the shutdown entry and reset the event */
            ExFreePoolWithTag(ShutdownEntry, TAG_SHUTDOWN_ENTRY);
            KeClearEvent(&Event);

            /* Go to the next entry */
            ListEntry = ExInterlockedRemoveHeadList(&ShutdownListHead,
                                                    &ShutdownListLock);
         }
    }
    else if (Phase == 1)
    {
        /* Acquire resource forever */
        ExAcquireResourceExclusiveLite(&IopDatabaseResource, TRUE);

        /* Shutdown disk file systems */
        IopShutdownBaseFileSystems(&IopDiskFileSystemQueueHead);

        /* Shutdown cdrom file systems */
        IopShutdownBaseFileSystems(&IopCdRomFileSystemQueueHead);

        /* Shutdown tape filesystems */
        IopShutdownBaseFileSystems(&IopTapeFileSystemQueueHead);

        /* Loop last-chance shutdown notifications */
        ListEntry = ExInterlockedRemoveHeadList(&LastChanceShutdownListHead,
                                                &ShutdownListLock);
        while (ListEntry)
        {
            /* Get the shutdown entry */
            ShutdownEntry = CONTAINING_RECORD(ListEntry,
                                              SHUTDOWN_ENTRY,
                                              ShutdownList);

            /* Get the attached device */
            DeviceObject = IoGetAttachedDevice(ShutdownEntry->DeviceObject);

            /* Build the shutdown IRP and call the driver */
            Irp = IoBuildSynchronousFsdRequest(IRP_MJ_SHUTDOWN,
                                               DeviceObject,
                                               NULL,
                                               0,
                                               NULL,
                                               &Event,
                                               &StatusBlock);
            if (Irp)
            {
                Status = IoCallDriver(DeviceObject, Irp);
                if (Status == STATUS_PENDING)
                {
                    /* Wait on the driver */
                    KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
                }
            }

            /* Remove the flag */
            ShutdownEntry->DeviceObject->Flags &= ~DO_SHUTDOWN_REGISTERED;

            /* Get rid of our reference to it */
            ObDereferenceObject(ShutdownEntry->DeviceObject);

            /* Free the shutdown entry and reset the event */
            ExFreePoolWithTag(ShutdownEntry, TAG_SHUTDOWN_ENTRY);
            KeClearEvent(&Event);

            /* Go to the next entry */
            ListEntry = ExInterlockedRemoveHeadList(&LastChanceShutdownListHead,
                                                    &ShutdownListLock);
         }

    }
}

NTSTATUS
NTAPI
IopGetDeviceObjectPointer(IN PUNICODE_STRING ObjectName,
                          IN ACCESS_MASK DesiredAccess,
                          OUT PFILE_OBJECT *FileObject,
                          OUT PDEVICE_OBJECT *DeviceObject,
                          IN ULONG AttachFlag)
{
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK StatusBlock;
    PFILE_OBJECT LocalFileObject;
    HANDLE FileHandle;
    NTSTATUS Status;

    /* Open the Device */
    InitializeObjectAttributes(&ObjectAttributes,
                               ObjectName,
                               OBJ_KERNEL_HANDLE,
                               NULL,
                               NULL);
    Status = ZwOpenFile(&FileHandle,
                        DesiredAccess,
                        &ObjectAttributes,
                        &StatusBlock,
                        0,
                        FILE_NON_DIRECTORY_FILE | AttachFlag);
    if (!NT_SUCCESS(Status)) return Status;

    /* Get File Object */
    Status = ObReferenceObjectByHandle(FileHandle,
                                       0,
                                       IoFileObjectType,
                                       KernelMode,
                                       (PVOID*)&LocalFileObject,
                                       NULL);
    if (NT_SUCCESS(Status))
    {
        /* Return the requested data */
        *DeviceObject = IoGetRelatedDeviceObject(LocalFileObject);
        *FileObject = LocalFileObject;
    }

    /* Close the handle */
    ZwClose(FileHandle);

    return Status;
}

PDEVICE_OBJECT
NTAPI
IopGetLowestDevice(IN PDEVICE_OBJECT DeviceObject)
{
    PDEVICE_OBJECT LowestDevice;
    PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;

    /* Get the current device and its extension */
    LowestDevice = DeviceObject;
    DeviceExtension = IoGetDevObjExtension(LowestDevice);

    /* Keep looping as long as we're attached */
    while (DeviceExtension->AttachedTo)
    {
        /* Get the lowest device and its extension */
        LowestDevice = DeviceExtension->AttachedTo;
        DeviceExtension = IoGetDevObjExtension(LowestDevice);
    }

    /* Return the lowest device */
    return LowestDevice;
}

VOID
NTAPI
IopEditDeviceList(IN PDRIVER_OBJECT DriverObject,
                  IN PDEVICE_OBJECT DeviceObject,
                  IN IOP_DEVICE_LIST_OPERATION Type)
{
    PDEVICE_OBJECT Previous;
    KIRQL OldIrql;

    /* Lock the Device list while we edit it */
    OldIrql = KeAcquireQueuedSpinLock(LockQueueIoDatabaseLock);

    /* Check the type of operation */
    if (Type == IopRemove)
    {
        /* Get the current device and check if it's the current one */
        Previous = DeviceObject->DriverObject->DeviceObject;
        if (Previous == DeviceObject)
        {
            /* It is, simply unlink this one directly */
            DeviceObject->DriverObject->DeviceObject =
                DeviceObject->NextDevice;
        }
        else
        {
            /* It's not, so loop until we find the device */
            while (Previous->NextDevice != DeviceObject)
            {
                /* Not this one, keep moving */
                if (!Previous->NextDevice)
                {
                    DPRINT1("Failed to remove PDO %p on driver %wZ (not found)\n",
                            DeviceObject,
                            &DeviceObject->DriverObject->DriverName);

                    ASSERT(FALSE);
                    KeReleaseQueuedSpinLock(LockQueueIoDatabaseLock, OldIrql);
                    return;
                }
                Previous = Previous->NextDevice;
            }

            /* We found it, now unlink us */
            Previous->NextDevice = DeviceObject->NextDevice;
        }
    }
    else
    {
        /* Link the device object and the driver object */
        DeviceObject->NextDevice = DriverObject->DeviceObject;
        DriverObject->DeviceObject = DeviceObject;
    }

    /* Release the device list lock */
    KeReleaseQueuedSpinLock(LockQueueIoDatabaseLock, OldIrql);
}

VOID
NTAPI
IopUnloadDevice(IN PDEVICE_OBJECT DeviceObject)
{
    PDRIVER_OBJECT DriverObject = DeviceObject->DriverObject;
    PEXTENDED_DEVOBJ_EXTENSION ThisExtension = IoGetDevObjExtension(DeviceObject);

    /* Check if deletion is pending */
    if (ThisExtension->ExtensionFlags & DOE_DELETE_PENDING)
    {
        if (DeviceObject->AttachedDevice)
        {
            DPRINT("Device object is in the middle of a device stack\n");
            return;
        }

        if (DeviceObject->ReferenceCount)
        {
            DPRINT("Device object still has %d references\n", DeviceObject->ReferenceCount);
            return;
        }

        /* Check if we have a Security Descriptor */
        if (DeviceObject->SecurityDescriptor)
        {
            /* Dereference it */
            ObDereferenceSecurityDescriptor(DeviceObject->SecurityDescriptor, 1);
        }

        /* Remove the device from the list */
        IopEditDeviceList(DeviceObject->DriverObject, DeviceObject, IopRemove);

        /* Dereference the keep-alive */
        ObDereferenceObject(DeviceObject);
    }

    /* We can't unload a non-PnP driver here */
    if (DriverObject->Flags & DRVO_LEGACY_DRIVER)
    {
        DPRINT("Not a PnP driver! '%wZ' will not be unloaded!\n", &DriverObject->DriverName);
        return;
    }

    /* Return if we've already called unload (maybe we're in it?) */
    if (DriverObject->Flags & DRVO_UNLOAD_INVOKED) return;

    /* We can't unload unless there's an unload handler */
    if (!DriverObject->DriverUnload)
    {
        DPRINT1("No DriverUnload function on PnP driver! '%wZ' will not be unloaded!\n", &DriverObject->DriverName);
        return;
    }

    /* Bail if there are still devices present */
    if (DriverObject->DeviceObject)
    {
        DPRINT("Devices still present! '%wZ' will not be unloaded!\n", &DriverObject->DriverName);
        return;
    }

    DPRINT1("Unloading driver '%wZ' (automatic)\n", &DriverObject->DriverName);

    /* Set the unload invoked flag */
    DriverObject->Flags |= DRVO_UNLOAD_INVOKED;

    /* Unload it */
    DriverObject->DriverUnload(DriverObject);

    /* Make object temporary so it can be deleted */
    ObMakeTemporaryObject(DriverObject);
}

VOID
NTAPI
IopDereferenceDeviceObject(IN PDEVICE_OBJECT DeviceObject,
                           IN BOOLEAN ForceUnload)
{
    /* Sanity check */
    ASSERT(DeviceObject->ReferenceCount);

    /* Dereference the device */
    InterlockedDecrement(&DeviceObject->ReferenceCount);

    /*
     * Check if we can unload it and it's safe to unload (or if we're forcing
     * an unload, which is OK too).
     */
    ASSERT(!ForceUnload);
    if (!(DeviceObject->ReferenceCount) &&
        (IoGetDevObjExtension(DeviceObject)->ExtensionFlags & DOE_DELETE_PENDING))
    {
        /* Unload it */
        IopUnloadDevice(DeviceObject);
    }
}

VOID
NTAPI
IopStartNextPacketByKey(IN PDEVICE_OBJECT DeviceObject,
                        IN BOOLEAN Cancelable,
                        IN ULONG Key)
{
    PKDEVICE_QUEUE_ENTRY Entry;
    PIRP Irp;
    KIRQL OldIrql;

    /* Acquire the cancel lock if this is cancelable */
    if (Cancelable) IoAcquireCancelSpinLock(&OldIrql);

    /* Clear the current IRP */
    DeviceObject->CurrentIrp = NULL;

    /* Remove an entry from the queue */
    Entry = KeRemoveByKeyDeviceQueue(&DeviceObject->DeviceQueue, Key);
    if (Entry)
    {
        /* Get the IRP and set it */
        Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.DeviceQueueEntry);
        DeviceObject->CurrentIrp = Irp;

        /* Check if this is a cancelable packet */
        if (Cancelable)
        {
            /* Check if the caller requested no cancellation */
            if (IoGetDevObjExtension(DeviceObject)->StartIoFlags &
                DOE_SIO_NO_CANCEL)
            {
                /* He did, so remove the cancel routine */
                Irp->CancelRoutine = NULL;
            }

            /* Release the cancel lock */
            IoReleaseCancelSpinLock(OldIrql);
        }

        /* Call the Start I/O Routine */
        DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
    }
    else
    {
        /* Otherwise, release the cancel lock if we had acquired it */
        if (Cancelable) IoReleaseCancelSpinLock(OldIrql);
    }
}

VOID
NTAPI
IopStartNextPacket(IN PDEVICE_OBJECT DeviceObject,
                   IN BOOLEAN Cancelable)
{
    PKDEVICE_QUEUE_ENTRY Entry;
    PIRP Irp;
    KIRQL OldIrql;

    /* Acquire the cancel lock if this is cancelable */
    if (Cancelable) IoAcquireCancelSpinLock(&OldIrql);

    /* Clear the current IRP */
    DeviceObject->CurrentIrp = NULL;

    /* Remove an entry from the queue */
    Entry = KeRemoveDeviceQueue(&DeviceObject->DeviceQueue);
    if (Entry)
    {
        /* Get the IRP and set it */
        Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.DeviceQueueEntry);
        DeviceObject->CurrentIrp = Irp;

        /* Check if this is a cancelable packet */
        if (Cancelable)
        {
            /* Check if the caller requested no cancellation */
            if (IoGetDevObjExtension(DeviceObject)->StartIoFlags &
                DOE_SIO_NO_CANCEL)
            {
                /* He did, so remove the cancel routine */
                Irp->CancelRoutine = NULL;
            }

            /* Release the cancel lock */
            IoReleaseCancelSpinLock(OldIrql);
        }

        /* Call the Start I/O Routine */
        DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
    }
    else
    {
        /* Otherwise, release the cancel lock if we had acquired it */
        if (Cancelable) IoReleaseCancelSpinLock(OldIrql);
    }
}

VOID
NTAPI
IopStartNextPacketByKeyEx(IN PDEVICE_OBJECT DeviceObject,
                          IN ULONG Key,
                          IN ULONG Flags)
{
    PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;
    ULONG CurrentKey = Key;
    ULONG CurrentFlags = Flags;

    /* Get the device extension and start the packet loop */
    DeviceExtension = IoGetDevObjExtension(DeviceObject);
    while (TRUE)
    {
        /* Increase the count */
        if (InterlockedIncrement(&DeviceExtension->StartIoCount) > 1)
        {
            /*
             * We've already called the routine once...
             * All we have to do is save the key and add the new flags
             */
            DeviceExtension->StartIoFlags |= CurrentFlags;
            DeviceExtension->StartIoKey = CurrentKey;
        }
        else
        {
            /* Mask out the current packet flags and key */
            DeviceExtension->StartIoFlags &= ~(DOE_SIO_WITH_KEY |
                                               DOE_SIO_NO_KEY |
                                               DOE_SIO_CANCELABLE);
            DeviceExtension->StartIoKey = 0;

            /* Check if this is a packet start with key */
            if (Flags & DOE_SIO_WITH_KEY)
            {
                /* Start the packet with a key */
                IopStartNextPacketByKey(DeviceObject,
                                        (Flags & DOE_SIO_CANCELABLE) ?
                                        TRUE : FALSE,
                                        CurrentKey);
            }
            else if (Flags & DOE_SIO_NO_KEY)
            {
                /* Start the packet */
                IopStartNextPacket(DeviceObject,
                                   (Flags & DOE_SIO_CANCELABLE) ?
                                   TRUE : FALSE);
            }
        }

        /* Decrease the Start I/O count and check if it's 0 now */
        if (!InterlockedDecrement(&DeviceExtension->StartIoCount))
        {
            /* Get the current active key and flags */
            CurrentKey = DeviceExtension->StartIoKey;
            CurrentFlags = DeviceExtension->StartIoFlags & (DOE_SIO_WITH_KEY |
                                                            DOE_SIO_NO_KEY |
                                                            DOE_SIO_CANCELABLE);

            /* Check if we should still loop */
            if (!(CurrentFlags & (DOE_SIO_WITH_KEY | DOE_SIO_NO_KEY))) break;
        }
        else
        {
            /* There are still Start I/Os active, so quit this loop */
            break;
        }
    }
}

NTSTATUS
NTAPI
IopGetRelatedTargetDevice(IN PFILE_OBJECT FileObject,
                          OUT PDEVICE_NODE *DeviceNode)
{
    NTSTATUS Status;
    IO_STACK_LOCATION Stack = {0};
    PDEVICE_RELATIONS DeviceRelations;
    PDEVICE_OBJECT DeviceObject = NULL;

    ASSERT(FileObject);

    /* Get DeviceObject related to given FileObject */
    DeviceObject = IoGetRelatedDeviceObject(FileObject);
    if (!DeviceObject) return STATUS_NO_SUCH_DEVICE;

    /* Define input parameters */
    Stack.MajorFunction = IRP_MJ_PNP;
    Stack.MinorFunction = IRP_MN_QUERY_DEVICE_RELATIONS;
    Stack.Parameters.QueryDeviceRelations.Type = TargetDeviceRelation;
    Stack.FileObject = FileObject;

    /* Call the driver to query all relations (IRP_MJ_PNP) */
    Status = IopSynchronousCall(DeviceObject,
                                &Stack,
                                (PVOID)&DeviceRelations);
    if (!NT_SUCCESS(Status)) return Status;

    /* Make sure it's not NULL and contains only one object */
    ASSERT(DeviceRelations);
    ASSERT(DeviceRelations->Count == 1);

    /* Finally get the device node */
    *DeviceNode = IopGetDeviceNode(DeviceRelations->Objects[0]);
    if (!*DeviceNode) Status = STATUS_NO_SUCH_DEVICE;

    /* Free the DEVICE_RELATIONS structure, it's not needed anymore */
    ExFreePool(DeviceRelations);

    return Status;
}

/* PUBLIC FUNCTIONS ***********************************************************/

/*
 * IoAttachDevice
 *
 * Layers a device over the highest device in a device stack.
 *
 * Parameters
 *    SourceDevice
 *       Device to be attached.
 *
 *    TargetDevice
 *       Name of the target device.
 *
 *    AttachedDevice
 *       Caller storage for the device attached to.
 *
 * Status
 *    @implemented
 */
NTSTATUS
NTAPI
IoAttachDevice(PDEVICE_OBJECT SourceDevice,
               PUNICODE_STRING TargetDeviceName,
               PDEVICE_OBJECT *AttachedDevice)
{
   NTSTATUS Status;
   PFILE_OBJECT FileObject = NULL;
   PDEVICE_OBJECT TargetDevice = NULL;

    /* Call the helper routine for an attach operation */
    Status = IopGetDeviceObjectPointer(TargetDeviceName,
                                       FILE_READ_ATTRIBUTES,
                                       &FileObject,
                                       &TargetDevice,
                                       IO_ATTACH_DEVICE_API);
    if (!NT_SUCCESS(Status)) return Status;

    /* Attach the device */
    Status = IoAttachDeviceToDeviceStackSafe(SourceDevice,
                                             TargetDevice,
                                             AttachedDevice);

    /* Dereference it */
    ObDereferenceObject(FileObject);
    return Status;
}

/*
 * IoAttachDeviceByPointer
 *
 * Status
 *    @implemented
 */
NTSTATUS
NTAPI
IoAttachDeviceByPointer(IN PDEVICE_OBJECT SourceDevice,
                        IN PDEVICE_OBJECT TargetDevice)
{
    PDEVICE_OBJECT AttachedDevice;
    NTSTATUS Status = STATUS_SUCCESS;

    /* Do the Attach */
    AttachedDevice = IoAttachDeviceToDeviceStack(SourceDevice, TargetDevice);
    if (!AttachedDevice) Status = STATUS_NO_SUCH_DEVICE;

    /* Return the status */
    return Status;
}

/*
 * @implemented
 */
PDEVICE_OBJECT
NTAPI
IoAttachDeviceToDeviceStack(IN PDEVICE_OBJECT SourceDevice,
                            IN PDEVICE_OBJECT TargetDevice)
{
    /* Attach it safely */
    return IopAttachDeviceToDeviceStackSafe(SourceDevice,
                                            TargetDevice,
                                            NULL);
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
IoAttachDeviceToDeviceStackSafe(IN PDEVICE_OBJECT SourceDevice,
                                IN PDEVICE_OBJECT TargetDevice,
                                IN OUT PDEVICE_OBJECT *AttachedToDeviceObject)
{
    /* Call the internal function */
    if (!IopAttachDeviceToDeviceStackSafe(SourceDevice,
                                          TargetDevice,
                                          AttachedToDeviceObject))
    {
        /* Nothing found */
        return STATUS_NO_SUCH_DEVICE;
    }

    /* Success! */
    return STATUS_SUCCESS;
}

/*
 * IoCreateDevice
 *
 * Allocates memory for and intializes a device object for use for
 * a driver.
 *
 * Parameters
 *    DriverObject
 *       Driver object passed by IO Manager when the driver was loaded.
 *
 *    DeviceExtensionSize
 *       Number of bytes for the device extension.
 *
 *    DeviceName
 *       Unicode name of device.
 *
 *    DeviceType
 *       Device type of the new device.
 *
 *    DeviceCharacteristics
 *       Bit mask of device characteristics.
 *
 *    Exclusive
 *       TRUE if only one thread can access the device at a time.
 *
 *    DeviceObject
 *       On successful return this parameter is filled by pointer to
 *       allocated device object.
 *
 * Status
 *    @implemented
 */
NTSTATUS
NTAPI
IoCreateDevice(IN PDRIVER_OBJECT DriverObject,
               IN ULONG DeviceExtensionSize,
               IN PUNICODE_STRING DeviceName,
               IN DEVICE_TYPE DeviceType,
               IN ULONG DeviceCharacteristics,
               IN BOOLEAN Exclusive,
               OUT PDEVICE_OBJECT *DeviceObject)
{
    WCHAR AutoNameBuffer[20];
    UNICODE_STRING AutoName;
    PDEVICE_OBJECT CreatedDeviceObject;
    PDEVOBJ_EXTENSION DeviceObjectExtension;
    OBJECT_ATTRIBUTES ObjectAttributes;
    NTSTATUS Status;
    ULONG AlignedDeviceExtensionSize;
    ULONG TotalSize;
    HANDLE TempHandle;
    PAGED_CODE();

    /* Check if we have to generate a name */
    if (DeviceCharacteristics & FILE_AUTOGENERATED_DEVICE_NAME)
    {
        /* Generate it */
        swprintf(AutoNameBuffer,
                 L"\\Device\\%08lx",
                 InterlockedIncrementUL(&IopDeviceObjectNumber));

        /* Initialize the name */
        RtlInitUnicodeString(&AutoName, AutoNameBuffer);
        DeviceName = &AutoName;
    }

    /* Initialize the Object Attributes */
    InitializeObjectAttributes(&ObjectAttributes,
                               DeviceName,
                               OBJ_KERNEL_HANDLE,
                               NULL,
                               SePublicOpenUnrestrictedSd);

    /* Honor exclusive flag */
    if (Exclusive) ObjectAttributes.Attributes |= OBJ_EXCLUSIVE;

    /* Create a permanent object for named devices */
    if (DeviceName) ObjectAttributes.Attributes |= OBJ_PERMANENT;

    /* Align the Extension Size to 8-bytes */
    AlignedDeviceExtensionSize = (DeviceExtensionSize + 7) &~ 7;

    /* Total Size */
    TotalSize = AlignedDeviceExtensionSize +
                sizeof(DEVICE_OBJECT) +
                sizeof(EXTENDED_DEVOBJ_EXTENSION);

    /* Create the Device Object */
    *DeviceObject = NULL;
    Status = ObCreateObject(KernelMode,
                            IoDeviceObjectType,
                            &ObjectAttributes,
                            KernelMode,
                            NULL,
                            TotalSize,
                            0,
                            0,
                            (PVOID*)&CreatedDeviceObject);
    if (!NT_SUCCESS(Status)) return Status;

    /* Clear the whole Object and extension so we don't null stuff manually */
    RtlZeroMemory(CreatedDeviceObject, TotalSize);

    /*
     * Setup the Type and Size. Note that we don't use the aligned size,
     * because that's only padding for the DevObjExt and not part of the Object.
     */
    CreatedDeviceObject->Type = IO_TYPE_DEVICE;
    CreatedDeviceObject->Size = sizeof(DEVICE_OBJECT) + (USHORT)DeviceExtensionSize;

    /* The kernel extension is after the driver internal extension */
    DeviceObjectExtension = (PDEVOBJ_EXTENSION)
                            ((ULONG_PTR)(CreatedDeviceObject + 1) +
                             AlignedDeviceExtensionSize);

    /* Set the Type and Size. Question: why is Size 0 on Windows? */
    DeviceObjectExtension->Type = IO_TYPE_DEVICE_OBJECT_EXTENSION;
    DeviceObjectExtension->Size = 0;

    /* Initialize with Power Manager */
    PoInitializeDeviceObject(DeviceObjectExtension);

    /* Link the Object and Extension */
    DeviceObjectExtension->DeviceObject = CreatedDeviceObject;
    CreatedDeviceObject->DeviceObjectExtension = DeviceObjectExtension;

    /* Set Device Object Data */
    CreatedDeviceObject->DeviceType = DeviceType;
    CreatedDeviceObject->Characteristics = DeviceCharacteristics;
    CreatedDeviceObject->DeviceExtension = DeviceExtensionSize ?
                                           CreatedDeviceObject + 1 :
                                           NULL;
    CreatedDeviceObject->StackSize = 1;
    CreatedDeviceObject->AlignmentRequirement = 0;

    /* Set the Flags */
    CreatedDeviceObject->Flags = DO_DEVICE_INITIALIZING;
    if (Exclusive) CreatedDeviceObject->Flags |= DO_EXCLUSIVE;
    if (DeviceName) CreatedDeviceObject->Flags |= DO_DEVICE_HAS_NAME;

    /* Attach a Vpb for Disks and Tapes, and create the Device Lock */
    if ((CreatedDeviceObject->DeviceType == FILE_DEVICE_DISK) ||
        (CreatedDeviceObject->DeviceType == FILE_DEVICE_VIRTUAL_DISK) ||
        (CreatedDeviceObject->DeviceType == FILE_DEVICE_CD_ROM) ||
        (CreatedDeviceObject->DeviceType == FILE_DEVICE_TAPE))
    {
        /* Create Vpb */
        Status = IopCreateVpb(CreatedDeviceObject);
        if (!NT_SUCCESS(Status))
        {
            /* Dereference the device object and fail */
            ObDereferenceObject(CreatedDeviceObject);
            return Status;
        }

        /* Initialize Lock Event */
        KeInitializeEvent(&CreatedDeviceObject->DeviceLock,
                          SynchronizationEvent,
                          TRUE);
    }

    /* Set the right Sector Size */
    switch (DeviceType)
    {
        /* All disk systems */
        case FILE_DEVICE_DISK_FILE_SYSTEM:
        case FILE_DEVICE_DISK:
        case FILE_DEVICE_VIRTUAL_DISK:

            /* The default is 512 bytes */
            CreatedDeviceObject->SectorSize  = 512;
            break;

        /* CD-ROM file systems */
        case FILE_DEVICE_CD_ROM_FILE_SYSTEM:

            /* The default is 2048 bytes */
            CreatedDeviceObject->SectorSize = 2048;
    }

    /* Create the Device Queue */
    if ((CreatedDeviceObject->DeviceType == FILE_DEVICE_DISK_FILE_SYSTEM) ||
        (CreatedDeviceObject->DeviceType == FILE_DEVICE_FILE_SYSTEM) ||
        (CreatedDeviceObject->DeviceType == FILE_DEVICE_CD_ROM_FILE_SYSTEM) ||
        (CreatedDeviceObject->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) ||
        (CreatedDeviceObject->DeviceType == FILE_DEVICE_TAPE_FILE_SYSTEM))
    {
        /* Simple FS Devices, they don't need a real Device Queue */
        InitializeListHead(&CreatedDeviceObject->Queue.ListEntry);
    }
    else
    {
        /* An actual Device, initialize its DQ */
        KeInitializeDeviceQueue(&CreatedDeviceObject->DeviceQueue);
    }

    /* Insert the Object */
    Status = ObInsertObject(CreatedDeviceObject,
                            NULL,
                            FILE_READ_DATA | FILE_WRITE_DATA,
                            1,
                            (PVOID*)&CreatedDeviceObject,
                            &TempHandle);
    if (!NT_SUCCESS(Status)) return Status;

    /* Now do the final linking */
    ObReferenceObject(DriverObject);
    ASSERT((DriverObject->Flags & DRVO_UNLOAD_INVOKED) == 0);
    CreatedDeviceObject->DriverObject = DriverObject;
    IopEditDeviceList(DriverObject, CreatedDeviceObject, IopAdd);

    /* Link with the power manager */
    if (CreatedDeviceObject->Vpb) PoVolumeDevice(CreatedDeviceObject);

    /* Close the temporary handle and return to caller */
    ObCloseHandle(TempHandle, KernelMode);
    *DeviceObject = CreatedDeviceObject;
    return STATUS_SUCCESS;
}

/*
 * IoDeleteDevice
 *
 * Status
 *    @implemented
 */
VOID
NTAPI
IoDeleteDevice(IN PDEVICE_OBJECT DeviceObject)
{
    PIO_TIMER Timer;

    /* Check if the device is registered for shutdown notifications */
    if (DeviceObject->Flags & DO_SHUTDOWN_REGISTERED)
    {
        /* Call the shutdown notifications */
        IoUnregisterShutdownNotification(DeviceObject);
    }

    /* Check if it has a timer */
    Timer = DeviceObject->Timer;
    if (Timer)
    {
        /* Remove it and free it */
        IopRemoveTimerFromTimerList(Timer);
        ExFreePoolWithTag(Timer, TAG_IO_TIMER);
    }

    /* Check if the device has a name */
    if (DeviceObject->Flags & DO_DEVICE_HAS_NAME)
    {
        /* It does, make it temporary so we can remove it */
        ObMakeTemporaryObject(DeviceObject);
    }

    /* Set the pending delete flag */
    IoGetDevObjExtension(DeviceObject)->ExtensionFlags |= DOE_DELETE_PENDING;

    /* Unlink with the power manager */
    if (DeviceObject->Vpb) PoRemoveVolumeDevice(DeviceObject);

    /* Check if the device object can be unloaded */
    if (!DeviceObject->ReferenceCount) IopUnloadDevice(DeviceObject);
}

/*
 * IoDetachDevice
 *
 * Status
 *    @implemented
 */
VOID
NTAPI
IoDetachDevice(IN PDEVICE_OBJECT TargetDevice)
{
    PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;

    /* Sanity check */
    DeviceExtension = IoGetDevObjExtension(TargetDevice->AttachedDevice);
    ASSERT(DeviceExtension->AttachedTo == TargetDevice);

    /* Remove the attachment */
    DeviceExtension->AttachedTo = NULL;
    TargetDevice->AttachedDevice = NULL;

    /* Check if it's ok to delete this device */
    if ((IoGetDevObjExtension(TargetDevice)->ExtensionFlags & DOE_DELETE_PENDING) &&
        !(TargetDevice->ReferenceCount))
    {
        /* It is, do it */
        IopUnloadDevice(TargetDevice);
    }
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
IoEnumerateDeviceObjectList(IN  PDRIVER_OBJECT DriverObject,
                            IN  PDEVICE_OBJECT *DeviceObjectList,
                            IN  ULONG DeviceObjectListSize,
                            OUT PULONG ActualNumberDeviceObjects)
{
    ULONG ActualDevices = 1;
    PDEVICE_OBJECT CurrentDevice = DriverObject->DeviceObject;
    KIRQL OldIrql;

    /* Lock the Device list while we enumerate it */
    OldIrql = KeAcquireQueuedSpinLock(LockQueueIoDatabaseLock);

    /* Find out how many devices we'll enumerate */
    while ((CurrentDevice = CurrentDevice->NextDevice)) ActualDevices++;

    /* Go back to the first */
    CurrentDevice = DriverObject->DeviceObject;

    /* Start by at least returning this */
    *ActualNumberDeviceObjects = ActualDevices;

    /* Check if we can support so many */
    if ((ActualDevices * sizeof(PDEVICE_OBJECT)) > DeviceObjectListSize)
    {
        /* Fail because the buffer was too small */
        KeReleaseQueuedSpinLock(LockQueueIoDatabaseLock, OldIrql);
        return STATUS_BUFFER_TOO_SMALL;
    }

    /* Check if the caller wanted the device list */
    if (DeviceObjectList)
    {
        /* Loop through all the devices */
        while (ActualDevices)
        {
            /* Reference each Device */
            ObReferenceObject(CurrentDevice);

            /* Add it to the list */
            *DeviceObjectList = CurrentDevice;

            /* Go to the next one */
            CurrentDevice = CurrentDevice->NextDevice;
            ActualDevices--;
            DeviceObjectList++;
        }
    }

    /* Release the device list lock */
    KeReleaseQueuedSpinLock(LockQueueIoDatabaseLock, OldIrql);

    /* Return the status */
    return STATUS_SUCCESS;
}

/*
 * IoGetAttachedDevice
 *
 * Status
 *    @implemented
 */
PDEVICE_OBJECT
NTAPI
IoGetAttachedDevice(PDEVICE_OBJECT DeviceObject)
{
    /* Get the last attached device */
    while (DeviceObject->AttachedDevice)
    {
        /* Move to the next one */
        DeviceObject = DeviceObject->AttachedDevice;
    }

    /* Return it */
    return DeviceObject;
}

/*
 * IoGetAttachedDeviceReference
 *
 * Status
 *    @implemented
 */
PDEVICE_OBJECT
NTAPI
IoGetAttachedDeviceReference(PDEVICE_OBJECT DeviceObject)
{
    /* Reference the Attached Device */
    DeviceObject = IoGetAttachedDevice(DeviceObject);
    ObReferenceObject(DeviceObject);
    return DeviceObject;
}

/*
 * @implemented
 */
PDEVICE_OBJECT
NTAPI
IoGetDeviceAttachmentBaseRef(IN PDEVICE_OBJECT DeviceObject)
{
    /* Reference the lowest attached device */
    DeviceObject = IopGetLowestDevice(DeviceObject);
    ObReferenceObject(DeviceObject);
    return DeviceObject;
}

/*
 * IoGetDeviceObjectPointer
 *
 * Status
 *    @implemented
 */
NTSTATUS
NTAPI
IoGetDeviceObjectPointer(IN PUNICODE_STRING ObjectName,
                         IN ACCESS_MASK DesiredAccess,
                         OUT PFILE_OBJECT *FileObject,
                         OUT PDEVICE_OBJECT *DeviceObject)
{
    /* Call the helper routine for a normal operation */
    return IopGetDeviceObjectPointer(ObjectName,
                                     DesiredAccess,
                                     FileObject,
                                     DeviceObject,
                                     0);
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
IoGetDiskDeviceObject(IN PDEVICE_OBJECT FileSystemDeviceObject,
                      OUT PDEVICE_OBJECT *DiskDeviceObject)
{
    PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;
    PVPB Vpb;
    KIRQL OldIrql;
    NTSTATUS Status;

    /* Make sure there's a VPB */
    if (!FileSystemDeviceObject->Vpb) return STATUS_INVALID_PARAMETER;

    /* Acquire it */
    IoAcquireVpbSpinLock(&OldIrql);

    /* Get the Device Extension */
    DeviceExtension = IoGetDevObjExtension(FileSystemDeviceObject);

    /* Make sure this one has a VPB too */
    Vpb = DeviceExtension->Vpb;
    if (Vpb)
    {
        /* Make sure that it's mounted */
        if ((Vpb->ReferenceCount) &&
            (Vpb->Flags & VPB_MOUNTED))
        {
            /* Return the Disk Device Object */
            *DiskDeviceObject = Vpb->RealDevice;

            /* Reference it and return success */
            ObReferenceObject(Vpb->RealDevice);
            Status = STATUS_SUCCESS;
        }
        else
        {
            /* It's not, so return failure */
            Status = STATUS_VOLUME_DISMOUNTED;
        }
    }
    else
    {
        /* Fail */
        Status = STATUS_INVALID_PARAMETER;
    }

    /* Release the lock */
    IoReleaseVpbSpinLock(OldIrql);
    return Status;
}

/*
 * @implemented
 */
PDEVICE_OBJECT
NTAPI
IoGetLowerDeviceObject(IN PDEVICE_OBJECT DeviceObject)
{
    PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;
    PDEVICE_OBJECT LowerDeviceObject = NULL;

    /* Make sure it's not getting deleted */
    DeviceExtension = IoGetDevObjExtension(DeviceObject);
    if (!(DeviceExtension->ExtensionFlags & (DOE_UNLOAD_PENDING |
                                           DOE_DELETE_PENDING |
                                           DOE_REMOVE_PENDING |
                                           DOE_REMOVE_PROCESSED)))
    {
        /* Get the Lower Device Object */
        LowerDeviceObject = DeviceExtension->AttachedTo;

        /* Check that we got a valid device object */
        if (LowerDeviceObject)
        {
            /* We did so let's reference it */
            ObReferenceObject(LowerDeviceObject);
        }
    }

    /* Return it */
    return LowerDeviceObject;
}

/*
 * @implemented
 */
PDEVICE_OBJECT
NTAPI
IoGetRelatedDeviceObject(IN PFILE_OBJECT FileObject)
{
    PDEVICE_OBJECT DeviceObject = FileObject->DeviceObject;

    /* Check if we have a VPB with a device object */
    if ((FileObject->Vpb) && (FileObject->Vpb->DeviceObject))
    {
        /* Then use the DO from the VPB */
        ASSERT(!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN));
        DeviceObject = FileObject->Vpb->DeviceObject;
    }
    else if (!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN) &&
              (FileObject->DeviceObject->Vpb) &&
              (FileObject->DeviceObject->Vpb->DeviceObject))
    {
        /* The disk device actually has a VPB, so get the DO from there */
        DeviceObject = FileObject->DeviceObject->Vpb->DeviceObject;
    }
    else
    {
        /* Otherwise, this was a direct open */
        DeviceObject = FileObject->DeviceObject;
    }

    /* Sanity check */
    ASSERT(DeviceObject != NULL);

    /* Check if we were attached */
    if (DeviceObject->AttachedDevice)
    {
        /* Check if the file object has an extension present */
        if (FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
        {
            /* Sanity check, direct open files can't have this */
            ASSERT(!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN));

            /* Check if the extension is really present */
            if (FileObject->FileObjectExtension)
            {
                /* FIXME: Unhandled yet */
                DPRINT1("FOEs not supported\n");
                ASSERT(FALSE);
            }
        }

        /* Return the highest attached device */
        DeviceObject = IoGetAttachedDevice(DeviceObject);
    }

    /* Return the DO we found */
    return DeviceObject;
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
IoGetRelatedTargetDevice(IN PFILE_OBJECT FileObject,
                         OUT PDEVICE_OBJECT *DeviceObject)
{
    NTSTATUS Status;
    PDEVICE_NODE DeviceNode = NULL;

    /* Call the internal helper function */
    Status = IopGetRelatedTargetDevice(FileObject, &DeviceNode);
    if (NT_SUCCESS(Status) && DeviceNode)
    {
        *DeviceObject = DeviceNode->PhysicalDeviceObject;
    }
    return Status;
}

/*
 * @implemented
 */
PDEVICE_OBJECT
NTAPI
IoGetBaseFileSystemDeviceObject(IN PFILE_OBJECT FileObject)
{
    PDEVICE_OBJECT DeviceObject;

    /*
    * If the FILE_OBJECT's VPB is defined,
    * get the device from it.
    */
    if ((FileObject->Vpb) && (FileObject->Vpb->DeviceObject))
    {
        /* Use the VPB's Device Object's */
        DeviceObject = FileObject->Vpb->DeviceObject;
    }
    else if (!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN) &&
             (FileObject->DeviceObject->Vpb) &&
             (FileObject->DeviceObject->Vpb->DeviceObject))
    {
        /* Use the VPB's File System Object */
        DeviceObject = FileObject->DeviceObject->Vpb->DeviceObject;
    }
    else
    {
        /* Use the FO's Device Object */
        DeviceObject = FileObject->DeviceObject;
    }

    /* Return the device object we found */
    ASSERT(DeviceObject != NULL);
    return DeviceObject;
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
IoRegisterLastChanceShutdownNotification(IN PDEVICE_OBJECT DeviceObject)
{
    PSHUTDOWN_ENTRY Entry;

    /* Allocate the shutdown entry */
    Entry = ExAllocatePoolWithTag(NonPagedPool,
                                  sizeof(SHUTDOWN_ENTRY),
                                  TAG_SHUTDOWN_ENTRY);
    if (!Entry) return STATUS_INSUFFICIENT_RESOURCES;

    /* Set the DO */
    Entry->DeviceObject = DeviceObject;

    /* Reference it so it doesn't go away */
    ObReferenceObject(DeviceObject);

    /* Insert it into the list */
    ExInterlockedInsertHeadList(&LastChanceShutdownListHead,
                                &Entry->ShutdownList,
                                &ShutdownListLock);

    /* Set the shutdown registered flag */
    DeviceObject->Flags |= DO_SHUTDOWN_REGISTERED;
    return STATUS_SUCCESS;
}

/*
 * @implemented
 */
NTSTATUS
NTAPI
IoRegisterShutdownNotification(PDEVICE_OBJECT DeviceObject)
{
    PSHUTDOWN_ENTRY Entry;

    /* Allocate the shutdown entry */
    Entry = ExAllocatePoolWithTag(NonPagedPool,
                                  sizeof(SHUTDOWN_ENTRY),
                                  TAG_SHUTDOWN_ENTRY);
    if (!Entry) return STATUS_INSUFFICIENT_RESOURCES;

    /* Set the DO */
    Entry->DeviceObject = DeviceObject;

    /* Reference it so it doesn't go away */
    ObReferenceObject(DeviceObject);

    /* Insert it into the list */
    ExInterlockedInsertHeadList(&ShutdownListHead,
                                &Entry->ShutdownList,
                                &ShutdownListLock);

    /* Set the shutdown registered flag */
    DeviceObject->Flags |= DO_SHUTDOWN_REGISTERED;
    return STATUS_SUCCESS;
}

/*
 * @implemented
 */
VOID
NTAPI
IoUnregisterShutdownNotification(PDEVICE_OBJECT DeviceObject)
{
    PSHUTDOWN_ENTRY ShutdownEntry;
    PLIST_ENTRY NextEntry;
    KIRQL OldIrql;

    /* Remove the flag */
    DeviceObject->Flags &= ~DO_SHUTDOWN_REGISTERED;

    /* Acquire the shutdown lock and loop the shutdown list */
    KeAcquireSpinLock(&ShutdownListLock, &OldIrql);
    NextEntry = ShutdownListHead.Flink;
    while (NextEntry != &ShutdownListHead)
    {
        /* Get the entry */
        ShutdownEntry = CONTAINING_RECORD(NextEntry,
                                          SHUTDOWN_ENTRY,
                                          ShutdownList);

        /* Get if the DO matches */
        if (ShutdownEntry->DeviceObject == DeviceObject)
        {
            /* Remove it from the list */
            RemoveEntryList(NextEntry);
            NextEntry = NextEntry->Blink;

            /* Free the entry */
            ExFreePoolWithTag(ShutdownEntry, TAG_SHUTDOWN_ENTRY);

            /* Get rid of our reference to it */
            ObDereferenceObject(DeviceObject);
        }

        /* Go to the next entry */
        NextEntry = NextEntry->Flink;
    }

    /* Now loop the last chance list */
    NextEntry = LastChanceShutdownListHead.Flink;
    while (NextEntry != &LastChanceShutdownListHead)
    {
        /* Get the entry */
        ShutdownEntry = CONTAINING_RECORD(NextEntry,
                                          SHUTDOWN_ENTRY,
                                          ShutdownList);

        /* Get if the DO matches */
        if (ShutdownEntry->DeviceObject == DeviceObject)
        {
            /* Remove it from the list */
            RemoveEntryList(NextEntry);
            NextEntry = NextEntry->Blink;

            /* Free the entry */
            ExFreePoolWithTag(ShutdownEntry, TAG_SHUTDOWN_ENTRY);

            /* Get rid of our reference to it */
            ObDereferenceObject(DeviceObject);
        }

        /* Go to the next entry */
        NextEntry = NextEntry->Flink;
    }

    /* Release the shutdown lock */
    KeReleaseSpinLock(&ShutdownListLock, OldIrql);
}

/*
 * @implemented
 */
VOID
NTAPI
IoSetStartIoAttributes(IN PDEVICE_OBJECT DeviceObject,
                       IN BOOLEAN DeferredStartIo,
                       IN BOOLEAN NonCancelable)
{
    PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;

    /* Get the Device Extension */
    DeviceExtension = IoGetDevObjExtension(DeviceObject);

    /* Set the flags the caller requested */
    DeviceExtension->StartIoFlags |= (DeferredStartIo) ? DOE_SIO_DEFERRED : 0;
    DeviceExtension->StartIoFlags |= (NonCancelable) ? DOE_SIO_NO_CANCEL : 0;
}

/*
 * @implemented
 */
VOID
NTAPI
IoStartNextPacketByKey(IN PDEVICE_OBJECT DeviceObject,
                       IN BOOLEAN Cancelable,
                       IN ULONG Key)
{
    PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;

    /* Get the Device Extension */
    DeviceExtension = IoGetDevObjExtension(DeviceObject);

    /* Check if deferred start was requested */
    if (DeviceExtension->StartIoFlags & DOE_SIO_DEFERRED)
    {
        /* Call our internal function to handle the defered case */
        IopStartNextPacketByKeyEx(DeviceObject,
                                  Key,
                                  DOE_SIO_WITH_KEY |
                                  (Cancelable ? DOE_SIO_CANCELABLE : 0));
    }
    else
    {
        /* Call the normal routine */
        IopStartNextPacketByKey(DeviceObject, Cancelable, Key);
    }
}

/*
 * @implemented
 */
VOID
NTAPI
IoStartNextPacket(IN PDEVICE_OBJECT DeviceObject,
                  IN BOOLEAN Cancelable)
{
    PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;

    /* Get the Device Extension */
    DeviceExtension = IoGetDevObjExtension(DeviceObject);

    /* Check if deferred start was requested */
    if (DeviceExtension->StartIoFlags & DOE_SIO_DEFERRED)
    {
        /* Call our internal function to handle the defered case */
        IopStartNextPacketByKeyEx(DeviceObject,
                                  0,
                                  DOE_SIO_NO_KEY |
                                  (Cancelable ? DOE_SIO_CANCELABLE : 0));
    }
    else
    {
        /* Call the normal routine */
        IopStartNextPacket(DeviceObject, Cancelable);
    }
}

/*
 * @implemented
 */
VOID
NTAPI
IoStartPacket(IN PDEVICE_OBJECT DeviceObject,
              IN PIRP Irp,
              IN PULONG Key,
              IN PDRIVER_CANCEL CancelFunction)
{
    BOOLEAN Stat;
    KIRQL OldIrql, CancelIrql;

    /* Raise to dispatch level */
    KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

    /* Check if we should acquire the cancel lock */
    if (CancelFunction)
    {
        /* Acquire and set it */
        IoAcquireCancelSpinLock(&CancelIrql);
        Irp->CancelRoutine = CancelFunction;
    }

    /* Check if we have a key */
    if (Key)
    {
        /* Insert by key */
        Stat = KeInsertByKeyDeviceQueue(&DeviceObject->DeviceQueue,
                                        &Irp->Tail.Overlay.DeviceQueueEntry,
                                        *Key);
    }
    else
    {
        /* Insert without a key */
        Stat = KeInsertDeviceQueue(&DeviceObject->DeviceQueue,
                                   &Irp->Tail.Overlay.DeviceQueueEntry);
    }

    /* Check if this was a first insert */
    if (!Stat)
    {
        /* Set the IRP */
        DeviceObject->CurrentIrp = Irp;

        /* Check if this is a cancelable packet */
        if (CancelFunction)
        {
            /* Check if the caller requested no cancellation */
            if (IoGetDevObjExtension(DeviceObject)->StartIoFlags &
                DOE_SIO_NO_CANCEL)
            {
                /* He did, so remove the cancel routine */
                Irp->CancelRoutine = NULL;
            }

            /* Release the cancel lock */
            IoReleaseCancelSpinLock(CancelIrql);
        }

        /* Call the Start I/O function */
        DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
    }
    else
    {
        /* The packet was inserted... check if we have a cancel function */
        if (CancelFunction)
        {
            /* Check if the IRP got cancelled */
            if (Irp->Cancel)
            {
                /*
                 * Set the cancel IRQL, clear the currnet cancel routine and
                 * call ours
                 */
                Irp->CancelIrql = CancelIrql;
                Irp->CancelRoutine = NULL;
                CancelFunction(DeviceObject, Irp);
            }
            else
            {
                /* Otherwise, release the lock */
                IoReleaseCancelSpinLock(CancelIrql);
            }
        }
    }

    /* Return back to previous IRQL */
    KeLowerIrql(OldIrql);
}

#if defined (_WIN64)
ULONG
NTAPI
IoWMIDeviceObjectToProviderId(
    IN PDEVICE_OBJECT DeviceObject)
{
    UNIMPLEMENTED;
    return 0;
}
#endif

/* EOF */
