Skip to content

Commit

Permalink
PCI/PM: add PCIe runtime D3cold support
Browse files Browse the repository at this point in the history
This patch adds runtime D3cold support and corresponding ACPI platform
support.  This patch only enables runtime D3cold support; it does not
enable D3cold support during system suspend/hibernate.

D3cold is the deepest power saving state for a PCIe device, where its main
power is removed.  While it is in D3cold, you can't access the device at
all, not even its configuration space (which is still accessible in D3hot).
Therefore the PCI PM registers can not be used to transition into/out of
the D3cold state; that must be done by platform logic such as ACPI _PR3.

To support wakeup from D3cold, a system may provide auxiliary power, which
allows a device to request wakeup using a Beacon or the sideband WAKE#
signal.  WAKE# is usually connected to platform logic such as ACPI GPE.
This is quite different from other power saving states, where devices
request wakeup via a PME message on the PCIe link.

Some devices, such as those in plug-in slots, have no direct platform
logic.  For example, there is usually no ACPI _PR3 for them.  D3cold
support for these devices can be done via the PCIe Downstream Port leading
to the device.  When the PCIe port is powered on/off, the device is powered
on/off too.  Wakeup events from the device will be notified to the
corresponding PCIe port.

For more information about PCIe D3cold and corresponding ACPI support,
please refer to:

- PCI Express Base Specification Revision 2.0
- Advanced Configuration and Power Interface Specification Revision 5.0

[bhelgaas: changelog]
Reviewed-by: Rafael J. Wysocki <[email protected]>
Originally-by: Zheng Yan <[email protected]>
Signed-off-by: Huang Ying <[email protected]>
Signed-off-by: Bjorn Helgaas <[email protected]>
  • Loading branch information
yhuang-intel authored and bjorn-helgaas committed Jun 23, 2012
1 parent 8497f69 commit 448bd85
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 22 deletions.
23 changes: 19 additions & 4 deletions drivers/pci/pci-acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ static void pci_acpi_wake_dev(acpi_handle handle, u32 event, void *context)
if (event != ACPI_NOTIFY_DEVICE_WAKE || !pci_dev)
return;

if (pci_dev->current_state == PCI_D3cold) {
pci_wakeup_event(pci_dev);
pm_runtime_resume(&pci_dev->dev);
return;
}

if (!pci_dev->pm_cap || !pci_dev->pme_support
|| pci_check_pme_status(pci_dev)) {
if (pci_dev->pme_poll)
Expand Down Expand Up @@ -187,10 +193,13 @@ acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)

static pci_power_t acpi_pci_choose_state(struct pci_dev *pdev)
{
int acpi_state;
int acpi_state, d_max;

acpi_state = acpi_pm_device_sleep_state(&pdev->dev, NULL,
ACPI_STATE_D3);
if (pdev->no_d3cold)
d_max = ACPI_STATE_D3_HOT;
else
d_max = ACPI_STATE_D3_COLD;
acpi_state = acpi_pm_device_sleep_state(&pdev->dev, NULL, d_max);
if (acpi_state < 0)
return PCI_POWER_ERROR;

Expand Down Expand Up @@ -297,7 +306,13 @@ static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable)

