diff options
Diffstat (limited to 'roms/skiboot/hdata/memory.c')
-rwxr-xr-x | roms/skiboot/hdata/memory.c | 958 |
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(); + } +} |