/*++

Copyright (c) 1998-2000  Microsoft Corporation All Rights Reserved

Module Name:
   
     DummyDma.c

Abstract:   

    This sample demonstrates how to correctly setup a "packet based"
    busmaster DMA operation using system managed queue. 

    The driver is written for a hypothetical PCI busmaster capable
    device. When a user request reaches the driver (in the form of
    a Device I/O Control IRP), the IRP is queued. Upon completion of
    any previously outstanding requests, a new request is started. 
    The routine calls the ProgramTransfer function to build
    the Scatter Gather block for our "virtual" device. The transfer is
    completed when our "Pseudo ISR" (a simple, timer call back routine)
    is called. The timer DPC is queued every time we program a transfer
    operation.

    The IOCTL is defined as of type METHOD_IN_DIRECT. The output buffer
    (output buffer pointer in the call to DeviceIOControl) is described
    by the MDL present at IRP->MdlAddress when the request reaches the
    driver. This is the buffer that we will generate the scatter gather
    block that our "hypothetical" PCI device driver will perform a DMA
    read operation on.

    This sample also contains the PnP code. It does not have any power 
    mangement or WMI code required for a Windows 2000 driver. See the 
    other samples or the DDK documentation for information on how to 
    handle power management and WMI IRPs.

Author:

    Abdul Ismail (Compaq Computer Corp.)   Oct 1998

		
Environment:

    Kernel mode

Revision History:

    Peter Lee (Compaq Computer Corp.)  Feb 1999
       -    Added the PnP code. Modified from toaster 
            sample driver.

--*/

#include "DummyDma.h"				// includes wdm
#include <initguid.h>
#include "dmademo.h"				// ioctl definition


//
// Global debug error level 
//
ULONG   DDmaDebugLevel = 3;

#if DBG

//
// DDmaDbgPrint needs these header files.
//
#include "stdarg.h"
#include "stdio.h"
#include "stddef.h"

#define     TEMP_BUFFER_SIZE        1024


VOID
DDmaDbgPrint    (
    ULONG   DebugPrintLevel,
    PCCHAR  DebugMessage,
    ...
    )   
    
/*++

Routine Description:

    Debug print for the dummy dma driver.

Arguments:

    DebugPrintLevel - print level between 0 and 3, with 3 the most verbose
    
Return Value:
    
    None.
  
 --*/
 {

 va_list    list;
 UCHAR      debugMessageBuffer[TEMP_BUFFER_SIZE];

 va_start(list, DebugMessage);
 if (DebugPrintLevel <= DDmaDebugLevel) {

     DbgPrint("DummyDma.sys: ");

     vsprintf(debugMessageBuffer, DebugMessage, list);

     DbgPrint(debugMessageBuffer);
 }

 va_end(list);

 return;
 
}

#endif // DBG


PCHAR
PnPMinorFunctionString (
    UCHAR MinorFunction
);


//
// Global variable
//
GLOBALS Globals;


//
// Private routines.
//
VOID
pDummyDmaCallBack(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP           Irp,
	IN PSCATTER_GATHER_LIST ScatterGatherList,
	IN PVOID          Context 
	);

NTSTATUS
pDummyDmaCanRemoveDevice(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp
    );

NTSTATUS
pDummyDmaCanStopDevice(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp
    );

VOID
pDummyDmaCompleted(
	IN PKDPC		  Dpc,
	IN PVOID		  DeferredContext,
	IN PVOID		  SystemArgument1,
	IN PVOID		  SystemArgument2
	);
   
VOID
pDummyDmaProgramTransfer(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP           Irp 
	);

NTSTATUS
pDummyDmaReturnResources (
    IN PDEVICE_OBJECT DeviceObject
    );


#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, DummyDmaDispatchPnp)
#pragma alloc_text (PAGE, DummyDmaStartDevice)
#pragma alloc_text (PAGE, DummyDmaUnload)
#pragma alloc_text (PAGE, DummyDmaSendIrpSynchronously)
#endif


NTSTATUS
DriverEntry(
	IN PDRIVER_OBJECT   DriverObject,
	IN PUNICODE_STRING  RegistryPath 
	)
/*++

Routine Description:

    This routine is called at system initialization time to initialize
    this driver.

Arguments:

    DriverObject    - Supplies the driver object.

    RegistryPath    - Supplies the registry path to driver-specific key in the registry.

Return Value:

    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise.

--*/
{
    DD_DebugPrint ((3, "Entered the driver!\n"));

    //
    // Save the RegistryPath.
    //

    Globals.RegistryPath.MaximumLength = RegistryPath->Length +
                                          sizeof(UNICODE_NULL);
    Globals.RegistryPath.Length = RegistryPath->Length;
    Globals.RegistryPath.Buffer = ExAllocatePool(
                                       PagedPool,
                                       Globals.RegistryPath.MaximumLength
                                       );    

    if (!Globals.RegistryPath.Buffer) {

        DD_DebugPrint ((3, 
                "Couldn't allocate pool for registry path.\n"));

        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlCopyUnicodeString(&(Globals.RegistryPath), RegistryPath);

    //
    // Set up the dispatch entry points that we support.
    //
    DriverObject->MajorFunction[IRP_MJ_CREATE]         = 
    DriverObject->MajorFunction[IRP_MJ_CLOSE]          = DummyDmaCreateClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DummyDmaDispatch;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP]        = DummyDmaCleanup;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = DummyDmaSystemControl;
    DriverObject->DriverStartIo                        = DummyDmaStartIo;               
    DriverObject->DriverUnload                         = DummyDmaUnload;

    //
    // PnP and Power entry points.
    //
    DriverObject->MajorFunction[IRP_MJ_PNP]            =  DummyDmaDispatchPnp;
    DriverObject->MajorFunction[IRP_MJ_POWER]          =  DummyDmaDispatchPower;

    //
    // AddDevice Function.
    //
    DriverObject->DriverExtension->AddDevice = DummyDmaAddDevice;

    return(STATUS_SUCCESS);

}


NTSTATUS
DummyDmaAddDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT PhysicalDeviceObject
    )
