/* GLOBALS ********************************************************************/
+ULONG PciPowerDelayTable[PowerDeviceD3 * PowerDeviceD3] =
+{
+ 0, // D0 -> D0
+ 0, // D1 -> D0
+ 200, // D2 -> D0
+ 10000, // D3 -> D0
+
+ 0, // D0 -> D1
+ 0, // D1 -> D1
+ 200, // D2 -> D1
+ 10000, // D3 -> D1
+
+ 200, // D0 -> D2
+ 200, // D1 -> D2
+ 0, // D2 -> D2
+ 10000, // D3 -> D2
+
+ 10000, // D0 -> D3
+ 10000, // D1 -> D3
+ 10000, // D2 -> D3
+ 0 // D3 -> D3
+};
+
/* FUNCTIONS ******************************************************************/
+NTSTATUS
+NTAPI
+PciStallForPowerChange(IN PPCI_PDO_EXTENSION PdoExtension,
+ IN DEVICE_POWER_STATE PowerState,
+ IN ULONG_PTR CapOffset)
+{
+ ULONG PciState, TimeoutEntry, PmcsrOffset, TryCount;
+ PPCI_VERIFIER_DATA VerifierData;
+ LARGE_INTEGER Interval;
+ PCI_PMCSR Pmcsr;
+ KIRQL Irql;
+
+ /* Make sure the power state is valid, and the device can support it */
+ ASSERT((PdoExtension->PowerState.CurrentDeviceState >= PowerDeviceD0) &&
+ (PdoExtension->PowerState.CurrentDeviceState <= PowerDeviceD3));
+ ASSERT((PowerState >= PowerDeviceD0) && (PowerState <= PowerDeviceD3));
+ ASSERT(!(PdoExtension->HackFlags & PCI_HACK_NO_PM_CAPS));
+
+ /* Save the current IRQL */
+ Irql = KeGetCurrentIrql();
+
+ /* Pick the expected timeout for this transition */
+ TimeoutEntry = PciPowerDelayTable[PowerState * PdoExtension->PowerState.CurrentDeviceState];
+
+ /* PCI power states are one less than NT power states */
+ PciState = PowerState - 1;
+
+ /* The state status is stored in the PMCSR offset */
+ PmcsrOffset = CapOffset + FIELD_OFFSET(PCI_PM_CAPABILITY, PMCSR);
+
+ /* Try changing the power state up to 100 times */
+ TryCount = 100;
+ while (--TryCount)
+ {
+ /* Check if this state transition will take time */
+ if (TimeoutEntry > 0)
+ {
+ /* Check if this is happening at high IRQL */
+ if (Irql >= DISPATCH_LEVEL)
+ {
+ /* Can't wait at high IRQL, stall the processor */
+ KeStallExecutionProcessor(TimeoutEntry);
+ }
+ else
+ {
+ /* Do a wait for the timeout specified instead */
+ Interval.QuadPart = -10 * TimeoutEntry;
+ Interval.QuadPart -= KeQueryTimeIncrement() - 1;
+ KeDelayExecutionThread(0, 0, &Interval);
+ }
+ }
+
+ /* Read the PMCSR and see if the state has changed */
+ PciReadDeviceConfig(PdoExtension, &Pmcsr, PmcsrOffset, sizeof(PCI_PMCSR));
+ if (Pmcsr.PowerState == PciState) return STATUS_SUCCESS;
+
+ /* Try again, forcing a timeout of 1ms */
+ TimeoutEntry = 1000;
+ }
+
+ /* Call verifier with this error */
+ VerifierData = PciVerifierRetrieveFailureData(2);
+ ASSERT(VerifierData);
+ VfFailDeviceNode(PdoExtension->PhysicalDeviceObject,
+ PCI_VERIFIER_DETECTED_VIOLATION,
+ 2, // The PMCSR register was not updated within the spec-mandated time.
+ VerifierData->FailureClass,
+ &VerifierData->AssertionControl,
+ VerifierData->DebuggerMessageText,
+ "%DevObj%Ulong",
+ PdoExtension->PhysicalDeviceObject,
+ PciState);
+
+ return STATUS_DEVICE_PROTOCOL_ERROR;
+}
+
+NTSTATUS
+NTAPI
+PciSetPowerManagedDevicePowerState(IN PPCI_PDO_EXTENSION DeviceExtension,
+ IN DEVICE_POWER_STATE DeviceState,
+ IN BOOLEAN IrpSet)
+{
+ NTSTATUS Status;
+ PCI_PM_CAPABILITY PmCaps;
+ ULONG CapsOffset;
+
+ /* Assume success */
+ Status = STATUS_SUCCESS;
+
+ /* Check if this device can support low power states */
+ if (!(PciCanDisableDecodes(DeviceExtension, NULL, 0, TRUE)) &&
+ (DeviceState != PowerDeviceD0))
+ {
+ /* Simply return success, ignoring this request */
+ DPRINT1("Cannot disable decodes on this device, ignoring PM request...\n");
+ return Status;
+ }
+
+ /* Does the device support power management at all? */
+ if (!(DeviceExtension->HackFlags & PCI_HACK_NO_PM_CAPS))
+ {
+ /* Get the PM capabailities register */
+ CapsOffset = PciReadDeviceCapability(DeviceExtension,
+ DeviceExtension->CapabilitiesPtr,
+ PCI_CAPABILITY_ID_POWER_MANAGEMENT,
+ &PmCaps.Header,
+ sizeof(PCI_PM_CAPABILITY));
+ ASSERT(CapsOffset);
+ ASSERT(DeviceState != PowerDeviceUnspecified);
+
+ /* Check if the device is being powered up */
+ if (DeviceState == PowerDeviceD0)
+ {
+ /* Set full power state */
+ PmCaps.PMCSR.ControlStatus.PowerState = 0;
+
+ /* Check if the device supports Cold-D3 poweroff */
+ if (PmCaps.PMC.Capabilities.Support.PMED3Cold)
+ {
+ /* If there was a pending PME, clear it */
+ PmCaps.PMCSR.ControlStatus.PMEStatus = 1;
+ }
+ }
+ else
+ {
+ /* Otherwise, just set the new power state, converting from NT */
+ PmCaps.PMCSR.ControlStatus.PowerState = DeviceState - 1;
+ }
+
+ /* Write the new power state in the PMCSR */
+ PciWriteDeviceConfig(DeviceExtension,
+ &PmCaps.PMCSR,
+ CapsOffset + FIELD_OFFSET(PCI_PM_CAPABILITY, PMCSR),
+ sizeof(PCI_PMCSR));
+
+ /* Now wait for the change to "stick" based on the spec-mandated time */
+ Status = PciStallForPowerChange(DeviceExtension, DeviceState, CapsOffset);
+ if (!NT_SUCCESS(Status)) return Status;
+ }
+ else
+ {
+ /* Nothing to do! */
+ DPRINT1("No PM on this device, ignoring request\n");
+ }
+
+ /* Check if new resources have to be assigned */
+ if (IrpSet)
+ {
+ /* Check if the new device state is lower (higher power) than now */
+ if (DeviceState < DeviceExtension->PowerState.CurrentDeviceState)
+ {
+ /* We would normally re-assign resources after powerup */
+ UNIMPLEMENTED_DBGBREAK();
+ Status = STATUS_NOT_IMPLEMENTED;
+ }
+ }
+
+ /* Return the power state change status */
+ return Status;
+}
+
+NTSTATUS
+NTAPI
+PciFdoWaitWake(IN PIRP Irp,
+ IN PIO_STACK_LOCATION IoStackLocation,
+ IN PPCI_FDO_EXTENSION DeviceExtension)
+{
+ UNIMPLEMENTED;
+ while (TRUE);
+ return STATUS_NOT_SUPPORTED;
+}
+
+NTSTATUS
+NTAPI
+PciFdoSetPowerState(IN PIRP Irp,
+ IN PIO_STACK_LOCATION IoStackLocation,
+ IN PPCI_FDO_EXTENSION DeviceExtension)
+{
+ UNIMPLEMENTED;
+ while (TRUE);
+ return STATUS_NOT_SUPPORTED;
+}
+
+NTSTATUS
+NTAPI
+PciFdoIrpQueryPower(IN PIRP Irp,
+ IN PIO_STACK_LOCATION IoStackLocation,
+ IN PPCI_FDO_EXTENSION DeviceExtension)
+{
+ UNIMPLEMENTED;
+ while (TRUE);
+ return STATUS_NOT_SUPPORTED;
+}
+
/* EOF */