diff options
Diffstat (limited to 'drivers/pci/pci-driver.c')
-rw-r--r-- | drivers/pci/pci-driver.c | 420 |
1 files changed, 255 insertions, 165 deletions
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 99d867bcf22a..c697f2680856 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -16,6 +16,7 @@ #include <linux/string.h> #include <linux/slab.h> #include <linux/sched.h> +#include <linux/cpu.h> #include "pci.h" /* @@ -48,7 +49,7 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count) subdevice=PCI_ANY_ID, class=0, class_mask=0; unsigned long driver_data=0; int fields=0; - int retval; + int retval=0; fields = sscanf(buf, "%x %x %x %x %x %x %lx", &vendor, &device, &subvendor, &subdevice, @@ -58,16 +59,18 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count) /* Only accept driver_data values that match an existing id_table entry */ - retval = -EINVAL; - while (ids->vendor || ids->subvendor || ids->class_mask) { - if (driver_data == ids->driver_data) { - retval = 0; - break; + if (ids) { + retval = -EINVAL; + while (ids->vendor || ids->subvendor || ids->class_mask) { + if (driver_data == ids->driver_data) { + retval = 0; + break; + } + ids++; } - ids++; + if (retval) /* No match */ + return retval; } - if (retval) /* No match */ - return retval; dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); if (!dynid) @@ -183,32 +186,43 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv, return pci_match_id(drv->id_table, dev); } +struct drv_dev_and_id { + struct pci_driver *drv; + struct pci_dev *dev; + const struct pci_device_id *id; +}; + +static long local_pci_probe(void *_ddi) +{ + struct drv_dev_and_id *ddi = _ddi; + + return ddi->drv->probe(ddi->dev, ddi->id); +} + static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev, const struct pci_device_id *id) { - int error; -#ifdef CONFIG_NUMA - /* Execute driver initialization on node where the - device's bus is attached to. This way the driver likely - allocates its local memory on the right node without - any need to change it. */ - struct mempolicy *oldpol; - cpumask_t oldmask = current->cpus_allowed; - int node = dev_to_node(&dev->dev); + int error, node; + struct drv_dev_and_id ddi = { drv, dev, id }; + /* Execute driver initialization on node where the device's + bus is attached to. This way the driver likely allocates + its local memory on the right node without any need to + change it. */ + node = dev_to_node(&dev->dev); if (node >= 0) { + int cpu; node_to_cpumask_ptr(nodecpumask, node); - set_cpus_allowed_ptr(current, nodecpumask); - } - /* And set default memory allocation policy */ - oldpol = current->mempolicy; - current->mempolicy = NULL; /* fall back to system default policy */ -#endif - error = drv->probe(dev, id); -#ifdef CONFIG_NUMA - set_cpus_allowed_ptr(current, &oldmask); - current->mempolicy = oldpol; -#endif + + get_online_cpus(); + cpu = cpumask_any_and(nodecpumask, cpu_online_mask); + if (cpu < nr_cpu_ids) + error = work_on_cpu(cpu, local_pci_probe, &ddi); + else + error = local_pci_probe(&ddi); + put_online_cpus(); + } else + error = local_pci_probe(&ddi); return error; } @@ -300,21 +314,12 @@ static void pci_device_shutdown(struct device *dev) #ifdef CONFIG_PM_SLEEP -static bool pci_has_legacy_pm_support(struct pci_dev *pci_dev) -{ - struct pci_driver *drv = pci_dev->driver; - - return drv && (drv->suspend || drv->suspend_late || drv->resume - || drv->resume_early); -} - /* * Default "suspend" method for devices that have no driver provided suspend, - * or not even a driver at all. + * or not even a driver at all (second part). */ -static void pci_default_pm_suspend(struct pci_dev *pci_dev) +static void pci_pm_set_unknown_state(struct pci_dev *pci_dev) { - pci_save_state(pci_dev); /* * mark its power state as "unknown", since we don't know if * e.g. the BIOS will change its device state when we suspend. @@ -325,19 +330,9 @@ static void pci_default_pm_suspend(struct pci_dev *pci_dev) /* * Default "resume" method for devices that have no driver provided resume, - * or not even a driver at all (first part). - */ -static void pci_default_pm_resume_early(struct pci_dev *pci_dev) -{ - /* restore the PCI config space */ - pci_restore_state(pci_dev); -} - -/* - * Default "resume" method for devices that have no driver provided resume, * or not even a driver at all (second part). */ -static int pci_default_pm_resume_late(struct pci_dev *pci_dev) +static int pci_pm_reenable_device(struct pci_dev *pci_dev) { int retval; @@ -363,8 +358,16 @@ static int pci_legacy_suspend(struct device *dev, pm_message_t state) i = drv->suspend(pci_dev, state); suspend_report_result(drv->suspend, i); } else { - pci_default_pm_suspend(pci_dev); + pci_save_state(pci_dev); + /* + * This is for compatibility with existing code with legacy PM + * support. + */ + pci_pm_set_unknown_state(pci_dev); } + + pci_fixup_device(pci_fixup_suspend, pci_dev); + return i; } @@ -381,32 +384,130 @@ static int pci_legacy_suspend_late(struct device *dev, pm_message_t state) return i; } +static int pci_legacy_resume_early(struct device *dev) +{ + int error = 0; + struct pci_dev * pci_dev = to_pci_dev(dev); + struct pci_driver * drv = pci_dev->driver; + + pci_fixup_device(pci_fixup_resume_early, pci_dev); + + if (drv && drv->resume_early) + error = drv->resume_early(pci_dev); + return error; +} + static int pci_legacy_resume(struct device *dev) { int error; struct pci_dev * pci_dev = to_pci_dev(dev); struct pci_driver * drv = pci_dev->driver; + pci_fixup_device(pci_fixup_resume, pci_dev); + if (drv && drv->resume) { error = drv->resume(pci_dev); } else { - pci_default_pm_resume_early(pci_dev); - error = pci_default_pm_resume_late(pci_dev); + /* restore the PCI config space */ + pci_restore_state(pci_dev); + error = pci_pm_reenable_device(pci_dev); } return error; } -static int pci_legacy_resume_early(struct device *dev) +/* Auxiliary functions used by the new power management framework */ + +static int pci_restore_standard_config(struct pci_dev *pci_dev) { + struct pci_dev *parent = pci_dev->bus->self; int error = 0; - struct pci_dev * pci_dev = to_pci_dev(dev); - struct pci_driver * drv = pci_dev->driver; - if (drv && drv->resume_early) - error = drv->resume_early(pci_dev); + /* Check if the device's bus is operational */ + if (!parent || parent->current_state == PCI_D0) { + pci_restore_state(pci_dev); + pci_update_current_state(pci_dev, PCI_D0); + } else { + dev_warn(&pci_dev->dev, "unable to restore config, " + "bridge %s in low power state D%d\n", pci_name(parent), + parent->current_state); + pci_dev->current_state = PCI_UNKNOWN; + error = -EAGAIN; + } + return error; } +static bool pci_is_bridge(struct pci_dev *pci_dev) +{ + return !!(pci_dev->subordinate); +} + +static void pci_pm_default_resume_noirq(struct pci_dev *pci_dev) +{ + if (pci_restore_standard_config(pci_dev)) + pci_fixup_device(pci_fixup_resume_early, pci_dev); +} + +static int pci_pm_default_resume(struct pci_dev *pci_dev) +{ + /* + * pci_restore_standard_config() should have been called once already, + * but it would have failed if the device's parent bridge had not been + * in power state D0 at that time. Check it and try again if necessary. + */ + if (pci_dev->current_state == PCI_UNKNOWN) { + int error = pci_restore_standard_config(pci_dev); + if (error) + return error; + } + + pci_fixup_device(pci_fixup_resume, pci_dev); + + if (!pci_is_bridge(pci_dev)) + pci_enable_wake(pci_dev, PCI_D0, false); + + return pci_pm_reenable_device(pci_dev); +} + +static void pci_pm_default_suspend_generic(struct pci_dev *pci_dev) +{ + /* If device is enabled at this point, disable it */ + pci_disable_enabled_device(pci_dev); + /* + * Save state with interrupts enabled, because in principle the bus the + * device is on may be put into a low power state after this code runs. + */ + pci_save_state(pci_dev); +} + +static void pci_pm_default_suspend(struct pci_dev *pci_dev) +{ + pci_pm_default_suspend_generic(pci_dev); + + if (!pci_is_bridge(pci_dev)) + pci_prepare_to_sleep(pci_dev); + + pci_fixup_device(pci_fixup_suspend, pci_dev); +} + +static bool pci_has_legacy_pm_support(struct pci_dev *pci_dev) +{ + struct pci_driver *drv = pci_dev->driver; + bool ret = drv && (drv->suspend || drv->suspend_late || drv->resume + || drv->resume_early); + + /* + * Legacy PM support is used by default, so warn if the new framework is + * supported as well. Drivers are supposed to support either the + * former, or the latter, but not both at the same time. + */ + WARN_ON(ret && drv->driver.pm); + + return ret; +} + +/* New power management framework */ + static int pci_pm_prepare(struct device *dev) { struct device_driver *drv = dev->driver; @@ -434,15 +535,16 @@ static int pci_pm_suspend(struct device *dev) struct device_driver *drv = dev->driver; int error = 0; - if (drv && drv->pm) { - if (drv->pm->suspend) { - error = drv->pm->suspend(dev); - suspend_report_result(drv->pm->suspend, error); - } - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_suspend(dev, PMSG_SUSPEND); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_suspend(dev, PMSG_SUSPEND); + + if (drv && drv->pm && drv->pm->suspend) { + error = drv->pm->suspend(dev); + suspend_report_result(drv->pm->suspend, error); } - pci_fixup_device(pci_fixup_suspend, pci_dev); + + if (!error) + pci_pm_default_suspend(pci_dev); return error; } @@ -453,56 +555,50 @@ static int pci_pm_suspend_noirq(struct device *dev) struct device_driver *drv = dev->driver; int error = 0; - if (drv && drv->pm) { - if (drv->pm->suspend_noirq) { - error = drv->pm->suspend_noirq(dev); - suspend_report_result(drv->pm->suspend_noirq, error); - } - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_suspend_late(dev, PMSG_SUSPEND); - } else { - pci_default_pm_suspend(pci_dev); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_suspend_late(dev, PMSG_SUSPEND); + + if (drv && drv->pm && drv->pm->suspend_noirq) { + error = drv->pm->suspend_noirq(dev); + suspend_report_result(drv->pm->suspend_noirq, error); } + if (!error) + pci_pm_set_unknown_state(pci_dev); + return error; } -static int pci_pm_resume(struct device *dev) +static int pci_pm_resume_noirq(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct device_driver *drv = dev->driver; int error = 0; - pci_fixup_device(pci_fixup_resume, pci_dev); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_resume_early(dev); - if (drv && drv->pm) { - if (drv->pm->resume) - error = drv->pm->resume(dev); - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_resume(dev); - } else { - error = pci_default_pm_resume_late(pci_dev); - } + pci_pm_default_resume_noirq(pci_dev); + + if (drv && drv->pm && drv->pm->resume_noirq) + error = drv->pm->resume_noirq(dev); return error; } -static int pci_pm_resume_noirq(struct device *dev) +static int pci_pm_resume(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct device_driver *drv = dev->driver; int error = 0; - pci_fixup_device(pci_fixup_resume_early, to_pci_dev(dev)); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_resume(dev); - if (drv && drv->pm) { - if (drv->pm->resume_noirq) - error = drv->pm->resume_noirq(dev); - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_resume_early(dev); - } else { - pci_default_pm_resume_early(pci_dev); - } + error = pci_pm_default_resume(pci_dev); + + if (!error && drv && drv->pm && drv->pm->resume) + error = drv->pm->resume(dev); return error; } @@ -524,16 +620,17 @@ static int pci_pm_freeze(struct device *dev) struct device_driver *drv = dev->driver; int error = 0; - if (drv && drv->pm) { - if (drv->pm->freeze) { - error = drv->pm->freeze(dev); - suspend_report_result(drv->pm->freeze, error); - } - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_suspend(dev, PMSG_FREEZE); - pci_fixup_device(pci_fixup_suspend, pci_dev); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_suspend(dev, PMSG_FREEZE); + + if (drv && drv->pm && drv->pm->freeze) { + error = drv->pm->freeze(dev); + suspend_report_result(drv->pm->freeze, error); } + if (!error) + pci_pm_default_suspend_generic(pci_dev); + return error; } @@ -543,50 +640,50 @@ static int pci_pm_freeze_noirq(struct device *dev) struct device_driver *drv = dev->driver; int error = 0; - if (drv && drv->pm) { - if (drv->pm->freeze_noirq) { - error = drv->pm->freeze_noirq(dev); - suspend_report_result(drv->pm->freeze_noirq, error); - } - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_suspend_late(dev, PMSG_FREEZE); - } else { - pci_default_pm_suspend(pci_dev); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_suspend_late(dev, PMSG_FREEZE); + + if (drv && drv->pm && drv->pm->freeze_noirq) { + error = drv->pm->freeze_noirq(dev); + suspend_report_result(drv->pm->freeze_noirq, error); } + if (!error) + pci_pm_set_unknown_state(pci_dev); + return error; } -static int pci_pm_thaw(struct device *dev) +static int pci_pm_thaw_noirq(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct device_driver *drv = dev->driver; int error = 0; - if (drv && drv->pm) { - if (drv->pm->thaw) - error = drv->pm->thaw(dev); - } else if (pci_has_legacy_pm_support(pci_dev)) { - pci_fixup_device(pci_fixup_resume, pci_dev); - error = pci_legacy_resume(dev); - } + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_resume_early(dev); + + pci_update_current_state(pci_dev, PCI_D0); + + if (drv && drv->pm && drv->pm->thaw_noirq) + error = drv->pm->thaw_noirq(dev); return error; } -static int pci_pm_thaw_noirq(struct device *dev) +static int pci_pm_thaw(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct device_driver *drv = dev->driver; int error = 0; - if (drv && drv->pm) { - if (drv->pm->thaw_noirq) - error = drv->pm->thaw_noirq(dev); - } else if (pci_has_legacy_pm_support(pci_dev)) { - pci_fixup_device(pci_fixup_resume_early, to_pci_dev(dev)); - error = pci_legacy_resume_early(dev); - } + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_resume(dev); + + pci_pm_reenable_device(pci_dev); + + if (drv && drv->pm && drv->pm->thaw) + error = drv->pm->thaw(dev); return error; } @@ -597,17 +694,17 @@ static int pci_pm_poweroff(struct device *dev) struct device_driver *drv = dev->driver; int error = 0; - pci_fixup_device(pci_fixup_suspend, pci_dev); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_suspend(dev, PMSG_HIBERNATE); - if (drv && drv->pm) { - if (drv->pm->poweroff) { - error = drv->pm->poweroff(dev); - suspend_report_result(drv->pm->poweroff, error); - } - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_suspend(dev, PMSG_HIBERNATE); + if (drv && drv->pm && drv->pm->poweroff) { + error = drv->pm->poweroff(dev); + suspend_report_result(drv->pm->poweroff, error); } + if (!error) + pci_pm_default_suspend(pci_dev); + return error; } @@ -616,54 +713,47 @@ static int pci_pm_poweroff_noirq(struct device *dev) struct device_driver *drv = dev->driver; int error = 0; - if (drv && drv->pm) { - if (drv->pm->poweroff_noirq) { - error = drv->pm->poweroff_noirq(dev); - suspend_report_result(drv->pm->poweroff_noirq, error); - } - } else if (pci_has_legacy_pm_support(to_pci_dev(dev))) { - error = pci_legacy_suspend_late(dev, PMSG_HIBERNATE); + if (pci_has_legacy_pm_support(to_pci_dev(dev))) + return pci_legacy_suspend_late(dev, PMSG_HIBERNATE); + + if (drv && drv->pm && drv->pm->poweroff_noirq) { + error = drv->pm->poweroff_noirq(dev); + suspend_report_result(drv->pm->poweroff_noirq, error); } return error; } -static int pci_pm_restore(struct device *dev) +static int pci_pm_restore_noirq(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct device_driver *drv = dev->driver; int error = 0; - if (drv && drv->pm) { - if (drv->pm->restore) - error = drv->pm->restore(dev); - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_resume(dev); - } else { - error = pci_default_pm_resume_late(pci_dev); - } - pci_fixup_device(pci_fixup_resume, pci_dev); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_resume_early(dev); + + pci_pm_default_resume_noirq(pci_dev); + + if (drv && drv->pm && drv->pm->restore_noirq) + error = drv->pm->restore_noirq(dev); return error; } -static int pci_pm_restore_noirq(struct device *dev) +static int pci_pm_restore(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct device_driver *drv = dev->driver; int error = 0; - pci_fixup_device(pci_fixup_resume, pci_dev); + if (pci_has_legacy_pm_support(pci_dev)) + return pci_legacy_resume(dev); - if (drv && drv->pm) { - if (drv->pm->restore_noirq) - error = drv->pm->restore_noirq(dev); - } else if (pci_has_legacy_pm_support(pci_dev)) { - error = pci_legacy_resume_early(dev); - } else { - pci_default_pm_resume_early(pci_dev); - } - pci_fixup_device(pci_fixup_resume_early, pci_dev); + error = pci_pm_default_resume(pci_dev); + + if (!error && drv && drv->pm && drv->pm->restore) + error = drv->pm->restore(dev); return error; } |