Skip to contentSkip to conversation

powerpc/64s: Move idle code to powernv C code

RFC PATCH Superseded Nicholas Piggin 2 participants 3 messages Jul 21, 2018 – Jul 25, 2018

Subsystems (2)

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Nicholas Piggin

author reviewer
#0 lore raw

RFC PATCH v2 1/1 powerpc/64s: Move idle code to powernv C code

Reimplement Book3S idle code to C, in the powernv platform code. Assembly stubs are used to save and restore the stack frame and non-volatile GPRs before going to idle, but these are small and mostly agnostic to microarchitecture implementation details.

The optimisation where EC=ESL=0 idle modes did not have to save GPRs or mtmsrd L=0 is restored, because it's simple to do.

Idle wakeup no longer uses the ->cpu_restore call to reinit SPRs, but saves and restores them all explicitly. This can easily be extended to tracking the set of system-wide SPRs that do not have to be saved each time.

Moving the HMI, SPR, OPAL, locking, etc. to C is the only real way this stuff will cope with non-trivial new CPU implementation details, firmware changes, etc., without becoming unmaintainable.

Since RFC v1:
- Now tested and working with POWER9 hash and radix.
- KVM support added. This took a bit of work to untangle and might still have some issues, but POWER9 seems to work including hash on radix with dependent threads mode.
- This snowballed a bit because of KVM and other details making it not feasible to leave POWER7/8 code alone. That's only half done at the moment.
- So far this trades about 800 lines of asm for 500 of C. With POWER7/8 support done it might be another hundred or so lines of C.

Would appreciate any feedback on the approach in particular the significantly different (and hopefully cleaner) KVM approach.

Thanks,
Nick

---
 include/asm/book3s/64/mmu-hash.h |    1 
 include/asm/cpuidle.h            |   17 
 include/asm/paca.h               |   40 -
 include/asm/processor.h          |    8 
 include/asm/reg.h                |    7 
 kernel/asm-offsets.c             |   12 
 kernel/dt_cpu_ftrs.c             |   21 
 kernel/exceptions-64s.S          |   17 
 kernel/idle_book3s.S             |  996 +++------------------------------------
 kernel/setup-common.c            |    4 
 kvm/book3s_hv_rmhandlers.S       |   94 ++-
 mm/slb.c                         |    7 
 platforms/powernv/idle.c         |  713 +++++++++++++++++++++++----
 platforms/powernv/subcore.c      |    2 
 xmon/xmon.c                      |   26 -
 15 files changed, 817 insertions(+), 1148 deletions(-)
arch/powerpc/include/asm/book3s/64/mmu-hash.h +1 -0
index 50ed64fba4ae..c626319a962d 100644
@@ -486,6 +486,7 @@ static inline void hpte_init_pseries(void) { }
 extern void hpte_init_native(void);
 
 extern void slb_initialize(void);
+extern void __slb_flush_and_rebolt(void);
 extern void slb_flush_and_rebolt(void);
 
 extern void slb_vmalloc_update(void);
arch/powerpc/include/asm/cpuidle.h +1 -16
index e210a83eb196..edf33dc0d098 100644
@@ -28,6 +28,7 @@
  * yet woken from the winkle state.
  */
 #define PNV_CORE_IDLE_LOCK_BIT			0x10000000
+#define NR_PNV_CORE_IDLE_LOCK_BIT		28
 
 #define PNV_CORE_IDLE_WINKLE_COUNT		0x00010000
 #define PNV_CORE_IDLE_WINKLE_COUNT_ALL_BIT	0x00080000
@@ -68,22 +69,6 @@
 #define ERR_DEEP_STATE_ESL_MISMATCH	-2
 
 #ifndef __ASSEMBLY__
-/* Additional SPRs that need to be saved/restored during stop */
-struct stop_sprs {
-	u64 pid;
-	u64 ldbar;
-	u64 fscr;
-	u64 hfscr;
-	u64 mmcr1;
-	u64 mmcr2;
-	u64 mmcra;
-};
-
-extern u32 pnv_fastsleep_workaround_at_entry[];
-extern u32 pnv_fastsleep_workaround_at_exit[];
-
-extern u64 pnv_first_deep_stop_state;
-
 unsigned long pnv_cpu_offline(unsigned int cpu);
 int validate_psscr_val_mask(u64 *psscr_val, u64 *psscr_mask, u32 flags);
 static inline void report_invalid_psscr_val(u64 psscr_val, int err)
