Synchronize with trunk revision 59636 (just before Alex's CreateProcess revamp).
[reactos.git] / drivers / bus / pcix / power.c
index 426d39d..06fc80f 100644 (file)
 
 /* 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)
 {
+    UNREFERENCED_PARAMETER(Irp);
+    UNREFERENCED_PARAMETER(IoStackLocation);
+    UNREFERENCED_PARAMETER(DeviceExtension);
+
     UNIMPLEMENTED;
     while (TRUE);
     return STATUS_NOT_SUPPORTED;
@@ -33,6 +221,10 @@ PciFdoSetPowerState(IN PIRP Irp,
                     IN PIO_STACK_LOCATION IoStackLocation,
                     IN PPCI_FDO_EXTENSION DeviceExtension)
 {
+    UNREFERENCED_PARAMETER(Irp);
+    UNREFERENCED_PARAMETER(IoStackLocation);
+    UNREFERENCED_PARAMETER(DeviceExtension);
+
     UNIMPLEMENTED;
     while (TRUE);
     return STATUS_NOT_SUPPORTED;
@@ -44,6 +236,10 @@ PciFdoIrpQueryPower(IN PIRP Irp,
                     IN PIO_STACK_LOCATION IoStackLocation,
                     IN PPCI_FDO_EXTENSION DeviceExtension)
 {
+    UNREFERENCED_PARAMETER(Irp);
+    UNREFERENCED_PARAMETER(IoStackLocation);
+    UNREFERENCED_PARAMETER(DeviceExtension);
+
     UNIMPLEMENTED;
     while (TRUE);
     return STATUS_NOT_SUPPORTED;