PM / OPP: Add per OPP table mutex
authorViresh Kumar <viresh.kumar@linaro.org>
Mon, 23 Jan 2017 04:41:41 +0000 (10:11 +0530)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 30 Jan 2017 08:22:20 +0000 (09:22 +0100)
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 <viresh.kumar@linaro.org>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/base/power/opp/core.c
drivers/base/power/opp/opp.h

index 622dd32f8dda3fe22ac3f5ea9b5d67fb0c8c2fdb..dcebd5efb6a16175976475e08ba55556fee05f13 100644 (file)
@@ -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;
index 0a5eb4d1e8f7d31812b9c4e7f5cc72b3a4c524eb..105243b063737d0b9d05e0ece1a97b17a802a4e4 100644 (file)
@@ -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;