// ===================================================================
//
//      testpnp.c
//
//      rev 1.0 beta
//
//      this module handles USB PnP calls.
//
//      USB device driver for USB Device Example 
//      kernel mode driver 
//
//
//      to be compiled with 
//      - Windows 98 Beta 3 DDK 
//     
// ===================================================================

#include <wdm.h>
#include "test98.h"
#include "stdio.h"

// ==================================================================
NTSTATUS
Test98_AddDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT PhysicalDeviceObject
    )
/*++
    
    to create and initialize our FDO, set things up...

    DeviceObject - Device object representing the new FDO.

    DriverObject - This very self referenced driver.

--*/
{
    NTSTATUS            status;
    PDEVICE_OBJECT      deviceObject;
    PFDO_DEVICE_DATA    deviceData;
    UNICODE_STRING      deviceNameUni;
    PWCHAR              deviceName;
    ULONG               nameLength;

    UNICODE_STRING      pdoUniName;
    WCHAR               pdoName[] = L"\\Device\\Test-0" ;  // DeviceNameBuffer
    
    WCHAR DeviceLinkBuffer[] = L"\\DosDevices\\Test-0";
    UNICODE_STRING  DeviceLinkUnicodeString;   

    RtlInitUnicodeString (&pdoUniName, pdoName);
        
    status = IoCreateDevice (
                    DriverObject,  // our driver object
                    sizeof (FDO_DEVICE_DATA), // device object extension size
                    
                    &pdoUniName,  
                    
                    FILE_DEVICE_UNKNOWN,   
                    0,          // No special characteristics
                    FALSE,      
                    &deviceObject); // The device object created

    if (NT_SUCCESS (status)) {

        RtlInitUnicodeString (&DeviceLinkUnicodeString, 
                                DeviceLinkBuffer);

        status = IoCreateSymbolicLink(&DeviceLinkUnicodeString,
                        &pdoUniName);                                

        deviceData = (PFDO_DEVICE_DATA) deviceObject->DeviceExtension;
        RtlFillMemory (deviceData, sizeof (FDO_DEVICE_DATA), 0);

        deviceData->IsFDO = TRUE;
        deviceData->DebugLevel = TEST_DEFAULT_DEBUG_OUTPUT_LEVEL;
        deviceData->Self = deviceObject;
 
        KeInitializeSpinLock (&deviceData->Spin);

        deviceData->Removed = FALSE;
        
        InitializeListHead (&deviceData->PDOs);

        // Set the PDO for use with PlugPlay functions
        deviceData->UnderlyingPDO = PhysicalDeviceObject;

        // Attach our driver to the device stack.
        deviceData->TopOfStack = IoAttachDeviceToDeviceStack (
                                        deviceObject,
                                        PhysicalDeviceObject);

        // Bias outstanding request to 1 so that we can look for a
        // transition to zero when processing the remove device PlugPlay IRP.
        
        deviceData->OutstandingIO = 1;

        KeInitializeEvent(&deviceData->RemoveEvent,
                          SynchronizationEvent,
                          FALSE); // initialized to not signalled

        deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
        deviceObject->Flags |= DO_POWER_PAGABLE;

        // register device functionality
        status = IoRegisterDeviceInterface (
                    PhysicalDeviceObject,
                    (LPGUID) &GUID_TEST_DEVICE,
                    NULL, // No ref string
                    &deviceData->DevClassAssocName);

        status = IoSetDeviceInterfaceState (
                        &deviceData->DevClassAssocName,
                        TRUE);

        }

return status;

}

// ==================================================================
NTSTATUS
Test98_FDO_PnPComplete (
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Pirp,
    IN PVOID            Context
    );

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

    Handles Major PnP IRPS...

