/*++

Copyright (c) 1997-1998 Microsoft Corporation, All Rights Reserved

Module Name:

    pnp.c

Abstract:

    This module contains general PnP and Power code for the i8042prt Driver.

Environment:

    Kernel mode.

Revision History:

--*/
#include "i8042prt.h"
#include "i8042log.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, I8xAddDevice)
#pragma alloc_text(PAGE, I8xFilterResourceRequirements)
#pragma alloc_text(PAGE, I8xFindPortCallout)
#pragma alloc_text(PAGE, I8xManuallyRemoveDevice)
#pragma alloc_text(PAGE, I8xPnP)
#pragma alloc_text(PAGE, I8xPower)
#pragma alloc_text(PAGE, I8xRegisterDeviceInterface)
#pragma alloc_text(PAGE, I8xRemovePort)
#pragma alloc_text(PAGE, I8xSendIrpSynchronously) 
#endif

NTSTATUS
I8xAddDevice (
    IN PDRIVER_OBJECT   Driver,
    IN PDEVICE_OBJECT   PDO
    )
/*++

Routine Description:

    Adds a device to the stack and sets up the appropriate flags and 
    device extension for the newly created device.
    
Arguments:

    Driver - The driver object
    PDO    - the device that we are attaching ourselves on top of
    
Return Value:

    NTSTATUS result code.

--*/
{
    PCOMMON_DATA             commonData;
    PIO_ERROR_LOG_PACKET     errorLogEntry;
    PDEVICE_OBJECT           device;
    NTSTATUS                 status = STATUS_SUCCESS;
    ULONG                    maxSize;

    PAGED_CODE();

    Print(DBG_PNP_TRACE, ("enter Add Device \n"));

    maxSize = sizeof(PORT_KEYBOARD_EXTENSION) > sizeof(PORT_MOUSE_EXTENSION) ?
              sizeof(PORT_KEYBOARD_EXTENSION) :
              sizeof(PORT_MOUSE_EXTENSION);

    status = IoCreateDevice(Driver,                 // driver
                            maxSize,                // size of extension
                            NULL,                   // device name
                            FILE_DEVICE_8042_PORT,  // device type  ?? unknown at this time!!!
                            0,                      // device characteristics
                            FALSE,                  // exclusive
                            &device                 // new device
                            );

    if (!NT_SUCCESS(status)) {
        return (status);
    }

    RtlZeroMemory(device->DeviceExtension, maxSize);

    commonData = GET_COMMON_DATA(device->DeviceExtension);
    commonData->TopOfStack = IoAttachDeviceToDeviceStack(device, PDO);

    if (commonData->TopOfStack == NULL) {
        //
        // Not good; in only extreme cases will this fail
        //
        errorLogEntry = (PIO_ERROR_LOG_PACKET)
            IoAllocateErrorLogEntry(Driver, (UCHAR)sizeof(IO_ERROR_LOG_PACKET));
        if (errorLogEntry) {
            errorLogEntry->ErrorCode = I8042_ATTACH_DEVICE_FAILED;
            errorLogEntry->DumpDataSize = 0;
            errorLogEntry->SequenceNumber = 0;
            errorLogEntry->MajorFunctionCode = 0;
            errorLogEntry->IoControlCode = 0;
            errorLogEntry->RetryCount = 0;
            errorLogEntry->UniqueErrorValue = 0;
            errorLogEntry->FinalStatus =  STATUS_DEVICE_NOT_CONNECTED;

            IoWriteErrorLogEntry (errorLogEntry);
        }

        IoDeleteDevice (device);
        return STATUS_DEVICE_NOT_CONNECTED; 
    }

    ASSERT(commonData->TopOfStack);

    commonData->Self =          device;
    commonData->PDO =           PDO;
    commonData->PowerState =    PowerDeviceD0;

    KeInitializeSpinLock(&commonData->InterruptSpinLock);

    //
    // Initialize the data consumption timer
    //
    KeInitializeTimer(&commonData->DataConsumptionTimer);

    //
    // Initialize the port DPC queue to log overrun and internal
    // device errors.
    //
    KeInitializeDpc(
        &commonData->ErrorLogDpc,
        (PKDEFERRED_ROUTINE) I8042ErrorLogDpc,
        device
        );

    //
    // Initialize the device completion DPC for requests that exceed the
    // maximum number of retries.
    //
    KeInitializeDpc(
        &commonData->RetriesExceededDpc,
        (PKDEFERRED_ROUTINE) I8042RetriesExceededDpc,
        device
        );

    //
    // Initialize the device completion DPC for requests that have timed out
    //
    KeInitializeDpc(
        &commonData->TimeOutDpc,
        (PKDEFERRED_ROUTINE) I8042TimeOutDpc,
        device
        );

    //
    // Initialize the port completion DPC object in the device extension.
    // This DPC routine handles the completion of successful set requests.
    //
    IoInitializeDpcRequest(device, I8042CompletionDpc);

    IoInitializeRemoveLock(&commonData->RemoveLock,
                           I8042_POOL_TAG,
                           0,
                           0);

    device->Flags |= DO_BUFFERED_IO;
    device->Flags |= DO_POWER_PAGABLE;
    device->Flags &= ~DO_DEVICE_INITIALIZING;

    Print(DBG_PNP_TRACE, ("Add Device (0x%x)\n", status));

    return status;
}

NTSTATUS
I8xSendIrpSynchronously (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN BOOLEAN Strict
    )
/*++

Routine Description:

    Generic routine to send an irp DeviceObject and wait for its return up the
    device stack.
    
Arguments:

    DeviceObject - The device object to which we want to send the Irp
    
    Irp - The Irp we want to send
    
Return Value:

    return code from the Irp
--*/
{
    KEVENT   event;
    NTSTATUS status;

    PAGED_CODE();

    KeInitializeEvent(&event,
                      SynchronizationEvent,
                      FALSE
                      );

    IoCopyCurrentIrpStackLocationToNext(Irp);

    IoSetCompletionRoutine(Irp,
                           I8xPnPComplete,
                           &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;
    }

    if (!Strict && 
        (status == STATUS_NOT_SUPPORTED ||
         status == STATUS_INVALID_DEVICE_REQUEST)) {
        status = STATUS_SUCCESS;
    }

    return status;
}

NTSTATUS
I8xPnPComplete (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PKEVENT Event
    )
