Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

regulator: avoid deadlock when disabling regulator with supply

I have a regulator A that sets regulator B as its supply. When I call
set_supply to add B as the supply for A, regulator A gets added to the
supply_list for regulator B.

When I call regulator_disable(A), I end up with a call chain like this:

regulator_disable(A)
> mutex_lock(A)
> _regulator_disable(A)
>> _regulator_disable(B)
>>> _notifier_call_chain(B)
>>>> mutex_lock(A)

Which results in dead lock since we are trying to acquire the mutex lock
for regulator A which we already hold.

This patch addresses this issue by moving the call to disable regulator
B outside of the lock aquired inside the initial call to
regulator_disable.

This change also addresses the issue of not acquiring the mutex for
regulator B before calling _regulator_disable(B).

Signed-off-by: Jeffrey Carlyle <jeff.carlyle@motorola.com>
Acked-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: Liam Girdwood <lrg@slimlogic.co.uk>

authored by

Jeffrey Carlyle and committed by
Liam Girdwood
8cbf811d 688fe99a

+26 -9
+26 -9
drivers/regulator/core.c
··· 64 64 }; 65 65 66 66 static int _regulator_is_enabled(struct regulator_dev *rdev); 67 - static int _regulator_disable(struct regulator_dev *rdev); 67 + static int _regulator_disable(struct regulator_dev *rdev, 68 + struct regulator_dev **supply_rdev_ptr); 68 69 static int _regulator_get_voltage(struct regulator_dev *rdev); 69 70 static int _regulator_get_current_limit(struct regulator_dev *rdev); 70 71 static unsigned int _regulator_get_mode(struct regulator_dev *rdev); ··· 1355 1354 EXPORT_SYMBOL_GPL(regulator_enable); 1356 1355 1357 1356 /* locks held by regulator_disable() */ 1358 - static int _regulator_disable(struct regulator_dev *rdev) 1357 + static int _regulator_disable(struct regulator_dev *rdev, 1358 + struct regulator_dev **supply_rdev_ptr) 1359 1359 { 1360 1360 int ret = 0; 1361 1361 ··· 1384 1382 } 1385 1383 1386 1384 /* decrease our supplies ref count and disable if required */ 1387 - if (rdev->supply) 1388 - _regulator_disable(rdev->supply); 1385 + *supply_rdev_ptr = rdev->supply; 1389 1386 1390 1387 rdev->use_count = 0; 1391 1388 } else if (rdev->use_count > 1) { ··· 1414 1413 int regulator_disable(struct regulator *regulator) 1415 1414 { 1416 1415 struct regulator_dev *rdev = regulator->rdev; 1416 + struct regulator_dev *supply_rdev = NULL; 1417 1417 int ret = 0; 1418 1418 1419 1419 mutex_lock(&rdev->mutex); 1420 - ret = _regulator_disable(rdev); 1420 + ret = _regulator_disable(rdev, &supply_rdev); 1421 1421 mutex_unlock(&rdev->mutex); 1422 + 1423 + /* decrease our supplies ref count and disable if required */ 1424 + while (supply_rdev != NULL) { 1425 + rdev = supply_rdev; 1426 + 1427 + mutex_lock(&rdev->mutex); 1428 + _regulator_disable(rdev, &supply_rdev); 1429 + mutex_unlock(&rdev->mutex); 1430 + } 1431 + 1422 1432 return ret; 1423 1433 } 1424 1434 EXPORT_SYMBOL_GPL(regulator_disable); 1425 1435 1426 1436 /* locks held by regulator_force_disable() */ 1427 - static int _regulator_force_disable(struct regulator_dev *rdev) 1437 + static int _regulator_force_disable(struct regulator_dev *rdev, 1438 + struct regulator_dev **supply_rdev_ptr) 1428 1439 { 1429 1440 int ret = 0; 1430 1441 ··· 1455 1442 } 1456 1443 1457 1444 /* decrease our supplies ref count and disable if required */ 1458 - if (rdev->supply) 1459 - _regulator_disable(rdev->supply); 1445 + *supply_rdev_ptr = rdev->supply; 1460 1446 1461 1447 rdev->use_count = 0; 1462 1448 return ret; ··· 1472 1460 */ 1473 1461 int regulator_force_disable(struct regulator *regulator) 1474 1462 { 1463 + struct regulator_dev *supply_rdev = NULL; 1475 1464 int ret; 1476 1465 1477 1466 mutex_lock(&regulator->rdev->mutex); 1478 1467 regulator->uA_load = 0; 1479 - ret = _regulator_force_disable(regulator->rdev); 1468 + ret = _regulator_force_disable(regulator->rdev, &supply_rdev); 1480 1469 mutex_unlock(&regulator->rdev->mutex); 1470 + 1471 + if (supply_rdev) 1472 + regulator_disable(get_device_regulator(rdev_get_dev(supply_rdev))); 1473 + 1481 1474 return ret; 1482 1475 } 1483 1476 EXPORT_SYMBOL_GPL(regulator_force_disable);