regulator: qcom_pm8008-regulator: Avoid deadlock in OCP handling

For handling OCP events in PM8008, the PM8008 driver will acquire the
chip-level regulator's mutex and then the affected LDO's regulator
mutex, as part of the call flow, needed for calling
regulator_notifier_call_chain twice. This could lead to a deadlock if
any PM8008 LDO client also tries to enable the affected LDO at the same
time. 

Thread 1: Camera trying to enable a PM8008 LDO

    [<ffffff9bb5ecebac>] mutex_lock+0x54 (en_supply regulator mutex, taken in below thread)
    [<ffffff9bb50912b8>] regulator_disable+0x30
    [<ffffff9bb50a0ad4>] pm8008_regulator_enable+0x26c
    [<ffffff9bb5096cfc>] _regulator_do_enable+0x25c
    [<ffffff9bb5091228>] regulator_enable+0x168 (PM8008 regulator mutex acquired)

Thread 2: OCP IRQ trigger and handling

    [<ffffff9bb5ecebac>] mutex_lock+0x54 (PM8008 regulator mutex, taken in above thread)
    [<ffffff9bb50a0450>] pm8008_ldo_cb+0x170
    [<ffffff9bb4af7270>] blocking_notifier_call_chain+0x90
    [<ffffff9bb5094448>] regulator_notifier_call_chain+0x30
    [<ffffff9bb50a15e0>] pm8008_ocp_irq+0x30 (en_supply regulator mutex acquired)

To avoid this race condition, move the second call to
regulator_notifier_call_chain along with the acquisition of the
LDO regulator mutex into a queued work task, to avoid the OCP handling
thread holding both mutexes simultaneously.

Change-Id: I649bfdc5cfd1d7bf84e6538cf91df6898adb90a0
Signed-off-by: Jishnu Prakash <quic_jprakash@quicinc.com>
This commit is contained in:
Jishnu Prakash 2021-12-19 19:29:34 +05:30
parent 2753cc581b
commit 5a48708e96

View File

@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. */
/* Copyright (c) 2021, Qualcomm Innovation Center, Inc. All rights reserved. */
#define pr_fmt(fmt) "PM8008: %s: " fmt, __func__
@ -113,6 +114,7 @@ struct pm8008_regulator {
int step_rate;
bool enable_ocp_broadcast;
enum pmic_subtype pmic_subtype;
struct work_struct notify_clients_work;
};
static const struct regulator_data pm8008_reg_data[PM8008_MAX_LDO] = {
@ -601,18 +603,26 @@ static int pm8008_ldo_cb(struct notifier_block *nb, ulong event, void *data)
goto error;
}
/* Notify the consumers about the OCP event */
mutex_lock(&pm8008_reg->rdev->mutex);
regulator_notifier_call_chain(pm8008_reg->rdev,
REGULATOR_EVENT_OVER_CURRENT, NULL);
mutex_unlock(&pm8008_reg->rdev->mutex);
schedule_work(&pm8008_reg->notify_clients_work);
error:
return NOTIFY_OK;
}
static void notify_clients_work(struct work_struct *work)
{
struct pm8008_regulator *pm8008_reg = container_of(work,
struct pm8008_regulator, notify_clients_work);
/* Notify the consumers about the OCP event */
mutex_lock(&pm8008_reg->rdev->mutex);
regulator_notifier_call_chain(pm8008_reg->rdev,
REGULATOR_EVENT_OVER_CURRENT, NULL);
mutex_unlock(&pm8008_reg->rdev->mutex);
}
static int pm8008_regulator_register_init(struct pm8008_regulator *pm8008_reg,
const struct regulator_data *reg_data)
const struct regulator_data *reg_data)
{
int i, rc;
@ -789,6 +799,8 @@ static int pm8008_register_ldo(struct pm8008_regulator *pm8008_reg,
rc);
return rc;
}
INIT_WORK(&pm8008_reg->notify_clients_work,
notify_clients_work);
}
pr_debug("%s regulator registered\n", name);