/*
 * UNIMODEM "Fakemodem" controllerless driver illustrative example
 *
 * (C) 2000 Microsoft Corporation
 * All Rights Reserved
 *
 */

#include "fakemodem.h"


#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,FakeModemPnP)
#pragma alloc_text(PAGE,FakeModemDealWithResources)
#endif


NTSTATUS
ForwardIrp(
    PDEVICE_OBJECT   NextDevice,
    PIRP   Irp
    )

{
    IoSkipCurrentIrpStackLocation(Irp);
    return IoCallDriver(NextDevice, Irp);

}


NTSTATUS
FakeModemAdapterIoCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PKEVENT pdoIoCompletedEvent
    )
{
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);

    KeSetEvent(pdoIoCompletedEvent, IO_NO_INCREMENT, FALSE);

    return STATUS_MORE_PROCESSING_REQUIRED;
}


NTSTATUS
WaitForLowerDriverToCompleteIrp(
   PDEVICE_OBJECT    TargetDeviceObject,
   PIRP              Irp,
   PKEVENT           Event
   )

{
    NTSTATUS         Status;

    KeResetEvent(Event);

    IoSetCompletionRoutine(Irp, FakeModemAdapterIoCompletion, Event, TRUE, 
            TRUE, TRUE);

    Status = IoCallDriver(TargetDeviceObject, Irp);

    if (Status == STATUS_PENDING) 
    {
         D_ERROR(DbgPrint("MODEM: Waiting for PDO\n");)

         KeWaitForSingleObject(Event, Executive, KernelMode, FALSE, NULL);
    }

    return Irp->IoStatus.Status;

}

