/* Copyright 2013-2018 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 <cpu.h>
#include <device.h>
#include <vpd.h>
#include <ccan/str/str.h>
#include <libfdt/libfdt.h>
#include <mem_region.h>
#include <types.h>
#include <inttypes.h>
#include <processor.h>

#include "spira.h"
#include "hdata.h"

struct HDIF_ram_area_id {
	__be16 id;
#define RAM_AREA_INSTALLED	0x8000
#define RAM_AREA_FUNCTIONAL	0x4000
	__be16 flags;
	__be32 dimm_id;
	__be32 speed;
} __packed;

struct HDIF_ram_area_size {
	__be32 reserved1;
	__be32 mb;
} __packed;

struct HDIF_ms_area_address_range {
	__be64 start;
	__be64 end;
	__be32 chip;
	__be32 mirror_attr;
	__be64 mirror_start;
	__be32 controller_id;
	__be32 phys_attr;
} __packed;
#define PHYS_ATTR_TYPE_MASK 	0xff000000
#define   PHYS_ATTR_TYPE_STD		0
#define   PHYS_ATTR_TYPE_NVDIMM		1
#define   PHYS_ATTR_TYPE_MRAM		2
#define   PHYS_ATTR_TYPE_PCM		3

#define PHYS_ATTR_STATUS_MASK 	0x00ff0000
/*
 * The values here are mutually exclusive. I have no idea why anyone
 * decided encoding these are flags rather than sequential numbers was
 * a good idea, but here we are.
 */
#define   PHYS_ATTR_STATUS_CANT_SAVE 	0x01
#define   PHYS_ATTR_STATUS_SAVE_FAILED	0x02
#define   PHYS_ATTR_STATUS_SAVED	0x04
#define   PHYS_ATTR_STATUS_NOT_SAVED	0x08
#define   PHYS_ATTR_STATUS_MEM_INVALID	0xff

#define MS_CONTROLLER_MCBIST_ID(id)	GETFIELD(PPC_BITMASK32(0, 1), id)
#define MS_CONTROLLER_MCS_ID(id)	GETFIELD(PPC_BITMASK32(4, 7), id)
#define MS_CONTROLLER_MCA_ID(id)	GETFIELD(PPC_BITMASK32(8, 15), id)

struct HDIF_ms_area_id {
	__be16 id;
#define MS_PTYPE_RISER_CARD	0x8000
#define MS_PTYPE_MEM_CARD	0x4000
#define MS_PTYPE_CEC_FRU	0x2000
#define MS_PTYPE_HYBRID_CARD	0x1000
	__be16 parent_type;
#define MS_AREA_INSTALLED	0x8000
#define MS_AREA_FUNCTIONAL	0x4000
#define MS_AREA_SHARED		0x2000
	__be16 flags;
	__be16 share_id;
} __packed;

static void append_chip_id(struct dt_node *mem, u32 id)
{
	struct dt_property *prop;
	size_t len, i;
	be32 *p;

	prop = __dt_find_property(mem, "ibm,chip-id");
	if (!prop)
		return;
	len = prop->len >> 2;
	p = (be32*)prop->prop;

	/* Check if it exists already */
	for (i = 0; i < len; i++) {
		if (be32_to_cpu(p[i]) == id)
			return;
	}

	/* Add it to the list */
	dt_resize_property(&prop, (len + 1) << 2);
	p = (be32 *)prop->prop;
	p[len] = cpu_to_be32(id);
}

static void update_status(struct dt_node *mem, uint32_t status)
{
	switch (status) {
	case PHYS_ATTR_STATUS_CANT_SAVE:
		if (!dt_find_property(mem, "save-trigged-unarmed"))
			dt_add_property(mem, "save-trigger-unarmed", NULL, 0);
		break;

	case PHYS_ATTR_STATUS_SAVE_FAILED:
		if (!dt_find_property(mem, "save-failed"))
			dt_add_property(mem, "save-failed", NULL, 0);

		break;

	case PHYS_ATTR_STATUS_MEM_INVALID:
		if (dt_find_property(mem, "save-trigged-unarmed"))
			dt_add_property_string(mem, "status",
				"disabled-memory-invalid");
		break;
	}
}

