[RFC,v2,2/2] opal : Support spr save-restore using new

Message ID 20181015091531.14126-3-akshay.adiga@linux.vnet.ibm.com
State New
Headers show
Series
  • New device-tree format and Opal based idle save-restore
Related show

Checks

Context Check Description
snowpatch_ozlabs/make_check success Test make_check on branch master
snowpatch_ozlabs/apply_patch success master/apply_patch Successfully applied

Commit Message

Akshay Adiga Oct. 15, 2018, 9:15 a.m.
From: Abhishek Goel <huntbag@linux.vnet.ibm.com>

In an attempt to make the powernv idle code backward compatible, and
to some extent forward compatible, add support for pre-stop entry and
post-stop exit actions in OPAL. If a kernel knows about this opal call,
then just a firmware supporting newer hardware is required, instead of
waiting for kernel updates. Thus the kernel can enable a stop state, when
the opal support for it exists, even if the kernel support doesn't exist.

Signed-off-by: Abhishek Goel <huntbag@linux.vnet.ibm.com>
---
Changes from v1 :
 - All the decision making such as identifying first thread in the core
   and taking locks before restoring in such cases have also been moved
   to OPAL
 - Deep states have only opal-support


 asm/head.S              |   2 -
 hw/chiptod.c            |   2 +-
 hw/slw.c                | 194 ++++++++++++++++++++++++++++++++++++++--
 include/cpu.h           |  28 ++++++
 include/opal-api.h      |   4 +-
 include/opal-internal.h |   1 +
 include/processor.h     |  17 ++++
 7 files changed, 238 insertions(+), 10 deletions(-)

Patch

diff --git a/asm/head.S b/asm/head.S
index 803fbf1a..34368df5 100644
--- a/asm/head.S
+++ b/asm/head.S
@@ -28,8 +28,6 @@ 
 #define PPC_INST_SLEEP		.long 0x4c0003a4
 #define PPC_INST_RVWINKLE	.long 0x4c0003e4
 
-#define PPC_INST_STOP		.long 0x4c0002e4
-
 #define GET_STACK(stack_reg,pir_reg)					\
 	sldi	stack_reg,pir_reg,STACK_SHIFT;				\
 	addis	stack_reg,stack_reg,CPU_STACKS_OFFSET@ha;		\
diff --git a/hw/chiptod.c b/hw/chiptod.c
index 97c8b8dd..8bb4e2f2 100644
--- a/hw/chiptod.c
+++ b/hw/chiptod.c
@@ -1599,7 +1599,7 @@  error_out:
 	return rc;
 }
 
