Skip to content

Commit

Permalink
PCI / PM: Make PCIe PME interrupts wake up from suspend-to-idle
Browse files Browse the repository at this point in the history
To make PCIe PME interrupts wake up the system from suspend to idle,
make the PME driver use enable_irq_wake() on the IRQ during system
suspend (if there are any wakeup devices below the given PCIe port)
without disabling PME interrupts.  This way, an interrupt will still
trigger if a wakeup event happens and the system will be woken up (or
system suspend in progress will be aborted) by means of the new
mechanics introduced previously.

This change allows Wake-on-LAN to be used for wakeup from
suspend-to-idle on my MSI Wind tesbed netbook.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
  • Loading branch information
Rafael J. Wysocki committed Sep 1, 2014
1 parent 5613570 commit 76cde7e
Showing 1 changed file with 51 additions and 10 deletions.
61 changes: 51 additions & 10 deletions drivers/pci/pcie/pme.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,17 @@ static int __init pcie_pme_setup(char *str)
}
__setup("pcie_pme=", pcie_pme_setup);

enum pme_suspend_level {
PME_SUSPEND_NONE = 0,
PME_SUSPEND_WAKEUP,
PME_SUSPEND_NOIRQ,
};

struct pcie_pme_service_data {
spinlock_t lock;
struct pcie_device *srv;
struct work_struct work;
bool noirq; /* Don't enable the PME interrupt used by this service. */
enum pme_suspend_level suspend_level;
};

/**
Expand Down Expand Up @@ -223,7 +229,7 @@ static void pcie_pme_work_fn(struct work_struct *work)
spin_lock_irq(&data->lock);

for (;;) {
if (data->noirq)
if (data->suspend_level != PME_SUSPEND_NONE)
break;

pcie_capability_read_dword(port, PCI_EXP_RTSTA, &rtsta);
Expand All @@ -250,7 +256,7 @@ static void pcie_pme_work_fn(struct work_struct *work)
spin_lock_irq(&data->lock);
}

if (!data->noirq)
if (data->suspend_level == PME_SUSPEND_NONE)
pcie_pme_interrupt_enable(port, true);

spin_unlock_irq(&data->lock);
Expand Down Expand Up @@ -367,6 +373,21 @@ static int pcie_pme_probe(struct pcie_device *srv)
return ret;
}

static bool pcie_pme_check_wakeup(struct pci_bus *bus)
{
struct pci_dev *dev;

if (!bus)
return false;

list_for_each_entry(dev, &bus->devices, bus_list)
if (device_may_wakeup(&dev->dev)
|| pcie_pme_check_wakeup(dev->subordinate))
return true;

return false;
}

/**
* pcie_pme_suspend - Suspend PCIe PME service device.
* @srv: PCIe service device to suspend.
Expand All @@ -375,11 +396,26 @@ static int pcie_pme_suspend(struct pcie_device *srv)
{
struct pcie_pme_service_data *data = get_service_data(srv);
struct pci_dev *port = srv->port;
bool wakeup;

if (device_may_wakeup(&port->dev)) {
wakeup = true;
} else {
down_read(&pci_bus_sem);
wakeup = pcie_pme_check_wakeup(port->subordinate);
up_read(&pci_bus_sem);
}
spin_lock_irq(&data->lock);
pcie_pme_interrupt_enable(port, false);
pcie_clear_root_pme_status(port);
data->noirq = true;
if (wakeup) {
enable_irq_wake(srv->irq);
data->suspend_level = PME_SUSPEND_WAKEUP;
} else {
struct pci_dev *port = srv->port;

pcie_pme_interrupt_enable(port, false);
pcie_clear_root_pme_status(port);
data->suspend_level = PME_SUSPEND_NOIRQ;
}
spin_unlock_irq(&data->lock);

synchronize_irq(srv->irq);
Expand All @@ -394,12 +430,17 @@ static int pcie_pme_suspend(struct pcie_device *srv)
static int pcie_pme_resume(struct pcie_device *srv)
{
struct pcie_pme_service_data *data = get_service_data(srv);
struct pci_dev *port = srv->port;

spin_lock_irq(&data->lock);
data->noirq = false;
pcie_clear_root_pme_status(port);
pcie_pme_interrupt_enable(port, true);
if (data->suspend_level == PME_SUSPEND_NOIRQ) {
struct pci_dev *port = srv->port;

pcie_clear_root_pme_status(port);
pcie_pme_interrupt_enable(port, true);
} else {
disable_irq_wake(srv->irq);
}
data->suspend_level = PME_SUSPEND_NONE;
spin_unlock_irq(&data->lock);

return 0;
Expand Down

0 comments on commit 76cde7e

Please sign in to comment.