static bool add_address_range(struct dt_node *root,
			      const struct HDIF_ms_area_id *id,
			      const struct HDIF_ms_area_address_range *arange,
			      uint32_t mem_type, uint32_t mem_status)
{
	const char *compat = NULL, *dev_type = NULL, *name = NULL;
	struct dt_node *mem;
	u32 chip_id;
	u64 reg[2];

	chip_id = pcid_to_chip_id(be32_to_cpu(arange->chip));

	prlog(PR_DEBUG, "  Range: 0x%016llx..0x%016llx "
	      "on Chip 0x%x mattr: 0x%x pattr: 0x%x status:0x%x\n",
	      (long long)be64_to_cpu(arange->start),
	      (long long)be64_to_cpu(arange->end),
	      chip_id, arange->mirror_attr, mem_type, mem_status);

	/* reg contains start and length */
	reg[0] = cleanup_addr(be64_to_cpu(arange->start));
	reg[1] = cleanup_addr(be64_to_cpu(arange->end)) - reg[0];

	switch (mem_type) {
	case PHYS_ATTR_TYPE_STD:
		name = "memory";
		dev_type = "memory";
		break;

	case PHYS_ATTR_TYPE_NVDIMM:
	case PHYS_ATTR_TYPE_MRAM:
	case PHYS_ATTR_TYPE_PCM:
		/* fall through */
		name = "nvdimm";
		compat = "pmem-region";
		break;

	/*
	 * Future memory types could be volatile or non-volatile. Bail if don't
	 * recognise the type so we don't end up trashing data accidently.
	 */
	default:
		return false;
	}

	if (be16_to_cpu(id->flags) & MS_AREA_SHARED) {
		mem = dt_find_by_name_addr(dt_root, name, reg[0]);
		if (mem) {
			append_chip_id(mem, chip_id);
			if (mem_type == PHYS_ATTR_TYPE_NVDIMM)
				update_status(mem, mem_status);
			return true;
		}
	}

	mem = dt_new_addr(root, name, reg[0]);
	if (compat)
		dt_add_property_string(mem, "compatible", compat);
	if (dev_type)
		dt_add_property_string(mem, "device_type", dev_type);

	/* add in the nvdimm backup status flags */
	if (mem_type == PHYS_ATTR_TYPE_NVDIMM)
		update_status(mem, mem_status);

	/* common properties */

	dt_add_property_u64s(mem, "reg", reg[0], reg[1]);
	dt_add_property_cells(mem, "ibm,chip-id", chip_id);
	return true;
}

static u32 add_chip_id_to_ram_area(const struct HDIF_common_hdr *msarea,
				    struct dt_node *ram_area)
{
	const struct HDIF_array_hdr *arr;
	const struct HDIF_ms_area_address_range *arange;
	unsigned int size;
	u32 chip_id;

	/* Safe to assume pointers are valid here. */
	arr = HDIF_get_idata(msarea, 4, &size);
	arange = (void *)arr + be32_to_cpu(arr->offset);
	chip_id = pcid_to_chip_id(be32_to_cpu(arange->chip));
	dt_add_property_cells(ram_area, "ibm,chip-id", chip_id);

	return chip_id;
}

