// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/* Copyright 2013-2019 IBM Corp. */

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <lock.h>
#include <device.h>
#include <compiler.h>
#include <hostservices.h>
#include <mem_region.h>
#include <xscom.h>
#include <fsp.h>
#include <chip.h>
#include <console.h>
#include <mem-map.h>
#include <timebase.h>
#include <occ.h>

#define HOSTBOOT_RUNTIME_INTERFACE_VERSION 1

struct host_interfaces {
	/** Interface version. */
	uint64_t interface_version;

	/** Put a string to the console. */
	void (*puts)(const char*);
	/** Critical failure in runtime execution. */
	void (*assert)(void);

	/** OPTIONAL. Hint to environment that the page may be executed. */
	int (*set_page_execute)(void*);

	/** malloc */
	void *(*malloc)(size_t);
	/** free */
	void (*free)(void*);
	/** realloc */
	void *(*realloc)(void*, size_t);

	/** sendErrorLog
	 * @param[in] plid Platform Log identifier
	 * @param[in] data size in bytes
	 * @param[in] pointer to data
	 * @return 0 on success else error code
	 */
	int (*send_error_log)(uint32_t,uint32_t,void *);

	/** Scan communication read
	 * @param[in] chip_id (based on devtree defn)
	 * @param[in] address
	 * @param[in] pointer to 8-byte data buffer
	 * @return 0 on success else return code
	 */
	int (*scom_read)(uint64_t, uint64_t, void*);

	/** Scan communication write
	 * @param[in] chip_id (based on devtree defn)
	 * @param[in] address
	 * @param[in] pointer to 8-byte data buffer
	 * @return 0 on success else return code
	 */
	int (*scom_write)(uint64_t, uint64_t, const void *);

	/** lid_load
	 *  Load a LID from PNOR, FSP, etc.
	 *
	 *  @param[in] LID number.
	 *  @param[out] Allocated buffer for LID.
	 *  @param[out] Size of LID (in bytes).
	 *
	 *  @return 0 on success, else RC.
	 */
	int (*lid_load)(uint32_t lid, void **buf, size_t *len);

	/** lid_unload
	 *  Release memory from previously loaded LID.
	 *
	 *  @param[in] Allocated buffer for LID to release.
	 *
	 *  @return 0 on success, else RC.
	 */
	int (*lid_unload)(void *buf);

	/** Get the address of a reserved memory region by its devtree name.
	 *
	 *  @param[in] Devtree name (ex. "ibm,hbrt-vpd-image")
	 *  @return physical address of region (or NULL).
	 **/
	uint64_t (*get_reserved_mem)(const char*);

	/**
	 * @brief  Force a core to be awake, or clear the force
	 * @param[in] i_core  Core to wake up (pid)
	 * @param[in] i_mode  0=force awake
	 *                1=clear force
	 *                2=clear all previous forces
	 * @return rc  non-zero on error
	 */
	int (*wakeup)( uint32_t i_core, uint32_t i_mode );

	/**
	 * @brief Delay/sleep for at least the time given
	 * @param[in] seconds
	 * @param[in] nano seconds
	 */
	void (*nanosleep)(uint64_t i_seconds, uint64_t i_nano_seconds);

	// Reserve some space for future growth.
	void (*reserved[32])(void);
};

struct runtime_interfaces {
	/** Interface version. */
	uint64_t interface_version;

	/** Execute CxxTests that may be contained in the image.
	 *
	 * @param[in] - Pointer to CxxTestStats structure for results reporting.
	 */
	void (*cxxtestExecute)(void *);
	/** Get a list of lids numbers of the lids known to HostBoot
	 *
	 * @param[out] o_num - the number of lids in the list
	 * @return a pointer to the list
	 */
	const uint32_t * (*get_lid_list)(size_t * o_num);

