Skip to content

Commit df17e62

Browse files
Matthew Garrettjbarnes993
authored andcommitted
PCI: Add support for polling PME state on suspended legacy PCI devices
Not all hardware vendors hook up the PME line for legacy PCI devices, meaning that wakeup events get lost. The only way around this is to poll the devices to see if their state has changed, so add support for doing that on legacy PCI devices that aren't part of the core chipset. Acked-by: Rafael J. Wysocki <[email protected]> Signed-off-by: Matthew Garrett <[email protected]> Signed-off-by: Jesse Barnes <[email protected]>
1 parent bf4d290 commit df17e62

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed

drivers/pci/pci.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ EXPORT_SYMBOL(pci_pci_problems);
3838

3939
unsigned int pci_pm_d3_delay;
4040

41+
static void pci_pme_list_scan(struct work_struct *work);
42+
43+
static LIST_HEAD(pci_pme_list);
44+
static DEFINE_MUTEX(pci_pme_list_mutex);
45+
static DECLARE_DELAYED_WORK(pci_pme_work, pci_pme_list_scan);
46+
47+
struct pci_pme_device {
48+
struct list_head list;
49+
struct pci_dev *dev;
50+
};
51+
52+
#define PME_TIMEOUT 1000 /* How long between PME checks */
53+
4154
static void pci_dev_d3_sleep(struct pci_dev *dev)
4255
{
4356
unsigned int delay = dev->d3_delay;
@@ -1331,6 +1344,32 @@ bool pci_pme_capable(struct pci_dev *dev, pci_power_t state)
13311344
return !!(dev->pme_support & (1 << state));
13321345
}
13331346

1347+
static void pci_pme_list_scan(struct work_struct *work)
1348+
{
1349+
struct pci_pme_device *pme_dev;
1350+
1351+
mutex_lock(&pci_pme_list_mutex);
1352+
if (!list_empty(&pci_pme_list)) {
1353+
list_for_each_entry(pme_dev, &pci_pme_list, list)
1354+
pci_pme_wakeup(pme_dev->dev, NULL);
1355+
schedule_delayed_work(&pci_pme_work, msecs_to_jiffies(PME_TIMEOUT));
1356+
}
1357+
mutex_unlock(&pci_pme_list_mutex);
1358+
}
1359+
1360+
/**
1361+
* pci_external_pme - is a device an external PCI PME source?
1362+
* @dev: PCI device to check
1363+
*
1364+
*/
1365+
1366+
static bool pci_external_pme(struct pci_dev *dev)
1367+
{
1368+
if (pci_is_pcie(dev) || dev->bus->number == 0)
1369+
return false;
1370+
return true;
1371+
}
1372+
13341373
/**
13351374
* pci_pme_active - enable or disable PCI device's PME# function
13361375
* @dev: PCI device to handle.
@@ -1354,6 +1393,44 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
13541393

13551394
pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr);
13561395

1396+
/* PCI (as opposed to PCIe) PME requires that the device have
1397+
its PME# line hooked up correctly. Not all hardware vendors
1398+
do this, so the PME never gets delivered and the device
1399+
remains asleep. The easiest way around this is to
1400+
periodically walk the list of suspended devices and check
1401+
whether any have their PME flag set. The assumption is that
1402+
we'll wake up often enough anyway that this won't be a huge
1403+
hit, and the power savings from the devices will still be a
1404+
win. */
1405+
1406+
if (pci_external_pme(dev)) {
1407+
struct pci_pme_device *pme_dev;
1408+
if (enable) {
1409+
pme_dev = kmalloc(sizeof(struct pci_pme_device),
1410+
GFP_KERNEL);
1411+
if (!pme_dev)
1412+
goto out;
1413+
pme_dev->dev = dev;
1414+
mutex_lock(&pci_pme_list_mutex);
1415+
list_add(&pme_dev->list, &pci_pme_list);
1416+
if (list_is_singular(&pci_pme_list))
1417+
schedule_delayed_work(&pci_pme_work,
1418+
msecs_to_jiffies(PME_TIMEOUT));
1419+
mutex_unlock(&pci_pme_list_mutex);
1420+
} else {
1421+
mutex_lock(&pci_pme_list_mutex);
1422+
list_for_each_entry(pme_dev, &pci_pme_list, list) {
1423+
if (pme_dev->dev == dev) {
1424+
list_del(&pme_dev->list);
1425+
kfree(pme_dev);
1426+
break;
1427+
}
1428+
}
1429+
mutex_unlock(&pci_pme_list_mutex);
1430+
}
1431+
}
1432+
1433+
out:
13571434
dev_printk(KERN_DEBUG, &dev->dev, "PME# %s\n",
13581435
enable ? "enabled" : "disabled");
13591436
}

0 commit comments

Comments
 (0)