static void add_bus_freq_to_ram_area(struct dt_node *ram_node, u32 chip_id)
{
	const struct sppcia_cpu_timebase *timebase;
	bool got_pcia = false;
	const void *pcia;
	u64 freq;
	u32 size;

	pcia = get_hdif(&spira.ntuples.pcia, SPPCIA_HDIF_SIG);
	if (!pcia) {
		prlog(PR_WARNING, "HDAT: Failed to add memory bus frequency "
		      "as PCIA does not exist\n");
		return;
	}

	for_each_pcia(pcia) {
		const struct sppcia_core_unique *id;

		id = HDIF_get_idata(pcia, SPPCIA_IDATA_CORE_UNIQUE, &size);
		if (!id || size < sizeof(*id)) {
			prlog(PR_WARNING, "HDAT: Bad id size %u @ %p\n", size, id);
			return;
		}

		if (chip_id == pcid_to_chip_id(be32_to_cpu(id->proc_chip_id))) {
			got_pcia = true;
			break;
		}
	}

	if (got_pcia == false)
		return;

	timebase = HDIF_get_idata(pcia, SPPCIA_IDATA_TIMEBASE, &size);
	if (!timebase || size < sizeof(*timebase)) {
		/**
		 * @fwts-label HDATBadTimebaseSize
		 * @fwts-advice HDAT described an invalid size for timebase,
		 * which means there's a disagreement between HDAT and OPAL.
		 * This is most certainly a firmware bug.
		 */
		prlog(PR_ERR, "HDAT: Bad timebase size %u @ %p\n", size,
		      timebase);
		return;
	}

	freq = ((u64)be32_to_cpu(timebase->memory_bus_frequency)) * 1000000ul;
	dt_add_property_u64(ram_node, "ibm,memory-bus-frequency", freq);
}

static void add_size_to_ram_area(struct dt_node *ram_node,
				 const struct HDIF_common_hdr *ramarea)
{
	char	str[16];
	const struct HDIF_ram_area_size *ram_area_sz;

	/* DIMM size */
	ram_area_sz = HDIF_get_idata(ramarea, 3, NULL);
	if (!CHECK_SPPTR(ram_area_sz))
		return;

	memset(str, 0, 16);
	snprintf(str, 16, "%d", be32_to_cpu(ram_area_sz->mb));
	dt_add_property_string(ram_node, "size", str);
}

static void vpd_add_ram_area(const struct HDIF_common_hdr *msarea)
{
	unsigned int i;
	unsigned int ram_sz;
	const struct HDIF_common_hdr *ramarea;
	const struct HDIF_child_ptr *ramptr;
	const struct HDIF_ram_area_id *ram_id;
	struct dt_node *ram_node;
	u32 chip_id;
	const void *vpd_blob;

	ramptr = HDIF_child_arr(msarea, 0);
	if (!CHECK_SPPTR(ramptr)) {
		prerror("MS AREA: No RAM area at %p\n", msarea);
		return;
	}

	for (i = 0; i < be32_to_cpu(ramptr->count); i++) {
		ramarea = HDIF_child(msarea, ramptr, i, "RAM   ");
		if (!CHECK_SPPTR(ramarea))
			continue;

		ram_id = HDIF_get_idata(ramarea, 2, &ram_sz);
		if (!CHECK_SPPTR(ram_id))
			continue;

		/* Don't add VPD for non-existent RAM */
		if (!(be16_to_cpu(ram_id->flags) & RAM_AREA_INSTALLED))
			continue;

		ram_node = dt_add_vpd_node(ramarea, 0, 1);
		if (!ram_node)
			continue;

		chip_id = add_chip_id_to_ram_area(msarea, ram_node);
		add_bus_freq_to_ram_area(ram_node, chip_id);

		if (ram_sz >= offsetof(struct HDIF_ram_area_id, speed)) {
			dt_add_property_cells(ram_node, "frequency",
					      be32_to_cpu(ram_id->speed)*1000000);
		}

		vpd_blob = HDIF_get_idata(ramarea, 1, &ram_sz);

		/* DIMM size */
		add_size_to_ram_area(ram_node, ramarea);
		/*
		 * For direct-attached memory we have a DDR "Serial
		 * Presence Detection" blob rather than an IBM keyword
		 * blob.
		 */
		if (!vpd_valid(vpd_blob, ram_sz))
			dt_add_property(ram_node, "spd", vpd_blob, ram_sz);
	}
}