NTSTATUS
FakeModemPnP(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

{

    PDEVICE_EXTENSION deviceExtension = DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
    KEVENT pdoStartedEvent;
    NTSTATUS status;
    PDEVICE_RELATIONS deviceRelations = NULL;
    PDEVICE_RELATIONS *DeviceRelations;

    ULONG newRelationsSize, oldRelationsSize = 0;

    switch (irpSp->MinorFunction) {

        case IRP_MN_START_DEVICE:

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_START_DEVICE\n");)

            //  Send this down to the PDO first so the bus driver can setup
            //  our resources so we can talk to the hardware

            KeInitializeEvent(&deviceExtension->PdoStartEvent, 
                    SynchronizationEvent, FALSE);

            IoCopyCurrentIrpStackLocationToNext(Irp);

            status=WaitForLowerDriverToCompleteIrp(
                    deviceExtension->LowerDevice, Irp, 
                    &deviceExtension->PdoStartEvent);

            if (status == STATUS_SUCCESS) 
            {
                deviceExtension->Started=TRUE;
                //
                //  do something useful with resources
                //
                FakeModemDealWithResources(DeviceObject, Irp);
            }


            Irp->IoStatus.Status = status;
            Irp->IoStatus.Information=0L;

            IoCompleteRequest(Irp, IO_NO_INCREMENT);

            return status;

        case IRP_MN_QUERY_DEVICE_RELATIONS: {

            PDEVICE_RELATIONS    CurrentRelations=
                (PDEVICE_RELATIONS)Irp->IoStatus.Information;

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_QUERY_DEVICE_RELATIONS type=%d\n",irpSp->Parameters.QueryDeviceRelations.Type);)
            D_PNP(DbgPrint("                                         Information=%08lx\n",Irp->IoStatus.Information);)

            switch (irpSp->Parameters.QueryDeviceRelations.Type ) 
            {
                case TargetDeviceRelation:

                default: {

                    IoCopyCurrentIrpStackLocationToNext(Irp);

                    return IoCallDriver(deviceExtension->LowerDevice, Irp);

                }
            }

        }

        case IRP_MN_QUERY_REMOVE_DEVICE:

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_QUERY_REMOVE_DEVICE\n");)

            deviceExtension->Removing=TRUE;

            return ForwardIrp(deviceExtension->LowerDevice,Irp);


        case IRP_MN_CANCEL_REMOVE_DEVICE:

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_CANCEL_REMOVE_DEVICE\n");)

            deviceExtension->Removing=FALSE;

            return ForwardIrp(deviceExtension->LowerDevice,Irp);


        case IRP_MN_SURPRISE_REMOVAL:

            // Fall through

        case IRP_MN_REMOVE_DEVICE: {

            ULONG    NewReferenceCount;
            NTSTATUS StatusToReturn;

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_REMOVE_DEVICE\n");)

            //  the device is going away, block new requests
            
            deviceExtension->Removing=TRUE;

            // Complete all pending requests

            FakeModemKillPendingIrps(DeviceObject);

            // send it down to the PDO
            
            IoCopyCurrentIrpStackLocationToNext(Irp);

            StatusToReturn=IoCallDriver(deviceExtension->LowerDevice, Irp);

            //  remove the ref for the AddDevice

            NewReferenceCount=InterlockedDecrement
                (&deviceExtension->ReferenceCount);

            if (NewReferenceCount != 0) {
            
                //  Still have outstanding request, wait
           
                D_PNP(DbgPrint("FAKEMODEM: IRP_MN_REMOVE_DEVICE- waiting for refcount to drop, %d\n",NewReferenceCount);)

                KeWaitForSingleObject(&deviceExtension->RemoveEvent, 
                        Executive, KernelMode, FALSE, NULL);

                D_PNP(DbgPrint("FAKEMODEM: IRP_MN_REMOVE_DEVICE- Done waiting\n");)
            }

            ASSERT(deviceExtension->ReferenceCount == 0);

            IoDetachDevice(deviceExtension->LowerDevice);

            IoDeleteDevice(DeviceObject);

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_REMOVE_DEVICE %08lx\n",StatusToReturn);)

            return StatusToReturn;
        }


        case IRP_MN_QUERY_STOP_DEVICE:

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_QUERY_STOP_DEVICE\n");)

            if (deviceExtension->OpenCount != 0) {
                
                //  no can do
                
                D_PNP(DbgPrint("FAKEMODEM: IRP_MN_QUERY_STOP_DEVICE -- failing\n");)

                Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;

                IoCompleteRequest( Irp, IO_NO_INCREMENT);

                return STATUS_UNSUCCESSFUL;
            }

            deviceExtension->Started=FALSE;

            return ForwardIrp(deviceExtension->LowerDevice,Irp);


        case IRP_MN_CANCEL_STOP_DEVICE:

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_CANCEL_STOP_DEVICE\n");)

            deviceExtension->Started=TRUE;

            return ForwardIrp(deviceExtension->LowerDevice,Irp);

        case IRP_MN_STOP_DEVICE:

            D_PNP(DbgPrint("FAKEMODEM: IRP_MN_STOP_DEVICE\n");)

            deviceExtension->Started=FALSE;

            return ForwardIrp(deviceExtension->LowerDevice,Irp);

        case IRP_MN_QUERY_CAPABILITIES: {

            ULONG   i;
            KEVENT  WaitEvent;

            // Send this down to the PDO first

            KeInitializeEvent(&WaitEvent, SynchronizationEvent, FALSE);

            IoCopyCurrentIrpStackLocationToNext(Irp);

            status=WaitForLowerDriverToCompleteIrp
                (deviceExtension->LowerDevice, Irp, &WaitEvent);

            irpSp = IoGetCurrentIrpStackLocation(Irp);

            for (i = PowerSystemUnspecified; i < PowerSystemMaximum;   i++) 
            {
                deviceExtension->SystemPowerStateMap[i]=PowerDeviceD3;
            }

            for (i = PowerSystemWorking; i < PowerSystemHibernate;  i++) {

                D_POWER(DbgPrint("FAKEMODEM: DevicePower for System %d is %d\n",i,irpSp->Parameters.DeviceCapabilities.Capabilities->DeviceState[i]);)
                deviceExtension->SystemPowerStateMap[i]=irpSp->Parameters.DeviceCapabilities.Capabilities->DeviceState[i];
            }

            deviceExtension->SystemPowerStateMap[PowerSystemWorking]=PowerDeviceD0;

            deviceExtension->SystemWake=irpSp->Parameters.DeviceCapabilities.Capabilities->SystemWake;
            deviceExtension->DeviceWake=irpSp->Parameters.DeviceCapabilities.Capabilities->DeviceWake;

            D_POWER(DbgPrint("FAKEMODEM: DeviceWake=%d, SystemWake=%d\n",
                        deviceExtension->DeviceWake,
                        deviceExtension->SystemWake);)

            IoCompleteRequest( Irp, IO_NO_INCREMENT);

            return status;

        }

        default:

            D_PNP(DbgPrint("FAKEMODEM: PnP IRP, MN func=%d\n",irpSp->MinorFunction);)

            return ForwardIrp(deviceExtension->LowerDevice,Irp);



    }

    // If device has started again then we can continue processing

    if (deviceExtension->Started)
    {
        WriteIrpWorker(DeviceObject);
    }


    return STATUS_SUCCESS;
}





NTSTATUS
FakeModemDealWithResources(
    IN PDEVICE_OBJECT   Fdo,
    IN PIRP             Irp
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
    ULONG count;
    ULONG i;


    PCM_RESOURCE_LIST pResourceList;
    PCM_PARTIAL_RESOURCE_LIST pPartialResourceList;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR pPartialResourceDesc;
    
    PCM_FULL_RESOURCE_DESCRIPTOR pFullResourceDesc = NULL;
    
    //  Get resource list
    
    pResourceList = irpSp->Parameters.StartDevice.AllocatedResources;

    if (pResourceList != NULL) {

        pFullResourceDesc   = &pResourceList->List[0];

    } else {

        pFullResourceDesc=NULL;

    }

    
    // Ok, if we have a full resource descriptor.  Let's take it apart.
    
    if (pFullResourceDesc) {

        pPartialResourceList    = &pFullResourceDesc->PartialResourceList;
        pPartialResourceDesc    = pPartialResourceList->PartialDescriptors;
        count                   = pPartialResourceList->Count;


        // Pull out the stuff that is in the full descriptor.

        // Now run through the partial resource descriptors looking for the
        // port interrupt, and clock rate.


        for (i = 0;     i < count;     i++, pPartialResourceDesc++) {

            switch (pPartialResourceDesc->Type) {

                case CmResourceTypeMemory: {

                    D_PNP(DbgPrint("FAKEMODEM: Memory resource at %x, length %d, addressSpace=%d\n",
                                    pPartialResourceDesc->u.Memory.Start.LowPart,
                                    pPartialResourceDesc->u.Memory.Length,
                                    pPartialResourceDesc->Flags
                                    );)
                    break;
                }


                case CmResourceTypePort: {

                    D_PNP(DbgPrint("FAKEMODEM: Port resource at %x, length %d, addressSpace=%d\n",
                                    pPartialResourceDesc->u.Port.Start.LowPart,
                                    pPartialResourceDesc->u.Port.Length,
                                    pPartialResourceDesc->Flags
                                    );)
                    break;
                }

                case CmResourceTypeDma: {

                    D_PNP(DbgPrint("FAKEMODEM: DMA channel %d, port %d, addressSpace=%d\n",
                                    pPartialResourceDesc->u.Dma.Channel,
                                    pPartialResourceDesc->u.Dma.Port
                                    );)

                    break;


                    break;
                }


                case CmResourceTypeInterrupt: {

                    D_PNP(DbgPrint("FAKEMODEM: Interrupt resource, level=%d, vector=%d, %s\n",
                                   pPartialResourceDesc->u.Interrupt.Level,
                                   pPartialResourceDesc->u.Interrupt.Vector,
                                   (pPartialResourceDesc->Flags & CM_RESOURCE_INTERRUPT_LATCHED) ? "Latched" : "Level"
                                   );)

                    break;
                }

        
                default: {

                    D_PNP(DbgPrint("FAKEMODEM: Other resources\n");)
                    break;
                }
            }
        }
    }

    return status;
}