--*/
{
    PIO_STACK_LOCATION      irpStack;
    NTSTATUS                status;
    PCOMMON_DEVICE_DATA     commonData;
    KIRQL                   oldIrq;

    PAGED_CODE ();

    status = STATUS_SUCCESS;
    irpStack = IoGetCurrentIrpStackLocation (Irp);
    ASSERT (IRP_MJ_PNP == irpStack->MajorFunction);

    commonData = (PCOMMON_DEVICE_DATA) DeviceObject->DeviceExtension;

    
    status = Test98_FDO_PnP (
                    DeviceObject,
                    Irp,
                    irpStack,
                    (PFDO_DEVICE_DATA) commonData);
    
    return status;

}

// ==================================================================
NTSTATUS
Test98_FDO_PnP (
    IN PDEVICE_OBJECT       DeviceObject,
    IN PIRP                 Irp,
    IN PIO_STACK_LOCATION   IrpStack,
    IN PFDO_DEVICE_DATA     DeviceData
    )
/*++

    Handles requests from the PlugPlay system for the FDO

--*/
{
    NTSTATUS    status;
    KIRQL       oldIrq;
    KEVENT      event;
    ULONG       length;
    ULONG       i;
    PLIST_ENTRY entry;
    PPDO_DEVICE_DATA    pdoData;
    PDEVICE_RELATIONS   relations;
    PIO_STACK_LOCATION  stack;

    PAGED_CODE ();

    status = Test98_IncIoCount (DeviceData);
    if (!NT_SUCCESS (status)) {
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
    }

    stack = IoGetCurrentIrpStackLocation (Irp);

    switch (IrpStack->MinorFunction) {
    case IRP_MN_START_DEVICE:
        // PnP manager send this IPR after assigning resoureces to the device; 
        // the device may have been recently numerate and being started 
        // for the first time 

        if (DeviceData->Started) {
            status = STATUS_SUCCESS;
            break;
        }

        Test98_KdPrint (DeviceData, TEST_DBG_PNP_TRACE, ("Start Device\n"));

        KeInitializeEvent (&event, NotificationEvent, FALSE);
        IoCopyCurrentIrpStackLocationToNext (Irp);

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

        status = IoCallDriver (DeviceData->TopOfStack, Irp);

        if (STATUS_PENDING == status) {
            // wait for it...

            status = KeWaitForSingleObject (&event,
                                            Executive,
                                            KernelMode,
                                            FALSE, // Not allertable
                                            NULL); // No timeout structure

            ASSERT (STATUS_SUCCESS == status);

            status = Irp->IoStatus.Status;
        }

        if (NT_SUCCESS(status)) {
            //
            // Do what ever after device started
            //

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

                status = STATUS_INSUFFICIENT_RESOURCES;
                break;
            }
            status = Test98_StartFdo (DeviceData,
                                    &stack->Parameters.StartDevice.AllocatedResources->List[0].PartialResourceList,
                                    &stack->Parameters.StartDevice.AllocatedResourcesTranslated->List[0].PartialResourceList);


            if (NT_SUCCESS (status)) {
                DeviceData->Started = TRUE;
            }
        }

        //
        // We must now complete the IRP, since we stopped it in the
        // completetion routine with MORE_PROCESSING_REQUIRED.
        //

        Irp->IoStatus.Information = 0;
        break;

    case IRP_MN_QUERY_STOP_DEVICE:
        Test98_KdPrint (DeviceData, TEST_DBG_PNP_TRACE, ("Query Stop Device\n"));

        // This occurs during shutdown when user explicitly requesting
        // the service to be stopped
        //        
       
        if (DeviceData->NumPDOs) {
            status = STATUS_UNSUCCESSFUL;
        } else {
            status = STATUS_SUCCESS;
        }

        Irp->IoStatus.Status = status;
        IoSkipCurrentIrpStackLocation (Irp);
        status = IoCallDriver (DeviceData->TopOfStack, Irp);

        Test98_DecIoCount (DeviceData);
        return status;

    case IRP_MN_STOP_DEVICE:
        Test98_KdPrint (DeviceData, TEST_DBG_PNP_TRACE, ("Stop Device\n"));

        // PnP manager sends this IPR to stop a device so that it can reconfigure
        // its resources.  sends this only after IRP_MN_QUERY_STOP_DEVICE
        // completed successfully
        //
        if (DeviceData->Started) {
            //
            // Free resources given by start device.
            //
            if (DeviceData->MappedPorts) {
                MmUnmapIoSpace (DeviceData->TestPortAddress, 1);
            }
        }
        DeviceData->Started = FALSE;

        //
        // don't need a completion routine so fire and forget.
        //
        Irp->IoStatus.Status = STATUS_SUCCESS;
        IoSkipCurrentIrpStackLocation (Irp);
        status = IoCallDriver (DeviceData->TopOfStack, Irp);

        Test98_DecIoCount (DeviceData);
        return status;

    case IRP_MN_REMOVE_DEVICE:
        Test98_KdPrint (DeviceData, TEST_DBG_PNP_TRACE, ("Remove Device\n"));

        //
        // PnP manager uses this IRP to dirct drivers to remove a device
        //
        DeviceData->Removed = TRUE;

        //
        // Complete any outstanding IRPs queued by the driver here.
        //

        IoSetDeviceInterfaceState (&DeviceData->DevClassAssocName, FALSE);

        //
        // complete any outstanding reqeusts now.
        // Fire and forget
        //
        
        IoSkipCurrentIrpStackLocation (Irp);
        IoCallDriver (DeviceData->TopOfStack, Irp);

        //
        // Wait for all outstanding requests to complete
        //
        i = InterlockedDecrement (&DeviceData->OutstandingIO);

        ASSERT (0 < i);

        if (0 != InterlockedDecrement (&DeviceData->OutstandingIO)) {
            Test98_KdPrint (DeviceData, TEST_DBG_PNP_INFO,
                          ("Remove Device waiting for request to complete\n"));

            KeWaitForSingleObject (&DeviceData->RemoveEvent,
                                   Suspended,
                                   KernelMode,
                                   FALSE, // Not Alertable
                                   NULL); // No timeout
        }
        //
        // Free the associated resources
        // Detatch from the undelying devices.
        //
        Test98_KdPrint(DeviceData, TEST_DBG_PNP_INFO,
                        ("IoDetachDevice: 0x%x\n", DeviceData->TopOfStack));
        IoDetachDevice (DeviceData->TopOfStack);

        //
        // Clean up any resources here
        //
        if (DeviceData->Started) {
            //
            // Free resources given by start device.
            //
            if (DeviceData->MappedPorts) {
                MmUnmapIoSpace (DeviceData->TestPortAddress, 1);
            }
        }

        ExFreePool (DeviceData->DevClassAssocName.Buffer);
        Test98_KdPrint(DeviceData, TEST_DBG_PNP_INFO,
                        ("IoDeleteDevice1: 0x%x\n", DeviceObject));
        IoDeleteDevice (DeviceObject);

        return STATUS_SUCCESS;

    case IRP_MN_QUERY_REMOVE_DEVICE:
        //
        // the driver indicates to system whether it can be removed  
        // in response to this IPR...
        //

        Irp->IoStatus.Status = STATUS_SUCCESS;
        IoSkipCurrentIrpStackLocation (Irp);
        status = IoCallDriver (DeviceData->TopOfStack, Irp);
        Test98_DecIoCount (DeviceData);
        return status;

    default:
        //
        // In the default case we merely call the next driver since
        // we don't know what to do.
        // Fire and Forget
        //
        IoSkipCurrentIrpStackLocation (Irp);

        //
        // Done, do NOT complete the IRP, it will be processed by the lower
        // device object, which will complete the IRP
        //

        status = IoCallDriver (DeviceData->TopOfStack, Irp);
        Test98_DecIoCount (DeviceData);
        return status;
    }

    Irp->IoStatus.Status = status;
    DbgRaiseIrql (DISPATCH_LEVEL, &oldIrq);
    IoCompleteRequest (Irp, IO_NO_INCREMENT);
    DbgLowerIrql (oldIrq);

    Test98_DecIoCount (DeviceData);
    return status;
}

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

    A completion routine for use when calling the lower device objects to
    which our FDO is attached.

