aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/core/direct-controls.c
diff options
context:
space:
mode:
authorAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
committerAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
commitaf1a266670d040d2f4083ff309d732d648afba2a (patch)
tree2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/core/direct-controls.c
parente02cda008591317b1625707ff8e115a4841aa889 (diff)
Add submodule dependency filesHEADmaster
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/core/direct-controls.c')
-rw-r--r--roms/skiboot/core/direct-controls.c1161
1 files changed, 1161 insertions, 0 deletions
diff --git a/roms/skiboot/core/direct-controls.c b/roms/skiboot/core/direct-controls.c
new file mode 100644
index 000000000..37bcf9826
--- /dev/null
+++ b/roms/skiboot/core/direct-controls.c
@@ -0,0 +1,1161 @@
+// SPDX-License-Identifier: Apache-2.0
+/*
+ * Directly control CPU cores/threads. SRESET, special wakeup, etc
+ *
+ * Copyright 2017-2019 IBM Corp.
+ */
+
+#include <direct-controls.h>
+#include <skiboot.h>
+#include <opal.h>
+#include <cpu.h>
+#include <xscom.h>
+#include <xscom-p8-regs.h>
+#include <xscom-p9-regs.h>
+#include <xscom-p10-regs.h>
+#include <timebase.h>
+#include <chip.h>
+
+
+/**************** mambo direct controls ****************/
+
+extern unsigned long callthru_tcl(const char *str, int len);
+
+static void mambo_sreset_cpu(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ char tcl_cmd[50];
+
+ snprintf(tcl_cmd, sizeof(tcl_cmd),
+ "mysim cpu %i:%i:%i start_thread 0x100",
+ chip_id, core_id, thread_id);
+ callthru_tcl(tcl_cmd, strlen(tcl_cmd));
+}
+
+static void mambo_stop_cpu(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ char tcl_cmd[50];
+
+ snprintf(tcl_cmd, sizeof(tcl_cmd),
+ "mysim cpu %i:%i:%i stop_thread",
+ chip_id, core_id, thread_id);
+ callthru_tcl(tcl_cmd, strlen(tcl_cmd));
+}
+
+/**************** POWER8 direct controls ****************/
+
+static int p8_core_set_special_wakeup(struct cpu_thread *cpu)
+{
+ uint64_t val, poll_target, stamp;
+ uint32_t core_id;
+ int rc;
+
+ /*
+ * Note: HWP checks for checkstops, but I assume we don't need to
+ * as we wouldn't be running if one was present
+ */
+
+ /* Grab core ID once */
+ core_id = pir_to_core_id(cpu->pir);
+
+ prlog(PR_DEBUG, "RESET Waking up core 0x%x\n", core_id);
+
+ /*
+ * The original HWp reads the XSCOM first but ignores the result
+ * and error, let's do the same until I know for sure that is
+ * not necessary
+ */
+ xscom_read(cpu->chip_id,
+ XSCOM_ADDR_P8_EX_SLAVE(core_id, EX_PM_SPECIAL_WAKEUP_PHYP),
+ &val);
+
+ /* Then we write special wakeup */
+ rc = xscom_write(cpu->chip_id,
+ XSCOM_ADDR_P8_EX_SLAVE(core_id,
+ EX_PM_SPECIAL_WAKEUP_PHYP),
+ PPC_BIT(0));
+ if (rc) {
+ prerror("RESET: XSCOM error %d asserting special"
+ " wakeup on 0x%x\n", rc, cpu->pir);
+ return rc;
+ }
+
+ /*
+ * HWP uses the history for Perf register here, dunno why it uses
+ * that one instead of the pHyp one, maybe to avoid clobbering it...
+ *
+ * In any case, it does that to check for run/nap vs.sleep/winkle/other
+ * to decide whether to poll on checkstop or not. Since we don't deal
+ * with checkstop conditions here, we ignore that part.
+ */
+
+ /*
+ * Now poll for completion of special wakeup. The HWP is nasty here,
+ * it will poll at 5ms intervals for up to 200ms. This is not quite
+ * acceptable for us at runtime, at least not until we have the
+ * ability to "context switch" HBRT. In practice, because we don't
+ * winkle, it will never take that long, so we increase the polling
+ * frequency to 1us per poll. However we do have to keep the same
+ * timeout.
+ *
+ * We don't use time_wait_ms() either for now as we don't want to
+ * poll the FSP here.
+ */
+ stamp = mftb();
+ poll_target = stamp + msecs_to_tb(200);
+ val = 0;
+ while (!(val & EX_PM_GP0_SPECIAL_WAKEUP_DONE)) {
+ /* Wait 1 us */
+ time_wait_us(1);
+
+ /* Read PM state */
+ rc = xscom_read(cpu->chip_id,
+ XSCOM_ADDR_P8_EX_SLAVE(core_id, EX_PM_GP0),
+ &val);
+ if (rc) {
+ prerror("RESET: XSCOM error %d reading PM state on"
+ " 0x%x\n", rc, cpu->pir);
+ return rc;
+ }
+ /* Check timeout */
+ if (mftb() > poll_target)
+ break;
+ }
+
+ /* Success ? */
+ if (val & EX_PM_GP0_SPECIAL_WAKEUP_DONE) {
+ uint64_t now = mftb();
+ prlog(PR_TRACE, "RESET: Special wakeup complete after %ld us\n",
+ tb_to_usecs(now - stamp));
+ return 0;
+ }
+
+ /*
+ * We timed out ...
+ *
+ * HWP has a complex workaround for HW255321 which affects
+ * Murano DD1 and Venice DD1. Ignore that for now
+ *
+ * Instead we just dump some XSCOMs for error logging
+ */
+ prerror("RESET: Timeout on special wakeup of 0x%0x\n", cpu->pir);
+ prerror("RESET: PM0 = 0x%016llx\n", val);
+ val = -1;
+ xscom_read(cpu->chip_id,
+ XSCOM_ADDR_P8_EX_SLAVE(core_id, EX_PM_SPECIAL_WAKEUP_PHYP),
+ &val);
+ prerror("RESET: SPC_WKUP = 0x%016llx\n", val);
+ val = -1;
+ xscom_read(cpu->chip_id,
+ XSCOM_ADDR_P8_EX_SLAVE(core_id,
+ EX_PM_IDLE_STATE_HISTORY_PHYP),
+ &val);
+ prerror("RESET: HISTORY = 0x%016llx\n", val);
+
+ return OPAL_HARDWARE;
+}
+
+static int p8_core_clear_special_wakeup(struct cpu_thread *cpu)
+{
+ uint64_t val;
+ uint32_t core_id;
+ int rc;
+
+ /*
+ * Note: HWP checks for checkstops, but I assume we don't need to
+ * as we wouldn't be running if one was present
+ */
+
+ /* Grab core ID once */
+ core_id = pir_to_core_id(cpu->pir);
+
+ prlog(PR_DEBUG, "RESET: Releasing core 0x%x wakeup\n", core_id);
+
+ /*
+ * The original HWp reads the XSCOM first but ignores the result
+ * and error, let's do the same until I know for sure that is
+ * not necessary
+ */
+ xscom_read(cpu->chip_id,
+ XSCOM_ADDR_P8_EX_SLAVE(core_id, EX_PM_SPECIAL_WAKEUP_PHYP),
+ &val);
+
+ /* Then we write special wakeup */
+ rc = xscom_write(cpu->chip_id,
+ XSCOM_ADDR_P8_EX_SLAVE(core_id,
+ EX_PM_SPECIAL_WAKEUP_PHYP), 0);
+ if (rc) {
+ prerror("RESET: XSCOM error %d deasserting"
+ " special wakeup on 0x%x\n", rc, cpu->pir);
+ return rc;
+ }
+
+ /*
+ * The original HWp reads the XSCOM again with the comment
+ * "This puts an inherent delay in the propagation of the reset
+ * transition"
+ */
+ xscom_read(cpu->chip_id,
+ XSCOM_ADDR_P8_EX_SLAVE(core_id, EX_PM_SPECIAL_WAKEUP_PHYP),
+ &val);
+
+ return 0;
+}
+
+static int p8_stop_thread(struct cpu_thread *cpu)
+{
+ 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;
+
+ xscom_addr = XSCOM_ADDR_P8_EX(core_id,
+ P8_EX_TCTL_DIRECT_CONTROLS(thread_id));
+
+ if (xscom_write(chip_id, xscom_addr, P8_DIRECT_CTL_STOP)) {
+ prlog(PR_ERR, "Could not stop thread %u:%u:%u:"
+ " Unable to write EX_TCTL_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+
+ return OPAL_SUCCESS;
+}
+
+static int p8_sreset_thread(struct cpu_thread *cpu)
+{
+ 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;
+
+ xscom_addr = XSCOM_ADDR_P8_EX(core_id,
+ P8_EX_TCTL_DIRECT_CONTROLS(thread_id));
+
+ if (xscom_write(chip_id, xscom_addr, P8_DIRECT_CTL_PRENAP)) {
+ prlog(PR_ERR, "Could not prenap thread %u:%u:%u:"
+ " Unable to write EX_TCTL_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+ if (xscom_write(chip_id, xscom_addr, P8_DIRECT_CTL_SRESET)) {
+ prlog(PR_ERR, "Could not sreset thread %u:%u:%u:"
+ " Unable to write EX_TCTL_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+
+ return OPAL_SUCCESS;
+}
+
+
+/**************** POWER9 direct controls ****************/
+
+/* Long running instructions may take time to complete. Timeout 100ms */
+#define P9_QUIESCE_POLL_INTERVAL 100
+#define P9_QUIESCE_TIMEOUT 100000
+
+/* Waking may take up to 5ms for deepest sleep states. Set timeout to 100ms */
+#define P9_SPWKUP_POLL_INTERVAL 100
+#define P9_SPWKUP_TIMEOUT 100000
+
+/*
+ * This implements direct control facilities of processor cores and threads
+ * using scom registers.
+ */
+
+static int p9_core_is_gated(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t sshhyp_addr;
+ uint64_t val;
+
+ sshhyp_addr = XSCOM_ADDR_P9_EC_SLAVE(core_id, P9_EC_PPM_SSHHYP);
+
+ if (xscom_read(chip_id, sshhyp_addr, &val)) {
+ prlog(PR_ERR, "Could not query core gated on %u:%u:"
+ " Unable to read PPM_SSHHYP.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+
+ return !!(val & P9_CORE_GATED);
+}
+
+static int p9_core_set_special_wakeup(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t swake_addr;
+ uint32_t sshhyp_addr;
+ uint64_t val;
+ int i;
+
+ swake_addr = XSCOM_ADDR_P9_EC_SLAVE(core_id, EC_PPM_SPECIAL_WKUP_HYP);
+ sshhyp_addr = XSCOM_ADDR_P9_EC_SLAVE(core_id, P9_EC_PPM_SSHHYP);
+
+ if (xscom_write(chip_id, swake_addr, P9_SPWKUP_SET)) {
+ prlog(PR_ERR, "Could not set special wakeup on %u:%u:"
+ " Unable to write PPM_SPECIAL_WKUP_HYP.\n",
+ chip_id, core_id);
+ goto out_fail;
+ }
+
+ for (i = 0; i < P9_SPWKUP_TIMEOUT / P9_SPWKUP_POLL_INTERVAL; i++) {
+ if (xscom_read(chip_id, sshhyp_addr, &val)) {
+ prlog(PR_ERR, "Could not set special wakeup on %u:%u:"
+ " Unable to read PPM_SSHHYP.\n",
+ chip_id, core_id);
+ goto out_fail;
+ }
+ if (val & P9_SPECIAL_WKUP_DONE) {
+ /*
+ * CORE_GATED will be unset on a successful special
+ * wakeup of the core which indicates that the core is
+ * out of stop state. If CORE_GATED is still set then
+ * raise error.
+ */
+ if (p9_core_is_gated(cpu)) {
+ /* Deassert spwu for this strange error */
+ xscom_write(chip_id, swake_addr, 0);
+ prlog(PR_ERR, "Failed special wakeup on %u:%u"
+ " as CORE_GATED is set\n",
+ chip_id, core_id);
+ goto out_fail;
+ } else {
+ return 0;
+ }
+ }
+ time_wait_us(P9_SPWKUP_POLL_INTERVAL);
+ }
+
+ prlog(PR_ERR, "Could not set special wakeup on %u:%u:"
+ " timeout waiting for SPECIAL_WKUP_DONE.\n",
+ chip_id, core_id);
+
+out_fail:
+ /*
+ * As per the special wakeup protocol we should not de-assert
+ * the special wakeup on the core until WAKEUP_DONE is set.
+ * So even on error do not de-assert.
+ */
+ return OPAL_HARDWARE;
+}
+
+static int p9_core_clear_special_wakeup(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t swake_addr;
+
+ swake_addr = XSCOM_ADDR_P9_EC_SLAVE(core_id, EC_PPM_SPECIAL_WKUP_HYP);
+
+ /*
+ * De-assert special wakeup after a small delay.
+ * The delay may help avoid problems setting and clearing special
+ * wakeup back-to-back. This should be confirmed.
+ */
+ time_wait_us(1);
+ if (xscom_write(chip_id, swake_addr, 0)) {
+ prlog(PR_ERR, "Could not clear special wakeup on %u:%u:"
+ " Unable to write PPM_SPECIAL_WKUP_HYP.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+
+ /*
+ * Don't wait for de-assert to complete as other components
+ * could have requested for special wkeup. Wait for 10ms to
+ * avoid back-to-back asserts
+ */
+ time_wait_us(10000);
+ return 0;
+}
+
+static int p9_thread_quiesced(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ uint32_t ras_addr;
+ uint64_t ras_status;
+
+ ras_addr = XSCOM_ADDR_P9_EC(core_id, P9_RAS_STATUS);
+ if (xscom_read(chip_id, ras_addr, &ras_status)) {
+ prlog(PR_ERR, "Could not check thread state on %u:%u:"
+ " Unable to read RAS_STATUS.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+
+ /*
+ * This returns true when the thread is quiesced and all
+ * instructions completed. For sreset this may not be necessary,
+ * but we may want to use instruction ramming or stepping
+ * direct controls where it is important.
+ */
+ if ((ras_status & P9_THREAD_QUIESCED(thread_id))
+ == P9_THREAD_QUIESCED(thread_id))
+ return 1;
+
+ return 0;
+}
+
+static int p9_cont_thread(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ uint32_t cts_addr;
+ uint32_t ti_addr;
+ uint32_t dctl_addr;
+ uint64_t core_thread_state;
+ uint64_t thread_info;
+ bool active, stop;
+ int rc;
+
+ rc = p9_thread_quiesced(cpu);
+ if (rc < 0)
+ return rc;
+ if (!rc) {
+ prlog(PR_ERR, "Could not cont thread %u:%u:%u:"
+ " Thread is not quiesced.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_BUSY;
+ }
+
+ cts_addr = XSCOM_ADDR_P9_EC(core_id, P9_CORE_THREAD_STATE);
+ ti_addr = XSCOM_ADDR_P9_EC(core_id, P9_THREAD_INFO);
+ dctl_addr = XSCOM_ADDR_P9_EC(core_id, P9_EC_DIRECT_CONTROLS);
+
+ if (xscom_read(chip_id, cts_addr, &core_thread_state)) {
+ prlog(PR_ERR, "Could not resume thread %u:%u:%u:"
+ " Unable to read CORE_THREAD_STATE.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+ if (core_thread_state & PPC_BIT(56 + thread_id))
+ stop = true;
+ else
+ stop = false;
+
+ if (xscom_read(chip_id, ti_addr, &thread_info)) {
+ prlog(PR_ERR, "Could not resume thread %u:%u:%u:"
+ " Unable to read THREAD_INFO.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+ if (thread_info & PPC_BIT(thread_id))
+ active = true;
+ else
+ active = false;
+
+ if (!active || stop) {
+ if (xscom_write(chip_id, dctl_addr, P9_THREAD_CLEAR_MAINT(thread_id))) {
+ prlog(PR_ERR, "Could not resume thread %u:%u:%u:"
+ " Unable to write EC_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ }
+ } else {
+ if (xscom_write(chip_id, dctl_addr, P9_THREAD_CONT(thread_id))) {
+ prlog(PR_ERR, "Could not resume thread %u:%u:%u:"
+ " Unable to write EC_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ }
+ }
+
+ return 0;
+}
+
+static int p9_stop_thread(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ uint32_t dctl_addr;
+ int rc;
+ int i;
+
+ dctl_addr = XSCOM_ADDR_P9_EC(core_id, P9_EC_DIRECT_CONTROLS);
+
+ rc = p9_thread_quiesced(cpu);
+ if (rc < 0)
+ return rc;
+ if (rc) {
+ prlog(PR_ERR, "Could not stop thread %u:%u:%u:"
+ " Thread is quiesced already.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_BUSY;
+ }
+
+ if (xscom_write(chip_id, dctl_addr, P9_THREAD_STOP(thread_id))) {
+ prlog(PR_ERR, "Could not stop thread %u:%u:%u:"
+ " Unable to write EC_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+
+ for (i = 0; i < P9_QUIESCE_TIMEOUT / P9_QUIESCE_POLL_INTERVAL; i++) {
+ int rc = p9_thread_quiesced(cpu);
+ if (rc < 0)
+ break;
+ if (rc)
+ return 0;
+
+ time_wait_us(P9_QUIESCE_POLL_INTERVAL);
+ }
+
+ prlog(PR_ERR, "Could not stop thread %u:%u:%u:"
+ " Unable to quiesce thread.\n",
+ chip_id, core_id, thread_id);
+
+ return OPAL_HARDWARE;
+}
+
+static int p9_sreset_thread(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ uint32_t dctl_addr;
+
+ dctl_addr = XSCOM_ADDR_P9_EC(core_id, P9_EC_DIRECT_CONTROLS);
+
+ if (xscom_write(chip_id, dctl_addr, P9_THREAD_SRESET(thread_id))) {
+ prlog(PR_ERR, "Could not sreset thread %u:%u:%u:"
+ " Unable to write EC_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+
+ return 0;
+}
+
+/**************** POWER10 direct controls ****************/
+
+/* Long running instructions may take time to complete. Timeout 100ms */
+#define P10_QUIESCE_POLL_INTERVAL 100
+#define P10_QUIESCE_TIMEOUT 100000
+
+/* Waking may take up to 5ms for deepest sleep states. Set timeout to 100ms */
+#define P10_SPWU_POLL_INTERVAL 100
+#define P10_SPWU_TIMEOUT 100000
+
+/*
+ * This implements direct control facilities of processor cores and threads
+ * using scom registers.
+ */
+static int p10_core_is_gated(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t ssh_addr;
+ uint64_t val;
+
+ ssh_addr = XSCOM_ADDR_P10_QME_CORE(core_id, P10_QME_SSH_HYP);
+
+ if (xscom_read(chip_id, ssh_addr, &val)) {
+ prlog(PR_ERR, "Could not query core gated on %u:%u:"
+ " Unable to read QME_SSH_HYP.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+
+ return !!(val & P10_SSH_CORE_GATED);
+}
+
+
+static int p10_core_set_special_wakeup(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t spwu_addr, ssh_addr;
+ uint64_t val;
+ int i;
+
+ /* P10 could use SPWU_HYP done bit instead of SSH? */
+ spwu_addr = XSCOM_ADDR_P10_QME_CORE(core_id, P10_QME_SPWU_HYP);
+ ssh_addr = XSCOM_ADDR_P10_QME_CORE(core_id, P10_QME_SSH_HYP);
+
+ if (xscom_write(chip_id, spwu_addr, P10_SPWU_REQ)) {
+ prlog(PR_ERR, "Could not set special wakeup on %u:%u:"
+ " Unable to write QME_SPWU_HYP.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+
+ for (i = 0; i < P10_SPWU_TIMEOUT / P10_SPWU_POLL_INTERVAL; i++) {
+ if (xscom_read(chip_id, ssh_addr, &val)) {
+ prlog(PR_ERR, "Could not set special wakeup on %u:%u:"
+ " Unable to read QME_SSH_HYP.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+ if (val & P10_SSH_SPWU_DONE) {
+ /*
+ * CORE_GATED will be unset on a successful special
+ * wakeup of the core which indicates that the core is
+ * out of stop state. If CORE_GATED is still set then
+ * check SPWU register and raise error only if SPWU_DONE
+ * is not set, else print a warning and consider SPWU
+ * operation as successful.
+ * This is in conjunction with a micocode bug, which
+ * calls out the fact that SPW can succeed in the case
+ * the core is gated but SPWU_HYP bit is set.
+ */
+ if (p10_core_is_gated(cpu)) {
+ if(xscom_read(chip_id, spwu_addr, &val)) {
+ prlog(PR_ERR, "Core %u:%u:"
+ " unable to read QME_SPWU_HYP\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+ if (val & P10_SPWU_DONE) {
+ /*
+ * If SPWU DONE bit is set then
+ * SPWU operation is complete
+ */
+ prlog(PR_DEBUG, "Special wakeup on "
+ "%u:%u: core remains gated while"
+ " SPWU_HYP DONE set\n",
+ chip_id, core_id);
+ return 0;
+ }
+ /* Deassert spwu for this strange error */
+ xscom_write(chip_id, spwu_addr, 0);
+ prlog(PR_ERR,
+ "Failed special wakeup on %u:%u"
+ " core remains gated.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ } else {
+ return 0;
+ }
+ }
+ time_wait_us(P10_SPWU_POLL_INTERVAL);
+ }
+
+ prlog(PR_ERR, "Could not set special wakeup on %u:%u:"
+ " operation timeout.\n",
+ chip_id, core_id);
+ /*
+ * As per the special wakeup protocol we should not de-assert
+ * the special wakeup on the core until WAKEUP_DONE is set.
+ * So even on error do not de-assert.
+ */
+
+ return OPAL_HARDWARE;
+}
+
+static int p10_core_clear_special_wakeup(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t spwu_addr;
+
+ spwu_addr = XSCOM_ADDR_P10_QME_CORE(core_id, P10_QME_SPWU_HYP);
+
+ /* Add a small delay here if spwu problems time_wait_us(1); */
+ if (xscom_write(chip_id, spwu_addr, 0)) {
+ prlog(PR_ERR, "Could not clear special wakeup on %u:%u:"
+ " Unable to write QME_SPWU_HYP.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+
+ return 0;
+}
+
+static int p10_thread_quiesced(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ uint32_t ras_addr;
+ uint64_t ras_status;
+
+ ras_addr = XSCOM_ADDR_P10_EC(core_id, P10_EC_RAS_STATUS);
+ if (xscom_read(chip_id, ras_addr, &ras_status)) {
+ prlog(PR_ERR, "Could not check thread state on %u:%u:"
+ " Unable to read EC_RAS_STATUS.\n",
+ chip_id, core_id);
+ return OPAL_HARDWARE;
+ }
+
+ /*
+ * p10_thread_stop for the purpose of sreset wants QUIESCED
+ * and MAINT bits set. Step, RAM, etc. need more, but we don't
+ * use those in skiboot.
+ *
+ * P10 could try wait for more here in case of errors.
+ */
+ if (!(ras_status & P10_THREAD_QUIESCED(thread_id)))
+ return 0;
+
+ if (!(ras_status & P10_THREAD_MAINT(thread_id)))
+ return 0;
+
+ return 1;
+}
+
+static int p10_cont_thread(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ uint32_t cts_addr;
+ uint32_t ti_addr;
+ uint32_t dctl_addr;
+ uint64_t core_thread_state;
+ uint64_t thread_info;
+ bool active, stop;
+ int rc;
+ int i;
+
+ rc = p10_thread_quiesced(cpu);
+ if (rc < 0)
+ return rc;
+ if (!rc) {
+ prlog(PR_ERR, "Could not cont thread %u:%u:%u:"
+ " Thread is not quiesced.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_BUSY;
+ }
+
+ cts_addr = XSCOM_ADDR_P10_EC(core_id, P10_EC_CORE_THREAD_STATE);
+ ti_addr = XSCOM_ADDR_P10_EC(core_id, P10_EC_THREAD_INFO);
+ dctl_addr = XSCOM_ADDR_P10_EC(core_id, P10_EC_DIRECT_CONTROLS);
+
+ if (xscom_read(chip_id, cts_addr, &core_thread_state)) {
+ prlog(PR_ERR, "Could not resume thread %u:%u:%u:"
+ " Unable to read EC_CORE_THREAD_STATE.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+ if (core_thread_state & P10_THREAD_STOPPED(thread_id))
+ stop = true;
+ else
+ stop = false;
+
+ if (xscom_read(chip_id, ti_addr, &thread_info)) {
+ prlog(PR_ERR, "Could not resume thread %u:%u:%u:"
+ " Unable to read EC_THREAD_INFO.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+ if (thread_info & P10_THREAD_ACTIVE(thread_id))
+ active = true;
+ else
+ active = false;
+
+ if (!active || stop) {
+ if (xscom_write(chip_id, dctl_addr, P10_THREAD_CLEAR_MAINT(thread_id))) {
+ prlog(PR_ERR, "Could not resume thread %u:%u:%u:"
+ " Unable to write EC_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ }
+ } else {
+ if (xscom_write(chip_id, dctl_addr, P10_THREAD_START(thread_id))) {
+ prlog(PR_ERR, "Could not resume thread %u:%u:%u:"
+ " Unable to write EC_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ }
+ }
+
+ for (i = 0; i < P10_QUIESCE_TIMEOUT / P10_QUIESCE_POLL_INTERVAL; i++) {
+ int rc = p10_thread_quiesced(cpu);
+ if (rc < 0)
+ break;
+ if (!rc)
+ return 0;
+
+ time_wait_us(P10_QUIESCE_POLL_INTERVAL);
+ }
+
+ prlog(PR_ERR, "Could not start thread %u:%u:%u:"
+ " Unable to start thread.\n",
+ chip_id, core_id, thread_id);
+
+ return OPAL_HARDWARE;
+}
+
+static int p10_stop_thread(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ uint32_t dctl_addr;
+ int rc;
+ int i;
+
+ dctl_addr = XSCOM_ADDR_P10_EC(core_id, P10_EC_DIRECT_CONTROLS);
+
+ rc = p10_thread_quiesced(cpu);
+ if (rc < 0)
+ return rc;
+ if (rc) {
+ prlog(PR_ERR, "Could not stop thread %u:%u:%u:"
+ " Thread is quiesced already.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_BUSY;
+ }
+
+ if (xscom_write(chip_id, dctl_addr, P10_THREAD_STOP(thread_id))) {
+ prlog(PR_ERR, "Could not stop thread %u:%u:%u:"
+ " Unable to write EC_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+
+ for (i = 0; i < P10_QUIESCE_TIMEOUT / P10_QUIESCE_POLL_INTERVAL; i++) {
+ int rc = p10_thread_quiesced(cpu);
+ if (rc < 0)
+ break;
+ if (rc)
+ return 0;
+
+ time_wait_us(P10_QUIESCE_POLL_INTERVAL);
+ }
+
+ prlog(PR_ERR, "Could not stop thread %u:%u:%u:"
+ " Unable to quiesce thread.\n",
+ chip_id, core_id, thread_id);
+
+ return OPAL_HARDWARE;
+}
+
+static int p10_sreset_thread(struct cpu_thread *cpu)
+{
+ uint32_t chip_id = pir_to_chip_id(cpu->pir);
+ uint32_t core_id = pir_to_core_id(cpu->pir);
+ uint32_t thread_id = pir_to_thread_id(cpu->pir);
+ uint32_t dctl_addr;
+
+ dctl_addr = XSCOM_ADDR_P10_EC(core_id, P10_EC_DIRECT_CONTROLS);
+
+ if (xscom_write(chip_id, dctl_addr, P10_THREAD_SRESET(thread_id))) {
+ prlog(PR_ERR, "Could not sreset thread %u:%u:%u:"
+ " Unable to write EC_DIRECT_CONTROLS.\n",
+ chip_id, core_id, thread_id);
+ return OPAL_HARDWARE;
+ }
+
+ return 0;
+}
+
+/**************** generic direct controls ****************/
+
+int dctl_set_special_wakeup(struct cpu_thread *t)
+{
+ struct cpu_thread *c = t->ec_primary;
+ int rc = OPAL_SUCCESS;
+
+ if (proc_gen == proc_gen_unknown)
+ return OPAL_UNSUPPORTED;
+
+ lock(&c->dctl_lock);
+ if (c->special_wakeup_count == 0) {
+ if (proc_gen == proc_gen_p10)
+ rc = p10_core_set_special_wakeup(c);
+ else if (proc_gen == proc_gen_p9)
+ rc = p9_core_set_special_wakeup(c);
+ else /* (proc_gen == proc_gen_p8) */
+ rc = p8_core_set_special_wakeup(c);
+ }
+ if (!rc)
+ c->special_wakeup_count++;
+ unlock(&c->dctl_lock);
+
+ return rc;
+}
+
+int dctl_clear_special_wakeup(struct cpu_thread *t)
+{
+ struct cpu_thread *c = t->ec_primary;
+ int rc = OPAL_SUCCESS;
+
+ if (proc_gen == proc_gen_unknown)
+ return OPAL_UNSUPPORTED;
+
+ lock(&c->dctl_lock);
+ if (!c->special_wakeup_count)
+ goto out;
+ if (c->special_wakeup_count == 1) {
+ if (proc_gen == proc_gen_p10)
+ rc = p10_core_clear_special_wakeup(c);
+ else if (proc_gen == proc_gen_p9)
+ rc = p9_core_clear_special_wakeup(c);
+ else /* (proc_gen == proc_gen_p8) */
+ rc = p8_core_clear_special_wakeup(c);
+ }
+ if (!rc)
+ c->special_wakeup_count--;
+out:
+ unlock(&c->dctl_lock);
+
+ return rc;
+}
+
+int dctl_core_is_gated(struct cpu_thread *t)
+{
+ struct cpu_thread *c = t->primary;
+
+ if (proc_gen == proc_gen_p10)
+ return p10_core_is_gated(c);
+ else if (proc_gen == proc_gen_p9)
+ return p9_core_is_gated(c);
+ else
+ return OPAL_UNSUPPORTED;
+}
+
+static int dctl_stop(struct cpu_thread *t)
+{
+ struct cpu_thread *c = t->ec_primary;
+ int rc;
+
+ lock(&c->dctl_lock);
+ if (t->dctl_stopped) {
+ unlock(&c->dctl_lock);
+ return OPAL_BUSY;
+ }
+ if (proc_gen == proc_gen_p10)
+ rc = p10_stop_thread(t);
+ else if (proc_gen == proc_gen_p9)
+ rc = p9_stop_thread(t);
+ else /* (proc_gen == proc_gen_p8) */
+ rc = p8_stop_thread(t);
+ if (!rc)
+ t->dctl_stopped = true;
+ unlock(&c->dctl_lock);
+
+ return rc;
+}
+
+static int dctl_cont(struct cpu_thread *t)
+{
+ struct cpu_thread *c = t->primary;
+ int rc;
+
+ if (proc_gen != proc_gen_p10 && proc_gen != proc_gen_p9)
+ return OPAL_UNSUPPORTED;
+
+ lock(&c->dctl_lock);
+ if (!t->dctl_stopped) {
+ unlock(&c->dctl_lock);
+ return OPAL_BUSY;
+ }
+ if (proc_gen == proc_gen_p10)
+ rc = p10_cont_thread(t);
+ else /* (proc_gen == proc_gen_p9) */
+ rc = p9_cont_thread(t);
+ if (!rc)
+ t->dctl_stopped = false;
+ unlock(&c->dctl_lock);
+
+ return rc;
+}
+
+/*
+ * NOTE:
+ * The POWER8 sreset does not provide SRR registers, so it can be used
+ * for fast reboot, but not OPAL_SIGNAL_SYSTEM_RESET or anywhere that is
+ * expected to return. For now, callers beware.
+ */
+static int dctl_sreset(struct cpu_thread *t)
+{
+ struct cpu_thread *c = t->ec_primary;
+ int rc;
+
+ lock(&c->dctl_lock);
+ if (!t->dctl_stopped) {
+ unlock(&c->dctl_lock);
+ return OPAL_BUSY;
+ }
+ if (proc_gen == proc_gen_p10)
+ rc = p10_sreset_thread(t);
+ else if (proc_gen == proc_gen_p9)
+ rc = p9_sreset_thread(t);
+ else /* (proc_gen == proc_gen_p8) */
+ rc = p8_sreset_thread(t);
+ if (!rc)
+ t->dctl_stopped = false;
+ unlock(&c->dctl_lock);
+
+ return rc;
+}
+
+
+/**************** fast reboot API ****************/
+
+int sreset_all_prepare(void)
+{
+ struct cpu_thread *cpu;
+
+ if (proc_gen == proc_gen_unknown)
+ return OPAL_UNSUPPORTED;
+
+ prlog(PR_DEBUG, "RESET: Resetting from cpu: 0x%x (core 0x%x)\n",
+ this_cpu()->pir, pir_to_core_id(this_cpu()->pir));
+
+ if (chip_quirk(QUIRK_MAMBO_CALLOUTS)) {
+ for_each_ungarded_cpu(cpu) {
+ if (cpu == this_cpu())
+ continue;
+ mambo_stop_cpu(cpu);
+ }
+ return OPAL_SUCCESS;
+ }
+
+ /* Assert special wakup on all cores. Only on operational cores. */
+ for_each_ungarded_primary(cpu) {
+ if (dctl_set_special_wakeup(cpu) != OPAL_SUCCESS)
+ return OPAL_HARDWARE;
+ }
+
+ prlog(PR_DEBUG, "RESET: Stopping the world...\n");
+
+ /* Put everybody in stop except myself */
+ for_each_ungarded_cpu(cpu) {
+ if (cpu == this_cpu())
+ continue;
+ if (dctl_stop(cpu) != OPAL_SUCCESS)
+ return OPAL_HARDWARE;
+
+ }
+
+ return OPAL_SUCCESS;
+}
+
+void sreset_all_finish(void)
+{
+ struct cpu_thread *cpu;
+
+ if (chip_quirk(QUIRK_MAMBO_CALLOUTS))
+ return;
+
+ for_each_ungarded_primary(cpu)
+ dctl_clear_special_wakeup(cpu);
+}
+
+int sreset_all_others(void)
+{
+ struct cpu_thread *cpu;
+
+ prlog(PR_DEBUG, "RESET: Resetting all threads but self...\n");
+
+ /*
+ * mambo should actually implement stop as well, and implement
+ * the dctl_ helpers properly. Currently it's racy just sresetting.
+ */
+ if (chip_quirk(QUIRK_MAMBO_CALLOUTS)) {
+ for_each_ungarded_cpu(cpu) {
+ if (cpu == this_cpu())
+ continue;
+ mambo_sreset_cpu(cpu);
+ }
+ return OPAL_SUCCESS;
+ }
+
+ for_each_ungarded_cpu(cpu) {
+ if (cpu == this_cpu())
+ continue;
+ if (dctl_sreset(cpu) != OPAL_SUCCESS)
+ return OPAL_HARDWARE;
+ }
+
+ return OPAL_SUCCESS;
+}
+
+
+/**************** OPAL_SIGNAL_SYSTEM_RESET API ****************/
+
+/*
+ * This provides a way for the host to raise system reset exceptions
+ * on other threads using direct control scoms on POWER9.
+ *
+ * We assert special wakeup on the core first.
+ * Then stop target thread and wait for it to quiesce.
+ * Then sreset the target thread, which resumes execution on that thread.
+ * Then de-assert special wakeup on the core.
+ */
+static int64_t do_sreset_cpu(struct cpu_thread *cpu)
+{
+ int rc;
+
+ if (this_cpu() == cpu) {
+ prlog(PR_ERR, "SRESET: Unable to reset self\n");
+ return OPAL_PARAMETER;
+ }
+
+ rc = dctl_set_special_wakeup(cpu);
+ if (rc)
+ return rc;
+
+ rc = dctl_stop(cpu);
+ if (rc)
+ goto out_spwk;
+
+ rc = dctl_sreset(cpu);
+ if (rc)
+ goto out_cont;
+
+ dctl_clear_special_wakeup(cpu);
+
+ return 0;
+
+out_cont:
+ dctl_cont(cpu);
+out_spwk:
+ dctl_clear_special_wakeup(cpu);
+
+ return rc;
+}
+
+static struct lock sreset_lock = LOCK_UNLOCKED;
+
+int64_t opal_signal_system_reset(int cpu_nr)
+{
+ struct cpu_thread *cpu;
+ int64_t ret;
+
+ if (proc_gen != proc_gen_p9 && proc_gen != proc_gen_p10)
+ return OPAL_UNSUPPORTED;
+
+ /*
+ * Broadcasts unsupported. Not clear what threads should be
+ * signaled, so it's better for the OS to perform one-at-a-time
+ * for now.
+ */
+ if (cpu_nr < 0)
+ return OPAL_CONSTRAINED;
+
+ /* Reset a single CPU */
+ cpu = find_cpu_by_server(cpu_nr);
+ if (!cpu) {
+ prlog(PR_ERR, "SRESET: could not find cpu by server %d\n", cpu_nr);
+ return OPAL_PARAMETER;
+ }
+
+ lock(&sreset_lock);
+ ret = do_sreset_cpu(cpu);
+ unlock(&sreset_lock);
+
+ return ret;
+}
+
+void direct_controls_init(void)
+{
+ if (chip_quirk(QUIRK_MAMBO_CALLOUTS))
+ return;
+
+ if (proc_gen != proc_gen_p9 && proc_gen != proc_gen_p10)
+ return;
+
+ opal_register(OPAL_SIGNAL_SYSTEM_RESET, opal_signal_system_reset, 1);
+}