forked from mirrors/linux
		
	cpufreq: scpi/scmi: Fix freeing of dynamic OPPs
Since the commit2a4eb7358a"OPP: Don't remove dynamic OPPs from _dev_pm_opp_remove_table()", dynamically created OPP aren't automatically removed anymore by dev_pm_opp_cpumask_remove_table(). This affects the scpi and scmi cpufreq drivers which no longer free OPPs on failures or on invocations of the policy->exit() callback. Create a generic OPP helper dev_pm_opp_remove_all_dynamic() which can be called from these drivers instead of dev_pm_opp_cpumask_remove_table(). In dev_pm_opp_remove_all_dynamic(), we need to make sure that the opp_list isn't getting accessed simultaneously from other parts of the OPP core while the helper is freeing dynamic OPPs, i.e. we can't drop the opp_table->lock while traversing through the OPP list. And to accomplish that, this patch also creates _opp_kref_release_unlocked() which can be called from this new helper with the opp_table lock already held. Cc: 4.20 <stable@vger.kernel.org> # v4.20 Reported-by: Valentin Schneider <valentin.schneider@arm.com> Fixes:2a4eb7358a"OPP: Don't remove dynamic OPPs from _dev_pm_opp_remove_table()" Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Tested-by: Valentin Schneider <valentin.schneider@arm.com> Reviewed-by: Sudeep Holla <sudeep.holla@arm.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
		
							parent
							
								
									088d923a11
								
							
						
					
					
						commit
						1690d8bb91
					
				
					 4 changed files with 67 additions and 9 deletions
				
			
		|  | @ -176,7 +176,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy) | ||||||
| out_free_priv: | out_free_priv: | ||||||
| 	kfree(priv); | 	kfree(priv); | ||||||
| out_free_opp: | out_free_opp: | ||||||
| 	dev_pm_opp_cpumask_remove_table(policy->cpus); | 	dev_pm_opp_remove_all_dynamic(cpu_dev); | ||||||
| 
 | 
 | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  | @ -188,7 +188,7 @@ static int scmi_cpufreq_exit(struct cpufreq_policy *policy) | ||||||
| 	cpufreq_cooling_unregister(priv->cdev); | 	cpufreq_cooling_unregister(priv->cdev); | ||||||
| 	dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); | 	dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); | ||||||
| 	kfree(priv); | 	kfree(priv); | ||||||
| 	dev_pm_opp_cpumask_remove_table(policy->related_cpus); | 	dev_pm_opp_remove_all_dynamic(priv->cpu_dev); | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -177,7 +177,7 @@ static int scpi_cpufreq_init(struct cpufreq_policy *policy) | ||||||
| out_free_priv: | out_free_priv: | ||||||
| 	kfree(priv); | 	kfree(priv); | ||||||
| out_free_opp: | out_free_opp: | ||||||
| 	dev_pm_opp_cpumask_remove_table(policy->cpus); | 	dev_pm_opp_remove_all_dynamic(cpu_dev); | ||||||
| 
 | 
 | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  | @ -190,7 +190,7 @@ static int scpi_cpufreq_exit(struct cpufreq_policy *policy) | ||||||