-static int64_t opal_resync_timebase(void)
+int64_t opal_resync_timebase(void)
 {
 	/* Mambo and qemu doesn't simulate the chiptod */
 	if (chip_quirk(QUIRK_NO_CHIPTOD))
diff --git a/hw/slw.c b/hw/slw.c
index c0181534..db7d7fb5 100644
--- a/hw/slw.c
+++ b/hw/slw.c
@@ -429,6 +429,7 @@  struct cpu_idle_states {
 	u64 pm_ctrl_reg_val;
 	u64 pm_ctrl_reg_mask;
 	u32 flags;
+	bool opal_supported;
 };
 
 static struct cpu_idle_states power7_cpu_idle_states[] = {
@@ -526,6 +527,7 @@  static struct cpu_idle_states power9_cpu_idle_states[] = {
 		.latency_ns = 2000,
 		.dt_node_type = 0,
 		.version = "ibm,state-v1",
+		.opal_supported = false,
 		.residency_ns = 20000,
 		.flags = 0*OPAL_PM_DEC_STOP \
 		       | 0*OPAL_PM_TIMEBASE_STOP  \
@@ -546,6 +548,7 @@  static struct cpu_idle_states power9_cpu_idle_states[] = {
 		.name = "stop1",
 		.dt_node_type = 1,
 		.version = "ibm,state-v1",
+		.opal_supported = false,
 		.type = "cpuidle",
 		.latency_ns = 5000,
 		.residency_ns = 50000,
@@ -570,6 +573,7 @@  static struct cpu_idle_states power9_cpu_idle_states[] = {
 		.name = "stop2",
 		.dt_node_type = 1,
 		.version = "ibm,state-v1",
+		.opal_supported = false,
 		.type = "cpuidle",
 		.latency_ns = 10000,
 		.residency_ns = 100000,
@@ -588,7 +592,8 @@  static struct cpu_idle_states power9_cpu_idle_states[] = {
 	{
 		.name = "stop4",
 		.dt_node_type = 1,
-		.version = "ibm,state-v1",
+		.version = "",
+		.opal_supported = true,
 		.type = "cpuidle",
 		.latency_ns = 100000,
 		.residency_ns = 10000000,
@@ -607,7 +612,8 @@  static struct cpu_idle_states power9_cpu_idle_states[] = {
 	{
 		.name = "stop5",
 		.dt_node_type = 1,
-		.version = "ibm,state-v1",
+		.version = "",
+		.opal_supported = true,
 		.type = "cpuidle",
 		.latency_ns = 200000,
 		.residency_ns = 20000000,
@@ -627,7 +633,8 @@  static struct cpu_idle_states power9_cpu_idle_states[] = {
 	{
 		.name = "stop8",
 		.dt_node_type = 1,
-		.version = "ibm,state-v1",
+		.version = "",
+		.opal_supported = false,
 		.type = "cpuoffline",
 		.latency_ns = 2000000,
 		.residency_ns = 20000000,
@@ -647,7 +654,8 @@  static struct cpu_idle_states power9_cpu_idle_states[] = {
 	{
 		.name = "stop11",
 		.dt_node_type = 1,
-		.version = "ibm,state-v1",
+		.version = "",
+		.opal_supported = true,
 		.type = "cpuoffline",
 		.latency_ns = 10000000,
 		.residency_ns = 100000000,
@@ -844,6 +852,169 @@  static void slw_late_init_p9(struct proc_chip *chip)
 	}
 }
 
+#define PSSCR_RL_MASK			0xF
+#define PSSCR_PLS_MASK			0xF000000000000000UL
+#define PSSCR_PLS_SHIFT			60
+#define SRR1_WS_HVLOSS			0x30000
+#define NR_PNV_CORE_IDLE_LOCK_BIT	28
+#define PNV_CORE_IDLE_LOCK_BIT		(0x1 << NR_PNV_CORE_IDLE_LOCK_BIT)
+#define BIT_MASK(nr)			(0x1 << ((nr) % 64))
+#define BIT_WORD(nr)			((nr) / 64)
+
+static u64 test_and_set_bit_lock(u64 mask, u64 *_p)
+{
+	u64 old, t;
+	u64 *p = (u64 *)_p;
+
+	asm volatile(
+		"1:	ldarx	%0,0,%3,1	\n"
+		"	or	%1,%0,%2	\n"
+		"	dcbt	0,%3		\n"
+		"	stdcx.	%1,0,%3		\n"
+		"	bne-	1b		\n"
+		"	isync			\n"
+
+		: "=&r"	(old), "=&r" (t)
+		: "r" (mask), "r" (p)
+		: "cc", "memory");
+
+	return (old & mask);
+}
+
+static inline void atomic_unlock_and_stop_thread_idle(void)
+{
+	u64 new, tmp;
+	struct cpu_thread *first = this_cpu()->primary;
+	u64 *state = &first->idle_state;
+	u64 thread = 0x1 << (u64) cpu_get_thread_index(this_cpu());
+	u64 s = *state;
+
+	isync();
+
+again:
+	new = (s | thread) & ~(PNV_CORE_IDLE_LOCK_BIT);
+	tmp = __cmpxchg64(state, s, new);
+	if (tmp != s) {
+		s = tmp;
+		goto again;
+	}
+}
+
+/*
+ * opal_cpuidle_save: Save the SPRs and any other resources
+ *		      when going to a deep idle stop states.
+ * @psscr     : The requested psscr values
+ */
+static int opal_cpuidle_save(u64 psscr)
+{
+	struct cpu_thread *first = this_cpu()->primary;
+	u64 *state = &first->idle_state;
+	struct p9_sprs *sprs = &this_cpu()->sprs;
+
+	memset(sprs, 0, sizeof(sprs));
+	/*
+	 * TODO Fix this to use the RL value of the first thread
+	 * that loses hypervisor resources.
+	 */
+	if ((psscr & PSSCR_RL_MASK) < 4) {
+		prlog(PR_ERR, "opal cpuidle save : unexpected opal call\n");
+		return OPAL_SUCCESS;
+	}
+
+	/* saving all the SPRs */
+	sprs->ldbar = mfspr(SPR_LDBAR);
+	sprs->ptcr  = mfspr(SPR_PTCR);
+	sprs->tscr  = mfspr(SPR_TSCR);
+	sprs->amor  = mfspr(SPR_AMOR);
+	sprs->rpr   = mfspr(SPR_RPR);
+
+	sprs->wort  = mfspr(SPR_WORT);
+	sprs->purr  = mfspr(SPR_PURR);
+	sprs->spurr = mfspr(SPR_SPURR);
+	sprs->dscr  = mfspr(SPR_DSCR);
+	sprs->lpcr  = mfspr(SPR_LPCR);
+	sprs->pid   = mfspr(SPR_PID);
+	sprs->fscr  = mfspr(SPR_FSCR);
+	sprs->hfscr = mfspr(SPR_HFSCR);
+	sprs->mmcra = mfspr(SPR_MMCRA);
+	sprs->mmcr0 = mfspr(SPR_MMCR0);
+	sprs->mmcr1 = mfspr(SPR_MMCR1);
+	sprs->mmcr2 = mfspr(SPR_MMCR2);
+	sprs->sprg3 = mfspr(SPR_SPRG3);
+
+	*state = (u64) cpu_get_thread_index(this_cpu()) & *state;
+
+	this_cpu()->in_opal_call--;
+	asm volatile(PPC_STOP);
+
+	return OPAL_WRONG_STATE;
+}
+
+opal_call(OPAL_IDLE_SAVE, opal_cpuidle_save, 1);
+
+/*
+ * opal_cpuidle_restore: Restore the SPRs and any other resources
+ *                     on wakeup from a  deep idle stop states.
+ * @psscr     : The psscr value at wakeup from stop.
+ * @srr1      : The SRR1 value at wakeup from stop.
+ */
+static int opal_cpuidle_restore(u64 psscr, u64 srr1)
+{
+	struct cpu_thread *first = this_cpu()->primary;
+	u64 *state = &first->idle_state;
+	struct p9_sprs *sprs = &this_cpu()->sprs;
+
+	while (test_and_set_bit_lock(BIT_MASK(NR_PNV_CORE_IDLE_LOCK_BIT),
+			state+BIT_WORD(NR_PNV_CORE_IDLE_LOCK_BIT)) != 0)
+		asm volatile("" : : : "memory");
+	isync();
+
+	/*
+	 * TODO Fix this to use the RL value of the first thread
+	 * that loses hypervisor resources.
+	 */
+	if ((psscr & PSSCR_PLS_MASK) >> PSSCR_PLS_SHIFT  < 4) {
+		prlog(PR_ERR, "opal cpuidle restore : unexpected opal call\n");
+		return OPAL_SUCCESS;
+	}
+
+	//Check if it is the first thread in the core
+	if ((*state & ((1 << cpu_thread_count) - 1)) != 0)
+		goto core_woken;
+
+	/* Per-core SPRs */
+	mtspr(SPR_PTCR,		sprs->ptcr);
+	mtspr(SPR_RPR,		sprs->rpr);
+	mtspr(SPR_TSCR,		sprs->tscr);
+	mtspr(SPR_LDBAR,	sprs->ldbar);
+	mtspr(SPR_AMOR,		sprs->amor);
+
+	if (srr1 & SRR1_WS_HVLOSS)
+		/* TB loss */
+		opal_resync_timebase();
+
+core_woken:
+
+	atomic_unlock_and_stop_thread_idle();
+
+	mtspr(SPR_WORT,		sprs->wort);
+	mtspr(SPR_PURR,		sprs->purr);
+	mtspr(SPR_SPURR,	sprs->spurr);
+	mtspr(SPR_DSCR,		sprs->dscr);
+	mtspr(SPR_LPCR,		sprs->lpcr);
+	mtspr(SPR_PID,		sprs->pid);
+	mtspr(SPR_FSCR,		sprs->fscr);
+	mtspr(SPR_HFSCR,	sprs->hfscr);
+	mtspr(SPR_MMCRA,	sprs->mmcra);
+	mtspr(SPR_MMCR0,	sprs->mmcr0);
+	mtspr(SPR_MMCR1,	sprs->mmcr1);
+	mtspr(SPR_MMCR2,	sprs->mmcr2);
+	mtspr(SPR_SPRG3,	sprs->sprg3);
+	return OPAL_SUCCESS;
+}
+
+opal_call(OPAL_IDLE_RESTORE, opal_cpuidle_restore, 2);
+
 /* Add device tree properties to describe idle states */
 void add_cpu_idle_state_properties(void)
 {
@@ -1088,8 +1259,19 @@  void add_cpu_idle_state_properties(void)
 			if (!dt_state_node)
 				prlog(PR_ERR, "Error creating %s\n", states[i].name);
 
-			strncpy(version_buf, states[i].version, strlen(states[i].version)+1);
-			dt_add_property_strings(dt_state_node, "compatible", version_buf);
+			/* If both version and opal-support are there */
+			if (states[i].version[0] !=  '\0') {
+				strncpy(version_buf, states[i].version, strlen(states[i].version)+1);
+				if (states[i].opal_supported) {
+					dt_add_property_strings(dt_state_node,
+						  "compatible",	version_buf,
+						  "opal-support");
+				} else {
+					dt_add_property_string(dt_state_node, "compatible", version_buf);
+				}
+			} else if (states[i].opal_supported) {
+				dt_add_property_string(dt_state_node, "compatible", "opal-support");
+			}
 			dt_add_property_strings(dt_state_node, "type", states[i].type);
 
 			*lat_buf = cpu_to_fdt32(states[i].latency_ns);
diff --git a/include/cpu.h b/include/cpu.h
index 2fe47982..ea3517c7 100644
--- a/include/cpu.h
+++ b/include/cpu.h
@@ -44,6 +44,31 @@  enum cpu_thread_state {
 struct cpu_job;
 struct xive_cpu_state;
 
+struct p9_sprs {
+	/* per core */
+	u64 ptcr;
+	u64 rpr;
+	u64 tscr;
+	u64 ldbar;
+	u64 amor;
+
+	/* per thread */
+	u64 lpcr;
+	u64 hfscr;
+	u64 fscr;
+	u64 pid;
+	u64 purr;
+	u64 spurr;
+	u64 dscr;
+	u64 wort;
+	u64 sprg3;
+
+	u64 mmcra;
+	u32 mmcr0;
+	u32 mmcr1;
+	u64 mmcr2;
+};
+
 struct cpu_thread {
 	/*
 	 * "stack_guard" must be at offset 0 to match the
@@ -124,6 +149,9 @@  struct cpu_thread {
 	/* The lock requested by this cpu, used for deadlock detection */
 	struct lock			*requested_lock;
 #endif
+
+	u64				idle_state;
+	struct p9_sprs			sprs;
 };
 
 /* This global is set to 1 to allow secondaries to callin,
diff --git a/include/opal-api.h b/include/opal-api.h
index 5f397c8e..1e69b6eb 100644
--- a/include/opal-api.h
+++ b/include/opal-api.h
@@ -226,7 +226,9 @@ 
 #define OPAL_NX_COPROC_INIT			167
 #define OPAL_NPU_SET_RELAXED_ORDER		168
 #define OPAL_NPU_GET_RELAXED_ORDER		169
-#define OPAL_LAST				169
+#define OPAL_IDLE_SAVE				170
+#define OPAL_IDLE_RESTORE			171
+#define OPAL_LAST				171
 
 #define QUIESCE_HOLD			1 /* Spin all calls at entry */
 #define QUIESCE_REJECT			2 /* Fail all calls with OPAL_BUSY */
diff --git a/include/opal-internal.h b/include/opal-internal.h
index 40bad457..2f1ce86d 100644
--- a/include/opal-internal.h
+++ b/include/opal-internal.h
@@ -60,6 +60,7 @@  extern void add_opal_node(void);
 	__opal_register((token) + 0*sizeof(func(__test_args##nargs)),	\
 			(func), (nargs))
 extern void __opal_register(uint64_t token, void *func, unsigned num_args);
+extern int64_t opal_resync_timebase(void);
 
 int64_t opal_quiesce(uint32_t shutdown_type, int32_t cpu);
 
diff --git a/include/processor.h b/include/processor.h
index 6b262b45..11be1dc1 100644
--- a/include/processor.h
+++ b/include/processor.h
@@ -88,6 +88,19 @@ 
 #define SPR_HID5	0x3f6
 #define SPR_PIR		0x3ff	/* RO: Processor Identification */
 
+#define SPR_PTCR	0x1D0   /* Partition table control Register */
+#define SPR_WORT	0x37f   /* Workload optimization register - thread */
+#define SPR_WORC	0x35f   /* Workload optimization register - core */
+#define SPR_FSCR	0x099   /* Facility Status & Control Register */
+#define SPR_HFSCR	0xbe    /* HV=1 Facility Status & Control Register */
+#define SPR_LDBAR	0x352   /* LD Base Address Register */
+#define SPR_PID		0x030   /* Process ID */
+#define SPR_SPRG3	0x113   /* Special Purpose Register General 3 */
+/* Performance counter Registers */
+#define SPR_MMCR0	0x31b
+#define SPR_MMCRA	0x312
+#define SPR_MMCR1	0x31e
+#define SPR_MMCR2	0x311
 
 /* Bits in LPCR */
 
@@ -202,6 +215,10 @@ 
 #define PVR_TYPE_P8NVL	0x004c /* Naples */
 #define PVR_TYPE_P9	0x004e
 
+/* Power management instruction */
+#define PPC_INST_STOP	.long 0x4c0002e4
+#define PPC_STOP	stringify(PPC_INST_STOP)
+
 #ifdef __ASSEMBLY__
 
 /* Thread priority control opcodes */