regulator: core: fix a possible race in disable_work handling
authorTirupathi Reddy <tirupath@codeaurora.org>
Wed, 12 Jul 2017 11:38:13 +0000 (17:08 +0530)
committerMark Brown <broonie@kernel.org>
Thu, 20 Jul 2017 12:22:29 +0000 (13:22 +0100)
A race condition between queueing and processing the disable_work
instances results in having a work instance in the queue and the
deferred_disables variable of regulator device structure having a
value '0'. If no new regulator_disable_deferred() call later from
clients, the deferred_disables variable value remains '0' and hits
BUG() in regulator_disable_work() when the queued instance scheduled
for processing the work.

The race occurs as below:

Core-0      Core-1
.....        /* deferred_disables = 2 */   .....
.....        /* disable_work is queued */  .....
.....      .....
regulator_disable_deferred:  regulator_disable_work:
   mutex_lock(&rdev->mutex);      .....
   rdev->deferred_disables++;              .....
   mutex_unlock(&rdev->mutex);      .....
   queue_delayed_work(...)     mutex_lock(&rdev->mutex);
.....     count =rdev->deferred_disables;
.....     rdev->deferred_disables = 0;
.....      .....
.....     mutex_unlock(&rdev->mutex);
.....      .....
.....     return;
.....      .....
/* No new regulator_disable_deferred() calls from clients */
/* The newly queued instance is scheduled for processing */
.....      .....
regulator_disable_work:
.....
   mutex_lock(&rdev->mutex);
   BUG_ON(!rdev->deferred_disables); /* deferred_disables = 0 */

The race is fixed by removing the work instance that is queued while
processing the previous queued instance. Cancel the newly queued instance
from disable_work() handler just after reset the deferred_disables variable
to value '0'. Also move the work queueing step before mutex_unlock in
regulator_disable_deferred().

Also use mod_delayed_work() in the pace of queue_delayed_work() as
queue_delayed_work() always uses the delay requested in the first call
when multiple consumers call regulator_disable_deferred() close in time
and does not guarantee the semantics of regulator_disable_deferred().

Signed-off-by: Tirupathi Reddy <tirupath@codeaurora.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/regulator/core.c

index e567fa5..9f4d484 100644 (file)
@@ -2396,6 +2396,14 @@ static void regulator_disable_work(struct work_struct *work)
        count = rdev->deferred_disables;
        rdev->deferred_disables = 0;
 
+       /*
+        * Workqueue functions queue the new work instance while the previous
+        * work instance is being processed. Cancel the queued work instance
+        * as the work instance under processing does the job of the queued
+        * work instance.
+        */
+       cancel_delayed_work(&rdev->disable_work);
+
        for (i = 0; i < count; i++) {
                ret = _regulator_disable(rdev);
                if (ret != 0)
@@ -2439,10 +2447,10 @@ int regulator_disable_deferred(struct regulator *regulator, int ms)
 
        mutex_lock(&rdev->mutex);
        rdev->deferred_disables++;
+       mod_delayed_work(system_power_efficient_wq, &rdev->disable_work,
+                        msecs_to_jiffies(ms));
        mutex_unlock(&rdev->mutex);
 
-       queue_delayed_work(system_power_efficient_wq, &rdev->disable_work,
-                          msecs_to_jiffies(ms));
        return 0;
 }
 EXPORT_SYMBOL_GPL(regulator_disable_deferred);