static void vpd_parse_spd(struct dt_node *dimm, const char *spd, u32 size)
{
	u16 *vendor;
	u32 *sn;

	/* SPD is too small */
	if (size < 512) {
		prlog(PR_WARNING, "MSVPD: Invalid SPD size. "
		      "Expected 512 bytes, got %d\n", size);
		return;
	}

	/* Supports DDR4 format pasing only */
	if (spd[0x2] < 0xc) {
		prlog(PR_WARNING,
		      "MSVPD: SPD format (%x) not supported\n", spd[0x2]);
		return;
	}

	dt_add_property_string(dimm, "device_type", "memory-dimm-ddr4");

	/* DRAM device type */
	dt_add_property_cells(dimm, "memory-id", spd[0x2]);

	/* Module revision code */
	dt_add_property_cells(dimm, "product-version", spd[0x15d]);

	/* Serial number */
	sn = (u32 *)&spd[0x145];
	dt_add_property_cells(dimm, "serial-number", be32_to_cpu(*sn));

	/* Part number */
	dt_add_property_nstr(dimm, "part-number", &spd[0x149], 20);

	/* Module manufacturer ID */
	vendor = (u16 *)&spd[0x140];
	dt_add_property_cells(dimm, "manufacturer-id", be16_to_cpu(*vendor));
}

static void add_mca_dimm_info(struct dt_node *mca,
			      const struct HDIF_common_hdr *msarea)
{
	unsigned int i, size;
	const struct HDIF_child_ptr *ramptr;
	const struct HDIF_common_hdr *ramarea;
	const struct spira_fru_id *fru_id;
	const struct HDIF_ram_area_id *ram_id;
	const struct HDIF_ram_area_size *ram_area_sz;
	struct dt_node *dimm;
	const void *vpd_blob;

	ramptr = HDIF_child_arr(msarea, 0);
	if (!CHECK_SPPTR(ramptr)) {
		prerror("MS AREA: No RAM area at %p\n", msarea);
		return;
	}

	for (i = 0; i < be32_to_cpu(ramptr->count); i++) {
		ramarea = HDIF_child(msarea, ramptr, i, "RAM   ");
		if (!CHECK_SPPTR(ramarea))
			continue;

		fru_id = HDIF_get_idata(ramarea, 0, NULL);
		if (!fru_id)
			continue;

		/* Use Resource ID to add dimm node */
		dimm = dt_find_by_name_addr(mca, "dimm",
					    be16_to_cpu(fru_id->rsrc_id));
		if (dimm)
			continue;
		dimm= dt_new_addr(mca, "dimm", be16_to_cpu(fru_id->rsrc_id));
		assert(dimm);
		dt_add_property_cells(dimm, "reg", be16_to_cpu(fru_id->rsrc_id));

		/* Add location code */
		slca_vpd_add_loc_code(dimm, be16_to_cpu(fru_id->slca_index));

		/* DIMM size */
		ram_area_sz = HDIF_get_idata(ramarea, 3, NULL);
		if (!CHECK_SPPTR(ram_area_sz))
			continue;
		dt_add_property_cells(dimm, "size", be32_to_cpu(ram_area_sz->mb));

		/* DIMM state */
		ram_id = HDIF_get_idata(ramarea, 2, NULL);
		if (!CHECK_SPPTR(ram_id))
			continue;

		if ((be16_to_cpu(ram_id->flags) & RAM_AREA_INSTALLED) &&
		    (be16_to_cpu(ram_id->flags) & RAM_AREA_FUNCTIONAL))
			dt_add_property_string(dimm, "status", "okay");
		else
			dt_add_property_string(dimm, "status", "disabled");

		vpd_blob = HDIF_get_idata(ramarea, 1, &size);
		if (!CHECK_SPPTR(vpd_blob))
			continue;
		if (vpd_valid(vpd_blob, size))
			vpd_data_parse(dimm, vpd_blob, size);
		else
			vpd_parse_spd(dimm, vpd_blob, size);
	}
}