arch/powerpc/include/asm/paca.h +23 -17
index 4e9cede5a7e7..27f0e1d6a462 100644
@@ -168,7 +168,6 @@ struct paca_struct {
 	u8 irq_happened;		/* irq happened while soft-disabled */
 	u8 io_sync;			/* writel() needs spin_unlock sync */
 	u8 irq_work_pending;		/* IRQ_WORK interrupt while soft-disable */
-	u8 nap_state_lost;		/* NV GPR values lost in power7_idle */
 #ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
 	u8 pmcregs_in_use;		/* pseries puts this in lppaca */
 #endif
@@ -178,23 +177,30 @@ struct paca_struct {
 #endif
 
 #ifdef CONFIG_PPC_POWERNV
-	/* Per-core mask tracking idle threads and a lock bit-[L][TTTTTTTT] */
-	u32 *core_idle_state_ptr;
-	u8 thread_idle_state;		/* PNV_THREAD_RUNNING/NAP/SLEEP	*/
-	/* Mask to indicate thread id in core */
-	u8 thread_mask;
-	/* Mask to denote subcore sibling threads */
-	u8 subcore_sibling_mask;
-	/* Flag to request this thread not to stop */
-	atomic_t dont_stop;
-	/* The PSSCR value that the kernel requested before going to stop */
-	u64 requested_psscr;
+	union {
+		/* P7/P8 specific fields */
+		struct {
+			/* Per-core mask tracking idle threads and a lock bit-[L][TTTTTTTT] */
+			unsigned long *core_idle_state_ptr;
+			/* PNV_THREAD_RUNNING/NAP/SLEEP	*/
+			u8 thread_idle_state;
+			/* Mask to indicate thread id in core */
+			u8 thread_mask;
+			/* Mask to denote subcore sibling threads */
+			u8 subcore_sibling_mask;
+		};
 
-	/*
-	 * Save area for additional SPRs that need to be
-	 * saved/restored during cpuidle stop.
-	 */
-	struct stop_sprs stop_sprs;
+		/* P9 specific fields */
+		struct {
+			/* The PSSCR value that the kernel requested before going to stop */
+			u64 requested_psscr;
+			unsigned long idle_state;
+#ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+			/* Flag to request this thread not to stop */
+			atomic_t dont_stop;
+#endif
+		};
+	};
 #endif
 
 #ifdef CONFIG_PPC_BOOK3S_64
arch/powerpc/include/asm/processor.h +6 -2
index 5debe337ea9d..6a2a527ef63a 100644
@@ -507,13 +507,17 @@ static inline unsigned long get_clean_sp(unsigned long sp, int is_32)
 }
 #endif
 
+/* asm stubs */
+extern unsigned long isa3_idle_stop_noloss(unsigned long psscr_val);
+extern unsigned long isa3_idle_stop_mayloss(unsigned long psscr_val);
+extern unsigned long isa206_idle_insn_mayloss(unsigned long type);
+
 extern unsigned long cpuidle_disable;
 enum idle_boot_override {IDLE_NO_OVERRIDE = 0, IDLE_POWERSAVE_OFF};
 
 extern int powersave_nap;	/* set if nap mode can be used in idle loop */
-extern unsigned long power7_idle_insn(unsigned long type); /* PNV_THREAD_NAP/etc*/
+
 extern void power7_idle_type(unsigned long type);
-extern unsigned long power9_idle_stop(unsigned long psscr_val);
 extern unsigned long power9_offline_stop(unsigned long psscr_val);
 extern void power9_idle_type(unsigned long stop_psscr_val,
 			      unsigned long stop_psscr_mask);
arch/powerpc/include/asm/reg.h +3 -4
index d85d000d05b5..76513a2a2192 100644
@@ -753,10 +753,9 @@
 #define	  SRR1_WAKERESET	0x00100000 /* System reset */
 #define   SRR1_WAKEHDBELL	0x000c0000 /* Hypervisor doorbell on P8 */
 #define	  SRR1_WAKESTATE	0x00030000 /* Powersave exit mask [46:47] */
-#define	  SRR1_WS_DEEPEST	0x00030000 /* Some resources not maintained,
-					  * may not be recoverable */
-#define	  SRR1_WS_DEEPER	0x00020000 /* Some resources not maintained */
-#define	  SRR1_WS_DEEP		0x00010000 /* All resources maintained */
+#define	  SRR1_WS_HVLOSS	0x00030000 /* HV resources not maintained */
+#define	  SRR1_WS_GPRLOSS	0x00020000 /* GPRs not maintained */
+#define	  SRR1_WS_NOLOSS	0x00010000 /* All resources maintained */
 #define   SRR1_PROGTM		0x00200000 /* TM Bad Thing */
 #define   SRR1_PROGFPE		0x00100000 /* Floating Point Enabled */
 #define   SRR1_PROGILL		0x00080000 /* Illegal instruction */
arch/powerpc/kernel/asm-offsets.c +1 -11
index 89cf15566c4e..f05db66e0d67 100644
@@ -256,7 +256,6 @@ int main(void)
 	OFFSET(ACCOUNT_USER_TIME, paca_struct, accounting.utime);
 	OFFSET(ACCOUNT_SYSTEM_TIME, paca_struct, accounting.stime);
 	OFFSET(PACA_TRAP_SAVE, paca_struct, trap_save);
-	OFFSET(PACA_NAPSTATELOST, paca_struct, nap_state_lost);
 	OFFSET(PACA_SPRG_VDSO, paca_struct, sprg_vdso);
 #else /* CONFIG_PPC64 */
 #ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
@@ -762,20 +761,11 @@ int main(void)
 #endif
 
 #ifdef CONFIG_PPC_POWERNV
+	/* POWER7/8 specific idle fields (kernel/idle_book3s.S) */
 	OFFSET(PACA_CORE_IDLE_STATE_PTR, paca_struct, core_idle_state_ptr);
 	OFFSET(PACA_THREAD_IDLE_STATE, paca_struct, thread_idle_state);
 	OFFSET(PACA_THREAD_MASK, paca_struct, thread_mask);
 	OFFSET(PACA_SUBCORE_SIBLING_MASK, paca_struct, subcore_sibling_mask);
-	OFFSET(PACA_REQ_PSSCR, paca_struct, requested_psscr);
-	OFFSET(PACA_DONT_STOP, paca_struct, dont_stop);
-#define STOP_SPR(x, f)	OFFSET(x, paca_struct, stop_sprs.f)
-	STOP_SPR(STOP_PID, pid);
-	STOP_SPR(STOP_LDBAR, ldbar);
-	STOP_SPR(STOP_FSCR, fscr);
-	STOP_SPR(STOP_HFSCR, hfscr);
-	STOP_SPR(STOP_MMCR1, mmcr1);
-	STOP_SPR(STOP_MMCR2, mmcr2);
-	STOP_SPR(STOP_MMCRA, mmcra);
 #endif
 
 	DEFINE(PPC_DBELL_SERVER, PPC_DBELL_SERVER);
arch/powerpc/kernel/dt_cpu_ftrs.c +1 -20
index f432054234a4..d635d78facdc 100644
@@ -71,7 +71,6 @@ static int hv_mode;
 
 static struct {
 	u64	lpcr;
-	u64	lpcr_clear;
 	u64	hfscr;
 	u64	fscr;
 } system_registers;
@@ -80,24 +79,7 @@ static void (*init_pmu_registers)(void);
 
 static void __restore_cpu_cpufeatures(void)
 {
-	u64 lpcr;
-
-	/*
-	 * LPCR is restored by the power on engine already. It can be changed
-	 * after early init e.g., by radix enable, and we have no unified API
-	 * for saving and restoring such SPRs.
-	 *
-	 * This ->restore hook should really be removed from idle and register
-	 * restore moved directly into the idle restore code, because this code
-	 * doesn't know how idle is implemented or what it needs restored here.
-	 *
-	 * The best we can do to accommodate secondary boot and idle restore
-	 * for now is "or" LPCR with existing.
-	 */
-	lpcr = mfspr(SPRN_LPCR);
-	lpcr |= system_registers.lpcr;
-	lpcr &= ~system_registers.lpcr_clear;
-	mtspr(SPRN_LPCR, lpcr);
+	mtspr(SPRN_LPCR, system_registers.lpcr);
 	if (hv_mode) {
 		mtspr(SPRN_LPID, 0);
 		mtspr(SPRN_HFSCR, system_registers.hfscr);
@@ -318,7 +300,6 @@ static int __init feat_enable_mmu_hash_v3(struct dt_cpu_feature *f)
 {
 	u64 lpcr;
 
-	system_registers.lpcr_clear |= (LPCR_ISL | LPCR_UPRT | LPCR_HR);
 	lpcr = mfspr(SPRN_LPCR);
 	lpcr &= ~(LPCR_ISL | LPCR_UPRT | LPCR_HR);
 	mtspr(SPRN_LPCR, lpcr);
arch/powerpc/kernel/exceptions-64s.S +9 -8
index 76a14702cb9c..137f0f446472 100644
@@ -135,8 +135,9 @@ TRAMP_KVM(PACA_EXNMI, 0x100)
 
 #ifdef CONFIG_PPC_P7_NAP
 EXC_COMMON_BEGIN(system_reset_idle_common)
-	mfspr	r12,SPRN_SRR1
-	b	pnv_powersave_wakeup
+	mfspr	r3,SPRN_SRR1
+	bltlr	cr3	/* no state loss, return to idle caller */
+	b	idle_return_gpr_loss
 #endif
 
 /*
@@ -415,17 +416,17 @@ EXC_COMMON_BEGIN(machine_check_idle_common)
 	 * Then decrement MCE nesting after finishing with the stack.
 	 */
 	ld	r3,_MSR(r1)
+	ld	r4,_LINK(r1)
 
 	lhz	r11,PACA_IN_MCE(r13)
 	subi	r11,r11,1
 	sth	r11,PACA_IN_MCE(r13)
 
-	/* Turn off the RI bit because SRR1 is used by idle wakeup code. */
-	/* Recoverability could be improved by reducing the use of SRR1. */
-	li	r11,0
-	mtmsrd	r11,1
-
-	b	pnv_powersave_wakeup_mce
+	mtlr	r4
+	rlwinm	r10,r3,47-31,30,31
+	cmpwi	cr3,r10,2
+	bltlr	cr3	/* no state loss, return to idle caller */
+	b	idle_return_gpr_loss
 #endif
 	/*
 	 * Handle machine check early in real mode. We come here with
arch/powerpc/kernel/idle_book3s.S +90 -906

1,035 lines not on this page · raw message

arch/powerpc/kernel/setup-common.c +2 -2
index 40b44bb53a4e..e089da156ef3 100644
@@ -401,8 +401,8 @@ void __init check_for_initrd(void)
 
 #ifdef CONFIG_SMP
 
-int threads_per_core, threads_per_subcore, threads_shift;
-cpumask_t threads_core_mask;
+int threads_per_core, threads_per_subcore, threads_shift __read_mostly;
+cpumask_t threads_core_mask __read_mostly;
 EXPORT_SYMBOL_GPL(threads_per_core);
 EXPORT_SYMBOL_GPL(threads_per_subcore);
 EXPORT_SYMBOL_GPL(threads_shift);
arch/powerpc/kvm/book3s_hv_rmhandlers.S +56 -38

169 lines not on this page · raw message

arch/powerpc/mm/slb.c +4 -3
index cb796724a6fc..2d5db5e0132e 100644
@@ -90,7 +90,7 @@ static inline void create_shadowed_slbe(unsigned long ea, int ssize,
 		     : "memory" );
 }
 
-static void __slb_flush_and_rebolt(void)
+void __slb_flush_and_rebolt(void)
 {
 	/* If you change this make sure you change SLB_NUM_BOLTED
 	 * and PR KVM appropriately too. */
@@ -128,6 +128,8 @@ static void __slb_flush_and_rebolt(void)
 		        "r"(ksp_vsid_data),
 		        "r"(ksp_esid_data)
 		     : "memory");
+
+	get_paca()->slb_cache_ptr = 0;
 }
 
 void slb_flush_and_rebolt(void)
@@ -142,7 +144,6 @@ void slb_flush_and_rebolt(void)
 	hard_irq_disable();
 
 	__slb_flush_and_rebolt();
-	get_paca()->slb_cache_ptr = 0;
 }
 
 void slb_vmalloc_update(void)
@@ -213,6 +214,7 @@ void switch_slb(struct task_struct *tsk, struct mm_struct *mm)
 			asm volatile("slbie %0" : : "r" (slbie_data));
 		}
 		asm volatile("isync" : : : "memory");