/*++

Routine Description:

    The Plug and Play subsystem is handing us a brand new PDO, for which we
    (by means of INF registration) have been asked to provide a driver.

    We need to determine if we need to be in the driver stack for the device.
    Create a functional device object to attach to the stack.
    Initialize that device object.
    Return status success.

    Remember: we can NOT actually send ANY non pnp IRPS to the given driver
    stack, UNTIL we have received an IRP_MN_START_DEVICE.

Arguments:

    DriverObject - pointer to a driver object.

    PhysicalDeviceObject -  pointer to a device object created by the
                            underlying bus driver.

Return Value:

    NT status code.

--*/
{
    NTSTATUS                    status = STATUS_SUCCESS;
    PDEVICE_OBJECT              DeviceObject = NULL;
    PDUMMYDMA_DEVICE_EXTENSION  DeviceExtension;

    DD_DebugPrint ((3, "AddDevice PDO (0x%x)\n", PhysicalDeviceObject));

    //
    // Remember that you CANNOT send an IRP to the PDO because it has not
    // been started as of yet, but you can make Plug and Play queries to find
    // out things like hardware, compatible ID's, etc.
    //

 
    //
    // Create a functional device object.
    //
    status = IoCreateDevice(
                 DriverObject,
                 sizeof (DUMMYDMA_DEVICE_EXTENSION),
                 NULL,
                 FILE_DEVICE_UNKNOWN,
                 0,                     // No standard device characteristics
                 FALSE,                 // This isn't an exclusive device
                 &DeviceObject
                 );

    
    if (!NT_SUCCESS (status)) {
		
        DD_DebugPrint ((0, "Cannot create device object. Status = 0x%0x\n", status));
	
        return status;
    }

    DD_DebugPrint ((3, "AddDevice FDO (0x%x)\n", DeviceObject));

    //
    // Initialize the device extension.
    //
    DeviceExtension = (PDUMMYDMA_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //
    // The device is not started
    //

    DeviceExtension->DevicePnPState = NotStarted;

    //
    // We will ignore all requests until start.
    //
    DeviceExtension->HoldNewRequests = FALSE;
    DeviceExtension->Self = DeviceObject;
    DeviceExtension->PDO = PhysicalDeviceObject;
    DeviceExtension->NextLowerDriver = NULL;

    //
    // Initialize the remove event to Not-Signaled.
    //
    KeInitializeEvent(&DeviceExtension->RemoveEvent, 
                      SynchronizationEvent, 
                      FALSE);

    //  
    // Initialize the stop event to Signaled:
    // there are no Irps that prevent the device from being 
    // stopped. This event will be set when the OutstandingIO
    // becomes 0.
    //
    KeInitializeEvent(&DeviceExtension->StopEvent, 
                      SynchronizationEvent, 
                      TRUE);

    DeviceExtension->OutstandingIO = 1; // biased to 1.  Transition to zero during
                                        // remove device means IO is finished.
                                        // Transition to 1 means the device can be 
                                        // stopped.

    DeviceObject->Flags |= DO_POWER_PAGABLE;
    DeviceObject->Flags |= DO_DIRECT_IO;

    //
    // Attach our driver to the device stack.
    //
    DeviceExtension->NextLowerDriver = IoAttachDeviceToDeviceStack (DeviceObject,
                                                       PhysicalDeviceObject);

    //
    // If this attachment fails then top level deviceobject is being deleted. 
    // A broken pnp system.
    //
    if (DeviceExtension->NextLowerDriver == NULL) {

        DD_DebugPrint ((0, "IoAttachDeviceToDeviceStack failed."));

        //
        // Delete our device object
        //
        IoDeleteDevice( DriverObject->DeviceObject );

        return STATUS_UNSUCCESSFUL;

    }

    //
    // Tell the Plug and Play system that this device will need an interface
    // device class shingle.
    //
    
    status = IoRegisterDeviceInterface (
                PhysicalDeviceObject,
                (LPGUID) &GUID_DUMMYDMA_INTERFACE,
                NULL, 
                &DeviceExtension->SymbolicLinkName);

    if (!NT_SUCCESS (status)) {
        DD_DebugPrint ((0,
            "AddDevice: IoRegisterDeviceInterface failed (%x)\n", status));
        IoDetachDevice(DeviceExtension->NextLowerDriver);
        IoDeleteDevice (DeviceObject);
        return status;
    }

    //
    //  Clear the Device Initializing bit since the Device Object was created
    //  outside of DriverEntry.
    //    
    DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    return STATUS_SUCCESS;

}


NTSTATUS
DummyDmaStartDevice(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is called from the DummyDmaDispatchPnp to actually start 
	the hardware. Performs whatever initialization is needed for a device 
	when it is started.

Arguments:

    DeviceObject - Pointer to a device object.

    Irp -- Pointer to current IRP.

Return Value:

    NT status code.

--*/
{
    DEVICE_DESCRIPTION          DeviceDescription;
    NTSTATUS                    status = STATUS_SUCCESS;
    PDUMMYDMA_DEVICE_EXTENSION	DeviceExtension = 
        (PDUMMYDMA_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resource;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resourceTrans;
    PCM_PARTIAL_RESOURCE_LIST   partialResourceList;
    PCM_PARTIAL_RESOURCE_LIST   partialResourceListTranslated;
    PIO_STACK_LOCATION  stack;
    ULONG i;

    stack = IoGetCurrentIrpStackLocation (Irp);

    PAGED_CODE();

    //
    // Do whatever initialization is needed when starting the device: 
    // gather information about it,  update the registry, etc.
    //
    // With "real" hardware one would search the allocated resource list 
    // (IoStackLocation->Parameters.StartDevice.*, where IoStackLocation is
    // the current irp stack location) to figure out the correct resources 
    // required by the hardware. In this case, we will fake a PCI bus master 
    // capable DMA device and set up an Adapter Object to perform Dummy 
    // DMA operations.
    //

    if ((NULL != stack->Parameters.StartDevice.AllocatedResources) &&
        (NULL != stack->Parameters.StartDevice.AllocatedResourcesTranslated)) {

        // AllocatedResources contains the list of resources that the driver 
        // requested, therefore, drivers should search this list when looking 
        // for I/O or memory resources. AllocatedResourcesTranslated contains 
        // the list of resources which represent how the HAL accesses those 
        // resources, i.e. the resources used in the REGISTER and PORT routines. 
        // Confusing these two resource lists can lead to system hangs or crashes! 

        //
        // Parameters.StartDevice.AllocatedResources points to a 
        // CM_RESOURCE_LIST describing the hardware resources that 
        // the PnP Manager assigned to the device. This list contains 
        // the resources in raw form. 
        //

        partialResourceList = 
            &stack->Parameters.StartDevice.AllocatedResources->List[0].PartialResourceList;

        resource = &partialResourceList->PartialDescriptors[0];
    
        //
        // Parameters.StartDevice.AllocatedResourcesTranslated points 
        // to a CM_RESOURCE_LIST describing the hardware resources that 
        // the PnP Manager assigned to the device. This list contains 
        // the resources in translated form. Use the translated resources 
        // to connect the interrupt vector, map I/O space, and map memory.
        //

        partialResourceListTranslated = 
            &stack->Parameters.StartDevice.AllocatedResourcesTranslated->List[0].PartialResourceList;

        resourceTrans = &partialResourceListTranslated->PartialDescriptors[0];

        for (i = 0;
                i < partialResourceList->Count; i++, resource++, resourceTrans++) {

            switch (resource->Type) {
            case CmResourceTypePort:

                DD_DebugPrint((2, "Resource RAW Port: (%x) Length: (%d)\n", 
                    resource->u.Port.Start.LowPart, resource->u.Port.Length));

                switch (resourceTrans->Type) {

                case CmResourceTypePort:

                    ASSERT (resourceTrans->u.Port.Length == resource->u.Port.Length);
                
                    DD_DebugPrint((2, 
                        "Resource Translated Port: (%x) Length: (%d)\n", 
                        resourceTrans->u.Port.Start.LowPart, 
                        resourceTrans->u.Port.Length));

                    break;

                case CmResourceTypeMemory:

                    ASSERT (resource->u.Port.Length == resourceTrans->u.Memory.Length);
                                
                    DD_DebugPrint((2, 
                        "Resource Translated Memory: (%x) Length: (%d)\n", 
                        resourceTrans->u.Memory.Start.LowPart, 
                        resourceTrans->u.Memory.Length));

                    break;

                default:
                    DD_DebugPrint((0, "Unhandled resource_type (0x%x)\n", 
                                            resourceTrans->Type));
                }             

                break;

            case CmResourceTypeMemory:
        
                DD_DebugPrint((2, "Resource RAW Memory: (%x) Length: (%d)\n", 
                    resource->u.Memory.Start.LowPart, resource->u.Memory.Length));
                
                DD_DebugPrint((2, "Resource Translated Memory: (%x) Length: (%d)\n", 
                    resourceTrans->u.Memory.Start.LowPart, resourceTrans->u.Memory.Length));

                break;

            case CmResourceTypeInterrupt:
            default:

                DD_DebugPrint((0, "Unhandled resource type (0x%x)\n", resource->Type));
                break;
    
            }
        }
    }


    RtlZeroMemory(&DeviceDescription, sizeof(DEVICE_DESCRIPTION));
    DeviceDescription.Version           = DEVICE_DESCRIPTION_VERSION;

    DeviceDescription.Master            = TRUE;                 // busmaster adapter
    DeviceDescription.ScatterGather     = TRUE;                 // Device supports scatter/gather

    DeviceDescription.Dma32BitAddresses = TRUE;

    DeviceDescription.Reserved1         = FALSE;

    DeviceDescription.InterfaceType     = PCIBus;               // Replace with actual BusType

    DeviceDescription.MaximumLength     = 0x4000 * 0x2000;      // Should be the max. possible that
                                                                // the adapter can handle.

    //
    // Compute the number of map registers we'd like to get.
    //
    DeviceExtension->MaxMapRegisters = (DeviceDescription.MaximumLength / PAGE_SIZE) + 1;

    //
    // Create and get a pointer to the DMA adapter object
    //
    DeviceExtension->Adapter = IoGetDmaAdapter( DeviceExtension->PDO,
                                                &DeviceDescription,
                                                &DeviceExtension->MaxMapRegisters );

    if (DeviceExtension->Adapter == NULL)
    {
        DD_DebugPrint ((0, "Couldn't get DMA adapter object\n") );

        return STATUS_UNSUCCESSFUL;
    }

    DD_DebugPrint ((1, "Maximum available map registers for DMA adapter: %x\n", DeviceExtension->MaxMapRegisters) );

    KeInitializeTimer( &DeviceExtension->DmaCompletedTimer );

    KeInitializeDpc( &DeviceExtension->DmaCompletedDpc, pDummyDmaCompleted, DeviceExtension );

    DD_DebugPrint ((2, "All initialized!\n") );

    //
    // Mark the device as active and not holding IRPs.
    //
    DeviceExtension->DevicePnPState = Started;
    DeviceExtension->HoldNewRequests = FALSE;
    
    //
    // Enable device interface.
    // 
    
    status = IoSetDeviceInterfaceState(&DeviceExtension->SymbolicLinkName, TRUE);
    
    if (!NT_SUCCESS (status)) {

        //
        // It returns STATUS_OBJECT_NAME_EXISTS if we enable a device interface
        // that was already enabled, which could happen if the device is stopped
        // and restarted for resource rebalancing.
        //
        if (status == STATUS_OBJECT_NAME_EXISTS) {
            status = STATUS_SUCCESS;
        }
        else {
		    DD_DebugPrint((0, "IoSetDeviceInterfaceState failed: 0x%x\n", status));
		    IoDetachDevice(DeviceExtension->NextLowerDriver);
		    IoDeleteDevice (DeviceObject);
		    return status;
        }
    }

    //
    // The last thing to do is to process the held IRPs queue.
    //
    DummyDmaProcessQueuedRequests(DeviceObject);

    return status;
}


NTSTATUS
DummyDmaCreateClose(   IN PDEVICE_OBJECT   DeviceObject,
                       IN PIRP             Irp )
/*++

Routine Description:

    This routine is the dispatch for create/close requests. 

Arguments:

    DeviceObject    - Supplies the device object.
    Irp             - Supplies the I/O request packet.

Return Value:

    STATUS_SUCCESS  - Success.

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION	DeviceExtension = 
        (PDUMMYDMA_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION  irpStack;

    PAGED_CODE ();

    if (DeviceExtension->HoldNewRequests) {
        //
        // Set the DeviceQueue to busy in the DeviceObject to make
        // sure we can queue up this request when we call IoStartPacket.
        //

        //
        // Acquired DeviceQueue lock.
        //
        KeAcquireSpinLockAtDpcLevel(&DeviceObject->DeviceQueue.Lock);

        //
        // Set the DeviceQueue busy flag.
        //
        DeviceObject->DeviceQueue.Busy = TRUE;

        //
        // Acquired DeviceQueue lock.
        //
        KeReleaseSpinLockFromDpcLevel(&DeviceObject->DeviceQueue.Lock);
        
        //
        // Set Status to Pending and queue the request...
        //
        IoMarkIrpPending( Irp );

        //
        // Queue up the request.
        //
        IoStartPacket(DeviceObject, Irp, 0, DummyDmaCancelQueued);
        return STATUS_PENDING;
    }

    DummyDmaIoIncrement (DeviceExtension);
    
    irpStack = IoGetCurrentIrpStackLocation (Irp);

    // 
    // Just complete the IRP.
    //
    switch (irpStack->MajorFunction) {
    case IRP_MJ_CREATE:

        DD_DebugPrint((3, "Create \n"));
        break;

    case IRP_MJ_CLOSE:
        DD_DebugPrint((3, "Close \n"));
        break;

    }

    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest( Irp, IO_NO_INCREMENT );

    DummyDmaIoDecrement (DeviceExtension);

    return STATUS_SUCCESS;
}


NTSTATUS
DummyDmaDispatchPnp (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    The plug and play dispatch routines.

    Most of these these the driver will completely ignore.
    In all cases it must pass on the IRP to the lower driver.

Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

      NT status code

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION	DeviceExtension =
        (PDUMMYDMA_DEVICE_EXTENSION) DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION			stack;
    NTSTATUS                    status = STATUS_SUCCESS;
    LONG                        requestCount;  
    PPNP_DEVICE_STATE           deviceState;

    PAGED_CODE ();

    DD_DebugPrint ((0, "Dispatch Pnp\n"));
   
    stack = IoGetCurrentIrpStackLocation (Irp);

    DD_DebugPrint((2, "%s\n", 
                PnPMinorFunctionString(stack->MinorFunction)));

    if (DeviceExtension->DevicePnPState == Deleted) {
        //
        // Since the device is removed, we will not hold any IRPs.
        // We just fail it.
        //
        Irp->IoStatus.Status = STATUS_DELETE_PENDING;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return STATUS_DELETE_PENDING;
    }

    DummyDmaIoIncrement (DeviceExtension);
    
    switch (stack->MinorFunction) {
    case IRP_MN_START_DEVICE:

        //
        // The device is starting.
        //
        // We cannot touch the device (send it any non pnp irps) until a
        // start device has been passed down to the lower drivers.
        //

        DD_DebugPrint((2, "Starting Device...\n"));
        
        //
        // We can't completely rule out that we might receive a request
        // before the Irp is completed by the lower drivers. This might
        // happen if we're a filter driver above a function drivers that
        // exposes a device interface (symbolic link) and an app from user
        // mode start sending requests to us...  So, we need to queue those
        // requests (we can't touch the hardware yet).
        //
        DeviceExtension->HoldNewRequests = TRUE;

        //
        // Pass the IRP down.
        //

        status = DummyDmaSendIrpSynchronously(DeviceExtension->NextLowerDriver, 
                                                Irp);
        if (NT_SUCCESS (status)) {
            //
            // Lower drivers have finished their start operation, so now
            // we can finish ours.
            //
            status = DummyDmaStartDevice (DeviceObject, Irp);
        }

        //
        // We must now complete the IRP, since we stopped it in the
        // completion routine with MORE_PROCESSING_REQUIRED.
        //
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);

        break;

    
    case IRP_MN_QUERY_STOP_DEVICE:

        //
        // If we can stop the device, we need to set the HoldNewRequests flag,
        // so further requests will be queued. We don't care about processing
        // some old requests (if there are any), because we expect to be 
        // started again in the future.
        //
        status = pDummyDmaCanStopDevice(DeviceObject, Irp);

        if (NT_SUCCESS(status)) {

            //
            // OK, we can stop our device.
            // First, don't allow any requests to be passed down.
            //
            DeviceExtension->DevicePnPState = StopPending;
            DeviceExtension->HoldNewRequests = TRUE;
            DD_DebugPrint((3, "Holding requests...\n"));

            //
            // Then, wait for the existing ones to be finished
            // (since we expect to give up our resources, we
            // can't allow such requests). First, we need to decrement
            // this very operation (and to keep in mind not to decrement
            // after the switch).
            //
            DummyDmaIoDecrement(DeviceExtension);

            KeWaitForSingleObject(
               &DeviceExtension->StopEvent,
               Executive, // Waiting for reason of a driver
               KernelMode, // Waiting in kernel mode
               FALSE, // No alert
               NULL); // No timeout

            
            IoSkipCurrentIrpStackLocation (Irp);
            status = IoCallDriver (DeviceExtension->NextLowerDriver, Irp);
            return status;

        }   else    {

            //
            // The device can't be stopped, complete the request.
            //
            Irp->IoStatus.Status = status;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);

        }

        break;
    
   case IRP_MN_CANCEL_STOP_DEVICE:
        //
        // Send this IRP down and wait for it to come back.
        // Set the HoldNewRequests flag to not hold any new requests,
        // and process the queued up IRPs.

        status = DummyDmaSendIrpSynchronously(DeviceExtension->NextLowerDriver, Irp);
        DD_DebugPrint((3, "Status after waiting = 0x%x\n", status));
        
        DeviceExtension->HoldNewRequests = FALSE;
        DeviceExtension->DevicePnPState = Started;
        
        DD_DebugPrint((2, "Cancel stop...\n"));
        
        //
        // Process the queued requests
        //
        DummyDmaProcessQueuedRequests(DeviceObject);

        //
        // We must now complete the IRP, since we stopped it in the
        // completion routine with MORE_PROCESSING_REQUIRED.
        //
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
       
        break;

    case IRP_MN_STOP_DEVICE:

        //
        // After the stop IRP has been sent to the lower driver object, the
        // bus may NOT send any more IRPs down that touch the device until 
        // another START has occurred.  For this reason we are holding IRPs
        // and waiting for the outstanding requests to be finished when
        // QUERY_STOP is received.
        // IRP_MN_STOP_DEVICE doesn't change anything in this behavior
        // (we continue to hold IRPs until a IRP_MN_START_DEVICE is issued).
        //

        DD_DebugPrint((2, "Stopping device...\n"));

        //
        // This is the right place to actually give up all the resources used
        // This might include calls to IoDisconnectInterrupt, MmUnmapIoSpace, 
        // etc.
        //

        //
        // Must never fail IRP_MN_STOP_DEVICE. pDummyDmaReturnResource should
        // not fail. If it does, just print something to the debugger.
        //
        
        status = pDummyDmaReturnResources(DeviceObject);

        if (!NT_SUCCESS (status)) {
            DD_DebugPrint((0, "pDummyDmaReturnResources failed: 0x%x\n", status));
        }

        //
        // Mark the device not started. We don't have race conditions here, since
        // it's not possible to receive a start and a stop Irp 
        // "in the same time". 
        //
        DeviceExtension->DevicePnPState = Stopped;

        //
        // Pass the IRP down
        //
        IoSkipCurrentIrpStackLocation (Irp);
        status = IoCallDriver (DeviceExtension->NextLowerDriver, Irp);

        break;

    case IRP_MN_QUERY_REMOVE_DEVICE:

        //
        // If we can stop the device, we need to set the HoldNewRequestsFlag,
        // so further requests will be queued. 
        // The difference from IRP_MN_QUERY_STOP_DEVICE is that we will 
        // attempt to process the requests queued before 
        // (it's likely we won't have another chance to do this, since we 
        // expect that the device will be removed).
        // We then start queuing new IRPs in the event of receiving a 
        // IRP_MN_CANCEL_STOP_DEVICE.
        // 
        
         status = pDummyDmaCanRemoveDevice(DeviceObject, Irp);

         if (NT_SUCCESS(status)) {
             //
             // First, process the old requests.
             //
             DD_DebugPrint((3, "Processing requests\n"));
             
             DummyDmaProcessQueuedRequests(DeviceObject);
             
             //
             // Now prepare to hold the new ones (eventually we might
             // get a IRP_MN_CANCEL_REMOVE_DEVICE) and we need to 
             // process the queued requests then.
             //
             DeviceExtension->HoldNewRequests = TRUE;

             DeviceExtension->DevicePnPState = RemovePending;
             
             DD_DebugPrint((3, "Query - remove holding requests...\n"));
             
             //
             // We need to decrement this very operation (and don't
             // forget to skip the decrement after the switch).
             //
             DummyDmaIoDecrement(DeviceExtension);

             //
             // Wait for all the requests to be completed
             //
             KeWaitForSingleObject(
               &DeviceExtension->StopEvent,
               Executive, // Waiting for reason of a driver
               KernelMode, // Waiting in kernel mode
               FALSE, // No alert
               NULL); // No timeout

             IoSkipCurrentIrpStackLocation (Irp);
             
             status = IoCallDriver (DeviceExtension->NextLowerDriver, Irp);

             //
             // Since we have already decrement the I/O count, we
             // do not want to decrement again at the end of the switch.
             // Use the goto statement to skip the decrement at the end.
             //
             goto exit;

         }   else    {

             //
             // The device can't be removed, just complete the request.
             //
             Irp->IoStatus.Status = status;
             IoCompleteRequest(Irp, IO_NO_INCREMENT);
         }

         break;
     
    case IRP_MN_CANCEL_REMOVE_DEVICE:

        //
        // We need to reset the HoldNewRequests flag, since the device
        // resume its normal activities.
        //

        status = DummyDmaSendIrpSynchronously(DeviceExtension->NextLowerDriver, Irp);

        DeviceExtension->HoldNewRequests = FALSE;
        DeviceExtension->DevicePnPState = Started;

        DD_DebugPrint((2, "Cancel remove...\n"));

        //        
        // Process the queued requests that arrived between 
        // QUERY_REMOVE and CANCEL_REMOVE
        //

        DummyDmaProcessQueuedRequests(DeviceObject);

        //
        // We must now complete the IRP, since we stopped it in the
        // completion routine with MORE_PROCESSING_REQUIRED.
        //
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);

        break;        
            
   case IRP_MN_REMOVE_DEVICE:

       //
       // The Plug and Play system has dictated the removal of this device.  We
       // have no choice but to detach and delete the device object.
       // (If we wanted to express an interest in preventing this removal,
       // we should have failed the query remove IRP).
       //

       DD_DebugPrint((2, "Removing device...\n"));
       
       if(DeviceExtension->DevicePnPState != SurpriseRemovePending)
       {
           //
           // This means you are here either after receiving a 
           // QUERY_REMOVE or without first receiving
           // a STOP or SURPRISE_REMOVE (!?). Either way
           //  disable the interface, return resources,
           //  and fail all the pending request,.
           //
            
           DeviceExtension->HoldNewRequests = FALSE;

           //
           // Disable the Interface
           // 
            
           status = IoSetDeviceInterfaceState(&DeviceExtension->SymbolicLinkName, FALSE);
        
           if (!NT_SUCCESS (status)) {
               DD_DebugPrint((0, 
                       "IoSetDeviceInterfaceState failed: 0x%x\n", status));
           }

           //
           // Return hardware resources.
           //
            
           pDummyDmaReturnResources(DeviceObject);

       }

       DeviceExtension->DevicePnPState = Deleted;

       //
       // Fail all the pending request.
       // Note that DeviceExtension->DevicePnPState is Deleted, so 
       // DummyDmaProcessQueuedRequests will simply flush the queue, 
       // completing each IRP with STATUS_DELETE_PENDING
       //       

       DummyDmaProcessQueuedRequests(DeviceObject);
                     
       //
       // Send on the remove IRP.
       // We need to send the remove down the stack before we detach,
       // but we don't need to wait for the completion of this operation
       // (and to register a completion routine).
       //
       
       IoSkipCurrentIrpStackLocation (Irp);
       status = IoCallDriver (DeviceExtension->NextLowerDriver, Irp);

       //
       // We need two decrements here, one for the increment in 
       // DD_PnpDispatch, the other for the 1-biased value of
       // OutstandingIO. Also, we need to wait that all the requests
       // are served.
       // 

       requestCount = DummyDmaIoDecrement (DeviceExtension);

       //
       // The requestCount is a least one here (is 1-biased)
       //
       ASSERT(requestCount > 0);
        
       DummyDmaIoDecrement (DeviceExtension);
       
       KeWaitForSingleObject (
           &DeviceExtension->RemoveEvent,
           Executive,
           KernelMode,
           FALSE,
           NULL);
        
       //
       // Detach the FDO from the device stack
       //
       IoDetachDevice (DeviceExtension->NextLowerDriver);       

       //
       // Clean up
       //
       RtlFreeUnicodeString(&DeviceExtension->SymbolicLinkName);
       IoDeleteDevice (DeviceExtension->Self);
       
       return STATUS_SUCCESS;

    case IRP_MN_SURPRISE_REMOVAL:

        // 
        // The device has been unexpectedly removed from the machine
        // and is no longer available for I/O ("surprise" removal). 
        // We must return device and memory resources, 
        // disable interfaces, We will defer failing any outstanding
		// requests to IRP_MN_REMOVE_DEVICE.
        //

        DeviceExtension->HoldNewRequests = FALSE;
        DeviceExtension->DevicePnPState = SurpriseRemovePending;
        
        //
        // Disable the device interface.
        //
        
        status = IoSetDeviceInterfaceState(&DeviceExtension->SymbolicLinkName, FALSE);
        
        if (!NT_SUCCESS (status)) {
            DD_DebugPrint((0, "IoSetDeviceInterfaceState failed: 0x%x\n", status));
        }

        //
        // Return any resources acquired during device startup.
        //

        pDummyDmaReturnResources(DeviceObject);

        IoSkipCurrentIrpStackLocation (Irp);
        status = IoCallDriver (DeviceExtension->NextLowerDriver, Irp);

        break;

    case IRP_MN_QUERY_PNP_DEVICE_STATE:
    case IRP_MN_QUERY_CAPABILITIES:
    case IRP_MN_QUERY_DEVICE_RELATIONS:
    case IRP_MN_QUERY_INTERFACE:
    case IRP_MN_QUERY_RESOURCES:
    case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
    case IRP_MN_READ_CONFIG:
    case IRP_MN_WRITE_CONFIG:
    case IRP_MN_EJECT:
    case IRP_MN_SET_LOCK:
    case IRP_MN_QUERY_ID:
    default:

        //
        // Pass down all the unhandled Irps.
        //
        IoSkipCurrentIrpStackLocation (Irp);
        status = IoCallDriver (DeviceExtension->NextLowerDriver, Irp);

        break;

    }

    //
    // We need to decrement only in some cases.
    // For the "query" minor codes we decrement inside the switch.
    //
    DummyDmaIoDecrement(DeviceExtension);
    
exit:
    //
    // Return without decrement
    //

    return status;

}


NTSTATUS
DummyDmaCleanup (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

/*++

Routine Description:

    The dispatch routine for cleanup: we need to walk the Irp queue and
    to cancel all the requests for which the file object is the same with
    the one in the Irp.
    
    
Arguments:

   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.

Return Value:

   NT status code

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION              deviceExtension;
    KIRQL                   oldIrql;
    LIST_ENTRY                cleanupList;
    PLIST_ENTRY             thisEntry, nextEntry, listHead;
    PIRP                    pendingIrp;
    PIO_STACK_LOCATION      pendingIrpStack, irpStack;

    DD_DebugPrint((3, "Cleanup called.\n"));

    deviceExtension = (PDUMMYDMA_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    DummyDmaIoIncrement (deviceExtension);    

    irpStack = IoGetCurrentIrpStackLocation(Irp);
    
    //
    // Initialize the cleanup list
    //

    InitializeListHead(&cleanupList);

    //
    // We must acquire the DeviceQueue lock first.
    //

    KeAcquireSpinLockAtDpcLevel(&DeviceObject->DeviceQueue.Lock);
    
    //
    // Walk the list to find all the Irp belonging to the fileobject
    // by matching the fileobject pointer of the input Irp and the
    // queued Irps.
    //

    listHead = &DeviceObject->DeviceQueue.DeviceListHead;

    for(thisEntry = listHead->Flink,nextEntry = thisEntry->Flink;
        thisEntry != listHead;
        thisEntry = nextEntry,nextEntry = thisEntry->Flink)
    {

        pendingIrp = CONTAINING_RECORD(thisEntry, IRP,
                                Tail.Overlay.DeviceQueueEntry);
        pendingIrpStack = IoGetCurrentIrpStackLocation(pendingIrp);

        if (irpStack->FileObject == pendingIrpStack->FileObject) 
        {
            //
            // Remove the Irp from the devicequeue and insert
            // it into the cleanup list
            //

            RemoveEntryList(thisEntry);

            //
            // Set the cancel routine to NULL.
            //
            if (IoSetCancelRoutine(pendingIrp, NULL) == NULL) {
                //
                // The cancel routine has run but it must be waiting to hold
                // the device queue lock. It will cancel the IRP as soon as we
                // drop the lock outside this loop. We will initialize the IRP's
                // listEntry so that the cancel routine wouldn't barf when it
                // tries to remove the IRP from the queue, and leave the IRP alone.
                //
                InitializeListHead(thisEntry);
            }
            else {
                //
                // Cancel routine has not been called and will never be 
                // called. Queue the IRP in the cleanup list and cancel it
                // after dropping the lock.
                //                
                InsertTailList(&cleanupList, thisEntry);
            }
        }
    }
    
    // 
    // Release the DeviceQueue lock
    //
    KeReleaseSpinLockFromDpcLevel(&DeviceObject->DeviceQueue.Lock);
        
    //
    // Cancel all the Irps in the cleanup list.
    //

    while(!IsListEmpty(&cleanupList))
    {
        //
        // Complete the IRP 
        //
        thisEntry = RemoveHeadList(&cleanupList);
        pendingIrp = CONTAINING_RECORD(thisEntry, IRP,
                                Tail.Overlay.DeviceQueueEntry);

        //
        // You must clear the cancel routine before completing the IRP
        //
        IoSetCancelRoutine(pendingIrp, NULL);

        pendingIrp->IoStatus.Information = 0;
        pendingIrp->IoStatus.Status = STATUS_CANCELLED;
        IoCompleteRequest(pendingIrp, IO_NO_INCREMENT);
    }

    DummyDmaIoDecrement (deviceExtension);    
    
    return STATUS_SUCCESS;
}


VOID
DummyDmaUnload( IN PDRIVER_OBJECT   DriverObject )
/*++

Routine Description:

    Free all allocated resources.

Arguments:

    DeviceObject    - Supplies the device object.

Return Value:

    STATUS_SUCCESS  - Success.

--*/
{
    PAGED_CODE ();
  
    //
    // We should not be unloaded until all the devices we control 
    // have been removed from our queue.  
    //
    DD_DebugPrint ((2, "Unloading!!\n") );

    ExFreePool(Globals.RegistryPath.Buffer);

}


NTSTATUS
DummyDmaDispatch(   IN PDEVICE_OBJECT   DeviceObject,
                    IN PIRP             Irp )
/*++

Routine Description:

    This routine is the dispatch for DEVICE_CONTROL requests. If the
    IOCTL is supported, then it queues the request.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the I/O request packet.

Return Value:

    STATUS_PENDING              - The request is pending.
    STATUS_NOT_IMPLEMENTED      - The request is not implemented

--*/
{
    PIO_STACK_LOCATION      IrpStack;	
    NTSTATUS				Status = STATUS_NOT_IMPLEMENTED;
    PDUMMYDMA_DEVICE_EXTENSION  DeviceExtension = DeviceObject->DeviceExtension;

    DD_DebugPrint ((4, "Dispatching a new request..\n"));

    //
    // Make sure we have a valid request.
    //
    IrpStack = IoGetCurrentIrpStackLocation(Irp);

    if ((IrpStack->Parameters.DeviceIoControl.IoControlCode) != IOCTL_DUMMY_DMA) {
        
        // don't know how to handle this ioctl
        Irp->IoStatus.Status = Status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);

        return Status;
    }        

    //
    // Check for zero length transfer. If your device has a maximum buffer
    // length, you should also check for this here.
    //
    if ((IrpStack->Parameters.DeviceIoControl.OutputBufferLength) == 0) {
            
        //
        // Bad request. Just complete the request.
        //
        Status = STATUS_INVALID_USER_BUFFER;
        Irp->IoStatus.Status = STATUS_INVALID_USER_BUFFER;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);

        return Status;
            
    }

    //
    // Queue up the request. By default, this device only handles one
    // request at a time. For normal operations, the DPC will start the
    // next request once it is finish processing a request. If we need
    // to hold requests because we are stop or going to sleep, the DPC
    // will not start the next packet by not calling IoStartNextPacket.
    // Eventually, when we come back up or get restarted, 
    // DummyDmaProcessQueueRequests routine will kick things up again.
    //

    //
    // Set Status to Pending and queue the request...
    //
    IoMarkIrpPending( Irp );

    //
    // If we need to hold requests, we need to queue it up. Mark the
    // device queue in the DeviceObject as busy to make sure we can 
    // queue up the request.
    //
    if (DeviceExtension->HoldNewRequests) {
        //
        // Acquired DeviceQueue lock.
        //
        KeAcquireSpinLockAtDpcLevel(&DeviceObject->DeviceQueue.Lock);

        //
        // Set the DeviceQueue busy flag.
        //
        DeviceObject->DeviceQueue.Busy = TRUE;

        //
        // Acquired DeviceQueue lock.
        //
        KeReleaseSpinLockFromDpcLevel(&DeviceObject->DeviceQueue.Lock);
    }

    //
    // Queue up the request.
    //
    IoStartPacket(DeviceObject, Irp, 0, DummyDmaCancelQueued);

    return STATUS_PENDING;
}


NTSTATUS
DummyDmaStartIo(    
	IN PDEVICE_OBJECT   DeviceObject,
	IN PIRP             Irp 
	)
/*++

Routine Description:

    StartIo routine.

    This routine initializes the packet no. and FinalPacket fields in the
    DeviceExtension. The return buffer (described by Irp->MdlAddress) may
    require more MapRegisters than are allotted to this device, in which case
    we will break up the transfer. PacketNo will be incremented each time we
    break the transfer up. FinalPack will be set to TRUE when we come to the
    last packet of the transfer.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the I/O request packet.

Return Value:

    NT status code.

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION  DeviceExtension = DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION  irpStack;
    KIRQL               oldIrql;
    NTSTATUS            status = STATUS_PENDING;

    DD_DebugPrint ((4, "Starting a request...\n"));

    //
    // Increment the ref count. Only counting the requests that
    // are currently being process.
    //
    DummyDmaIoIncrement(DeviceExtension);

    IoAcquireCancelSpinLock(&oldIrql);

    //
    // Check to see whether the CurrentIrp is cancelled between the
    // time-frame IoStartPacket released the lock and StartIo (above
    // call) routine acquired the lock.
    //

    if(Irp != DeviceObject->CurrentIrp)
    {
        //
        // Don't touch the Irp; release the lock and return
        //
        IoReleaseCancelSpinLock(oldIrql);

        DummyDmaIoDecrement(DeviceExtension);

        return status;
    }

    //
    // Set the cancel routine to NULL.
    //

    IoSetCancelRoutine (Irp, NULL);

    //
    // Check the cancel flag to determine whether to
    // cancel or process the Irp.
    //

    if(Irp->Cancel)
    {    
        // 
        // In this case, the Irp is marked for cancellation 
        // but the cancel routine wasn't called. This can
        // only happen if there is no cancel routine and someone
        // called IoCancelIrp while the Irp is in the device queue.
        //

        IoReleaseCancelSpinLock(oldIrql);
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = STATUS_CANCELLED;

        //
        // Check to see if the device is stopped or going to sleep.
        // If so, don't start the next packet.
        //
        if (DeviceExtension->HoldNewRequests != TRUE) {
            //        
            // Start the next packet.
            //
            IoStartNextPacket(DeviceObject, TRUE);
        }

        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        DummyDmaIoDecrement(DeviceExtension);

        return status;
    }

    IoReleaseCancelSpinLock(oldIrql);

    //
    // Get Stack location
    //
    irpStack = IoGetCurrentIrpStackLocation(Irp);

    //
    // Check which IRP this is as we could possibly be
    // processing CREATE or CLOSE IRPs.
    //
    if ((irpStack->MajorFunction == IRP_MJ_CREATE) || 
        (irpStack->MajorFunction == IRP_MJ_CLOSE)) {

        //
        // Since all we do is just complete these IRPs,
        // complete them here. 
        //
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = STATUS_SUCCESS;

        //
        // Check to see if the device is stopped or going to sleep.
        // If so, don't start the next packet.
        //
        if (DeviceExtension->HoldNewRequests != TRUE) {
            //        
            // Start the next packet.
            //
            IoStartNextPacket(DeviceObject, TRUE);
        }

        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        DummyDmaIoDecrement(DeviceExtension);

        status = STATUS_SUCCESS;
    }
    else {
        //
        // Process the request
        //

        DeviceExtension->PacketNumber = 0;
        DeviceExtension->FinalPacket  = FALSE;

        //
        // Perform the IO
        //
        pDummyDmaProgramTransfer( DeviceObject, Irp );
    }

    //
    // If something goes wrong, we should just let the DPC
    // finish this request.
    //

    return status;
}


VOID
pDummyDmaProgramTransfer(   IN PDEVICE_OBJECT   DeviceObject,
                            IN PIRP             Irp )
/*++

Routine Description:

    This routine calculates the no. of MapRegisters required to perform this
    DMA request. If it requires more MapRegisters than are allotted to this
    device, we will break up the transfer. We will then be called again from
    our Pseudo ISR (Timer DPC) to continue processing the same Irp, until we
    complete the transfer. If the numberofMapRegisters required is less than
    or equal to the amount available to this device, we will set FinalPacket
    to TRUE.

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the I/O request packet.

Return Value:

    None

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION  DeviceExtension = DeviceObject->DeviceExtension;
    NTSTATUS                    Status;
    LARGE_INTEGER               DueTime;
    PGET_SCATTER_GATHER_LIST	GetScatterGatherList;

    DD_DebugPrint ((4, "Program Transfer..\n"));

    //
    // Set the amount of time in which to call our Pseudo ISR...
    //
    DueTime.QuadPart = -100;

    //
    // Store the currentIrp in the device Extension.
    //
    DeviceExtension->CurrentIrp = Irp;

    DeviceExtension->CurrentVirtualAddress = MmGetMdlVirtualAddress(Irp->MdlAddress);

    DeviceExtension->CurrentTransferLength = MmGetMdlByteCount(Irp->MdlAddress);

    //
    // Update the CurrentVA and CurrentTransferLength.
    //
    DeviceExtension->CurrentVirtualAddress +=
        (DeviceExtension->PacketNumber * (DeviceExtension->MaxMapRegisters - 1) * PAGE_SIZE);
    DeviceExtension->CurrentTransferLength -=
        (DeviceExtension->PacketNumber * (DeviceExtension->MaxMapRegisters - 1) * PAGE_SIZE);

    //
    // Calculate the number of map registers needed for this transfer.
    //
    DeviceExtension->numberOfMapRegisters  = 
            ADDRESS_AND_SIZE_TO_SPAN_PAGES( DeviceExtension->CurrentVirtualAddress,
                                            DeviceExtension->CurrentTransferLength );

    //
    // If the number of map registers needed for the buffer is
    // greater than the maximum number of map registers we can
    // use, we will have to split this transfer.
    //
    // Note: We could have just called GetScatterGatherList. In the
    //       event the number of map registers needed is greater
    //       than what we can use, GetScatterGatherList will return
    //       insufficient resource error.
    //
    if (DeviceExtension->numberOfMapRegisters > DeviceExtension->MaxMapRegisters)
    {
        //
        // Set FinalPacket to FALSE.
        //
        DeviceExtension->FinalPacket = FALSE;
        
        //
        // Increment Packet number.
        //
        DeviceExtension->PacketNumber++;

        DeviceExtension->CurrentTransferLength = (DeviceExtension->MaxMapRegisters - 1) * PAGE_SIZE;

    }
    else
    {
        //
        // This is the last packet of this transfer...
        //
        DeviceExtension->FinalPacket = TRUE;
    }

    //
    // Flush any outstanding DMA.
    //
    KeFlushIoBuffers(Irp->MdlAddress, TRUE, TRUE);

    //
    // Ask the system for the necessary scatter gather list. 
    //
    // GetScatterGatherList will allocate the scatter gather list for you and
    // call AllocateAdapterChannel and MapTransfer on your behalf.
    //
    // NOTE: The following call only reads from device. If you wish to write 
    // to device, set the last parameter to TRUE.
    //
    DD_DebugPrint ((4, "Getting scatter gather list..\n"));
    GetScatterGatherList = DeviceExtension->Adapter->DmaOperations->GetScatterGatherList;
    Status = GetScatterGatherList(  DeviceExtension->Adapter,
                                    DeviceObject,
                                    Irp->MdlAddress,
                                    DeviceExtension->CurrentVirtualAddress,
                                    DeviceExtension->CurrentTransferLength,
                                    pDummyDmaCallBack,
                                    NULL,
                                    FALSE);

    DD_DebugPrint ((4, "Return from scatter gather list\n"));

    if (!NT_SUCCESS(Status))
    {
        //
        // Something went wrong. Queue the pseudo ISR anyway... Set the Status to what was
        // returned by GetScatterGatherList.
        //
        DD_DebugPrint ((0, "GetScatterGatherList failed, status: 0x%X\n", Status) );
        DeviceExtension->IrpStatus = Status;
        DeviceExtension->FinalPacket = TRUE;
        KeSetTimer( &DeviceExtension->DmaCompletedTimer, DueTime, &DeviceExtension->DmaCompletedDpc );
    }
}


VOID
pDummyDmaCallBack(  IN PDEVICE_OBJECT		DeviceObject,
                    IN PIRP					Irp,
                    IN PSCATTER_GATHER_LIST ScatterGatherList,
                    IN PVOID				Context )
/*++

Routine Description:

    This routine is called when the system is able to allocate the scatter
    gather list to satisfy our earlier call to GetScatterGatherList. By the
    time this function gets called, the system has already called 
    AllocatedAdapterChannel and MapTransfer on our behalf. All we need to do is pass
    down the ScatterGather block to the hardware.
    (This will be passed down to real DMA capable h/w in most cases)

Arguments:

    DeviceObject    - Supplies the device object.

    Irp             - Supplies the I/O request packet.

    ScatterGather	- Supplies the scatter gather list.

    Context         - Any context that was passed in on the earlier call to
                      GetScatterGatherList.

Return Value:

    None.

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION  DeviceExtension = DeviceObject->DeviceExtension;
    PHYSICAL_ADDRESS            logicalAddress;
    LARGE_INTEGER               DueTime;
    ULONG						index;

    DD_DebugPrint ((4, "Reading scatter gather list..\n"));

    //
    // Store the scatter gather list that was passed in to this routine.
    // We need this scatter gather list when we call PutScatterGatherList.
    //
    DeviceExtension->ScatterGatherList = ScatterGatherList;

    //
    // Traverse the scatter gather list.
    //
    for (index = 0; index < ScatterGatherList->NumberOfElements; ++index) {
        logicalAddress = ScatterGatherList->Elements[index].Address;
        
        // 
        // Write this now or later to the hardware.
        //
    }

    DeviceExtension->IrpStatus = STATUS_SUCCESS;

    //
    // Queue the pseudo ISR.
    //
    DueTime.QuadPart = -100;
    KeSetTimer( &DeviceExtension->DmaCompletedTimer, DueTime, &DeviceExtension->DmaCompletedDpc );

}


VOID
pDummyDmaCompleted( IN PKDPC    Dpc,
                    IN PVOID    DeferredContext,
                    IN PVOID    SystemArgument1,
                    IN PVOID    SystemArgument2 )
/*++

Routine Description:

    This routine is called once a transfer is completed (successfully/unsuccessfully).
    If the transfer is successful, then call PutScatterGatherList to flush the adapter
    buffers and free the MapRegisters. Also if this is the final part of the transfer, 
    complete the IRP and Start the next request.

Arguments:

    Dpc             - Supplies the DPC obejct

    DeferredContext - Supplies the Context passed when KeInitializeDpc was called.

    SystemArgument1 - Context which is ignored in this case.

    SystemArgument2 - Context which is ignored in this case.

Return Value:

    None

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION  DeviceExtension = DeferredContext;
    PIRP                        Irp             = DeviceExtension->CurrentIrp;
    PLIST_ENTRY					headOfList;
    PIRP						currentIrp;
    PPUT_SCATTER_GATHER_LIST	PutScatterGatherList;
    KIRQL                       oldIrql;

    DD_DebugPrint ((4, "DMA completed\n"));

    if (DeviceExtension->IrpStatus == STATUS_SUCCESS)
    {
        //
        // Call PutScatterGatherList to return resources to the system.
        // PutScatterGatherList will flush the adapter buffers to ensure
        // any data remaining in the adapter's internal cache has been
        // successfully flushed into system memory. In addition,
        // PutScatterGatherList will release the map registers it received
        // when GetScatterGatherList was called.
        //
        // NOTE: The following call only reads from device. If you wish to write 
        // to device, set the last parameter to TRUE.
        
        PutScatterGatherList = DeviceExtension->Adapter->DmaOperations->PutScatterGatherList;
        PutScatterGatherList(DeviceExtension->Adapter,
                             DeviceExtension->ScatterGatherList,
                             FALSE);
    }

    //
    // Check to see if the request was cancelled.
    //
    if (Irp->Cancel) {
        // cancel the request
        DeviceExtension->IrpStatus = STATUS_CANCELLED;
        Irp->IoStatus.Information = 0;

        //
        // let the next check complete the request and
        // start the next request
        //
        DeviceExtension->FinalPacket = TRUE;
    }

    //
    // Check to see if we need to start another request.
    //
    if (DeviceExtension->FinalPacket) {
        //
        // The last portion of this IRP is completed.
        //

        //
        // Decrement the ref count.
        //
        DummyDmaIoDecrement(DeviceExtension);

        //
        // Check to see if we should start another request if one 
        // is available from the queue. We do not want to start 
        // another request if we are going to stop or going to sleep,
        // i.e. we are queuing the requests. By not calling 
        // IoStartNextPacket, we block the device queue.
        //
        if (DeviceExtension->HoldNewRequests != TRUE) {
            //
            // Start another request.
            //
            IoStartNextPacket(DeviceExtension->Self, TRUE);
        }

        Irp->IoStatus.Status = DeviceExtension->IrpStatus;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );

    }
    else
    {
        //
        // Start the next part of this transfer request...
        //
        pDummyDmaProgramTransfer( DeviceExtension->Self, Irp );
    }
}


NTSTATUS
DummyDmaDispatchPnpComplete (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
/*++

Routine Description:

  The pnp IRP was completed by the lower-level drivers.
    Signal this to whoever registered us.

Arguments:
    
   DeviceObject - pointer to a device object.

   Irp - pointer to an I/O Request Packet.
   
   Context - pointer to the event to be signaled.

Return Value:

    NT status code
        
--*/
{
    PIO_STACK_LOCATION  stack;
    PKEVENT             event = (PKEVENT)Context;
    NTSTATUS            status;

    UNREFERENCED_PARAMETER (DeviceObject);

    status = STATUS_SUCCESS;
    stack = IoGetCurrentIrpStackLocation (Irp);

    if (Irp->PendingReturned) {
        IoMarkIrpPending( Irp );
    }

    KeSetEvent (event, 0, FALSE);

    //
    // Take the IRP back so that we can continue using it during
    // the dispatch routine.
    // NB: The dispatch routine will have to call IoCompleteRequest
    //
    return STATUS_MORE_PROCESSING_REQUIRED;
            
}
   
    
VOID
DummyDmaProcessQueuedRequests(
    IN PDEVICE_OBJECT DeviceObject
    )   

/*++

Routine Description:

    Removes and processes the entries in the queue. If the routine is called 
    while we are handling IRP_MN_REMOVE_DEVICE, the IRPs are completed with 
    STATUS_DELETE_PENDING. 

Arguments:

    DeviceObject - pointer to the device object
    
Return Value:

    VOID.

--*/
{
    
    PLIST_ENTRY         headOfList;
    PIRP                currentIrp;
    PDUMMYDMA_DEVICE_EXTENSION   DeviceExtension = 
        (PDUMMYDMA_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    KIRQL               oldIrql;
    LIST_ENTRY          cleanupList;
    PLIST_ENTRY         thisEntry, nextEntry, listHead;
    BOOLEAN             emptyQueue;

    DD_DebugPrint ((2, "Processing queue requests...\n"));
    
    //
    // We need to dequeue all the entries in the queue, to reset the cancel 
    // routine for each of them and then to process them:
    // - if the device is active, we will process it
    // - else we will complete them with STATUS_DELETE_PENDING
    // (it is a surprise removal and we need to dispose the queue)
    //
    // If the device is active, we will defer to the StartIo
    // routine to reset the cancel routine.
    //

    KeAcquireSpinLock(&DeviceObject->DeviceQueue.Lock, &oldIrql);

    emptyQueue = (IsListEmpty(&DeviceObject->DeviceQueue.DeviceListHead) &&
                    (DeviceObject->DeviceQueue.Busy == FALSE));

    if (DeviceExtension->DevicePnPState == Deleted) {
        // Fail all requests in the queue. We will copy requests
        // to another list before completing them so we don't
        // complete any requests while holding onto the spinlock.

        //
        // Initialize the cleanup list
        //

        InitializeListHead(&cleanupList);

        while (!IsListEmpty(&DeviceObject->DeviceQueue.DeviceListHead)) {
            
            headOfList = RemoveHeadList(&DeviceObject->DeviceQueue.DeviceListHead);       
			
            currentIrp = CONTAINING_RECORD(headOfList,
                                            IRP,
                                            Tail.Overlay.ListEntry);

            currentIrp->IoStatus.Information = 0;
            currentIrp->IoStatus.Status = STATUS_DELETE_PENDING;            

            //
            // Check to see if the request is cancel.
            //
            if (currentIrp->Cancel) {

                if (IoSetCancelRoutine (currentIrp, NULL)) {
                    //
                    // This IRP was just cancelled but the cancel
                    // routine has not been called yet. It is safe to
                    // cancel this IRP. Queue up the IRP in the 
                    // cleanup list and complete them after we have
                    // release the device queue lock. This is to ensure
                    // we do not call out of the driver while holding
                    // a lock.
                    //
                    InsertTailList(&cleanupList, headOfList);
                }
                else {
                    //
                    // The cancel routine has ran but it must be waiting
                    // to hold the device queue lock. The cancel routine
                    // will cancel the IRP as soon as we release the lock.
                    // Initialize the IRPs listEntry so that the cancel 
                    // routine will not barf when it tries to remove the
                    // IRP from the device queue.
                    //
                    InitializeListHead(headOfList);
                }
            }
			
        }

        KeReleaseSpinLock(&DeviceObject->DeviceQueue.Lock, oldIrql);

        //
        // Complete the IRPs
        //

        while(!IsListEmpty(&cleanupList)) {

            headOfList = RemoveHeadList(&cleanupList);

            currentIrp = CONTAINING_RECORD(headOfList,
                                            IRP,
                                            Tail.Overlay.ListEntry);


            IoCompleteRequest(currentIrp, IO_NO_INCREMENT);
        }

    }
    else {
        
        KeReleaseSpinLock(&DeviceObject->DeviceQueue.Lock, oldIrql);

        //
        // Kick off a request. The DPC will finish the requests queued up.
        //
        // Make sure there is something in the device queue. If it is empty,
        // then don't do anything.
        //
        // Note: We don't care about the IRP currently pointed to by DeviceObject
        //       since before we stop, we have already finish processing the
        //       IRP. Just start the next IRP in the queue.
        //        
        if (!emptyQueue) {
            IoStartNextPacket(DeviceObject, TRUE);      
        }
        
    }

    return;

}
    

VOID
DummyDmaCancelQueued (
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Irp
    )

/*++

Routine Description:

    The cancel routine. Will remove the IRP from the queue and will complete it.
    The cancel spin lock is already acquired when this routine is called.

Arguments:

    DeviceObject - pointer to the device object.
    
    Irp - pointer to the IRP to be cancelled.
    
    
Return Value:

    VOID.

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION DeviceExtension = 
        (PDUMMYDMA_DEVICE_EXTENSION)DeviceObject->DeviceExtension; 
    BOOLEAN                wasQueued;
    
    //
    // Don't assume that the IRP being cancelled is in the queue.
    // Only complete the IRP if it IS in the queue.
    //

    DD_DebugPrint ((2, "Cancel queue for IRP 0x%0x.\n", Irp));

    //
    // Release the cancel spinlock with the current IRQL value of
    // the processor.
    //
    IoReleaseCancelSpinLock( KeGetCurrentIrql() );

    //
    // First check whether the cancelled Irp is CurrentIrp.
    //

    if(Irp == DeviceObject->CurrentIrp)
    {               
        //
        // We will let the StartIo routine handle completing
        // this request. By the time StartIo gets hold of
        // this IRP, the IRP is marked as cancel and will be
        // completed. 
        //

        return;
        
    }
    else
    {
        //
        // Remove the IRP from the queue
        //
        wasQueued = KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,
                        &Irp->Tail.Overlay.DeviceQueueEntry );

        //
        // Note: The IRP may not be in the queue if the device has been remove
        // and we are flushing the queue. As we are flushing the queue, we may
        // have remove the request from the queue before the cancel routine gets
        // a chance to remove it.
        //

        //
        // Complete the IRP.
        //
        Irp->IoStatus.Status = STATUS_CANCELLED;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

    }
   
    return;
 
} 

  
NTSTATUS
pDummyDmaCanStopDevice(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp
    )
/*++

Routine Description:

    This routine determines if the device can be safely stopped. In our 
    particular case, we'll assume we can always stop the device.
    A device might fail the request if it doesn't have a queue for the
    requests it might come or if it was notified that it is in the paging
    path. 

Arguments:

    DeviceObject - pointer to the device object.
    
    Irp - pointer to the current IRP.
    
Return Value:

    STATUS_SUCCESS if the device can be safely stopped, an appropriate 
    NT Status if not.

--*/
{
    UNREFERENCED_PARAMETER(DeviceObject);
    UNREFERENCED_PARAMETER(Irp);

    return STATUS_SUCCESS;

}


NTSTATUS
pDummyDmaCanRemoveDevice(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp
    )
/*++

Routine Description:

    This routine determines is the device can be safely removed. In our 
    particular case, we'll assume we can always remove the device.
    A device might be removed if, for example, it has openend handles or
    removing the device could result in losing data (plus the reasons 
    mentioned at pDummyDmaCanStopDevice).

Arguments:

    DeviceObject - pointer to the device object.
    
    Irp - pointer to the current IRP.
    
Return Value:

    STATUS_SUCCESS if the device can be safely removed, an appropriate 
    NT Status if not.

--*/
{
    UNREFERENCED_PARAMETER(DeviceObject);
    UNREFERENCED_PARAMETER(Irp);

    return STATUS_SUCCESS;

}


NTSTATUS
pDummyDmaReturnResources (
    IN PDEVICE_OBJECT DeviceObject
    )

/*++

Routine Description:

    This routine returns all the resources acquired during
    device startup. 
    
Arguments:

    DeviceObject - pointer to the device object.
        
    
Return Value:

    STATUS_SUCCESS if the device can be safely removed, an appropriate 
    NT Status if not.

--*/
{
    PDUMMYDMA_DEVICE_EXTENSION	DeviceExtension =
        (PDUMMYDMA_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    PPUT_DMA_ADAPTER			PutDmaAdapter;

    //
    // Disconnect from the interrupt or unmap any I/O ports
    // that are mapped in StartDevice.
    //

    //
    // Return the DMA adapter back to the system. Make sure to NULL out the
    // DMA adapter to prevent further access to it.
    //
    PutDmaAdapter = DeviceExtension->Adapter->DmaOperations->PutDmaAdapter;
    PutDmaAdapter(DeviceExtension->Adapter);
    DeviceExtension->Adapter = NULL;
    
    return STATUS_SUCCESS;
}


NTSTATUS
DummyDmaSendIrpSynchronously (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    Sends the Irp down the stack and waits for it to complete.
    
Arguments:

    DeviceObject - pointer to the device object.
    
    Irp - pointer to the current IRP.
    
Return Value:

    NT status code

--*/
{
    KEVENT   event;
    NTSTATUS status;

    PAGED_CODE();

    KeInitializeEvent(&event, SynchronizationEvent, FALSE);

    IoCopyCurrentIrpStackLocationToNext(Irp);

    IoSetCompletionRoutine(Irp,
                           DummyDmaDispatchPnpComplete,
                           &event,
                           TRUE,
                           TRUE,
                           TRUE
                           );

    status = IoCallDriver(DeviceObject, Irp);

    //
    // Wait for lower drivers to be done with the Irp
    //
    
    if (status == STATUS_PENDING) {
       KeWaitForSingleObject(&event,
                             Executive,
                             KernelMode,
                             FALSE,
                             NULL
                             );
       status = Irp->IoStatus.Status;
    }

    return status;
}


LONG
DummyDmaIoIncrement(
    IN  OUT PDUMMYDMA_DEVICE_EXTENSION   DeviceExtension
    )   
/*++

Routine Description:

    This routine increments the number of requests the device receives
    
Arguments:

    DeviceExtension - pointer to the device extension.
    
Return Value:

    The value of OutstandingIO field in the device extension.

--*/
{

    LONG            result;
    
    result = InterlockedIncrement(&DeviceExtension->OutstandingIO);
   
    ASSERT(result > 0);

    //
    // Need to clear StopEvent (when OutstandingIO bumps from 1 to 2) 
    //
    if (result == 2) {

        //
        // We need to clear the event
        //

        KeClearEvent(&DeviceExtension->StopEvent);

    }

    return result;

}


LONG
DummyDmaIoDecrement(
    IN  OUT PDUMMYDMA_DEVICE_EXTENSION  DeviceExtension
    )   

/*++

Routine Description:

    This routine decrements the number of requests the device receives

Arguments:

    DeviceObject - pointer to the device object.
    
Return Value:

    The value of OutstandingIO field in the device extension.

--*/
{

    LONG            result;
    
    result = InterlockedDecrement(&DeviceExtension->OutstandingIO);
   
    ASSERT(result >= 0);

    if (result == 1) {

        //
        // Set the stop event. Note that when this happens
        // (i.e. a transition from 2 to 1), the type of requests we 
        // want to be processed are already held instead of being 
        // passed away, so that we can't "miss" a request that
        // will appear between the decrement and the moment when
        // the value is actually used.
        //
 
        KeSetEvent (&DeviceExtension->StopEvent, 
                    IO_NO_INCREMENT, 
                    FALSE);
        
    }
    
    if (result == 0) {

        //
        // The count is 1-biased, so it can be zero only if an 
        // extra decrement is done when a remove Irp is received 
        //
        ASSERT(DeviceExtension->DevicePnPState == Deleted);

        //
        // Set the remove event, so the device object can be deleted
        //
        KeSetEvent (&DeviceExtension->RemoveEvent, 
                    IO_NO_INCREMENT, 
                    FALSE);
        
    }

    return result;

}


PCHAR
PnPMinorFunctionString (
    UCHAR MinorFunction
)
{
    switch (MinorFunction)
    {
        case IRP_MN_START_DEVICE:
            return "IRP_MN_START_DEVICE";
        case IRP_MN_QUERY_REMOVE_DEVICE:
            return "IRP_MN_QUERY_REMOVE_DEVICE";
        case IRP_MN_REMOVE_DEVICE:
            return "IRP_MN_REMOVE_DEVICE";
        case IRP_MN_CANCEL_REMOVE_DEVICE:
            return "IRP_MN_CANCEL_REMOVE_DEVICE";
        case IRP_MN_STOP_DEVICE:
            return "IRP_MN_STOP_DEVICE";
        case IRP_MN_QUERY_STOP_DEVICE:
            return "IRP_MN_QUERY_STOP_DEVICE";
        case IRP_MN_CANCEL_STOP_DEVICE:
            return "IRP_MN_CANCEL_STOP_DEVICE";
        case IRP_MN_QUERY_DEVICE_RELATIONS:
            return "IRP_MN_QUERY_DEVICE_RELATIONS";
        case IRP_MN_QUERY_INTERFACE:
            return "IRP_MN_QUERY_INTERFACE";
        case IRP_MN_QUERY_CAPABILITIES:
            return "IRP_MN_QUERY_CAPABILITIES";
        case IRP_MN_QUERY_RESOURCES:
            return "IRP_MN_QUERY_RESOURCES";
        case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
            return "IRP_MN_QUERY_RESOURCE_REQUIREMENTS";
        case IRP_MN_QUERY_DEVICE_TEXT:
            return "IRP_MN_QUERY_DEVICE_TEXT";
        case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:
            return "IRP_MN_FILTER_RESOURCE_REQUIREMENTS";
        case IRP_MN_READ_CONFIG:
            return "IRP_MN_READ_CONFIG";
        case IRP_MN_WRITE_CONFIG:
            return "IRP_MN_WRITE_CONFIG";
        case IRP_MN_EJECT:
            return "IRP_MN_EJECT";
        case IRP_MN_SET_LOCK:
            return "IRP_MN_SET_LOCK";
        case IRP_MN_QUERY_ID:
            return "IRP_MN_QUERY_ID";
        case IRP_MN_QUERY_PNP_DEVICE_STATE:
            return "IRP_MN_QUERY_PNP_DEVICE_STATE";
        case IRP_MN_QUERY_BUS_INFORMATION:
            return "IRP_MN_QUERY_BUS_INFORMATION";
        case IRP_MN_DEVICE_USAGE_NOTIFICATION:
            return "IRP_MN_DEVICE_USAGE_NOTIFICATION";
        case IRP_MN_SURPRISE_REMOVAL:
            return "IRP_MN_SURPRISE_REMOVAL";
            
        default:
            return "IRP_MN_?????";
    }
}