static inline void dt_add_mem_reg_property(struct dt_node *node, u64 addr)
{
	dt_add_property_cells(node, "#address-cells", 1);
	dt_add_property_cells(node, "#size-cells", 0);
	dt_add_property_cells(node, "reg", addr);
}

static void add_memory_controller(const struct HDIF_common_hdr *msarea,
				  const struct HDIF_ms_area_address_range *arange)
{
	uint32_t chip_id, version;
	uint32_t controller_id, mcbist_id, mcs_id, mca_id;
	struct dt_node *xscom, *mcbist, *mcs, *mca;

	/*
	 * Memory hierarchy may change between processor version. Presently
	 * it's only creating memory hierarchy for P9 (Nimbus) and P9P (Axone).
	 */
	version = PVR_TYPE(mfspr(SPR_PVR));
	if (version != PVR_TYPE_P9 && version != PVR_TYPE_P9P)
		return;

	chip_id = pcid_to_chip_id(be32_to_cpu(arange->chip));
	controller_id = be32_to_cpu(arange->controller_id);
	xscom = find_xscom_for_chip(chip_id);
	if (!xscom) {
		prlog(PR_WARNING,
		      "MS AREA: Can't find XSCOM for chip %d\n", chip_id);
		return;
	}

	mcbist_id = MS_CONTROLLER_MCBIST_ID(controller_id);
	mcbist = dt_find_by_name_addr(xscom, "mcbist", mcbist_id);
	if (!mcbist) {
		mcbist = dt_new_addr(xscom, "mcbist", mcbist_id);
		assert(mcbist);
		dt_add_property_cells(mcbist, "#address-cells", 1);
		dt_add_property_cells(mcbist, "#size-cells", 0);
		dt_add_property_cells(mcbist, "reg", mcbist_id, 0);
	}

	mcs_id = MS_CONTROLLER_MCS_ID(controller_id);
	mcs = dt_find_by_name_addr(mcbist, "mcs", mcs_id);
	if (!mcs) {
		mcs = dt_new_addr(mcbist, "mcs", mcs_id);
		assert(mcs);
		dt_add_mem_reg_property(mcs, mcs_id);
	}

	mca_id = MS_CONTROLLER_MCA_ID(controller_id);
	mca = dt_find_by_name_addr(mcs, "mca", mca_id);
	if (!mca) {
		mca = dt_new_addr(mcs, "mca", mca_id);
		assert(mca);
		dt_add_mem_reg_property(mca, mca_id);
	}

	add_mca_dimm_info(mca, msarea);
}

