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)
commitc9ccaa0cac3fc8e7d17a668aabfdf632c7c0517a
tree18bec2aa296cb39252cfa511a7f0932ce19ae5b0
parent5771a8c08880cdca3bfb4a3fc6d309d6bba20877
regulator: core: fix a possible race in disable_work handling

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