aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hdata/memory.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/hdata/memory.c')
-rwxr-xr-xroms/skiboot/hdata/memory.c958
1 files changed, 958 insertions, 0 deletions
diff --git a/roms/skiboot/hdata/memory.c b/roms/skiboot/hdata/memory.c
new file mode 100755
index 000000000..efdb502b1
--- /dev/null
+++ b/roms/skiboot/hdata/memory.c
@@ -0,0 +1,958 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#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_ENCRYPTED 0x10
+#define PHYS_ATTR_STATUS_ERR_DETECTED 0x40
+#define PHYS_ATTR_STATUS_MEM_INVALID 0xff
+
+/* Memory Controller ID for Nimbus P9 systems */
+#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)
+
+/* Memory Controller ID for P9 AXONE systems */
+#define MS_CONTROLLER_MC_ID(id) GETFIELD(PPC_BITMASK32(0, 1), id)
+#define MS_CONTROLLER_MI_ID(id) GETFIELD(PPC_BITMASK32(4, 7), id)
+#define MS_CONTROLLER_MCC_ID(id) GETFIELD(PPC_BITMASK32(8, 15), id)
+#define MS_CONTROLLER_OMI_ID(id) GETFIELD(PPC_BITMASK32(16, 31), 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;
+
+
+// FIXME: it should be 9, current HDATs are broken
+#define MSAREA_IDATA_MMIO_IDX 8
+struct HDIF_ms_area_ocmb_mmio {
+ __be64 range_start;
+ __be64 range_end;
+ __be32 controller_id;
+ __be32 proc_chip_id;
+ __be64 hbrt_id;
+#define OCMB_SCOM_8BYTE_ACCESS PPC_BIT(0)
+#define OCMB_SCOM_4BYTE_ACCESS PPC_BIT(1)
+ __be64 flags;
+} __packed;
+
+static void append_chip_id(struct dt_node *mem, u32 id)
+{
+ struct dt_property *prop;
+ size_t len, i;
+
+ prop = __dt_find_property(mem, "ibm,chip-id");
+ if (!prop)
+ return;
+ len = prop->len >> 2;
+
+ /* Check if it exists already */
+ for (i = 0; i < len; i++) {
+ if (dt_property_get_cell(prop, i) == id)
+ return;
+ }
+
+ /* Add it to the list */
+ dt_resize_property(&prop, (len + 1) << 2);
+ dt_property_set_cell(prop, len, 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, be32_to_cpu(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)
+{
+ __be16 *vendor;
+ __be32 *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 = (__be32 *)&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 = (__be16 *)&spd[0x140];
+ dt_add_property_cells(dimm, "manufacturer-id", be16_to_cpu(*vendor));
+}
+
+static void add_dimm_info(struct dt_node *parent,
+ 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(parent, "dimm",
+ be16_to_cpu(fru_id->rsrc_id));
+ if (dimm)
+ continue;
+ dimm= dt_new_addr(parent, "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_p9n(const struct HDIF_common_hdr *msarea,
+ const struct HDIF_ms_area_address_range *arange)
+{
+ uint32_t chip_id;
+ uint32_t controller_id, mcbist_id, mcs_id, mca_id;
+ struct dt_node *xscom, *mcbist, *mcs, *mca;
+
+ 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_dimm_info(mca, msarea);
+}
+
+static void add_memory_buffer_mmio(const struct HDIF_common_hdr *msarea)
+{
+ const struct HDIF_ms_area_ocmb_mmio *mmio;
+ uint64_t min_addr = ~0ull, hbrt_id = 0;
+ const struct HDIF_array_hdr *array;
+ unsigned int i, count, ranges = 0;
+ struct dt_node *membuf;
+ beint64_t *reg, *flags;
+
+ if (proc_gen <= proc_gen_p9 && PVR_TYPE(mfspr(SPR_PVR)) != PVR_TYPE_P9P)
+ return;
+
+ if (be16_to_cpu(msarea->version) < 0x50) {
+ prlog(PR_WARNING, "MS AREA: Inconsistent MSAREA version %x for P9P system",
+ be16_to_cpu(msarea->version));
+ return;
+ }
+
+ array = HDIF_get_iarray(msarea, MSAREA_IDATA_MMIO_IDX, &count);
+ if (!array || count <= 0) {
+ prerror("MS AREA: No OCMB MMIO array at MS Area %p\n", msarea);
+ return;
+ }
+
+ reg = zalloc(count * 2 * sizeof(*reg));
+ flags = zalloc(count * sizeof(*flags));
+
+ /* grab the hbrt id from the first range. */
+ HDIF_iarray_for_each(array, i, mmio) {
+ hbrt_id = be64_to_cpu(mmio->hbrt_id);
+ break;
+ }
+
+ prlog(PR_DEBUG, "Adding memory buffer MMIO ranges for %"PRIx64"\n",
+ hbrt_id);
+
+ HDIF_iarray_for_each(array, i, mmio) {
+ uint64_t start, end;
+
+ if (hbrt_id != be64_to_cpu(mmio->hbrt_id)) {
+ prerror("HBRT ID mismatch!\n");
+ continue;
+ }
+
+ start = cleanup_addr(be64_to_cpu(mmio->range_start));
+ end = cleanup_addr(be64_to_cpu(mmio->range_end));
+ if (start < min_addr)
+ min_addr = start;
+
+ prlog(PR_DEBUG, " %"PRIx64" - [%016"PRIx64"-%016"PRIx64")\n",
+ hbrt_id, start, end);
+
+ reg[2 * ranges ] = cpu_to_be64(start);
+ reg[2 * ranges + 1] = cpu_to_be64(end - start + 1);
+ flags[ranges] = mmio->flags; /* both are BE */
+ ranges++;
+ }
+
+ membuf = dt_find_by_name_addr(dt_root, "memory-buffer", min_addr);
+ if (membuf) {
+ prerror("attempted to duplicate %s\n", membuf->name);
+ goto out;
+ }
+
+ membuf = dt_new_addr(dt_root, "memory-buffer", min_addr);
+ assert(membuf);
+
+ dt_add_property_string(membuf, "compatible", "ibm,explorer");
+ dt_add_property_cells(membuf, "ibm,chip-id", hbrt_id);
+
+ /*
+ * FIXME: We should probably be sorting the address ranges based
+ * on the starting address.
+ */
+ dt_add_property(membuf, "reg", reg, sizeof(*reg) * 2 * ranges);
+ dt_add_property(membuf, "flags", flags, sizeof(*flags) * ranges);
+
+out:
+ free(flags);
+ free(reg);
+}
+
+static void add_memory_controller(const struct HDIF_common_hdr *msarea,
+ const struct HDIF_ms_area_address_range *arange)
+{
+ const uint32_t version = PVR_TYPE(mfspr(SPR_PVR));
+ /*
+ * Memory hierarchy may change between processor version. Presently
+ * it's only creating memory hierarchy for P9 (Nimbus) and P9P (Axone).
+ */
+
+ if (version == PVR_TYPE_P9)
+ return add_memory_controller_p9n(msarea, arange);
+ else if (version == PVR_TYPE_P9P)
+ return; //return add_memory_controller_p9p(msarea, arange);
+ else
+ return;
+}
+
+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);
+
+ add_memory_buffer_mmio(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));
+
+ /*
+ * Most reservations are used by HBRT itself so we should leave
+ * the label as-is. The exception is hbrt-code-image which is
+ * used by opal-prd to locate the HBRT image. Older versions
+ * of opal-prd expect this to be "ibm,hbrt-code-image" so make
+ * sure the prefix is there.
+ */
+ if (!strcmp(label, "hbrt-code-image"))
+ strcpy(label, "ibm,hbrt-code-image");
+ 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 < offsetof(struct msvpd_ms_addr_config, max_possible_ms_address)) {
+ 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();
+ }
+}