static void get_msareas(struct dt_node *root,
			const struct HDIF_common_hdr *ms_vpd)
{
	unsigned int i;
	const struct HDIF_child_ptr *msptr;

	/* First childptr refers to msareas. */
	msptr = HDIF_child_arr(ms_vpd, MSVPD_CHILD_MS_AREAS);
	if (!CHECK_SPPTR(msptr)) {
		prerror("MS VPD: no children at %p\n", ms_vpd);
		return;
	}

	for (i = 0; i < be32_to_cpu(msptr->count); i++) {
		const struct HDIF_common_hdr *msarea;
		const struct HDIF_array_hdr *arr;
		const struct HDIF_ms_area_address_range *arange;
		const struct HDIF_ms_area_id *id;
		const void *fruid;
		unsigned int size, j, offset;
		u16 flags;

		msarea = HDIF_child(ms_vpd, msptr, i, "MSAREA");
		if (!CHECK_SPPTR(msarea))
			return;

		id = HDIF_get_idata(msarea, 2, &size);
		if (!CHECK_SPPTR(id))
			return;
		if (size < sizeof(*id)) {
			prerror("MS VPD: %p msarea #%i id size too small!\n",
				ms_vpd, i);
			return;
		}

		flags = be16_to_cpu(id->flags);
		prlog(PR_DEBUG, "MS VPD: %p, area %i: %s %s %s\n",
		       ms_vpd, i,
		       flags & MS_AREA_INSTALLED ?
		       "installed" : "not installed",
		       flags & MS_AREA_FUNCTIONAL ?
		       "functional" : "not functional",
		       flags & MS_AREA_SHARED ?
		       "shared" : "not shared");

		if ((flags & (MS_AREA_INSTALLED|MS_AREA_FUNCTIONAL))
		    != (MS_AREA_INSTALLED|MS_AREA_FUNCTIONAL))
			continue;

		arr = HDIF_get_idata(msarea, 4, &size);
		if (!CHECK_SPPTR(arr))
			continue;

		if (size < sizeof(*arr)) {
			prerror("MS VPD: %p msarea #%i arr size too small!\n",
				ms_vpd, i);
			return;
		}

		offset = offsetof(struct HDIF_ms_area_address_range, mirror_start);
		if (be32_to_cpu(arr->eactsz) < offset) {
			prerror("MS VPD: %p msarea #%i arange size too small!\n",
				ms_vpd, i);
			return;
		}

		fruid = HDIF_get_idata(msarea, 0, &size);
		if (!CHECK_SPPTR(fruid))
			return;

		/* Add Raiser card VPD */
		if (be16_to_cpu(id->parent_type) & MS_PTYPE_RISER_CARD)
			dt_add_vpd_node(msarea, 0, 1);

		/* Add RAM Area VPD */
		vpd_add_ram_area(msarea);

		/* This offset is from the arr, not the header! */
		arange = (void *)arr + be32_to_cpu(arr->offset);
		for (j = 0; j < be32_to_cpu(arr->ecnt); j++) {
			uint32_t type = 0, status = 0;

			/*
			 * Check that the required fields are present in this
			 * version of the HDAT structure.
			 */
			offset = offsetof(struct HDIF_ms_area_address_range, controller_id);
			if (be32_to_cpu(arr->eactsz) >= offset)
				add_memory_controller(msarea, arange);

			offset = offsetof(struct HDIF_ms_area_address_range, phys_attr);
			if (be32_to_cpu(arr->eactsz) >= offset) {
				uint32_t attr = be32_to_cpu(arange->phys_attr);

				type = GETFIELD(PHYS_ATTR_TYPE_MASK, attr);
				status = GETFIELD(PHYS_ATTR_STATUS_MASK, attr);
			}

			if (!add_address_range(root, id, arange, type, status))
				prerror("Unable to use memory range %d from MSAREA %d\n", j, i);

			arange = (void *)arange + be32_to_cpu(arr->esize);
		}
	}
}

static struct dt_node *dt_hb_reserves;

static struct dt_node *add_hb_reserve_node(const char *name, u64 start, u64 end)
{
	/* label size + "ibm," + NULL */
	char node_name[HB_RESERVE_MEM_LABEL_SIZE + 5] = { 0 };
	struct dt_node *node, *hb;

	if (!dt_hb_reserves) {
		hb = dt_new_check(dt_root, "ibm,hostboot");
		dt_add_property_cells(hb, "#size-cells", 2);
		dt_add_property_cells(hb, "#address-cells", 2);

		dt_hb_reserves = dt_new_check(hb, "reserved-memory");
		dt_add_property(dt_hb_reserves, "ranges", NULL, 0);
		dt_add_property_cells(dt_hb_reserves, "#size-cells", 2);
		dt_add_property_cells(dt_hb_reserves, "#address-cells", 2);
	}

	/* Add "ibm," to reserved node name */
	if (strncasecmp(name, "ibm", 3))
		snprintf(node_name, 5, "ibm,");
	strcat(node_name, name);

	node = dt_new_addr(dt_hb_reserves, node_name, start);
	if (!node) {
		prerror("Unable to create node for %s@%llx\n",
			node_name, (unsigned long long) start);
		return NULL;
	}

