Skip to content

Commit 754bd2b

Browse files
committed
hwmon: (pmbus/core) Protect regulator operations with mutex
The regulator operations pmbus_regulator_get_voltage(), pmbus_regulator_set_voltage(), and pmbus_regulator_list_voltage() access PMBus registers and shared data but were not protected by the update_lock mutex. This could lead to race conditions. However, adding mutex protection directly to these functions causes a deadlock because pmbus_regulator_notify() (which calls regulator_notifier_call_chain()) is often called with the mutex already held (e.g., from pmbus_fault_handler()). If a regulator callback then calls one of the now-protected voltage functions, it will attempt to acquire the same mutex. Rework pmbus_regulator_notify() to utilize a worker function to send notifications outside of the mutex protection. Events are stored as atomics in a per-page bitmask and processed by the worker. Initialize the worker and its associated data during regulator registration, and ensure it is cancelled on device removal using devm_add_action_or_reset(). While at it, remove the unnecessary include of linux/of.h. Cc: Sanman Pradhan <[email protected]> Fixes: ddbb4db ("hwmon: (pmbus) Add regulator support") Reviewed-by: Sanman Pradhan <[email protected]> Signed-off-by: Guenter Roeck <[email protected]>
1 parent cd65847 commit 754bd2b

1 file changed

Lines changed: 89 additions & 25 deletions

File tree

drivers/hwmon/pmbus/pmbus_core.c

Lines changed: 89 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Copyright (c) 2012 Guenter Roeck
77
*/
88

9+
#include <linux/atomic.h>
910
#include <linux/debugfs.h>
1011
#include <linux/delay.h>
1112
#include <linux/dcache.h>
@@ -21,8 +22,8 @@
2122
#include <linux/pmbus.h>
2223
#include <linux/regulator/driver.h>
2324
#include <linux/regulator/machine.h>
24-
#include <linux/of.h>
2525
#include <linux/thermal.h>
26+
#include <linux/workqueue.h>
2627
#include "pmbus.h"
2728

2829
/*
@@ -112,6 +113,11 @@ struct pmbus_data {
112113

113114
struct mutex update_lock;
114115

116+
#if IS_ENABLED(CONFIG_REGULATOR)
117+
atomic_t regulator_events[PMBUS_PAGES];
118+
struct work_struct regulator_notify_work;
119+
#endif
120+
115121
bool has_status_word; /* device uses STATUS_WORD register */
116122
int (*read_status)(struct i2c_client *client, int page);
117123

@@ -3228,12 +3234,19 @@ static int pmbus_regulator_get_voltage(struct regulator_dev *rdev)
32283234
.class = PSC_VOLTAGE_OUT,
32293235
.convert = true,
32303236
};
3237+
int ret;
32313238

3239+
mutex_lock(&data->update_lock);
32323240
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT);
3233-
if (s.data < 0)
3234-
return s.data;
3241+
if (s.data < 0) {
3242+
ret = s.data;
3243+
goto unlock;
3244+
}
32353245

3236-
return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
3246+
ret = (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
3247+
unlock:
3248+
mutex_unlock(&data->update_lock);
3249+
return ret;
32373250
}
32383251

32393252
static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
@@ -3250,16 +3263,22 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
32503263
};
32513264
int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */
32523265
int low, high;
3266+
int ret;
32533267

32543268
*selector = 0;
32553269

3270+
mutex_lock(&data->update_lock);
32563271
low = pmbus_regulator_get_low_margin(client, s.page);
3257-
if (low < 0)
3258-
return low;
3272+
if (low < 0) {
3273+
ret = low;
3274+
goto unlock;
3275+
}
32593276

32603277
high = pmbus_regulator_get_high_margin(client, s.page);
3261-
if (high < 0)
3262-
return high;
3278+
if (high < 0) {
3279+
ret = high;
3280+
goto unlock;
3281+
}
32633282

32643283
/* Make sure we are within margins */
32653284
if (low > val)
@@ -3269,7 +3288,10 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
32693288

32703289
val = pmbus_data2reg(data, &s, val);
32713290

3272-
return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
3291+
ret = _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
3292+
unlock:
3293+
mutex_unlock(&data->update_lock);
3294+
return ret;
32733295
}
32743296