+		get_paca()->slb_cache_ptr = 0;
 	} else {
 		__slb_flush_and_rebolt();
 	}
@@ -221,7 +223,6 @@ void switch_slb(struct task_struct *tsk, struct mm_struct *mm)
 	if (offset == 1 || offset > SLB_CACHE_ENTRIES)
 		asm volatile("slbie %0" : : "r" (slbie_data));
 
-	get_paca()->slb_cache_ptr = 0;
 	copy_mm_to_paca(mm);
 
 	/*
arch/powerpc/platforms/powernv/idle.c +607 -106

905 lines not on this page · raw message

arch/powerpc/platforms/powernv/subcore.c +1 -1
index 45563004feda..1d7a9fd30dd1 100644
@@ -183,7 +183,7 @@ static void unsplit_core(void)
 	cpu = smp_processor_id();
 	if (cpu_thread_in_core(cpu) != 0) {
 		while (mfspr(SPRN_HID0) & mask)
-			power7_idle_insn(PNV_THREAD_NAP);
+			power7_idle_type(PNV_THREAD_NAP);
 
 		per_cpu(split_state, cpu).step = SYNC_STEP_UNSPLIT;
 		return;
arch/powerpc/xmon/xmon.c +12 -14
index 21119cfe8474..d5f3a26c45ff 100644
@@ -2417,7 +2417,6 @@ static void dump_one_paca(int cpu)
 	DUMP(p, irq_happened, "%#-*x");
 	DUMP(p, io_sync, "%#-*x");
 	DUMP(p, irq_work_pending, "%#-*x");
-	DUMP(p, nap_state_lost, "%#-*x");
 	DUMP(p, sprg_vdso, "%#-*llx");
 
 #ifdef CONFIG_PPC_TRANSACTIONAL_MEM
@@ -2425,19 +2424,18 @@ static void dump_one_paca(int cpu)
 #endif
 
 #ifdef CONFIG_PPC_POWERNV
-	DUMP(p, core_idle_state_ptr, "%-*px");
-	DUMP(p, thread_idle_state, "%#-*x");
-	DUMP(p, thread_mask, "%#-*x");
-	DUMP(p, subcore_sibling_mask, "%#-*x");
-	DUMP(p, requested_psscr, "%#-*llx");
-	DUMP(p, stop_sprs.pid, "%#-*llx");
-	DUMP(p, stop_sprs.ldbar, "%#-*llx");
-	DUMP(p, stop_sprs.fscr, "%#-*llx");
-	DUMP(p, stop_sprs.hfscr, "%#-*llx");
-	DUMP(p, stop_sprs.mmcr1, "%#-*llx");
-	DUMP(p, stop_sprs.mmcr2, "%#-*llx");
-	DUMP(p, stop_sprs.mmcra, "%#-*llx");
-	DUMP(p, dont_stop.counter, "%#-*x");
+	if (!cpu_has_feature(CPU_FTR_ARCH_300)) {
+		DUMP(p, core_idle_state_ptr, "%-*px");
+		DUMP(p, thread_idle_state, "%#-*x");
+		DUMP(p, thread_mask, "%#-*x");
+		DUMP(p, subcore_sibling_mask, "%#-*x");
+	} else {
+		DUMP(p, idle_state, "%#-*lx");
+		DUMP(p, requested_psscr, "%#-*llx");
+#ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+		DUMP(p, dont_stop.counter, "%#-*x");
+#endif
+	}
 #endif
 
 	DUMP(p, accounting.utime, "%#-*lx");
#1 lore raw

Hello Nicholas,

Quoted 28 lines · Nicholas Piggin

Reimplement Book3S idle code to C, in the powernv platform code. Assembly stubs are used to save and restore the stack frame and non-volatile GPRs before going to idle, but these are small and mostly agnostic to microarchitecture implementation details.

The optimisation where EC=ESL=0 idle modes did not have to save GPRs or mtmsrd L=0 is restored, because it's simple to do.

Idle wakeup no longer uses the ->cpu_restore call to reinit SPRs, but saves and restores them all explicitly. This can easily be extended to tracking the set of system-wide SPRs that do not have to be saved each time.

Moving the HMI, SPR, OPAL, locking, etc. to C is the only real way this stuff will cope with non-trivial new CPU implementation details, firmware changes, etc., without becoming unmaintainable.

Since RFC v1:
- Now tested and working with POWER9 hash and radix.
- KVM support added. This took a bit of work to untangle and might still have some issues, but POWER9 seems to work including hash on radix with dependent threads mode.
- This snowballed a bit because of KVM and other details making it not feasible to leave POWER7/8 code alone. That's only half done at the moment.
- So far this trades about 800 lines of asm for 500 of C. With POWER7/8 support done it might be another hundred or so lines of C.

Quoted 2 lines

Would appreciate any feedback on the approach in particular the significantly different (and hopefully cleaner) KVM approach.

I have reviewed the C-code movement from idle_book3s.S to powernv/idle.c. Some comments on that part of the code inline.

I haven't yet gone through the book3s_hv_rmhandlers.S code changes.

Quoted 4 lines

Thanks,
Nick

[..snip..]

Quoted 8 lines

#ifdef CONFIG_PPC_POWERNV

arch/powerpc/include/asm/paca.h +0 -6
-	/* Per-core mask tracking idle threads and a lock bit-[L][TTTTTTTT] */
-	u32 *core_idle_state_ptr;
-	u8 thread_idle_state;		/* PNV_THREAD_RUNNING/NAP/SLEEP	*/
-	/* Mask to indicate thread id in core */
-	u8 thread_mask;
-	/* Mask to denote subcore sibling threads */