	dt_add_property_u64s(node, "reg", start, end - start + 1);

	return node;
}

static void get_hb_reserved_mem(struct HDIF_common_hdr *ms_vpd)
{
	const struct msvpd_hb_reserved_mem *hb_resv_mem;
	u64 start_addr, end_addr, label_size;
	struct dt_node *node;
	int count, i;
	char label[HB_RESERVE_MEM_LABEL_SIZE + 1];

	/*
	 * XXX: Reservation names only exist on P9 and on P7/8 we get the
	 *      reserved ranges through the hostboot mini-FDT instead.
	 */
	if (proc_gen < proc_gen_p9)
		return;

	count = HDIF_get_iarray_size(ms_vpd, MSVPD_IDATA_HB_RESERVED_MEM);
	if (count <= 0) {
		prerror("MS VPD: No hostboot reserved memory found\n");
		return;
	}

	for (i = 0; i < count; i++) {
		hb_resv_mem = HDIF_get_iarray_item(ms_vpd,
						   MSVPD_IDATA_HB_RESERVED_MEM,
						   i, NULL);
		if (!CHECK_SPPTR(hb_resv_mem))
			continue;

		label_size = be32_to_cpu(hb_resv_mem->label_size);
		start_addr = be64_to_cpu(hb_resv_mem->start_addr);
		end_addr = be64_to_cpu(hb_resv_mem->end_addr);

		/* Zero length regions are a normal, but should be ignored */
		if (start_addr - end_addr == 0) {
			prlog(PR_DEBUG, "MEM: Ignoring zero length range\n");
			continue;
		}

		/*
		 * Workaround broken HDAT reserve regions which are
		 * bigger than 512MB
		 */
		if ((end_addr - start_addr) > 0x20000000) {
			prlog(PR_ERR, "MEM: Ignoring Bad HDAT reserve: too big\n");
			continue;
		}

		/* remove the HRMOR bypass bit */
		start_addr &= ~HRMOR_BIT;
		end_addr &= ~HRMOR_BIT;
		if (label_size > HB_RESERVE_MEM_LABEL_SIZE)
			label_size = HB_RESERVE_MEM_LABEL_SIZE;

		memset(label, 0, HB_RESERVE_MEM_LABEL_SIZE + 1);
		memcpy(label, hb_resv_mem->label, label_size);
		label[label_size] = '\0';

		/* Unnamed reservations are always broken. Ignore them. */
		if (strlen(label) == 0)
			continue;

		prlog(PR_DEBUG, "MEM: Reserve '%s' %#" PRIx64 "-%#" PRIx64 " (type/inst=0x%08x)\n",
		      label, start_addr, end_addr, be32_to_cpu(hb_resv_mem->type_instance));

		node = add_hb_reserve_node(label, start_addr, end_addr);
		if (!node) {
			prerror("unable to add node?\n");
			continue;
		}

		/* the three low bytes of type_instance is the instance data */
		dt_add_property_cells(node, "ibm,prd-instance",
			(be32_to_cpu(hb_resv_mem->type_instance) & 0xffffff));

		dt_add_property_string(node, "ibm,prd-label", label);
	}
}

