From 9bb68c36040d0ffde007f1f7798227c0a460b5fd Mon Sep 17 00:00:00 2001 From: Mike Tipton Date: Tue, 6 Oct 2020 17:54:09 -0700 Subject: [PATCH] clk: qcom: Fix race condition when unvoting regulators in sync_state Normally clk_vote_vdd_level() and clk_unvote_vdd_level() are called while holding the clock framework's prepare_lock since they are called in the prepare paths. However, we're also calling clk_unvote_vdd_level() from qcom_cc_sync_state() when removing proxy votes. This happens outside of the clock framework, so the prepare_lock isn't protecting this case. If the sync_state callback fires while another thread is voting/unvoting voltage from a normal clock call, then there's a race condition that can result in regulators being set to the incorrect voltage or being pre-maturely disabled. Add a mutex to protect this case. Change-Id: Ib979e2edaaa5824dc80f7d4f065d72ca0b1a4444 Signed-off-by: Mike Tipton --- drivers/clk/qcom/vdd-class.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/clk/qcom/vdd-class.c b/drivers/clk/qcom/vdd-class.c index cca6ec118324..c3ecf1c2b639 100644 --- a/drivers/clk/qcom/vdd-class.c +++ b/drivers/clk/qcom/vdd-class.c @@ -8,6 +8,8 @@ #include "vdd-class.h" +static DEFINE_MUTEX(vdd_lock); + /* * Aggregate the vdd_class level votes and call regulator framework functions * to enforce the highest vote. @@ -86,12 +88,15 @@ int clk_vote_vdd_level(struct clk_vdd_class_data *vdd_data, int level) if (level >= vdd_class->num_levels) return -EINVAL; + mutex_lock(&vdd_lock); vdd_class->level_votes[level]++; ret = clk_aggregate_vdd(vdd_class); if (ret) vdd_class->level_votes[level]--; + mutex_unlock(&vdd_lock); + return ret; } EXPORT_SYMBOL(clk_vote_vdd_level); @@ -121,12 +126,15 @@ int clk_unvote_vdd_level(struct clk_vdd_class_data *vdd_data, int level) return -EINVAL; } + mutex_lock(&vdd_lock); vdd_class->level_votes[level]--; ret = clk_aggregate_vdd(vdd_class); if (ret) vdd_class->level_votes[level]++; + mutex_unlock(&vdd_lock); + return ret; } EXPORT_SYMBOL(clk_unvote_vdd_level);