/*++

Routine Description:

    Completion routine for all PnP IRPs
    
Arguments:

    DeviceObject - Pointer to the DeviceObject

    Irp - Pointer to the request packet
    
    Event - The event to set once processing is complete 

Return Value:

    STATUS_MORE_PROCESSING_REQUIRED

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

    //
    // Since this completion routines sole purpose in life is to synchronize
    // Irp, we know that unless something else happens that the IoCallDriver
    // will unwind AFTER the we have complete this Irp.  Therefore we should
    // NOT bubble up the pending bit.
    //
    // if (Irp->PendingReturned) {
    //     IoMarkIrpPending(Irp);
    // }
    //

    KeSetEvent(Event, 0, FALSE);
    return STATUS_MORE_PROCESSING_REQUIRED;
}

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

Routine Description:

    This is the dispatch routine for PnP requests
Arguments:

    DeviceObject - Pointer to the device object

    Irp - Pointer to the request packet


Return Value:

    STATUS_SUCCESSFUL if successful,
    an valid NTSTATUS error code otherwise

--*/
{
    PPORT_KEYBOARD_EXTENSION   kbExtension;
    PPORT_MOUSE_EXTENSION      mouseExtension;
    PCOMMON_DATA               commonData;
    PIO_STACK_LOCATION         stack;
    NTSTATUS                   status = STATUS_SUCCESS;
    KIRQL                      oldIrql;

    PAGED_CODE();

    commonData = GET_COMMON_DATA(DeviceObject->DeviceExtension);
    stack = IoGetCurrentIrpStackLocation(Irp);

    status = IoAcquireRemoveLock(&commonData->RemoveLock, Irp);
    if (!NT_SUCCESS(status)) {
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        return status;
    }

    Print(DBG_PNP_TRACE,
          ("I8xPnP (%s),  enter (min func=0x%x)\n",
          commonData->IsKeyboard ? "kb" : "mou",
          (ULONG) stack->MinorFunction
          ));

    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.
        //
        status = I8xSendIrpSynchronously(commonData->TopOfStack, Irp, TRUE);

        if (NT_SUCCESS(status) && NT_SUCCESS(Irp->IoStatus.Status)) {
            //
            // As we are successfully now back from our start device
            // we can do work.

            ExAcquireFastMutexUnsafe(&Globals.DispatchMutex);

            if (commonData->Started) {
                Print(DBG_PNP_ERROR,
                      ("received 1+ starts on %s\n",
                      commonData->IsKeyboard ? "kb" : "mouse"
                      ));
            }
            else {
                //
                // commonData->IsKeyboard is set during
                //  IOCTL_INTERNAL_KEYBOARD_CONNECT to TRUE and 
                //  IOCTL_INTERNAL_MOUSE_CONNECT to FALSE
                //
                if (IS_KEYBOARD(commonData)) {
                    status = I8xKeyboardStartDevice(
                      (PPORT_KEYBOARD_EXTENSION) DeviceObject->DeviceExtension,
                      stack->Parameters.StartDevice.AllocatedResourcesTranslated
                      );
                }
                else {
                    status = I8xMouseStartDevice(
                      (PPORT_MOUSE_EXTENSION) DeviceObject->DeviceExtension,
                      stack->Parameters.StartDevice.AllocatedResourcesTranslated
                      );
                }
    
                if (NT_SUCCESS(status)) {
                    InterlockedIncrement(&Globals.StartedDevices);
                    commonData->Started = TRUE;
                }
            }

            ExReleaseFastMutexUnsafe(&Globals.DispatchMutex);
        }

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

        break;

    case IRP_MN_FILTER_RESOURCE_REQUIREMENTS: 
        //
        // The general rule of thumb for handling this minor code is this:  
        //    add resources when the irp is going down the stack and
        //    remove resources when the irp is coming back up the stack
        //
        // The irp has the original resources on the way down.
        //
        status = I8xSendIrpSynchronously(commonData->TopOfStack, Irp, FALSE);

        if (NT_SUCCESS(status)) {
            status = I8xFilterResourceRequirements(DeviceObject,
                                                   Irp
                                                   );
        }
        else {
           Print(DBG_PNP_ERROR,
                 ("error pending filter res req event (0x%x)\n",
                 status
                 ));
        }
   
        //
        // Irp->IoStatus.Information will contain the new i/o resource 
        // requirements list so leave it alone
        //
        Irp->IoStatus.Status = status;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        break;
    
    case IRP_MN_QUERY_PNP_DEVICE_STATE: 

        status = I8xSendIrpSynchronously(commonData->TopOfStack, Irp, FALSE);
        if (NT_SUCCESS(status)) {
            (PNP_DEVICE_STATE) Irp->IoStatus.Information |=
                commonData->PnpDeviceState;
        }
        else {
            Print(DBG_PNP_ERROR,
                  ("error pending query pnp device state event (0x%x)\n",
                  status
                  ));

        }
   
        //
        // Irp->IoStatus.Information will contain the new i/o resource 
        // requirements list so leave it alone
        //
        Irp->IoStatus.Status = status;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        break;

    //
    // Don't let either of the requests succeed, otherwise the kb/mouse
    // might be rendered useless.
    //
    //  NOTE: this behavior is particular to i8042prt.  Any other driver,
    //        especially any other keyboard or port driver, should 
    //        succeed the query remove or stop.  i8042prt has this different 
    //        behavior because of the shared I/O ports but independent interrupts.
    //
    //        FURTHERMORE, if you allow the query to succeed, it should be sent
    //        down the stack (see sermouse.sys for an example of how to do this)
    //
    case IRP_MN_QUERY_REMOVE_DEVICE:
    case IRP_MN_QUERY_STOP_DEVICE:
        status = (MANUALLY_REMOVED(commonData) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL);

        //
        // If we succeed the irp, we must send it down the stack
        //
        if (NT_SUCCESS(status)) {
            IoSkipCurrentIrpStackLocation(Irp);
            status = IoCallDriver(commonData->TopOfStack, Irp);
        }
        else {
            Irp->IoStatus.Status = status; 
            Irp->IoStatus.Information = 0;    
            IoCompleteRequest(Irp, IO_NO_INCREMENT);
        }
        break;

    //
    // PnP rules dictate we send the IRP down to the PDO first
    //
    case IRP_MN_CANCEL_REMOVE_DEVICE:
    case IRP_MN_CANCEL_STOP_DEVICE:
        status = I8xSendIrpSynchronously(commonData->TopOfStack, Irp, FALSE);

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

        break;

    // case IRP_MN_SURPRISE_REMOVAL:
    case IRP_MN_REMOVE_DEVICE:
        Print(DBG_PNP_INFO,
              ("(surprise) remove device (0x%x function 0x%x)\n",
              commonData->Self,
              (ULONG) stack->MinorFunction));

        if (commonData->Initialized) {
            IoWMIRegistrationControl(commonData->Self,
                                     WMIREG_ACTION_DEREGISTER
                                     );
        }

        if (commonData->Started) {
             InterlockedDecrement(&Globals.StartedDevices);
        }

        //
        // Wait for any pending I/O to drain
        //
        IoReleaseRemoveLockAndWait(&commonData->RemoveLock, Irp);

        ExAcquireFastMutexUnsafe(&Globals.DispatchMutex);
        if (IS_KEYBOARD(commonData)) {
            I8xKeyboardRemoveDevice(DeviceObject);
        }
        else {
            I8xMouseRemoveDevice(DeviceObject);
        }
        ExReleaseFastMutexUnsafe(&Globals.DispatchMutex);

        //
        // Set these flags so that when a surprise remove is sent, it will be
        // handled just like a remove, and when the remove comes, no other 
        // removal type actions will occur.
        //
        commonData->Started = FALSE;
        commonData->Initialized = FALSE;

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

        IoDetachDevice(commonData->TopOfStack); 
        IoDeleteDevice(DeviceObject);
        
        return status;

    case IRP_MN_QUERY_CAPABILITIES:

        //
        // Change the device caps to not allow wait wake requests on level
        // triggered interrupts for mice because when an errant mouse movement
        // occurs while we are going to sleep, the interrupt will remain
        // triggered indefinitely.
        //
        // If the mouse does not have a level triggered interrupt, just let the
        // irp go by...
        //
        if (commonData->Started &&
            IS_MOUSE(commonData) && IS_LEVEL_TRIGGERED(commonData)) {

            Print(DBG_PNP_NOISE, ("query caps, mouse is level triggered\n"));

            status = I8xSendIrpSynchronously(commonData->TopOfStack, Irp, TRUE);
            if (NT_SUCCESS(status) && NT_SUCCESS(Irp->IoStatus.Status)) {
                PDEVICE_CAPABILITIES devCaps;

                Print(DBG_PNP_INFO, ("query caps, removing wake caps\n"));

                stack = IoGetCurrentIrpStackLocation(Irp);
                devCaps = stack->Parameters.DeviceCapabilities.Capabilities;

                ASSERT(devCaps);

                if (devCaps) {
                    Print(DBG_PNP_NOISE,
                          ("old DeviceWake was D%d and SystemWake was S%d.\n",
                          devCaps->DeviceWake-1, devCaps->SystemWake-1
                          )) ;

                    devCaps->DeviceWake = PowerDeviceUnspecified;
                    devCaps->SystemWake = PowerSystemUnspecified;
                }
            }

            IoCompleteRequest(Irp, IO_NO_INCREMENT);
            break;
        }

    case IRP_MN_STOP_DEVICE:
    case IRP_MN_QUERY_DEVICE_RELATIONS:
    case IRP_MN_QUERY_INTERFACE:
    case IRP_MN_QUERY_DEVICE_TEXT:
    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:
        //
        // Here the driver below i8042prt might modify the behavior of these IRPS
        // Please see PlugPlay documentation for use of these IRPs.
        //
        IoSkipCurrentIrpStackLocation(Irp);
        status = IoCallDriver(commonData->TopOfStack, Irp);
        break;
    }

    Print(DBG_PNP_TRACE,
          ("I8xPnP (%s) exit (status=0x%x)\n",
          commonData->IsKeyboard ? "kb" : "mou",
          status
          ));

    IoReleaseRemoveLock(&commonData->RemoveLock, Irp);

    return status;
}

