[Skiboot] [PATCH] core/fast-reboot.c: Add sreset opal call

Alistair Popple alistair at popple.id.au
Tue Nov 15 13:49:13 AEDT 2016


Sending a NMI to other CPUs regardless of their current state requires
a way to reset them. POWER hardware has a method of directly injecting
resets via direct thread control, however this only works if the
thread is not active (eg. in a sleep or nap state).

Resetting an active thread can be performed either via forcing the
threads to an inactive state (as fast reboot does) or by ramming an
instruction sequence that simulates an sreset. This patch implements
the latter as forcing a thread to the inactive state is not ideal for
debug purposes as the threads loose state.

Signed-off-by: Alistair Popple <alistair at popple.id.au>
---

Currently active threads on the currently executing core cannot be
sreset as a thread cannot ram other threads on the same core. This
means the caller will need to reset these threads to make the call
from a different core.

core/fast-reboot.c                            | 349 +++++++++++++++++++++++++-
 doc/opal-api/opal-signal-system-reset-128.txt |  30 +++
 include/opal-api.h                            |   3 +-
 include/skiboot.h                             |   1 +
 platforms/astbmc/common.c                     |   2 +
 5 files changed, 375 insertions(+), 10 deletions(-)
 create mode 100644 doc/opal-api/opal-signal-system-reset-128.txt

diff --git a/core/fast-reboot.c b/core/fast-reboot.c
index 66b3182..08d7c25 100644
--- a/core/fast-reboot.c
+++ b/core/fast-reboot.c
@@ -28,10 +28,33 @@
 #include <chiptod.h>

 #define P8_EX_TCTL_DIRECT_CONTROLS(t)	(0x10013000 + (t) * 0x10)
-#define P8_DIRECT_CTL_STOP		PPC_BIT(63)
 #define P8_DIRECT_CTL_PRENAP		PPC_BIT(47)
 #define P8_DIRECT_CTL_SRESET		PPC_BIT(60)
-
+#define P8_DIRECT_CTL_START		PPC_BIT(62)
+#define P8_DIRECT_CTL_STOP		PPC_BIT(63)
+#define P8_EX_TCTL_RAS_STATUS(t)	(0x10013002 + (t) * 0x10)
+#define RAS_STATUS_SRQ_EMPTY		PPC_BIT(8)
+#define RAS_STATUS_LSU_QUIESCED	PPC_BIT(9)
+#define RAS_STATUS_INST_COMPLETE	PPC_BIT(12)
+#define RAS_STATUS_THREAD_ACTIVE	PPC_BIT(48)
+#define RAS_STATUS_TS_QUIESCE		PPC_BIT(49)
+#define P8_EX_THREAD_ACTIVE		0x1001310e
+#define P8_EX_SPRC_REG			0x10013280
+#define SPRC_REG_SCRATCH_SPR		PPC_BIT(57)
+#define P8_EX_SPR_MODE_REG		0x10013281
+#define SPR_MODE_SPRC_WR_EN		PPC_BIT(3)
+#define SPR_MODE_SPRC_SEL		PPC_BITMASK(16, 19)
+#define SPR_MODE_SPRC_T_SEL		PPC_BITMASK(20, 27)
+#define P8_EX_SCR0_REG			0x10013283
+#define P8_EX_RAM_MODE_REG		0x10013c00
+#define RAM_MODE_ENABLE		PPC_BIT(0)
+#define P8_EX_RAM_CTRL_REG		0x10013c01
+#define RAM_CTRL_THREAD_SELECT		PPC_BITMASK(0, 2)
+#define RAM_CTRL_INSTR			PPC_BITMASK(3, 34)
+#define P8_EX_RAM_STATUS_REG		0x10013c02
+#define RAM_STATUS			PPC_BIT(1)
+
+#define RAS_STATUS_TIMEOUT		10

 /* Flag tested by the OPAL entry code */
 uint8_t reboot_in_progress;
@@ -39,6 +62,14 @@ static volatile bool fast_boot_release;
 static struct cpu_thread *last_man_standing;
 static struct lock reset_lock = LOCK_UNLOCKED;