	/** Load OCC Image and common data into mainstore, also setup OCC BARSs
	 *
	 * @param[in] i_homer_addr_phys - The physical mainstore address of the
	 *                                start of the HOMER image
	 * @param[in] i_homer_addr_va - Virtual memory address of the HOMER image
	 * @param[in] i_common_addr_phys - The physical mainstore address of the
	 *                                 OCC common area.
	 * @param[in] i_common_addr_va - Virtual memory address of the common area
	 * @param[in] i_chip - The HW chip id (XSCOM chip ID)
	 * @return 0 on success else return code
	 */
	int(*loadOCC)(uint64_t i_homer_addr_phys,
			uint64_t i_homer_addr_va,
			uint64_t i_common_addr_phys,
			uint64_t i_common_addr_va,
			uint64_t i_chip);

	/** Start OCC on all chips, by module
	 *
	 *  @param[in] i_chip - Array of functional HW chip ids
	 *  @Note The caller must include a complete modules worth of chips
	 *  @param[in] i_num_chips - Number of chips in the array
	 *  @return 0 on success else return code
	 */
	int (*startOCCs)(uint64_t* i_chip,
			size_t i_num_chips);

	/** Stop OCC hold OCCs in reset
	 *
	 *  @param[in] i_chip - Array of functional HW chip ids
	 *  @Note The caller must include a complete modules worth of chips
	 *  @param[in] i_num_chips - Number of chips in the array
	 *  @return 0 on success else return code
	 */
	int (*stopOCCs)(uint64_t* i_chip,
			size_t i_num_chips);

	/* Reserve some space for future growth. */
	void (*reserved[32])(void);
};

/*
 * The host interfaces are called by hostboot, which considers r16 a regular
 * non-volatile register that it may have used at the time of the call.
 * Skiboot requires r16 to be set to the CPU pointer while it runs. So the
 * host interface handlers must save HBRT's r16, switch r16 to the CPU pointer,
 * do their thing, and then restore HBRT's r16 value before returning.
 */
static uint64_t host_interface_pre_fixup(void)
{
	uint64_t r16 = (uint64_t)__this_cpu;

	restore_cpu_ptr_r16();

	return r16;
}

static void host_interface_post_fixup(uint64_t r16)
{
	set_cpu_ptr_r16(r16);
}

static struct runtime_interfaces *hservice_runtime;

static char *hbrt_con_buf = (char *)HBRT_CON_START;
static size_t hbrt_con_pos;
static bool hbrt_con_wrapped;

#define HBRT_CON_IN_LEN		0
#define HBRT_CON_OUT_LEN	(HBRT_CON_LEN - HBRT_CON_IN_LEN)

static struct memcons hbrt_memcons __section(".data.memcons") = {
	.magic		= CPU_TO_BE64(MEMCONS_MAGIC),
	.obuf_phys	= CPU_TO_BE64(HBRT_CON_START),
	.ibuf_phys	= CPU_TO_BE64(HBRT_CON_START + HBRT_CON_OUT_LEN),
	.obuf_size	= CPU_TO_BE32(HBRT_CON_OUT_LEN),
	.ibuf_size	= CPU_TO_BE32(HBRT_CON_IN_LEN),
};

static void hservice_putc(char c)
{
	uint64_t r16 = host_interface_pre_fixup();
	uint32_t opos;

	hbrt_con_buf[hbrt_con_pos++] = c;
	if (hbrt_con_pos >= HBRT_CON_OUT_LEN) {
		hbrt_con_pos = 0;
		hbrt_con_wrapped = true;
	}

	/*
	 * We must always re-generate memcons.out_pos because
	 * under some circumstances, the console script will
	 * use a broken putmemproc that does RMW on the full
	 * 8 bytes containing out_pos and in_prod, thus corrupting
	 * out_pos
	 */
	opos = hbrt_con_pos;
	if (hbrt_con_wrapped)
		opos |= MEMCONS_OUT_POS_WRAP;
	lwsync();
	hbrt_memcons.out_pos = cpu_to_be32(opos);

	host_interface_post_fixup(r16);
}

static void hservice_puts(const char *str)
{
	uint64_t r16 = host_interface_pre_fixup();
	char c;

	while((c = *(str++)) != 0)
		hservice_putc(c);
	hservice_putc(10);

	host_interface_post_fixup(r16);
}

