diff options
Diffstat (limited to 'drivers/base/power')
-rw-r--r-- | drivers/base/power/domain.c | 3 | ||||
-rw-r--r-- | drivers/base/power/opp.c | 19 | ||||
-rw-r--r-- | drivers/base/power/qos.c | 1 | ||||
-rw-r--r-- | drivers/base/power/runtime.c | 89 | ||||
-rw-r--r-- | drivers/base/power/wakeup.c | 6 |
5 files changed, 107 insertions, 11 deletions
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index acc3a8ded29..9a6b05a3560 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -433,8 +433,7 @@ static bool genpd_abort_poweroff(struct generic_pm_domain *genpd) */ void genpd_queue_power_off_work(struct generic_pm_domain *genpd) { - if (!work_pending(&genpd->power_off_work)) - queue_work(pm_wq, &genpd->power_off_work); + queue_work(pm_wq, &genpd->power_off_work); } /** diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 50b2831e027..32ee0fc7ea5 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -162,7 +162,7 @@ unsigned long opp_get_voltage(struct opp *opp) return v; } -EXPORT_SYMBOL(opp_get_voltage); +EXPORT_SYMBOL_GPL(opp_get_voltage); /** * opp_get_freq() - Gets the frequency corresponding to an available opp @@ -192,7 +192,7 @@ unsigned long opp_get_freq(struct opp *opp) return f; } -EXPORT_SYMBOL(opp_get_freq); +EXPORT_SYMBOL_GPL(opp_get_freq); /** * opp_get_opp_count() - Get number of opps available in the opp list @@ -225,7 +225,7 @@ int opp_get_opp_count(struct device *dev) return count; } -EXPORT_SYMBOL(opp_get_opp_count); +EXPORT_SYMBOL_GPL(opp_get_opp_count); /** * opp_find_freq_exact() - search for an exact frequency @@ -276,7 +276,7 @@ struct opp *opp_find_freq_exact(struct device *dev, unsigned long freq, return opp; } -EXPORT_SYMBOL(opp_find_freq_exact); +EXPORT_SYMBOL_GPL(opp_find_freq_exact); /** * opp_find_freq_ceil() - Search for an rounded ceil freq @@ -323,7 +323,7 @@ struct opp *opp_find_freq_ceil(struct device *dev, unsigned long *freq) return opp; } -EXPORT_SYMBOL(opp_find_freq_ceil); +EXPORT_SYMBOL_GPL(opp_find_freq_ceil); /** * opp_find_freq_floor() - Search for a rounded floor freq @@ -374,7 +374,7 @@ struct opp *opp_find_freq_floor(struct device *dev, unsigned long *freq) return opp; } -EXPORT_SYMBOL(opp_find_freq_floor); +EXPORT_SYMBOL_GPL(opp_find_freq_floor); /** * opp_add() - Add an OPP table from a table definitions @@ -568,7 +568,7 @@ int opp_enable(struct device *dev, unsigned long freq) { return opp_set_availability(dev, freq, true); } -EXPORT_SYMBOL(opp_enable); +EXPORT_SYMBOL_GPL(opp_enable); /** * opp_disable() - Disable a specific OPP @@ -590,7 +590,7 @@ int opp_disable(struct device *dev, unsigned long freq) { return opp_set_availability(dev, freq, false); } -EXPORT_SYMBOL(opp_disable); +EXPORT_SYMBOL_GPL(opp_disable); #ifdef CONFIG_CPU_FREQ /** @@ -661,6 +661,7 @@ int opp_init_cpufreq_table(struct device *dev, return 0; } +EXPORT_SYMBOL_GPL(opp_init_cpufreq_table); /** * opp_free_cpufreq_table() - free the cpufreq table @@ -678,6 +679,7 @@ void opp_free_cpufreq_table(struct device *dev, kfree(*table); *table = NULL; } +EXPORT_SYMBOL_GPL(opp_free_cpufreq_table); #endif /* CONFIG_CPU_FREQ */ /** @@ -738,4 +740,5 @@ int of_init_opp_table(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(of_init_opp_table); #endif diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index d21349544ce..3d4d1f8aac5 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -91,6 +91,7 @@ enum pm_qos_flags_status dev_pm_qos_flags(struct device *dev, s32 mask) return ret; } +EXPORT_SYMBOL_GPL(dev_pm_qos_flags); /** * __dev_pm_qos_read_value - Get PM QoS constraint for a given device. diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 3148b10dc2e..1244930e3d7 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -124,6 +124,76 @@ unsigned long pm_runtime_autosuspend_expiration(struct device *dev) } EXPORT_SYMBOL_GPL(pm_runtime_autosuspend_expiration); +static int dev_memalloc_noio(struct device *dev, void *data) +{ + return dev->power.memalloc_noio; +} + +/* + * pm_runtime_set_memalloc_noio - Set a device's memalloc_noio flag. + * @dev: Device to handle. + * @enable: True for setting the flag and False for clearing the flag. + * + * Set the flag for all devices in the path from the device to the + * root device in the device tree if @enable is true, otherwise clear + * the flag for devices in the path whose siblings don't set the flag. + * + * The function should only be called by block device, or network + * device driver for solving the deadlock problem during runtime + * resume/suspend: + * + * If memory allocation with GFP_KERNEL is called inside runtime + * resume/suspend callback of any one of its ancestors(or the + * block device itself), the deadlock may be triggered inside the + * memory allocation since it might not complete until the block + * device becomes active and the involed page I/O finishes. The + * situation is pointed out first by Alan Stern. Network device + * are involved in iSCSI kind of situation. + * + * The lock of dev_hotplug_mutex is held in the function for handling + * hotplug race because pm_runtime_set_memalloc_noio() may be called + * in async probe(). + * + * The function should be called between device_add() and device_del() + * on the affected device(block/network device). + */ +void pm_runtime_set_memalloc_noio(struct device *dev, bool enable) +{ + static DEFINE_MUTEX(dev_hotplug_mutex); + + mutex_lock(&dev_hotplug_mutex); + for (;;) { + bool enabled; + + /* hold power lock since bitfield is not SMP-safe. */ + spin_lock_irq(&dev->power.lock); + enabled = dev->power.memalloc_noio; + dev->power.memalloc_noio = enable; + spin_unlock_irq(&dev->power.lock); + + /* + * not need to enable ancestors any more if the device + * has been enabled. + */ + if (enabled && enable) + break; + + dev = dev->parent; + + /* + * clear flag of the parent device only if all the + * children don't set the flag because ancestor's + * flag was set by any one of the descendants. + */ + if (!dev || (!enable && + device_for_each_child(dev, NULL, + dev_memalloc_noio))) + break; + } + mutex_unlock(&dev_hotplug_mutex); +} +EXPORT_SYMBOL_GPL(pm_runtime_set_memalloc_noio); + /** * rpm_check_suspend_allowed - Test whether a device may be suspended. * @dev: Device to test. @@ -278,7 +348,24 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev) if (!cb) return -ENOSYS; - retval = __rpm_callback(cb, dev); + if (dev->power.memalloc_noio) { + unsigned int noio_flag; + + /* + * Deadlock might be caused if memory allocation with + * GFP_KERNEL happens inside runtime_suspend and + * runtime_resume callbacks of one block device's + * ancestor or the block device itself. Network + * device might be thought as part of iSCSI block + * device, so network device and its ancestor should + * be marked as memalloc_noio too. + */ + noio_flag = memalloc_noio_save(); + retval = __rpm_callback(cb, dev); + memalloc_noio_restore(noio_flag); + } else { + retval = __rpm_callback(cb, dev); + } dev->power.runtime_error = retval; return retval != -EACCES ? retval : -EIO; diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index e6ee5e80e54..79715e7fa43 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -382,6 +382,12 @@ static void wakeup_source_activate(struct wakeup_source *ws) { unsigned int cec; + /* + * active wakeup source should bring the system + * out of PM_SUSPEND_FREEZE state + */ + freeze_wake(); + ws->active = true; ws->active_count++; ws->last_time = ktime_get(); |