LONG
I8xManuallyRemoveDevice(
    PCOMMON_DATA CommonData
    )
/*++

Routine Description:

    Invalidates CommonData->PDO's device state and sets the manually removed 
    flag
    
Arguments:

    CommonData - represent either the keyboard or mouse
    
Return Value:

    new device count for that particular type of device
    
--*/
{
    LONG deviceCount;

    PAGED_CODE();

    if (IS_KEYBOARD(CommonData)) {

        deviceCount = InterlockedDecrement(&Globals.AddedKeyboards);
        if (deviceCount < 1) {
            Print(DBG_PNP_INFO, ("clear kb (manually remove)\n"));
            CLEAR_KEYBOARD_PRESENT();
        }

    } else {

        deviceCount = InterlockedDecrement(&Globals.AddedMice);
        if (deviceCount < 1) {
            Print(DBG_PNP_INFO, ("clear mou (manually remove)\n"));
            CLEAR_MOUSE_PRESENT();
        }
        
    }

    CommonData->PnpDeviceState |= PNP_DEVICE_REMOVED | PNP_DEVICE_DONT_DISPLAY_IN_UI;
    IoInvalidateDeviceState(CommonData->PDO);

    return deviceCount;
}

#define PhysAddrCmp(a,b) ( (a).LowPart == (b).LowPart && (a).HighPart == (b).HighPart )

BOOLEAN
I8xRemovePort(
    IN PIO_RESOURCE_DESCRIPTOR ResDesc
    )
/*++

Routine Description:

    If the physical address contained in the ResDesc is not in the list of 
    previously seen physicall addresses, it is placed within the list.
    
Arguments:

    ResDesc - contains the physical address

Return Value:

    TRUE  - if the physical address was found in the list
    FALSE - if the physical address was not found in the list (and thus inserted
            into it)
--*/
{
    ULONG               i;
    PHYSICAL_ADDRESS   address;

    PAGED_CODE();

    if (Globals.ControllerData->KnownPortsCount == -1) {
        return FALSE;
    }

    address =  ResDesc->u.Port.MinimumAddress;
    for (i = 0; i < Globals.ControllerData->KnownPortsCount; i++) {
        if (PhysAddrCmp(address, Globals.ControllerData->KnownPorts[i])) {
            return TRUE;
        }
    }

    if (Globals.ControllerData->KnownPortsCount < MaximumPortCount) {
        Globals.ControllerData->KnownPorts[
            Globals.ControllerData->KnownPortsCount++] = address;
    }

    Print(DBG_PNP_INFO,
          ("Saw port [0x%08x %08x] - [0x%08x %08x]\n",
          address.HighPart,
          address.LowPart,
          ResDesc->u.Port.MaximumAddress.HighPart,
          ResDesc->u.Port.MaximumAddress.LowPart
          ));

    return FALSE;
}

NTSTATUS
I8xFindPortCallout(
    IN PVOID                        Context,
    IN PUNICODE_STRING              PathName,
    IN INTERFACE_TYPE               BusType,
    IN ULONG                        BusNumber,
    IN PKEY_VALUE_FULL_INFORMATION *BusInformation,
    IN CONFIGURATION_TYPE           ControllerType,
    IN ULONG                        ControllerNumber,
    IN PKEY_VALUE_FULL_INFORMATION *ControllerInformation,
    IN CONFIGURATION_TYPE           PeripheralType,
    IN ULONG                        PeripheralNumber,
    IN PKEY_VALUE_FULL_INFORMATION *PeripheralInformation
    )