static void hservice_mark(void)
{
	hservice_puts("--------------------------------------------------"
		      "--------------------------------------------------\n");
}

static void hservice_assert(void)
{
	uint64_t r16 = host_interface_pre_fixup();

	/**
	 * @fwts-label HBRTassert
	 * @fwts-advice HBRT triggered assert: you need to debug HBRT
	 */
	prlog(PR_EMERG, "HBRT: Assertion from hostservices\n");
	abort();
	/* Should not be reached... */
	host_interface_post_fixup(r16);
}

static void *hservice_malloc(size_t size)
{
	uint64_t r16 = host_interface_pre_fixup();
	void *mem;

	mem = malloc(size);

	host_interface_post_fixup(r16);

	return mem;
}

static void hservice_free(void *ptr)
{
	uint64_t r16 = host_interface_pre_fixup();
	free(ptr);
	host_interface_post_fixup(r16);
}


static void *hservice_realloc(void *ptr, size_t size)
{
	uint64_t r16 = host_interface_pre_fixup();
	void *mem;

	mem = realloc(ptr, size);

	host_interface_post_fixup(r16);

	return mem;
}

struct hbrt_elog_ent {
	void *buf;
	unsigned int size;
	unsigned int plid;
	struct list_node link;
};
static LIST_HEAD(hbrt_elogs);
static struct lock hbrt_elog_lock = LOCK_UNLOCKED;
static bool hbrt_elog_sending;
static void hservice_start_elog_send(void);