static void parse_trace_reservations(struct HDIF_common_hdr *ms_vpd)
{
	unsigned int size;
	int count, i;

	/*
	 * The trace arrays are only setup when hostboot is explicitly
	 * configured to enable them. We need to check and gracefully handle
	 * when they're not present.
	 */

	if (!HDIF_get_idata(ms_vpd, MSVPD_IDATA_TRACE_AREAS, &size) || !size) {
		prlog(PR_DEBUG, "MS VPD: No trace areas found\n");
		return;
	}

	count = HDIF_get_iarray_size(ms_vpd, MSVPD_IDATA_TRACE_AREAS);
	if (count <= 0) {
		prlog(PR_DEBUG, "MS VPD: No trace areas found\n");
		return;
	}

	prlog(PR_INFO, "MS VPD: Found %d trace areas\n", count);

	for (i = 0; i < count; i++) {
		const struct msvpd_trace *trace_area;
		struct dt_node *node;
		u64 start, end;

		trace_area = HDIF_get_iarray_item(ms_vpd,
				MSVPD_IDATA_TRACE_AREAS, i, &size);

		if (!trace_area)
			return; /* shouldn't happen */

		start = be64_to_cpu(trace_area->start) & ~HRMOR_BIT;
		end = be64_to_cpu(trace_area->end) & ~HRMOR_BIT;

		prlog(PR_INFO,
			"MS VPD: Trace area: 0x%.16"PRIx64"-0x%.16"PRIx64"\n",
			start, end);

		node = add_hb_reserve_node("trace-area", start, end);
		if (!node) {
			prerror("MEM: Unable to reserve trace area %p-%p\n",
				(void *) start, (void *) end);
			continue;
		}

		dt_add_property(node, "no-map", NULL, 0);
	}
}

static bool __memory_parse(struct dt_node *root)
{
	struct HDIF_common_hdr *ms_vpd;
	const struct msvpd_ms_addr_config *msac;
	const struct msvpd_total_config_ms *tcms;
	unsigned int size;

	ms_vpd = get_hdif(&spira.ntuples.ms_vpd, MSVPD_HDIF_SIG);
	if (!ms_vpd) {
		prerror("MS VPD: invalid\n");
		op_display(OP_FATAL, OP_MOD_MEM, 0x0000);
		return false;
	}
	if (be32_to_cpu(spira.ntuples.ms_vpd.act_len) < sizeof(*ms_vpd)) {
		prerror("MS VPD: invalid size %u\n",
			be32_to_cpu(spira.ntuples.ms_vpd.act_len));
		op_display(OP_FATAL, OP_MOD_MEM, 0x0001);
		return false;
	}

	prlog(PR_DEBUG, "MS VPD: is at %p\n", ms_vpd);

	msac = HDIF_get_idata(ms_vpd, MSVPD_IDATA_MS_ADDR_CONFIG, &size);
	if (!CHECK_SPPTR(msac) || size < sizeof(*msac)) {
		prerror("MS VPD: bad msac size %u @ %p\n", size, msac);
		op_display(OP_FATAL, OP_MOD_MEM, 0x0002);
		return false;
	}
	prlog(PR_DEBUG, "MS VPD: MSAC is at %p\n", msac);

	dt_add_property_u64(dt_root, DT_PRIVATE "maxmem",
			    be64_to_cpu(msac->max_configured_ms_address));

	tcms = HDIF_get_idata(ms_vpd, MSVPD_IDATA_TOTAL_CONFIG_MS, &size);
	if (!CHECK_SPPTR(tcms) || size < sizeof(*tcms)) {
		prerror("MS VPD: Bad tcms size %u @ %p\n", size, tcms);
		op_display(OP_FATAL, OP_MOD_MEM, 0x0003);
		return false;
	}
	prlog(PR_DEBUG, "MS VPD: TCMS is at %p\n", tcms);

	prlog(PR_DEBUG, "MS VPD: Maximum configured address: 0x%llx\n",
	      (long long)be64_to_cpu(msac->max_configured_ms_address));
	prlog(PR_DEBUG, "MS VPD: Maximum possible address: 0x%llx\n",
	      (long long)be64_to_cpu(msac->max_possible_ms_address));

	get_msareas(root, ms_vpd);

	get_hb_reserved_mem(ms_vpd);

	parse_trace_reservations(ms_vpd);

	prlog(PR_INFO, "MS VPD: Total MB of RAM: 0x%llx\n",
	       (long long)be64_to_cpu(tcms->total_in_mb));

	return true;
}

void memory_parse(void)
{
	if (!__memory_parse(dt_root)) {
		prerror("MS VPD: Failed memory init !\n");
		abort();
	}
}