32753297
static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
@@ -3279,6 +3301,7 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
32793301
struct i2c_client *client = to_i2c_client(dev->parent);
32803302
struct pmbus_data *data = i2c_get_clientdata(client);
32813303
int val, low, high;
3304+
int ret;
32823305

32833306
if (data->flags & PMBUS_VOUT_PROTECTED)
32843307
return 0;
@@ -3291,18 +3314,29 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
32913314
val = DIV_ROUND_CLOSEST(rdev->desc->min_uV +
32923315
(rdev->desc->uV_step * selector), 1000); /* convert to mV */
32933316

3317+
mutex_lock(&data->update_lock);
3318+
32943319
low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev));
3295-
if (low < 0)
3296-
return low;
3320+
if (low < 0) {
3321+
ret = low;
3322+
goto unlock;
3323+
}
32973324

32983325
high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev));
3299-
if (high < 0)
3300-
return high;
3326+
if (high < 0) {
3327+
ret = high;
3328+
goto unlock;
3329+
}
33013330

3302-
if (val >= low && val <= high)
3303-
return val * 1000; /* unit is uV */
3331+
if (val >= low && val <= high) {
3332+
ret = val * 1000; /* unit is uV */
3333+
goto unlock;
3334+
}
33043335

3305-
return 0;
3336+
ret = 0;
3337+
unlock:
3338+
mutex_unlock(&data->update_lock);
3339+
return ret;
33063340
}
33073341

33083342
const struct regulator_ops pmbus_regulator_ops = {
@@ -3333,12 +3367,42 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev,
33333367
}
33343368
EXPORT_SYMBOL_NS_GPL(pmbus_regulator_init_cb, "PMBUS");
33353369

3370+
static void pmbus_regulator_notify_work_cancel(void *data)
3371+
{
3372+
struct pmbus_data *pdata = data;
3373+
3374+
cancel_work_sync(&pdata->regulator_notify_work);
3375+
}
3376+
3377+
static void pmbus_regulator_notify_worker(struct work_struct *work)
3378+
{
3379+
struct pmbus_data *data =
3380+
container_of(work, struct pmbus_data, regulator_notify_work);
3381+
int i, j;
3382+
3383+
for (i = 0; i < data->info->pages; i++) {
3384+
int event;
3385+
3386+
event = atomic_xchg(&data->regulator_events[i], 0);
3387+
if (!event)
3388+
continue;
3389+
3390+
for (j = 0; j < data->info->num_regulators; j++) {
3391+
if (i == rdev_get_id(data->rdevs[j])) {
3392+
regulator_notifier_call_chain(data->rdevs[j],
3393+
event, NULL);
3394+
break;
3395+
}
3396+
}
3397+
}
3398+
}
3399+
33363400
static int pmbus_regulator_register(struct pmbus_data *data)
33373401
{
33383402
struct device *dev = data->dev;
33393403
const struct pmbus_driver_info *info = data->info;
33403404
const struct pmbus_platform_data *pdata = dev_get_platdata(dev);
3341-
int i;
3405+
int i, ret;
33423406

33433407
data->rdevs = devm_kzalloc(dev, sizeof(struct regulator_dev *) * info->num_regulators,
33443408
GFP_KERNEL);
@@ -3362,19 +3426,19 @@ static int pmbus_regulator_register(struct pmbus_data *data)
33623426
info->reg_desc[i].name);
33633427
}
33643428

3429+
INIT_WORK(&data->regulator_notify_work, pmbus_regulator_notify_worker);
3430+
3431+
ret = devm_add_action_or_reset(dev, pmbus_regulator_notify_work_cancel, data);
3432+
if (ret)
3433+
return ret;
3434+
33653435
return 0;
33663436
}
33673437

33683438
static void pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
33693439
{
3370-
int j;
3371-
3372-
for (j = 0; j < data->info->num_regulators; j++) {
3373-
if (page == rdev_get_id(data->rdevs[j])) {
3374-
regulator_notifier_call_chain(data->rdevs[j], event, NULL);
3375-
break;
3376-
}
3377-
}
3440+
atomic_or(event, &data->regulator_events[page]);
3441+
schedule_work(&data->regulator_notify_work);
33783442
}
33793443
#else
33803444
static int pmbus_regulator_register(struct pmbus_data *data)

0 commit comments

Comments
 (0)