--*/
{
    UNREFERENCED_PARAMETER (DeviceObject);

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

    KeSetEvent ((PKEVENT) Context, 1, FALSE);
    // No special priority
    // No Wait

    return STATUS_MORE_PROCESSING_REQUIRED; // Keep this IRP
}

// ==================================================================
NTSTATUS
Test98_PnPRemove (
    PDEVICE_OBJECT      Device,
    PPDO_DEVICE_DATA    PdoData
    )
/*++
    The PnP manager has instructed that this driver should be removed.

    We should therefore complete any requests queued in the driver
    and IoDeleteDevice.  and Return from the dispatch routine.

--*/

{
    PdoData->Removed = TRUE;

    //
    // Complete any outsanding requests with STATUS_DELETE_PENDING.
    //
    if (PdoData->Attached) {
        return STATUS_SUCCESS;
    }

    //
    // Free any resources.
    //
    ExFreePool (PdoData->HardwareIDs);

    Test98_KdPrint(PdoData, TEST_DBG_PNP_INFO,
                        ("IoDeleteDevice2: 0x%x\n", Device));
    IoDeleteDevice (Device);
    return STATUS_SUCCESS;
}

// ==================================================================
NTSTATUS
Test98_StartFdo (
    IN  PFDO_DEVICE_DATA            FdoData,
    IN  PCM_PARTIAL_RESOURCE_LIST   PartialResourceList,
    IN  PCM_PARTIAL_RESOURCE_LIST   PartialResourceListTranslated
    )
