diff options
Diffstat (limited to 'hdata/memory.c')
-rw-r--r-- | hdata/memory.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/hdata/memory.c b/hdata/memory.c new file mode 100644 index 0000000..8cca689 --- /dev/null +++ b/hdata/memory.c @@ -0,0 +1,377 @@ +/* Copyright 2013-2014 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 <memory.h> +#include <cpu.h> +#include <device_tree.h> +#include <device.h> +#include <vpd.h> +#include <ccan/str/str.h> +#include <libfdt/libfdt.h> +#include <types.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; +}; + +struct HDIF_ram_area_size { + __be64 mb; +}; + +struct ram_area { + const struct HDIF_ram_area_id *raid; + const struct HDIF_ram_area_size *rasize; +}; + +struct HDIF_ms_area_address_range { + __be64 start; + __be64 end; + __be32 chip; + __be32 mirror_attr; + __be64 mirror_start; +}; + +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; +}; + +static struct dt_node *find_shared(struct dt_node *root, u16 id, u64 start, u64 len) +{ + struct dt_node *i; + + for (i = dt_first(root); i; i = dt_next(root, i)) { + __be64 reg[2]; + const struct dt_property *shared, *type; + + type = dt_find_property(i, "device_type"); + if (!type || strcmp(type->prop, "memory") != 0) + continue; + + shared = dt_find_property(i, DT_PRIVATE "share-id"); + if (!shared || fdt32_to_cpu(*(u32 *)shared->prop) != id) + continue; + + memcpy(reg, dt_find_property(i, "reg")->prop, sizeof(reg)); + if (be64_to_cpu(reg[0]) == start && be64_to_cpu(reg[1]) == len) + break; + } + return i; +} + +static void append_chip_id(struct dt_node *mem, u32 id) +{ + struct dt_property *prop; + size_t len, i; + u32 *p; + + prop = __dt_find_property(mem, "ibm,chip-id"); + if (!prop) + return; + len = prop->len >> 2; + p = (u32 *)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 = (u32 *)prop->prop; + p[len] = cpu_to_be32(id); +} + +static bool add_address_range(struct dt_node *root, + const struct HDIF_ms_area_id *id, + const struct HDIF_ms_area_address_range *arange) +{ + struct dt_node *mem; + u64 reg[2]; + char name[sizeof("memory@") + STR_MAX_CHARS(reg[0])]; + u32 chip_id; + + printf(" Range: 0x%016llx..0x%016llx on Chip 0x%x mattr: 0x%x\n", + (long long)arange->start, (long long)arange->end, + pcid_to_chip_id(arange->chip), arange->mirror_attr); + + /* 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]; + + chip_id = pcid_to_chip_id(be32_to_cpu(arange->chip)); + + if (be16_to_cpu(id->flags) & MS_AREA_SHARED) { + /* Only enter shared nodes once. */ + mem = find_shared(root, be16_to_cpu(id->share_id), + reg[0], reg[1]); + if (mem) { + append_chip_id(mem, chip_id); + return true; + } + } + sprintf(name, "memory@%llx", (long long)reg[0]); + + mem = dt_new(root, name); + dt_add_property_string(mem, "device_type", "memory"); + dt_add_property_cells(mem, "ibm,chip-id", chip_id); + dt_add_property_u64s(mem, "reg", reg[0], reg[1]); + if (be16_to_cpu(id->flags) & MS_AREA_SHARED) + dt_add_property_cells(mem, DT_PRIVATE "share-id", + be16_to_cpu(id->share_id)); + + return true; +} + +static void 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); +} + +static void add_size_to_ram_area(struct dt_node *ram_node, + const struct HDIF_common_hdr *hdr, + int indx_vpd) +{ + const void *fruvpd; + unsigned int fruvpd_sz; + const void *kw; + char *str; + uint8_t kwsz; + + fruvpd = HDIF_get_idata(hdr, indx_vpd, &fruvpd_sz); + if (!CHECK_SPPTR(fruvpd)) + return; + + /* DIMM Size */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "SZ", &kwsz); + if (!kw) + return; + + str = zalloc(kwsz + 1); + memcpy(str, kw, kwsz); + dt_add_property_string(ram_node, "size", str); + free(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; + + 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; + + if ((be16_to_cpu(ram_id->flags) & RAM_AREA_INSTALLED) && + (be16_to_cpu(ram_id->flags) & RAM_AREA_FUNCTIONAL)) { + ram_node = dt_add_vpd_node(ramarea, 0, 1); + if (ram_node) { + add_chip_id_to_ram_area(msarea, ram_node); + add_size_to_ram_area(ram_node, ramarea, 1); + } + } + } +} + +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; + 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); + printf("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; + } + + if (be32_to_cpu(arr->eactsz) < sizeof(*arange)) { + 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++) { + if (!add_address_range(root, id, arange)) + return; + arange = (void *)arange + be32_to_cpu(arr->esize); + } + } +} + +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; + } + + printf("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; + } + printf("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; + } + printf("MS VPD: TCMS is at %p\n", tcms); + + printf("MS VPD: Maximum configured address: 0x%llx\n", + (long long)be64_to_cpu(msac->max_configured_ms_address)); + printf("MS VPD: Maximum possible address: 0x%llx\n", + (long long)be64_to_cpu(msac->max_possible_ms_address)); + + get_msareas(root, ms_vpd); + + printf("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(); + } +} + |