[Skiboot] [RFC PATCH v2 2/2] opal : Support spr save-restore using new
Akshay Adiga
akshay.adiga at linux.vnet.ibm.com
Mon Oct 15 20:15:31 AEDT 2018
From: Abhishek Goel <huntbag at 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 at 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(-)
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 at 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 */
--
2.17.1
More information about the Skiboot
mailing list