+/* opcodes for instruction ramming */
+#define MFNIA(r) (0x00000004UL | ((r) << 21))
+#define MTNIA(r) (0x00000002UL | ((r) << 21))
+#define MFMSR(r) (0x7c0000a6UL | ((r) << 21))
+#define MTMSR(r) (0x7c000124UL | ((r) << 21))
+#define MFSPR(r, s) (0x7c0002a6UL | ((r) << 21) | (((s) & 0x1f) << 16) | (((s) & 0x3e0) << 6))
+#define MTSPR(s, r) (0x7c0003a6UL | ((r) << 21) | (((s) & 0x1f) << 16) | (((s) & 0x3e0) << 6))
+
 static int set_special_wakeup(struct cpu_thread *cpu)
 {
 	uint64_t val, poll_target, stamp;
@@ -203,11 +234,10 @@ static int clr_special_wakeup(struct cpu_thread *cpu)

 extern unsigned long callthru_tcl(const char *str, int len);

-static void set_direct_ctl(struct cpu_thread *cpu, uint64_t bits)
+static void set_direct_ctl(struct cpu_thread *cpu, uint32_t thread_id, uint64_t bits)
 {
 	uint32_t core_id = pir_to_core_id(cpu->pir);
 	uint32_t chip_id = pir_to_chip_id(cpu->pir);
-	uint32_t thread_id = pir_to_thread_id(cpu->pir);
 	uint32_t xscom_addr;
 	char tcl_cmd[50];

@@ -225,6 +255,35 @@ static void set_direct_ctl(struct cpu_thread *cpu, uint64_t bits)
 	xscom_write(chip_id, xscom_addr, bits);
 }

+static uint64_t get_ras_status(struct cpu_thread *cpu, uint32_t thread_id)
+{
+	uint32_t core_id = pir_to_core_id(cpu->pir);
+	uint32_t chip_id = pir_to_chip_id(cpu->pir);
+	uint32_t xscom_addr;
+	uint64_t ras_status;
+
+	xscom_addr = XSCOM_ADDR_P8_EX(core_id, P8_EX_TCTL_RAS_STATUS(thread_id));
+	xscom_read(chip_id, xscom_addr, &ras_status);
+
+	return ras_status;
+}
+
+static void set_ram_thread_active(struct cpu_thread *cpu, uint32_t thread_id, bool active)
+{
+	uint32_t core_id = pir_to_core_id(cpu->pir);
+	uint32_t chip_id = pir_to_chip_id(cpu->pir);
+	uint32_t xscom_addr;
+	uint64_t thread_active;
+
+	xscom_addr = XSCOM_ADDR_P8_EX(core_id, P8_EX_THREAD_ACTIVE);
+	xscom_read(chip_id, xscom_addr, &thread_active);
+	if (active)
+		thread_active |= PPC_BIT(8) >> thread_id;
+	else
+		thread_active &= ~(PPC_BIT(8) >> thread_id);
+	xscom_write(chip_id, xscom_addr, thread_active);
+}
+
 static bool fast_reset_p8(void)
 {
 	struct cpu_thread *cpu;
@@ -247,7 +306,7 @@ static bool fast_reset_p8(void)
 	/* Put everybody in stop except myself */
 	for_each_cpu(cpu) {
 		if (cpu != this_cpu())
-			set_direct_ctl(cpu, P8_DIRECT_CTL_STOP);
+			set_direct_ctl(cpu, pir_to_thread_id(cpu->pir), P8_DIRECT_CTL_STOP);

 		/* Also make sure that saved_r1 is 0 ! That's what will
 		 * make our reset vector jump to fast_reboot_entry
@@ -264,7 +323,7 @@ static bool fast_reset_p8(void)
 	/* Put everybody in pre-nap except myself */
 	for_each_cpu(cpu) {
 		if (cpu != this_cpu())
-			set_direct_ctl(cpu, P8_DIRECT_CTL_PRENAP);
+			set_direct_ctl(cpu, pir_to_thread_id(cpu->pir), P8_DIRECT_CTL_PRENAP);
 	}

 	prlog(PR_DEBUG, "RESET: Resetting all threads but one...\n");
@@ -272,7 +331,7 @@ static bool fast_reset_p8(void)
 	/* Reset everybody except my own core threads */
 	for_each_cpu(cpu) {
 		if (cpu != this_cpu())
-			set_direct_ctl(cpu, P8_DIRECT_CTL_SRESET);
+			set_direct_ctl(cpu, pir_to_thread_id(cpu->pir), P8_DIRECT_CTL_SRESET);
 	}

 	return true;
@@ -441,8 +500,8 @@ void __noreturn fast_reboot_entry(void)
 	lock(&reset_lock);
 	if (last_man_standing && next_cpu(first_cpu())) {
 		prlog(PR_DEBUG, "RESET: last man standing fixup...\n");
-		set_direct_ctl(last_man_standing, P8_DIRECT_CTL_PRENAP);
-		set_direct_ctl(last_man_standing, P8_DIRECT_CTL_SRESET);
+		set_direct_ctl(last_man_standing, pir_to_thread_id(last_man_standing->pir), P8_DIRECT_CTL_PRENAP);
+		set_direct_ctl(last_man_standing, pir_to_thread_id(last_man_standing->pir), P8_DIRECT_CTL_SRESET);
 	}
 	last_man_standing = NULL;
 	unlock(&reset_lock);
@@ -544,3 +603,275 @@ void __noreturn fast_reboot_entry(void)
 	/* Load and boot payload */
 	load_and_boot_kernel(true);
 }
+
+/*
+ * RAMs the opcodes in *opcodes and store the results of each opcode
+ * into *results. *results must point to an array the same size as
+ * *opcodes. Each entry from *results is put into SCR0 prior to
+ * executing an opcode so that it may also be used to pass in
+ * data. Note that only register r0 is saved and restored so opcodes
+ * must not touch other registers.
+ */
+static int ram_instructions(struct cpu_thread *cpu, uint32_t thread_id, uint64_t *opcodes,
+			    uint64_t *results, int len, unsigned int lpar)
+{
+	int i, rc = OPAL_SUCCESS;
+	uint64_t ram_mode, val, opcode, r0 = 0;
+	uint32_t chip_id = pir_to_chip_id(cpu->pir);
+	uint32_t core_id = pir_to_core_id(cpu->pir);
+
+
+	/* Activate RAM mode */
+	xscom_read(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_RAM_MODE_REG), &ram_mode);
+	ram_mode |= RAM_MODE_ENABLE;
+
+	/* Enable HV mode on thread */
+	ram_mode |= PPC_BIT(2) >> thread_id*2;
+	xscom_write(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_RAM_MODE_REG), ram_mode);
+
+	/* Setup SPRC to use SPRD */
+	val = SPR_MODE_SPRC_WR_EN;
+	val = SETFIELD(SPR_MODE_SPRC_SEL, val, 1 << (3 - lpar));
+	val = SETFIELD(SPR_MODE_SPRC_T_SEL, val, 1 << (7 - thread_id));
+	xscom_write(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_SPR_MODE_REG), val);
+	xscom_write(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_SPRC_REG), SPRC_REG_SCRATCH_SPR);
+
+	for (i = -1; i <= len; i++) {
+		if (i < 0)
+			/* Save r0 (assumes opcodes don't touch other registers) */
+			opcode = MTSPR(277, 0);
+		else if (i < len) {
+			xscom_write(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_SCR0_REG), results[i]);
+			opcode = opcodes[i];
+		} else if (i >= len) {
+			/* Restore r0 */
+			xscom_write(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_SCR0_REG), r0);
+			opcode = MFSPR(0, 277);
+		}
+
+		/* ram instruction */
+		val = SETFIELD(RAM_CTRL_THREAD_SELECT, 0ULL, thread_id);
+		val = SETFIELD(RAM_CTRL_INSTR, val, opcode);
+		xscom_write(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_RAM_CTRL_REG), val);
+
+		/* wait for completion */
+		do {
+			xscom_read(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_RAM_STATUS_REG), &val);
+		} while (!val);
+
+		if (!(val & RAM_STATUS)) {
+			prlog(PR_ERR, "Instruction ramming failed with status 0x%llx\n", val);
+			rc = OPAL_HARDWARE;
+		}
+
+		/* Save the results */
+		xscom_read(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_SCR0_REG), &val);
+		if (i < 0)
+			r0 = val;
+		else if (i < len)
+			results[i] = val;
+	}
+
+	/* Disable RAM mode */
+	ram_mode &= ~RAM_MODE_ENABLE;
+
+	/* Disable HV mode on thread */
+	ram_mode &= ~(PPC_BIT(2) >> thread_id*2);
+	xscom_write(chip_id, XSCOM_ADDR_P8_EX(core_id, P8_EX_RAM_MODE_REG), ram_mode);
+
+	return rc;
+}
+
+#define SRESET_SRR1 0x9000000000101001
+static int emulate_sreset(struct cpu_thread *cpu, int thread_id)
+{
+	int rc = OPAL_SUCCESS;
+	uint64_t opcodes[] = {
+		MFMSR(0), MTSPR(277, 0),		/* Get MSR */
+		MFNIA(0), MTSPR(277, 0),		/* Get NIA */
+		MFSPR(0, 277), MTMSR(0),		/* Put modified MSR back */
+		MFSPR(0, 277), MTSPR(SPR_SRR0, 0),	/* Set SRR0 to NIA */
+		MFSPR(0, 277), MTSPR(SPR_SRR1, 0),	/* Set SRR1 to SRESET value */
+		MFSPR(0, 277), MTNIA(0),		/* Set NIA */
+	};
+	uint64_t results[] = {
+		0, 0,
+		0, 0,
+		0, 0,
+		0, 0,					/* SRR0 */
+		SRESET_SRR1, 0,		      		/* SRR1 SRESET value */
+		0x100, 0,				/* Set NIA = 0x100 */
+	};
+
+	BUILD_ASSERT(ARRAY_SIZE(opcodes) == ARRAY_SIZE(results));
+
+	set_ram_thread_active(cpu, thread_id, true);
+
+	prlog(PR_ERR, "SRESET: About to ram cpu 0x%x thread 0x%x\n",
+	      cpu->pir, thread_id);
+
+	/* Ram the first 4 instructions to get MSR and NIA */
+	rc = ram_instructions(cpu, thread_id, opcodes, results, 4, 0);
+
+	/* Set MSR, SRR0 = NIA and ram remaining instructions*/
+	results[4] = (results[1] & ~(MSR_IR | MSR_DR | MSR_FE0 | MSR_FE1 | MSR_EE | MSR_RI)) | MSR_HV;
+	results[6] = results[3];
+	rc |= ram_instructions(cpu, thread_id, &opcodes[4], &results[4], ARRAY_SIZE(opcodes) - 4, 0);
+
+	set_ram_thread_active(cpu, thread_id, false);
+
+	return rc;
+}
+
+/*
+ * Apply an sreset to the given threads in a core. When ramming
+ * instructions the whole core must be quiesced so we can't apply an
+ * sreset to active threads on the same core as we're running
+ */
+static int64_t sreset_core(struct cpu_thread *cpu, unsigned int thread_mask)
+{
+	uint32_t thread_id;
+	uint32_t sreset_mask = 0, ram_mask = 0;
+	uint64_t ras_status;
+	int timeout;
+	int64_t rc = 0;
+	unsigned int max_thread_id = pir_to_thread_id(-1U);
+	unsigned int max_thread_mask = (1 << (max_thread_id + 1)) - 1;
+
+	assert(cpu == cpu->primary);
+	thread_mask &= max_thread_mask;
+	if (this_cpu() == cpu) {
+		prlog(PR_WARNING, "SRESET: Unable to reset threads on self\n");
+		return OPAL_PARAMETER;
+	}
+
+	prlog(PR_INFO, "SRESET: Start reset for cpu 0x%x thread_mask 0x%x\n",
+	      cpu->pir, thread_mask);
+
+	/* cpu is the primary thread */
+	if (set_special_wakeup(cpu) != OPAL_SUCCESS)
+		return OPAL_BUSY;
+
+	/* Stop threads selected for sreset */
+	for (thread_id = 0; thread_id <= max_thread_id; thread_id++) {
+		if (!((1 << thread_id) & thread_mask))
+			continue;
+
+		set_direct_ctl(cpu, thread_id, P8_DIRECT_CTL_STOP);
+		ras_status = get_ras_status(cpu, thread_id);
+		if (!(ras_status & RAS_STATUS_THREAD_ACTIVE))
+			sreset_mask |= 1 << thread_id;
+	}
+
+	if (thread_mask == sreset_mask)
+		/* All threads selected for sreset can be sreset directly */
+		prlog(PR_INFO, "SRESET: All threads inactive, doing direct sreset\n");
+		for (thread_id = 0; thread_id <= max_thread_id; thread_id++)
+			if ((1 << thread_id) & thread_mask) {
+				set_direct_ctl(cpu, thread_id, P8_DIRECT_CTL_SRESET);
+				rc = OPAL_SUCCESS;
+				goto out;
+			}
+
+	/* Need to emulate sreset so stop all other threads */
+	for (thread_id = 0; thread_id <= max_thread_id; thread_id++)
+		if (!((1 << thread_id) & thread_mask))
+			set_direct_ctl(cpu, thread_id, P8_DIRECT_CTL_STOP);
+
+	/* Work out which threads to sreset and which need sreset emulation */
+	for (thread_id = 0; thread_id <= max_thread_id; thread_id++) {
+		ras_status = get_ras_status(cpu, thread_id);
+		if (!(ras_status & RAS_STATUS_THREAD_ACTIVE))
+			sreset_mask |= 1 << thread_id;
+		else {
+			for (timeout = 0; timeout < RAS_STATUS_TIMEOUT; timeout++) {
+				ras_status = get_ras_status(cpu, thread_id);
+				if ((ras_status & RAS_STATUS_SRQ_EMPTY)
+				    && (ras_status & RAS_STATUS_LSU_QUIESCED)
+				    && (ras_status & RAS_STATUS_TS_QUIESCE)) {
+					ram_mask |= 1 << thread_id;
+					break;
+				}
+			}
+		}
+	}
+
+	/*
+	 * To emulate sreset we need to make sure all threads on a core are either:
+	 *  a) Quiesced
+	 *  b) Not active (recorded in sreset_mask)
+	 * So skip ramming threads if we're not in the right state.
+	 */
+	if ((ram_mask & thread_mask) && (ram_mask | sreset_mask) != max_thread_mask) {
+		prlog(PR_ERR, "SRESET: Unable to quiesce all threads for ramming (sreset_mask 0x%08x / ram_mask 0x%08x)\n",
+		      sreset_mask, ram_mask);
+		ram_mask = 0;
+		rc = OPAL_PARTIAL;
+	}
+
+	/* We need to ram threads before doing the direct sresets as
+	 * that makes the threads active */
+	for (thread_id = 0; thread_id <= max_thread_id; thread_id++) {
+		if (!((1 << thread_id) & thread_mask))
+			continue;
+
+		if ((1 << thread_id) & ram_mask)
+			emulate_sreset(cpu, thread_id);
+	}
+
+	for (thread_id = 0; thread_id <= max_thread_id; thread_id++) {
+		if (!((1 << thread_id) & thread_mask))
+			continue;
+
+		if ((1 << thread_id) & sreset_mask) {
+			prlog(PR_ERR, "SRESET: cpu 0x%x thread 0x%x not active, applying SRESET directly\n",
+			      cpu->pir, thread_id);
+			set_direct_ctl(cpu, thread_id, P8_DIRECT_CTL_SRESET);
+		}
+	}
+
+out:
+	/* Start all threads */
+	for (thread_id = 0; thread_id <= max_thread_id; thread_id++)
+		set_direct_ctl(cpu, thread_id, P8_DIRECT_CTL_START);
+
+	clr_special_wakeup(cpu);
+
+	return rc;
+}
+
+#define SYS_RESET_ALLBUTSELF -2
+int64_t signal_system_reset(int cpu_nr)
+{
+	int64_t rc = 0;
+	struct cpu_thread *cpu;
+	uint32_t thread_id;
+
+	if (proc_gen != proc_gen_p8)
+		return OPAL_UNSUPPORTED;
+
+	/* Reset a single CPU */
+	if (cpu_nr >= 0) {
+		cpu = find_cpu_by_server(cpu_nr);
+		if (!cpu)
+			return OPAL_PARAMETER;
+
+		thread_id = pir_to_thread_id(cpu->pir);
+		cpu = cpu->primary;
+		return sreset_core(cpu, 1 << thread_id);
+	}
+
+	/* Otherwise reset all CPUs */
+	for_each_cpu(cpu) {
+		if (cpu->primary == this_cpu()->primary)
+			continue;
+
+		if (cpu->primary != cpu)
+			continue;
+
+		/* sreset all threads on a core */
+		rc |= sreset_core(cpu, -1);
+	}
+
+	return rc;
+}
diff --git a/doc/opal-api/opal-signal-system-reset-128.txt b/doc/opal-api/opal-signal-system-reset-128.txt
new file mode 100644
index 0000000..bb1b869
--- /dev/null
+++ b/doc/opal-api/opal-signal-system-reset-128.txt
@@ -0,0 +1,30 @@
+OPAL_SIGNAL_SYSTEM_RESET
+-------------------
+
+#define OPAL_SIGNAL_SYSTEM_RESET			128
+
+int64_t signal_system_reset(int cpu_nr)
+
+Arguments:
+
+  int cpu_nr
+    Either the cpu server number of the target cpu to reset or
+    SYS_RESET_ALLBUTSELF (-2) to indicate all but the current cpu
+    should be reset.
+
+This OPAL call causes the specified cpu(s) to be reset to the system
+reset exception handler (0x100). Sleeping cpus will be woken with
+SRR1[42:45] = 0x0100 indicating an interrupt caused by SCOM when in
+power saving mode. Active cpus will also indicate interrupt caused by
+SCOM but will have SRR1[46:47] = 0 as the interrupt did not occur
+during a power saving mode.
+
+Resetting active threads on the same core as this call is run is
+currently not supported.
+
+Return Values:
+OPAL_SUCCESS: the power down was updated successful
+OPAL_PARAMETER: a parameter was incorrect
+OPAL_HARDWARE: hardware indicated failure during instruction ramming
+OPAL_PARTIAL: not all requested cpus could be reset at this time
+OPAL_UNSUPPORTED: this processor generation is not supported
diff --git a/include/opal-api.h b/include/opal-api.h
index 05ff51d..be9a534 100644
--- a/include/opal-api.h
+++ b/include/opal-api.h
@@ -181,7 +181,8 @@
 #define OPAL_INT_SET_MFRR			125
 #define OPAL_PCI_TCE_KILL			126
 #define OPAL_NMMU_SET_PTCR			127
-#define OPAL_LAST				127
+#define OPAL_SIGNAL_SYSTEM_RESET		128
+#define OPAL_LAST				128

 /* Device tree flags */

diff --git a/include/skiboot.h b/include/skiboot.h
index 2ef7677..b796a2c 100644
--- a/include/skiboot.h
+++ b/include/skiboot.h
@@ -191,6 +191,7 @@ extern unsigned long get_symbol(unsigned long addr,

 /* Fast reboot support */
 extern void fast_reboot(void);
+extern int64_t signal_system_reset(int __unused targets);
 extern void __noreturn __secondary_cpu_entry(void);
 extern void __noreturn load_and_boot_kernel(bool is_reboot);
 extern void cleanup_tlb(void);
diff --git a/platforms/astbmc/common.c b/platforms/astbmc/common.c
index e1a8a4d..e4761ee 100644
--- a/platforms/astbmc/common.c
+++ b/platforms/astbmc/common.c
@@ -356,4 +356,6 @@ void astbmc_early_init(void)
 	uart_init();

 	prd_init();
+
+	opal_register(OPAL_SIGNAL_SYSTEM_RESET, signal_system_reset, 1);
 }
--
2.1.4


More information about the Skiboot mailing list