/*
        starting our FDO...

*/
{
    ULONG i;
    NTSTATUS status = STATUS_SUCCESS;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resource;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resourceTrans;

    Test98_KdPrint (FdoData, TEST_DBG_PNP_TRACE, ("StartFdo\n"));

    for (i = 0,
         resource = &PartialResourceList->PartialDescriptors[0],
         resourceTrans = &PartialResourceListTranslated->PartialDescriptors[0];

         i < PartialResourceList->Count && NT_SUCCESS(status);
         i++, resource++, resourceTrans++) {

        switch (resource->Type) {
        case CmResourceTypePort:

            FdoData->PhysicalAddress = resource->u.Port.Start;
            Test98_KdPrint (FdoData, TEST_DBG_PNP_INFO,
                          ("HardwareResource: Port (%x) ->",
                           FdoData->PhysicalAddress.LowPart));

            switch (resourceTrans->Type) {
            case CmResourceTypePort:

                ASSERT (1 == resourceTrans->u.Port.Length);

                // Nothing to do here but note the address;
                FdoData->TestPortAddress =
                    (PVOID) resourceTrans->u.Port.Start.LowPart;

                break;

            case CmResourceTypeMemory:
                //
                // We need to map the memory
                //

                ASSERT (1 == resourceTrans->u.Memory.Length);

                FdoData->TestPortAddress =
                    MmMapIoSpace (resourceTrans->u.Memory.Start,
                                  resourceTrans->u.Memory.Length,
                                  MmNonCached);


                FdoData->MappedPorts = TRUE;

                Test98_KdPrint_Cont (FdoData, TEST_DBG_PNP_INFO,
                                   ("Mem: (%x)\n", FdoData->TestPortAddress));
                break;

            default:
                Test98_KdPrint_Cont (FdoData, TEST_DBG_PNP_INFO,
                                   ("Unknown \n", FdoData->TestPortAddress));
                
            }

            break;

        case CmResourceTypeMemory:

            ASSERT (CmResourceTypeMemory == resourceTrans->Type);

            FdoData->PhysicalAddress = resource->u.Memory.Start;
            FdoData->TestPortAddress =
                MmMapIoSpace (resourceTrans->u.Memory.Start,
                              resourceTrans->u.Memory.Length,
                              MmNonCached);

            FdoData->MappedPorts = TRUE;

            ASSERT (1 == resource->u.Memory.Length);

            Test98_KdPrint (FdoData, TEST_DBG_PNP_INFO,
                          ("HardwareResource: Memory (%x) -> Mem (%x)",
                           FdoData->PhysicalAddress.LowPart,
                           FdoData->TestPortAddress));

            break;

        case CmResourceTypeInterrupt:
        default:
            
            Test98_KdPrint (FdoData, TEST_DBG_PNP_ERROR,
                          ("Unhandled resource type (0x%x)\n",
                           resource->Type));
            status = STATUS_UNSUCCESSFUL;
        }
    }
    return status;
}

