/* Copyright 2017 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <direct-controls.h>
#include <skiboot.h>
#include <opal.h>
#include <cpu.h>
#include <xscom.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 ****************/

#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)

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 ****************/

#define P9_RAS_STATUS			0x10a02
#define P9_THREAD_QUIESCED(t)		PPC_BITMASK(0 + 8*(t), 3 + 8*(t))
/* Long running instructions may take time to complete. Timeout 100ms */
#define P9_QUIESCE_POLL_INTERVAL	100
#define P9_QUIESCE_TIMEOUT		100000

#define P9_CORE_THREAD_STATE		0x10ab3
#define P9_THREAD_INFO			0x10a9b

#define P9_EC_DIRECT_CONTROLS		0x10a9c
#define P9_THREAD_STOP(t)		PPC_BIT(7 + 8*(t))
#define P9_THREAD_CONT(t)		PPC_BIT(6 + 8*(t))
#define P9_THREAD_SRESET(t)		PPC_BIT(4 + 8*(t))
#define P9_THREAD_CLEAR_MAINT(t)	PPC_BIT(3 + 8*(t))
#define P9_THREAD_PWR(t)		PPC_BIT(32 + 8*(t))

/* EC_PPM_SPECIAL_WKUP_HYP */
#define P9_SPWKUP_SET			PPC_BIT(0)

#define P9_EC_PPM_SSHHYP		0x0114
#define P9_CORE_GATED			PPC_BIT(0)
#define P9_SPECIAL_WKUP_DONE		PPC_BIT(1)

/* 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_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 (dctl_core_is_gated(cpu)) {
				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;
}

/**************** generic direct controls ****************/

int dctl_set_special_wakeup(struct cpu_thread *t)
{
	struct cpu_thread *c = t->primary;
	int rc = OPAL_SUCCESS;

	if (proc_gen != proc_gen_p9 && proc_gen != proc_gen_p8)
		return OPAL_UNSUPPORTED;

	lock(&c->dctl_lock);
	if (c->special_wakeup_count == 0) {
		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->primary;
	int rc = OPAL_SUCCESS;

	if (proc_gen != proc_gen_p9 && proc_gen != proc_gen_p8)
		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_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;
	uint32_t chip_id = pir_to_chip_id(c->pir);
	uint32_t core_id = pir_to_core_id(c->pir);
	uint32_t sshhyp_addr;
	uint64_t val;

	if (proc_gen != proc_gen_p9)
		return OPAL_UNSUPPORTED;

	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 dctl_stop(struct cpu_thread *t)
{
	struct cpu_thread *c = t->primary;
	int rc;

	if (proc_gen != proc_gen_p9 && proc_gen != proc_gen_p8)
		return OPAL_UNSUPPORTED;

	lock(&c->dctl_lock);
	if (t->dctl_stopped) {
		unlock(&c->dctl_lock);
		return OPAL_BUSY;
	}
	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_p9)
		return OPAL_UNSUPPORTED;

	lock(&c->dctl_lock);
	if (!t->dctl_stopped) {
		unlock(&c->dctl_lock);
		return OPAL_BUSY;
	}
	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->primary;
	int rc;

	if (proc_gen != proc_gen_p9 && proc_gen != proc_gen_p8)
		return OPAL_UNSUPPORTED;

	lock(&c->dctl_lock);
	if (!t->dctl_stopped) {
		unlock(&c->dctl_lock);
		return OPAL_BUSY;
	}
	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;

	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 p9_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)
		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 = p9_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)
		return;

	opal_register(OPAL_SIGNAL_SYSTEM_RESET, opal_signal_system_reset, 1);
}
