From 023d3779145ec6b7a0f38f19672a347b92feb74e Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 31 Jan 2011 11:06:39 +0100 Subject: PM / Wakeup: Combine atomic counters to avoid reordering issues The memory barrier in wakeup_source_deactivate() is supposed to prevent the callers of pm_wakeup_pending() and pm_get_wakeup_count() from seeing the new value of events_in_progress (0, in particular) and the old value of event_count at the same time. However, if wakeup_source_deactivate() is executed by CPU0 and, for instance, pm_wakeup_pending() is executed by CPU1, where both processors can reorder operations, the memory barrier in wakeup_source_deactivate() doesn't affect CPU1 which can reorder reads. In that case CPU1 may very well decide to fetch event_count before it's modified and events_in_progress after it's been updated, so pm_wakeup_pending() may fail to detect a wakeup event. This issue can be addressed by using a single atomic variable to store both events_in_progress and event_count, so that they can be updated together in a single atomic operation. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/wakeup.c | 61 +++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 8ec406d..e5e73b5 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -24,12 +24,26 @@ */ bool events_check_enabled; -/* The counter of registered wakeup events. */ -static atomic_t event_count = ATOMIC_INIT(0); -/* A preserved old value of event_count. */ +/* + * Combined counters of registered wakeup events and wakeup events in progress. + * They need to be modified together atomically, so it's better to use one + * atomic variable to hold them both. + */ +static atomic_t combined_event_count = ATOMIC_INIT(0); + +#define IN_PROGRESS_BITS (sizeof(int) * 4) +#define MAX_IN_PROGRESS ((1 << IN_PROGRESS_BITS) - 1) + +static void split_counters(unsigned int *cnt, unsigned int *inpr) +{ + unsigned int comb = atomic_read(&combined_event_count); + + *cnt = (comb >> IN_PROGRESS_BITS); + *inpr = comb & MAX_IN_PROGRESS; +} + +/* A preserved old value of the events counter. */ static unsigned int saved_count; -/* The counter of wakeup events being processed. */ -static atomic_t events_in_progress = ATOMIC_INIT(0); static DEFINE_SPINLOCK(events_lock); @@ -307,7 +321,8 @@ static void wakeup_source_activate(struct wakeup_source *ws) ws->timer_expires = jiffies; ws->last_time = ktime_get(); - atomic_inc(&events_in_progress); + /* Increment the counter of events in progress. */ + atomic_inc(&combined_event_count); } /** @@ -394,14 +409,10 @@ static void wakeup_source_deactivate(struct wakeup_source *ws) del_timer(&ws->timer); /* - * event_count has to be incremented before events_in_progress is - * modified, so that the callers of pm_check_wakeup_events() and - * pm_save_wakeup_count() don't see the old value of event_count and - * events_in_progress equal to zero at the same time. + * Increment the counter of registered wakeup events and decrement the + * couter of wakeup events in progress simultaneously. */ - atomic_inc(&event_count); - smp_mb__before_atomic_dec(); - atomic_dec(&events_in_progress); + atomic_add(MAX_IN_PROGRESS, &combined_event_count); } /** @@ -556,8 +567,10 @@ bool pm_wakeup_pending(void) spin_lock_irqsave(&events_lock, flags); if (events_check_enabled) { - ret = ((unsigned int)atomic_read(&event_count) != saved_count) - || atomic_read(&events_in_progress); + unsigned int cnt, inpr; + + split_counters(&cnt, &inpr); + ret = (cnt != saved_count || inpr > 0); events_check_enabled = !ret; } spin_unlock_irqrestore(&events_lock, flags); @@ -579,19 +592,22 @@ bool pm_wakeup_pending(void) */ bool pm_get_wakeup_count(unsigned int *count) { - bool ret; + unsigned int cnt, inpr; if (capable(CAP_SYS_ADMIN)) events_check_enabled = false; - while (atomic_read(&events_in_progress) && !signal_pending(current)) { + for (;;) { + split_counters(&cnt, &inpr); + if (inpr == 0 || signal_pending(current)) + break; pm_wakeup_update_hit_counts(); schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT)); } - ret = !atomic_read(&events_in_progress); - *count = atomic_read(&event_count); - return ret; + split_counters(&cnt, &inpr); + *count = cnt; + return !inpr; } /** @@ -605,11 +621,12 @@ bool pm_get_wakeup_count(unsigned int *count) */ bool pm_save_wakeup_count(unsigned int count) { + unsigned int cnt, inpr; bool ret = false; spin_lock_irq(&events_lock); - if (count == (unsigned int)atomic_read(&event_count) - && !atomic_read(&events_in_progress)) { + split_counters(&cnt, &inpr); + if (cnt == count && inpr == 0) { saved_count = count; events_check_enabled = true; ret = true; -- cgit v1.1 From 378eef99ad45700aabfba2bd962516e5608b259a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 31 Jan 2011 11:06:50 +0100 Subject: PM / Wakeup: Make pm_save_wakeup_count() work as documented According to Documentation/ABI/testing/sysfs-power, the /sys/power/wakeup_count interface should only make the kernel react to wakeup events during suspend if the last write to it has been successful. However, if /sys/power/wakeup_count is written to two times in a row, where the first write is successful and the second is not, the kernel will still react to wakeup events during suspend due to a bug in pm_save_wakeup_count(). Fix the bug by making pm_save_wakeup_count() clear events_check_enabled unconditionally before checking if there are any new wakeup events registered since the previous read from /sys/power/wakeup_count. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/wakeup.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index e5e73b5..07e08c3 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -616,25 +616,25 @@ bool pm_get_wakeup_count(unsigned int *count) * * If @count is equal to the current number of registered wakeup events and the * current number of wakeup events being processed is zero, store @count as the - * old number of registered wakeup events to be used by pm_check_wakeup_events() - * and return true. Otherwise return false. + * old number of registered wakeup events for pm_check_wakeup_events(), enable + * wakeup events detection and return 'true'. Otherwise disable wakeup events + * detection and return 'false'. */ bool pm_save_wakeup_count(unsigned int count) { unsigned int cnt, inpr; - bool ret = false; + events_check_enabled = false; spin_lock_irq(&events_lock); split_counters(&cnt, &inpr); if (cnt == count && inpr == 0) { saved_count = count; events_check_enabled = true; - ret = true; } spin_unlock_irq(&events_lock); - if (!ret) + if (!events_check_enabled) pm_wakeup_update_hit_counts(); - return ret; + return events_check_enabled; } static struct dentry *wakeup_sources_stats_dentry; -- cgit v1.1 From 790c7885a4b2105e41f7a80b035fdb78e406154f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 31 Jan 2011 11:07:01 +0100 Subject: PM / Wakeup: Don't update events_check_enabled in pm_get_wakeup_count() Since pm_save_wakeup_count() has just been changed to clear events_check_enabled unconditionally before checking if there are any new wakeup events registered since the last read from /sys/power/wakeup_count, the detection of wakeup events during suspend may be disabled, after it's been enabled, by writing a "wrong" value back to /sys/power/wakeup_count. For this reason, it is not necessary to update events_check_enabled in pm_get_wakeup_count() any more. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/wakeup.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 07e08c3..8283638 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -586,17 +586,14 @@ bool pm_wakeup_pending(void) * Store the number of registered wakeup events at the address in @count. Block * if the current number of wakeup events being processed is nonzero. * - * Return false if the wait for the number of wakeup events being processed to + * Return 'false' if the wait for the number of wakeup events being processed to * drop down to zero has been interrupted by a signal (and the current number - * of wakeup events being processed is still nonzero). Otherwise return true. + * of wakeup events being processed is still nonzero). Otherwise return 'true'. */ bool pm_get_wakeup_count(unsigned int *count) { unsigned int cnt, inpr; - if (capable(CAP_SYS_ADMIN)) - events_check_enabled = false; - for (;;) { split_counters(&cnt, &inpr); if (inpr == 0 || signal_pending(current)) -- cgit v1.1 From 0295a34d61f14522fddb26856191520d2e1d7e77 Mon Sep 17 00:00:00 2001 From: Mandeep Singh Baines Date: Mon, 31 Jan 2011 11:07:14 +0100 Subject: PM: Use appropriate printk() priority level in trace.c printk()s without a priority level default to KERN_WARNING. To reduce noise at KERN_WARNING, this patch sets the priority level appriopriately for unleveled printks()s. This should be useful to folks that look at dmesg warnings closely. Changed these messages to pr_info(). Signed-off-by: Mandeep Singh Baines Signed-off-by: Rafael J. Wysocki --- drivers/base/power/trace.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/trace.c b/drivers/base/power/trace.c index 9f4258d..c80e138 100644 --- a/drivers/base/power/trace.c +++ b/drivers/base/power/trace.c @@ -112,7 +112,7 @@ static unsigned int read_magic_time(void) unsigned int val; get_rtc_time(&time); - printk("Time: %2d:%02d:%02d Date: %02d/%02d/%02d\n", + pr_info("Time: %2d:%02d:%02d Date: %02d/%02d/%02d\n", time.tm_hour, time.tm_min, time.tm_sec, time.tm_mon + 1, time.tm_mday, time.tm_year % 100); val = time.tm_year; /* 100 years */ @@ -179,7 +179,7 @@ static int show_file_hash(unsigned int value) unsigned int hash = hash_string(lineno, file, FILEHASH); if (hash != value) continue; - printk(" hash matches %s:%u\n", file, lineno); + pr_info(" hash matches %s:%u\n", file, lineno); match++; } return match; @@ -255,7 +255,7 @@ static int late_resume_init(void) val = val / FILEHASH; dev = val /* % DEVHASH */; - printk(" Magic number: %d:%d:%d\n", user, file, dev); + pr_info(" Magic number: %d:%d:%d\n", user, file, dev); show_file_hash(file); show_dev_hash(dev); return 0; -- cgit v1.1 From 4681b17154b3fd81f898802262985f662344e6ed Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:25:48 +0100 Subject: USB / Hub: Do not call device_set_wakeup_capable() under spinlock A subsequent patch will modify device_set_wakeup_capable() in such a way that it will call functions which may sleep and therefore it shouldn't be called under spinlocks. In preparation to that, modify usb_set_device_state() to avoid calling device_set_wakeup_capable() under device_state_lock. Tested-by: Minchan Kim Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 0f299b7..19d3435 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1465,6 +1465,7 @@ void usb_set_device_state(struct usb_device *udev, enum usb_device_state new_state) { unsigned long flags; + int wakeup = -1; spin_lock_irqsave(&device_state_lock, flags); if (udev->state == USB_STATE_NOTATTACHED) @@ -1479,11 +1480,10 @@ void usb_set_device_state(struct usb_device *udev, || new_state == USB_STATE_SUSPENDED) ; /* No change to wakeup settings */ else if (new_state == USB_STATE_CONFIGURED) - device_set_wakeup_capable(&udev->dev, - (udev->actconfig->desc.bmAttributes - & USB_CONFIG_ATT_WAKEUP)); + wakeup = udev->actconfig->desc.bmAttributes + & USB_CONFIG_ATT_WAKEUP; else - device_set_wakeup_capable(&udev->dev, 0); + wakeup = 0; } if (udev->state == USB_STATE_SUSPENDED && new_state != USB_STATE_SUSPENDED) @@ -1495,6 +1495,8 @@ void usb_set_device_state(struct usb_device *udev, } else recursively_mark_NOTATTACHED(udev); spin_unlock_irqrestore(&device_state_lock, flags); + if (wakeup >= 0) + device_set_wakeup_capable(&udev->dev, wakeup); } EXPORT_SYMBOL_GPL(usb_set_device_state); -- cgit v1.1 From cb8f51bdadb7969139c2e39c2defd4cde98c1ea8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:26:02 +0100 Subject: PM: Do not create wakeup sysfs files for devices that cannot wake up Currently, wakeup sysfs attributes are created for all devices, regardless of whether or not they are wakeup-capable. This is excessive and complicates wakeup device identification from user space (i.e. to identify wakeup-capable devices user space has to read /sys/devices/.../power/wakeup for all devices and see if they are not empty). Fix this issue by avoiding to create wakeup sysfs files for devices that cannot wake up the system from sleep states (i.e. whose power.can_wakeup flags are unset during registration) and modify device_set_wakeup_capable() so that it adds (or removes) the relevant sysfs attributes if a device's wakeup capability status is changed. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/power.h | 21 ++++++------ drivers/base/power/sysfs.c | 78 +++++++++++++++++++++++++++++---------------- drivers/base/power/wakeup.c | 29 +++++++++++++++++ 3 files changed, 90 insertions(+), 38 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index 698dde7..f2a25f1 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -58,19 +58,18 @@ static inline void device_pm_move_last(struct device *dev) {} * sysfs.c */ -extern int dpm_sysfs_add(struct device *); -extern void dpm_sysfs_remove(struct device *); -extern void rpm_sysfs_remove(struct device *); +extern int dpm_sysfs_add(struct device *dev); +extern void dpm_sysfs_remove(struct device *dev); +extern void rpm_sysfs_remove(struct device *dev); +extern int wakeup_sysfs_add(struct device *dev); +extern void wakeup_sysfs_remove(struct device *dev); #else /* CONFIG_PM */ -static inline int dpm_sysfs_add(struct device *dev) -{ - return 0; -} - -static inline void dpm_sysfs_remove(struct device *dev) -{ -} +static inline int dpm_sysfs_add(struct device *dev) { return 0; } +static inline void dpm_sysfs_remove(struct device *dev) {} +static inline void rpm_sysfs_remove(struct device *dev) {} +static inline int wakeup_sysfs_add(struct device *dev) { return 0; } +static inline void wakeup_sysfs_remove(struct device *dev) {} #endif diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 0b1e46b..fff49be 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -431,26 +431,18 @@ static ssize_t async_store(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(async, 0644, async_show, async_store); #endif /* CONFIG_PM_ADVANCED_DEBUG */ -static struct attribute * power_attrs[] = { - &dev_attr_wakeup.attr, -#ifdef CONFIG_PM_SLEEP - &dev_attr_wakeup_count.attr, - &dev_attr_wakeup_active_count.attr, - &dev_attr_wakeup_hit_count.attr, - &dev_attr_wakeup_active.attr, - &dev_attr_wakeup_total_time_ms.attr, - &dev_attr_wakeup_max_time_ms.attr, - &dev_attr_wakeup_last_time_ms.attr, -#endif +static struct attribute *power_attrs[] = { #ifdef CONFIG_PM_ADVANCED_DEBUG +#ifdef CONFIG_PM_SLEEP &dev_attr_async.attr, +#endif #ifdef CONFIG_PM_RUNTIME &dev_attr_runtime_status.attr, &dev_attr_runtime_usage.attr, &dev_attr_runtime_active_kids.attr, &dev_attr_runtime_enabled.attr, #endif -#endif +#endif /* CONFIG_PM_ADVANCED_DEBUG */ NULL, }; static struct attribute_group pm_attr_group = { @@ -458,9 +450,26 @@ static struct attribute_group pm_attr_group = { .attrs = power_attrs, }; -#ifdef CONFIG_PM_RUNTIME +static struct attribute *wakeup_attrs[] = { +#ifdef CONFIG_PM_SLEEP + &dev_attr_wakeup.attr, + &dev_attr_wakeup_count.attr, + &dev_attr_wakeup_active_count.attr, + &dev_attr_wakeup_hit_count.attr, + &dev_attr_wakeup_active.attr, + &dev_attr_wakeup_total_time_ms.attr, + &dev_attr_wakeup_max_time_ms.attr, + &dev_attr_wakeup_last_time_ms.attr, +#endif + NULL, +}; +static struct attribute_group pm_wakeup_attr_group = { + .name = power_group_name, + .attrs = wakeup_attrs, +}; static struct attribute *runtime_attrs[] = { +#ifdef CONFIG_PM_RUNTIME #ifndef CONFIG_PM_ADVANCED_DEBUG &dev_attr_runtime_status.attr, #endif @@ -468,6 +477,7 @@ static struct attribute *runtime_attrs[] = { &dev_attr_runtime_suspended_time.attr, &dev_attr_runtime_active_time.attr, &dev_attr_autosuspend_delay_ms.attr, +#endif /* CONFIG_PM_RUNTIME */ NULL, }; static struct attribute_group pm_runtime_attr_group = { @@ -480,35 +490,49 @@ int dpm_sysfs_add(struct device *dev) int rc; rc = sysfs_create_group(&dev->kobj, &pm_attr_group); - if (rc == 0 && !dev->power.no_callbacks) { + if (rc) + return rc; + + if (pm_runtime_callbacks_present(dev)) { rc = sysfs_merge_group(&dev->kobj, &pm_runtime_attr_group); if (rc) - sysfs_remove_group(&dev->kobj, &pm_attr_group); + goto err_out; + } + + if (device_can_wakeup(dev)) { + rc = sysfs_merge_group(&dev->kobj, &pm_wakeup_attr_group); + if (rc) { + if (pm_runtime_callbacks_present(dev)) + sysfs_unmerge_group(&dev->kobj, + &pm_runtime_attr_group); + goto err_out; + } } + return 0; + + err_out: + sysfs_remove_group(&dev->kobj, &pm_attr_group); return rc; } -void rpm_sysfs_remove(struct device *dev) +int wakeup_sysfs_add(struct device *dev) { - sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group); + return sysfs_merge_group(&dev->kobj, &pm_wakeup_attr_group); } -void dpm_sysfs_remove(struct device *dev) +void wakeup_sysfs_remove(struct device *dev) { - rpm_sysfs_remove(dev); - sysfs_remove_group(&dev->kobj, &pm_attr_group); + sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group); } -#else /* CONFIG_PM_RUNTIME */ - -int dpm_sysfs_add(struct device * dev) +void rpm_sysfs_remove(struct device *dev) { - return sysfs_create_group(&dev->kobj, &pm_attr_group); + sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group); } -void dpm_sysfs_remove(struct device * dev) +void dpm_sysfs_remove(struct device *dev) { + rpm_sysfs_remove(dev); + sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group); sysfs_remove_group(&dev->kobj, &pm_attr_group); } - -#endif diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 8283638..4573c83 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -242,6 +242,35 @@ int device_wakeup_disable(struct device *dev) EXPORT_SYMBOL_GPL(device_wakeup_disable); /** + * device_set_wakeup_capable - Set/reset device wakeup capability flag. + * @dev: Device to handle. + * @capable: Whether or not @dev is capable of waking up the system from sleep. + * + * If @capable is set, set the @dev's power.can_wakeup flag and add its + * wakeup-related attributes to sysfs. Otherwise, unset the @dev's + * power.can_wakeup flag and remove its wakeup-related attributes from sysfs. + * + * This function may sleep and it can't be called from any context where + * sleeping is not allowed. + */ +void device_set_wakeup_capable(struct device *dev, bool capable) +{ + if (!!dev->power.can_wakeup == !!capable) + return; + + if (device_is_registered(dev)) { + if (capable) { + if (wakeup_sysfs_add(dev)) + return; + } else { + wakeup_sysfs_remove(dev); + } + } + dev->power.can_wakeup = capable; +} +EXPORT_SYMBOL_GPL(device_set_wakeup_capable); + +/** * device_init_wakeup - Device wakeup initialization. * @dev: Device to handle. * @enable: Whether or not to enable @dev as a wakeup device. -- cgit v1.1 From cd51e61cf4e8b220da37dc35e9c2dc2dc258b4de Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Feb 2011 00:04:52 +0100 Subject: PM / ACPI: Remove references to pm_flags from bus.c If direct references to pm_flags are removed from drivers/acpi/bus.c, CONFIG_ACPI will not need to depend on CONFIG_PM any more. Make that happen. Signed-off-by: Rafael J. Wysocki Acked-by: Len Brown --- drivers/acpi/Kconfig | 1 - drivers/acpi/bus.c | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 2aa042a..3a17ca5 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -7,7 +7,6 @@ menuconfig ACPI depends on !IA64_HP_SIM depends on IA64 || X86 depends on PCI - depends on PM select PNP default y help diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 7ced61f..973b070 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -40,6 +40,7 @@ #include #include #include +#include #include "internal.h" @@ -1025,13 +1026,13 @@ static int __init acpi_init(void) if (!result) { pci_mmcfg_late_init(); - if (!(pm_flags & PM_APM)) - pm_flags |= PM_ACPI; - else { + if (pm_apm_enabled()) { printk(KERN_INFO PREFIX "APM is already active, exiting\n"); disable_acpi(); result = -ENODEV; + } else { + pm_set_acpi_flag(); } } else disable_acpi(); -- cgit v1.1 From aa33860158114d0df3c7997bc1dd41c0168e1c2a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Feb 2011 00:06:54 +0100 Subject: PM: Remove CONFIG_PM_OPS After redefining CONFIG_PM to depend on (CONFIG_PM_SLEEP || CONFIG_PM_RUNTIME) the CONFIG_PM_OPS option is redundant and can be replaced with CONFIG_PM. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sleep.c | 4 ++-- drivers/base/power/Makefile | 3 +-- drivers/net/e1000e/netdev.c | 8 ++++---- drivers/net/pch_gbe/pch_gbe_main.c | 2 +- drivers/pci/pci-driver.c | 4 ++-- drivers/scsi/Makefile | 2 +- drivers/scsi/scsi_priv.h | 2 +- drivers/scsi/scsi_sysfs.c | 2 +- drivers/usb/core/hcd-pci.c | 4 ++-- 9 files changed, 15 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index d6a8cd1..8ea092f 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -585,7 +585,7 @@ int acpi_suspend(u32 acpi_state) return -EINVAL; } -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM /** * acpi_pm_device_sleep_state - return preferred power state of ACPI device * in the system sleep state given by %acpi_target_sleep_state @@ -671,7 +671,7 @@ int acpi_pm_device_sleep_state(struct device *dev, int *d_min_p) *d_min_p = d_min; return d_max; } -#endif /* CONFIG_PM_OPS */ +#endif /* CONFIG_PM */ #ifdef CONFIG_PM_SLEEP /** diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index abe46ed..118c1b9 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,7 +1,6 @@ -obj-$(CONFIG_PM) += sysfs.o +obj-$(CONFIG_PM) += sysfs.o generic_ops.o obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o obj-$(CONFIG_PM_RUNTIME) += runtime.o -obj-$(CONFIG_PM_OPS) += generic_ops.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_OPP) += opp.o diff --git a/drivers/net/e1000e/netdev.c b/drivers/net/e1000e/netdev.c index 2e50228..6d513a3 100644 --- a/drivers/net/e1000e/netdev.c +++ b/drivers/net/e1000e/netdev.c @@ -5338,7 +5338,7 @@ void e1000e_disable_aspm(struct pci_dev *pdev, u16 state) __e1000e_disable_aspm(pdev, state); } -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM static bool e1000e_pm_ready(struct e1000_adapter *adapter) { return !!adapter->tx_ring->buffer_info; @@ -5489,7 +5489,7 @@ static int e1000_runtime_resume(struct device *dev) return __e1000_resume(pdev); } #endif /* CONFIG_PM_RUNTIME */ -#endif /* CONFIG_PM_OPS */ +#endif /* CONFIG_PM */ static void e1000_shutdown(struct pci_dev *pdev) { @@ -6196,7 +6196,7 @@ static DEFINE_PCI_DEVICE_TABLE(e1000_pci_tbl) = { }; MODULE_DEVICE_TABLE(pci, e1000_pci_tbl); -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM static const struct dev_pm_ops e1000_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(e1000_suspend, e1000_resume) SET_RUNTIME_PM_OPS(e1000_runtime_suspend, @@ -6210,7 +6210,7 @@ static struct pci_driver e1000_driver = { .id_table = e1000_pci_tbl, .probe = e1000_probe, .remove = __devexit_p(e1000_remove), -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM .driver.pm = &e1000_pm_ops, #endif .shutdown = e1000_shutdown, diff --git a/drivers/net/pch_gbe/pch_gbe_main.c b/drivers/net/pch_gbe/pch_gbe_main.c index b99e90a..8c66e22 100644 --- a/drivers/net/pch_gbe/pch_gbe_main.c +++ b/drivers/net/pch_gbe/pch_gbe_main.c @@ -2446,7 +2446,7 @@ static struct pci_driver pch_gbe_pcidev = { .id_table = pch_gbe_pcidev_id, .probe = pch_gbe_probe, .remove = pch_gbe_remove, -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM .driver.pm = &pch_gbe_pm_ops, #endif .shutdown = pch_gbe_shutdown, diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 88246dd..d86ea8b 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -431,7 +431,7 @@ static void pci_device_shutdown(struct device *dev) pci_msix_shutdown(pci_dev); } -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM /* Auxiliary functions used for system resume and run-time resume. */ @@ -1059,7 +1059,7 @@ static int pci_pm_runtime_idle(struct device *dev) #endif /* !CONFIG_PM_RUNTIME */ -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM const struct dev_pm_ops pci_dev_pm_ops = { .prepare = pci_pm_prepare, diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 2e9a87e..ef6de66 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -165,7 +165,7 @@ scsi_mod-$(CONFIG_SCSI_NETLINK) += scsi_netlink.o scsi_mod-$(CONFIG_SYSCTL) += scsi_sysctl.o scsi_mod-$(CONFIG_SCSI_PROC_FS) += scsi_proc.o scsi_mod-y += scsi_trace.o -scsi_mod-$(CONFIG_PM_OPS) += scsi_pm.o +scsi_mod-$(CONFIG_PM) += scsi_pm.o scsi_tgt-y += scsi_tgt_lib.o scsi_tgt_if.o diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index b4056d1..342ee1a 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -146,7 +146,7 @@ static inline void scsi_netlink_exit(void) {} #endif /* scsi_pm.c */ -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM extern const struct dev_pm_ops scsi_bus_pm_ops; #endif #ifdef CONFIG_PM_RUNTIME diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 490ce21..e44ff64 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -383,7 +383,7 @@ struct bus_type scsi_bus_type = { .name = "scsi", .match = scsi_bus_match, .uevent = scsi_bus_uevent, -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM .pm = &scsi_bus_pm_ops, #endif }; diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index f71e8e3..64a035b 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -335,7 +335,7 @@ void usb_hcd_pci_shutdown(struct pci_dev *dev) } EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown); -#ifdef CONFIG_PM_OPS +#ifdef CONFIG_PM #ifdef CONFIG_PPC_PMAC static void powermac_set_asic(struct pci_dev *pci_dev, int enable) @@ -580,4 +580,4 @@ const struct dev_pm_ops usb_hcd_pci_pm_ops = { }; EXPORT_SYMBOL_GPL(usb_hcd_pci_pm_ops); -#endif /* CONFIG_PM_OPS */ +#endif /* CONFIG_PM */ -- cgit v1.1 From e8665002477f0278f84f898145b1f141ba26ee26 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 12 Feb 2011 01:42:41 +0100 Subject: PM: Allow pm_runtime_suspend() to succeed during system suspend The dpm_prepare() function increments the runtime PM reference counters of all devices to prevent pm_runtime_suspend() from executing subsystem-level callbacks. However, this was supposed to guard against a specific race condition that cannot happen, because the power management workqueue is freezable, so pm_runtime_suspend() can only be called synchronously during system suspend and we can rely on subsystems and device drivers to avoid doing that unnecessarily. Make dpm_prepare() drop the runtime PM reference to each device after making sure that runtime resume is not pending for it. Signed-off-by: Rafael J. Wysocki Acked-by: Kevin Hilman --- drivers/base/power/main.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 8340497..f7a7559 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -669,7 +669,6 @@ static void dpm_complete(pm_message_t state) mutex_unlock(&dpm_list_mtx); device_complete(dev, state); - pm_runtime_put_sync(dev); mutex_lock(&dpm_list_mtx); put_device(dev); @@ -1005,12 +1004,9 @@ static int dpm_prepare(pm_message_t state) if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) pm_wakeup_event(dev, 0); - if (pm_wakeup_pending()) { - pm_runtime_put_sync(dev); - error = -EBUSY; - } else { - error = device_prepare(dev, state); - } + pm_runtime_put_sync(dev); + error = pm_wakeup_pending() ? + -EBUSY : device_prepare(dev, state); mutex_lock(&dpm_list_mtx); if (error) { -- cgit v1.1 From 6831c6edc7b272a08dd2a6c71bb183a48fe98ae6 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 15 Feb 2011 21:22:24 +0100 Subject: PM: Drop pm_flags that is not necessary The variable pm_flags is used to prevent APM from being enabled along with ACPI, which would lead to problems. However, acpi_init() is always called before apm_init() and after acpi_init() has returned, it is known whether or not ACPI will be used. Namely, if acpi_disabled is not set after acpi_init() has returned, this means that ACPI is enabled. Thus, it is sufficient to check acpi_disabled in apm_init() to prevent APM from being enabled in parallel with ACPI. Signed-off-by: Rafael J. Wysocki Acked-by: Len Brown --- drivers/acpi/bus.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 973b070..9749980 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -1007,8 +1007,7 @@ struct kobject *acpi_kobj; static int __init acpi_init(void) { - int result = 0; - + int result; if (acpi_disabled) { printk(KERN_INFO PREFIX "Interpreter disabled.\n"); @@ -1023,29 +1022,18 @@ static int __init acpi_init(void) init_acpi_device_notify(); result = acpi_bus_init(); - - if (!result) { - pci_mmcfg_late_init(); - if (pm_apm_enabled()) { - printk(KERN_INFO PREFIX - "APM is already active, exiting\n"); - disable_acpi(); - result = -ENODEV; - } else { - pm_set_acpi_flag(); - } - } else + if (result) { disable_acpi(); - - if (acpi_disabled) return result; + } + pci_mmcfg_late_init(); acpi_scan_init(); acpi_ec_init(); acpi_debugfs_init(); acpi_sleep_proc_init(); acpi_wakeup_device_init(); - return result; + return 0; } subsys_initcall(acpi_init); -- cgit v1.1 From 7538e3db6e015e890825fbd9f8659952896ddd5b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 16 Feb 2011 21:53:17 +0100 Subject: PM: Add support for device power domains The platform bus type is often used to handle Systems-on-a-Chip (SoC) where all devices are represented by objects of type struct platform_device. In those cases the same "platform" device driver may be used with multiple different system configurations, but the actions needed to put the devices it handles into a low-power state and back into the full-power state may depend on the design of the given SoC. The driver, however, cannot possibly include all the information necessary for the power management of its device on all the systems it is used with. Moreover, the device hierarchy in its current form also is not suitable for representing this kind of information. The patch below attempts to address this problem by introducing objects of type struct dev_power_domain that can be used for representing power domains within a SoC. Every struct dev_power_domain object provides a sets of device power management callbacks that can be used to perform what's needed for device power management in addition to the operations carried out by the device's driver and subsystem. Namely, if a struct dev_power_domain object is pointed to by the pwr_domain field in a struct device, the callbacks provided by its ops member will be executed in addition to the corresponding callbacks provided by the device's subsystem and driver during all power transitions. Signed-off-by: Rafael J. Wysocki Tested-and-acked-by: Kevin Hilman --- drivers/base/power/main.c | 37 +++++++++++++++++++++++++++++++++++++ drivers/base/power/runtime.c | 19 +++++++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index f7a7559..05b9891 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -423,6 +423,11 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) TRACE_DEVICE(dev); TRACE_RESUME(0); + if (dev->pwr_domain) { + pm_dev_dbg(dev, state, "EARLY power domain "); + pm_noirq_op(dev, &dev->pwr_domain->ops, state); + } + if (dev->bus && dev->bus->pm) { pm_dev_dbg(dev, state, "EARLY "); error = pm_noirq_op(dev, dev->bus->pm, state); @@ -518,6 +523,11 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) dev->power.in_suspend = false; + if (dev->pwr_domain) { + pm_dev_dbg(dev, state, "power domain "); + pm_op(dev, &dev->pwr_domain->ops, state); + } + if (dev->bus) { if (dev->bus->pm) { pm_dev_dbg(dev, state, ""); @@ -629,6 +639,11 @@ static void device_complete(struct device *dev, pm_message_t state) { device_lock(dev); + if (dev->pwr_domain && dev->pwr_domain->ops.complete) { + pm_dev_dbg(dev, state, "completing power domain "); + dev->pwr_domain->ops.complete(dev); + } + if (dev->class && dev->class->pm && dev->class->pm->complete) { pm_dev_dbg(dev, state, "completing class "); dev->class->pm->complete(dev); @@ -745,6 +760,13 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state) if (dev->bus && dev->bus->pm) { pm_dev_dbg(dev, state, "LATE "); error = pm_noirq_op(dev, dev->bus->pm, state); + if (error) + goto End; + } + + if (dev->pwr_domain) { + pm_dev_dbg(dev, state, "LATE power domain "); + pm_noirq_op(dev, &dev->pwr_domain->ops, state); } End: @@ -864,6 +886,13 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) pm_dev_dbg(dev, state, "legacy "); error = legacy_suspend(dev, state, dev->bus->suspend); } + if (error) + goto End; + } + + if (dev->pwr_domain) { + pm_dev_dbg(dev, state, "power domain "); + pm_op(dev, &dev->pwr_domain->ops, state); } End: @@ -976,7 +1005,15 @@ static int device_prepare(struct device *dev, pm_message_t state) pm_dev_dbg(dev, state, "preparing class "); error = dev->class->pm->prepare(dev); suspend_report_result(dev->class->pm->prepare, error); + if (error) + goto End; + } + + if (dev->pwr_domain && dev->pwr_domain->ops.prepare) { + pm_dev_dbg(dev, state, "preparing power domain "); + dev->pwr_domain->ops.prepare(dev); } + End: device_unlock(dev); diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 42615b4..25edc9a 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -168,6 +168,7 @@ static int rpm_check_suspend_allowed(struct device *dev) static int rpm_idle(struct device *dev, int rpmflags) { int (*callback)(struct device *); + int (*domain_callback)(struct device *); int retval; retval = rpm_check_suspend_allowed(dev); @@ -222,10 +223,19 @@ static int rpm_idle(struct device *dev, int rpmflags) else callback = NULL; - if (callback) { + if (dev->pwr_domain) + domain_callback = dev->pwr_domain->ops.runtime_idle; + else + domain_callback = NULL; + + if (callback || domain_callback) { spin_unlock_irq(&dev->power.lock); - callback(dev); + if (domain_callback) + retval = domain_callback(dev); + + if (!retval && callback) + callback(dev); spin_lock_irq(&dev->power.lock); } @@ -390,6 +400,8 @@ static int rpm_suspend(struct device *dev, int rpmflags) else pm_runtime_cancel_pending(dev); } else { + if (dev->pwr_domain) + rpm_callback(dev->pwr_domain->ops.runtime_suspend, dev); no_callback: __update_runtime_status(dev, RPM_SUSPENDED); pm_runtime_deactivate_timer(dev); @@ -569,6 +581,9 @@ static int rpm_resume(struct device *dev, int rpmflags) __update_runtime_status(dev, RPM_RESUMING); + if (dev->pwr_domain) + rpm_callback(dev->pwr_domain->ops.runtime_resume, dev); + if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) callback = dev->bus->pm->runtime_resume; else if (dev->type && dev->type->pm && dev->type->pm->runtime_resume) -- cgit v1.1 From 9659cc0678b954f187290c6e8b247a673c5d37e1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 18 Feb 2011 23:20:21 +0100 Subject: PM: Make system-wide PM and runtime PM treat subsystems consistently The code handling system-wide power transitions (eg. suspend-to-RAM) can in theory execute callbacks provided by the device's bus type, device type and class in each phase of the power transition. In turn, the runtime PM core code only calls one of those callbacks at a time, preferring bus type callbacks to device type or class callbacks and device type callbacks to class callbacks. It seems reasonable to make them both behave in the same way in that respect. Moreover, even though a device may belong to two subsystems (eg. bus type and device class) simultaneously, in practice power management callbacks for system-wide power transitions are always provided by only one of them (ie. if the bus type callbacks are defined, the device class ones are not and vice versa). Thus it is possible to modify the code handling system-wide power transitions so that it follows the core runtime PM code (ie. treats the subsystem callbacks as mutually exclusive). On the other hand, the core runtime PM code will choose to execute, for example, a runtime suspend callback provided by the device type even if the bus type's struct dev_pm_ops object exists, but the runtime_suspend pointer in it happens to be NULL. This is confusing, because it may lead to the execution of callbacks from different subsystems during different operations (eg. the bus type suspend callback may be executed during runtime suspend of the device, while the device type callback will be executed during system suspend). Make all of the power management code treat subsystem callbacks in a consistent way, such that: (1) If the device's type is defined (eg. dev->type is not NULL) and its pm pointer is not NULL, the callbacks from dev->type->pm will be used. (2) If dev->type is NULL or dev->type->pm is NULL, but the device's class is defined (eg. dev->class is not NULL) and its pm pointer is not NULL, the callbacks from dev->class->pm will be used. (3) If dev->type is NULL or dev->type->pm is NULL and dev->class is NULL or dev->class->pm is NULL, the callbacks from dev->bus->pm will be used provided that both dev->bus and dev->bus->pm are not NULL. Signed-off-by: Rafael J. Wysocki Acked-by: Kevin Hilman Reasoning-sounds-sane-to: Grant Likely Acked-by: Greg Kroah-Hartman --- drivers/base/power/main.c | 150 ++++++++++++++++++------------------------- drivers/base/power/runtime.c | 18 +++--- 2 files changed, 73 insertions(+), 95 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 05b9891..052dc53 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -428,26 +428,17 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) pm_noirq_op(dev, &dev->pwr_domain->ops, state); } - if (dev->bus && dev->bus->pm) { - pm_dev_dbg(dev, state, "EARLY "); - error = pm_noirq_op(dev, dev->bus->pm, state); - if (error) - goto End; - } - if (dev->type && dev->type->pm) { pm_dev_dbg(dev, state, "EARLY type "); error = pm_noirq_op(dev, dev->type->pm, state); - if (error) - goto End; - } - - if (dev->class && dev->class->pm) { + } else if (dev->class && dev->class->pm) { pm_dev_dbg(dev, state, "EARLY class "); error = pm_noirq_op(dev, dev->class->pm, state); + } else if (dev->bus && dev->bus->pm) { + pm_dev_dbg(dev, state, "EARLY "); + error = pm_noirq_op(dev, dev->bus->pm, state); } -End: TRACE_RESUME(error); return error; } @@ -528,36 +519,34 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) pm_op(dev, &dev->pwr_domain->ops, state); } - if (dev->bus) { - if (dev->bus->pm) { - pm_dev_dbg(dev, state, ""); - error = pm_op(dev, dev->bus->pm, state); - } else if (dev->bus->resume) { - pm_dev_dbg(dev, state, "legacy "); - error = legacy_resume(dev, dev->bus->resume); - } - if (error) - goto End; - } - - if (dev->type) { - if (dev->type->pm) { - pm_dev_dbg(dev, state, "type "); - error = pm_op(dev, dev->type->pm, state); - } - if (error) - goto End; + if (dev->type && dev->type->pm) { + pm_dev_dbg(dev, state, "type "); + error = pm_op(dev, dev->type->pm, state); + goto End; } if (dev->class) { if (dev->class->pm) { pm_dev_dbg(dev, state, "class "); error = pm_op(dev, dev->class->pm, state); + goto End; } else if (dev->class->resume) { pm_dev_dbg(dev, state, "legacy class "); error = legacy_resume(dev, dev->class->resume); + goto End; } } + + if (dev->bus) { + if (dev->bus->pm) { + pm_dev_dbg(dev, state, ""); + error = pm_op(dev, dev->bus->pm, state); + } else if (dev->bus->resume) { + pm_dev_dbg(dev, state, "legacy "); + error = legacy_resume(dev, dev->bus->resume); + } + } + End: device_unlock(dev); complete_all(&dev->power.completion); @@ -644,19 +633,18 @@ static void device_complete(struct device *dev, pm_message_t state) dev->pwr_domain->ops.complete(dev); } - if (dev->class && dev->class->pm && dev->class->pm->complete) { - pm_dev_dbg(dev, state, "completing class "); - dev->class->pm->complete(dev); - } - - if (dev->type && dev->type->pm && dev->type->pm->complete) { + if (dev->type && dev->type->pm) { pm_dev_dbg(dev, state, "completing type "); - dev->type->pm->complete(dev); - } - - if (dev->bus && dev->bus->pm && dev->bus->pm->complete) { + if (dev->type->pm->complete) + dev->type->pm->complete(dev); + } else if (dev->class && dev->class->pm) { + pm_dev_dbg(dev, state, "completing class "); + if (dev->class->pm->complete) + dev->class->pm->complete(dev); + } else if (dev->bus && dev->bus->pm) { pm_dev_dbg(dev, state, "completing "); - dev->bus->pm->complete(dev); + if (dev->bus->pm->complete) + dev->bus->pm->complete(dev); } device_unlock(dev); @@ -741,27 +729,23 @@ static pm_message_t resume_event(pm_message_t sleep_state) */ static int device_suspend_noirq(struct device *dev, pm_message_t state) { - int error = 0; - - if (dev->class && dev->class->pm) { - pm_dev_dbg(dev, state, "LATE class "); - error = pm_noirq_op(dev, dev->class->pm, state); - if (error) - goto End; - } + int error; if (dev->type && dev->type->pm) { pm_dev_dbg(dev, state, "LATE type "); error = pm_noirq_op(dev, dev->type->pm, state); if (error) - goto End; - } - - if (dev->bus && dev->bus->pm) { + return error; + } else if (dev->class && dev->class->pm) { + pm_dev_dbg(dev, state, "LATE class "); + error = pm_noirq_op(dev, dev->class->pm, state); + if (error) + return error; + } else if (dev->bus && dev->bus->pm) { pm_dev_dbg(dev, state, "LATE "); error = pm_noirq_op(dev, dev->bus->pm, state); if (error) - goto End; + return error; } if (dev->pwr_domain) { @@ -769,8 +753,7 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state) pm_noirq_op(dev, &dev->pwr_domain->ops, state); } -End: - return error; + return 0; } /** @@ -857,25 +840,22 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) goto End; } + if (dev->type && dev->type->pm) { + pm_dev_dbg(dev, state, "type "); + error = pm_op(dev, dev->type->pm, state); + goto Domain; + } + if (dev->class) { if (dev->class->pm) { pm_dev_dbg(dev, state, "class "); error = pm_op(dev, dev->class->pm, state); + goto Domain; } else if (dev->class->suspend) { pm_dev_dbg(dev, state, "legacy class "); error = legacy_suspend(dev, state, dev->class->suspend); + goto Domain; } - if (error) - goto End; - } - - if (dev->type) { - if (dev->type->pm) { - pm_dev_dbg(dev, state, "type "); - error = pm_op(dev, dev->type->pm, state); - } - if (error) - goto End; } if (dev->bus) { @@ -886,11 +866,10 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) pm_dev_dbg(dev, state, "legacy "); error = legacy_suspend(dev, state, dev->bus->suspend); } - if (error) - goto End; } - if (dev->pwr_domain) { + Domain: + if (!error && dev->pwr_domain) { pm_dev_dbg(dev, state, "power domain "); pm_op(dev, &dev->pwr_domain->ops, state); } @@ -985,28 +964,27 @@ static int device_prepare(struct device *dev, pm_message_t state) device_lock(dev); - if (dev->bus && dev->bus->pm && dev->bus->pm->prepare) { - pm_dev_dbg(dev, state, "preparing "); - error = dev->bus->pm->prepare(dev); - suspend_report_result(dev->bus->pm->prepare, error); - if (error) - goto End; - } - - if (dev->type && dev->type->pm && dev->type->pm->prepare) { + if (dev->type && dev->type->pm) { pm_dev_dbg(dev, state, "preparing type "); - error = dev->type->pm->prepare(dev); + if (dev->type->pm->prepare) + error = dev->type->pm->prepare(dev); suspend_report_result(dev->type->pm->prepare, error); if (error) goto End; - } - - if (dev->class && dev->class->pm && dev->class->pm->prepare) { + } else if (dev->class && dev->class->pm) { pm_dev_dbg(dev, state, "preparing class "); - error = dev->class->pm->prepare(dev); + if (dev->class->pm->prepare) + error = dev->class->pm->prepare(dev); suspend_report_result(dev->class->pm->prepare, error); if (error) goto End; + } else if (dev->bus && dev->bus->pm) { + pm_dev_dbg(dev, state, "preparing "); + if (dev->bus->pm->prepare) + error = dev->bus->pm->prepare(dev); + suspend_report_result(dev->bus->pm->prepare, error); + if (error) + goto End; } if (dev->pwr_domain && dev->pwr_domain->ops.prepare) { diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 25edc9a..54597c8 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -214,12 +214,12 @@ static int rpm_idle(struct device *dev, int rpmflags) dev->power.idle_notification = true; - if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_idle) - callback = dev->bus->pm->runtime_idle; - else if (dev->type && dev->type->pm && dev->type->pm->runtime_idle) + if (dev->type && dev->type->pm) callback = dev->type->pm->runtime_idle; else if (dev->class && dev->class->pm) callback = dev->class->pm->runtime_idle; + else if (dev->bus && dev->bus->pm) + callback = dev->bus->pm->runtime_idle; else callback = NULL; @@ -382,12 +382,12 @@ static int rpm_suspend(struct device *dev, int rpmflags) __update_runtime_status(dev, RPM_SUSPENDING); - if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend) - callback = dev->bus->pm->runtime_suspend; - else if (dev->type && dev->type->pm && dev->type->pm->runtime_suspend) + if (dev->type && dev->type->pm) callback = dev->type->pm->runtime_suspend; else if (dev->class && dev->class->pm) callback = dev->class->pm->runtime_suspend; + else if (dev->bus && dev->bus->pm) + callback = dev->bus->pm->runtime_suspend; else callback = NULL; @@ -584,12 +584,12 @@ static int rpm_resume(struct device *dev, int rpmflags) if (dev->pwr_domain) rpm_callback(dev->pwr_domain->ops.runtime_resume, dev); - if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) - callback = dev->bus->pm->runtime_resume; - else if (dev->type && dev->type->pm && dev->type->pm->runtime_resume) + if (dev->type && dev->type->pm) callback = dev->type->pm->runtime_resume; else if (dev->class && dev->class->pm) callback = dev->class->pm->runtime_resume; + else if (dev->bus && dev->bus->pm) + callback = dev->bus->pm->runtime_resume; else callback = NULL; -- cgit v1.1 From 7ae496187876d264c712d7c102c45edb8eb41363 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Fri, 25 Feb 2011 23:46:18 +0100 Subject: PM / OPP: opp_find_freq_exact() documentation fix opp_find_freq_exact() documentation has is_available instead of available. This also fixes warning with the kernel-doc: scripts/kernel-doc drivers/base/power/opp.c >/dev/null Warning(drivers/base/power/opp.c:246): No description found for parameter 'available' Warning(drivers/base/power/opp.c:246): Excess function parameter 'is_available' description in 'opp_find_freq_exact' Signed-off-by: Nishanth Menon Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 2bb9b4c..56a6899 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -222,7 +222,7 @@ int opp_get_opp_count(struct device *dev) * opp_find_freq_exact() - search for an exact frequency * @dev: device for which we do this operation * @freq: frequency to search for - * @is_available: true/false - match for available opp + * @available: true/false - match for available opp * * Searches for exact match in the opp list and returns pointer to the matching * opp if found, else returns ERR_PTR in case of error and should be handled -- cgit v1.1 From 40dc166cb5dddbd36aa4ad11c03915ea538f5a61 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 15 Mar 2011 00:43:46 +0100 Subject: PM / Core: Introduce struct syscore_ops for core subsystems PM Some subsystems need to carry out suspend/resume and shutdown operations with one CPU on-line and interrupts disabled. The only way to register such operations is to define a sysdev class and a sysdev specifically for this purpose which is cumbersome and inefficient. Moreover, the arguments taken by sysdev suspend, resume and shutdown callbacks are practically never necessary. For this reason, introduce a simpler interface allowing subsystems to register operations to be executed very late during system suspend and shutdown and very early during resume in the form of strcut syscore_ops objects. Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman --- drivers/base/Makefile | 2 +- drivers/base/syscore.c | 117 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 drivers/base/syscore.c (limited to 'drivers') diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 5f51c3b..4c5701c 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -1,6 +1,6 @@ # Makefile for the Linux device tree -obj-y := core.o sys.o bus.o dd.o \ +obj-y := core.o sys.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o diff --git a/drivers/base/syscore.c b/drivers/base/syscore.c new file mode 100644 index 0000000..90af294 --- /dev/null +++ b/drivers/base/syscore.c @@ -0,0 +1,117 @@ +/* + * syscore.c - Execution of system core operations. + * + * Copyright (C) 2011 Rafael J. Wysocki , Novell Inc. + * + * This file is released under the GPLv2. + */ + +#include +#include +#include + +static LIST_HEAD(syscore_ops_list); +static DEFINE_MUTEX(syscore_ops_lock); + +/** + * register_syscore_ops - Register a set of system core operations. + * @ops: System core operations to register. + */ +void register_syscore_ops(struct syscore_ops *ops) +{ + mutex_lock(&syscore_ops_lock); + list_add_tail(&ops->node, &syscore_ops_list); + mutex_unlock(&syscore_ops_lock); +} +EXPORT_SYMBOL_GPL(register_syscore_ops); + +/** + * unregister_syscore_ops - Unregister a set of system core operations. + * @ops: System core operations to unregister. + */ +void unregister_syscore_ops(struct syscore_ops *ops) +{ + mutex_lock(&syscore_ops_lock); + list_del(&ops->node); + mutex_unlock(&syscore_ops_lock); +} +EXPORT_SYMBOL_GPL(unregister_syscore_ops); + +#ifdef CONFIG_PM_SLEEP +/** + * syscore_suspend - Execute all the registered system core suspend callbacks. + * + * This function is executed with one CPU on-line and disabled interrupts. + */ +int syscore_suspend(void) +{ + struct syscore_ops *ops; + int ret = 0; + + WARN_ONCE(!irqs_disabled(), + "Interrupts enabled before system core suspend.\n"); + + list_for_each_entry_reverse(ops, &syscore_ops_list, node) + if (ops->suspend) { + if (initcall_debug) + pr_info("PM: Calling %pF\n", ops->suspend); + ret = ops->suspend(); + if (ret) + goto err_out; + WARN_ONCE(!irqs_disabled(), + "Interrupts enabled after %pF\n", ops->suspend); + } + + return 0; + + err_out: + pr_err("PM: System core suspend callback %pF failed.\n", ops->suspend); + + list_for_each_entry_continue(ops, &syscore_ops_list, node) + if (ops->resume) + ops->resume(); + + return ret; +} + +/** + * syscore_resume - Execute all the registered system core resume callbacks. + * + * This function is executed with one CPU on-line and disabled interrupts. + */ +void syscore_resume(void) +{ + struct syscore_ops *ops; + + WARN_ONCE(!irqs_disabled(), + "Interrupts enabled before system core resume.\n"); + + list_for_each_entry(ops, &syscore_ops_list, node) + if (ops->resume) { + if (initcall_debug) + pr_info("PM: Calling %pF\n", ops->resume); + ops->resume(); + WARN_ONCE(!irqs_disabled(), + "Interrupts enabled after %pF\n", ops->resume); + } +} +#endif /* CONFIG_PM_SLEEP */ + +/** + * syscore_shutdown - Execute all the registered system core shutdown callbacks. + */ +void syscore_shutdown(void) +{ + struct syscore_ops *ops; + + mutex_lock(&syscore_ops_lock); + + list_for_each_entry_reverse(ops, &syscore_ops_list, node) + if (ops->shutdown) { + if (initcall_debug) + pr_info("PM: Calling %pF\n", ops->shutdown); + ops->shutdown(); + } + + mutex_unlock(&syscore_ops_lock); +} -- cgit v1.1