msm: pm: Notify secure code of L2 power mode from last core only

Notify secure code L2 low power mode only if the current core is the
last core down. In certain scenarios, its possible that Linux and
secure code have different views of what a last core and could result in
secure code not invalidating a cache when the L2 was placed in a
non-retention mode.

Consider a two core scenario where Core 1 is power collapsed and Core 0
is the last core down setting L2 to be invalidated. Before Core 0 makes
the scm call, its possible that is interrupted to process a secure
interrupt. If the Core 1 wakes up in this time and decides to vote a
different L2 mode, this mode wouldn't be consistent with what Core 0
passes down to secure code when resuming from interrupt handling. In
these cases, the secure code could end up incorrectly invalidating the L2
on warmboot.

Also, the secure code and Linux should have a consistent view of what
last core down is to flush and invalidate L2. To ensure this, acquire a
remote (hardware) spinlock to serialize the last portion of the
power-collapse sequence. The remote spinlock will be released by the
secure code after it acquires its own lock to serialize the power
collapse sequence.

msm: pm: Acquire scm_handoff_lock conditionally

Acquisition of the scm_handoff_lock requires a TrustZone software
image that will release the lock. Without this, a deadlock will
occur since the lock will remain held forever.

Since this lock is only really needed if the qcom,allow-synced-levels
property is specified for the lpm-levels device, conditionally acquire
the lock based on this property. This avoid the TrustZone software
dependency when qcom,allow-synced-levels is not specified.

Change-Id: I8f8f1e4f9bdab6e70ef2a5484faba4e8ce8ac20c
Signed-off-by: Matt Wagantall <mattw@codeaurora.org>
Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
diff --git a/arch/arm/mach-msm/msm-pm.c b/arch/arm/mach-msm/msm-pm.c
index f9a9343..080bab3 100644
--- a/arch/arm/mach-msm/msm-pm.c
+++ b/arch/arm/mach-msm/msm-pm.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -25,10 +25,12 @@
 #include <linux/platform_device.h>
 #include <linux/of_platform.h>
 #include <linux/cpu_pm.h>
+#include <linux/remote_spinlock.h>
 #include <asm/uaccess.h>
 #include <asm/suspend.h>
 #include <asm/cacheflush.h>
 #include <asm/outercache.h>
+#include <mach/remote_spinlock.h>
 #include <mach/scm.h>
 #include <mach/msm_bus.h>
 #include <mach/jtag.h>
@@ -117,6 +119,12 @@
 DEFINE_PER_CPU(struct clk *, cpu_clks);
 static struct clk *l2_clk;
 
+static int cpu_count;
+static DEFINE_SPINLOCK(cpu_cnt_lock);
+#define SCM_HANDOFF_LOCK_ID "S:7"
+static bool need_scm_handoff_lock;
+static remote_spinlock_t scm_handoff_lock;
+
 static void (*msm_pm_disable_l2_fn)(void);
 static void (*msm_pm_enable_l2_fn)(void);
 static void (*msm_pm_flush_l2_fn)(void);
@@ -478,8 +486,30 @@
 static int msm_pm_collapse(unsigned long unused)
 {
 	uint32_t cpu = smp_processor_id();
+	enum msm_pm_l2_scm_flag flag = MSM_SCM_L2_ON;
 
-	if (msm_pm_get_l2_flush_flag() == MSM_SCM_L2_OFF) {
+	spin_lock(&cpu_cnt_lock);
+	cpu_count++;
+	if (cpu_count == num_online_cpus())
+		flag = msm_pm_get_l2_flush_flag();
+
+	pr_debug("cpu:%d cores_in_pc:%d L2 flag: %d\n",
+			cpu, cpu_count, flag);
+
+	/*
+	 * The scm_handoff_lock will be release by the secure monitor.
+	 * It is used to serialize power-collapses from this point on,
+	 * so that both Linux and the secure context have a consistent
+	 * view regarding the number of running cpus (cpu_count).
+	 *
+	 * It must be acquired before releasing cpu_cnt_lock.
+	 */
+	if (need_scm_handoff_lock)
+		remote_spin_lock_rlock_id(&scm_handoff_lock,
+					  REMOTE_SPINLOCK_TID_START + cpu);
+	spin_unlock(&cpu_cnt_lock);
+
+	if (flag == MSM_SCM_L2_OFF) {
 		flush_cache_all();
 		if (msm_pm_flush_l2_fn)
 			msm_pm_flush_l2_fn();
@@ -491,8 +521,7 @@
 
 	msm_pc_inc_debug_count(cpu, MSM_PC_ENTRY_COUNTER);
 
-	scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC,
-				msm_pm_get_l2_flush_flag());
+	scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag);
 
 	msm_pc_inc_debug_count(cpu, MSM_PC_FALLTHRU_COUNTER);
 
@@ -534,6 +563,12 @@
 	collapsed = save_cpu_regs ?
 		!cpu_suspend(0, msm_pm_collapse) : msm_pm_pc_hotplug();
 
+	if (save_cpu_regs) {
+		spin_lock(&cpu_cnt_lock);
+		cpu_count--;
+		BUG_ON(cpu_count > num_online_cpus());
+		spin_unlock(&cpu_cnt_lock);
+	}
 	msm_jtag_restore_state();
 
 	if (collapsed) {
@@ -1166,6 +1201,7 @@
 	struct resource *res = NULL;
 	int i;
 	struct msm_pm_init_data_type pdata_local;
+	struct device_node *lpm_node;
 	int ret = 0;
 
 	memset(&pdata_local, 0, sizeof(struct msm_pm_init_data_type));
@@ -1192,6 +1228,23 @@
 		msm_pc_debug_counters_phys = 0;
 	}
 
+	lpm_node = of_parse_phandle(pdev->dev.of_node, "qcom,lpm-levels", 0);
+	if (!lpm_node) {
+		pr_warn("Could not get qcom,lpm-levels handle\n");
+		return -EINVAL;
+	}
+	need_scm_handoff_lock = of_property_read_bool(lpm_node,
+						      "qcom,allow-synced-levels");
+	if (need_scm_handoff_lock) {
+		ret = remote_spin_lock_init(&scm_handoff_lock,
+					    SCM_HANDOFF_LOCK_ID);
+		if (ret) {
+			pr_err("%s: Failed initializing scm_handoff_lock (%d)\n",
+				__func__, ret);
+			return ret;
+		}
+	}
+
 	if (pdev->dev.of_node) {
 		enum msm_pm_pc_mode_type pc_mode;