| 	clk_put(priv->clk); | 	clk_put(priv->clk); | ||||||
| 	dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); | 	dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); | ||||||
| 	kfree(priv); | 	kfree(priv); | ||||||
| 	dev_pm_opp_cpumask_remove_table(policy->related_cpus); | 	dev_pm_opp_remove_all_dynamic(priv->cpu_dev); | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -988,11 +988,9 @@ void _opp_free(struct dev_pm_opp *opp) | ||||||
| 	kfree(opp); | 	kfree(opp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void _opp_kref_release(struct kref *kref) | static void _opp_kref_release(struct dev_pm_opp *opp, | ||||||
|  | 			      struct opp_table *opp_table) | ||||||
| { | { | ||||||
| 	struct dev_pm_opp *opp = container_of(kref, struct dev_pm_opp, kref); |  | ||||||
| 	struct opp_table *opp_table = opp->opp_table; |  | ||||||
| 
 |  | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Notify the changes in the availability of the operable | 	 * Notify the changes in the availability of the operable | ||||||
| 	 * frequency/voltage list. | 	 * frequency/voltage list. | ||||||
|  | @ -1002,7 +1000,22 @@ static void _opp_kref_release(struct kref *kref) | ||||||
| 	opp_debug_remove_one(opp); | 	opp_debug_remove_one(opp); | ||||||
| 	list_del(&opp->node); | 	list_del(&opp->node); | ||||||
| 	kfree(opp); | 	kfree(opp); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|  | static void _opp_kref_release_unlocked(struct kref *kref) | ||||||
|  | { | ||||||
|  | 	struct dev_pm_opp *opp = container_of(kref, struct dev_pm_opp, kref); | ||||||
|  | 	struct opp_table *opp_table = opp->opp_table; | ||||||
|  | 
 | ||||||
|  | 	_opp_kref_release(opp, opp_table); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void _opp_kref_release_locked(struct kref *kref) | ||||||
|  | { | ||||||
|  | 	struct dev_pm_opp *opp = container_of(kref, struct dev_pm_opp, kref); | ||||||
|  | 	struct opp_table *opp_table = opp->opp_table; | ||||||
|  | 
 | ||||||
|  | 	_opp_kref_release(opp, opp_table); | ||||||
| 	mutex_unlock(&opp_table->lock); | 	mutex_unlock(&opp_table->lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1013,10 +1026,16 @@ void dev_pm_opp_get(struct dev_pm_opp *opp) | ||||||
| 
 | 
 | ||||||
| void dev_pm_opp_put(struct dev_pm_opp *opp) | void dev_pm_opp_put(struct dev_pm_opp *opp) | ||||||
| { | { | ||||||
| 	kref_put_mutex(&opp->kref, _opp_kref_release, &opp->opp_table->lock); | 	kref_put_mutex(&opp->kref, _opp_kref_release_locked, | ||||||
|  | 		       &opp->opp_table->lock); | ||||||
| } | } | ||||||
| EXPORT_SYMBOL_GPL(dev_pm_opp_put); | EXPORT_SYMBOL_GPL(dev_pm_opp_put); | ||||||
| 
 | 
 | ||||||
|  | static void dev_pm_opp_put_unlocked(struct dev_pm_opp *opp) | ||||||
|  | { | ||||||
|  | 	kref_put(&opp->kref, _opp_kref_release_unlocked); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * dev_pm_opp_remove()  - Remove an OPP from OPP table |  * dev_pm_opp_remove()  - Remove an OPP from OPP table | ||||||
|  * @dev:	device for which we do this operation |  * @dev:	device for which we do this operation | ||||||
|  | @ -1060,6 +1079,40 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq) | ||||||
| } | } | ||||||
| EXPORT_SYMBOL_GPL(dev_pm_opp_remove); | EXPORT_SYMBOL_GPL(dev_pm_opp_remove); | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * dev_pm_opp_remove_all_dynamic() - Remove all dynamically created OPPs | ||||||
|  |  * @dev:	device for which we do this operation | ||||||
|  |  * | ||||||
|  |  * This function removes all dynamically created OPPs from the opp table. | ||||||
|  |  */ | ||||||
|  | void dev_pm_opp_remove_all_dynamic(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct opp_table *opp_table; | ||||||
|  | 	struct dev_pm_opp *opp, *temp; | ||||||
|  | 	int count = 0; | ||||||
|  | 
 | ||||||
|  | 	opp_table = _find_opp_table(dev); | ||||||
|  | 	if (IS_ERR(opp_table)) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	mutex_lock(&opp_table->lock); | ||||||
|  | 	list_for_each_entry_safe(opp, temp, &opp_table->opp_list, node) { | ||||||
|  | 		if (opp->dynamic) { | ||||||
|  | 			dev_pm_opp_put_unlocked(opp); | ||||||
|  | 			count++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	mutex_unlock(&opp_table->lock); | ||||||
|  | 
 | ||||||
|  | 	/* Drop the references taken by dev_pm_opp_add() */ | ||||||
|  | 	while (count--) | ||||||
|  | 		dev_pm_opp_put_opp_table(opp_table); | ||||||
|  | 
 | ||||||
|  | 	/* Drop the reference taken by _find_opp_table() */ | ||||||
|  | 	dev_pm_opp_put_opp_table(opp_table); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(dev_pm_opp_remove_all_dynamic); | ||||||
|  | 
 | ||||||
| struct dev_pm_opp *_opp_allocate(struct opp_table *table) | struct dev_pm_opp *_opp_allocate(struct opp_table *table) | ||||||
| { | { | ||||||
| 	struct dev_pm_opp *opp; | 	struct dev_pm_opp *opp; | ||||||
|  |  | ||||||
|  | @ -108,6 +108,7 @@ void dev_pm_opp_put(struct dev_pm_opp *opp); | ||||||
| int dev_pm_opp_add(struct device *dev, unsigned long freq, | int dev_pm_opp_add(struct device *dev, unsigned long freq, | ||||||
| 		   unsigned long u_volt); | 		   unsigned long u_volt); | ||||||
| void dev_pm_opp_remove(struct device *dev, unsigned long freq); | void dev_pm_opp_remove(struct device *dev, unsigned long freq); | ||||||
|  | void dev_pm_opp_remove_all_dynamic(struct device *dev); | ||||||
| 
 | 
 | ||||||
| int dev_pm_opp_enable(struct device *dev, unsigned long freq); | int dev_pm_opp_enable(struct device *dev, unsigned long freq); | ||||||
| 
 | 
 | ||||||
|  | @ -217,6 +218,10 @@ static inline void dev_pm_opp_remove(struct device *dev, unsigned long freq) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static inline void dev_pm_opp_remove_all_dynamic(struct device *dev) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static inline int dev_pm_opp_enable(struct device *dev, unsigned long freq) | static inline int dev_pm_opp_enable(struct device *dev, unsigned long freq) | ||||||
| { | { | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Viresh Kumar
						Viresh Kumar