| /* 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 <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 "spira.h" |
| #include "hdata.h" |
| |
| struct HDIF_ram_area_id { |
| __be16 id; |
| #define RAM_AREA_INSTALLED 0x8000 |
| #define RAM_AREA_FUNCTIONAL 0x4000 |
| __be16 flags; |
| } __packed; |
| |
| struct HDIF_ram_area_size { |
| __be64 mb; |
| } __packed; |
| |
| 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; |
| } __packed; |
| |
| 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 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, *region; |
| |
| 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; |
| |
| region = dt_find_property(i, "reg"); |
| if (!region) |
| continue; |
| memcpy(reg, region->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; |
| 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 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; |
| u32 chip_id; |
| size_t namesz = sizeof("memory@") + STR_MAX_CHARS(reg[0]); |
| |
| name = (char*)malloc(namesz); |
| assert(name); |
| |
| 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\n", |
| (long long)be64_to_cpu(arange->start), |
| (long long)be64_to_cpu(arange->end), |
| chip_id, 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]; |
| |
| 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); |
| free(name); |
| return true; |
| } |
| } |
| snprintf(name, namesz, "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)); |
| |
| free(name); |
| |
| 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 *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); |
| if (!str){ |
| prerror("Allocation failed\n"); |
| return; |
| } |
| 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; |
| 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); |
| |
| vpd_blob = HDIF_get_idata(ramarea, 1, &ram_sz); |
| |
| /* |
| * 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)) { |
| /* the ibm,vpd blob was added in dt_add_vpd_node() */ |
| add_size_to_ram_area(ram_node, ramarea, 1); |
| } else { |
| /* |
| * FIXME: There's probably a way to calculate the |
| * size of the DIMM from the SPD info. |
| */ |
| dt_add_property(ram_node, "spd", vpd_blob, ram_sz); |
| } |
| } |
| } |
| |
| 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); |
| 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; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| #define HRMOR_BIT (1ul << 63) |
| |
| 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; |
| int unnamed = 0, count, i; |
| char *label; |
| |
| /* |
| * 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 > 64) |
| label_size = 64; |
| |
| label = malloc(label_size+1); |
| assert(label); |
| |
| memcpy(label, hb_resv_mem->label, label_size); |
| label[label_size] = '\0'; |
| |
| if (strlen(label) == 0) |
| snprintf(label, 64, "hostboot-reserve-%d", unnamed++); |
| |
| |
| 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)); |
| |
| mem_reserve_hw(label, start_addr, end_addr - start_addr + 1); |
| } |
| } |
| |
| 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); |
| |
| 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(); |
| } |
| } |
| |