windows-nt/Source/XPSP1/NT/base/busdrv/pci/power.c
2020-09-26 16:20:57 +08:00

1773 lines
45 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1996-2000 Microsoft Corporation
Module Name:
power.c
Abstract:
This module contains power management code for PCI.SYS.
Author:
Joe Dai (joedai) 11-Sept-1997
Peter Johnston (peterj) 24-Oct-1997
Revision History:
--*/
#include "pcip.h"
NTSTATUS
PciFdoWaitWakeCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PPCI_FDO_EXTENSION FdoExtension
);
NTSTATUS
PciFdoWaitWakeCallBack(
IN PDEVICE_OBJECT DeviceObject,
IN UCHAR MinorFunction,
IN POWER_STATE PowerState,
IN PVOID Context,
IN PIO_STATUS_BLOCK IoStatus
);
VOID
PciFdoWaitWakeCancel(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
);
VOID
PciFdoSetPowerStateCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN UCHAR MinorFunction,
IN POWER_STATE PowerState,
IN PVOID Context,
IN PIO_STATUS_BLOCK IoStatus
);
NTSTATUS
PciPdoWaitWakeCallBack(
IN PDEVICE_OBJECT DeviceObject,
IN UCHAR MinorFunction,
IN POWER_STATE PowerState,
IN PVOID Context,
IN PIO_STATUS_BLOCK IoStatus
);
VOID
PciPdoAdjustPmeEnable(
IN PPCI_PDO_EXTENSION PdoExtension,
IN BOOLEAN Enable
);
VOID
PciPmeClearPmeStatus(
IN PDEVICE_OBJECT Pdo
);
//
// This table is taken from the PCI spec. The units are microseconds.
LONG PciPowerDelayTable[4][4] = {
// D0 D1 D2 D3(Hot)
0, 0, 200, 10000, // D0
0, 0, 200, 10000, // D1
200, 200, 0, 10000, // D2
10000, 10000, 10000, 0 // D3(Hot)
};
VOID
PciPdoAdjustPmeEnable(
IN PPCI_PDO_EXTENSION PdoExtension,
IN BOOLEAN Enable
)
/*++
Routine Description:
Enable or Disable the PME Enable bit for a device(function).
Note: The PDO Extension lock is held on entry and is not released
by this routine.
Arguments:
PdoExtension - Pointer to the PDO Extension for the device whose
PME Enable bit is to be altered.
Enable - TRUE if PME Enable is to be set, FALSE if to be cleared.
Return Value:
None.
--*/
{
//
// Is the device's PME management owned by someone else?
//
if (PdoExtension->NoTouchPmeEnable) {
PciDebugPrint(
PciDbgWaitWake,
"AdjustPmeEnable on pdox %08x but PME not owned.\n",
PdoExtension
);
return;
}
//
// Really update the PME signal. Note that we always need to supply
// the 3rd argument as FALSE --- we don't want to just clear the PME
// Status bit
//
PciPmeAdjustPmeEnable( PdoExtension, Enable, FALSE );
}
NTSTATUS
PciPdoIrpQueryPower(
IN PIRP Irp,
IN PIO_STACK_LOCATION IrpSp,
IN PPCI_COMMON_EXTENSION DeviceExtension
)
{
//
// pass 1, claim we can do it.
//
//
// ADRIAO N.B. 08/29/1999 -
// For D-IRPs, we do *not* want to verify the requested D-state is
// actually supported. See PciQueryPowerCapabilities for details.
//
return STATUS_SUCCESS;
}
NTSTATUS
PciPdoSetPowerState (
IN PIRP Irp,
IN PIO_STACK_LOCATION IrpStack,
IN PPCI_COMMON_EXTENSION DeviceExtension
)
/*++
Routine Description:
Handles SetPower Irps send to a PCI PDO
If the irp is an S-Irp, then do nothing
If the irp is an D-Irp, then put the device in the appropriate state.
Exceptions: If the device is in the hibernate path, then don't
actually power down if we are hibernating
Arguments:
Irp - The request
IrpStack - The current stack location
DeviceExtension - The device that is getting powered down
Return Value:
NTSTATUS
--*/
{
DEVICE_POWER_STATE desiredDeviceState;
NTSTATUS status;
PPCI_PDO_EXTENSION pdoExtension;
POWER_ACTION powerAction;
POWER_STATE powerState;
pdoExtension = (PPCI_PDO_EXTENSION) DeviceExtension;
ASSERT_PCI_PDO_EXTENSION(pdoExtension);
status = STATUS_SUCCESS;
switch (IrpStack->Parameters.Power.Type) {
case DevicePowerState:
desiredDeviceState = IrpStack->Parameters.Power.State.DeviceState;
powerAction = IrpStack->Parameters.Power.ShutdownType;
break;
case SystemPowerState:
return STATUS_SUCCESS;
default:
return STATUS_NOT_SUPPORTED;
}
if ((desiredDeviceState == PowerDeviceD0)
&& (pdoExtension->PowerState.CurrentDeviceState == PowerDeviceD0)) {
return STATUS_SUCCESS;
}
#if DBG
if ((desiredDeviceState < PowerDeviceD0) ||
(desiredDeviceState > PowerDeviceD3)) {
//
// Invalid power level.
//
return STATUS_INVALID_PARAMETER;
}
#endif
//
// If the device is trying to power down perform some sanity checks
//
if (desiredDeviceState > PowerDeviceD0) {
if (pdoExtension->OnDebugPath) {
KdPowerTransition(desiredDeviceState);
}
//
// If device is currently in D0 state, capture it's command
// register settings in case the FDO changed them since we
// looked at them.
//
if (pdoExtension->PowerState.CurrentDeviceState == PowerDeviceD0) {
PciGetCommandRegister(pdoExtension,
&pdoExtension->CommandEnables);
}
//
// Prevent race conditions and remember that the device is off before
// we actually turn it off
//
pdoExtension->PowerState.CurrentDeviceState = desiredDeviceState;
if (pdoExtension->DisablePowerDown) {
//
// Powerdown of this device disabled (based on device type).
//
PciDebugPrint(
PciDbgObnoxious,
"PCI power down of PDOx %08x, disabled, ignored.\n",
pdoExtension
);
return STATUS_SUCCESS;
}
//
// Device driver should likely not be powering down any device
// that's on the hibernate path or the crashdump path
//
if ( powerAction == PowerActionHibernate &&
(pdoExtension->PowerState.Hibernate || pdoExtension->PowerState.CrashDump ) ) {
//
// Don't actually change the device, but new device state was
// recorded above (as if we HAD done it) so we know to reset
// resources as the system comes up again.
//
return STATUS_SUCCESS;
}
//
// If we are a device on the VGA path then don't turn off for shutdown so we can
// display the "Safe to turn off your machine" screen. For hibernate we also
// don't want to turn off so we can display the "Dumping stuff to your disk progress
// bar" but this is accomplished by video putting device on the video path on the hibernate
// path.
//
if (IrpStack->Parameters.Power.State.DeviceState == PowerDeviceD3
&& (IrpStack->Parameters.Power.ShutdownType == PowerActionShutdownReset ||
IrpStack->Parameters.Power.ShutdownType == PowerActionShutdownOff ||
IrpStack->Parameters.Power.ShutdownType == PowerActionShutdown)
&& PciIsOnVGAPath(pdoExtension)) {
return STATUS_SUCCESS;
}
//
// If this device is on the debug path then don't power it down so we
// can report if this crashes the machine...
//
if (pdoExtension->OnDebugPath) {
return STATUS_SUCCESS;
}
} else {
//
// Device is powering UP.
//
// Verify the device is still the same as before (and that someone
// hasn't removed/replaced it with something else)
//
if (!PciIsSameDevice(pdoExtension)) {
return STATUS_NO_SUCH_DEVICE;
}
}
//
// Place the device in the proper power state
//
status = PciSetPowerManagedDevicePowerState(
pdoExtension,
desiredDeviceState,
TRUE
);
//
// If the device is transitioning to the D0 state, reset the common
// config information on the device and inform the system of the device
// state change.
//
if (desiredDeviceState == PowerDeviceD0) {
if (NT_SUCCESS(status)) {
pdoExtension->PowerState.CurrentDeviceState = desiredDeviceState;
PoSetPowerState (
pdoExtension->PhysicalDeviceObject,
DevicePowerState,
IrpStack->Parameters.Power.State
);
if (pdoExtension->OnDebugPath) {
KdPowerTransition(PowerDeviceD0);
}
}
} else {
//
// The new device state is something other then D0.
// notify the system before continuing
//
PoSetPowerState (
pdoExtension->PhysicalDeviceObject,
DevicePowerState,
IrpStack->Parameters.Power.State
);
//
// Turn off the device's IO and MEMory access.
//
PciDecodeEnable(pdoExtension, FALSE, NULL);
status = STATUS_SUCCESS;
}
return status;
}
NTSTATUS
PciPdoWaitWake(
IN PIRP Irp,
IN PIO_STACK_LOCATION IrpSp,
IN PPCI_COMMON_EXTENSION DeviceExtension
)
/*++
Routine Description:
Handle IRP_MN_WAIT_WAKE for PCI PDOs.
This operation is used to wait for the device to signal a wake event.
By waiting for a wake signal from a device, its wake event is enabled
so long as the System Power State is above the requested SystemWake
state. By not waiting for a wake signal from a device, its wake
signal is not enabled.
Arguments:
DeviceObject - Pointer to the device object for which this IRP applies.
Irp - Pointer to the IRP.
Return Value:
NT status.
STATUS_INVALID_DEVICE_STATE, if the device is in the PowerD0 state or
a state below which can support waking or if the SystemWake state
is below a state which can be supported.
A pending IRP_MN_WAIT_WAKE will complete with this error if the
device's state is changed to be incompatible with the wake request.
STATUS_DEVICE_BUSY, if the device already has a WAIT_WAKE request
outstanding. To change the SystemWake level the outstanding
IRP must be canceled.
STATUS_INVALID_DEVICE_REQUEST, if the device is not capable of
signaling a wakeup. In theory we should have gotten out
before getting this far because DeviceWake woud be unspecified.
STATUS_SUCCESS. The device has signaled a WAKE event.
STATUS_PENDING. This is the expected return, the IRP will not
complete until the wait is complete or cancelled.
--*/
{
BOOLEAN pmeCapable;
DEVICE_POWER_STATE devPower;
NTSTATUS status;
PPCI_FDO_EXTENSION fdoExtension;
PIO_STACK_LOCATION irpStack;
PIRP irpToParent;
POWER_STATE powerState;
PPCI_PDO_EXTENSION pdoExtension;
ULONG waitCount;
pdoExtension = (PPCI_PDO_EXTENSION) DeviceExtension;
ASSERT_PCI_PDO_EXTENSION(pdoExtension);
PoStartNextPowerIrp(Irp);
devPower = pdoExtension->PowerState.CurrentDeviceState;
//
// The docs say WAIT_WAKE is allowed only from a state < D0, and
// only if current power state supports wakeup.
//
ASSERT(devPower < PowerDeviceMaximum);
if ((devPower > pdoExtension->PowerState.DeviceWakeLevel) ||
(pdoExtension->PowerState.DeviceWakeLevel == PowerDeviceUnspecified)) {
//
// NTRAID #62653 - 4/28/2000 - andrewth
// Need to add system state to conditions here.
//
PciDebugPrint(
PciDbgWaitWake,
"WaitWake: pdox %08x current state (%d) not valid for waiting\n",
pdoExtension,
devPower
);
status = STATUS_INVALID_DEVICE_STATE;
goto PciPdoWaitWakeFailIrp;
}
PCI_LOCK_OBJECT(pdoExtension);
//
// Only one WAIT_WAKE IRP allowed. Set THIS IRP as the wait wake
// irp in the pdo extension, if and only if, there is no other irp
// there.
//
if (pdoExtension->PowerState.WaitWakeIrp != NULL) {
//
// A WAIT_WAKE IRP is already pending for this device.
//
PCI_UNLOCK_OBJECT(pdoExtension);
PciDebugPrint(
PciDbgWaitWake,
"WaitWake: pdox %08x is already waiting\n",
devPower
);
status = STATUS_DEVICE_BUSY;
goto PciPdoWaitWakeFailIrp;
}
//
// Does this device support Power Management? That is, do we
// know how to enable PME?
//
PciPmeGetInformation(
pdoExtension->PhysicalDeviceObject,
&pmeCapable,
NULL,
NULL
);
if (pmeCapable == FALSE) {
//
// This device does not support Power Management.
// Don't allow a wait wake. In theory we couldn't
// have gotten here because our capabilities should
// have stopped the caller from even trying this.
//
PCI_UNLOCK_OBJECT(pdoExtension);
PciDebugPrint(
PciDbgWaitWake,
"WaitWake: pdox %08x does not support PM\n",
devPower
);
status = STATUS_INVALID_DEVICE_REQUEST;
goto PciPdoWaitWakeFailIrp;
}
fdoExtension = PCI_PARENT_FDOX(pdoExtension);
ASSERT_PCI_FDO_EXTENSION(fdoExtension);
if (fdoExtension->Fake) {
//
// Parent is really PCMCIA.sys, his filter will take care
// of sending the wait wake to the parent,... bail out.
//
PCI_UNLOCK_OBJECT(pdoExtension);
return STATUS_PENDING;
}
//
// We're going to do this. Set the wait wake irp field in the
// pdo extension and set cancel routine for this IRP.
//
PciDebugPrint(
PciDbgWaitWake,
"WaitWake: pdox %08x setting PMEEnable.\n",
pdoExtension
);
pdoExtension->PowerState.WaitWakeIrp = Irp;
IoMarkIrpPending(Irp);
pdoExtension->PowerState.SavedCancelRoutine =
IoSetCancelRoutine(Irp, PciPdoWaitWakeCancelRoutine);
//
// NTRAID #62653 - 4/28/2000 - andrewth
// What is the correct behaviour if there are stacked
// cancel routines?
//
ASSERT(!pdoExtension->PowerState.SavedCancelRoutine);
//
// Set the PME Enable bit.
//
PciPdoAdjustPmeEnable( pdoExtension, TRUE );
//
// Remember that the parent now has one more child that is armed
// for wakeup
//
waitCount = InterlockedIncrement(&fdoExtension->ChildWaitWakeCount);
//
// Once we have a wait count reference, we can unlock the object
//
PCI_UNLOCK_OBJECT(pdoExtension);
//
// This PDO is now waiting. If this is the first child of this
// PDO's parent bus to enter this state, the parent bus should
// also enter this state.
//
if (waitCount == 1) {
//
// Note that there are two values that can use here, the
// SystemWakeLevel of the FDO itself or the SystemWakeLevel of
// the PDO. Both are equally valid, but since we want to catch people
// who fail to prevent the system from going into a deeper sleep state
// than their device can support, we use the SystemWakeLevel from the
// PDO, which conviniently enough, is stored in the irp..
//
powerState.SystemState = IrpSp->Parameters.WaitWake.PowerState;
//
// Request a power irp to go to our parent stack
//
PoRequestPowerIrp(
fdoExtension->FunctionalDeviceObject,
IRP_MN_WAIT_WAKE,
powerState,
PciPdoWaitWakeCallBack,
fdoExtension,
NULL
);
}
//
// If we get to this point, then we will return pending because we
// have queue up the request
//
status = STATUS_PENDING;
PciPdoWaitWakeFailIrp:
if (!NT_SUCCESS(status)) {
Irp->IoStatus.Status = status;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
} else {
ASSERT( status == STATUS_PENDING );
}
return status;
}
NTSTATUS
PciPdoWaitWakeCallBack(
IN PDEVICE_OBJECT DeviceObject,
IN UCHAR MinorFunction,
IN POWER_STATE PowerState,
IN PVOID Context,
IN PIO_STATUS_BLOCK IoStatus
)
/*++
Routine Description:
This is the callback routine that gets invoked when the W/W irp that was
sent to the FDO by a PDO is finished. The purpose of this routine is to
see if we need to re-arm the W/W on the FDO because we have more devices
with W/W outstanding on them
Arguments:
DeviceObject - The FDO's device object
MinorFunction - IRP_MN_WAIT_WAKE
PowerState - The sleep state that was used to wake up the system
Context - The FDO Extension
IoStatus - The Status of the request
Return Value:
NTSTATUS
--*/
{
BOOLEAN pmeStatus;
PPCI_FDO_EXTENSION fdoExtension = (PPCI_FDO_EXTENSION) Context;
PIRP finishedIrp;
PPCI_PDO_EXTENSION pdoExtension;
//
// Normally, the IRP (to the PDO) will have completed with
// STATUS_SUCCESS. In that case, just wake up the one device
// which is signalling for wakeup. If the IRP to the PDO
// failed, wake up ALL devices that are dependent on this wake.
//
//
// For each child
//
for (pdoExtension = fdoExtension->ChildPdoList;
pdoExtension && fdoExtension->ChildWaitWakeCount;
pdoExtension = pdoExtension->Next) {
//
// Does this device do power management and if so, does
// it have an outstanding WaitWake IRP?
//
PCI_LOCK_OBJECT(pdoExtension);
if (pdoExtension->PowerState.WaitWakeIrp != NULL) {
PciPmeGetInformation(
pdoExtension->PhysicalDeviceObject,
NULL,
&pmeStatus,
NULL
);
//
// Is this device signalling for a wakeup? (Or, if we
// are completing wake irps because our own wait_wake
// failed).
//
if (pmeStatus || !NT_SUCCESS(IoStatus->Status)) {
//
// Yes. Complete its outstanding wait wake IRP.
//
#if DBG
if (pmeStatus) {
PciDebugPrint(
PciDbgWaitWake,
"PCI - pdox %08x is signalling a PME\n",
pdoExtension
);
} else {
PciDebugPrint(
PciDbgWaitWake,
"PCI - waking pdox %08x because fdo wait failed %0x.\n",
pdoExtension,
IoStatus->Status
);
}
#endif
//
// Wait_wake irp being dequeued, disable the PME enable,
// clear PMEStatus (if set) and EOI this device.
//
PciPdoAdjustPmeEnable( pdoExtension, FALSE );
//
// Make sure this IRP will not be completed again,
// or, canceled.
//
finishedIrp = pdoExtension->PowerState.WaitWakeIrp;
pdoExtension->PowerState.WaitWakeIrp = NULL;
IoSetCancelRoutine(finishedIrp, NULL);
PoStartNextPowerIrp( finishedIrp );
PciCompleteRequest(
finishedIrp, // send down parent status
IoStatus->Status
);
//
// Decrement the waiter count.
//
ASSERT(fdoExtension->ChildWaitWakeCount > 0);
InterlockedDecrement( &(fdoExtension->ChildWaitWakeCount) );
}
}
PCI_UNLOCK_OBJECT(pdoExtension);
}
//
// Did we succeed this irp?
//
if (!NT_SUCCESS(IoStatus->Status)) {
PciDebugPrint(
PciDbgWaitWake,
"WaitWake (fdox %08x) - WaitWake Irp Failed %08x\n",
fdoExtension,
IoStatus->Status
);
return IoStatus->Status;
}
//
// Are there any children with outstanding WaitWakes on thems?
//
if (fdoExtension->ChildWaitWakeCount) {
PciDebugPrint(
PciDbgWaitWake,
"WaitWake (fdox %08x) - WaitWake Irp restarted - count = %x\n",
fdoExtension,
fdoExtension->ChildWaitWakeCount
);
//
// Loop
//
PoRequestPowerIrp(
DeviceObject,
MinorFunction,
PowerState,
PciPdoWaitWakeCallBack,
Context,
NULL
);
#if DBG
} else {
PciDebugPrint(
PciDbgWaitWake,
"WaitWake (fdox %08x) - WaitWake Irp Finished\n",
fdoExtension
);
#endif
}
//
// Done
//
return STATUS_SUCCESS;
}
VOID
PciPdoWaitWakeCancelRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
)
/*++
Routine Description:
Cancel an outstanding WAIT_WAKE IRP.
Note: The Cancel Spin Lock is held on entry.
Arguments:
DeviceObject - Pointer to the device object for which this IRP applies.
Irp - Pointer to the IRP.
Return Value:
None.
--*/
{
PPCI_PDO_EXTENSION pdoExtension;
PPCI_FDO_EXTENSION fdoExtension;
KIRQL oldIrql;
ULONG waitCount;
pdoExtension = (PPCI_PDO_EXTENSION) DeviceObject->DeviceExtension;
PciDebugPrint(
PciDbgWaitWake,
"WaitWake (pdox %08x) Cancel routine, Irp %08x.\n",
pdoExtension,
Irp
);
ASSERT_PCI_PDO_EXTENSION(pdoExtension);
oldIrql = Irp->CancelIrql;
IoReleaseCancelSpinLock(oldIrql);
PCI_LOCK_OBJECT(pdoExtension);
if (pdoExtension->PowerState.WaitWakeIrp == NULL) {
//
// The WaitWake IRP has already been dealt with.
//
PCI_UNLOCK_OBJECT(pdoExtension);
return;
}
//
// Clear WaitWake Irp in the PDO.
//
pdoExtension->PowerState.WaitWakeIrp = NULL;
PciPdoAdjustPmeEnable(pdoExtension, FALSE);
//
// As this is a cancel, the wait wake count in the parent has not
// been decremented. Decrement it here and if decrementing to
// zero waiters, cancel the IRP at the parent.
//
fdoExtension = PCI_PARENT_FDOX(pdoExtension);
ASSERT_PCI_FDO_EXTENSION(fdoExtension);
waitCount = InterlockedDecrement(&fdoExtension->ChildWaitWakeCount);
PCI_UNLOCK_OBJECT(pdoExtension);
if (waitCount == 0) {
//
// Cancel the parent's wait wake also.
//
PciDebugPrint(
PciDbgWaitWake,
"WaitWake (pdox %08x) zero waiters remain on parent, cancelling parent wait.\n",
pdoExtension
);
IoCancelIrp(fdoExtension->PowerState.WaitWakeIrp);
}
//
// Complete the IRP.
//
Irp->IoStatus.Information = 0;
PoStartNextPowerIrp(Irp);
PciCompleteRequest(Irp, STATUS_CANCELLED);
//
// NTRAID #62653 - 4/28/2000 - andrewth
// Need to cause the bus parent to decrement its outstanding
// IRP counter,... how to make this happen?
//
return;
}
NTSTATUS
PciFdoIrpQueryPower(
IN PIRP Irp,
IN PIO_STACK_LOCATION IrpSp,
IN PPCI_COMMON_EXTENSION DeviceExtension
)
{
//
// pass 1, claim we can do it.
//
return STATUS_SUCCESS;
}
NTSTATUS
PciFdoSetPowerState (
IN PIRP Irp,
IN PIO_STACK_LOCATION IrpSp,
IN PPCI_COMMON_EXTENSION DeviceExtension
)
/*++
Routine Description:
Handle SET_POWER irps set to an FDO
Basic Rules for handling this:
- If this is a DEVICE power irp, we don't need to do anything since
for root buses and bridges, all necessary programming is done by the
PDO
- If this is a SYSTEM power irp, then
a) block all incoming IRP_MN_POWER requests (using a spinlock)
b) use the capabilities table in the device extension to determine
what the "highest" allowed DEVICE state that we should transition
the device into
c) look at all the children of this device and see if we can pick a
"lower" DEVICE state.
d) Consideration should be granted if a child is armed for wake
or if this device is armed for wake (in general, both should be
true, or both should be false)
e) Remember the answer as the "Desired State"
f) Release the spinlock and allow other IRP_MN_POWER requests in
g) Use PoRequestPowerIrp() to request a power irp to put the device
in the appropriate state
h) return STATUS_PENDING
- In another thread context (ie: in the context of the completion
passed to PoRequestPowerIrp), complete the irp that was handed to
us
Arguments:
Irp - The Power Irp
IrpSp - The current stack location in the irp
DeviceExtension - The device whose power we want to set
--*/
{
POWER_STATE desiredState;
PPCI_FDO_EXTENSION fdoExtension;
NTSTATUS status;
SYSTEM_POWER_STATE systemState;
fdoExtension = (PPCI_FDO_EXTENSION) DeviceExtension;
ASSERT_PCI_FDO_EXTENSION(fdoExtension);
//
// If this is a device power irp, remember that we say it go by, and
// remember what D-state the bus/bridge is now in. If we needed to do more
// here, then we should have to distinguish between power up and power down
// requests. PowerDown requests we can add the code in-line. PowerUp
// requests would force us to set a completion routine on the IRP and do
// the work in the completion routine
//
if (IrpSp->Parameters.Power.Type == DevicePowerState) {
fdoExtension->PowerState.CurrentDeviceState =
IrpSp->Parameters.Power.State.DeviceState;
return STATUS_SUCCESS;
}
//
// If we aren't started, don't touch the power IRP.
//
if (fdoExtension->DeviceState != PciStarted) {
return STATUS_NOT_SUPPORTED;
}
//
// If this isn't a SystemPowerState irp, then we don't know what it is, and
// so we will not support it
//
ASSERT( IrpSp->Parameters.Power.Type == SystemPowerState );
if (IrpSp->Parameters.Power.Type != SystemPowerState) {
return STATUS_NOT_SUPPORTED;
}
//
// If this is a Shutdown so we can warm reboot don't take the bridges to D3 as
// if the video or boot device is behind the bridge and the BIOS doesn't power
// things up (most don't) then we don't reboot...
//
if (IrpSp->Parameters.Power.State.SystemState == PowerSystemShutdown
&& IrpSp->Parameters.Power.ShutdownType == PowerActionShutdownReset) {
return STATUS_SUCCESS;
}
//
// Grab the system state that we want to go to
//
systemState = IrpSp->Parameters.Power.State.SystemState;
ASSERT( systemState > PowerSystemUnspecified && systemState < PowerSystemMaximum );
//
// At this point, we can assume that we will transition the Device into a
// least the following D-state
//
desiredState.DeviceState = fdoExtension->PowerState.SystemStateMapping[ systemState ];
//
// Mark the irp as pending
//
IoMarkIrpPending( Irp );
//
// Send a request
//
PoRequestPowerIrp(
fdoExtension->FunctionalDeviceObject,
IRP_MN_SET_POWER,
desiredState,
PciFdoSetPowerStateCompletion,
Irp,
NULL
);
return STATUS_PENDING;
}
VOID
PciFdoSetPowerStateCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN UCHAR MinorFunction,
IN POWER_STATE PowerState,
IN PVOID Context,
IN PIO_STATUS_BLOCK IoStatus
)
/*++
Routine Description:
This routine is called when the D-Irp that was requested by the FDO
has been completed.
This routine needs to pass the S-Irp that initiated the D-Irp all the
way down the stack
Arguments:
DeviceObject - The FDO device object
MinorFunction - IRP_MN_SET_POWER
PowerState - Whatever the requested power state was
Context - This is really the S-Irp that requested the D-Irp
IoStatus - The result of the D-Irp
Return Value:
None
--*/
{
PPCI_FDO_EXTENSION fdoExtension;
PIRP irp = (PIRP) Context;
PIO_STACK_LOCATION irpSp;
UNREFERENCED_PARAMETER( MinorFunction );
UNREFERENCED_PARAMETER( PowerState );
ASSERT( IoStatus->Status == STATUS_SUCCESS );
//
// Grab a pointer to the FDO extension and make sure that it is valid
fdoExtension = (PPCI_FDO_EXTENSION) DeviceObject->DeviceExtension;
ASSERT_PCI_FDO_EXTENSION(fdoExtension);
irpSp = IoGetCurrentIrpStackLocation(irp);
//
// Check if we are returning from a hibernate and powering on the bus
//
if (irpSp->Parameters.Power.State.SystemState == PowerSystemWorking
&& fdoExtension->Hibernated) {
fdoExtension->Hibernated = FALSE;
//
// Scan the bus and turn off any new hardware
//
PciScanHibernatedBus(fdoExtension);
}
if (irpSp->Parameters.Power.ShutdownType == PowerActionHibernate
&& irpSp->Parameters.Power.State.SystemState > PowerSystemWorking) {
//
// We're powering down for a hibernate so remember
//
fdoExtension->Hibernated = TRUE;
}
//
// Mark the current irp as having succeeded
//
irp->IoStatus.Status = STATUS_SUCCESS;
//
// Start the next power irp for this device
//
PoStartNextPowerIrp( irp );
//
// Get ready to pass the power irp down the stack
//
IoCopyCurrentIrpStackLocationToNext( irp );
//
// Pass the irp down the stack
//
PoCallDriver( fdoExtension->AttachedDeviceObject, irp );
}
NTSTATUS
PciFdoWaitWake(
IN PIRP Irp,
IN PIO_STACK_LOCATION IrpSp,
IN PPCI_COMMON_EXTENSION DeviceExtension
)
/*++
Routine Description:
Handle IRP_MN_WAIT_WAKE for PCI FDOs.
PCI FDOs receive a WAIT_WAKE IRP when the number of child PDOs
with a pending WAIT_WAKE IRP transitions from 0 to 1.
Arguments:
DeviceObject - Pointer to the device object for which this IRP applies.
Irp - Pointer to the IRP.
Return Value:
NT status.
--*/
{
PIO_STACK_LOCATION irpStack;
PPCI_FDO_EXTENSION fdoExtension;
NTSTATUS status;
fdoExtension = (PPCI_FDO_EXTENSION) DeviceExtension;
ASSERT_PCI_FDO_EXTENSION(fdoExtension);
irpStack = IrpSp;
PCI_LOCK_OBJECT(fdoExtension);
//
// Only one WAIT_WAKE IRP allowed. Set THIS IRP as the wait wake
// irp in the fdo extension, if and only if, there is no other irp
// there.
//
// Note: The ChildWaitWakeCount field is incremented by the PCI
// driver before sending this IRP down. Only accept this IRP if
// the ChildWaitWakeCount field is one (ie don't listen to ACPI).
//
if (!fdoExtension->ChildWaitWakeCount) {
//
// Didn't come from a PCI PDO, ignore it.
//
PciDebugPrint(
PciDbgWaitWake,
"WaitWake (fdox %08x) Unexpected WaitWake IRP IGNORED.\n",
fdoExtension
);
status = STATUS_DEVICE_BUSY;
goto Cleanup;
}
if (fdoExtension->PowerState.WaitWakeIrp != NULL) {
//
// A WAIT_WAKE IRP is already pending for this device.
//
PciDebugPrint(
PciDbgWaitWake,
"WaitWake: fdox %08x already waiting (%d waiters)\n",
fdoExtension,
fdoExtension->ChildWaitWakeCount
);
status = STATUS_DEVICE_BUSY;
goto Cleanup;
}
fdoExtension->PowerState.WaitWakeIrp = Irp;
//
// This IRP will be passed down to the underlying PDO who
// will pend it. The completion routine does needs to check
// that the bus is capable of checking its children and then
// examining each child (that has a wait wake outstanding).
//
PciDebugPrint(
PciDbgWaitWake,
"WaitWake: fdox %08x is a now waiting for a wake event\n",
fdoExtension
);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(
Irp,
PciFdoWaitWakeCompletion,
fdoExtension,
TRUE,
TRUE,
TRUE
);
Irp->IoStatus.Status = status = STATUS_SUCCESS;
Cleanup:
PCI_UNLOCK_OBJECT(fdoExtension);
//
// Start the next power irp
//
PoStartNextPowerIrp(Irp);
if (!NT_SUCCESS(status) ) {
PciCompleteRequest(Irp, status);
return status;
}
//
// Pass the IRP down the stack.
//
return PoCallDriver(fdoExtension->AttachedDeviceObject ,Irp);
}
NTSTATUS
PciFdoWaitWakeCallBack(
IN PDEVICE_OBJECT DeviceObject,
IN UCHAR MinorFunction,
IN POWER_STATE PowerState,
IN PVOID Context,
IN PIO_STATUS_BLOCK IoStatus
)
/*++
Routine Description:
This routine is called when a device has transitioned back into the
into the D-zero state
Arguments:
DeviceObject - Pointer to the FDO
MinorFunction - IRP_MN_SET_POWER
PowerState - D0
Context - The WaitWake irp that caused us to make this transition
IoStatus - The status of the request
Return Value:
NTSTATUS
--*/
{
PIRP waitWakeIrp = (PIRP) Context;
UNREFERENCED_PARAMETER( MinorFunction );
UNREFERENCED_PARAMETER( PowerState );
//
// Complete the wait wake irp
//
PoStartNextPowerIrp( waitWakeIrp );
PciCompleteRequest( waitWakeIrp, IoStatus->Status );
//
// Done
//
return IoStatus->Status;
}
VOID
PciFdoWaitWakeCancel(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
)
/*++
Routine Description:
Cancel an outstanding WAIT_WAKE IRP.
Note: The Cancel Spin Lock is held on entry.
Arguments:
DeviceObject - Pointer to the device object for which this IRP applies.
Irp - Pointer to the IRP.
Return Value:
None.
--*/
{
PPCI_FDO_EXTENSION fdoExtension;
NTSTATUS status;
KIRQL oldIrql;
fdoExtension = (PPCI_FDO_EXTENSION)DeviceObject->DeviceExtension;
PciDebugPrint(
PciDbgWaitWake,
"WaitWake (fdox %08x) Cancel routine, Irp %08x.\n",
fdoExtension,
Irp
);
ASSERT_PCI_FDO_EXTENSION(fdoExtension);
oldIrql = Irp->CancelIrql;
IoReleaseCancelSpinLock(oldIrql);
PCI_LOCK_OBJECT(fdoExtension);
if (fdoExtension->PowerState.WaitWakeIrp == NULL) {
//
// The WaitWake IRP has already been dealt with.
//
PCI_UNLOCK_OBJECT(fdoExtension);
return;
}
fdoExtension->PowerState.WaitWakeIrp = NULL;
PCI_UNLOCK_OBJECT(fdoExtension);
Irp->IoStatus.Information = 0;
PoStartNextPowerIrp(Irp);
PciCompleteRequest(Irp, STATUS_CANCELLED);
return;
}
NTSTATUS
PciFdoWaitWakeCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PPCI_FDO_EXTENSION FdoExtension
)
/*++
Routine Description:
Handle IRP_MN_WAIT_WAKE completion for PCI FDOs.
WAIT_WAKE completion at the FDO means some device (not necesarily
a child of this FDO) is signalling wake. This routine scans each
child to see if that device is the one. This is a recursive
operation.
Arguments:
DeviceObject - Pointer to the device object for which this IRP applies.
Irp - Pointer to the IRP.
Return Value:
NT status.
--*/
{
POWER_STATE powerState;
PciDebugPrint(
PciDbgWaitWake,
"WaitWake (fdox %08x) Completion routine, Irp %08x, IrpStatus = %08x.\n",
FdoExtension,
Irp,
Irp->IoStatus.Status
);
ASSERT_PCI_FDO_EXTENSION(FdoExtension);
//
// We will need the device's lock for some of the following...
//
PCI_LOCK_OBJECT(FdoExtension);
ASSERT (FdoExtension->PowerState.WaitWakeIrp);
//
// We no longer have a WaitWake irp in the the FDO...
//
FdoExtension->PowerState.WaitWakeIrp = NULL;
//
// Check the bus is at a power level at which the config space
// of its children can be examined.
//
// NTRAID #62653 - 4/28/2000 - andrewth
//
// Question: Should we depend on PO to take care of this requirement?
// can we use PO to do the needed power state changes?
//
// Assumption: The parent of this bus is powered at this moment.
//
if (FdoExtension->PowerState.CurrentDeviceState != PowerDeviceD0) {
powerState.SystemState = PowerDeviceD0;
//
// Power up the bus.
//
PoRequestPowerIrp(
DeviceObject,
IRP_MN_SET_POWER,
powerState,
PciFdoWaitWakeCallBack,
Irp,
NULL
);
PCI_UNLOCK_OBJECT(FdoExtension);
return STATUS_MORE_PROCESSING_REQUIRED;
}
//
// Done with lock
//
PCI_UNLOCK_OBJECT(FdoExtension);
return STATUS_SUCCESS;
}
NTSTATUS
PciStallForPowerChange(
IN PPCI_PDO_EXTENSION PdoExtension,
IN DEVICE_POWER_STATE PowerState,
IN UCHAR PowerCapabilityPointer
)
{
NTSTATUS status = STATUS_DEVICE_PROTOCOL_ERROR;
PVERIFIER_DATA verifierData;
LONG delay;
ULONG retries = 100;
KIRQL irql;
PCI_PMCSR pmcsr;
ASSERT(PdoExtension->PowerState.CurrentDeviceState >= PowerDeviceD0
&& PdoExtension->PowerState.CurrentDeviceState <= PowerDeviceD3);
ASSERT(PowerState >= PowerDeviceD0 && PowerState <= PowerDeviceD3);
ASSERT(!(PdoExtension->HackFlags & PCI_HACK_NO_PM_CAPS));
//
// Lookup the delay we are meant to do as in the PCI spec
//
delay = PciPowerDelayTable[PdoExtension->PowerState.CurrentDeviceState-1][PowerState-1];
//
// Stall in a polite fashion if IRQL allows
//
irql = KeGetCurrentIrql();
while (retries--) {
if (delay > 0) {
if (irql < DISPATCH_LEVEL) {
//
// Get off the processor.
//
// timeoutPeriod is in units of 100ns, negative means
// relative.
//
LARGE_INTEGER timeoutPeriod;
timeoutPeriod.QuadPart = -10 * delay;
timeoutPeriod.QuadPart -= (KeQueryTimeIncrement() - 1);
KeDelayExecutionThread(KernelMode,
FALSE,
&timeoutPeriod
);
} else {
//
// Spin, units are microseconds
//
KeStallExecutionProcessor((ULONG)delay);
}
}
//
// Reread the status and control register. The assumption here is that
// some cards don't get their act together fast enough and the fact that
// they arn't ready yet is reflected by them not updating the power control
// register with what we just wrote to it. This is not in the PCI spec
// but is how some of these broken cards work and it can't hurt...
//
PciReadDeviceConfig(
PdoExtension,
&pmcsr,
PowerCapabilityPointer + FIELD_OFFSET(PCI_PM_CAPABILITY,PMCSR),
sizeof(PCI_PMCSR)
);
//
// Pci power states are 0-3 where as NT power states are 1-4
//
if (pmcsr.PowerState == PowerState-1) {
//
// Device is ready, we're done.
//
return STATUS_SUCCESS;
}
//
// Subsequent iterations, delay 1ms.
//
delay = 1000;
}
//
// So how nasty can this sort of problem be?
//
// If this is an ATI M1 (mobile video) and on some machines under some
// circumstances (and no ATI doesn't know which ones) they disable the
// operation of the PMCSR. It would have been nice if they had just
// removed the PM capability from the list so we would have never
// attempted to power manage this chip but they would have failed
// WHQL. Unfortunately it is not possible to just add these to the
// list of devices that have bad PM because some BIOSes (read HP and
// Dell) monitor this register to save extra state from the chip and
// thus if we don't change it we spin in AML forever.
//
// Yes this is a gross hack.
//
verifierData = PciVerifierRetrieveFailureData(
PCI_VERIFIER_PMCSR_TIMEOUT
);
ASSERT(verifierData);
VfFailDeviceNode(
PdoExtension->PhysicalDeviceObject,
PCI_VERIFIER_DETECTED_VIOLATION,
PCI_VERIFIER_PMCSR_TIMEOUT,
verifierData->FailureClass,
&verifierData->Flags,
verifierData->FailureText,
"%DevObj%Ulong",
PdoExtension->PhysicalDeviceObject,
PowerState-1
);
return status;
}
NTSTATUS
PciSetPowerManagedDevicePowerState(
IN PPCI_PDO_EXTENSION PdoExtension,
IN DEVICE_POWER_STATE DeviceState,
IN BOOLEAN RefreshConfigSpace
)
/*++
Routine Description:
If the PCI device supports the PCI Power Management extensions,
set the device to the desired state. Otherwise, this routine
does (can do) nothing.
Arguments:
PdoExtension Pointer to the PDO device extension for the device
being programmed. The current power state stored
in the extension is *not* updated by this function.
DeviceState Power state the device is to be set to.
Return Value:
None.
--*/
{
PCI_PM_CAPABILITY pmCap;
UCHAR pmCapPtr = 0;
NTSTATUS status = STATUS_SUCCESS;
//
// If we are standing by then we want to power down the video to preseve the batteries,
// we have already (in PdoPdoSetPoweState) decided to leave the video on for the hibernate
// and shutdown cases.
//
if ((!PciCanDisableDecodes(PdoExtension, NULL, 0, PCI_CAN_DISABLE_VIDEO_DECODES)) &&
(DeviceState != PowerDeviceD0)) {
//
// Here is a device we unfortunately can't turn off. We do not however
// convert this to D0 - the virtual state of the device will represent
// a powered down device, and only when a real D0 is requested will we
// restore all the various state.
//
return STATUS_SUCCESS;
}
if (!(PdoExtension->HackFlags & PCI_HACK_NO_PM_CAPS) ) {
pmCapPtr = PciReadDeviceCapability(
PdoExtension,
PdoExtension->CapabilitiesPtr,
PCI_CAPABILITY_ID_POWER_MANAGEMENT,
&pmCap,
sizeof(pmCap)
);
if (pmCapPtr == 0) {
//
// We don't have a power management capability - how did we get here?
//
ASSERT(pmCapPtr);
return STATUS_INVALID_DEVICE_REQUEST;
}
//
// Set the device into its new D state
//
switch (DeviceState) {
case PowerDeviceD0:
pmCap.PMCSR.ControlStatus.PowerState = 0;
//
// PCI Power Management Specification. Table-7. Page 25
//
if (pmCap.PMC.Capabilities.Support.PMED3Cold) {
pmCap.PMCSR.ControlStatus.PMEStatus = 1;
}
break;
case PowerDeviceUnspecified:
ASSERT( DeviceState != PowerDeviceUnspecified);
pmCapPtr = 0;
break;
default:
pmCap.PMCSR.ControlStatus.PowerState = (DeviceState - 1);
break;
}
if (pmCapPtr) {
PciWriteDeviceConfig(
PdoExtension,
&pmCap.PMCSR.ControlStatus,
pmCapPtr + FIELD_OFFSET(PCI_PM_CAPABILITY,PMCSR.ControlStatus),
sizeof(pmCap.PMCSR.ControlStatus)
);
} else {
//
// Debug only. ControlFlags should have been set so this
// can't happen.
//
ASSERT(pmCapPtr);
}
//
// Stall for the appropriate time
//
status = PciStallForPowerChange(PdoExtension, DeviceState, pmCapPtr);
}
//
// Only update the config space if:
// - The device is happy and in the correct power state
// - We have been asked to refresh the config space
// - We have powered up the device
//
if (NT_SUCCESS(status)
&& RefreshConfigSpace
&& DeviceState < PdoExtension->PowerState.CurrentDeviceState) {
status = PciSetResources(PdoExtension, TRUE, FALSE);
}
return status;
}