===================================================================
@@ -20,6 +20,7 @@
#include <linux/device.h>
#include <linux/pcieport_if.h>
#include <linux/pm_runtime.h>
+#include <linux/pm_wakeup.h>
#include "../pci.h"
#include "portdrv.h"
@@ -41,11 +42,18 @@ static int __init pcie_pme_setup(char *s
}
__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. */
+ struct wakeup_source *ws;
+ enum pme_suspend_level suspend_level;
};
/**
@@ -223,7 +231,7 @@ static void pcie_pme_work_fn(struct work
spin_lock_irq(&data->lock);
for (;;) {
- if (data->noirq)
+ if (data->suspend_level == PME_SUSPEND_NOIRQ)
break;
pcie_capability_read_dword(port, PCI_EXP_RTSTA, &rtsta);
@@ -234,6 +242,11 @@ static void pcie_pme_work_fn(struct work
*/
pcie_clear_root_pme_status(port);
+ if (data->suspend_level == PME_SUSPEND_WAKEUP) {
+ __pm_wakeup_event(data->ws, 0);
+ break;
+ }
+
spin_unlock_irq(&data->lock);
pcie_pme_handle_request(port, rtsta & 0xffff);
spin_lock_irq(&data->lock);
@@ -250,7 +263,7 @@ static void pcie_pme_work_fn(struct work
spin_lock_irq(&data->lock);
}
- if (!data->noirq)
+ if (data->suspend_level != PME_SUSPEND_NOIRQ)
pcie_pme_interrupt_enable(port, true);
spin_unlock_irq(&data->lock);
@@ -356,10 +369,12 @@ static int pcie_pme_probe(struct pcie_de
pcie_pme_interrupt_enable(port, false);
pcie_clear_root_pme_status(port);
- ret = request_irq(srv->irq, pcie_pme_irq, IRQF_SHARED, "PCIe PME", srv);
+ ret = request_irq(srv->irq, pcie_pme_irq, IRQF_SHARED | IRQF_NO_SUSPEND,
+ "PCIe PME", srv);
if (ret) {
kfree(data);
} else {
+ data->ws = wakeup_source_register(dev_name(&srv->device));
pcie_pme_mark_devices(port);
pcie_pme_interrupt_enable(port, true);
}
@@ -367,6 +382,21 @@ static int pcie_pme_probe(struct pcie_de
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.
@@ -375,11 +405,25 @@ static int pcie_pme_suspend(struct pcie_
{
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) {
+ 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);
@@ -394,12 +438,15 @@ static int pcie_pme_suspend(struct pcie_
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);
+ }
+ data->suspend_level = PME_SUSPEND_NONE;
spin_unlock_irq(&data->lock);
return 0;
@@ -411,9 +458,12 @@ static int pcie_pme_resume(struct pcie_d
*/
static void pcie_pme_remove(struct pcie_device *srv)
{
+ struct pcie_pme_service_data *data = get_service_data(srv);
+
pcie_pme_suspend(srv);
free_irq(srv->irq, srv);
- kfree(get_service_data(srv));
+ wakeup_source_unregister(data->ws);
+ kfree(data);
}
static struct pcie_port_service_driver pcie_pme_driver = {
===================================================================
@@ -43,6 +43,8 @@ MODULE_LICENSE("GPL");
static int aer_probe(struct pcie_device *dev);
static void aer_remove(struct pcie_device *dev);
+static int aer_suspend(struct pcie_device *dev);
+static int aer_resume(struct pcie_device *dev);
static pci_ers_result_t aer_error_detected(struct pci_dev *dev,
enum pci_channel_state error);
static void aer_error_resume(struct pci_dev *dev);
@@ -60,6 +62,8 @@ static struct pcie_port_service_driver a
.probe = aer_probe,
.remove = aer_remove,
+ .suspend = aer_suspend,
+ .resume = aer_resume,
.err_handler = &aer_error_handlers,
@@ -222,9 +226,10 @@ irqreturn_t aer_irq(int irq, void *conte
next_prod_idx = rpc->prod_idx + 1;
if (next_prod_idx == AER_ERROR_SOURCES_MAX)
next_prod_idx = 0;
- if (next_prod_idx == rpc->cons_idx) {
+ if (next_prod_idx == rpc->cons_idx || rpc->suspended) {
/*
- * Error Storm Condition - possibly the same error occurred.
+ * Error Storm Condition (possibly the same error occurred) or
+ * the service has been suspended.
* Drop the error.
*/
spin_unlock_irqrestore(&rpc->e_lock, flags);
@@ -271,6 +276,38 @@ static struct aer_rpc *aer_alloc_rpc(str
}
/**
+ * aer_suspend - suspend the service
+ * @dev: pointer to the pcie_dev data structure
+ *
+ * Invoked during system suspend.
+ */
+static int aer_suspend(struct pcie_device *dev)
+{
+ struct aer_rpc *rpc = get_service_data(dev);
+
+ spin_lock_irq(&rpc->e_lock);
+ rpc->suspended = true;
+ spin_unlock_irq(&rpc->e_lock);
+ return 0;
+}
+
+/**
+ * aer_resume - resume the service
+ * @dev: pointer to the pcie_dev data structure
+ *
+ * Invoked during system resume.
+ */
+static int aer_resume(struct pcie_device *dev)
+{
+ struct aer_rpc *rpc = get_service_data(dev);
+
+ spin_lock_irq(&rpc->e_lock);
+ rpc->suspended = false;
+ spin_unlock_irq(&rpc->e_lock);
+ return 0;
+}
+
+/**
* aer_remove - clean up resources
* @dev: pointer to the pcie_dev data structure
*
@@ -320,7 +357,8 @@ static int aer_probe(struct pcie_device
}
/* Request IRQ ISR */
- status = request_irq(dev->irq, aer_irq, IRQF_SHARED, "aerdrv", dev);
+ status = request_irq(dev->irq, aer_irq, IRQF_SHARED | IRQF_NO_SUSPEND,
+ "aerdrv", dev);
if (status) {
dev_printk(KERN_DEBUG, device, "request IRQ failed\n");
aer_remove(dev);
===================================================================
@@ -73,6 +73,7 @@ struct aer_rpc {
* root port hierarchy
*/
wait_queue_head_t wait_release;
+ bool suspended;
};
struct aer_broadcast_data {