From 3419c75e15f82c3ab09bd944fddbde72c9e4b3ea Mon Sep 17 00:00:00 2001 From: Alex Chiang Date: Wed, 28 Jan 2009 14:59:18 -0700 Subject: PCI: properly clean up ASPM link state on device remove We only want to disable ASPM when the last function is removed from the parent's device list. We determine this by checking to see if the parent's device list is completely empty. Unfortunately, we never hit that code because the parent is considered an upstream port, and never had an ASPM link_state associated with it. The early check for !link_state causes us to return early, we never discover that our device list is empty, and thus we never remove the downstream ports' link_state nodes. Instead of checking to see if the parent's device list is empty, we can check to see if we are the last device on the list, and if so, then we know that we can clean up properly. Cc: Shaohua Li Signed-off-by: Alex Chiang Signed-off-by: Jesse Barnes --- drivers/pci/pcie/aspm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 586b6f75910d..b0367f168af4 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -718,9 +718,9 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev) /* * All PCIe functions are in one slot, remove one function will remove - * the the whole slot, so just wait + * the whole slot, so just wait until we are the last function left. */ - if (!list_empty(&parent->subordinate->devices)) + if (!list_is_last(&pdev->bus_list, &parent->subordinate->devices)) goto out; /* All functions are removed, so just disable ASPM for the link */ -- cgit v1.2.3 From 97c44836cdec1ea713a15d84098a1a908157e68f Mon Sep 17 00:00:00 2001 From: "Timothy S. Nelson" Date: Fri, 30 Jan 2009 06:12:47 +1100 Subject: PCI: return error on failure to read PCI ROMs This patch makes the ROM reading code return an error to user space if the size of the ROM read is equal to 0. The patch also emits a warnings if the contents of the ROM are invalid, and documents the effects of the "enable" file on ROM reading. Signed-off-by: Timothy S. Nelson Acked-by: Alex Villacis-Lasso Signed-off-by: Jesse Barnes --- Documentation/filesystems/sysfs-pci.txt | 13 ++++++++++++- arch/ia64/sn/kernel/io_acpi_init.c | 2 +- arch/ia64/sn/kernel/io_init.c | 2 +- drivers/pci/pci-sysfs.c | 4 ++-- drivers/pci/rom.c | 8 +++++--- include/linux/pci.h | 2 +- 6 files changed, 22 insertions(+), 9 deletions(-) (limited to 'drivers/pci') diff --git a/Documentation/filesystems/sysfs-pci.txt b/Documentation/filesystems/sysfs-pci.txt index 68ef48839c04..9f8740ca3f3b 100644 --- a/Documentation/filesystems/sysfs-pci.txt +++ b/Documentation/filesystems/sysfs-pci.txt @@ -9,6 +9,7 @@ that support it. For example, a given bus might look like this: | |-- class | |-- config | |-- device + | |-- enable | |-- irq | |-- local_cpus | |-- resource @@ -32,6 +33,7 @@ files, each with their own function. class PCI class (ascii, ro) config PCI config space (binary, rw) device PCI device (ascii, ro) + enable Whether the device is enabled (ascii, rw) irq IRQ number (ascii, ro) local_cpus nearby CPU mask (cpumask, ro) resource PCI resource host addresses (ascii, ro) @@ -57,10 +59,19 @@ used to do actual device programming from userspace. Note that some platforms don't support mmapping of certain resources, so be sure to check the return value from any attempted mmap. +The 'enable' file provides a counter that indicates how many times the device +has been enabled. If the 'enable' file currently returns '4', and a '1' is +echoed into it, it will then return '5'. Echoing a '0' into it will decrease +the count. Even when it returns to 0, though, some of the initialisation +may not be reversed. + The 'rom' file is special in that it provides read-only access to the device's ROM file, if available. It's disabled by default, however, so applications should write the string "1" to the file to enable it before attempting a read -call, and disable it following the access by writing "0" to the file. +call, and disable it following the access by writing "0" to the file. Note +that the device must be enabled for a rom read to return data succesfully. +In the event a driver is not bound to the device, it can be enabled using the +'enable' file, documented above. Accessing legacy resources through sysfs ---------------------------------------- diff --git a/arch/ia64/sn/kernel/io_acpi_init.c b/arch/ia64/sn/kernel/io_acpi_init.c index c5a214026a77..d0223abbbbd4 100644 --- a/arch/ia64/sn/kernel/io_acpi_init.c +++ b/arch/ia64/sn/kernel/io_acpi_init.c @@ -443,7 +443,7 @@ sn_acpi_slot_fixup(struct pci_dev *dev) size = pci_resource_len(dev, PCI_ROM_RESOURCE); addr = ioremap(pcidev_info->pdi_pio_mapped_addr[PCI_ROM_RESOURCE], size); - image_size = pci_get_rom_size(addr, size); + image_size = pci_get_rom_size(dev, addr, size); dev->resource[PCI_ROM_RESOURCE].start = (unsigned long) addr; dev->resource[PCI_ROM_RESOURCE].end = (unsigned long) addr + image_size - 1; diff --git a/arch/ia64/sn/kernel/io_init.c b/arch/ia64/sn/kernel/io_init.c index 4e1801bad83a..e2eb2da60f96 100644 --- a/arch/ia64/sn/kernel/io_init.c +++ b/arch/ia64/sn/kernel/io_init.c @@ -269,7 +269,7 @@ sn_io_slot_fixup(struct pci_dev *dev) rom = ioremap(pci_resource_start(dev, PCI_ROM_RESOURCE), size + 1); - image_size = pci_get_rom_size(rom, size + 1); + image_size = pci_get_rom_size(dev, rom, size + 1); dev->resource[PCI_ROM_RESOURCE].end = dev->resource[PCI_ROM_RESOURCE].start + image_size - 1; diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index db7ec14fa719..dfc4e0ddf241 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -768,8 +768,8 @@ pci_read_rom(struct kobject *kobj, struct bin_attribute *bin_attr, return -EINVAL; rom = pci_map_rom(pdev, &size); /* size starts out as PCI window size */ - if (!rom) - return 0; + if (!rom || !size) + return -EIO; if (off >= size) count = 0; diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c index 132a78159b60..29cbe47f219f 100644 --- a/drivers/pci/rom.c +++ b/drivers/pci/rom.c @@ -63,7 +63,7 @@ void pci_disable_rom(struct pci_dev *pdev) * The PCI window size could be much larger than the * actual image size. */ -size_t pci_get_rom_size(void __iomem *rom, size_t size) +size_t pci_get_rom_size(struct pci_dev *pdev, void __iomem *rom, size_t size) { void __iomem *image; int last_image; @@ -72,8 +72,10 @@ size_t pci_get_rom_size(void __iomem *rom, size_t size) do { void __iomem *pds; /* Standard PCI ROMs start out with these bytes 55 AA */ - if (readb(image) != 0x55) + if (readb(image) != 0x55) { + dev_err(&pdev->dev, "Invalid ROM contents\n"); break; + } if (readb(image + 1) != 0xAA) break; /* get the PCI data structure and check its signature */ @@ -159,7 +161,7 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) * size is much larger than the actual size of the ROM. * True size is important if the ROM is going to be copied. */ - *size = pci_get_rom_size(rom, *size); + *size = pci_get_rom_size(pdev, rom, *size); return rom; } diff --git a/include/linux/pci.h b/include/linux/pci.h index 48890cf3f96e..7bd624bfdcfd 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -684,7 +684,7 @@ int pci_enable_rom(struct pci_dev *pdev); void pci_disable_rom(struct pci_dev *pdev); void __iomem __must_check *pci_map_rom(struct pci_dev *pdev, size_t *size); void pci_unmap_rom(struct pci_dev *pdev, void __iomem *rom); -size_t pci_get_rom_size(void __iomem *rom, size_t size); +size_t pci_get_rom_size(struct pci_dev *pdev, void __iomem *rom, size_t size); /* Power management related routines */ int pci_save_state(struct pci_dev *dev); -- cgit v1.2.3 From ddb7c9d29fac34626aef2af9f19787a888e4ca9c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Feb 2009 01:56:14 +0100 Subject: PCI PM: Fix handling of devices without drivers Suspend to RAM is reported to break on some machines as a result of attempting to put one of driverless PCI devices into a low power state. Avoid that by not attepmting to power manage driverless devices during suspend. Fix up pci_pm_poweroff() after a previous incomplete fix for the same thing during hibernation. This patch is reported to fix the regression from 2.6.28 tracked as http://bugzilla.kernel.org/show_bug.cgi?id=12605 Signed-off-by: Rafael J. Wysocki Reported-and-tested-by: Eric Sesterhenn Acked-by: Linus Torvalds Signed-off-by: Jesse Barnes --- drivers/pci/pci-driver.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index ab1d615425a8..6613bef25bf2 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -445,11 +445,11 @@ static void pci_pm_default_suspend_generic(struct pci_dev *pci_dev) pci_save_state(pci_dev); } -static void pci_pm_default_suspend(struct pci_dev *pci_dev) +static void pci_pm_default_suspend(struct pci_dev *pci_dev, bool prepare) { pci_pm_default_suspend_generic(pci_dev); - if (!pci_is_bridge(pci_dev)) + if (prepare && !pci_is_bridge(pci_dev)) pci_prepare_to_sleep(pci_dev); pci_fixup_device(pci_fixup_suspend, pci_dev); @@ -497,19 +497,19 @@ static void pci_pm_complete(struct device *dev) static int pci_pm_suspend(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); - struct device_driver *drv = dev->driver; + struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; int error = 0; 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); + if (pm && pm->suspend) { + error = pm->suspend(dev); + suspend_report_result(pm->suspend, error); } if (!error) - pci_pm_default_suspend(pci_dev); + pci_pm_default_suspend(pci_dev, !!pm); return error; } @@ -663,22 +663,19 @@ static int pci_pm_thaw(struct device *dev) static int pci_pm_poweroff(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); - struct device_driver *drv = dev->driver; + struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; int error = 0; if (pci_has_legacy_pm_support(pci_dev)) return pci_legacy_suspend(dev, PMSG_HIBERNATE); - if (!drv || !drv->pm) - return 0; - - if (drv->pm->poweroff) { - error = drv->pm->poweroff(dev); - suspend_report_result(drv->pm->poweroff, error); + if (pm && pm->poweroff) { + error = pm->poweroff(dev); + suspend_report_result(pm->poweroff, error); } if (!error) - pci_pm_default_suspend(pci_dev); + pci_pm_default_suspend(pci_dev, !!pm); return error; } -- cgit v1.2.3 From 144a76bc885ef4852601c66595326e59f12877f8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Feb 2009 01:57:22 +0100 Subject: PCI PM: Check if the state has been saved before trying to restore it Check if the standard configuration registers of a PCI device have been saved during suspend before trying to restore them during resume. Signed-off-by: Rafael J. Wysocki Reported-By: Benjamin Herrenschmidt Acked-by: Linus Torvalds Signed-off-by: Jesse Barnes --- drivers/pci/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 48807556b47a..87c904233bf5 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1421,7 +1421,7 @@ int pci_restore_standard_config(struct pci_dev *dev) dev->current_state = PCI_D0; Restore: - return pci_restore_state(dev); + return dev->state_saved ? pci_restore_state(dev) : 0; } /** -- cgit v1.2.3 From 99dadce8756bf08f5f8baf749533d044f6b3ff25 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Feb 2009 01:59:09 +0100 Subject: PCI PM: Fix saving of device state in pci_legacy_suspend Make pci_legacy_suspend() save the state of the device if it is in PCI_UNKNOWN after its suspend callback has run and warn only if the power state of the device has been changed by its suspend callback. Also, use WARN_ONCE(), which is more useful, in pci_legacy_suspend(), so that the name of the offending function is printed. Additionally, remove the unnecessary line of code setting pci_dev->state_saved. Signed-off-by: Rafael J. Wysocki Reported-by: Benjamin Herrenschmidt Acked-by: Linus Torvalds Signed-off-by: Jesse Barnes --- drivers/pci/pci-driver.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 6613bef25bf2..fdb6a697e05a 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -355,6 +355,8 @@ static int pci_legacy_suspend(struct device *dev, pm_message_t state) int i = 0; if (drv && drv->suspend) { + pci_power_t prev = pci_dev->current_state; + pci_dev->state_saved = false; i = drv->suspend(pci_dev, state); @@ -365,12 +367,16 @@ static int pci_legacy_suspend(struct device *dev, pm_message_t state) if (pci_dev->state_saved) goto Fixup; - if (WARN_ON_ONCE(pci_dev->current_state != PCI_D0)) + if (pci_dev->current_state != PCI_D0 + && pci_dev->current_state != PCI_UNKNOWN) { + WARN_ONCE(pci_dev->current_state != prev, + "PCI PM: Device state not saved by %pF\n", + drv->suspend); goto Fixup; + } } pci_save_state(pci_dev); - pci_dev->state_saved = true; /* * This is for compatibility with existing code with legacy PM support. */ -- cgit v1.2.3 From 27be54a65c89c4b4aa9b25fc6fba31ffd01a08ca Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Feb 2009 02:00:11 +0100 Subject: PCI: PCIe portdrv: Simplify suspend and resume Simplify suspend and resume of the PCI Express port driver. It no longer needs to save and restore the standard configuration space of the device; this is now done by the PCI PM core layer. This patch is reported to fix the regression tracked as http://bugzilla.kernel.org/show_bug.cgi?id=12598 Signed-off-by: Rafael J. Wysocki Reported-and-tested-by: Parag Warudkar Acked-by: Linus Torvalds Signed-off-by: Jesse Barnes --- drivers/pci/pcie/portdrv_pci.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 99a914a027f8..f9b874eaeb9f 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -55,25 +55,13 @@ static int pcie_portdrv_suspend(struct pci_dev *dev, pm_message_t state) } -static int pcie_portdrv_suspend_late(struct pci_dev *dev, pm_message_t state) -{ - return pci_save_state(dev); -} - -static int pcie_portdrv_resume_early(struct pci_dev *dev) -{ - return pci_restore_state(dev); -} - static int pcie_portdrv_resume(struct pci_dev *dev) { - pcie_portdrv_restore_config(dev); + pci_set_master(dev); return pcie_port_device_resume(dev); } #else #define pcie_portdrv_suspend NULL -#define pcie_portdrv_suspend_late NULL -#define pcie_portdrv_resume_early NULL #define pcie_portdrv_resume NULL #endif @@ -292,8 +280,6 @@ static struct pci_driver pcie_portdriver = { .remove = pcie_portdrv_remove, .suspend = pcie_portdrv_suspend, - .suspend_late = pcie_portdrv_suspend_late, - .resume_early = pcie_portdrv_resume_early, .resume = pcie_portdrv_resume, .err_handler = &pcie_portdrv_err_handler, -- cgit v1.2.3 From cbbc2f6b0d438f80831c20124137ea92f0e5149b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Feb 2009 02:01:15 +0100 Subject: PCI PM: Do not disable and enable bridges during suspend-resume It is a mistake to disable and enable PCI bridges and PCI Express ports during suspend-resume, at least at the time when it is currently done. Disabling them may lead to problems with accessing devices behind them and they should be automatically enabled when their standard config spaces are restored. Fix this by not attempting to disable bridges during suspend and enable them during resume. Signed-off-by: Rafael J. Wysocki Acked-by: Linus Torvalds Signed-off-by: Jesse Barnes --- drivers/pci/pci-driver.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index fdb6a697e05a..ac6c9e493f4c 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -434,16 +434,18 @@ static int pci_pm_default_resume(struct pci_dev *pci_dev) { pci_fixup_device(pci_fixup_resume, pci_dev); - if (!pci_is_bridge(pci_dev)) - pci_enable_wake(pci_dev, PCI_D0, false); + if (pci_is_bridge(pci_dev)) + return 0; + 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); + /* If a non-bridge device is enabled at this point, disable it */ + if (!pci_is_bridge(pci_dev)) + 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. -- cgit v1.2.3 From 49c968111aee4a463d3247937b63efa63a65f378 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Feb 2009 02:02:15 +0100 Subject: PCI PM: Read power state from device after trying to change it on resume pci_restore_standard_config() unconditionally changes current_state to PCI_D0 after attempting to change the device's power state, but it should rather read the actual current power state from the device. Signed-off-by: Rafael J. Wysocki Acked-by: Linus Torvalds Signed-off-by: Jesse Barnes --- drivers/pci/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 87c904233bf5..e3efe6b19ee7 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1418,7 +1418,7 @@ int pci_restore_standard_config(struct pci_dev *dev) break; } - dev->current_state = PCI_D0; + pci_update_current_state(dev, PCI_D0); Restore: return dev->state_saved ? pci_restore_state(dev) : 0; -- cgit v1.2.3 From 5294e256717923f4a3297bb8b802f5e0625763f3 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Feb 2009 02:09:07 +0100 Subject: PCI PM: make the PM core more careful with drivers using the new PM framework Currently, the PM core always attempts to manage devices with drivers that use the new PM framework. In particular, it attempts to disable the devices (which is unnecessary), to save their state (which may be undesirable if the driver has done that already) and to put them into low power states (again, this may be undesirable if the driver has already put the device into a low power state). That need not be the right thing to do, so make the core be more careful in this respect. Generally, there are the following categories of devices to consider: * bridge devices without drivers * non-bridge devices without drivers * bridge devices with drivers * non-bridge devices with drivers and each of them should be handled differently. For bridge devices without drivers the PCI PM core will save their state on suspend and restore it (early) during resume, after putting them into D0 if necessary. It will not attempt to do anything else to these devices. For non-bridge devices without drivers the PCI PM core will disable them and save their state on suspend. During resume, it will put them into D0, if necessary, restore their state (early) and reenable them. For bridge devices with drivers the PCI PM core will only save their state on suspend if the driver hasn't done that already. Still, the core will restore their state (early) during resume, after putting them into D0, if necessary. For non-bridge devices with drivers the PCI PM core will only save their state on suspend if the driver hasn't done that already. Also, if the state of the device hasn't been saved by the driver, the core will attempt to put the device into a low power state. During resume the core will restore the state of the device (early), after putting it into D0, if necessary. Signed-off-by: Rafael J. Wysocki Acked-by: Linus Torvalds Signed-off-by: Jesse Barnes --- drivers/pci/pci-driver.c | 145 ++++++++++++++++++++++++++++++----------------- 1 file changed, 93 insertions(+), 52 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index ac6c9e493f4c..93eac1423585 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -430,39 +430,22 @@ static void pci_pm_default_resume_noirq(struct pci_dev *pci_dev) pci_fixup_device(pci_fixup_resume_early, pci_dev); } -static int pci_pm_default_resume(struct pci_dev *pci_dev) +static void pci_pm_default_resume(struct pci_dev *pci_dev) { pci_fixup_device(pci_fixup_resume, pci_dev); - if (pci_is_bridge(pci_dev)) - return 0; - - pci_enable_wake(pci_dev, PCI_D0, false); - return pci_pm_reenable_device(pci_dev); + if (!pci_is_bridge(pci_dev)) + pci_enable_wake(pci_dev, PCI_D0, false); } -static void pci_pm_default_suspend_generic(struct pci_dev *pci_dev) +static void pci_pm_default_suspend(struct pci_dev *pci_dev) { - /* If a non-bridge device is enabled at this point, disable it */ + /* Disable non-bridge devices without PM support */ if (!pci_is_bridge(pci_dev)) 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, bool prepare) -{ - pci_pm_default_suspend_generic(pci_dev); - - if (prepare && !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; @@ -506,20 +489,48 @@ static int pci_pm_suspend(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; - int error = 0; if (pci_has_legacy_pm_support(pci_dev)) return pci_legacy_suspend(dev, PMSG_SUSPEND); - if (pm && pm->suspend) { + if (!pm) { + pci_pm_default_suspend(pci_dev); + goto Fixup; + } + + pci_dev->state_saved = false; + + if (pm->suspend) { + pci_power_t prev = pci_dev->current_state; + int error; + error = pm->suspend(dev); suspend_report_result(pm->suspend, error); + if (error) + return error; + + if (pci_dev->state_saved) + goto Fixup; + + if (pci_dev->current_state != PCI_D0 + && pci_dev->current_state != PCI_UNKNOWN) { + WARN_ONCE(pci_dev->current_state != prev, + "PCI PM: State of device not saved by %pF\n", + pm->suspend); + goto Fixup; + } } - if (!error) - pci_pm_default_suspend(pci_dev, !!pm); + if (!pci_dev->state_saved) { + pci_save_state(pci_dev); + if (!pci_is_bridge(pci_dev)) + pci_prepare_to_sleep(pci_dev); + } - return error; + Fixup: + pci_fixup_device(pci_fixup_suspend, pci_dev); + + return 0; } static int pci_pm_suspend_noirq(struct device *dev) @@ -562,7 +573,7 @@ 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; + struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; int error = 0; /* @@ -575,12 +586,16 @@ static int pci_pm_resume(struct device *dev) if (pci_has_legacy_pm_support(pci_dev)) return pci_legacy_resume(dev); - error = pci_pm_default_resume(pci_dev); + pci_pm_default_resume(pci_dev); - if (!error && drv && drv->pm && drv->pm->resume) - error = drv->pm->resume(dev); + if (pm) { + if (pm->resume) + error = pm->resume(dev); + } else { + pci_pm_reenable_device(pci_dev); + } - return error; + return 0; } #else /* !CONFIG_SUSPEND */ @@ -597,21 +612,31 @@ static int pci_pm_resume(struct device *dev) static int pci_pm_freeze(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); - struct device_driver *drv = dev->driver; - int error = 0; + struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 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 (!pm) { + pci_pm_default_suspend(pci_dev); + return 0; } - if (!error) - pci_pm_default_suspend_generic(pci_dev); + pci_dev->state_saved = false; - return error; + if (pm->freeze) { + int error; + + error = pm->freeze(dev); + suspend_report_result(pm->freeze, error); + if (error) + return error; + } + + if (!pci_dev->state_saved) + pci_save_state(pci_dev); + + return 0; } static int pci_pm_freeze_noirq(struct device *dev) @@ -654,16 +679,18 @@ 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; + struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; int error = 0; 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); + if (pm) { + if (pm->thaw) + error = pm->thaw(dev); + } else { + pci_pm_reenable_device(pci_dev); + } return error; } @@ -677,13 +704,23 @@ static int pci_pm_poweroff(struct device *dev) if (pci_has_legacy_pm_support(pci_dev)) return pci_legacy_suspend(dev, PMSG_HIBERNATE); - if (pm && pm->poweroff) { + if (!pm) { + pci_pm_default_suspend(pci_dev); + goto Fixup; + } + + pci_dev->state_saved = false; + + if (pm->poweroff) { error = pm->poweroff(dev); suspend_report_result(pm->poweroff, error); } - if (!error) - pci_pm_default_suspend(pci_dev, !!pm); + if (!pci_dev->state_saved && !pci_is_bridge(pci_dev)) + pci_prepare_to_sleep(pci_dev); + + Fixup: + pci_fixup_device(pci_fixup_suspend, pci_dev); return error; } @@ -724,7 +761,7 @@ 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; + struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; int error = 0; /* @@ -737,10 +774,14 @@ static int pci_pm_restore(struct device *dev) if (pci_has_legacy_pm_support(pci_dev)) return pci_legacy_resume(dev); - error = pci_pm_default_resume(pci_dev); + pci_pm_default_resume(pci_dev); - if (!error && drv && drv->pm && drv->pm->restore) - error = drv->pm->restore(dev); + if (pm) { + if (pm->restore) + error = pm->restore(dev); + } else { + pci_pm_reenable_device(pci_dev); + } return error; } -- cgit v1.2.3