/*++

Routine Description:

    This is the callout routine sent as a parameter to
    IoQueryDeviceDescription.  It grabs the keyboard controller and
    peripheral configuration information.

Arguments:

    Context - Context parameter that was passed in by the routine
        that called IoQueryDeviceDescription.

    PathName - The full pathname for the registry key.

    BusType - Bus interface type (Isa, Eisa, Mca, etc.).

    BusNumber - The bus sub-key (0, 1, etc.).

    BusInformation - Pointer to the array of pointers to the full value
        information for the bus.

    ControllerType - The controller type (should be KeyboardController).

    ControllerNumber - The controller sub-key (0, 1, etc.).

    ControllerInformation - Pointer to the array of pointers to the full
        value information for the controller key.

    PeripheralType - The peripheral type (should be KeyboardPeripheral).

    PeripheralNumber - The peripheral sub-key.

    PeripheralInformation - Pointer to the array of pointers to the full
        value information for the peripheral key.


Return Value:

    None.  If successful, will have the following side-effects:

        - Sets DeviceObject->DeviceExtension->HardwarePresent.
        - Sets configuration fields in
          DeviceObject->DeviceExtension->Configuration.

--*/
{
    PUCHAR                          controllerData;
    NTSTATUS                        status = STATUS_UNSUCCESSFUL;
    ULONG                           i,
                                    listCount,
                                    portCount = 0;
    PIO_RESOURCE_LIST               pResList = (PIO_RESOURCE_LIST) Context;
    PIO_RESOURCE_DESCRIPTOR         pResDesc;
    PKEY_VALUE_FULL_INFORMATION     controllerInfo = NULL;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resourceDescriptor;

    PAGED_CODE();

    UNREFERENCED_PARAMETER(PathName);
    UNREFERENCED_PARAMETER(BusType);
    UNREFERENCED_PARAMETER(BusNumber);
    UNREFERENCED_PARAMETER(BusInformation);
    UNREFERENCED_PARAMETER(ControllerType);
    UNREFERENCED_PARAMETER(ControllerNumber);
    UNREFERENCED_PARAMETER(PeripheralType);
    UNREFERENCED_PARAMETER(PeripheralNumber);
    UNREFERENCED_PARAMETER(PeripheralInformation);

    pResDesc = pResList->Descriptors + pResList->Count;
    controllerInfo = ControllerInformation[IoQueryDeviceConfigurationData];

    Print(DBG_PNP_TRACE, ("I8xFindPortCallout enter\n"));

    if (controllerInfo->DataLength != 0) {
        controllerData = ((PUCHAR) controllerInfo) + controllerInfo->DataOffset;
        controllerData += FIELD_OFFSET(CM_FULL_RESOURCE_DESCRIPTOR,
                                       PartialResourceList);

        listCount = ((PCM_PARTIAL_RESOURCE_LIST) controllerData)->Count;

        resourceDescriptor =
            ((PCM_PARTIAL_RESOURCE_LIST) controllerData)->PartialDescriptors;

        for (i = 0; i < listCount; i++, resourceDescriptor++) {
            switch(resourceDescriptor->Type) {
            case CmResourceTypePort:
                
                if (portCount < 2) {

                    Print(DBG_PNP_INFO, 
                          ("found port [0x%x 0x%x] with length %d\n",
                          resourceDescriptor->u.Port.Start.HighPart,
                          resourceDescriptor->u.Port.Start.LowPart,
                          resourceDescriptor->u.Port.Length
                          ));

                    pResDesc->Type = resourceDescriptor->Type;
                    pResDesc->Flags = resourceDescriptor->Flags;
                    pResDesc->ShareDisposition = CmResourceShareDeviceExclusive;

                    pResDesc->u.Port.Alignment = 1;
                    pResDesc->u.Port.Length =
                        resourceDescriptor->u.Port.Length;
                    pResDesc->u.Port.MinimumAddress.QuadPart =
                        resourceDescriptor->u.Port.Start.QuadPart;
                    pResDesc->u.Port.MaximumAddress.QuadPart = 
                        pResDesc->u.Port.MinimumAddress.QuadPart +
                        pResDesc->u.Port.Length - 1;

                    pResList->Count++;

                    //
                    // We want to record the ports we stole from the kb as seen
                    // so that if the keyboard is started later, we can trim
                    // its resources and not have a resource conflict...
                    //
                    // ...we are getting too smart for ourselves here :]
                    //
                    I8xRemovePort(pResDesc);
                    pResDesc++;
                }

                status = STATUS_SUCCESS;

                break;

            default:
                Print(DBG_PNP_NOISE, ("type 0x%x found\n",
                                      (LONG) resourceDescriptor->Type));
                break;
            }
        }

    }

    Print(DBG_PNP_TRACE, ("I8xFindPortCallout exit (0x%x)\n", status));
    return status;
}

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

Routine Description:

    Iterates through the resource requirements list contained in the IRP and removes
    any duplicate requests for I/O ports.  (This is a common problem on the Alphas.)
    
    No removal is performed if more than one resource requirements list is present.
    
Arguments:

    DeviceObject - A pointer to the device object

    Irp - A pointer to the request packet which contains the resource req. list.


Return Value:

    None.
    