// ==================================================================
NTSTATUS
Test98_Remove (
    PTEST98_REMOVE_HARDWARE   Remove,
    PFDO_DEVICE_DATA            FdoData
    )
/*
    to remove the FDO and then tell Plug and Play about it.

*/
{
    KIRQL               irql;
    PLIST_ENTRY         entry;
    PPDO_DEVICE_DATA    pdoData;
    BOOLEAN             found = FALSE;
    PVOID               handle = NULL;

    KeAcquireSpinLock (&FdoData->Spin, &irql);

    for (entry = FdoData->PDOs.Flink;
         entry != &FdoData->PDOs;
         entry = entry->Flink) {

        pdoData = CONTAINING_RECORD (entry, PDO_DEVICE_DATA, Link);
        if (!handle) {
            handle = pdoData->Self;
        }
        if (handle == Remove->HardwareHandle) {
            pdoData->Attached = FALSE;
            RemoveEntryList (&pdoData->Link);
            FdoData->NumPDOs--;
            found = TRUE;
        }
    }
    KeReleaseSpinLock (&FdoData->Spin, irql);

    if (found) {
        IoInvalidateDeviceRelations (FdoData->UnderlyingPDO, BusRelations);
        return STATUS_SUCCESS;
    }
    return STATUS_INVALID_PARAMETER;
}