arch/powerpc/include/asm/paca.h struct paca_struct { · line 184

Quoted 10 lines
arch/powerpc/include/asm/paca.h +5 -5
-	u8 subcore_sibling_mask;
-	/* Flag to request this thread not to stop */
-	atomic_t dont_stop;
-	/* The PSSCR value that the kernel requested before going to stop */
-	u64 requested_psscr;
+	union {
+		/* P7/P8 specific fields */
+		struct {
+			/* Per-core mask tracking idle threads and a lock bit-[L][TTTTTTTT] */
+			unsigned long *core_idle_state_ptr;

Do we still need *core_idle_state_ptr ? Since this code is accessed through C where we have access to paca_ptrs global variable we can reference the "idle_state" (defined for P9 below). A lot of locking/unlocking code in powernv/idle.c further down the patch already does that, right?

Quoted 26 lines
arch/powerpc/include/asm/paca.h +18 -5
+			/* PNV_THREAD_RUNNING/NAP/SLEEP	*/
+			u8 thread_idle_state;
+			/* Mask to indicate thread id in core */
+			u8 thread_mask;
+			/* Mask to denote subcore sibling threads */
+			u8 subcore_sibling_mask;
+		};
 
-	/*
-	 * Save area for additional SPRs that need to be
-	 * saved/restored during cpuidle stop.
-	 */
-	struct stop_sprs stop_sprs;
+		/* P9 specific fields */
+		struct {
+			/* The PSSCR value that the kernel requested before going to stop */
+			u64 requested_psscr;
+			unsigned long idle_state;
+#ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+			/* Flag to request this thread not to stop */
+			atomic_t dont_stop;
+#endif
+		};
+	};
 #endif

[..snip..]

Quoted 7 lines
(quoted diff) +0 -0
 	/*
arch/powerpc/platforms/powernv/idle.c +0 -0
index 12f13acee1f6..5b6af21ea9aa 100644
@@ -16,6 +16,7 @@
 #include <linux/device.h>

[..snip..]

arch/powerpc/platforms/powernv/idle.c static DEVICE_ATTR(fastsleep_workaround_applyonce, 0600, · line 280

Quoted 6 lines
arch/powerpc/platforms/powernv/idle.c +6 -0
+static inline void atomic_start_thread_idle(void)
+{
+	int cpu = raw_smp_processor_id();
+	int first = cpu_first_thread_sibling(cpu);
+	int thread_nr = cpu_thread_in_core(cpu);
+	unsigned long *state = &paca_ptrs[first]->idle_state;

We are using this in both POWER8 and POWER9 code and are (rightly IMO) referencing the idle_state variable.

Quoted 78 lines
arch/powerpc/platforms/powernv/idle.c +78 -0
+
+	clear_bit(thread_nr, state);
+}
+
+static inline void atomic_stop_thread_idle(void)
+{
+	int cpu = raw_smp_processor_id();
+	int first = cpu_first_thread_sibling(cpu);
+	int thread_nr = cpu_thread_in_core(cpu);
+	unsigned long *state = &paca_ptrs[first]->idle_state;
+
+	set_bit(thread_nr, state);
+}
+
+static inline void atomic_lock_thread_idle(void)
+{
+	int cpu = raw_smp_processor_id();
+	int first = cpu_first_thread_sibling(cpu);
+	unsigned long *state = &paca_ptrs[first]->idle_state;
+
+	while (unlikely(test_and_set_bit_lock(NR_PNV_CORE_IDLE_LOCK_BIT, state)))
+		barrier();
+	isync();
+}
+
+static inline void atomic_unlock_and_stop_thread_idle(void)
+{
+	int cpu = raw_smp_processor_id();
+	int first = cpu_first_thread_sibling(cpu);
+	unsigned long thread = 1UL << cpu_thread_in_core(cpu);
+	unsigned long *state = &paca_ptrs[first]->idle_state;
+	u64 s = READ_ONCE(*state);
+	u64 new, tmp;
+
+	BUG_ON(!(s & PNV_CORE_IDLE_LOCK_BIT));
+	BUG_ON(s & thread);
+
+again:
+	new = (s | thread) & ~PNV_CORE_IDLE_LOCK_BIT;
+	tmp = cmpxchg(state, s, new);
+	if (unlikely(tmp != s)) {
+		s = tmp;
+		goto again;
+	}
+}
+
+static inline void atomic_unlock_thread_idle(void)
+{
+	int cpu = raw_smp_processor_id();
+	int first = cpu_first_thread_sibling(cpu);
+	unsigned long *state = &paca_ptrs[first]->idle_state;
+
+	BUG_ON(!test_bit(NR_PNV_CORE_IDLE_LOCK_BIT, state));
+	clear_bit_unlock(NR_PNV_CORE_IDLE_LOCK_BIT, state);
+}
+
+struct p7_sprs {
+	/* per core */
+	u64 tscr;
+	u64 worc;
+
+	/* per subcore */
+	u64 sdr1;
+	u64 rpr;
+	u64 amor;
+
+	/* per thread */
+	u64 lpcr;
+	u64 hfscr;
+	u64 fscr;
+	u64 pid;
+	u64 purr;
+	u64 spurr;
+	u64 dscr;
+	u64 wort;
+
+	u64 mmcra;
+	u32 mmcr0;

arch/powerpc/platforms/powernv/idle.c static DEVICE_ATTR(fastsleep_workaround_applyonce, 0600, · line 368

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +10 -0
+	u32 mmcr1;
+	u64 mmcr2;
+};
+
+static unsigned long power7_idle_insn(unsigned long type)
+{
+	int cpu = raw_smp_processor_id();
+	int first = cpu_first_thread_sibling(cpu);
+	unsigned long thread = 1UL << cpu_thread_in_core(cpu);
+	unsigned long *state = &paca_ptrs[first]->idle_state;

Ditto.

Quoted 11 lines
arch/powerpc/platforms/powernv/idle.c +11 -0
+	unsigned long srr1;
+	struct p7_sprs sprs;
+	int rc;
+
+	memset(&sprs, 0, sizeof(sprs));
+
+	if (type != PNV_THREAD_NAP) {
+		atomic_lock_thread_idle();
+
+		if (power7_fastsleep_workaround_entry) {
+			/* XXX: lock over application, last thread of core */

arch/powerpc/platforms/powernv/idle.c static DEVICE_ATTR(fastsleep_workaround_applyonce, 0600, · line 389

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +10 -0
+			rc = opal_config_cpu_idle_state(OPAL_CONFIG_IDLE_FASTSLEEP,
+							OPAL_CONFIG_IDLE_APPLY);
+			if (rc)
+				return 0; /* XXX */
+		}
+	}
+
+	if (type == PNV_THREAD_WINKLE) {
+		sprs.rpr = mfspr(SPRN_RPR);
+		sprs.tscr = mfspr(SPRN_TSCR);

Per-subcore SPRs aren't being saved here ?

Quoted 49 lines
arch/powerpc/platforms/powernv/idle.c +49 -0
+
+		sprs.lpcr = mfspr(SPRN_LPCR);
+		sprs.hfscr = mfspr(SPRN_HFSCR);
+		sprs.fscr = mfspr(SPRN_FSCR);
+		sprs.pid = mfspr(SPRN_PID);
+		sprs.purr = mfspr(SPRN_PURR);
+		sprs.spurr = mfspr(SPRN_SPURR);
+		sprs.dscr = mfspr(SPRN_DSCR);
+
+		sprs.mmcra = mfspr(SPRN_MMCRA);
+		sprs.mmcr0 = mfspr(SPRN_MMCR0);
+		sprs.mmcr1 = mfspr(SPRN_MMCR1);
+		sprs.mmcr2 = mfspr(SPRN_MMCR2);
+	}
+
+	atomic_unlock_thread_idle();
+
+	srr1 = isa206_idle_insn_mayloss(type);
+
+	WARN_ON_ONCE(!srr1);
+	WARN_ON_ONCE(mfmsr() & (MSR_IR|MSR_DR));
+
+	if (unlikely((srr1 & SRR1_WAKEMASK_P8) == SRR1_WAKEHMI))
+		hmi_exception_realmode(NULL);
+
+	if (likely((srr1 & SRR1_WAKESTATE) != SRR1_WS_HVLOSS))
+		return srr1;
+
+	/* HV state loss */
+	if (type == PNV_THREAD_NAP) {
+		BUG();
+		for (;;)
+			;
+	}
+
+	atomic_lock_thread_idle();
+
+	WARN_ON(*state & thread);
+
+	if ((*state & ((1 << threads_per_core) - 1)) != 0) {
+		isync();
+		goto core_woken;
+	}
+
+	/* Per-core SPRs */
+	mtspr(SPRN_RPR, sprs.rpr);
+	mtspr(SPRN_TSCR, sprs.tscr);
+
+	if (power7_fastsleep_workaround_exit) {

arch/powerpc/platforms/powernv/idle.c static DEVICE_ATTR(fastsleep_workaround_applyonce, 0600, · line 446

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +8 -0
+		rc = opal_config_cpu_idle_state(OPAL_CONFIG_IDLE_FASTSLEEP,
+						OPAL_CONFIG_IDLE_UNDO);
+		WARN_ON(rc);
+	}
+
 
 
+	/* TB */
+	if (opal_resync_timebase() != OPAL_SUCCESS)
+		BUG();

If we are waking up only from fastsleep, we won't lose any SPRs, but only the timebase. So we can return at this point.

Quoted 115 lines
arch/powerpc/platforms/powernv/idle.c +98 -0
+
+core_woken:
+	atomic_unlock_and_stop_thread_idle();
+
+	/* Per-thread SPRs */
+	mtspr(SPRN_LPCR, sprs.lpcr);
+	mtspr(SPRN_HFSCR, sprs.hfscr);
+	mtspr(SPRN_FSCR, sprs.fscr);
+	mtspr(SPRN_PID, sprs.pid);
+	mtspr(SPRN_PURR, sprs.purr);
+	mtspr(SPRN_SPURR, sprs.spurr);
+	mtspr(SPRN_DSCR, sprs.dscr);
+
+	mtspr(SPRN_MMCRA, sprs.mmcra);
+	mtspr(SPRN_MMCR0, sprs.mmcr0);
+	mtspr(SPRN_MMCR1, sprs.mmcr1);
+	mtspr(SPRN_MMCR2, sprs.mmcr2);
+
+	__slb_flush_and_rebolt();
+
+	return srr1;
+}
+
+extern unsigned long idle_kvm_start_guest(unsigned long srr1);
+
+unsigned long power7_offline(void)
+{
+	unsigned long srr1;
+
+	mtmsr(MSR_IDLE);
+
+#ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+	/* Tell KVM we're entering idle. */
+	/******************************************************/
+	/*  N O T E   W E L L    ! ! !    N O T E   W E L L   */
+	/* The following store to HSTATE_HWTHREAD_STATE(r13)  */
+	/* MUST occur in real mode, i.e. with the MMU off,    */
+	/* and the MMU must stay off until we clear this flag */
+	/* and test HSTATE_HWTHREAD_REQ(r13) in               */
+	/* pnv_powersave_wakeup in this file.                 */
+	/* The reason is that another thread can switch the   */
+	/* MMU to a guest context whenever this flag is set   */
+	/* to KVM_HWTHREAD_IN_IDLE, and if the MMU was on,    */
+	/* that would potentially cause this thread to start  */
+	/* executing instructions from guest memory in        */
+	/* hypervisor mode, leading to a host crash or data   */
+	/* corruption, or worse.                              */
+	/******************************************************/
+	local_paca->kvm_hstate.hwthread_state = KVM_HWTHREAD_IN_IDLE;
+#endif
+
+	__ppc64_runlatch_off();
+	srr1 = power7_idle_insn(power7_offline_type);
+	__ppc64_runlatch_on();
+
+#ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+	if (local_paca->kvm_hstate.hwthread_state != KVM_HWTHREAD_IN_KERNEL) {
+		local_paca->kvm_hstate.hwthread_state = KVM_HWTHREAD_IN_KERNEL;
+		/* Order setting hwthread_state vs. testing hwthread_req */
+		smp_mb();
+	}
+	if (local_paca->kvm_hstate.hwthread_req)
+		srr1 = idle_kvm_start_guest(srr1);
+#endif
+
+	mtmsr(MSR_KERNEL);
+
+	return srr1;
+}
+
 static unsigned long __power7_idle_type(unsigned long type)
 {
 	unsigned long srr1;
@@ -320,9 +521,11 @@ static unsigned long __power7_idle_type(unsigned long type)
 	if (!prep_irq_for_idle_irqsoff())
 		return 0;
 
+	mtmsr(MSR_IDLE);
 	__ppc64_runlatch_off();
 	srr1 = power7_idle_insn(type);
 	__ppc64_runlatch_on();
+	mtmsr(MSR_KERNEL);
 
 	fini_irq_for_idle_irqsoff();
 
@@ -345,6 +548,242 @@ void power7_idle(void)
 	power7_idle_type(PNV_THREAD_NAP);
 }
 
+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 mmcra;
+	u32 mmcr0;
+	u32 mmcr1;
+	u64 mmcr2;
+};
+
+static unsigned long power9_idle_stop(unsigned long psscr, bool mmu_on)
+{

arch/powerpc/platforms/powernv/idle.c void power7_idle(void) · line 586

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +10 -0
+	int cpu = raw_smp_processor_id();
+	int first = cpu_first_thread_sibling(cpu);
+	unsigned long *state = &paca_ptrs[first]->idle_state;
+	unsigned long srr1;
+	unsigned long mmcr0 = 0;
+	struct p9_sprs sprs;
+	bool sprs_saved = false;
+
+	if (!(psscr & (PSSCR_EC|PSSCR_ESL))) {
+		WARN_ON(!mmu_on);

So this is a warning that we are trying to perform a ESL = 0 stop from a kvm offline case. Hence we cannot return IR|DR disabled.

Quoted 51 lines
arch/powerpc/platforms/powernv/idle.c +51 -0
+
+		/*
+		 * Wake synchronously. SRESET via xscom may still cause
+		 * a 0x100 powersave wakeup with SRR1 reason!
+		 */
+		srr1 = isa3_idle_stop_noloss(psscr);
+		if (likely(!srr1))
+			return 0;
+
+		if ((srr1 & SRR1_WAKESTATE) != SRR1_WS_NOLOSS) {
+			/*
+			 * Registers not saved, can't recover!
+			 * This would be a hardware bug
+			 */
+			BUG();
+			for (;;)
+				;
+		}
+
+	} else {
+		if (!cpu_has_feature(CPU_FTR_POWER9_DD2_1)) {
+			 /*
+			  * POWER9 DD2 can incorrectly set PMAO when waking up
+			  * after a state-loss idle. Saving and restoring MMCR0
+			  * over idle is a workaround.
+			  */
+			mmcr0 = mfspr(SPRN_MMCR0);
+		}
+		if ((psscr & PSSCR_RL_MASK) >= pnv_first_hv_loss_level) {
+			sprs.lpcr = mfspr(SPRN_LPCR);
+			sprs.hfscr = mfspr(SPRN_HFSCR);
+			sprs.fscr = mfspr(SPRN_FSCR);
+			sprs.pid = mfspr(SPRN_PID);
+			sprs.purr = mfspr(SPRN_PURR);
+			sprs.spurr = mfspr(SPRN_SPURR);
+			sprs.dscr = mfspr(SPRN_DSCR);
+			sprs.wort = mfspr(SPRN_WORT);
+
+			sprs.mmcra = mfspr(SPRN_MMCRA);
+			sprs.mmcr0 = mfspr(SPRN_MMCR0);
+			sprs.mmcr1 = mfspr(SPRN_MMCR1);
+			sprs.mmcr2 = mfspr(SPRN_MMCR2);
+
+			sprs.ptcr = mfspr(SPRN_PTCR);
+			sprs.rpr = mfspr(SPRN_RPR);
+			sprs.tscr = mfspr(SPRN_TSCR);
+			sprs.ldbar = mfspr(SPRN_LDBAR);
+			sprs.amor = mfspr(SPRN_AMOR);
+
+			atomic_start_thread_idle();
+			sprs_saved = true;

arch/powerpc/platforms/powernv/idle.c void power7_idle(void) · line 647

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +10 -0
+		}
+#ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+		if (cpu_has_feature(CPU_FTR_P9_TM_XER_SO_BUG)) {
+			local_paca->requested_psscr = psscr;
+			/* order setting requested_psscr vs testing dont_stop */
+			smp_mb();
+			if (atomic_read(&local_paca->dont_stop)) {
+				local_paca->requested_psscr = 0;
+				return 0;
+			}

Can we move the local_paca->dont_stop check early on before saving the SPRs ?

Quoted 83 lines
arch/powerpc/platforms/powernv/idle.c +83 -0
+
+			srr1 = isa3_idle_stop_mayloss(psscr);
+			local_paca->requested_psscr = 0;
+		} else
+#endif
+			srr1 = isa3_idle_stop_mayloss(psscr);
+
+	       psscr = mfspr(SPRN_PSSCR);
+	}
+
+	WARN_ON_ONCE(!srr1);
+	WARN_ON_ONCE(mfmsr() & (MSR_IR|MSR_DR));
+
+	if ((srr1 & SRR1_WAKESTATE) != SRR1_WS_NOLOSS) {
+		unsigned long mmcra;
+
+		/*
+		 * Workaround for POWER9 DD2, if we lost resources, the ERAT
+		 * might have been mixed up and needs flushing. We also need
+		 * to reload MMCR0 (see mmcr0 comment above).
+		 */
+		if (!cpu_has_feature(CPU_FTR_POWER9_DD2_1)) {
+			asm volatile(PPC_INVALIDATE_ERAT);
+			mtspr(SPRN_MMCR0, mmcr0);
+		}
+
+		/*
+		 * DD2.2 and earlier need to set then clear bit 60 in MMCRA
+		 * to ensure the PMU starts running.
+		 */
+		mmcra = mfspr(SPRN_MMCRA);
+		mmcra |= PPC_BIT(60);
+		mtspr(SPRN_MMCRA, mmcra);
+		mmcra &= ~PPC_BIT(60);
+		mtspr(SPRN_MMCRA, mmcra);
+	}
+
+	if (unlikely((srr1 & SRR1_WAKEMASK_P8) == SRR1_WAKEHMI))
+		hmi_exception_realmode(NULL);
+
+	/*
+	 * On POWER9, SRR1 bits do not match exactly as expected.
+	 * SRR1_WS_GPRLOSS (10b) can also result in SPR loss, so
+	 * always test PSSCR if there is any state loss.
+	 */
+	if (likely((psscr & PSSCR_RL_MASK) < pnv_first_hv_loss_level)) {
+		if (sprs_saved)
+			atomic_stop_thread_idle();
+		goto out;
+	}
+
+	/* HV state loss */
+	if (!sprs_saved) {
+		BUG();
+		for (;;)
+			;
+	}
+
+	atomic_lock_thread_idle();
+
+	if ((*state & ((1 << threads_per_core) - 1)) != 0)
+		goto core_woken;
+
+	/* Per-core SPRs */
+	mtspr(SPRN_PTCR, sprs.ptcr);
+	mtspr(SPRN_RPR, sprs.rpr);
+	mtspr(SPRN_TSCR, sprs.tscr);
+	mtspr(SPRN_LDBAR, sprs.ldbar);
+	mtspr(SPRN_AMOR, sprs.amor);
+
+	if ((psscr & PSSCR_RL_MASK) >= pnv_first_tb_loss_level) {
+		/* TB loss */
+		if (opal_resync_timebase() != OPAL_SUCCESS)
+			BUG();
+	}
+
+core_woken:
+	atomic_unlock_and_stop_thread_idle();
+
+	/* Per-thread SPRs */
+	mtspr(SPRN_LPCR, sprs.lpcr);
+	mtspr(SPRN_HFSCR, sprs.hfscr);
+	mtspr(SPRN_FSCR, sprs.fscr);

arch/powerpc/platforms/powernv/idle.c void power7_idle(void) · line 740

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +10 -0
+	mtspr(SPRN_PID, sprs.pid);
+	mtspr(SPRN_PURR, sprs.purr);
+	mtspr(SPRN_SPURR, sprs.spurr);
+	mtspr(SPRN_DSCR, sprs.dscr);
+	mtspr(SPRN_WORT, sprs.wort);
+
+	mtspr(SPRN_MMCRA, sprs.mmcra);
+	mtspr(SPRN_MMCR0, sprs.mmcr0);
+	mtspr(SPRN_MMCR1, sprs.mmcr1);
+	mtspr(SPRN_MMCR2, sprs.mmcr2);

We need to set SPRN_SPRG3 to local_paca->sprg_vdso. Something I missed last year while adding restore_additional_sprs.

Quoted 21 lines
arch/powerpc/platforms/powernv/idle.c +21 -0
+
+	if (!radix_enabled())
+		__slb_flush_and_rebolt();
+
+out:
+	if (mmu_on)
+		mtmsr(MSR_KERNEL);
+
+	return srr1;
+}
+
+unsigned long power9_offline_stop(unsigned long psscr)
+{
+	unsigned long srr1;
+
+#ifndef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+	__ppc64_runlatch_off();
+	srr1 = power9_idle_stop(psscr, true);
+	__ppc64_runlatch_on();
+#else
+	/*

arch/powerpc/platforms/powernv/idle.c void power7_idle(void) · line 771

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +10 -0
+	 * Tell KVM we're entering idle.
+	 * This does not have to be done in real mode because the P9 MMU
+	 * is independent per-thread. Some steppings share radix/hash mode
+	 * between threads, but in that case KVM has a barrier sync in real
+	 * mode before and after switching between radix and hash.
+	 */
+	local_paca->kvm_hstate.hwthread_state = KVM_HWTHREAD_IN_IDLE;
+
+	__ppc64_runlatch_off();
+	srr1 = power9_idle_stop(psscr, false);

Ok, so the "mmu_on" parameter for power9_idle_stop() indicates whether we should return from the function call with translation enabled or not. In the case we are offlining the secondaries in the context of KVM, we want to ensure that when we wakeup we are still in real-mode so that we can perform the hypervisor--> guest transition in real-mode.

Is this understanding correct ?

If yes, and we requested an ESL=0 stop here (may be the device tree only had lite states), then srr1 returned by the function will be 0 here.

Shouldn't we explicitly set an MSR_IDLE at this point ?

Quoted 102 lines
arch/powerpc/platforms/powernv/idle.c +25 -22
+	__ppc64_runlatch_on();
+
+	if (local_paca->kvm_hstate.hwthread_state != KVM_HWTHREAD_IN_KERNEL) {
+		local_paca->kvm_hstate.hwthread_state = KVM_HWTHREAD_IN_KERNEL;
+		/* Order setting hwthread_state vs. testing hwthread_req */
+		smp_mb();
+	}
+	if (local_paca->kvm_hstate.hwthread_req)
+		srr1 = idle_kvm_start_guest(srr1);
+	mtmsr(MSR_KERNEL);
+#endif
+
+	return srr1;
+}
+
 static unsigned long __power9_idle_type(unsigned long stop_psscr_val,
 				      unsigned long stop_psscr_mask)
 {
@@ -358,7 +797,7 @@ static unsigned long __power9_idle_type(unsigned long stop_psscr_val,
 	psscr = (psscr & ~stop_psscr_mask) | stop_psscr_val;
 
 	__ppc64_runlatch_off();
-	srr1 = power9_idle_stop(psscr);
+	srr1 = power9_idle_stop(psscr, true);
 	__ppc64_runlatch_on();
 
 	fini_irq_for_idle_irqsoff();
@@ -407,7 +846,7 @@ void pnv_power9_force_smt4_catch(void)
 			atomic_inc(&paca_ptrs[cpu0+thr]->dont_stop);
 	}
 	/* order setting dont_stop vs testing requested_psscr */
-	mb();
+	smp_mb();
 	for (thr = 0; thr < threads_per_core; ++thr) {
 		if (!paca_ptrs[cpu0+thr]->requested_psscr)
 			++awake_threads;
@@ -466,7 +905,7 @@ static void pnv_program_cpu_hotplug_lpcr(unsigned int cpu, u64 lpcr_val)
 	 * Program the LPCR via stop-api only if the deepest stop state
 	 * can lose hypervisor context.
 	 */
-	if (supported_cpuidle_states & OPAL_PM_LOSE_FULL_CONTEXT)
+	if (pm_flags_possible & OPAL_PM_LOSE_FULL_CONTEXT)
 		opal_slw_set_reg(pir, SPRN_LPCR, lpcr_val);
 }
 
@@ -478,7 +917,6 @@ static void pnv_program_cpu_hotplug_lpcr(unsigned int cpu, u64 lpcr_val)
 unsigned long pnv_cpu_offline(unsigned int cpu)
 {
 	unsigned long srr1;
-	u32 idle_states = pnv_get_supported_cpuidle_states();
 	u64 lpcr_val;
 
 	/*
@@ -503,15 +941,8 @@ unsigned long pnv_cpu_offline(unsigned int cpu)
 		psscr = (psscr & ~pnv_deepest_stop_psscr_mask) |
 						pnv_deepest_stop_psscr_val;
 		srr1 = power9_offline_stop(psscr);
-
-	} else if ((idle_states & OPAL_PM_WINKLE_ENABLED) &&
-		   (idle_states & OPAL_PM_LOSE_FULL_CONTEXT)) {
-		srr1 = power7_idle_insn(PNV_THREAD_WINKLE);
-	} else if ((idle_states & OPAL_PM_SLEEP_ENABLED) ||
-		   (idle_states & OPAL_PM_SLEEP_ENABLED_ER1)) {
-		srr1 = power7_idle_insn(PNV_THREAD_SLEEP);
-	} else if (idle_states & OPAL_PM_NAP_ENABLED) {
-		srr1 = power7_idle_insn(PNV_THREAD_NAP);
+	} else if (cpu_has_feature(CPU_FTR_ARCH_206) && power7_offline_type) {
+		srr1 = power7_offline();
 	} else {
 		/* This is the fallback method. We emulate snooze */
 		while (!generic_check_cpu_restart(cpu)) {
@@ -623,7 +1054,8 @@ static int __init pnv_power9_idle_init(struct device_node *np, u32 *flags,
 	u64 *psscr_val = NULL;
 	u64 *psscr_mask = NULL;
 	u32 *residency_ns = NULL;
-	u64 max_residency_ns = 0;
+	u64 max_deep_residency_ns = 0;
+	u64 max_default_residency_ns = 0;
 	int rc = 0, i;
 
 	psscr_val = kcalloc(dt_idle_states, sizeof(*psscr_val), GFP_KERNEL);
@@ -661,26 +1093,33 @@ static int __init pnv_power9_idle_init(struct device_node *np, u32 *flags,
 	}
 
 	/*
-	 * Set pnv_first_deep_stop_state, pnv_deepest_stop_psscr_{val,mask},
-	 * and the pnv_default_stop_{val,mask}.
-	 *
-	 * pnv_first_deep_stop_state should be set to the first stop
-	 * level to cause hypervisor state loss.
-	 *
 	 * pnv_deepest_stop_{val,mask} should be set to values corresponding to
 	 * the deepest stop state.
 	 *
 	 * pnv_default_stop_{val,mask} should be set to values corresponding to
-	 * the shallowest (OPAL_PM_STOP_INST_FAST) loss-less stop state.
+	 * the deepest loss-less (OPAL_PM_STOP_INST_FAST) stop state.
 	 */
-	pnv_first_deep_stop_state = MAX_STOP_STATE;
+	pnv_first_tb_loss_level = MAX_STOP_STATE + 1;
+	pnv_first_hv_loss_level = MAX_STOP_STATE + 1;
 	for (i = 0; i < dt_idle_states; i++) {

arch/powerpc/platforms/powernv/idle.c static int __init pnv_power9_idle_init(struct device_node *np, u32 *flags, · line 1114

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +7 -0
 		int err;
 		u64 psscr_rl = psscr_val[i] & PSSCR_RL_MASK;
 
+		/*
+		 * Deep state idle entry/exit does not currently cope with
+		 * states that lose timebase but not SPRs, skip.
+		 */
+		if ( (flags[i] & OPAL_PM_TIMEBASE_STOP) &&
+		    !(flags[i] & OPAL_PM_LOSE_FULL_CONTEXT))
+			continue;

On POWER8 we have the capability of handling fastsleep which loses timebase but not SPRs.

Would it be ok to set the pnv_first_hv_loss_level to the smallest of the first state that loses either TIMEBASE or FULL_CONTEXT ? Worst case we will end up restoring the SPRs which are not lost, but functionally that is not incorrect no?

arch/powerpc/platforms/powernv/idle.c static int __init pnv_power9_idle_init(struct device_node *np, u32 *flags, · line 1125

Quoted 13 lines
arch/powerpc/platforms/powernv/idle.c +7 -2
+
+		if ((flags[i] & OPAL_PM_TIMEBASE_STOP) &&
+		     (pnv_first_tb_loss_level > psscr_rl))
+			pnv_first_tb_loss_level = psscr_rl;
+
 		if ((flags[i] & OPAL_PM_LOSE_FULL_CONTEXT) &&
-		     (pnv_first_deep_stop_state > psscr_rl))
-			pnv_first_deep_stop_state = psscr_rl;
+		     (pnv_first_hv_loss_level > psscr_rl))
+			pnv_first_hv_loss_level = psscr_rl;
 
 		err = validate_psscr_val_mask(&psscr_val[i], &psscr_mask[i],
 					      flags[i]);

--
Thanks and Regards
gautham.

author reviewer
#2 lore raw

Hi Gautham,

Thanks for the review, I also missed one or two things from you last one, but I haven't forgotten them.

Quoted 31 lines · On Wed, 25 Jul 2018 16:56:45 +0530 Gautham R Shenoy <[email protected]>…

Hello Nicholas,

Quoted 28 lines · Nicholas Piggin

Reimplement Book3S idle code to C, in the powernv platform code. Assembly stubs are used to save and restore the stack frame and non-volatile GPRs before going to idle, but these are small and mostly agnostic to microarchitecture implementation details.

The optimisation where EC=ESL=0 idle modes did not have to save GPRs or mtmsrd L=0 is restored, because it's simple to do.

Idle wakeup no longer uses the ->cpu_restore call to reinit SPRs, but saves and restores them all explicitly. This can easily be extended to tracking the set of system-wide SPRs that do not have to be saved each time.

Moving the HMI, SPR, OPAL, locking, etc. to C is the only real way this stuff will cope with non-trivial new CPU implementation details, firmware changes, etc., without becoming unmaintainable.

Since RFC v1:
- Now tested and working with POWER9 hash and radix.
- KVM support added. This took a bit of work to untangle and might still have some issues, but POWER9 seems to work including hash on radix with dependent threads mode.
- This snowballed a bit because of KVM and other details making it not feasible to leave POWER7/8 code alone. That's only half done at the moment.
- So far this trades about 800 lines of asm for 500 of C. With POWER7/8 support done it might be another hundred or so lines of C.

Quoted 6 lines
Quoted 2 lines

Would appreciate any feedback on the approach in particular the significantly different (and hopefully cleaner) KVM approach.

I have reviewed the C-code movement from idle_book3s.S to powernv/idle.c. Some comments on that part of the code inline.

Thanks. Yes the POWER7/8 code is a bit incomplete as you noticed, but you had some good suggestions there too.

Quoted 1 line

I haven't yet gone through the book3s_hv_rmhandlers.S code changes.

Yeah that's very tricky code and I don't know if I got it right yet. Will have to run it past the KVM people too.

Quoted 25 lines
Quoted 6 lines

Thanks,
Nick

[..snip..]

Quoted 16 lines

#ifdef CONFIG_PPC_POWERNV

arch/powerpc/include/asm/paca.h +3 -11
-	/* Per-core mask tracking idle threads and a lock bit-[L][TTTTTTTT] */
-	u32 *core_idle_state_ptr;
-	u8 thread_idle_state;		/* PNV_THREAD_RUNNING/NAP/SLEEP	*/
-	/* Mask to indicate thread id in core */
-	u8 thread_mask;
-	/* Mask to denote subcore sibling threads */
-	u8 subcore_sibling_mask;
-	/* Flag to request this thread not to stop */
-	atomic_t dont_stop;
-	/* The PSSCR value that the kernel requested before going to stop */
-	u64 requested_psscr;
+	union {
+		/* P7/P8 specific fields */
+		struct {
Quoted 8 lines

arch/powerpc/include/asm/paca.h struct paca_struct { · line 184

Quoted 2 lines
arch/powerpc/include/asm/paca.h +2 -0
+			/* Per-core mask tracking idle threads and a lock bit-[L][TTTTTTTT] */
+			unsigned long *core_idle_state_ptr;  

Do we still need *core_idle_state_ptr ? Since this code is accessed through C where we have access to paca_ptrs global variable we can reference the "idle_state" (defined for P9 below). A lot of locking/unlocking code in powernv/idle.c further down the patch already does that, right?

Yeah you're probably right I think.

Quoted 6 lines

arch/powerpc/platforms/powernv/idle.c static DEVICE_ATTR(fastsleep_workaround_applyonce, 0600, · line 389

Quoted 4 lines
arch/powerpc/platforms/powernv/idle.c +4 -0
+
+	if (type == PNV_THREAD_WINKLE) {
+		sprs.rpr = mfspr(SPRN_RPR);
+		sprs.tscr = mfspr(SPRN_TSCR);  

Per-subcore SPRs aren't being saved here ?

Right, it's a bit incomplete. I'll post out a new version with P8 support soon.

Quoted 9 lines
Quoted 7 lines
arch/powerpc/platforms/powernv/idle.c +7 -0
+
+	if (power7_fastsleep_workaround_exit) {
+		rc = opal_config_cpu_idle_state(OPAL_CONFIG_IDLE_FASTSLEEP,
+						OPAL_CONFIG_IDLE_UNDO);
+		WARN_ON(rc);
+	}
+  
Quoted 6 lines

arch/powerpc/platforms/powernv/idle.c static DEVICE_ATTR(fastsleep_workaround_applyonce, 0600, · line 446

Quoted 3 lines
arch/powerpc/platforms/powernv/idle.c +3 -0
+	/* TB */
+	if (opal_resync_timebase() != OPAL_SUCCESS)
+		BUG();  

If we are waking up only from fastsleep, we won't lose any SPRs, but only the timebase. So we can return at this point.

Okay, got it.

Quoted 7 lines
Quoted 7 lines
arch/powerpc/platforms/powernv/idle.c +7 -0
+static unsigned long power9_idle_stop(unsigned long psscr, bool mmu_on)
+{
+	int cpu = raw_smp_processor_id();
+	int first = cpu_first_thread_sibling(cpu);
+	unsigned long *state = &paca_ptrs[first]->idle_state;
+	unsigned long srr1;
+	unsigned long mmcr0 = 0;
Quoted 8 lines

arch/powerpc/platforms/powernv/idle.c void power7_idle(void) · line 586

Quoted 5 lines
arch/powerpc/platforms/powernv/idle.c +5 -0
+	struct p9_sprs sprs;
+	bool sprs_saved = false;
+
+	if (!(psscr & (PSSCR_EC|PSSCR_ESL))) {
+		WARN_ON(!mmu_on);  

So this is a warning that we are trying to perform a ESL = 0 stop from a kvm offline case. Hence we cannot return IR|DR disabled.

Yes.

Quoted 4 lines
Quoted 4 lines
arch/powerpc/platforms/powernv/idle.c +4 -0
+#ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+		if (cpu_has_feature(CPU_FTR_P9_TM_XER_SO_BUG)) {
+			local_paca->requested_psscr = psscr;
+			/* order setting requested_psscr vs testing dont_stop */
Quoted 8 lines

arch/powerpc/platforms/powernv/idle.c void power7_idle(void) · line 647

Quoted 5 lines
arch/powerpc/platforms/powernv/idle.c +5 -0
+			smp_mb();
+			if (atomic_read(&local_paca->dont_stop)) {
+				local_paca->requested_psscr = 0;
+				return 0;
+			}  

Can we move the local_paca->dont_stop check early on before saving the SPRs ?

It should be a very rare case but yes I don't see why not.

Quoted 9 lines
Quoted 9 lines
arch/powerpc/platforms/powernv/idle.c +9 -0
+	/* Per-thread SPRs */
+	mtspr(SPRN_LPCR, sprs.lpcr);
+	mtspr(SPRN_HFSCR, sprs.hfscr);
+	mtspr(SPRN_FSCR, sprs.fscr);
+	mtspr(SPRN_PID, sprs.pid);
+	mtspr(SPRN_PURR, sprs.purr);
+	mtspr(SPRN_SPURR, sprs.spurr);
+	mtspr(SPRN_DSCR, sprs.dscr);
+	mtspr(SPRN_WORT, sprs.wort);
Quoted 8 lines

arch/powerpc/platforms/powernv/idle.c static DEVICE_ATTR(fastsleep_workaround_applyonce, 0600, · line 463

Quoted 5 lines
arch/powerpc/platforms/powernv/idle.c +5 -0
+
+	mtspr(SPRN_MMCRA, sprs.mmcra);
+	mtspr(SPRN_MMCR0, sprs.mmcr0);
+	mtspr(SPRN_MMCR1, sprs.mmcr1);
+	mtspr(SPRN_MMCR2, sprs.mmcr2);  

We need to set SPRN_SPRG3 to local_paca->sprg_vdso. Something I missed last year while adding restore_additional_sprs.

Will add that.

Quoted 29 lines
Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +10 -0
+unsigned long power9_offline_stop(unsigned long psscr)
+{
+	unsigned long srr1;
+
+#ifndef CONFIG_KVM_BOOK3S_HV_POSSIBLE
+	__ppc64_runlatch_off();
+	srr1 = power9_idle_stop(psscr, true);
+	__ppc64_runlatch_on();
+#else
+	/*

arch/powerpc/platforms/powernv/idle.c void power7_idle(void) · line 771

Quoted 10 lines
arch/powerpc/platforms/powernv/idle.c +10 -0
+	 * Tell KVM we're entering idle.
+	 * This does not have to be done in real mode because the P9 MMU
+	 * is independent per-thread. Some steppings share radix/hash mode
+	 * between threads, but in that case KVM has a barrier sync in real
+	 * mode before and after switching between radix and hash.
+	 */
+	local_paca->kvm_hstate.hwthread_state = KVM_HWTHREAD_IN_IDLE;
+
+	__ppc64_runlatch_off();
+	srr1 = power9_idle_stop(psscr, false);  

Ok, so the "mmu_on" parameter for power9_idle_stop() indicates whether we should return from the function call with translation enabled or not. In the case we are offlining the secondaries in the context of KVM, we want to ensure that when we wakeup we are still in real-mode so that we can perform the hypervisor--> guest transition in real-mode.

Quoted 1 line

Is this understanding correct ?

Yes basically. Well we can wake up in guest MMU context so host can't turn the MMU back on before calling back to KVM code.

Quoted 5 lines

If yes, and we requested an ESL=0 stop here (may be the device tree only had lite states), then srr1 returned by the function will be 0 here.

Shouldn't we explicitly set an MSR_IDLE at this point ?

Previously we could not support EC=0 mode because KVM required a SRESET wake up. The C code does not require that, but I still don't think it can work properly because we don't get an SRR1. Without that, I think KVM doesn't know what to do when it wakes up. So I think it's a bit harder than it looks.

Quoted 24 lines
Quoted 24 lines
arch/powerpc/platforms/powernv/idle.c +3 -8
@@ -661,26 +1093,33 @@ static int __init pnv_power9_idle_init(struct device_node *np, u32 *flags,
 	}
 
 	/*
-	 * Set pnv_first_deep_stop_state, pnv_deepest_stop_psscr_{val,mask},
-	 * and the pnv_default_stop_{val,mask}.
-	 *
-	 * pnv_first_deep_stop_state should be set to the first stop
-	 * level to cause hypervisor state loss.
-	 *
 	 * pnv_deepest_stop_{val,mask} should be set to values corresponding to
 	 * the deepest stop state.
 	 *
 	 * pnv_default_stop_{val,mask} should be set to values corresponding to
-	 * the shallowest (OPAL_PM_STOP_INST_FAST) loss-less stop state.
+	 * the deepest loss-less (OPAL_PM_STOP_INST_FAST) stop state.
 	 */
-	pnv_first_deep_stop_state = MAX_STOP_STATE;
+	pnv_first_tb_loss_level = MAX_STOP_STATE + 1;
+	pnv_first_hv_loss_level = MAX_STOP_STATE + 1;
 	for (i = 0; i < dt_idle_states; i++) {
 		int err;
 		u64 psscr_rl = psscr_val[i] & PSSCR_RL_MASK;
 
Quoted 10 lines

arch/powerpc/platforms/powernv/idle.c static int __init pnv_power9_idle_init(struct device_node *np, u32 *flags, · line 1114

Quoted 7 lines
arch/powerpc/platforms/powernv/idle.c +7 -0
+		/*
+		 * Deep state idle entry/exit does not currently cope with
+		 * states that lose timebase but not SPRs, skip.
+		 */
+		if ( (flags[i] & OPAL_PM_TIMEBASE_STOP) &&
+		    !(flags[i] & OPAL_PM_LOSE_FULL_CONTEXT))
+			continue;  

On POWER8 we have the capability of handling fastsleep which loses timebase but not SPRs.

Right.

Quoted 4 lines

Would it be ok to set the pnv_first_hv_loss_level to the smallest of the first state that loses either TIMEBASE or FULL_CONTEXT ? Worst case we will end up restoring the SPRs which are not lost, but functionally that is not incorrect no?

This is POWER9 init so it should be okay, I will do that for POWER8.

I could just force the FULL_STOP flag for POWER9 case so theoretical future state that loses TB first can at least be used as you say, without adding too much complexity or untested code.

Thanks,
Nick