static int acpi_pci_run_wake(struct pci_dev *dev, bool enable)
{
if (dev->pme_interrupt)
/*
* Per PCI Express Base Specification Revision 2.0 section
* 5.3.3.2 Link Wakeup, platform support is needed for D3cold
* waking up to power on the main link even if there is PME
* support for D3cold
*/
if (dev->pme_interrupt && !dev->runtime_d3cold)
return 0;

if (!acpi_pm_device_run_wake(&dev->dev, enable))
Expand Down
10 changes: 9 additions & 1 deletion drivers/pci/pci-driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -1019,10 +1019,13 @@ static int pci_pm_runtime_suspend(struct device *dev)
if (!pm || !pm->runtime_suspend)
return -ENOSYS;

pci_dev->no_d3cold = false;
error = pm->runtime_suspend(dev);
suspend_report_result(pm->runtime_suspend, error);
if (error)
return error;
if (!pci_dev->d3cold_allowed)
pci_dev->no_d3cold = true;

pci_fixup_device(pci_fixup_suspend, pci_dev);

Expand All @@ -1044,6 +1047,7 @@ static int pci_pm_runtime_suspend(struct device *dev)

static int pci_pm_runtime_resume(struct device *dev)
{
int rc;
struct pci_dev *pci_dev = to_pci_dev(dev);
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;

Expand All @@ -1054,7 +1058,11 @@ static int pci_pm_runtime_resume(struct device *dev)
__pci_enable_wake(pci_dev, PCI_D0, true, false);
pci_fixup_device(pci_fixup_resume, pci_dev);

return pm->runtime_resume(dev);
rc = pm->runtime_resume(dev);

pci_dev->runtime_d3cold = false;

return rc;
}

static int pci_pm_runtime_idle(struct device *dev)
Expand Down
29 changes: 29 additions & 0 deletions drivers/pci/pci-sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <linux/pci-aspm.h>
#include <linux/slab.h>
#include <linux/vgaarb.h>
#include <linux/pm_runtime.h>
#include "pci.h"

static int sysfs_initialized; /* = 0 */
Expand Down Expand Up @@ -378,6 +379,31 @@ dev_bus_rescan_store(struct device *dev, struct device_attribute *attr,

#endif

#if defined(CONFIG_PM_RUNTIME) && defined(CONFIG_ACPI)
static ssize_t d3cold_allowed_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct pci_dev *pdev = to_pci_dev(dev);
unsigned long val;

if (strict_strtoul(buf, 0, &val) < 0)
return -EINVAL;

pdev->d3cold_allowed = !!val;
pm_runtime_resume(dev);

return count;
}

static ssize_t d3cold_allowed_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
return sprintf (buf, "%u\n", pdev->d3cold_allowed);
}
#endif

struct device_attribute pci_dev_attrs[] = {
__ATTR_RO(resource),
__ATTR_RO(vendor),
Expand All @@ -401,6 +427,9 @@ struct device_attribute pci_dev_attrs[] = {
#ifdef CONFIG_HOTPLUG
__ATTR(remove, (S_IWUSR|S_IWGRP), NULL, remove_store),
__ATTR(rescan, (S_IWUSR|S_IWGRP), NULL, dev_rescan_store),
#endif
#if defined(CONFIG_PM_RUNTIME) && defined(CONFIG_ACPI)
__ATTR(d3cold_allowed, 0644, d3cold_allowed_show, d3cold_allowed_store),
#endif
__ATTR_NULL,
};
Expand Down
114 changes: 105 additions & 9 deletions drivers/pci/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,8 @@ static int pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state)
dev_info(&dev->dev, "Refused to change power state, "
"currently in D%d\n", dev->current_state);

/* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT
/*
* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT
* INTERFACE SPECIFICATION, REV. 1.2", a device transitioning
* from D3hot to D0 _may_ perform an internal reset, thereby
* going to "D0 Uninitialized" rather than "D0 Initialized".
Expand Down Expand Up @@ -654,6 +655,16 @@ void pci_update_current_state(struct pci_dev *dev, pci_power_t state)
if (dev->pm_cap) {
u16 pmcsr;

/*
* Configuration space is not accessible for device in
* D3cold, so just keep or set D3cold for safety
*/
if (dev->current_state == PCI_D3cold)
return;
if (state == PCI_D3cold) {
dev->current_state = PCI_D3cold;
return;
}
pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
} else {
Expand Down Expand Up @@ -694,8 +705,50 @@ static int pci_platform_power_transition(struct pci_dev *dev, pci_power_t state)
*/
static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
{
if (state == PCI_D0)
if (state == PCI_D0) {
pci_platform_power_transition(dev, PCI_D0);
/*
* Mandatory power management transition delays, see
* PCI Express Base Specification Revision 2.0 Section
* 6.6.1: Conventional Reset. Do not delay for
* devices powered on/off by corresponding bridge,
* because have already delayed for the bridge.
*/
if (dev->runtime_d3cold) {
msleep(dev->d3cold_delay);
/*
* When powering on a bridge from D3cold, the
* whole hierarchy may be powered on into
* D0uninitialized state, resume them to give
* them a chance to suspend again
*/
pci_wakeup_bus(dev->subordinate);
}
}
}

/**
* __pci_dev_set_current_state - Set current state of a PCI device
* @dev: Device to handle
* @data: pointer to state to be set
*/
static int __pci_dev_set_current_state(struct pci_dev *dev, void *data)
{
pci_power_t state = *(pci_power_t *)data;

dev->current_state = state;
return 0;
}

/**
* __pci_bus_set_current_state - Walk given bus and set current state of devices
* @bus: Top bus of the subtree to walk.
* @state: state to be set
*/
static void __pci_bus_set_current_state(struct pci_bus *bus, pci_power_t state)
{
if (bus)
pci_walk_bus(bus, __pci_dev_set_current_state, &state);
}

/**
Expand All @@ -707,8 +760,15 @@ static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
*/
int __pci_complete_power_transition(struct pci_dev *dev, pci_power_t state)
{
return state >= PCI_D0 ?
pci_platform_power_transition(dev, state) : -EINVAL;
int ret;

if (state < PCI_D0)
return -EINVAL;
ret = pci_platform_power_transition(dev, state);
/* Power off the bridge may power off the whole hierarchy */
if (!ret && state == PCI_D3cold)
__pci_bus_set_current_state(dev->subordinate, PCI_D3cold);
return ret;
}
EXPORT_SYMBOL_GPL(__pci_complete_power_transition);

Expand All @@ -732,8 +792,8 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
int error;

/* bound the state we're entering */
if (state > PCI_D3hot)
state = PCI_D3hot;
if (state > PCI_D3cold)
state = PCI_D3cold;
else if (state < PCI_D0)
state = PCI_D0;
else if ((state == PCI_D1 || state == PCI_D2) && pci_no_d1d2(dev))
Expand All @@ -748,10 +808,15 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state)

/* This device is quirked not to be put into D3, so
don't put it in D3 */
if (state == PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3))
if (state >= PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3))
return 0;

error = pci_raw_set_power_state(dev, state);
/*
* To put device in D3cold, we put device into D3hot in native
* way, then put device into D3cold with platform ops
*/
error = pci_raw_set_power_state(dev, state > PCI_D3hot ?
PCI_D3hot : state);

if (!__pci_complete_power_transition(dev, state))
error = 0;
Expand Down Expand Up @@ -1497,6 +1562,28 @@ void pci_pme_wakeup_bus(struct pci_bus *bus)
pci_walk_bus(bus, pci_pme_wakeup, (void *)true);
}

/**
* pci_wakeup - Wake up a PCI device
* @dev: Device to handle.
* @ign: ignored parameter
*/
static int pci_wakeup(struct pci_dev *pci_dev, void *ign)
{
pci_wakeup_event(pci_dev);
pm_request_resume(&pci_dev->dev);
return 0;
}

/**
* pci_wakeup_bus - Walk given bus and wake up devices on it
* @bus: Top bus of the subtree to walk.
*/
void pci_wakeup_bus(struct pci_bus *bus)
{
if (bus)
pci_walk_bus(bus, pci_wakeup, NULL);
}