// ==================================================================
NTSTATUS
Test98_Power (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*

    We do nothing special for power;

*/
{
    PIO_STACK_LOCATION  irpStack;
    NTSTATUS            status;
    PCOMMON_DEVICE_DATA commonData;

    PAGED_CODE ();

    status = STATUS_SUCCESS;
    irpStack = IoGetCurrentIrpStackLocation (Irp);
    ASSERT (IRP_MJ_POWER == irpStack->MajorFunction);

    commonData = (PCOMMON_DEVICE_DATA) DeviceObject->DeviceExtension;

    status = Test98_FDO_Power ((PFDO_DEVICE_DATA) DeviceObject->DeviceExtension,
                                Irp);
    return status;
}


// ==================================================================
NTSTATUS
Test98_PowerComplete (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    );

// ==================================================================
NTSTATUS
Test98_FDO_Power (
    PFDO_DEVICE_DATA    Data,
    PIRP                Irp
    )
{
    NTSTATUS            status;
    BOOLEAN             hookit = FALSE;
    POWER_STATE         powerState;
    POWER_STATE_TYPE    powerType;
    PIO_STACK_LOCATION  stack;

    stack = IoGetCurrentIrpStackLocation (Irp);
    powerType = stack->Parameters.Power.Type;
    powerState = stack->Parameters.Power.State;

    PAGED_CODE ();

    status = Test98_IncIoCount (Data);
    if (!NT_SUCCESS (status)) {
        PoStartNextPowerIrp (Irp);
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
    }

    switch (stack->MinorFunction) {
    case IRP_MN_SET_POWER:
        Test98_KdPrint(Data,
                     TEST_DBG_PNP_TRACE,
                     ("Test-PnP Setting %s state to %d\n",
                      ((powerType == SystemPowerState) ?  "System" : "Device"),
                      powerState.SystemState));

        switch (powerType) {
        case DevicePowerState:

            status = Irp->IoStatus.Status = STATUS_SUCCESS;

            if (Data->DeviceState == powerState.DeviceState) {
                break;

            } else if (Data->DeviceState < powerState.DeviceState) {
                //
                // Powering down
                //

                PoSetPowerState (Data->Self, powerType, powerState);
                Data->DeviceState = powerState.DeviceState;

            } else {
                //
                // Powering Up
                //
                hookit = TRUE;
            }

            break;

        case SystemPowerState:
            if (Data->PowerQueryLock) {
                //
                // The reception of a power irp resolves the query lock.
                //
                Data->PowerQueryLock = FALSE;
            } else {
                ASSERT (Data->SystemState != powerState.SystemState);
            }

            status = Irp->IoStatus.Status = STATUS_SUCCESS;

            if (Data->SystemState == powerState.SystemState) {
                break;

            } else if (Data->SystemState < powerState.SystemState) {
                //
                // Powering down
                //

                Data->SystemState = powerState.SystemState;

                powerState.DeviceState = PowerDeviceD3;
                PoRequestPowerIrp (Data->Self,
                                   IRP_MN_SET_POWER,
                                   powerState,
                                   NULL, // no completion function
                                   NULL, // and no context
                                   NULL);

            } else {
                //
                // Powering Up
                //
                hookit = TRUE;
            }
            break;
        }

        break;

    case IRP_MN_QUERY_POWER:
        //
        Data->PowerQueryLock = TRUE;
        status = Irp->IoStatus.Status = STATUS_SUCCESS;
        break;

    default:
        break;
    }

    IoCopyCurrentIrpStackLocationToNext (Irp);

    if (hookit) {
        status = Test98_IncIoCount (Data);
        ASSERT (STATUS_SUCCESS == status);
        IoSetCompletionRoutine (Irp,
                                Test98_PowerComplete,
                                NULL,
                                TRUE,
                                TRUE,
                                TRUE);
        status = PoCallDriver (Data->TopOfStack, Irp);

    } else {
        //
        // Power IRPS come synchronously; drivers must call
        // PoStartNextPowerIrp, when they are ready for the next power
        // irp.  This can be called here, or in the completion routine
        //
        PoStartNextPowerIrp (Irp);

        status =  PoCallDriver (Data->TopOfStack, Irp);
    }

    Test98_DecIoCount (Data);
    return status;
}

// ==================================================================
NTSTATUS
Test98_PowerComplete (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    POWER_STATE         powerState;
    POWER_STATE_TYPE    powerType;
    PIO_STACK_LOCATION  stack;
    PFDO_DEVICE_DATA    data;

    UNREFERENCED_PARAMETER (Context);

    data = (PFDO_DEVICE_DATA) DeviceObject->DeviceExtension;
    stack = IoGetCurrentIrpStackLocation (Irp);
    powerType = stack->Parameters.Power.Type;
    powerState = stack->Parameters.Power.State;

    switch (stack->MinorFunction) {
    case IRP_MN_SET_POWER:
        switch (powerType) {
        case DevicePowerState:
            ASSERT (powerState.DeviceState < data->DeviceState);
            data->DeviceState = powerState.DeviceState;

            PoSetPowerState (data->Self, powerType, powerState);
            break;

        case SystemPowerState:
            //
            // the work of requesting the Device Power IRP on
            // behalf of the SystemPower Irp is work done by the FDO
            //

            ASSERT (powerState.SystemState < data->SystemState);
            data->SystemState = powerState.SystemState;

            powerState.DeviceState = PowerDeviceD0;
            PoRequestPowerIrp (data->Self,
                               IRP_MN_SET_POWER,
                               powerState,
                               NULL, // no completion function
                               NULL, // and no context
                               NULL);
            break;
        }
        break;

    case IRP_MN_QUERY_POWER:



        ASSERT (IRP_MN_QUERY_POWER != stack->MinorFunction);
        break;

    default:
        ASSERT (0xBADBAD == IRP_MN_QUERY_POWER);
        break;
    }


    PoStartNextPowerIrp (Irp);
    Test98_DecIoCount (data);

    return STATUS_SUCCESS;
}