--*/
{
    PIO_RESOURCE_REQUIREMENTS_LIST  pReqList = NULL,
                                    pNewReqList = NULL;
    PIO_RESOURCE_LIST               pResList = NULL,
                                    pNewResList = NULL;
    PIO_RESOURCE_DESCRIPTOR         pResDesc = NULL,
                                    pNewResDesc = NULL;
    ULONG                           i = 0, j = 0,
                                    removeCount,
                                    reqCount,
                                    size;
    BOOLEAN                         foundInt = FALSE,
                                    foundPorts = FALSE;

    PIO_STACK_LOCATION  stack;

    PAGED_CODE();

    ASSERT(DeviceObject);
    ASSERT(DeviceObject->DeviceExtension);

    Print(DBG_PNP_NOISE,
          ("Received IRP_MN_FILTER_RESOURCE_REQUIREMENTS for %s\n",
          (GET_COMMON_DATA(DeviceObject->DeviceExtension))->IsKeyboard ? "kb" : "mouse"
          ));

    stack = IoGetCurrentIrpStackLocation(Irp);

    //
    // The list can be in either the information field, or in the current
    //  stack location.  The Information field has a higher precedence over
    //  the stack location.
    //
    if (Irp->IoStatus.Information == 0) {
        pReqList =
            stack->Parameters.FilterResourceRequirements.IoResourceRequirementList;
        Irp->IoStatus.Information = (ULONG_PTR) pReqList;
    }
    else {
        pReqList = (PIO_RESOURCE_REQUIREMENTS_LIST) Irp->IoStatus.Information;
    }

    if (!pReqList) {
        // 
        // Not much can be done here except return
        //
        Print(DBG_PNP_MASK & ~ DBG_PNP_TRACE, 
              ("(%s) NULL resource list in I8xFilterResourceRequirements\n",
              (GET_COMMON_DATA(DeviceObject->DeviceExtension))->IsKeyboard ?
                  "kb" : "mou"
              ));

        return STATUS_SUCCESS;
    }

    ASSERT(Irp->IoStatus.Information != 0);
    ASSERT(pReqList != 0);

    reqCount = pReqList->AlternativeLists;

    //
    // Only one AlternativeList is supported.  If there is more than one list,
    // then there is now way of knowing which list will be chosen.  Also, if
    // there are multiple lists, then chances are that a list with no i/o port
    // conflicts will be chosen.
    //
    if (reqCount > 1) {
        return STATUS_SUCCESS;
    }

    pResList = pReqList->List;
    removeCount = 0;

    for (j = 0; j < pResList->Count; j++) {
        pResDesc = &pResList->Descriptors[j];
        switch (pResDesc->Type) {
        case CmResourceTypePort:
            Print(DBG_PNP_INFO, 
                  ("option = 0x%x, flags = 0x%x\n",
                  (LONG) pResDesc->Option,
                  (LONG) pResDesc->Flags
                  ));

            if (I8xRemovePort(pResDesc)) {
                //
                // Increment the remove count and tag this resource as
                // one that we don't want to copy to the new list
                //
                removeCount++;
                pResDesc->Type = I8X_REMOVE_RESOURCE;
            }

            foundPorts = TRUE;
            break;

        case CmResourceTypeInterrupt:
            if (Globals.ControllerData->Configuration.SharedInterrupts) {
                if (pResDesc->ShareDisposition != CmResourceShareShared) {
                    Print(DBG_PNP_INFO, ("forcing non shared int to shared\n"));
                }
                pResDesc->ShareDisposition = CmResourceShareShared;
            }

            foundInt = TRUE;
            break;

        default:
            break;
        }
    }

    if (removeCount) {
        size = pReqList->ListSize;

        // 
        // One element of the array is already allocated (via the struct 
        //  definition) so make sure that we are allocating at least that 
        //  much memory.
        //

        ASSERT(pResList->Count >= removeCount);
        if (pResList->Count > 1) {
            size -= removeCount * sizeof(IO_RESOURCE_DESCRIPTOR);
        }

        pNewReqList =
            (PIO_RESOURCE_REQUIREMENTS_LIST) ExAllocatePool(PagedPool, size);

        if (!pNewReqList) {
            //
            // This is not good, but the system doesn't really need to know about
            //  this, so just fix up our munging and return the original list
            //
            pReqList = stack->Parameters.FilterResourceRequirements.IoResourceRequirementList;
            reqCount = pReqList->AlternativeLists;
            removeCount = 0;
       
            for (i = 0; i < reqCount; i++) {
                pResList = &pReqList->List[i];
       
                for (j = 0; j < pResList->Count; j++) {
                    pResDesc = &pResList->Descriptors[j];
                    if (pResDesc->Type == I8X_REMOVE_RESOURCE) {
                        pResDesc->Type = CmResourceTypePort;
                    }
                }
            
            }

            return STATUS_SUCCESS;
        }

        //
        // Clear out the newly allocated list
        //
        RtlZeroMemory(pNewReqList,
                      size
                      );

        //
        // Copy the list header information except for the IO resource list
        // itself
        //
        RtlCopyMemory(pNewReqList,
                      pReqList,
                      sizeof(IO_RESOURCE_REQUIREMENTS_LIST) - 
                        sizeof(IO_RESOURCE_LIST)
                      );
        pNewReqList->ListSize = size;

        pResList = pReqList->List;
        pNewResList = pNewReqList->List;

        //
        // Copy the list header information except for the IO resource
        // descriptor list itself
        //
        RtlCopyMemory(pNewResList,
                      pResList,
                      sizeof(IO_RESOURCE_LIST) -
                        sizeof(IO_RESOURCE_DESCRIPTOR)
                      );

        pNewResList->Count = 0;
        pNewResDesc = pNewResList->Descriptors;

        for (j = 0; j < pResList->Count; j++) {
            pResDesc = &pResList->Descriptors[j];
            if (pResDesc->Type != I8X_REMOVE_RESOURCE) {
                //
                // Keep this resource, so copy it into the new list and
                // incement the count and the location for the next
                // IO resource descriptor
                //
                *pNewResDesc = *pResDesc;
                pNewResDesc++;
                pNewResList->Count++;

                Print(DBG_PNP_INFO,
                     ("List #%d, Descriptor #%d ... keeping res type %d\n",
                     i, j,
                     (ULONG) pResDesc->Type
                     ));
            }
            else {
                //
                // Decrement the remove count so we can assert it is
                //  zero once we are done
                //
                Print(DBG_PNP_INFO,
                      ("Removing port [0x%08x %08x] - [0x%#08x %08x]\n",
                      pResDesc->u.Port.MinimumAddress.HighPart,
                      pResDesc->u.Port.MinimumAddress.LowPart,
                      pResDesc->u.Port.MaximumAddress.HighPart,
                      pResDesc->u.Port.MaximumAddress.LowPart
                      ));
                removeCount--;
              }
        }

        ASSERT(removeCount == 0);

        //
        // There have been bugs where the old list was being used.  Zero it out to
        //  make sure that no conflicts arise.  (Not to mention the fact that some
        //  other code is accessing freed memory
        //
        RtlZeroMemory(pReqList,
                      pReqList->ListSize
                      );

        //
        // Free the old list and place the new one in its place
        //
        ExFreePool(pReqList);
        stack->Parameters.FilterResourceRequirements.IoResourceRequirementList =
            pNewReqList;
        Irp->IoStatus.Information = (ULONG_PTR) pNewReqList;
    }
    else if (!KEYBOARD_PRESENT() && !foundPorts && foundInt) {
        INTERFACE_TYPE                      interfaceType;
        NTSTATUS                            status;
        ULONG                               prevCount;
        CONFIGURATION_TYPE                  controllerType = KeyboardController;
        CONFIGURATION_TYPE                  peripheralType = KeyboardPeripheral;

        ASSERT( MOUSE_PRESENT() );

        Print(DBG_PNP_INFO, ("Adding ports to res list!\n"));

        //
        // We will now yank the resources from the keyboard to start the mouse
        // solo
        //
        size = pReqList->ListSize + 2 * sizeof(IO_RESOURCE_DESCRIPTOR);
        pNewReqList = (PIO_RESOURCE_REQUIREMENTS_LIST)
                        ExAllocatePool(
                            PagedPool,
                            size
                            );

        if (!pNewReqList) {
            return STATUS_SUCCESS;
        }

        //
        // Clear out the newly allocated list
        //
        RtlZeroMemory(pNewReqList,
                      size
                      );

        //
        // Copy the entire old list
        //
        RtlCopyMemory(pNewReqList,
                      pReqList,
                      pReqList->ListSize
                      );

        pResList = pReqList->List;
        pNewResList = pNewReqList->List;

        prevCount = pNewResList->Count;
        for (i = 0; i < MaximumInterfaceType; i++) {

            //
            // Get the registry information for this device.
            //
            interfaceType = i;
            status = IoQueryDeviceDescription(
                &interfaceType,
                NULL,
                &controllerType,
                NULL,
                &peripheralType,
                NULL,
                I8xFindPortCallout,
                (PVOID) pNewResList
                );

            if (NT_SUCCESS(status) || prevCount != pNewResList->Count) {
                break;
            }
        }

        if (NT_SUCCESS(status) || prevCount != pNewResList->Count) {
            pNewReqList->ListSize = size - (2 - (pNewResList->Count - prevCount));
    
            //
            // Free the old list and place the new one in its place
            //
            ExFreePool(pReqList);
            stack->Parameters.FilterResourceRequirements.IoResourceRequirementList =
                pNewReqList;
            Irp->IoStatus.Information = (ULONG_PTR) pNewReqList;
        }
        else {
            ExFreePool(pNewReqList);
        }
    }

    return STATUS_SUCCESS;
}