/**
* pci_pme_capable - check the capability of PCI device to generate PME#
* @dev: PCI device to handle.
Expand Down Expand Up @@ -1754,6 +1841,10 @@ int pci_prepare_to_sleep(struct pci_dev *dev)
if (target_state == PCI_POWER_ERROR)
return -EIO;

/* D3cold during system suspend/hibernate is not supported */
if (target_state > PCI_D3hot)
target_state = PCI_D3hot;

pci_enable_wake(dev, target_state, device_may_wakeup(&dev->dev));

error = pci_set_power_state(dev, target_state);
Expand Down Expand Up @@ -1791,12 +1882,16 @@ int pci_finish_runtime_suspend(struct pci_dev *dev)
if (target_state == PCI_POWER_ERROR)
return -EIO;

dev->runtime_d3cold = target_state == PCI_D3cold;

__pci_enable_wake(dev, target_state, true, pci_dev_run_wake(dev));

error = pci_set_power_state(dev, target_state);

if (error)
if (error) {
__pci_enable_wake(dev, target_state, true, false);
dev->runtime_d3cold = false;
}

return error;
}
Expand Down Expand Up @@ -1866,6 +1961,7 @@ void pci_pm_init(struct pci_dev *dev)

dev->pm_cap = pm;
dev->d3_delay = PCI_PM_D3_WAIT;
dev->d3cold_delay = PCI_PM_D3COLD_WAIT;

dev->d1_support = false;
dev->d2_support = false;
Expand Down
1 change: 1 addition & 0 deletions drivers/pci/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
extern void pci_disable_enabled_device(struct pci_dev *dev);
extern int pci_finish_runtime_suspend(struct pci_dev *dev);
extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
extern void pci_wakeup_bus(struct pci_bus *bus);
extern void pci_pm_init(struct pci_dev *dev);
extern void platform_pci_wakeup_init(struct pci_dev *dev);
extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
Expand Down
44 changes: 40 additions & 4 deletions drivers/pci/pcie/portdrv_pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,48 @@ static int pcie_port_resume_noirq(struct device *dev)
}

#ifdef CONFIG_PM_RUNTIME
static int pcie_port_runtime_pm(struct device *dev)
struct d3cold_info {
bool no_d3cold;
unsigned int d3cold_delay;
};

static int pci_dev_d3cold_info(struct pci_dev *pdev, void *data)
{
struct d3cold_info *info = data;

info->d3cold_delay = max_t(unsigned int, pdev->d3cold_delay,
info->d3cold_delay);
if (pdev->no_d3cold)
info->no_d3cold = true;
return 0;
}

static int pcie_port_runtime_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct d3cold_info d3cold_info = {
.no_d3cold = false,
.d3cold_delay = PCI_PM_D3_WAIT,
};

/*
* If any subordinate device disable D3cold, we should not put
* the port into D3cold. The D3cold delay of port should be
* the max of that of all subordinate devices.
*/
pci_walk_bus(pdev->subordinate, pci_dev_d3cold_info, &d3cold_info);
pdev->no_d3cold = d3cold_info.no_d3cold;
pdev->d3cold_delay = d3cold_info.d3cold_delay;
return 0;
}

static int pcie_port_runtime_resume(struct device *dev)
{
return 0;
}
#else
#define pcie_port_runtime_pm NULL
#define pcie_port_runtime_suspend NULL
#define pcie_port_runtime_resume NULL
#endif

static const struct dev_pm_ops pcie_portdrv_pm_ops = {
Expand All @@ -117,8 +153,8 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = {
.poweroff = pcie_port_device_suspend,
.restore = pcie_port_device_resume,
.resume_noirq = pcie_port_resume_noirq,
.runtime_suspend = pcie_port_runtime_pm,
.runtime_resume = pcie_port_runtime_pm,
.runtime_suspend = pcie_port_runtime_suspend,
.runtime_resume = pcie_port_runtime_resume,
};

#define PCIE_PORTDRV_PM_OPS (&pcie_portdrv_pm_ops)
Expand Down
Loading

0 comments on commit 448bd85

Please sign in to comment.