From patchwork Wed May 1 04:42:08 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Saravana Kannan X-Patchwork-Id: 2507061 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) by patchwork1.kernel.org (Postfix) with ESMTP id 4D7AA3FD85 for ; Wed, 1 May 2013 04:43:00 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UXOsS-0000Ms-GE; Wed, 01 May 2013 04:42:48 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UXOsP-0006lb-Tv; Wed, 01 May 2013 04:42:45 +0000 Received: from wolverine02.qualcomm.com ([199.106.114.251]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UXOsL-0006l6-T2 for linux-arm-kernel@lists.infradead.org; Wed, 01 May 2013 04:42:43 +0000 X-IronPort-AV: E=Sophos;i="4.87,587,1363158000"; d="scan'208";a="43180309" Received: from pdmz-ns-mip.qualcomm.com (HELO mostmsg01.qualcomm.com) ([199.106.114.10]) by wolverine02.qualcomm.com with ESMTP/TLS/DHE-RSA-AES256-SHA; 30 Apr 2013 21:42:16 -0700 Received: from skannan1-linux.qualcomm.com (pdmz-ns-snip_218_1.qualcomm.com [192.168.218.1]) by mostmsg01.qualcomm.com (Postfix) with ESMTPA id 12A3310004BF; Tue, 30 Apr 2013 21:42:16 -0700 (PDT) From: Saravana Kannan To: linux-arm-kernel@lists.infradead.org, Mike Turquette , Paul Walmsley , Shawn Guo , Sascha Hauer , Rob Herring , Mark Brown , Russell King , Ulf Hansson Subject: [PATCH] clk: Fix race condition between clk_set_parent and clk_enable() Date: Tue, 30 Apr 2013 21:42:08 -0700 Message-Id: <1367383328-25700-1-git-send-email-skannan@codeaurora.org> X-Mailer: git-send-email 1.7.8.3 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130501_004242_196860_63B2D6BC X-CRM114-Status: GOOD ( 24.09 ) X-Spam-Score: -4.2 (----) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-4.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [199.106.114.251 listed in list.dnswl.org] -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Andrew Lunn , Stephen Boyd , Linus Walleij , linux-arm-msm@vger.kernel.org, Magnus Damm , linux-kernel@vger.kernel.org, Amit Kucheria , Richard Zhao , Grant Likely , Deepak Saxena , Thomas Gleixner , Jamie Iles , Arnd Bergman , Jeremy Kerr X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org Without this patch, the following race conditions are possible. Race condition 1: * clk-A has two parents - clk-X and clk-Y. * All three are disabled and clk-X is current parent. * Thread A: clk_set_parent(clk-A, clk-Y). * Thread A: * Thread A: Grabs enable lock. * Thread A: Sees enable count of clk-A is 0, so doesn't enable clk-Y. * Thread A: Updates clk-A SW parent to clk-Y * Thread A: Releases enable lock. * Thread B: clk_enable(clk-A). * Thread B: clk_enable() enables clk-Y, then enabled clk-A and returns. clk-A is now enabled in software, but not clocking in hardware since the hardware parent is still clk-X. The only way to avoid race conditions between clk_set_parent() and clk_enable/disable() is to ensure that clk_enable/disable() calls don't require changes to hardware enable state between changes to software clock topology and hardware clock topology. There are options to achieve the above: 1. Grab the enable lock before changing software/hardware topology and release it afterwards. 2. Keep the clock enabled for the duration of software/hardware topology change so that any additional enable/disable calls don't try to change the hardware state. Once the topology change is complete, the clock can be put back in its original enable state. Option (1) is not an acceptable solution since the set_parent() ops might need to sleep. Therefore, this patch implements option (2). This patch doesn't violate any API semantics. clk_disable() doesn't guarantee that the clock is actually disabled. So, no clients of a clock can assume that a clock is disabled after their last call to clk_disable(). So, enabling the clock during a parent change is not a violation of any API semantics. This also has the nice side effect of simplifying the error handling code. Signed-off-by: Saravana Kannan Acked-by: Ulf Hansson --- It's been a while since I submitted a patch. So, apologies if I'm cc'ing people who no longer care about the state of the common clock framework. drivers/clk/clk.c | 72 +++++++++++++++++++++++----------------------------- 1 files changed, 32 insertions(+), 40 deletions(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 934cfd1..fe4055f 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -1377,67 +1377,59 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index) unsigned long flags; int ret = 0; struct clk *old_parent = clk->parent; - bool migrated_enable = false; - /* migrate prepare */ - if (clk->prepare_count) + /* + * Migrate prepare state between parents and prevent race with + * clk_enable(). + * + * If the clock is not prepared, then a race with + * clk_enable/disable() is impossible since we already have the + * prepare lock (future calls to clk_enable() need to be preceded by + * a clk_prepare()). + * + * If the clock is prepared, migrate the prepared state to the new + * parent and also protect against a race with clk_enable() by + * forcing the clock and the new parent on. This ensures that all + * future calls to clk_enable() are practically NOPs with respect to + * hardware and software states. + */ + if (clk->prepare_count) { __clk_prepare(parent); - - flags = clk_enable_lock(); - - /* migrate enable */ - if (clk->enable_count) { - __clk_enable(parent); - migrated_enable = true; + clk_enable(parent); + clk_enable(clk); } /* update the clk tree topology */ + flags = clk_enable_lock(); clk_reparent(clk, parent); - clk_enable_unlock(flags); /* change clock input source */ if (parent && clk->ops->set_parent) ret = clk->ops->set_parent(clk->hw, p_index); - if (ret) { - /* - * The error handling is tricky due to that we need to release - * the spinlock while issuing the .set_parent callback. This - * means the new parent might have been enabled/disabled in - * between, which must be considered when doing rollback. - */ - flags = clk_enable_lock(); + flags = clk_enable_lock(); clk_reparent(clk, old_parent); - - if (migrated_enable && clk->enable_count) { - __clk_disable(parent); - } else if (migrated_enable && (clk->enable_count == 0)) { - __clk_disable(old_parent); - } else if (!migrated_enable && clk->enable_count) { - __clk_disable(parent); - __clk_enable(old_parent); - } - clk_enable_unlock(flags); - if (clk->prepare_count) + if (clk->prepare_count) { + clk_disable(clk); + clk_disable(parent); __clk_unprepare(parent); - + } return ret; } - /* clean up enable for old parent if migration was done */ - if (migrated_enable) { - flags = clk_enable_lock(); - __clk_disable(old_parent); - clk_enable_unlock(flags); - } - - /* clean up prepare for old parent if migration was done */ - if (clk->prepare_count) + /* + * Finish the migration of prepare state and undo the changes done + * for preventing a race with clk_enable(). + */ + if (clk->prepare_count) { + clk_disable(clk); + clk_disable(old_parent); __clk_unprepare(old_parent); + } /* update debugfs with new clk tree topology */ clk_debug_reparent(clk, parent);