NTSTATUS
I8xRegisterDeviceInterface(
    PDEVICE_OBJECT PDO,
    CONST GUID * Guid,
    PUNICODE_STRING SymbolicName
    )
{
    NTSTATUS status;

    PAGED_CODE();

    status = IoRegisterDeviceInterface(
                PDO,
                Guid,
                NULL,
                SymbolicName 
                );

    if (NT_SUCCESS(status)) {
        status = IoSetDeviceInterfaceState(SymbolicName,
                                           TRUE
                                           );
    }

    return status;
}

void
I8xSetPowerFlag(
    IN ULONG Flag,
    IN BOOLEAN Set
    )
{
    KIRQL irql;

    KeAcquireSpinLock(&Globals.ControllerData->PowerSpinLock, &irql);
    if (Set) {
        Globals.PowerFlags |= Flag;
    }
    else {
        Globals.PowerFlags &= ~Flag;
    }
    KeReleaseSpinLock(&Globals.ControllerData->PowerSpinLock, irql);
}

BOOLEAN
I8xCheckPowerFlag(
    ULONG Flag
    )
{
    KIRQL irql;
    BOOLEAN rVal = FALSE;

    KeAcquireSpinLock(&Globals.ControllerData->PowerSpinLock, &irql);
    if (Globals.PowerFlags & Flag) {
        rVal = TRUE;
    }
    KeReleaseSpinLock(&Globals.ControllerData->PowerSpinLock, irql);
    
    return rVal;
}

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

Routine Description:

    This is the dispatch routine for power requests.  

Arguments:

    DeviceObject - Pointer to the device object.

    Irp - Pointer to the request packet.

Return Value:

    STATUS_SUCCESSFUL if successful,
    an valid NTSTATUS error code otherwise

--*/
{
    PCOMMON_DATA        commonData;
    PIO_STACK_LOCATION  stack;
    NTSTATUS            status = STATUS_SUCCESS;

    PAGED_CODE();

    commonData = GET_COMMON_DATA(DeviceObject->DeviceExtension);

    stack = IoGetCurrentIrpStackLocation(Irp);

    Print(DBG_POWER_TRACE,
          ("Power (%s), enter\n",
          commonData->IsKeyboard ? "keyboard" :
                                   "mouse"
          ));

    //
    // A power irp can be sent to the device before we have been started or
    // initialized.  Since the code below relies on StartDevice() to have
    // executed, just fire and forget the irp
    //
    if (!commonData->Started || !commonData->Initialized) {
        PoStartNextPowerIrp(Irp);
        Irp->IoStatus.Status = STATUS_SUCCESS;
        IoSkipCurrentIrpStackLocation(Irp);
        return PoCallDriver(commonData->TopOfStack, Irp);
    }

    switch(stack->MinorFunction) {
    case IRP_MN_WAIT_WAKE:
        Print(DBG_POWER_NOISE, ("Got IRP_MN_WAIT_WAKE\n" ));

        //
        // Fail all wait wake requests on level triggered interrupts for mice
        // because when an errant mouse movement occurs while we are going to
        // sleep, it will keep the interrupt triggered indefinitely.
        //
        // We should not even get into this situation because the caps of the 
        // mouse should have been altered to not report wait wake 
        //
        if (IS_MOUSE(commonData) && IS_LEVEL_TRIGGERED(commonData)) {

            PoStartNextPowerIrp(Irp);
            status = Irp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);

            Print(DBG_POWER_INFO | DBG_POWER_ERROR,
                  ("failing a wait wake request on a level triggered mouse\n"));

            return status;
        }

        break;

    case IRP_MN_POWER_SEQUENCE:
        Print(DBG_POWER_NOISE, ("Got IRP_MN_POWER_SEQUENCE\n" ));
        break;

    case IRP_MN_SET_POWER:
        Print(DBG_POWER_NOISE, ("Got IRP_MN_SET_POWER\n" ));

        //
        // Don't handle anything but DevicePowerState changes
        //
        if (stack->Parameters.Power.Type != DevicePowerState) {
            commonData->SystemState = stack->Parameters.Power.State.SystemState;

            Print(DBG_POWER_INFO, ("system power irp, S%d\n", commonData->SystemState-1));
            break;
        }

        //
        // Check for no change in state, and if none, do nothing.  This state
        // can occur when the device is armed for wake.  We will get a D0 in 
        // response to the WW irp completing and then another D0 corresponding
        // to the S0 irp sent to the stack.
        //
        if (stack->Parameters.Power.State.DeviceState ==
            commonData->PowerState) {
            Print(DBG_POWER_INFO,
                  ("no change in state (PowerDeviceD%d)\n",
                  commonData->PowerState-1
                  ));
            break;
        }

        switch (stack->Parameters.Power.State.DeviceState) {
        case PowerDeviceD0:
            Print(DBG_POWER_INFO, ("Powering up to PowerDeviceD0\n"));

            IoAcquireRemoveLock(&commonData->RemoveLock, Irp);

            if (IS_KEYBOARD(commonData)) {
                I8xSetPowerFlag(KBD_POWERED_UP_STARTED, TRUE);
            }
            else {
                I8xSetPowerFlag(MOU_POWERED_UP_STARTED, TRUE);
            }
                                
            //
            // PoSetPowerState will be called in I8xReinitalizeHardware for each
            // device once all the devices have powered back up
            //
            IoCopyCurrentIrpStackLocationToNext(Irp);
            IoSetCompletionRoutine(Irp,
                                   I8xPowerUpToD0Complete,
                                   NULL,
                                   TRUE,                // on success
                                   TRUE,                // on error
                                   TRUE                 // on cancel
                                   );

            //
            // PoStartNextPowerIrp() gets called when the irp gets completed 
            // in either the completion routine or the resulting work item
            //
            // It is OK to call PoCallDriver and return pending b/c we are 
            // pending the irp in the completion routine and we may change
            // the completion status if we can't alloc pool.  If we return the
            // value from PoCallDriver, we are tied to that status value on the
            // way back up.
            //                
            IoMarkIrpPending(Irp);
            PoCallDriver(commonData->TopOfStack, Irp);
            return STATUS_PENDING;

        case PowerDeviceD1:
        case PowerDeviceD2:
        case PowerDeviceD3:
            Print(DBG_POWER_INFO,
                  ("Powering down to PowerDeviceD%d\n",
                  stack->Parameters.Power.State.DeviceState-1
                  ));

            //
            // If WORK_ITEM_QUEUED is set, that means that a work item is
            // either queued to be run, or running now so we don't want to yank
            // any devices underneath from the work item
            //
            if (I8xCheckPowerFlag(WORK_ITEM_QUEUED)) {
                Print(DBG_POWER_INFO | DBG_POWER_ERROR,
                      ("denying power down request because work item is running\n"
                      ));

                PoStartNextPowerIrp(Irp);
                status = Irp->IoStatus.Status = STATUS_POWER_STATE_INVALID;
                IoCompleteRequest(Irp, IO_NO_INCREMENT);

                return status;
            }

            if (IS_KEYBOARD(commonData)) {
                I8xSetPowerFlag(KBD_POWERED_DOWN, TRUE);
            }
            else {
                I8xSetPowerFlag(MOU_POWERED_DOWN, TRUE);
            }

            PoSetPowerState(DeviceObject,
                            stack->Parameters.Power.Type,
                            stack->Parameters.Power.State
                            );

            //
            // Disconnect level triggered interupts on mice when we go into 
            // low power so errant mouse movement doesn't leave the interrupt
            // signalled for long periods of time
            //
            if (IS_MOUSE(commonData) && IS_LEVEL_TRIGGERED(commonData)) {
                PKINTERRUPT interrupt = commonData->InterruptObject;

                Print(DBG_POWER_NOISE,
                      ("disconnecting interrupt on level triggered mouse\n")
                      );

                commonData->InterruptObject = NULL;
                if (interrupt) {
                    IoDisconnectInterrupt(interrupt);
                }
            }

            commonData->PowerState = stack->Parameters.Power.State.DeviceState;
            commonData->ShutdownType = stack->Parameters.Power.ShutdownType;

            //
            // For what we are doing, we don't need a completion routine
            // since we don't race on the power requests.
            //
            Irp->IoStatus.Status = STATUS_SUCCESS;
            PoStartNextPowerIrp(Irp);
            IoSkipCurrentIrpStackLocation(Irp);
            return  PoCallDriver(commonData->TopOfStack, Irp);

        default:
            Print(DBG_POWER_INFO, ("unknown state\n"));
            break;
        }
        break;

    case IRP_MN_QUERY_POWER:
        Print(DBG_POWER_NOISE, ("Got IRP_MN_QUERY_POWER\n" ));
        break;

    default:
        Print(DBG_POWER_NOISE,
              ("Got unhandled minor function (%d)\n",
              stack->MinorFunction
              ));
        break;
    }

    Print(DBG_POWER_TRACE, ("Power, exit\n"));

    PoStartNextPowerIrp(Irp);

    IoSkipCurrentIrpStackLocation(Irp);
    return PoCallDriver(commonData->TopOfStack, Irp);
}

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

