From 86e12eac1f7f84b03512ecfa110c48b9204e6286 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 8 Dec 2016 14:45:20 +0100 Subject: PM / Domains: Rename functions in genpd for power on/off Currently the mix of genpd_poweron(), genpd_power_on(), genpd_sync_poweron() and the ->power_on() callback, makes a bit difficult to follow the path of execution. The similar applies to the functions dealing with power off. In a way to improve this understanding, let's do the following renaming: genpd_power_on() -> _genpd_power_on() genpd_poweron() -> genpd_power_on() genpd_sync_poweron() -> genpd_sync_power_on() genpd_power_off() -> _genpd_power_off() genpd_poweroff() -> genpd_power_off() genpd_sync_poweroff() -> genpd_sync_power_off() genpd_poweroff_unused() -> genpd_power_off_unused() Signed-off-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 72 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 36 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 2997026b4dfb..fd2e3e1eac65 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -201,7 +201,7 @@ static void genpd_sd_counter_inc(struct generic_pm_domain *genpd) smp_mb__after_atomic(); } -static int genpd_power_on(struct generic_pm_domain *genpd, bool timed) +static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed) { unsigned int state_idx = genpd->state_idx; ktime_t time_start; @@ -231,7 +231,7 @@ static int genpd_power_on(struct generic_pm_domain *genpd, bool timed) return ret; } -static int genpd_power_off(struct generic_pm_domain *genpd, bool timed) +static int _genpd_power_off(struct generic_pm_domain *genpd, bool timed) { unsigned int state_idx = genpd->state_idx; ktime_t time_start; @@ -262,10 +262,10 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool timed) } /** - * genpd_queue_power_off_work - Queue up the execution of genpd_poweroff(). + * genpd_queue_power_off_work - Queue up the execution of genpd_power_off(). * @genpd: PM domain to power off. * - * Queue up the execution of genpd_poweroff() unless it's already been done + * Queue up the execution of genpd_power_off() unless it's already been done * before. */ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) @@ -274,14 +274,14 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) } /** - * genpd_poweron - Restore power to a given PM domain and its masters. + * genpd_power_on - Restore power to a given PM domain and its masters. * @genpd: PM domain to power up. * @depth: nesting count for lockdep. * * Restore power to @genpd and all of its masters so that it is possible to * resume a device belonging to it. */ -static int genpd_poweron(struct generic_pm_domain *genpd, unsigned int depth) +static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) { struct gpd_link *link; int ret = 0; @@ -300,7 +300,7 @@ static int genpd_poweron(struct generic_pm_domain *genpd, unsigned int depth) genpd_sd_counter_inc(master); genpd_lock_nested(master, depth + 1); - ret = genpd_poweron(master, depth + 1); + ret = genpd_power_on(master, depth + 1); genpd_unlock(master); if (ret) { @@ -309,7 +309,7 @@ static int genpd_poweron(struct generic_pm_domain *genpd, unsigned int depth) } } - ret = genpd_power_on(genpd, true); + ret = _genpd_power_on(genpd, true); if (ret) goto err; @@ -368,14 +368,14 @@ static int genpd_dev_pm_qos_notifier(struct notifier_block *nb, } /** - * genpd_poweroff - Remove power from a given PM domain. + * genpd_power_off - Remove power from a given PM domain. * @genpd: PM domain to power down. * @is_async: PM domain is powered down from a scheduled work * * If all of the @genpd's devices have been suspended and all of its subdomains * have been powered down, remove power from @genpd. */ -static int genpd_poweroff(struct generic_pm_domain *genpd, bool is_async) +static int genpd_power_off(struct generic_pm_domain *genpd, bool is_async) { struct pm_domain_data *pdd; struct gpd_link *link; @@ -427,13 +427,13 @@ static int genpd_poweroff(struct generic_pm_domain *genpd, bool is_async) /* * If sd_count > 0 at this point, one of the subdomains hasn't - * managed to call genpd_poweron() for the master yet after - * incrementing it. In that case genpd_poweron() will wait + * managed to call genpd_power_on() for the master yet after + * incrementing it. In that case genpd_power_on() will wait * for us to drop the lock, so we can call .power_off() and let - * the genpd_poweron() restore power for us (this shouldn't + * the genpd_power_on() restore power for us (this shouldn't * happen very often). */ - ret = genpd_power_off(genpd, true); + ret = _genpd_power_off(genpd, true); if (ret) return ret; } @@ -459,7 +459,7 @@ static void genpd_power_off_work_fn(struct work_struct *work) genpd = container_of(work, struct generic_pm_domain, power_off_work); genpd_lock(genpd); - genpd_poweroff(genpd, true); + genpd_power_off(genpd, true); genpd_unlock(genpd); } @@ -578,7 +578,7 @@ static int genpd_runtime_suspend(struct device *dev) return 0; genpd_lock(genpd); - genpd_poweroff(genpd, false); + genpd_power_off(genpd, false); genpd_unlock(genpd); return 0; @@ -618,7 +618,7 @@ static int genpd_runtime_resume(struct device *dev) } genpd_lock(genpd); - ret = genpd_poweron(genpd, 0); + ret = genpd_power_on(genpd, 0); genpd_unlock(genpd); if (ret) @@ -658,7 +658,7 @@ err_poweroff: if (!pm_runtime_is_irq_safe(dev) || (pm_runtime_is_irq_safe(dev) && genpd_is_irq_safe(genpd))) { genpd_lock(genpd); - genpd_poweroff(genpd, 0); + genpd_power_off(genpd, 0); genpd_unlock(genpd); } @@ -674,9 +674,9 @@ static int __init pd_ignore_unused_setup(char *__unused) __setup("pd_ignore_unused", pd_ignore_unused_setup); /** - * genpd_poweroff_unused - Power off all PM domains with no devices in use. + * genpd_power_off_unused - Power off all PM domains with no devices in use. */ -static int __init genpd_poweroff_unused(void) +static int __init genpd_power_off_unused(void) { struct generic_pm_domain *genpd; @@ -694,7 +694,7 @@ static int __init genpd_poweroff_unused(void) return 0; } -late_initcall(genpd_poweroff_unused); +late_initcall(genpd_power_off_unused); #if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_GENERIC_DOMAINS_OF) @@ -727,7 +727,7 @@ static bool genpd_dev_active_wakeup(struct generic_pm_domain *genpd, } /** - * genpd_sync_poweroff - Synchronously power off a PM domain and its masters. + * genpd_sync_power_off - Synchronously power off a PM domain and its masters. * @genpd: PM domain to power off, if possible. * * Check if the given PM domain can be powered off (during system suspend or @@ -738,7 +738,7 @@ static bool genpd_dev_active_wakeup(struct generic_pm_domain *genpd, * executed sequentially, so it is guaranteed that it will never run twice in * parallel). */ -static void genpd_sync_poweroff(struct generic_pm_domain *genpd) +static void genpd_sync_power_off(struct generic_pm_domain *genpd) { struct gpd_link *link; @@ -751,18 +751,18 @@ static void genpd_sync_poweroff(struct generic_pm_domain *genpd) /* Choose the deepest state when suspending */ genpd->state_idx = genpd->state_count - 1; - genpd_power_off(genpd, false); + _genpd_power_off(genpd, false); genpd->status = GPD_STATE_POWER_OFF; list_for_each_entry(link, &genpd->slave_links, slave_node) { genpd_sd_counter_dec(link->master); - genpd_sync_poweroff(link->master); + genpd_sync_power_off(link->master); } } /** - * genpd_sync_poweron - Synchronously power on a PM domain and its masters. + * genpd_sync_power_on - Synchronously power on a PM domain and its masters. * @genpd: PM domain to power on. * * This function is only called in "noirq" and "syscore" stages of system power @@ -770,7 +770,7 @@ static void genpd_sync_poweroff(struct generic_pm_domain *genpd) * executed sequentially, so it is guaranteed that it will never run twice in * parallel). */ -static void genpd_sync_poweron(struct generic_pm_domain *genpd) +static void genpd_sync_power_on(struct generic_pm_domain *genpd) { struct gpd_link *link; @@ -778,11 +778,11 @@ static void genpd_sync_poweron(struct generic_pm_domain *genpd) return; list_for_each_entry(link, &genpd->slave_links, slave_node) { - genpd_sync_poweron(link->master); + genpd_sync_power_on(link->master); genpd_sd_counter_inc(link->master); } - genpd_power_on(genpd, false); + _genpd_power_on(genpd, false); genpd->status = GPD_STATE_ACTIVE; } @@ -894,7 +894,7 @@ static int pm_genpd_suspend_noirq(struct device *dev) * the same PM domain, so it is not necessary to use locking here. */ genpd->suspended_count++; - genpd_sync_poweroff(genpd); + genpd_sync_power_off(genpd); return 0; } @@ -924,7 +924,7 @@ static int pm_genpd_resume_noirq(struct device *dev) * guaranteed that this function will never run twice in parallel for * the same PM domain, so it is not necessary to use locking here. */ - genpd_sync_poweron(genpd); + genpd_sync_power_on(genpd); genpd->suspended_count--; if (genpd->dev_ops.stop && genpd->dev_ops.start) @@ -1012,12 +1012,12 @@ static int pm_genpd_restore_noirq(struct device *dev) if (genpd->suspended_count++ == 0) /* * The boot kernel might put the domain into arbitrary state, - * so make it appear as powered off to genpd_sync_poweron(), + * so make it appear as powered off to genpd_sync_power_on(), * so that it tries to power it on in case it was really off. */ genpd->status = GPD_STATE_POWER_OFF; - genpd_sync_poweron(genpd); + genpd_sync_power_on(genpd); if (genpd->dev_ops.stop && genpd->dev_ops.start) ret = pm_runtime_force_resume(dev); @@ -1072,9 +1072,9 @@ static void genpd_syscore_switch(struct device *dev, bool suspend) if (suspend) { genpd->suspended_count++; - genpd_sync_poweroff(genpd); + genpd_sync_power_off(genpd); } else { - genpd_sync_poweron(genpd); + genpd_sync_power_on(genpd); genpd->suspended_count--; } } @@ -2043,7 +2043,7 @@ int genpd_dev_pm_attach(struct device *dev) dev->pm_domain->sync = genpd_dev_pm_sync; genpd_lock(pd); - ret = genpd_poweron(pd, 0); + ret = genpd_power_on(pd, 0); genpd_unlock(pd); out: return ret ? -EPROBE_DEFER : 0; -- cgit v1.2.3 From 7f8538ebaefc075f364f14a4f4852b1885ed897c Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:40:55 +0530 Subject: PM / OPP: Fix memory leak while adding duplicate OPPs There are two types of duplicate OPPs that get different behavior from the core: A) An earlier OPP is marked 'available' and has same freq/voltages as the new one. B) An earlier OPP with same frequency, but is marked 'unavailable' OR doesn't have same voltages as the new one. The OPP core returns 0 for the first one, but -EEXIST for the second. While the OPP core returns 0 for the first case, its callers don't free the newly allocated OPP structure which isn't used anymore. Fix that by returning -EBUSY instead of 0, but make the callers return 0 eventually. As this isn't a critical fix, its not getting marked for stable kernel. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 18 ++++++++++++++++-- drivers/base/power/opp/of.c | 6 +++++- 2 files changed, 21 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index 35ff06283738..a8a5e01b7756 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -1067,6 +1067,16 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp, return true; } +/* + * Returns: + * 0: On success. And appropriate error message for duplicate OPPs. + * -EBUSY: For OPP with same freq/volt and is available. The callers of + * _opp_add() must return 0 if they receive -EBUSY from it. This is to make + * sure we don't print error messages unnecessarily if different parts of + * kernel try to initialize the OPP table. + * -EEXIST: For OPP with same freq but different volt or is unavailable. This + * should be considered an error by the callers of _opp_add(). + */ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table) { @@ -1099,7 +1109,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, /* Should we compare voltages for all regulators here ? */ return opp->available && - new_opp->supplies[0].u_volt == opp->supplies[0].u_volt ? 0 : -EEXIST; + new_opp->supplies[0].u_volt == opp->supplies[0].u_volt ? -EBUSY : -EEXIST; } new_opp->opp_table = opp_table; @@ -1173,8 +1183,12 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, new_opp->dynamic = dynamic; ret = _opp_add(dev, new_opp, opp_table); - if (ret) + if (ret) { + /* Don't return error for duplicate OPPs */ + if (ret == -EBUSY) + ret = 0; goto free_opp; + } mutex_unlock(&opp_table_lock); diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c index 3f7d2591b173..356c75edd656 100644 --- a/drivers/base/power/opp/of.c +++ b/drivers/base/power/opp/of.c @@ -327,8 +327,12 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) goto free_opp; ret = _opp_add(dev, new_opp, opp_table); - if (ret) + if (ret) { + /* Don't return error for duplicate OPPs */ + if (ret == -EBUSY) + ret = 0; goto free_opp; + } /* OPP to select on device suspend */ if (of_property_read_bool(np, "opp-suspend")) { -- cgit v1.2.3 From 1715371ddd5affdd63c7cb5220d104062c74ad66 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:40:56 +0530 Subject: PM / OPP: Remove useless TODO This TODO doesn't make sense anymore as we have all the information in a single OPP table. Remove it. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/of.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c index 356c75edd656..996ca3b42f47 100644 --- a/drivers/base/power/opp/of.c +++ b/drivers/base/power/opp/of.c @@ -246,8 +246,6 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table); static struct device_node *_of_get_opp_desc_node(struct device *dev) { /* - * TODO: Support for multiple OPP tables. - * * There should be only ONE phandle present in "operating-points-v2" * property. */ -- cgit v1.2.3 From 63a69ea4b88f37bda3bb13c4fb417a3218902022 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:40:57 +0530 Subject: PM / OPP: Rename _allocate_opp() to _opp_allocate() Make the naming consistent with how other routines are named. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 4 ++-- drivers/base/power/opp/of.c | 2 +- drivers/base/power/opp/opp.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index a8a5e01b7756..422482575954 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -1014,7 +1014,7 @@ unlock: } EXPORT_SYMBOL_GPL(dev_pm_opp_remove); -struct dev_pm_opp *_allocate_opp(struct device *dev, +struct dev_pm_opp *_opp_allocate(struct device *dev, struct opp_table **opp_table) { struct dev_pm_opp *opp; @@ -1167,7 +1167,7 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, /* Hold our table modification lock here */ mutex_lock(&opp_table_lock); - new_opp = _allocate_opp(dev, &opp_table); + new_opp = _opp_allocate(dev, &opp_table); if (!new_opp) { ret = -ENOMEM; goto unlock; diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c index 996ca3b42f47..f8512ca2bf41 100644 --- a/drivers/base/power/opp/of.c +++ b/drivers/base/power/opp/of.c @@ -287,7 +287,7 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) /* Hold our table modification lock here */ mutex_lock(&opp_table_lock); - new_opp = _allocate_opp(dev, &opp_table); + new_opp = _opp_allocate(dev, &opp_table); if (!new_opp) { ret = -ENOMEM; goto unlock; diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h index af9f2b849a66..0f23a1059605 100644 --- a/drivers/base/power/opp/opp.h +++ b/drivers/base/power/opp/opp.h @@ -193,7 +193,7 @@ struct opp_table { struct opp_table *_find_opp_table(struct device *dev); struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table); void _dev_pm_opp_remove_table(struct device *dev, bool remove_all); -struct dev_pm_opp *_allocate_opp(struct device *dev, struct opp_table **opp_table); +struct dev_pm_opp *_opp_allocate(struct device *dev, struct opp_table **opp_table); int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table); void _opp_remove(struct opp_table *opp_table, struct dev_pm_opp *opp, bool notify); int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, bool dynamic); -- cgit v1.2.3 From 04a86a84c42ca18f37ab446127dc619b41dd3b23 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:40:58 +0530 Subject: PM / OPP: Error out on failing to add static OPPs for v1 bindings The code adding static OPPs for V2 bindings already does so. Make the V1 bindings specific code behave the same. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/of.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c index f8512ca2bf41..c8fe815774ff 100644 --- a/drivers/base/power/opp/of.c +++ b/drivers/base/power/opp/of.c @@ -433,7 +433,7 @@ static int _of_add_opp_table_v1(struct device *dev) { const struct property *prop; const __be32 *val; - int nr; + int nr, ret; prop = of_find_property(dev->of_node, "operating-points", NULL); if (!prop) @@ -456,9 +456,13 @@ static int _of_add_opp_table_v1(struct device *dev) unsigned long freq = be32_to_cpup(val++) * 1000; unsigned long volt = be32_to_cpup(val++); - if (_opp_add_v1(dev, freq, volt, false)) - dev_warn(dev, "%s: Failed to add OPP %ld\n", - __func__, freq); + ret = _opp_add_v1(dev, freq, volt, false); + if (ret) { + dev_err(dev, "%s: Failed to add OPP %ld (%d)\n", + __func__, freq, ret); + dev_pm_opp_of_remove_table(dev); + return ret; + } nr -= 2; } -- cgit v1.2.3 From 969fceb3c7e6fc7fd0419b0392435717824f7ba5 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:40:59 +0530 Subject: PM / OPP: Add light weight _opp_free() routine The OPPs which are never successfully added using _opp_add() are not required to be freed with the _opp_remove() routine, as a simple kfree() is enough for them. Introduce a new light weight routine _opp_free(), which will do that. That also helps us removing the 'notify' parameter to _opp_remove(), which isn't required anymore. Note that _opp_free() contains a call to _remove_opp_table() as the OPP table might have been added for this very OPP only. The _remove_opp_table() routine returns quickly if there are more OPPs in the table. This will be simplified in later patches though. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 20 +++++++++++--------- drivers/base/power/opp/of.c | 2 +- drivers/base/power/opp/opp.h | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index 422482575954..ea5b90e0882c 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -929,6 +929,12 @@ static void _remove_opp_table(struct opp_table *opp_table) _kfree_device_rcu); } +void _opp_free(struct dev_pm_opp *opp, struct opp_table *opp_table) +{ + kfree(opp); + _remove_opp_table(opp_table); +} + /** * _kfree_opp_rcu() - Free OPP RCU handler * @head: RCU head @@ -944,7 +950,6 @@ static void _kfree_opp_rcu(struct rcu_head *head) * _opp_remove() - Remove an OPP from a table definition * @opp_table: points back to the opp_table struct this opp belongs to * @opp: pointer to the OPP to remove - * @notify: OPP_EVENT_REMOVE notification should be sent or not * * This function removes an opp definition from the opp table. * @@ -952,16 +957,13 @@ static void _kfree_opp_rcu(struct rcu_head *head) * It is assumed that the caller holds required mutex for an RCU updater * strategy. */ -void _opp_remove(struct opp_table *opp_table, struct dev_pm_opp *opp, - bool notify) +static void _opp_remove(struct opp_table *opp_table, struct dev_pm_opp *opp) { /* * Notify the changes in the availability of the operable * frequency/voltage list. */ - if (notify) - srcu_notifier_call_chain(&opp_table->srcu_head, - OPP_EVENT_REMOVE, opp); + srcu_notifier_call_chain(&opp_table->srcu_head, OPP_EVENT_REMOVE, opp); opp_debug_remove_one(opp); list_del_rcu(&opp->node); call_srcu(&opp_table->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu); @@ -1008,7 +1010,7 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq) goto unlock; } - _opp_remove(opp_table, opp, true); + _opp_remove(opp_table, opp); unlock: mutex_unlock(&opp_table_lock); } @@ -1200,7 +1202,7 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, return 0; free_opp: - _opp_remove(opp_table, new_opp, false); + _opp_free(new_opp, opp_table); unlock: mutex_unlock(&opp_table_lock); return ret; @@ -1912,7 +1914,7 @@ void _dev_pm_opp_remove_table(struct device *dev, bool remove_all) /* Free static OPPs */ list_for_each_entry_safe(opp, tmp, &opp_table->opp_list, node) { if (remove_all || !opp->dynamic) - _opp_remove(opp_table, opp, true); + _opp_remove(opp_table, opp); } } else { _remove_opp_dev(_find_opp_dev(dev, opp_table), opp_table); diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c index c8fe815774ff..67c9eeded4e1 100644 --- a/drivers/base/power/opp/of.c +++ b/drivers/base/power/opp/of.c @@ -362,7 +362,7 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) return 0; free_opp: - _opp_remove(opp_table, new_opp, false); + _opp_free(new_opp, opp_table); unlock: mutex_unlock(&opp_table_lock); return ret; diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h index 0f23a1059605..334f7570df32 100644 --- a/drivers/base/power/opp/opp.h +++ b/drivers/base/power/opp/opp.h @@ -194,8 +194,8 @@ struct opp_table *_find_opp_table(struct device *dev); struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table); void _dev_pm_opp_remove_table(struct device *dev, bool remove_all); struct dev_pm_opp *_opp_allocate(struct device *dev, struct opp_table **opp_table); +void _opp_free(struct dev_pm_opp *opp, struct opp_table *opp_table); int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table); -void _opp_remove(struct opp_table *opp_table, struct dev_pm_opp *opp, bool notify); int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, bool dynamic); void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of); -- cgit v1.2.3 From 9274c892430ef3240acf3f336916f64fe2b67b02 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:41:00 +0530 Subject: PM / OPP: Rename and split _dev_pm_opp_remove_table() Later patches would want to remove OPP table (and its OPPs) using the opp_table pointer instead of 'dev'. In order to prepare for that, rename _dev_pm_opp_remove_table() as _dev_pm_opp_find_and_remove_table() split out part of it. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 35 ++++++++++++++++++++++------------- drivers/base/power/opp/of.c | 2 +- drivers/base/power/opp/opp.h | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index ea5b90e0882c..b3a624a4af81 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -1888,11 +1888,29 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_notifier); * Free OPPs either created using static entries present in DT or even the * dynamically added entries based on remove_all param. */ -void _dev_pm_opp_remove_table(struct device *dev, bool remove_all) +static void _dev_pm_opp_remove_table(struct opp_table *opp_table, + struct device *dev, bool remove_all) { - struct opp_table *opp_table; struct dev_pm_opp *opp, *tmp; + opp_rcu_lockdep_assert(); + + /* Find if opp_table manages a single device */ + if (list_is_singular(&opp_table->dev_list)) { + /* Free static OPPs */ + list_for_each_entry_safe(opp, tmp, &opp_table->opp_list, node) { + if (remove_all || !opp->dynamic) + _opp_remove(opp_table, opp); + } + } else { + _remove_opp_dev(_find_opp_dev(dev, opp_table), opp_table); + } +} + +void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all) +{ + struct opp_table *opp_table; + /* Hold our table modification lock here */ mutex_lock(&opp_table_lock); @@ -1909,16 +1927,7 @@ void _dev_pm_opp_remove_table(struct device *dev, bool remove_all) goto unlock; } - /* Find if opp_table manages a single device */ - if (list_is_singular(&opp_table->dev_list)) { - /* Free static OPPs */ - list_for_each_entry_safe(opp, tmp, &opp_table->opp_list, node) { - if (remove_all || !opp->dynamic) - _opp_remove(opp_table, opp); - } - } else { - _remove_opp_dev(_find_opp_dev(dev, opp_table), opp_table); - } + _dev_pm_opp_remove_table(opp_table, dev, remove_all); unlock: mutex_unlock(&opp_table_lock); @@ -1939,6 +1948,6 @@ unlock: */ void dev_pm_opp_remove_table(struct device *dev) { - _dev_pm_opp_remove_table(dev, true); + _dev_pm_opp_find_and_remove_table(dev, true); } EXPORT_SYMBOL_GPL(dev_pm_opp_remove_table); diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c index 67c9eeded4e1..442fa46c4f5c 100644 --- a/drivers/base/power/opp/of.c +++ b/drivers/base/power/opp/of.c @@ -238,7 +238,7 @@ free_microvolt: */ void dev_pm_opp_of_remove_table(struct device *dev) { - _dev_pm_opp_remove_table(dev, false); + _dev_pm_opp_find_and_remove_table(dev, false); } EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table); diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h index 334f7570df32..c4b539a8533a 100644 --- a/drivers/base/power/opp/opp.h +++ b/drivers/base/power/opp/opp.h @@ -192,7 +192,7 @@ struct opp_table { /* Routines internal to opp core */ struct opp_table *_find_opp_table(struct device *dev); struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table); -void _dev_pm_opp_remove_table(struct device *dev, bool remove_all); +void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all); struct dev_pm_opp *_opp_allocate(struct device *dev, struct opp_table **opp_table); void _opp_free(struct dev_pm_opp *opp, struct opp_table *opp_table); int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table); -- cgit v1.2.3 From 8cd2f6e8f34e75198063a6d84675b18bdb106824 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:41:01 +0530 Subject: PM / OPP: Don't allocate OPP table from _opp_allocate() There is no point in trying to find/allocate the table for every OPP that is added for a device. It would be far more efficient to allocate the table only once and pass its pointer to the routines that add the OPP entry. Locking is removed from _opp_add_static_v2() and _opp_add_v1() now as the callers call them with that lock already held. Call to _remove_opp_table() routine is also removed from _opp_free() now, as opp_table isn't allocated from within _opp_allocate(). This is handled by the routines which created the OPP table in the first place. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 67 +++++++++++++++++++------------------- drivers/base/power/opp/of.c | 75 ++++++++++++++++++++++--------------------- drivers/base/power/opp/opp.h | 9 ++++-- 3 files changed, 78 insertions(+), 73 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index b3a624a4af81..dde7dd099aa0 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -829,7 +829,7 @@ struct opp_device *_add_opp_dev(const struct device *dev, * * Return: valid opp_table pointer if success, else NULL. */ -static struct opp_table *_add_opp_table(struct device *dev) +struct opp_table *_add_opp_table(struct device *dev) { struct opp_table *opp_table; struct opp_device *opp_dev; @@ -929,10 +929,9 @@ static void _remove_opp_table(struct opp_table *opp_table) _kfree_device_rcu); } -void _opp_free(struct dev_pm_opp *opp, struct opp_table *opp_table) +void _opp_free(struct dev_pm_opp *opp) { kfree(opp); - _remove_opp_table(opp_table); } /** @@ -1016,16 +1015,10 @@ unlock: } EXPORT_SYMBOL_GPL(dev_pm_opp_remove); -struct dev_pm_opp *_opp_allocate(struct device *dev, - struct opp_table **opp_table) +struct dev_pm_opp *_opp_allocate(struct opp_table *table) { struct dev_pm_opp *opp; int count, supply_size; - struct opp_table *table; - - table = _add_opp_table(dev); - if (!table) - return NULL; /* Allocate space for at least one supply */ count = table->regulator_count ? table->regulator_count : 1; @@ -1033,17 +1026,13 @@ struct dev_pm_opp *_opp_allocate(struct device *dev, /* allocate new OPP node and supplies structures */ opp = kzalloc(sizeof(*opp) + supply_size, GFP_KERNEL); - if (!opp) { - kfree(table); + if (!opp) return NULL; - } /* Put the supplies at the end of the OPP structure as an empty array */ opp->supplies = (struct dev_pm_opp_supply *)(opp + 1); INIT_LIST_HEAD(&opp->node); - *opp_table = table; - return opp; } @@ -1133,6 +1122,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, /** * _opp_add_v1() - Allocate a OPP based on v1 bindings. + * @opp_table: OPP table * @dev: device for which we do this operation * @freq: Frequency in Hz for this OPP * @u_volt: Voltage in uVolts for this OPP @@ -1158,22 +1148,18 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, * Duplicate OPPs (both freq and volt are same) and !opp->available * -ENOMEM Memory allocation failure */ -int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, - bool dynamic) +int _opp_add_v1(struct opp_table *opp_table, struct device *dev, + unsigned long freq, long u_volt, bool dynamic) { - struct opp_table *opp_table; struct dev_pm_opp *new_opp; unsigned long tol; int ret; - /* Hold our table modification lock here */ - mutex_lock(&opp_table_lock); + opp_rcu_lockdep_assert(); - new_opp = _opp_allocate(dev, &opp_table); - if (!new_opp) { - ret = -ENOMEM; - goto unlock; - } + new_opp = _opp_allocate(opp_table); + if (!new_opp) + return -ENOMEM; /* populate the opp table */ new_opp->rate = freq; @@ -1192,8 +1178,6 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, goto free_opp; } - mutex_unlock(&opp_table_lock); - /* * Notify the changes in the availability of the operable * frequency/voltage list. @@ -1202,9 +1186,8 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, return 0; free_opp: - _opp_free(new_opp, opp_table); -unlock: - mutex_unlock(&opp_table_lock); + _opp_free(new_opp); + return ret; } @@ -1722,7 +1705,25 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_register_put_opp_helper); */ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) { - return _opp_add_v1(dev, freq, u_volt, true); + struct opp_table *opp_table; + int ret; + + /* Hold our table modification lock here */ + mutex_lock(&opp_table_lock); + + opp_table = _add_opp_table(dev); + if (!opp_table) { + ret = -ENOMEM; + goto unlock; + } + + ret = _opp_add_v1(opp_table, dev, freq, u_volt, true); + if (ret) + _remove_opp_table(opp_table); + +unlock: + mutex_unlock(&opp_table_lock); + return ret; } EXPORT_SYMBOL_GPL(dev_pm_opp_add); @@ -1888,8 +1889,8 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_notifier); * Free OPPs either created using static entries present in DT or even the * dynamically added entries based on remove_all param. */ -static void _dev_pm_opp_remove_table(struct opp_table *opp_table, - struct device *dev, bool remove_all) +void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev, + bool remove_all) { struct dev_pm_opp *opp, *tmp; diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c index 442fa46c4f5c..cdbf733ac9b1 100644 --- a/drivers/base/power/opp/of.c +++ b/drivers/base/power/opp/of.c @@ -255,6 +255,7 @@ static struct device_node *_of_get_opp_desc_node(struct device *dev) /** * _opp_add_static_v2() - Allocate static OPPs (As per 'v2' DT bindings) + * @opp_table: OPP table * @dev: device for which we do this operation * @np: device node * @@ -276,22 +277,17 @@ static struct device_node *_of_get_opp_desc_node(struct device *dev) * -ENOMEM Memory allocation failure * -EINVAL Failed parsing the OPP node */ -static int _opp_add_static_v2(struct device *dev, struct device_node *np) +static int _opp_add_static_v2(struct opp_table *opp_table, struct device *dev, + struct device_node *np) { - struct opp_table *opp_table; struct dev_pm_opp *new_opp; u64 rate; u32 val; int ret; - /* Hold our table modification lock here */ - mutex_lock(&opp_table_lock); - - new_opp = _opp_allocate(dev, &opp_table); - if (!new_opp) { - ret = -ENOMEM; - goto unlock; - } + new_opp = _opp_allocate(opp_table); + if (!new_opp) + return -ENOMEM; ret = of_property_read_u64(np, "opp-hz", &rate); if (ret < 0) { @@ -347,8 +343,6 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) if (new_opp->clock_latency_ns > opp_table->clock_latency_ns_max) opp_table->clock_latency_ns_max = new_opp->clock_latency_ns; - mutex_unlock(&opp_table_lock); - pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n", __func__, new_opp->turbo, new_opp->rate, new_opp->supplies[0].u_volt, new_opp->supplies[0].u_volt_min, @@ -362,9 +356,8 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) return 0; free_opp: - _opp_free(new_opp, opp_table); -unlock: - mutex_unlock(&opp_table_lock); + _opp_free(new_opp); + return ret; } @@ -382,16 +375,20 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np) /* OPPs are already managed */ if (!_add_opp_dev(dev, opp_table)) ret = -ENOMEM; - mutex_unlock(&opp_table_lock); - return ret; + goto unlock; + } + + opp_table = _add_opp_table(dev); + if (!opp_table) { + ret = -ENOMEM; + goto unlock; } - mutex_unlock(&opp_table_lock); /* We have opp-table node now, iterate over it and add OPPs */ for_each_available_child_of_node(opp_np, np) { count++; - ret = _opp_add_static_v2(dev, np); + ret = _opp_add_static_v2(opp_table, dev, np); if (ret) { dev_err(dev, "%s: Failed to add OPP, %d\n", __func__, ret); @@ -400,15 +397,8 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np) } /* There should be one of more OPP defined */ - if (WARN_ON(!count)) - return -ENOENT; - - mutex_lock(&opp_table_lock); - - opp_table = _find_opp_table(dev); - if (WARN_ON(IS_ERR(opp_table))) { - ret = PTR_ERR(opp_table); - mutex_unlock(&opp_table_lock); + if (WARN_ON(!count)) { + ret = -ENOENT; goto free_table; } @@ -418,12 +408,12 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np) else opp_table->shared_opp = OPP_TABLE_ACCESS_EXCLUSIVE; - mutex_unlock(&opp_table_lock); - - return 0; + goto unlock; free_table: - dev_pm_opp_of_remove_table(dev); + _dev_pm_opp_remove_table(opp_table, dev, false); +unlock: + mutex_unlock(&opp_table_lock); return ret; } @@ -431,9 +421,10 @@ free_table: /* Initializes OPP tables based on old-deprecated bindings */ static int _of_add_opp_table_v1(struct device *dev) { + struct opp_table *opp_table; const struct property *prop; const __be32 *val; - int nr, ret; + int nr, ret = 0; prop = of_find_property(dev->of_node, "operating-points", NULL); if (!prop) @@ -451,22 +442,32 @@ static int _of_add_opp_table_v1(struct device *dev) return -EINVAL; } + mutex_lock(&opp_table_lock); + + opp_table = _add_opp_table(dev); + if (!opp_table) { + ret = -ENOMEM; + goto unlock; + } + val = prop->value; while (nr) { unsigned long freq = be32_to_cpup(val++) * 1000; unsigned long volt = be32_to_cpup(val++); - ret = _opp_add_v1(dev, freq, volt, false); + ret = _opp_add_v1(opp_table, dev, freq, volt, false); if (ret) { dev_err(dev, "%s: Failed to add OPP %ld (%d)\n", __func__, freq, ret); - dev_pm_opp_of_remove_table(dev); - return ret; + _dev_pm_opp_remove_table(opp_table, dev, false); + break; } nr -= 2; } - return 0; +unlock: + mutex_unlock(&opp_table_lock); + return ret; } /** diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h index c4b539a8533a..0a5eb4d1e8f7 100644 --- a/drivers/base/power/opp/opp.h +++ b/drivers/base/power/opp/opp.h @@ -191,13 +191,16 @@ struct opp_table { /* Routines internal to opp core */ struct opp_table *_find_opp_table(struct device *dev); +struct opp_table *_add_opp_table(struct device *dev); struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table); +void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev, bool remove_all); void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all); -struct dev_pm_opp *_opp_allocate(struct device *dev, struct opp_table **opp_table); -void _opp_free(struct dev_pm_opp *opp, struct opp_table *opp_table); +struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table); +void _opp_free(struct dev_pm_opp *opp); int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table); -int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt, bool dynamic); +int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic); void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of); +struct opp_table *_add_opp_table(struct device *dev); #ifdef CONFIG_OF void _of_init_opp_table(struct opp_table *opp_table, struct device *dev); -- cgit v1.2.3 From 3aa26a3b2ea63c4d09420e74421370655aa1cf8f Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:41:02 +0530 Subject: PM / OPP: Rename dev_pm_opp_get_suspend_opp() and return OPP rate There is only one user of dev_pm_opp_get_suspend_opp() and that uses it to get the OPP rate for the suspend_opp. Rename dev_pm_opp_get_suspend_opp() as dev_pm_opp_get_suspend_opp_freq() and return the rate directly from it. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index dde7dd099aa0..614d779ab6a2 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -328,32 +328,31 @@ unsigned long dev_pm_opp_get_max_transition_latency(struct device *dev) EXPORT_SYMBOL_GPL(dev_pm_opp_get_max_transition_latency); /** - * dev_pm_opp_get_suspend_opp() - Get suspend opp + * dev_pm_opp_get_suspend_opp_freq() - Get frequency of suspend opp in Hz * @dev: device for which we do this operation * - * Return: This function returns pointer to the suspend opp if it is - * defined and available, otherwise it returns NULL. - * - * Locking: This function must be called under rcu_read_lock(). opp is a rcu - * protected pointer. The reason for the same is that the opp pointer which is - * returned will remain valid for use with opp_get_{voltage, freq} only while - * under the locked area. The pointer returned must be used prior to unlocking - * with rcu_read_unlock() to maintain the integrity of the pointer. + * Return: This function returns the frequency of the OPP marked as suspend_opp + * if one is available, else returns 0; */ -struct dev_pm_opp *dev_pm_opp_get_suspend_opp(struct device *dev) +unsigned long dev_pm_opp_get_suspend_opp_freq(struct device *dev) { struct opp_table *opp_table; + unsigned long freq = 0; - opp_rcu_lockdep_assert(); + rcu_read_lock(); opp_table = _find_opp_table(dev); if (IS_ERR(opp_table) || !opp_table->suspend_opp || !opp_table->suspend_opp->available) - return NULL; + goto unlock; - return opp_table->suspend_opp; + freq = dev_pm_opp_get_freq(opp_table->suspend_opp); + +unlock: + rcu_read_unlock(); + return freq; } -EXPORT_SYMBOL_GPL(dev_pm_opp_get_suspend_opp); +EXPORT_SYMBOL_GPL(dev_pm_opp_get_suspend_opp_freq); /** * dev_pm_opp_get_opp_count() - Get number of opps available in the opp table -- cgit v1.2.3 From dc2c9ad52af45ebecf9743aacb1916ebb2f1e848 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:41:03 +0530 Subject: PM / OPP: Don't expose srcu_head to register notifiers Let the OPP core provide helpers to register notifiers for any device, instead of exposing srcu_head outside of the core. Signed-off-by: Viresh Kumar Acked-by: MyungJoo Ham Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 66 ++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 16 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index 614d779ab6a2..f35effad60d1 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -1860,29 +1860,63 @@ int dev_pm_opp_disable(struct device *dev, unsigned long freq) EXPORT_SYMBOL_GPL(dev_pm_opp_disable); /** - * dev_pm_opp_get_notifier() - find notifier_head of the device with opp - * @dev: device pointer used to lookup OPP table. + * dev_pm_opp_register_notifier() - Register OPP notifier for the device + * @dev: Device for which notifier needs to be registered + * @nb: Notifier block to be registered * - * Return: pointer to notifier head if found, otherwise -ENODEV or - * -EINVAL based on type of error casted as pointer. value must be checked - * with IS_ERR to determine valid pointer or error result. + * Return: 0 on success or a negative error value. + */ +int dev_pm_opp_register_notifier(struct device *dev, struct notifier_block *nb) +{ + struct opp_table *opp_table; + int ret; + + rcu_read_lock(); + + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) { + ret = PTR_ERR(opp_table); + goto unlock; + } + + ret = srcu_notifier_chain_register(&opp_table->srcu_head, nb); + +unlock: + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(dev_pm_opp_register_notifier); + +/** + * dev_pm_opp_unregister_notifier() - Unregister OPP notifier for the device + * @dev: Device for which notifier needs to be unregistered + * @nb: Notifier block to be unregistered * - * Locking: This function must be called under rcu_read_lock(). opp_table is a - * RCU protected pointer. The reason for the same is that the opp pointer which - * is returned will remain valid for use with opp_get_{voltage, freq} only while - * under the locked area. The pointer returned must be used prior to unlocking - * with rcu_read_unlock() to maintain the integrity of the pointer. + * Return: 0 on success or a negative error value. */ -struct srcu_notifier_head *dev_pm_opp_get_notifier(struct device *dev) +int dev_pm_opp_unregister_notifier(struct device *dev, + struct notifier_block *nb) { - struct opp_table *opp_table = _find_opp_table(dev); + struct opp_table *opp_table; + int ret; - if (IS_ERR(opp_table)) - return ERR_CAST(opp_table); /* matching type */ + rcu_read_lock(); + + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) { + ret = PTR_ERR(opp_table); + goto unlock; + } + + ret = srcu_notifier_chain_unregister(&opp_table->srcu_head, nb); - return &opp_table->srcu_head; +unlock: + rcu_read_unlock(); + + return ret; } -EXPORT_SYMBOL_GPL(dev_pm_opp_get_notifier); +EXPORT_SYMBOL(dev_pm_opp_unregister_notifier); /* * Free OPPs either created using static entries present in DT or even the -- cgit v1.2.3 From b6160e26936bcf1b9181bb34ad4f420ccd3f39f0 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 2 Jan 2017 14:41:04 +0530 Subject: PM / OPP: Split out part of _add_opp_table() and _remove_opp_table() Split out parts of _add_opp_table() and _remove_opp_table() into separate routines. This improves readability as well. Should result in no functional changes. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 76 +++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 32 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index f35effad60d1..622dd32f8dda 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -819,26 +819,12 @@ struct opp_device *_add_opp_dev(const struct device *dev, return opp_dev; } -/** - * _add_opp_table() - Find OPP table or allocate a new one - * @dev: device for which we do this operation - * - * It tries to find an existing table first, if it couldn't find one, it - * allocates a new OPP table and returns that. - * - * Return: valid opp_table pointer if success, else NULL. - */ -struct opp_table *_add_opp_table(struct device *dev) +static struct opp_table *_allocate_opp_table(struct device *dev) { struct opp_table *opp_table; struct opp_device *opp_dev; int ret; - /* Check for existing table for 'dev' first */ - opp_table = _find_opp_table(dev); - if (!IS_ERR(opp_table)) - return opp_table; - /* * Allocate a new OPP table. In the infrequent case where a new * device is needed to be added, we pay this penalty. @@ -874,6 +860,27 @@ struct opp_table *_add_opp_table(struct device *dev) return opp_table; } +/** + * _add_opp_table() - Find OPP table or allocate a new one + * @dev: device for which we do this operation + * + * It tries to find an existing table first, if it couldn't find one, it + * allocates a new OPP table and returns that. + * + * Return: valid opp_table pointer if success, else NULL. + */ +struct opp_table *_add_opp_table(struct device *dev) +{ + struct opp_table *opp_table; + + /* Check for existing table for 'dev' first */ + opp_table = _find_opp_table(dev); + if (!IS_ERR(opp_table)) + return opp_table; + + return _allocate_opp_table(dev); +} + /** * _kfree_device_rcu() - Free opp_table RCU handler * @head: RCU head @@ -886,6 +893,27 @@ static void _kfree_device_rcu(struct rcu_head *head) kfree_rcu(opp_table, rcu_head); } +static void _free_opp_table(struct opp_table *opp_table) +{ + struct opp_device *opp_dev; + + /* Release clk */ + if (!IS_ERR(opp_table->clk)) + clk_put(opp_table->clk); + + opp_dev = list_first_entry(&opp_table->dev_list, struct opp_device, + node); + + _remove_opp_dev(opp_dev, opp_table); + + /* dev_list must be empty now */ + WARN_ON(!list_empty(&opp_table->dev_list)); + + list_del_rcu(&opp_table->node); + call_srcu(&opp_table->srcu_head.srcu, &opp_table->rcu_head, + _kfree_device_rcu); +} + /** * _remove_opp_table() - Removes a OPP table * @opp_table: OPP table to be removed. @@ -894,8 +922,6 @@ static void _kfree_device_rcu(struct rcu_head *head) */ static void _remove_opp_table(struct opp_table *opp_table) { - struct opp_device *opp_dev; - if (!list_empty(&opp_table->opp_list)) return; @@ -911,21 +937,7 @@ static void _remove_opp_table(struct opp_table *opp_table) if (opp_table->set_opp) return; - /* Release clk */ - if (!IS_ERR(opp_table->clk)) - clk_put(opp_table->clk); - - opp_dev = list_first_entry(&opp_table->dev_list, struct opp_device, - node); - - _remove_opp_dev(opp_dev, opp_table); - - /* dev_list must be empty now */ - WARN_ON(!list_empty(&opp_table->dev_list)); - - list_del_rcu(&opp_table->node); - call_srcu(&opp_table->srcu_head.srcu, &opp_table->rcu_head, - _kfree_device_rcu); + _free_opp_table(opp_table); } void _opp_free(struct dev_pm_opp *opp) -- cgit v1.2.3 From 37a73ec0c9bbd712d24cc2035696893e5b6119a5 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 23 Jan 2017 10:11:41 +0530 Subject: PM / OPP: Add per OPP table mutex Add per OPP table lock to protect opp_table->opp_list. Note that at few places opp_list is used under the rcu_read_lock() and so a mutex can't be added there for now. This will be fixed by a later patch. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 31 +++++++++++++++++++++++++++---- drivers/base/power/opp/opp.h | 2 ++ 2 files changed, 29 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index 622dd32f8dda..dcebd5efb6a1 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -854,6 +854,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev) srcu_init_notifier_head(&opp_table->srcu_head); INIT_LIST_HEAD(&opp_table->opp_list); + mutex_init(&opp_table->lock); /* Secure the device table modification */ list_add_rcu(&opp_table->node, &opp_tables); @@ -909,6 +910,7 @@ static void _free_opp_table(struct opp_table *opp_table) /* dev_list must be empty now */ WARN_ON(!list_empty(&opp_table->dev_list)); + mutex_destroy(&opp_table->lock); list_del_rcu(&opp_table->node); call_srcu(&opp_table->srcu_head.srcu, &opp_table->rcu_head, _kfree_device_rcu); @@ -969,6 +971,8 @@ static void _kfree_opp_rcu(struct rcu_head *head) */ static void _opp_remove(struct opp_table *opp_table, struct dev_pm_opp *opp) { + mutex_lock(&opp_table->lock); + /* * Notify the changes in the availability of the operable * frequency/voltage list. @@ -978,6 +982,8 @@ static void _opp_remove(struct opp_table *opp_table, struct dev_pm_opp *opp) list_del_rcu(&opp->node); call_srcu(&opp_table->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu); + mutex_unlock(&opp_table->lock); + _remove_opp_table(opp_table); } @@ -1007,6 +1013,8 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq) if (IS_ERR(opp_table)) goto unlock; + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { if (opp->rate == freq) { found = true; @@ -1014,6 +1022,8 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq) } } + mutex_unlock(&opp_table->lock); + if (!found) { dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n", __func__, freq); @@ -1083,7 +1093,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table) { struct dev_pm_opp *opp; - struct list_head *head = &opp_table->opp_list; + struct list_head *head; int ret; /* @@ -1094,6 +1104,9 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, * loop, don't replace it with head otherwise it will become an infinite * loop. */ + mutex_lock(&opp_table->lock); + head = &opp_table->opp_list; + list_for_each_entry_rcu(opp, &opp_table->opp_list, node) { if (new_opp->rate > opp->rate) { head = &opp->node; @@ -1110,12 +1123,17 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, new_opp->supplies[0].u_volt, new_opp->available); /* Should we compare voltages for all regulators here ? */ - return opp->available && - new_opp->supplies[0].u_volt == opp->supplies[0].u_volt ? -EBUSY : -EEXIST; + ret = opp->available && + new_opp->supplies[0].u_volt == opp->supplies[0].u_volt ? -EBUSY : -EEXIST; + + mutex_unlock(&opp_table->lock); + return ret; } - new_opp->opp_table = opp_table; list_add_rcu(&new_opp->node, head); + mutex_unlock(&opp_table->lock); + + new_opp->opp_table = opp_table; ret = opp_debug_create_one(new_opp, opp_table); if (ret) @@ -1779,6 +1797,8 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, goto unlock; } + mutex_lock(&opp_table->lock); + /* Do we have the frequency? */ list_for_each_entry(tmp_opp, &opp_table->opp_list, node) { if (tmp_opp->rate == freq) { @@ -1786,6 +1806,9 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, break; } } + + mutex_unlock(&opp_table->lock); + if (IS_ERR(opp)) { r = PTR_ERR(opp); goto unlock; diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h index 0a5eb4d1e8f7..105243b06373 100644 --- a/drivers/base/power/opp/opp.h +++ b/drivers/base/power/opp/opp.h @@ -131,6 +131,7 @@ enum opp_table_access { * @rcu_head: RCU callback head used for deferred freeing * @dev_list: list of devices that share these OPPs * @opp_list: table of opps + * @lock: mutex protecting the opp_list. * @np: struct device_node pointer for opp's DT node. * @clock_latency_ns_max: Max clock latency in nanoseconds. * @shared_opp: OPP is shared between multiple devices. @@ -163,6 +164,7 @@ struct opp_table { struct rcu_head rcu_head; struct list_head dev_list; struct list_head opp_list; + struct mutex lock; struct device_node *np; unsigned long clock_latency_ns_max; -- cgit v1.2.3 From f067a982cefa8df5642212bb0c7e25974831f781 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 23 Jan 2017 10:11:42 +0530 Subject: PM / OPP: Add 'struct kref' to OPP table Add kref to struct opp_table for easier accounting of the OPP table. Note that the new routine dev_pm_opp_get_opp_table() takes the reference from under the opp_table_lock, which guarantees that the OPP table doesn't get freed unless dev_pm_opp_put_opp_table() is called for the OPP table. Two separate release mechanisms are added: locked and unlocked. In unlocked version the routines aren't required to take/drop opp_table_lock as the callers have already done that. This is required to avoid breaking git bisect, otherwise we may get lockdeps between commits. Once all the users of OPP table are updated the unlocked version shall be removed. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 51 +++++++++++++++++++++++++++++++++++++++++-- drivers/base/power/opp/opp.h | 3 +++ 2 files changed, 52 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index dcebd5efb6a1..ccc0d8913fd0 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -855,6 +855,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev) srcu_init_notifier_head(&opp_table->srcu_head); INIT_LIST_HEAD(&opp_table->opp_list); mutex_init(&opp_table->lock); + kref_init(&opp_table->kref); /* Secure the device table modification */ list_add_rcu(&opp_table->node, &opp_tables); @@ -894,8 +895,36 @@ static void _kfree_device_rcu(struct rcu_head *head) kfree_rcu(opp_table, rcu_head); } -static void _free_opp_table(struct opp_table *opp_table) +void _get_opp_table_kref(struct opp_table *opp_table) { + kref_get(&opp_table->kref); +} + +struct opp_table *dev_pm_opp_get_opp_table(struct device *dev) +{ + struct opp_table *opp_table; + + /* Hold our table modification lock here */ + mutex_lock(&opp_table_lock); + + opp_table = _find_opp_table(dev); + if (!IS_ERR(opp_table)) { + _get_opp_table_kref(opp_table); + goto unlock; + } + + opp_table = _allocate_opp_table(dev); + +unlock: + mutex_unlock(&opp_table_lock); + + return opp_table; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_get_opp_table); + +static void _opp_table_kref_release_unlocked(struct kref *kref) +{ + struct opp_table *opp_table = container_of(kref, struct opp_table, kref); struct opp_device *opp_dev; /* Release clk */ @@ -916,6 +945,24 @@ static void _free_opp_table(struct opp_table *opp_table) _kfree_device_rcu); } +static void dev_pm_opp_put_opp_table_unlocked(struct opp_table *opp_table) +{ + kref_put(&opp_table->kref, _opp_table_kref_release_unlocked); +} + +static void _opp_table_kref_release(struct kref *kref) +{ + _opp_table_kref_release_unlocked(kref); + mutex_unlock(&opp_table_lock); +} + +void dev_pm_opp_put_opp_table(struct opp_table *opp_table) +{ + kref_put_mutex(&opp_table->kref, _opp_table_kref_release, + &opp_table_lock); +} +EXPORT_SYMBOL_GPL(dev_pm_opp_put_opp_table); + /** * _remove_opp_table() - Removes a OPP table * @opp_table: OPP table to be removed. @@ -939,7 +986,7 @@ static void _remove_opp_table(struct opp_table *opp_table) if (opp_table->set_opp) return; - _free_opp_table(opp_table); + dev_pm_opp_put_opp_table_unlocked(opp_table); } void _opp_free(struct dev_pm_opp *opp) diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h index 105243b06373..aae4d8f480ef 100644 --- a/drivers/base/power/opp/opp.h +++ b/drivers/base/power/opp/opp.h @@ -131,6 +131,7 @@ enum opp_table_access { * @rcu_head: RCU callback head used for deferred freeing * @dev_list: list of devices that share these OPPs * @opp_list: table of opps + * @kref: for reference count of the table. * @lock: mutex protecting the opp_list. * @np: struct device_node pointer for opp's DT node. * @clock_latency_ns_max: Max clock latency in nanoseconds. @@ -164,6 +165,7 @@ struct opp_table { struct rcu_head rcu_head; struct list_head dev_list; struct list_head opp_list; + struct kref kref; struct mutex lock; struct device_node *np; @@ -192,6 +194,7 @@ struct opp_table { }; /* Routines internal to opp core */ +void _get_opp_table_kref(struct opp_table *opp_table); struct opp_table *_find_opp_table(struct device *dev); struct opp_table *_add_opp_table(struct device *dev); struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table); -- cgit v1.2.3 From fa30184d192ec78d443cf6d3abc37d9eb3b9253e Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 23 Jan 2017 10:11:43 +0530 Subject: PM / OPP: Return opp_table from dev_pm_opp_set_*() routines Now that we have proper kernel reference infrastructure in place for OPP tables, use it to guarantee that the OPP table isn't freed while being used by the callers of dev_pm_opp_set_*() APIs. Make them all return the pointer to the OPP table after taking its reference and put the reference back with dev_pm_opp_put_*() APIs. Now that the OPP table wouldn't get freed while these routines are executing after dev_pm_opp_get_opp_table() is called, there is no need to take opp_table_lock. Drop them as well. Remove the rcu specific comments from these routines as they aren't relevant anymore. Note that prototypes of dev_pm_opp_{set|put}_regulators() were already updated by another patch. Signed-off-by: Viresh Kumar Reviewed-by: Stephen Boyd Signed-off-by: Rafael J. Wysocki --- drivers/base/power/opp/core.c | 243 +++++++++--------------------------------- 1 file changed, 50 insertions(+), 193 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c index ccc0d8913fd0..1af349ab630e 100644 --- a/drivers/base/power/opp/core.c +++ b/drivers/base/power/opp/core.c @@ -974,18 +974,6 @@ static void _remove_opp_table(struct opp_table *opp_table) if (!list_empty(&opp_table->opp_list)) return; - if (opp_table->supported_hw) - return; - - if (opp_table->prop_name) - return; - - if (opp_table->regulators) - return; - - if (opp_table->set_opp) - return; - dev_pm_opp_put_opp_table_unlocked(opp_table); } @@ -1277,27 +1265,16 @@ free_opp: * specify the hierarchy of versions it supports. OPP layer will then enable * OPPs, which are available for those versions, based on its 'opp-supported-hw' * property. - * - * Locking: The internal opp_table and opp structures are RCU protected. - * Hence this function internally uses RCU updater strategy with mutex locks - * to keep the integrity of the internal data structures. Callers should ensure - * that this function is *NOT* called under RCU protection or in contexts where - * mutex cannot be locked. */ -int dev_pm_opp_set_supported_hw(struct device *dev, const u32 *versions, - unsigned int count) +struct opp_table *dev_pm_opp_set_supported_hw(struct device *dev, + const u32 *versions, unsigned int count) { struct opp_table *opp_table; - int ret = 0; - - /* Hold our table modification lock here */ - mutex_lock(&opp_table_lock); + int ret; - opp_table = _add_opp_table(dev); - if (!opp_table) { - ret = -ENOMEM; - goto unlock; - } + opp_table = dev_pm_opp_get_opp_table(dev); + if (!opp_table) + return ERR_PTR(-ENOMEM); /* Make sure there are no concurrent readers while updating opp_table */ WARN_ON(!list_empty(&opp_table->opp_list)); @@ -1318,65 +1295,40 @@ int dev_pm_opp_set_supported_hw(struct device *dev, const u32 *versions, } opp_table->supported_hw_count = count; - mutex_unlock(&opp_table_lock); - return 0; + + return opp_table; err: - _remove_opp_table(opp_table); -unlock: - mutex_unlock(&opp_table_lock); + dev_pm_opp_put_opp_table(opp_table); - return ret; + return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(dev_pm_opp_set_supported_hw); /** * dev_pm_opp_put_supported_hw() - Releases resources blocked for supported hw - * @dev: Device for which supported-hw has to be put. + * @opp_table: OPP table returned by dev_pm_opp_set_supported_hw(). * * This is required only for the V2 bindings, and is called for a matching * dev_pm_opp_set_supported_hw(). Until this is called, the opp_table structure * will not be freed. - * - * Locking: The internal opp_table and opp structures are RCU protected. - * Hence this function internally uses RCU updater strategy with mutex locks - * to keep the integrity of the internal data structures. Callers should ensure - * that this function is *NOT* called under RCU protection or in contexts where - * mutex cannot be locked. */ -void dev_pm_opp_put_supported_hw(struct device *dev) +void dev_pm_opp_put_supported_hw(struct opp_table *opp_table) { - struct opp_table *opp_table; - - /* Hold our table modification lock here */ - mutex_lock(&opp_table_lock); - - /* Check for existing table for 'dev' first */ - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) { - dev_err(dev, "Failed to find opp_table: %ld\n", - PTR_ERR(opp_table)); - goto unlock; - } - /* Make sure there are no concurrent readers while updating opp_table */ WARN_ON(!list_empty(&opp_table->opp_list)); if (!opp_table->supported_hw) { - dev_err(dev, "%s: Doesn't hav