static void hservice_elog_write_complete(struct fsp_msg *msg)
{
	struct hbrt_elog_ent *ent = msg->user_data;

	lock(&hbrt_elog_lock);
	prlog(PR_DEBUG, "HBRT: Completed send of PLID 0x%08x\n", ent->plid);
	hbrt_elog_sending = false;
	fsp_tce_unmap(PSI_DMA_HBRT_LOG_WRITE_BUF,
		      PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
	free(ent->buf);
	free(ent);
	fsp_freemsg(msg);
	hservice_start_elog_send();
	unlock(&hbrt_elog_lock);
}

static void hservice_start_elog_send(void)
{
	struct fsp_msg *msg;
	struct hbrt_elog_ent *ent;

 again:
	if (list_empty(&hbrt_elogs))
		return;
	ent = list_pop(&hbrt_elogs, struct hbrt_elog_ent, link);

	hbrt_elog_sending = true;

	prlog(PR_DEBUG, "HBRT: Starting send of PLID 0x%08x\n", ent->plid);

	fsp_tce_map(PSI_DMA_HBRT_LOG_WRITE_BUF, ent->buf,
		    PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);

	msg = fsp_mkmsg(FSP_CMD_WRITE_SP_DATA, 6, FSP_DATASET_HBRT_BLOB,
			0, 0, 0, PSI_DMA_HBRT_LOG_WRITE_BUF,
			ent->size);

	if (!msg) {
		prerror("HBRT: Failed to create error msg log to FSP\n");
		goto error;
	}
	msg->user_data = ent;
	if (!fsp_queue_msg(msg, hservice_elog_write_complete))
		return;
	prerror("FSP: Error queueing elog update\n");
 error:
	if (msg)
		fsp_freemsg(msg);
	fsp_tce_unmap(PSI_DMA_HBRT_LOG_WRITE_BUF,
		      PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
	free(ent->buf);
	free(ent);
	hbrt_elog_sending = false;
	goto again;
}

int hservice_send_error_log(uint32_t plid, uint32_t dsize, void *data)
{
	uint64_t r16 = host_interface_pre_fixup();
	struct hbrt_elog_ent *ent;
	void *abuf;

	prlog(PR_ERR, "HBRT: Error log generated with plid 0x%08x\n", plid);

	/* We only know how to send error logs to FSP */
	if (!fsp_present()) {
		prerror("HBRT: Warning, error log from HBRT discarded !\n");

		host_interface_post_fixup(r16);

		return OPAL_UNSUPPORTED;
	}
	if (dsize > PSI_DMA_HBRT_LOG_WRITE_BUF_SZ) {
		prerror("HBRT: Warning, error log from HBRT too big (%d) !\n",
			dsize);
		dsize = PSI_DMA_HBRT_LOG_WRITE_BUF_SZ;
	}

	lock(&hbrt_elog_lock);

	/* Create and populate a tracking structure */
	ent = zalloc(sizeof(struct hbrt_elog_ent));
	if (!ent) {
		unlock(&hbrt_elog_lock);

		host_interface_post_fixup(r16);

		return OPAL_NO_MEM;
	}

	/* Grab a 4k aligned page */
	abuf = memalign(0x1000, PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
	if (!abuf) {
		free(ent);
		unlock(&hbrt_elog_lock);

		host_interface_post_fixup(r16);

		return OPAL_NO_MEM;
	}
	memset(abuf, 0, PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
	memcpy(abuf, data, dsize);
	ent->buf = abuf;
	ent->size = dsize;
	ent->plid = plid;
	list_add_tail(&hbrt_elogs, &ent->link);
	if (!hbrt_elog_sending)
		hservice_start_elog_send();
	unlock(&hbrt_elog_lock);

	host_interface_post_fixup(r16);

	return 0;
}

static int hservice_scom_read(uint64_t chip_id, uint64_t addr, void *buf)
{
	uint64_t r16 = host_interface_pre_fixup();
	int ret;

	ret = xscom_read(chip_id, addr, buf);

	host_interface_post_fixup(r16);

	return ret;
}

static int hservice_scom_write(uint64_t chip_id, uint64_t addr,
			       const void *buf)
{
	uint64_t r16 = host_interface_pre_fixup();
	uint64_t val;
	int ret;

	memcpy(&val, buf, sizeof(val));
	ret = xscom_write(chip_id, addr, val);

	host_interface_post_fixup(r16);

	return ret;
}

struct hbrt_lid {
	void *load_addr;
	size_t len;
	uint32_t id;
	struct list_node link;
};
static LIST_HEAD(hbrt_lid_list);

static bool hbrt_lid_preload_complete = false;

bool hservices_lid_preload_complete(void)
{
	return hbrt_lid_preload_complete;
}

/* TODO: Few of the following routines can be generalized */
static int __hservice_lid_load(uint32_t lid, void **buf, size_t *len)
{
	int rc;

	/* Adjust LID side first or we get a cache mismatch */
	lid = fsp_adjust_lid_side(lid);

	/*
	 * Allocate a new buffer and load the LID into it
	 * XXX: We currently use the same size for each HBRT lid.
	 */
	*buf = malloc(HBRT_LOAD_LID_SIZE);
	*len = HBRT_LOAD_LID_SIZE;
	rc = fsp_preload_lid(lid, *buf, len);
	rc = fsp_wait_lid_loaded(lid);
	if (rc != 0)
		/* Take advantage of realloc corner case here. */
		*len = 0;
	*buf = realloc(*buf, *len);

	prlog(PR_DEBUG, "HBRT: LID 0x%08x successfully loaded, len=0x%lx\n",
			lid, (unsigned long)len);

	return rc;
}

static int __hservice_lid_preload(const uint32_t lid)
{
	struct hbrt_lid *hlid;
	void *buf;
	size_t len;
	int rc;

	hlid = zalloc(sizeof(struct hbrt_lid));
	if (!hlid) {
		prerror("HBRT: Could not allocate struct hbrt_lid\n");
		return OPAL_NO_MEM;
	}

	rc = __hservice_lid_load(lid, &buf, &len);
	if (rc) {
		free(hlid);
		return rc;
	}

	hlid->load_addr = buf;
	hlid->len = len;
	hlid->id = lid;
	list_add_tail(&hbrt_lid_list, &hlid->link);

	return 0;
}

/* Find and preload all lids needed by hostservices */
void hservices_lid_preload(void)
{
	const uint32_t *lid_list = NULL;
	size_t num_lids;
	int i;

	if (!hservice_runtime)
		return;

	lid_list = (const uint32_t *)hservice_runtime->get_lid_list(&num_lids);
	if (!lid_list) {
		prerror("HBRT: get_lid_list() returned NULL\n");
		return;
	}

	prlog(PR_INFO, "HBRT: %d lids to load\n", (int)num_lids);

	/* Currently HBRT needs only one (OCC) lid */
	for (i = 0; i < num_lids; i++)
		__hservice_lid_preload(lid_list[i]);

	hbrt_lid_preload_complete = true;
	occ_poke_load_queue();
}

static int hservice_lid_load(uint32_t lid, void **buf, size_t *len)
{
	uint64_t r16 = host_interface_pre_fixup();
	struct hbrt_lid *hlid;

	prlog(PR_INFO, "HBRT: Lid load request for 0x%08x\n", lid);

	if (list_empty(&hbrt_lid_list))	{ /* Should not happen */
		/**
		 * @fwts-label HBRTlidLoadFail
		 * @fwts-advice Firmware should have aborted boot
		 */
		prlog(PR_CRIT, "HBRT: LID Load failed\n");
		abort();
	}

	list_for_each(&hbrt_lid_list, hlid, link) {
		if (hlid->id == lid) {
			*buf = hlid->load_addr;
			*len = hlid->len;
			prlog(PR_DEBUG, "HBRT: LID Serviced from cache,"
			      " %x, len=0x%lx\n", hlid->id, hlid->len);

			host_interface_post_fixup(r16);

			return 0;
		}
	}

	host_interface_post_fixup(r16);

	return -ENOENT;
}

static int hservice_lid_unload(void *buf __unused)
{
	uint64_t r16 = host_interface_pre_fixup();

	host_interface_post_fixup(r16);

	/* We do nothing as the LID is held in cache */
	return 0;
}

static uint64_t hservice_get_reserved_mem(const char *name)
{
	struct mem_region *region;
	uint64_t ret;

	/* We assume it doesn't change after we've unlocked it, but
	 * lock ensures list is safe to walk. */
	lock(&mem_region_lock);
	region = find_mem_region(name);
	ret = region ? region->start : 0;
	unlock(&mem_region_lock);

	if (!ret)
		prlog(PR_WARNING, "HBRT: Mem region '%s' not found !\n", name);

	return ret;
}

static void hservice_nanosleep(uint64_t i_seconds, uint64_t i_nano_seconds)
{
	uint64_t r16 = host_interface_pre_fixup();
	struct timespec ts;

	ts.tv_sec = i_seconds;
	ts.tv_nsec = i_nano_seconds;
	nanosleep_nopoll(&ts, NULL);

	host_interface_post_fixup(r16);
}

int hservice_wakeup(uint32_t i_core, uint32_t i_mode)
{
	uint64_t r16 = host_interface_pre_fixup();
	struct cpu_thread *cpu;
	int rc = OPAL_SUCCESS;

	switch (proc_gen) {
	case proc_gen_p8:
		/*
		 * Mask out the top nibble of i_core since it may contain
		 * 0x4 (which we use for XSCOM targeting)
		 */
		i_core &= 0x0fffffff;
		i_core <<= 3;
		break;
	case proc_gen_p9:
		i_core &= SPR_PIR_P9_MASK;
		i_core <<= 2;
		break;
	case proc_gen_p10:
		i_core &= SPR_PIR_P10_MASK;
		i_core <<= 2;
		break;
	default:
		host_interface_post_fixup(r16);

		return OPAL_UNSUPPORTED;
	}

	/* What do we need to do ? */
	switch(i_mode) {
	case 0: /* Assert special wakeup */
		cpu = find_cpu_by_pir(i_core);
		if (!cpu) {
			host_interface_post_fixup(r16);

			return OPAL_PARAMETER;
		}
		prlog(PR_TRACE, "HBRT: Special wakeup assert for core 0x%x,"
		      " count=%d\n", i_core, cpu->hbrt_spec_wakeup);
		if (cpu->hbrt_spec_wakeup == 0)
			rc = dctl_set_special_wakeup(cpu);
		if (rc == 0)
			cpu->hbrt_spec_wakeup++;

		host_interface_post_fixup(r16);

		return rc;

	case 1: /* Deassert special wakeup */
		cpu = find_cpu_by_pir(i_core);
		if (!cpu) {
			host_interface_post_fixup(r16);

			return OPAL_PARAMETER;
		}
		prlog(PR_TRACE, "HBRT: Special wakeup release for core"
		      " 0x%x, count=%d\n", i_core, cpu->hbrt_spec_wakeup);
		if (cpu->hbrt_spec_wakeup == 0) {
			prerror("HBRT: Special wakeup clear"
				" on core 0x%x with count=0\n",
				i_core);

			host_interface_post_fixup(r16);

			return OPAL_WRONG_STATE;
		}
		/* What to do with count on errors ? */
		cpu->hbrt_spec_wakeup--;
		if (cpu->hbrt_spec_wakeup == 0)
			rc = dctl_clear_special_wakeup(cpu);

		host_interface_post_fixup(r16);

		return rc;

	case 2: /* Clear all special wakeups */
		prlog(PR_DEBUG, "HBRT: Special wakeup release for all cores\n");
		for_each_cpu(cpu) {
			if (cpu->hbrt_spec_wakeup) {
				cpu->hbrt_spec_wakeup = 0;
				/* What to do on errors ? */
				dctl_clear_special_wakeup(cpu);
			}
		}

		host_interface_post_fixup(r16);

		return OPAL_SUCCESS;

	default:

		host_interface_post_fixup(r16);

		return OPAL_PARAMETER;
	}
}

static struct host_interfaces hinterface = {
	.interface_version = HOSTBOOT_RUNTIME_INTERFACE_VERSION,
	.puts = hservice_puts,
	.assert = hservice_assert,
	.malloc = hservice_malloc,
	.free = hservice_free,
	.realloc = hservice_realloc,
	.send_error_log = hservice_send_error_log,
	.scom_read = hservice_scom_read,
	.scom_write = hservice_scom_write,
	.lid_load = hservice_lid_load,
	.lid_unload = hservice_lid_unload,
	.get_reserved_mem = hservice_get_reserved_mem,
	.wakeup = hservice_wakeup,
	.nanosleep = hservice_nanosleep,
};

int host_services_occ_load(void)
{
	struct proc_chip *chip;
	int rc = 0;

	prlog(PR_DEBUG, "HBRT: OCC Load requested\n");

	if (!(hservice_runtime && hservice_runtime->loadOCC)) {
		prerror("HBRT: No hservice_runtime->loadOCC\n");
		return -ENOENT;
	}

	for_each_chip(chip) {

		prlog(PR_DEBUG, "HBRT: Calling loadOCC() homer"
		      " %016llx, occ_common_area %016llx, chip %04x\n",
		      chip->homer_base,
		      chip->occ_common_base,
		      chip->id);

		rc = hservice_runtime->loadOCC(chip->homer_base,
						chip->homer_base,
						chip->occ_common_base,
						chip->occ_common_base,
						chip->id);

		hservice_mark();
		prlog(PR_DEBUG, "HBRT: -> rc = %d\n", rc);
	}
	return rc;
}

int host_services_occ_start(void)
{
	struct proc_chip *chip;
	int i, rc = 0, nr_chips=0;
	uint64_t chipids[MAX_CHIPS];

	prlog(PR_INFO, "HBRT: OCC Start requested\n");

	if (!(hservice_runtime && hservice_runtime->startOCCs)) {
		prerror("HBRT: No hservice_runtime->startOCCs\n");
		return -ENOENT;
	}

	for_each_chip(chip) {
		chipids[nr_chips++] = chip->id;
	}

	for (i = 0; i < nr_chips; i++)
		prlog(PR_TRACE, "HBRT: Calling startOCC() for %04llx\n",
		      chipids[i]);

	/* Lets start all OCC */
	rc = hservice_runtime->startOCCs(chipids, nr_chips);
	hservice_mark();
	prlog(PR_DEBUG, "HBRT: startOCCs() rc  = %d\n", rc);
	return rc;
}

int host_services_occ_stop(void)
{
	int i, rc = 0, nr_slaves = 0, nr_masters = 0;
	uint64_t *master_chipids = NULL, *slave_chipids = NULL;

	prlog(PR_INFO, "HBRT: OCC Stop requested\n");

	if (!(hservice_runtime && hservice_runtime->stopOCCs)) {
		prerror("HBRT: No hservice_runtime->stopOCCs\n");
		return -ENOENT;
	}

	rc = find_master_and_slave_occ(&master_chipids, &slave_chipids,
				       &nr_masters, &nr_slaves);
	if (rc)
		goto out;

	for (i = 0; i < nr_slaves; i++)
		prlog(PR_TRACE, "HBRT: Calling stopOCC() for %04llx ",
		      slave_chipids[i]);

	if (!nr_slaves)
		goto master;

	/* Lets STOP all the slave OCC */
	rc = hservice_runtime->stopOCCs(slave_chipids, nr_slaves);
	prlog(PR_DEBUG, "HBRT: stopOCCs() slave rc  = %d\n", rc);

master:
	for (i = 0; i < nr_masters; i++)
		prlog(PR_TRACE, "HBRT: Calling stopOCC() for %04llx ",
		      master_chipids[i]);

	/* Lets STOP all the master OCC */
	rc = hservice_runtime->stopOCCs(master_chipids, nr_masters);

	hservice_mark();
	prlog(PR_DEBUG, "HBRT: stopOCCs() master rc  = %d\n", rc);

out:
	free(master_chipids);
	free(slave_chipids);
	return rc;
}

bool hservices_init(void)
{
	void *code = NULL;
	struct runtime_interfaces *(*hbrt_init)(struct host_interfaces *);

	struct function_descriptor {
		void *addr;
		void *toc;
	} fdesc;

	code = (void *)hservice_get_reserved_mem("ibm,hbrt-code-image");
	if (!code) {
		prerror("HBRT: No ibm,hbrt-code-image found.\n");
		return false;
	}

	if (memcmp(code, "HBRTVERS", 8) != 0) {
		prerror("HBRT: Bad eyecatcher for ibm,hbrt-code-image!\n");
		return false;
	}

	prlog(PR_INFO, "HBRT: Found HostBoot Runtime version %llu\n",
	      ((u64 *)code)[1]);

	/* We enter at 0x100 into the image. */
	fdesc.addr = code + 0x100;
	/* It doesn't care about TOC */
	fdesc.toc = NULL;

	hbrt_init = (void *)&fdesc;

	hservice_runtime = hbrt_init(&hinterface);
	hservice_mark();
	if (!hservice_runtime) {
		prerror("HBRT: Host services init failed\n");
		return false;
	}

	prlog(PR_INFO, "HBRT: Interface version %llu\n",
	      hservice_runtime->interface_version);

	return true;
}		

static void hservice_send_hbrt_msg_resp(struct fsp_msg *msg)
{
	int status = (msg->resp->word1 >> 8) & 0xff;

	fsp_freemsg(msg);
	if (status) {
		prlog(PR_NOTICE, "HBRT: HBRT to FSP MBOX command failed "
		      "[rc=0x%x]\n", status);
	}

	fsp_tce_unmap(PSI_DMA_HBRT_FSP_MSG, PSI_DMA_HBRT_FSP_MSG_SIZE);
	/* Send response data to HBRT */
	prd_fw_resp_fsp_response(status);
}

#define FSP_STATUS_RR	(-8193)
/* Caller takes care of serializing MBOX message */
int hservice_send_hbrt_msg(void *data, u64 dsize)
{
	uint32_t tce_len, offset;
	int rc;
	uint64_t addr;
	struct fsp_msg *msg;

	prlog(PR_NOTICE, "HBRT: HBRT - FSP message generated\n");

	/* We only support FSP based system */
	if (!fsp_present()) {
		prlog(PR_DEBUG,
		      "HBRT: Warning, HBRT - FSP message discarded!\n");
		return OPAL_UNSUPPORTED;
	}

	/*
	 * If FSP is in R/R then send specific return code to HBRT (inside
	 * HBRT message) and return success to caller (opal_prd_msg()).
	 */
	if (fsp_in_rr()) {
		prlog(PR_DEBUG,
		      "HBRT: FSP is in R/R. Dropping HBRT - FSP message\n");
		prd_fw_resp_fsp_response(FSP_STATUS_RR);
		return OPAL_SUCCESS;
	}

	/* Adjust address, size for TCE mapping */
	addr = (u64)data & ~TCE_MASK;
	offset = (u64)data & TCE_MASK;
	tce_len = ALIGN_UP((dsize + offset), TCE_PSIZE);

	if (tce_len > PSI_DMA_HBRT_FSP_MSG_SIZE) {
		prlog(PR_DEBUG,
		      "HBRT: HBRT - FSP message is too big, discarded\n");
		return OPAL_PARAMETER;
	}
	fsp_tce_map(PSI_DMA_HBRT_FSP_MSG, (void *)addr, tce_len);

	msg = fsp_mkmsg(FSP_CMD_HBRT_TO_FSP, 3, 0,
			(PSI_DMA_HBRT_FSP_MSG + offset), dsize);
	if (!msg) {
		prlog(PR_DEBUG,
		      "HBRT: Failed to create HBRT - FSP message to FSP\n");
		rc = OPAL_NO_MEM;
		goto out_tce_unmap;
	}

	rc = fsp_queue_msg(msg, hservice_send_hbrt_msg_resp);
	if (rc == 0)
		return rc;

	prlog(PR_DEBUG, "HBRT: Failed to queue HBRT message to FSP\n");
	fsp_freemsg(msg);

out_tce_unmap:
	fsp_tce_unmap(PSI_DMA_HBRT_FSP_MSG, PSI_DMA_HBRT_FSP_MSG_SIZE);
	return rc;
}

void hservice_hbrt_msg_response(uint32_t rc)
{
	struct fsp_msg *resp;

	resp = fsp_mkmsg(FSP_RSP_FSP_TO_HBRT | (uint8_t)rc, 0);
	if (!resp) {
		prlog(PR_DEBUG,
		      "HBRT: Failed to allocate FSP - HBRT response message\n");
		return;
	}

	if (fsp_queue_msg(resp, fsp_freemsg)) {
		prlog(PR_DEBUG,
		      "HBRT: Failed to send FSP - HBRT response message\n");
		fsp_freemsg(resp);
		return;
	}
}

/* FSP sends HBRT notification message. Pass this message to HBRT */
static bool hservice_hbrt_msg_notify(uint32_t cmd_sub_mod, struct fsp_msg *msg)
{
	u32 tce_token, len;
	void *buf;

	if (cmd_sub_mod != FSP_CMD_FSP_TO_HBRT)
		return false;

	prlog(PR_TRACE, "HBRT: FSP - HBRT message generated\n");

	tce_token = fsp_msg_get_data_word(msg, 1);
	len = fsp_msg_get_data_word(msg, 2);
	buf = fsp_inbound_buf_from_tce(tce_token);
	if (!buf) {
		prlog(PR_DEBUG, "HBRT: Invalid inbound data\n");
		hservice_hbrt_msg_response(FSP_STATUS_INVALID_DATA);
		return true;
	}

	if (prd_hbrt_fsp_msg_notify(buf, len)) {
		hservice_hbrt_msg_response(FSP_STATUS_GENERIC_ERROR);
		prlog(PR_NOTICE, "Unable to send FSP - HBRT message\n");
	}

	return true;
}

static struct fsp_client fsp_hbrt_msg_client = {
	.message = hservice_hbrt_msg_notify,
};

/* Register for FSP 0xF2 class messages */
void hservice_fsp_init(void)
{
	if (proc_gen < proc_gen_p9)
		return;

	if (!fsp_present())
		return;

	/* Register for Class F2 */
	fsp_register_client(&fsp_hbrt_msg_client, FSP_MCLASS_HBRT);
}