Routine Description:

    Reinitializes the i8042 haardware after any type of hibernation/sleep.
    
Arguments:

    DeviceObject - Pointer to the device object

    Irp - Pointer to the request
    
    Context - Context passed in from the funciton that set the completion
              routine. UNUSED.


Return Value:

    STATUS_SUCCESSFUL if successful,
    an valid NTSTATUS error code otherwise

--*/
{
    NTSTATUS            status = STATUS_SUCCESS;
    PCOMMON_DATA        commonData;
    PPOWER_UP_WORK_ITEM item;
    KIRQL               irql;
    UCHAR               poweredDownDevices = 0,
                        poweredUpDevices = 0,
                        failedDevices = 0;
    BOOLEAN             queueItem = FALSE,
                        clearFlags = FALSE,
                        failMouIrp = FALSE; 
    PIRP                mouIrp = NULL,
                        kbdIrp = NULL;

    UNREFERENCED_PARAMETER(Context);

    commonData = GET_COMMON_DATA(DeviceObject->DeviceExtension);

    Print(DBG_POWER_TRACE,
          ("PowerUpToD0Complete (%s), Enter\n",
          commonData->IsKeyboard ? "kb" : "mouse"
          ));


    //
    // We can use a regular work item because we have a non completed power irp
    // which has an outstanding reference to this stack.
    //
    item = (PPOWER_UP_WORK_ITEM) ExAllocatePool(NonPagedPool,
                                                sizeof(POWER_UP_WORK_ITEM));

    KeAcquireSpinLock(&Globals.ControllerData->PowerSpinLock, &irql);

    Print(DBG_POWER_TRACE,
          ("Power up to D0 completion enter, power flags 0x%x\n",
          Globals.PowerFlags));

    if (NT_SUCCESS(Irp->IoStatus.Status)) {
        commonData->OutstandingPowerIrp = Irp;
        status = STATUS_MORE_PROCESSING_REQUIRED;

        if (IS_KEYBOARD(commonData)) {
            KEYBOARD_POWERED_UP_SUCCESSFULLY(); 
        }
        else {
            MOUSE_POWERED_UP_SUCCESSFULLY();
        }
    }
    else {
        if (IS_KEYBOARD(commonData)) {
            KEYBOARD_POWERED_UP_FAILURE();
        }
        else {
            MOUSE_POWERED_UP_FAILURE();
        }
    }

    if (KEYBOARD_POWERED_DOWN_SUCCESS()) {
        Print(DBG_POWER_NOISE, ("--kbd powered down successfully\n"));
        poweredDownDevices++;
    }
    if (MOUSE_POWERED_DOWN_SUCCESS()) {
        Print(DBG_POWER_NOISE, ("--mou powered down successfully\n"));
        poweredDownDevices++;
    }

    if (KEYBOARD_POWERED_UP_SUCCESS()) {
        Print(DBG_POWER_NOISE, ("++kbd powered up successfully\n"));
        poweredUpDevices++;
    }
    if (MOUSE_POWERED_UP_SUCCESS()) {
        Print(DBG_POWER_NOISE, ("++mou powered up successfully\n"));
        poweredUpDevices++;
    }

    if (KEYBOARD_POWERED_UP_FAILED()) {
        Print(DBG_POWER_NOISE|DBG_POWER_ERROR, (">>kbd powered down failed\n"));
        failedDevices++;
    }
    if (MOUSE_POWERED_UP_FAILED()) {
        Print(DBG_POWER_NOISE|DBG_POWER_ERROR, (">>mou powered down failed\n"));
        failedDevices++;
    }

    Print(DBG_POWER_INFO,
          ("up %d, down %d, failed %d, flags 0x%x\n",
          (ULONG) poweredUpDevices, 
          (ULONG) poweredDownDevices, 
          (ULONG) failedDevices, 
          Globals.PowerFlags));

    if ((poweredUpDevices + failedDevices) == poweredDownDevices) {
        if (poweredUpDevices > 0) {
            //
            // The ports are associated with the keyboard.  If it has failed to
            // power up while the mouse succeeded, we still need to fail the
            // mouse b/c there is no hardware to talk to
            //
            if (failedDevices > 0 && KEYBOARD_POWERED_UP_FAILED()) {
                ASSERT(MOUSE_POWERED_UP_SUCCESS());
                ASSERT(Globals.KeyboardExtension->OutstandingPowerIrp == NULL);

                mouIrp = Globals.MouseExtension->OutstandingPowerIrp;
                Globals.MouseExtension->OutstandingPowerIrp = NULL;
                Globals.PowerFlags &= ~MOU_POWER_FLAGS;
                clearFlags =  TRUE;

                if (mouIrp != Irp) {
                    //
                    // we have queued the irp, complete it later in this
                    // function under a special case
                    //
                    failMouIrp = TRUE;
                }
                else {
                    //
                    // The mouse irp is the current irp.  We have already
                    // completed the kbd irp in our previous processing.  Set
                    // the irp status to some unsuccessful value so that we will
                    // call PoStartNextPowerIrp later in this function.  Also
                    // set status to != STATUS_MORE_PROCESSING_REQUIRED so the 
                    // irp will be completed when the function exits.
                    //
                    status = mouIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                }

            }
            else {
                Print(DBG_POWER_INFO, ("at least one device powered up!\n"));
                queueItem = TRUE;
            }
        }
        else {
            Print(DBG_POWER_INFO,
                  ("all devices failed power up, 0x%x\n",
                   Globals.PowerFlags));

            clearFlags = TRUE;
        }
    }
    else {
        //
        // the other device is still powered down, wait for it to power back
        // up before processing power states 
        //
        Print(DBG_POWER_INFO,
              ("queueing, waiting for 2nd dev obj to power cycle\n"));
    }

    if (queueItem || clearFlags) {
        //
        // Extract the irp from each successfully started device and clear the
        // associated power flags for the device
        //
        if (MOUSE_POWERED_UP_SUCCESS()) {
            mouIrp = Globals.MouseExtension->OutstandingPowerIrp;
            Globals.MouseExtension->OutstandingPowerIrp = NULL;

            ASSERT(!TEST_PWR_FLAGS(MOU_POWERED_UP_FAILURE));
            Globals.PowerFlags &= ~MOU_POWER_FLAGS;
        }
        else {
            Globals.PowerFlags &= ~(MOU_POWERED_UP_FAILURE);
        }

        if (KEYBOARD_POWERED_UP_SUCCESS()) {
            kbdIrp = Globals.KeyboardExtension->OutstandingPowerIrp;
            Globals.KeyboardExtension->OutstandingPowerIrp = NULL;

            ASSERT(!TEST_PWR_FLAGS(KBD_POWERED_UP_FAILURE));
            Globals.PowerFlags &= ~(KBD_POWER_FLAGS);
        }
        else {
             Globals.PowerFlags &= ~(KBD_POWERED_UP_FAILURE);
        }

        //
        // Mark that the work item is queued.  This is used to make sure that 2
        // work items are not queued concucrrently
        //
        if (item && queueItem) {
            Print(DBG_POWER_INFO, ("setting work item queued flag\n"));

            Globals.PowerFlags |= WORK_ITEM_QUEUED;
        }
    }

    KeReleaseSpinLock(&Globals.ControllerData->PowerSpinLock, irql);

    if (queueItem) {
        if (item == NULL) {
            //
            // complete any queued power irps
            //
            Print(DBG_POWER_INFO | DBG_POWER_ERROR,
                  ("failed to alloc work item\n"));

            //
            // what about PoSetPowerState?
            //
            if (mouIrp != NULL) {
                Print(DBG_POWER_ERROR | DBG_POWER_INFO,
                      ("completing mouse power irp 0x%x", mouIrp));

                mouIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                mouIrp->IoStatus.Information = 0x0;

                PoStartNextPowerIrp(mouIrp);
                IoCompleteRequest(mouIrp, IO_NO_INCREMENT);
                IoReleaseRemoveLock(&Globals.MouseExtension->RemoveLock, 
                                    mouIrp);
                mouIrp = NULL;
            }

            if (kbdIrp != NULL) {
                Print(DBG_POWER_ERROR | DBG_POWER_INFO,
                      ("completing kbd power irp 0x%x", kbdIrp));

                kbdIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                kbdIrp->IoStatus.Information = 0x0;

                PoStartNextPowerIrp(kbdIrp);
                IoCompleteRequest(kbdIrp, IO_NO_INCREMENT);
                IoReleaseRemoveLock(&Globals.KeyboardExtension->RemoveLock, 
                                    kbdIrp);
                kbdIrp = NULL;
            }

            //
            // The passed in Irp has just been completed; by returning more
            // processing required, it will not be double completed
            //
            return STATUS_MORE_PROCESSING_REQUIRED;
        }
        else {
            RtlZeroMemory(item, sizeof(*item));

            if (MOUSE_STARTED()) {
                SET_RECORD_STATE(Globals.MouseExtension,
                                 RECORD_RESUME_FROM_POWER);
            }
    
            Print(DBG_POWER_INFO, ("queueing work item for init\n"));
    
            item->KeyboardPowerIrp = kbdIrp;
            item->MousePowerIrp = mouIrp;

            ExInitializeWorkItem(&item->Item, I8xReinitializeHardware, item);
            ExQueueWorkItem(&item->Item, DelayedWorkQueue);
        }
    }
    else if (item != NULL) {
        Print(DBG_POWER_NOISE,("freeing unused item %p\n", item));
        ExFreePool(item);
        item = NULL;
    }

    if (failMouIrp) {
        Print(DBG_POWER_INFO | DBG_POWER_ERROR,
              ("failing successful mouse irp %p because kbd failed power up\n",
               mouIrp));

        PoStartNextPowerIrp(mouIrp);
        IoCompleteRequest(mouIrp, IO_NO_INCREMENT);
        IoReleaseRemoveLock(&Globals.MouseExtension->RemoveLock, mouIrp);
        mouIrp = NULL;
    }

    if (!NT_SUCCESS(Irp->IoStatus.Status)) {
        Print(DBG_POWER_INFO | DBG_POWER_ERROR,
              ("irp %p failed, starting next\n", Irp));

        PoStartNextPowerIrp(Irp);
        Irp = NULL;
        ASSERT(status != STATUS_MORE_PROCESSING_REQUIRED);
    }

    return status;
}