diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
commit | 1d880992fd8c8457a2d990ac6622cfd58fb1b261 (patch) | |
tree | c4c843b12e96b5612c315db5a23c5da1a900618c /hdata | |
download | skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.zip skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.gz skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.bz2 |
Initial commit of Open Source release
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'hdata')
-rw-r--r-- | hdata/Makefile.inc | 8 | ||||
-rw-r--r-- | hdata/cpu-common.c | 280 | ||||
-rw-r--r-- | hdata/fsp.c | 200 | ||||
-rw-r--r-- | hdata/hdata.h | 49 | ||||
-rw-r--r-- | hdata/hdif.c | 140 | ||||
-rw-r--r-- | hdata/hdif.h | 141 | ||||
-rw-r--r-- | hdata/hostservices.c | 96 | ||||
-rw-r--r-- | hdata/iohub.c | 715 | ||||
-rw-r--r-- | hdata/memory.c | 377 | ||||
-rw-r--r-- | hdata/paca.c | 322 | ||||
-rw-r--r-- | hdata/pcia.c | 242 | ||||
-rw-r--r-- | hdata/slca.c | 89 | ||||
-rw-r--r-- | hdata/spira.c | 965 | ||||
-rw-r--r-- | hdata/spira.h | 864 | ||||
-rw-r--r-- | hdata/test/Makefile.check | 18 | ||||
-rw-r--r-- | hdata/test/hdata_to_dt.c | 215 | ||||
-rw-r--r-- | hdata/test/stubs.c | 47 | ||||
-rw-r--r-- | hdata/vpd-common.c | 38 | ||||
-rw-r--r-- | hdata/vpd.c | 851 |
19 files changed, 5657 insertions, 0 deletions
diff --git a/hdata/Makefile.inc b/hdata/Makefile.inc new file mode 100644 index 0000000..44f8c86 --- /dev/null +++ b/hdata/Makefile.inc @@ -0,0 +1,8 @@ +# -*-Makefile-*- + +SUBDIRS += hdata +HDATA_OBJS = spira.o paca.o pcia.o hdif.o memory.o fsp.o iohub.o vpd.o slca.o +HDATA_OBJS += cpu-common.o vpd-common.o hostservices.o +DEVSRC_OBJ = hdata/built-in.o + +$(DEVSRC_OBJ): $(HDATA_OBJS:%=hdata/%) diff --git a/hdata/cpu-common.c b/hdata/cpu-common.c new file mode 100644 index 0000000..f08fa27 --- /dev/null +++ b/hdata/cpu-common.c @@ -0,0 +1,280 @@ +/* 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 <skiboot.h> +#include "spira.h" +#include <cpu.h> +#include <ccan/str/str.h> +#include <device.h> + +#include "hdata.h" + +struct dt_node * add_core_common(struct dt_node *cpus, + const struct sppcia_cpu_cache *cache, + const struct sppaca_cpu_timebase *tb, + uint32_t int_server, bool okay) +{ + const char *name; + struct dt_node *cpu; + uint32_t version; + uint64_t freq; + const uint8_t pa_features[] = { + 6, 0, 0xf6, 0x3f, 0xc7, 0x00, 0x80, 0xc0 }; + + printf(" Cache: I=%u D=%u/%u/%u/%u\n", + be32_to_cpu(cache->icache_size_kb), + be32_to_cpu(cache->l1_dcache_size_kb), + be32_to_cpu(cache->l2_dcache_size_kb), + be32_to_cpu(cache->l3_dcache_size_kb), + be32_to_cpu(cache->l35_dcache_size_kb)); + + /* Use the boot CPU PVR to make up a CPU name in the device-tree + * since the HDAT doesn't seem to tell.... + */ + version = mfspr(SPR_PVR); + switch(PVR_TYPE(version)) { + case PVR_TYPE_P7: + name = "PowerPC,POWER7"; + break; + case PVR_TYPE_P7P: + name = "PowerPC,POWER7+"; + break; + case PVR_TYPE_P8E: + case PVR_TYPE_P8: + name = "PowerPC,POWER8"; + break; + default: + name = "PowerPC,Unknown"; + } + + cpu = dt_new_addr(cpus, name, int_server); + assert(cpu); + dt_add_property_string(cpu, "device_type", "cpu"); + dt_add_property_string(cpu, "status", okay ? "okay" : "bad"); + dt_add_property_cells(cpu, "reg", int_server); + dt_add_property_cells(cpu, "cpu-version", version); + dt_add_property(cpu, "64-bit", NULL, 0); + dt_add_property(cpu, "32-64-bridge", NULL, 0); + dt_add_property(cpu, "graphics", NULL, 0); + dt_add_property(cpu, "general-purpose", NULL, 0); + dt_add_property_cells(cpu, "ibm,processor-segment-sizes", + 0x1c, 0x28, 0xffffffff, 0xffffffff); + dt_add_property_cells(cpu, "ibm,processor-page-sizes", + 0xc, 0x10, 0x18, 0x22); + + /* Page size encodings appear to be the same for P7 and P8 */ + dt_add_property_cells(cpu, "ibm,segment-page-sizes", + 0x0c, 0x000, 3, 0x0c, 0x0000, /* 4K seg 4k pages */ + 0x10, 0x0007, /* 4K seg 64k pages */ + 0x18, 0x0038, /* 4K seg 16M pages */ + 0x10, 0x110, 2, 0x10, 0x0001, /* 64K seg 64k pages */ + 0x18, 0x0008, /* 64K seg 16M pages */ + 0x18, 0x100, 1, 0x18, 0x0000, /* 16M seg 16M pages */ + 0x22, 0x120, 1, 0x22, 0x0003); /* 16G seg 16G pages */ + + dt_add_property(cpu, "ibm,pa-features", + pa_features, sizeof(pa_features)); + dt_add_property_cells(cpu, "ibm,slb-size", 0x20); + + dt_add_property_cells(cpu, "ibm,vmx", 0x2); + dt_add_property_cells(cpu, "ibm,dfp", 0x2); + dt_add_property_cells(cpu, "ibm,purr", 0x1); + dt_add_property_cells(cpu, "ibm,spurr", 0x1); + + /* + * Do not create "clock-frequency" if the frequency doesn't + * fit in a single cell + */ + freq = ((uint64_t)be32_to_cpu(tb->actual_clock_speed)) * 1000000ul; + if (freq <= 0xfffffffful) + dt_add_property_cells(cpu, "clock-frequency", freq); + dt_add_property_cells(cpu, "ibm,extended-clock-frequency", + hi32(freq), lo32(freq)); + + /* FIXME: Hardcoding is bad. */ + dt_add_property_cells(cpu, "timebase-frequency", 512000000); + dt_add_property_cells(cpu, "ibm,extended-timebase-frequency", + 0, 512000000); + + dt_add_property_cells(cpu, "reservation-granule-size", + be32_to_cpu(cache->reservation_size)); + + dt_add_property_cells(cpu, "d-tlb-size", + be32_to_cpu(cache->dtlb_entries)); + dt_add_property_cells(cpu, "i-tlb-size", + be32_to_cpu(cache->itlb_entries)); + /* Assume unified TLB */ + dt_add_property_cells(cpu, "tlb-size", + be32_to_cpu(cache->dtlb_entries)); + dt_add_property_cells(cpu, "d-tlb-sets", + be32_to_cpu(cache->dtlb_assoc_sets)); + dt_add_property_cells(cpu, "i-tlb-sets", + be32_to_cpu(cache->itlb_assoc_sets)); + dt_add_property_cells(cpu, "tlb-sets", + be32_to_cpu(cache->dtlb_assoc_sets)); + + dt_add_property_cells(cpu, "d-cache-block-size", + be32_to_cpu(cache->dcache_block_size)); + dt_add_property_cells(cpu, "i-cache-block-size", + be32_to_cpu(cache->icache_block_size)); + dt_add_property_cells(cpu, "d-cache-size", + be32_to_cpu(cache->l1_dcache_size_kb)*1024); + dt_add_property_cells(cpu, "i-cache-size", + be32_to_cpu(cache->icache_size_kb)*1024); + dt_add_property_cells(cpu, "i-cache-sets", + be32_to_cpu(cache->icache_assoc_sets)); + dt_add_property_cells(cpu, "d-cache-sets", + be32_to_cpu(cache->dcache_assoc_sets)); + + if (cache->icache_line_size != cache->icache_block_size) + dt_add_property_cells(cpu, "i-cache-line-size", + be32_to_cpu(cache->icache_line_size)); + if (cache->l1_dcache_line_size != cache->dcache_block_size) + dt_add_property_cells(cpu, "d-cache-line-size", + be32_to_cpu(cache->l1_dcache_line_size)); + return cpu; +} + +void add_core_attr(struct dt_node *cpu, uint32_t attr) +{ + if (attr & CPU_ATTR_UNIFIED_PL1) + dt_add_property(cpu, "cache-unified", NULL, 0); + if (attr & CPU_ATTR_SPLIT_TLB) + dt_add_property(cpu, "tlb-split", NULL, 0); + if (attr & CPU_ATTR_TLBIA) + dt_add_property(cpu, "tlbia", NULL, 0); + if (attr & CPU_ATTR_PERF_MONITOR) + dt_add_property_cells(cpu, "performance-monitor", 0, 1); + if (attr & CPU_ATTR_EXTERN_CONT) + dt_add_property(cpu, "external-control", NULL, 0); +} + +static struct dt_node *create_cache_node(struct dt_node *cpus, + const struct sppcia_cpu_cache *cache, + const char *name, uint32_t unit_addr, + int okay) +{ + struct dt_node *node; + + node = dt_new_addr(cpus, name, unit_addr); + assert(node); + + dt_add_property_string(node, "device_type", "cache"); + dt_add_property_cells(node, "reg", unit_addr); + dt_add_property_string(node, "status", okay ? "okay" : "bad"); + dt_add_property(node, "cache-unified", NULL, 0); + + /* Assume cache associavitity sets is same for L2, L3 and L3.5 */ + dt_add_property_cells(node, "d-cache-sets", + be32_to_cpu(cache->l2_cache_assoc_sets)); + dt_add_property_cells(node, "i-cache-sets", + be32_to_cpu(cache->l2_cache_assoc_sets)); + + return node; +} + +static struct dt_node *l35_cache_node(struct dt_node *cpus, + const struct sppcia_cpu_cache *cache, + uint32_t unit_addr, int okay) +{ + struct dt_node *node; + + node = create_cache_node(cpus, cache, "l35-cache", unit_addr, okay); + + dt_add_property_cells(node, "d-cache-size", + be32_to_cpu(cache->l35_dcache_size_kb) * 1024); + dt_add_property_cells(node, "i-cache-size", + be32_to_cpu(cache->l35_dcache_size_kb) * 1024); + + if (cache->icache_line_size != cache->icache_block_size) + dt_add_property_cells(node, "i-cache-line-size", + be32_to_cpu(cache->icache_line_size)); + if (cache->l35_cache_line_size != cache->dcache_block_size) + dt_add_property_cells(node, "d-cache-line-size", + be32_to_cpu(cache->l35_cache_line_size)); + + return node; +} + +static struct dt_node *l3_cache_node(struct dt_node *cpus, + const struct sppcia_cpu_cache *cache, + uint32_t unit_addr, int okay) +{ + struct dt_node *node; + + node = create_cache_node(cpus, cache, "l3-cache", unit_addr, okay); + + dt_add_property_cells(node, "d-cache-size", + be32_to_cpu(cache->l3_dcache_size_kb) * 1024); + dt_add_property_cells(node, "i-cache-size", + be32_to_cpu(cache->l3_dcache_size_kb) * 1024); + + if (cache->icache_line_size != cache->icache_block_size) + dt_add_property_cells(node, "i-cache-line-size", + be32_to_cpu(cache->icache_line_size)); + if (cache->l3_line_size != cache->dcache_block_size) + dt_add_property_cells(node, "d-cache-line-size", + be32_to_cpu(cache->l3_line_size)); + + return node; +} + +static struct dt_node *l2_cache_node(struct dt_node *cpus, + const struct sppcia_cpu_cache *cache, + uint32_t unit_addr, int okay) +{ + struct dt_node *node; + + node = create_cache_node(cpus, cache, "l2-cache", unit_addr, okay); + + dt_add_property_cells(node, "d-cache-size", + be32_to_cpu(cache->l2_dcache_size_kb) * 1024); + dt_add_property_cells(node, "i-cache-size", + be32_to_cpu(cache->l2_dcache_size_kb) * 1024); + + if (cache->icache_line_size != cache->icache_block_size) + dt_add_property_cells(node, "i-cache-line-size", + be32_to_cpu(cache->icache_line_size)); + if (cache->l2_line_size != cache->dcache_block_size) + dt_add_property_cells(node, "d-cache-line-size", + be32_to_cpu(cache->l2_line_size)); + + return node; +} + +uint32_t add_core_cache_info(struct dt_node *cpus, + const struct sppcia_cpu_cache *cache, + uint32_t int_server, int okay) +{ + struct dt_node *l2_node, *l3_node, *l35_node; + uint32_t unit_addr; + + /* Use Processor Interrupt Line to genarate cache unit address */ + unit_addr = 0x20 << 24 | int_server; + l2_node = l2_cache_node(cpus, cache, unit_addr, okay); + + unit_addr = 0x30 << 24 | int_server; + l3_node = l3_cache_node(cpus, cache, unit_addr, okay); + /* Represents the next level of cache in the memory hierarchy */ + dt_add_property_cells(l2_node, "l2-cache", l3_node->phandle); + + if (be32_to_cpu(cache->l35_dcache_size_kb)) { + unit_addr = 0x35 << 24 | int_server; + l35_node = l35_cache_node(cpus, cache, unit_addr, okay); + dt_add_property_cells(l3_node, "l2-cache", l35_node->phandle); + } + + return l2_node->phandle; +} diff --git a/hdata/fsp.c b/hdata/fsp.c new file mode 100644 index 0000000..cf6bc96 --- /dev/null +++ b/hdata/fsp.c @@ -0,0 +1,200 @@ +/* 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 <device.h> +#include "spira.h" +#include <cpu.h> +#include <memory.h> +#include <vpd.h> +#include <ccan/str/str.h> +#include <device_tree.h> +#include <interrupts.h> + +#include "hdata.h" + +static struct dt_node *fsp_create_node(const void *spss, int i, + struct dt_node *parent) +{ + const struct spss_sp_impl *sp_impl; + struct dt_node *node; + unsigned int mask; + + /* Find an check the SP Implementation structure */ + sp_impl = HDIF_get_idata(spss, SPSS_IDATA_SP_IMPL, NULL); + if (!CHECK_SPPTR(sp_impl)) { + prerror("FSP #%d: SPSS/SP_Implementation not found !\n", i); + return NULL; + } + + printf("FSP #%d: FSP HW version %d, SW version %d, chip DD%d.%d\n", + i, sp_impl->hw_version, sp_impl->sw_version, + sp_impl->chip_version >> 4, sp_impl->chip_version & 0xf); + mask = SPSS_SP_IMPL_FLAGS_INSTALLED | SPSS_SP_IMPL_FLAGS_FUNCTIONAL; + if ((be16_to_cpu(sp_impl->func_flags) & mask) != mask) { + prerror("FSP #%d: FSP not installed or not functional\n", i); + return NULL; + } + + node = dt_new_addr(parent, "fsp", i); + assert(node); + dt_add_property_cells(node, "reg", i); + + if (sp_impl->hw_version == 1) { + dt_add_property_strings(node, "compatible", "ibm,fsp", + "ibm,fsp1"); + /* Offset into the FSP MMIO space where the mailbox + * registers are */ + /* seen in the FSP1 spec */ + dt_add_property_cells(node, "reg-offset", 0xb0016000); + } else if (sp_impl->hw_version == 2) { + dt_add_property_strings(node, "compatible", "ibm,fsp", + "ibm,fsp2"); + dt_add_property_cells(node, "reg-offset", 0xb0011000); + } + dt_add_property_cells(node, "hw-version", sp_impl->hw_version); + dt_add_property_cells(node, "sw-version", sp_impl->sw_version); + + if (be16_to_cpu(sp_impl->func_flags) & SPSS_SP_IMPL_FLAGS_PRIMARY) + dt_add_property(node, "primary", NULL, 0); + + return node; +} + +static uint32_t fsp_create_link(const struct spss_iopath *iopath, int index, + int fsp_index) +{ + struct dt_node *node; + const char *ststr; + bool current = false; + bool working = false; + uint32_t chip_id; + + switch(iopath->psi.link_status) { + case SPSS_IO_PATH_PSI_LINK_BAD_FRU: + ststr = "Broken"; + break; + case SPSS_IO_PATH_PSI_LINK_CURRENT: + ststr = "Active"; + current = working = true; + break; + case SPSS_IO_PATH_PSI_LINK_BACKUP: + ststr = "Backup"; + working = true; + break; + default: + ststr = "Unknown"; + } + printf("FSP #%d: IO PATH %d is %s PSI Link, GXHB at %llx\n", + fsp_index, index, ststr, (long long)iopath->psi.gxhb_base); + + chip_id = pcid_to_chip_id(iopath->psi.proc_chip_id); + node = dt_find_compatible_node_on_chip(dt_root, NULL, "ibm,psihb-x", + chip_id); + if (!node) { + prerror("FSP #%d: Can't find psihb node for link %d\n", + fsp_index, index); + } else { + if (current) + dt_add_property(node, "boot-link", NULL, 0); + dt_add_property_strings(node, "status", working ? "ok" : "bad"); + } + + return chip_id; +} + +static void fsp_create_links(const void *spss, int index, + struct dt_node *fsp_node) +{ + uint32_t *links = NULL; + unsigned int i, lp, lcount = 0; + int count; + + count = HDIF_get_iarray_size(spss, SPSS_IDATA_SP_IOPATH); + if (count < 0) { + prerror("FSP #%d: Can't find IO PATH array size !\n", index); + return; + } + printf("FSP #%d: Found %d IO PATH\n", index, count); + + /* Iterate all links */ + for (i = 0; i < count; i++) { + const struct spss_iopath *iopath; + unsigned int iopath_sz; + uint32_t chip; + + iopath = HDIF_get_iarray_item(spss, SPSS_IDATA_SP_IOPATH, + i, &iopath_sz); + if (!CHECK_SPPTR(iopath)) { + prerror("FSP #%d: Can't find IO PATH %d\n", index, i); + break; + } + if (iopath->iopath_type != SPSS_IOPATH_TYPE_PSI) { + prerror("FSP #%d: Unsupported IO PATH %d type 0x%04x\n", + index, i, iopath->iopath_type); + continue; + } + + chip = fsp_create_link(iopath, i, index); + lp = lcount++; + links = realloc(links, 4 * lcount); + links[lp] = chip; + } + if (links) + dt_add_property(fsp_node, "ibm,psi-links", links, lcount * 4); +} + +void fsp_parse(void) +{ + const void *base_spss, *spss; + struct dt_node *fsp_root, *fsp_node; + int i; + + /* + * Note on DT representation of the PSI links and FSPs: + * + * We create a XSCOM node for each PSI host bridge(one per chip), + * + * This is done in spira.c + * + * We do not create the /psi MMIO variant at this stage, it will + * be added by the psi driver in skiboot. + * + * We do not put the FSP(s) as children of these. Instead, we create + * a top-level /fsps node with the FSPs as children. + * + * Each FSP then has a "links" property which is an array of chip IDs + */ + + /* Find SPSS in SPIRA */ + base_spss = get_hdif(&spira.ntuples.sp_subsys, SPSS_HDIF_SIG); + if (!base_spss) { + printf("FSP: No SPSS in SPIRA !\n"); + return; + } + + fsp_root = dt_new(dt_root, "fsps"); + assert(fsp_root); + dt_add_property_cells(fsp_root, "#address-cells", 1); + dt_add_property_cells(fsp_root, "#size-cells", 0); + + /* Iterate FSPs in SPIRA */ + for_each_ntuple_idx(&spira.ntuples.sp_subsys, spss, i, SPSS_HDIF_SIG) { + fsp_node = fsp_create_node(spss, i, fsp_root); + if (fsp_node) + fsp_create_links(spss, i, fsp_node); + } +} + diff --git a/hdata/hdata.h b/hdata/hdata.h new file mode 100644 index 0000000..b9b7721 --- /dev/null +++ b/hdata/hdata.h @@ -0,0 +1,49 @@ +/* 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. + */ + +#ifndef __HDATA_H +#define __HDATA_H + +struct dt_node; + +extern void paca_parse(void); +extern bool pcia_parse(void); +extern void fsp_parse(void); +extern void io_parse(void); +extern struct dt_node *dt_add_vpd_node(const struct HDIF_common_hdr *hdr, + int indx_fru, int indx_vpd); +extern void vpd_parse(void); + +extern struct dt_node *find_xscom_for_chip(uint32_t chip_id); +extern uint32_t pcid_to_chip_id(uint32_t proc_chip_id); + +extern struct dt_node *add_core_common(struct dt_node *cpus, + const struct sppaca_cpu_cache *cache, + const struct sppaca_cpu_timebase *tb, + uint32_t int_server, bool okay); +extern void add_core_attr(struct dt_node *cpu, uint32_t attr); +extern uint32_t add_core_cache_info(struct dt_node *cpus, + const struct sppcia_cpu_cache *cache, + uint32_t int_server, int okay); +extern const struct slca_entry *slca_get_entry(uint16_t slca_index); +extern const char *slca_get_vpd_name(uint16_t slca_index); +extern const char *slca_get_loc_code_index(uint16_t slca_index); +extern void slca_vpd_add_loc_code(struct dt_node *node, uint16_t slca_index); + +extern bool hservices_from_hdat(const void *fdt, size_t size); + +#endif /* __HDATA_H */ + diff --git a/hdata/hdif.c b/hdata/hdif.c new file mode 100644 index 0000000..916b4dd --- /dev/null +++ b/hdata/hdif.c @@ -0,0 +1,140 @@ +/* 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 "hdif.h" + +const void *HDIF_get_idata(const struct HDIF_common_hdr *hdif, unsigned int di, + unsigned int *size) +{ + const struct HDIF_common_hdr *hdr = hdif; + const struct HDIF_idata_ptr *iptr; + + if (hdr->d1f0 != BE16_TO_CPU(0xd1f0)) { + prerror("HDIF: Bad header format !\n"); + return NULL; + } + + if (di >= be16_to_cpu(hdr->idptr_count)) { + prerror("HDIF: idata index out of range !\n"); + return NULL; + } + + iptr = (void *)hdif + be32_to_cpu(hdr->idptr_off) + + di * sizeof(struct HDIF_idata_ptr); + + if (size) + *size = be32_to_cpu(iptr->size); + + return (void *)hdif + be32_to_cpu(iptr->offset); +} + +const void *HDIF_get_iarray_item(const struct HDIF_common_hdr *hdif, + unsigned int di, unsigned int ai, + unsigned int *size) +{ + const struct HDIF_array_hdr *ahdr; + unsigned int asize; + const void *arr; + + arr = HDIF_get_idata(hdif, di, &asize); + if (!arr) + return NULL; + + if (asize < sizeof(struct HDIF_array_hdr)) { + prerror("HDIF: idata block too small for array !\n"); + return NULL; + } + + ahdr = arr; + + if (ai >= be32_to_cpu(ahdr->ecnt)) { + prerror("HDIF: idata array index out of range !\n"); + return NULL; + } + + if (size) + *size = be32_to_cpu(ahdr->eactsz); + + return arr + be32_to_cpu(ahdr->offset) + ai * be32_to_cpu(ahdr->esize); +} + +int HDIF_get_iarray_size(const struct HDIF_common_hdr *hdif, unsigned int di) +{ + const struct HDIF_array_hdr *ahdr; + unsigned int asize; + const void *arr; + + arr = HDIF_get_idata(hdif, di, &asize); + if (!arr) + return -1; + + if (asize < sizeof(struct HDIF_array_hdr)) { + prerror("HDIF: idata block too small for array !\n"); + return -1; + } + + ahdr = arr; + return be32_to_cpu(ahdr->ecnt); +} + +struct HDIF_child_ptr * +HDIF_child_arr(const struct HDIF_common_hdr *hdif, unsigned int idx) +{ + struct HDIF_child_ptr *children; + + children = (void *)hdif + be32_to_cpu(hdif->child_off); + + if (idx >= be16_to_cpu(hdif->child_count)) { + prerror("HDIF: child array idx out of range!\n"); + return NULL; + } + + return &children[idx]; +} + +struct HDIF_common_hdr *HDIF_child(const struct HDIF_common_hdr *hdif, + const struct HDIF_child_ptr *child, + unsigned int idx, + const char *eyecatcher) +{ + void *base = (void *)hdif; + struct HDIF_common_hdr *ret; + long child_off; + + /* child must be in hdif's child array */ + child_off = (void *)child - (base + be32_to_cpu(hdif->child_off)); + assert(child_off % sizeof(struct HDIF_child_ptr) == 0); + assert(child_off / sizeof(struct HDIF_child_ptr) + < be16_to_cpu(hdif->child_count)); + + assert(idx < be32_to_cpu(child->count)); + + if (be32_to_cpu(child->size) < sizeof(struct HDIF_common_hdr)) { + prerror("HDIF: %s child #%i too small: %u\n", + eyecatcher, idx, be32_to_cpu(child->size)); + return NULL; + } + + ret = base + be32_to_cpu(child->offset) + + be32_to_cpu(child->size) * idx; + if (!HDIF_check(ret, eyecatcher)) { + prerror("HDIF: %s child #%i bad type\n", + eyecatcher, idx); + return NULL; + } + + return ret; +} diff --git a/hdata/hdif.h b/hdata/hdif.h new file mode 100644 index 0000000..fad7454 --- /dev/null +++ b/hdata/hdif.h @@ -0,0 +1,141 @@ +/* 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. + */ + +#ifndef __HDIF_H +#define __HDIF_H + +#include <skiboot.h> +#include <types.h> +#include <ccan/endian/endian.h> + +struct HDIF_common_hdr { + __be16 d1f0; /* 0xd1f0 */ + char id[6]; /* eye catcher string */ + __be16 instnum; /* instance number */ + __be16 version; /* version */ + __be32 total_len; /* total structure length */ + __be32 hdr_len; /* header length (currently 0x20) */ + __be32 idptr_off; /* offset to idata pointers */ + __be16 idptr_count; /* number of idata pointers */ + __be16 child_count; /* number of child structures */ + __be32 child_off; /* offset to child structures array */ +} __packed __align(0x10); + +struct HDIF_idata_ptr { + __be32 offset; + __be32 size; +} __packed __align(0x8); + +struct HDIF_array_hdr { + __be32 offset; + __be32 ecnt; + __be32 esize; + __be32 eactsz; +} __packed __align(0x10); + +struct HDIF_child_ptr { + __be32 offset; + __be32 size; + __be32 count; +} __packed; + +#define HDIF_HDR_LEN (sizeof(struct HDIF_common_hdr)) +#define HDIF_ARRAY_OFFSET (sizeof(struct HDIF_array_hdr)) + +#define HDIF_ID(_id) .d1f0 = CPU_TO_BE16(0xd1f0), .id = _id + +#define HDIF_SIMPLE_HDR(id, vers, type) \ +{ \ + HDIF_ID(id), \ + .instnum = CPU_TO_BE16(0), \ + .version = CPU_TO_BE16(vers), \ + .total_len = CPU_TO_BE32(sizeof(type)), \ + .hdr_len = CPU_TO_BE32(HDIF_HDR_LEN), \ + .idptr_off = CPU_TO_BE32(HDIF_HDR_LEN), \ + .idptr_count = CPU_TO_BE16(1), \ + .child_count = CPU_TO_BE16(0), \ + .child_off = CPU_TO_BE32(0), \ +} + +#define HDIF_IDATA_PTR(_offset, _size) \ +{ \ + .offset = CPU_TO_BE32(_offset), \ + .size = _size, \ +} + +static inline bool HDIF_check(const void *hdif, const char id[]) +{ + const struct HDIF_common_hdr *hdr = hdif; + + return hdr->d1f0 == CPU_TO_BE16(0xd1f0) && + memcmp(hdr->id, id, sizeof(hdr->id)) == 0; +} + +/* HDIF_get_idata - Get a pointer to internal data block + * + * @hdif : HDIF structure pointer + * @di : Index of the idata pointer + * @size : Return the data size (or NULL if ignored) + */ +extern const void *HDIF_get_idata(const struct HDIF_common_hdr *hdif, + unsigned int di, + unsigned int *size); + +/* HDIF_get_iarray - Get a pointer to an elemnt of an internal data array + * + * @hdif : HDIF structure pointer + * @di : Index of the idata pointer + * @ai : Index in the resulting array + * @size : Return the entry actual size (or NULL if ignored) + */ +extern const void *HDIF_get_iarray_item(const struct HDIF_common_hdr *hdif, + unsigned int di, + unsigned int ai, unsigned int *size); + +/* HDIF_get_iarray_size - Get the number of elements of an internal data array + * + * @hdif : HDIF structure pointer + * @di : Index of the idata pointer + * + * A negative result means an error + */ +extern int HDIF_get_iarray_size(const struct HDIF_common_hdr *hdif, + unsigned int di); + +/* HDIF_child_arr - Get a child array from this HDIF. + * + * @hdif : HDIF structure pointer + * @idx : the child to get + * + * NULL means an error (not that many children). + */ +extern struct HDIF_child_ptr * +HDIF_child_arr(const struct HDIF_common_hdr *hdif, unsigned int idx); + +/* HDIF_child - Deref a child_ptr entry. + * + * @hdif : HDIF structure pointer + * @child : the child returned from HDIF_child_arr + * @idx : the index of the child to get (< child->count). + * @eyecatcher: the 6-char ID expected for this child. + * + * NULL means an error. + */ +extern struct HDIF_common_hdr *HDIF_child(const struct HDIF_common_hdr *hdif, + const struct HDIF_child_ptr *child, + unsigned int idx, + const char *eyecatcher); +#endif /* __HDIF_H */ diff --git a/hdata/hostservices.c b/hdata/hostservices.c new file mode 100644 index 0000000..ff2edbe --- /dev/null +++ b/hdata/hostservices.c @@ -0,0 +1,96 @@ +/* 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 <stdint.h> +#include <stdlib.h> +#include <stdio.h> + +#include <libfdt/libfdt.h> +#include <skiboot.h> +#include <device.h> +#include <compiler.h> +#include <hostservices.h> + +#include "spira.h" +#include "hdata.h" + +static void merge_property(const struct dt_node *src_root, + struct dt_node *dst_root, + const char *name) +{ + const struct dt_property *src; + struct dt_property *dst; + + /* Nothing to merge if old one doesn't exist. */ + src = dt_find_property(src_root, name); + if (!src) + return; + + /* Just create a new one if there's none in dst. */ + dst = __dt_find_property(dst_root, name); + if (!dst) { + dt_add_property(dst_root, name, src->prop, src->len); + return; + } + + /* Append src to dst. */ + dt_resize_property(&dst, dst->len + src->len); + memcpy(dst->prop + dst->len, src->prop, src->len); + dst->len += src->len; +} + +static void hservice_parse_dt_tree(const struct dt_node *src) +{ + const struct dt_property *sprop; + + /* Copy/merge reserved names & ranges properties. */ + list_for_each(&src->properties, sprop, list) { + if (streq(sprop->name, "reserved-names") || + streq(sprop->name, "reserved-ranges") || + streq(sprop->name, "ibm,enabled-idle-states")) + merge_property(src, dt_root, sprop->name); + } +} + +/* Get host services information from hdat. */ +bool hservices_from_hdat(const void *fdt, size_t size) +{ + int err; + struct dt_node *hservices; + + printf("HBRT: Found mini-DT at 0x%p size: 0x%08lx\n", fdt, size); + + /* For diagnostic purposes, we copy the whole blob over */ + dt_add_property(dt_root, "ibm,hbrt-mini-fdt", fdt, size); + + /* Parse & extract relevant properties */ + err = fdt_check_header(fdt); + if (err) { + prerror("HBRT: fdt blob %p hdr error %d\n", fdt, err); + return false; + } + + hservices = dt_new_root("ibm,hostservices"); + err = dt_expand_node(hservices, fdt, 0); + if (err < 0) { + prerror("HBRT: fdt blob %p parse error %d\n", fdt, err); + return false; + } + + hservice_parse_dt_tree(hservices); + return true; +} + diff --git a/hdata/iohub.c b/hdata/iohub.c new file mode 100644 index 0000000..0dbec36 --- /dev/null +++ b/hdata/iohub.c @@ -0,0 +1,715 @@ +/* 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 <skiboot.h> +#include "spira.h" +#include <cpu.h> +#include <fsp.h> +#include <opal.h> +#include <ccan/str/str.h> +#include <ccan/array_size/array_size.h> +#include <device.h> +#include <p5ioc2.h> +#include <p7ioc.h> +#include <vpd.h> + +#include "hdata.h" + +static void io_add_common(struct dt_node *hn, const struct cechub_io_hub *hub) +{ + dt_add_property_cells(hn, "#address-cells", 2); + dt_add_property_cells(hn, "#size-cells", 2); + dt_add_property_cells(hn, "ibm,buid-ext", hub->buid_ext); + dt_add_property_cells(hn, "ibm,chip-id", + pcid_to_chip_id(hub->proc_chip_id)); + dt_add_property_cells(hn, "ibm,gx-index", hub->gx_index); + dt_add_property_cells(hn, "revision", hub->ec_level); + + /* Instead of exposing the GX BARs as separate ranges as we *should* + * do in an ideal world, we just create a pass-through ranges and + * we use separate properties for the BARs. + * + * This is hackish but will do for now and avoids us having to + * do too complex ranges property parsing + */ + dt_add_property(hn, "ranges", NULL, 0); + dt_add_property_cells(hn, "ibm,gx-bar-1", + hi32(hub->gx_ctrl_bar1), lo32(hub->gx_ctrl_bar1)); + dt_add_property_cells(hn, "ibm,gx-bar-2", + hi32(hub->gx_ctrl_bar2), lo32(hub->gx_ctrl_bar2)); + + /* Add presence detect if valid */ + if (hub->flags & CECHUB_HUB_FLAG_FAB_BR0_PDT) + dt_add_property_cells(hn, "ibm,br0-presence-detect", + hub->fab_br0_pdt); + if (hub->flags & CECHUB_HUB_FLAG_FAB_BR1_PDT) + dt_add_property_cells(hn, "ibm,br1-presence-detect", + hub->fab_br1_pdt); +} + +static bool io_get_lx_info(const void *kwvpd, unsigned int kwvpd_sz, + int lx_idx, struct dt_node *hn) +{ + const void *lxr; + char recname[5]; + + /* Find LXRn, where n is the index passed in*/ + strcpy(recname, "LXR0"); + recname[3] += lx_idx; + lxr = vpd_find(kwvpd, kwvpd_sz, recname, "LX", NULL); + if (!lxr) { + /* Not found, try VINI */ + lxr = vpd_find(kwvpd, kwvpd_sz, "VINI", + "LX", NULL); + if (lxr) + lx_idx = VPD_LOAD_LXRN_VINI; + } + if (!lxr) { + printf("CEC: LXR%x not found !\n", lx_idx); + return false; + } + + printf("CEC: LXRn=%d LXR=%016lx\n", lx_idx, + lxr ? *(unsigned long *)lxr : 0); + printf("CEC: LX Info added to %llx\n", (long long)hn); + + /* Add the LX info */ + if (!dt_has_node_property(hn, "ibm,vpd-lx-info", NULL)) { + dt_add_property_cells(hn, "ibm,vpd-lx-info", + lx_idx, + ((uint32_t *)lxr)[0], + ((uint32_t *)lxr)[1]); + } + + return true; +} + + +static void io_get_loc_code(const void *sp_iohubs, struct dt_node *hn, const char *prop_name) +{ + const struct spira_fru_id *fru_id; + unsigned int fru_id_sz; + char loc_code[LOC_CODE_SIZE + 1]; + const char *slca_loc_code; + + /* Find SLCA Index */ + fru_id = HDIF_get_idata(sp_iohubs, CECHUB_FRU_ID_DATA, &fru_id_sz); + if (fru_id) { + memset(loc_code, 0, sizeof(loc_code)); + + /* Find LOC Code from SLCA Index */ + slca_loc_code = slca_get_loc_code_index(fru_id->slca_index); + if (slca_loc_code) { + strncpy(loc_code, slca_loc_code, LOC_CODE_SIZE); + if (!dt_has_node_property(hn, prop_name, NULL)) { + dt_add_property(hn, prop_name, loc_code, + strlen(loc_code) + 1); + } + printf("CEC: %s: %s (SLCA rsrc 0x%x)\n", + prop_name, loc_code, be16_to_cpu(fru_id->rsrc_id)); + } else { + printf("CEC: SLCA Loc not found: index %d\n", + fru_id->slca_index); + } + } else { + printf("CEC: Hub FRU ID not found...\n"); + } +} + +static struct dt_node *io_add_p5ioc2(const struct cechub_io_hub *hub, + const void *sp_iohubs) +{ + struct dt_node *hn; + uint64_t reg[2]; + + const void *kwvpd; + unsigned int kwvpd_sz; + + printf(" GX#%d BUID_Ext = 0x%x\n", + be32_to_cpu(hub->gx_index), + be32_to_cpu(hub->buid_ext)); + printf(" GX BAR 0 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar0)); + printf(" GX BAR 1 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar1)); + printf(" GX BAR 2 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar2)); + printf(" GX BAR 3 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar3)); + printf(" GX BAR 4 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar4)); + + /* We assume SBAR == GX0 + some hard coded offset */ + reg[0] = cleanup_addr(be64_to_cpu(hub->gx_ctrl_bar0) + P5IOC2_REGS_OFFSET); + reg[1] = 0x2000000; + + hn = dt_new_addr(dt_root, "io-hub", reg[0]); + if (!hn) + return NULL; + + dt_add_property(hn, "reg", reg, sizeof(reg)); + dt_add_property_strings(hn, "compatible", "ibm,p5ioc2"); + + kwvpd = HDIF_get_idata(sp_iohubs, CECHUB_ASCII_KEYWORD_VPD, &kwvpd_sz); + if (kwvpd && kwvpd != sp_iohubs) { + /* + * XX We don't know how to properly find the LXRn + * record so for now we'll just try LXR0 and if not + * found, we try LXR1 + */ + if (!io_get_lx_info(kwvpd, kwvpd_sz, 0, hn)) + io_get_lx_info(kwvpd, kwvpd_sz, 1, hn); + } else + printf("CEC: P5IOC2 Keywords not found.\n"); + + /* Get slots base loc code */ + io_get_loc_code(sp_iohubs, hn, "ibm,io-base-loc-code"); + + return hn; +} + +static struct dt_node *io_add_p7ioc(const struct cechub_io_hub *hub, + const void *sp_iohubs) +{ + struct dt_node *hn; + uint64_t reg[2]; + + const void *kwvpd; + unsigned int kwvpd_sz; + + printf(" GX#%d BUID_Ext = 0x%x\n", + be32_to_cpu(hub->gx_index), + be32_to_cpu(hub->buid_ext)); + printf(" GX BAR 0 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar0)); + printf(" GX BAR 1 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar1)); + printf(" GX BAR 2 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar2)); + printf(" GX BAR 3 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar3)); + printf(" GX BAR 4 = 0x%016llx\n", be64_to_cpu(hub->gx_ctrl_bar4)); + + /* We only know about memory map 1 */ + if (hub->mem_map_vers != 1) { + prerror("P7IOC: Unknown memory map %d\n", hub->mem_map_vers); + /* We try to continue anyway ... */ + } + + reg[0] = cleanup_addr(be64_to_cpu(hub->gx_ctrl_bar1)); + reg[1] = 0x2000000; + + hn = dt_new_addr(dt_root, "io-hub", reg[0]); + if (!hn) + return NULL; + + dt_add_property(hn, "reg", reg, sizeof(reg)); + dt_add_property_strings(hn, "compatible", "ibm,p7ioc", "ibm,ioda-hub"); + + kwvpd = HDIF_get_idata(sp_iohubs, CECHUB_ASCII_KEYWORD_VPD, &kwvpd_sz); + if (kwvpd && kwvpd != sp_iohubs) { + /* + * XX We don't know how to properly find the LXRn + * record so for now we'll just try LXR0 and if not + * found, we try LXR1 + */ + if (!io_get_lx_info(kwvpd, kwvpd_sz, 0, hn)) + io_get_lx_info(kwvpd, kwvpd_sz, 1, hn); + } else + printf("CEC: P7IOC Keywords not found.\n"); + + io_get_loc_code(sp_iohubs, hn, "ibm,io-base-loc-code"); + + return hn; +} + +static struct dt_node *io_add_phb3(const struct cechub_io_hub *hub, + const struct HDIF_common_hdr *sp_iohubs, + unsigned int index, struct dt_node *xcom, + unsigned int pe_xscom, + unsigned int pci_xscom, + unsigned int spci_xscom) +{ + struct dt_node *pbcq; + uint32_t reg[6]; + unsigned int hdif_vers; + + /* Get HDIF version */ + hdif_vers = be16_to_cpu(sp_iohubs->version); + + /* Create PBCQ node under xscom */ + pbcq = dt_new_addr(xcom, "pbcq", pe_xscom); + if (!pbcq) + return NULL; + + /* "reg" property contains in order the PE, PCI and SPCI XSCOM + * addresses + */ + reg[0] = pe_xscom; + reg[1] = 0x20; + reg[2] = pci_xscom; + reg[3] = 0x05; + reg[4] = spci_xscom; + reg[5] = 0x15; + dt_add_property(pbcq, "reg", reg, sizeof(reg)); + + /* A couple more things ... */ + dt_add_property_strings(pbcq, "compatible", "ibm,power8-pbcq"); + dt_add_property_cells(pbcq, "ibm,phb-index", index); + dt_add_property_cells(pbcq, "ibm,hub-id", be16_to_cpu(hub->hub_num)); + + /* The loc code of the PHB itself is different from the base + * loc code of the slots (It's actually the DCM's loc code). + */ + io_get_loc_code(sp_iohubs, pbcq, "ibm,loc-code"); + + /* We indicate that this is an IBM setup, which means that + * the presence detect A/B bits are meaningful. So far we + * don't know whether they make any sense on customer setups + * so we only set that when booting with HDAT + */ + dt_add_property(pbcq, "ibm,use-ab-detect", NULL, 0); + + /* HDAT spec has these in version 0x6A and later */ + if (hdif_vers >= 0x6a) { + u64 eq0 = be64_to_cpu(hub->phb_lane_eq[index][0]); + u64 eq1 = be64_to_cpu(hub->phb_lane_eq[index][1]); + u64 eq2 = be64_to_cpu(hub->phb_lane_eq[index][2]); + u64 eq3 = be64_to_cpu(hub->phb_lane_eq[index][3]); + dt_add_property_cells(pbcq, "ibm,lane-eq", + hi32(eq0), lo32(eq0), + hi32(eq1), lo32(eq1), + hi32(eq2), lo32(eq2), + hi32(eq3), lo32(eq3)); + } + + /* Currently we only create a PBCQ node, the actual PHB nodes + * will be added by sapphire later on. + */ + return pbcq; +} + +static struct dt_node *io_add_murano(const struct cechub_io_hub *hub, + const struct HDIF_common_hdr *sp_iohubs) +{ + struct dt_node *xscom; + unsigned int i, chip_id; + + chip_id = pcid_to_chip_id(be32_to_cpu(hub->proc_chip_id)); + + printf("CEC: HW CHIP=0x%x, HW TOPO=0x%04x\n", chip_id, + be16_to_cpu(hub->hw_topology)); + + xscom = find_xscom_for_chip(chip_id); + if (!xscom) { + prerror("MURANO: Can't find XSCOM for chip %d\n", chip_id); + return NULL; + } + + /* Create PHBs, max 3 */ + for (i = 0; i < 3; i++) { + if (hub->fab_br0_pdt & (0x80 >> i)) + /* XSCOM addresses for murano DD1.0 */ + io_add_phb3(hub, sp_iohubs, i, xscom, + 0x02012000 + (i * 0x400), + 0x09012000 + (i * 0x400), + 0x09013c00 + (i * 0x40)); + } + + /* HACK: We return the XSCOM device for the VPD info */ + return xscom; +} + +static void io_add_p8_cec_vpd(const struct HDIF_common_hdr *sp_iohubs) +{ + const struct HDIF_child_ptr *iokids; + const void *iokid; + const void *kwvpd; + unsigned int kwvpd_sz; + + /* P8 LXR0 kept in IO KID Keyword VPD */ + iokids = HDIF_child_arr(sp_iohubs, CECHUB_CHILD_IO_KIDS); + if (!CHECK_SPPTR(iokids)) { + printf("CEC: No IOKID child array !\n"); + return; + } + if (!iokids->count) { + printf("CEC: IOKID count is 0 !\n"); + return; + } + if (iokids->count > 1) { + printf("CEC: WARNING ! More than 1 IO KID !!! (%d)\n", + iokids->count); + /* Ignoring the additional ones */ + } + + iokid = HDIF_child(sp_iohubs, iokids, 0, "IO KID"); + if (!iokid) { + printf("CEC: No IO KID structure in child array !\n"); + return; + } + + /* Grab base location code for slots */ + io_get_loc_code(iokid, dt_root, "ibm,io-base-loc-code"); + + kwvpd = HDIF_get_idata(iokid, CECHUB_ASCII_KEYWORD_VPD, &kwvpd_sz); + if (!kwvpd) { + printf("CEC: No VPD entry in IO KID !\n"); + return; + } + + /* Grab LX load info */ + io_get_lx_info(kwvpd, kwvpd_sz, 0, dt_root); +} + +static struct dt_node *io_add_hea(const struct cechub_io_hub *hub, + const void *sp_io) +{ + struct dt_node *np, *gnp; + uint64_t reg[2]; + unsigned int i, vpd_sz; + uint8_t kw_sz; + const void *iokid, *vpd, *ccin; + const uint8_t *mac; + const struct HDIF_child_ptr *iokids; + + /* + * We have a table of supported dauther cards looked up + * by CCIN. We don't use the 1008 slot map in the VPD. + * + * This is basically translated from BML and will do for + * now especially since we don't really support p5ioc2 + * machine, this is just for lab use + * + * This is mostly untested on 10G ... we might need more + * info about the PHY in that case + */ + const struct hea_iocard { + const char ccin[4]; + struct { + uint32_t speed; + uint16_t ports; + uint16_t phy_id; + } pg[2]; + } hea_iocards[] = { + { + .ccin = "1818", /* HV4 something */ + .pg[0] = { .speed = 1000, .ports = 2, .phy_id = 0 }, + }, + { + .ccin = "1819", /* HV4 Titov Card */ + .pg[0] = { .speed = 1000, .ports = 2, .phy_id = 0 }, + .pg[1] = { .speed = 1000, .ports = 2, .phy_id = 0 }, + }, + { + .ccin = "1830", /* HV4 Sergei Card */ + .pg[0] = { .speed = 10000, .ports = 1, .phy_id = 0 }, + .pg[1] = { .speed = 10000, .ports = 1, .phy_id = 0 }, + }, + { + .ccin = "181A", /* L4 Evans Card */ + .pg[1] = { .speed = 1000, .ports = 2, .phy_id = 0 }, + }, + { + .ccin = "181B", /* L4 Weber Card */ + .pg[0] = { .speed = 10000, .ports = 1, .phy_id = 0 }, + .pg[1] = { .speed = 10000, .ports = 1, .phy_id = 0 }, + }, + { + .ccin = "181C", /* HV4 Gibson Card */ + .pg[0] = { .speed = 1000, .ports = 2, .phy_id = 0 }, + .pg[1] = { .speed = 1000, .ports = 2, .phy_id = 0 }, + }, + { + .ccin = "2BC4", /* MR Riverside 2 */ + .pg[0] = { .speed = 1000, .ports = 1, .phy_id = 1 }, + .pg[1] = { .speed = 1000, .ports = 1, .phy_id = 1 }, + }, + { + .ccin = "2BC5", /* MR Lions 2 */ + .pg[0] = { .speed = 10000, .ports = 1, .phy_id = 1 }, + .pg[1] = { .speed = 10000, .ports = 1, .phy_id = 1 }, + }, + { + .ccin = "2BC6", /* MR Onion 2 */ + .pg[0] = { .speed = 10000, .ports = 1, .phy_id = 1 }, + .pg[1] = { .speed = 1000, .ports = 2, .phy_id = 1 }, + }, + { + .ccin = "266D", /* Jupiter Bonzai */ + .pg[0] = { .speed = 1000, .ports = 2, .phy_id = 1 }, + .pg[1] = { .speed = 1000, .ports = 2, .phy_id = 1 }, + }, + /* The blade use an IO KID that's a bit oddball and seems to + * represent the backplane itself, but let's use it anyway + * + * XXX Probably want a different PHY type ! + */ + { + .ccin = "531C", /* P7 Blade */ + .pg[0] = { .speed = 1000, .ports = 2, .phy_id = 0 }, + }, + }; + const struct hea_iocard *card = NULL; + + /* WARNING: This makes quite a lot of nasty assumptions + * that appear to hold true on the few machines I care + * about, which is good enough for now. We don't officially + * support p5ioc2 anyway... + */ + + /* Get first IO KID, we only support one. Real support would + * mean using the FRU ID and the SLCA to find the right "stuff" + * but at this stage it's unnecessary + */ + iokids = HDIF_child_arr(sp_io, CECHUB_CHILD_IO_KIDS); + if (!CHECK_SPPTR(iokids)) { + prerror("HEA: no IOKID in HDAT child array !\n"); + return NULL; + } + if (!iokids->count) { + prerror("HEA: IOKID count is 0 !\n"); + return NULL; + } + if (iokids->count > 1) { + printf("HEA: WARNING ! More than 1 IO KID !!! (%d)\n", + iokids->count); + } + iokid = HDIF_child(sp_io, iokids, 0, "IO KID"); + if (!iokid) { + prerror("HEA: Failed to retrieve IO KID 0 !\n"); + return NULL; + } + + /* Grab VPD */ + vpd = HDIF_get_idata(iokid, IOKID_KW_VPD, &vpd_sz); + if (!CHECK_SPPTR(vpd)) { + prerror("HEA: Failed to retrieve VPD from IO KID !\n"); + return NULL; + } + + /* Grab the MAC address */ + mac = vpd_find(vpd, vpd_sz, "VINI", "B1", &kw_sz); + if (!mac || kw_sz < 8) { + prerror("HEA: Failed to retrieve MAC Address !\n"); + return NULL; + } + + /* Grab the CCIN (card ID) */ + ccin = vpd_find(vpd, vpd_sz, "VINI", "CC", &kw_sz); + if (!ccin || kw_sz < 4) { + prerror("HEA: Failed to retrieve CCIN !\n"); + return NULL; + } + + /* Now we could try to parse the 1008 slot map etc... but instead + * we'll do like BML and grab the CCIN & use it for known cards. + * We also grab the MAC + */ + for (i = 0; i < ARRAY_SIZE(hea_iocards) && !card; i++) { + if (strncmp(hea_iocards[i].ccin, ccin, 4)) + continue; + card = &hea_iocards[i]; + } + if (!card) { + prerror("HEA: Unknown CCIN 0x%.4s!\n", (const char *)ccin); + return NULL; + } + + /* Assume base address is BAR3 + 0x4000000000 */ + reg[0] = hub->gx_ctrl_bar3 + 0x4000000000; + reg[1] = 0xc0000000; + + printf("CEC: * Adding HEA to P5IOC2, assuming GBA=0x%llx\n", + (long long)reg[0]); + np = dt_new_addr(dt_root, "ibm,hea", reg[0]); + if (!np) + return NULL; + + dt_add_property(np, "reg", reg, sizeof(reg)); + dt_add_property_strings(np, "compatible", "ibm,p5ioc2-hea"); + dt_add_property_cells(np, "#address-cells", 1); + dt_add_property_cells(np, "#size-cells", 0); + dt_add_property(np, "ibm,vpd", vpd, vpd_sz); + dt_add_property_cells(np, "#mac-address", mac[7]); + dt_add_property(np, "mac-address-base", mac, 6); + /* BUID is base + 0x30 */ + dt_add_property(np, "interrupt-controller", NULL, 0); + dt_add_property_cells(np, "interrupt-base", + ((hub->buid_ext << 9) | 0x30) << 4); + dt_add_property_cells(np, "interrupt-max-count", 128); + + /* Always 2 port groups */ + for (i = 0; i < 2; i++) { + unsigned int clause; + + switch(card->pg[i].speed) { + case 1000: + clause = 0x22; + break; + case 10000: + clause = 0x45; + break; + default: + /* Unused port group */ + continue; + } + gnp = dt_new_addr(np, "portgroup", i + 1); + if (!gnp) + continue; + + dt_add_property_cells(gnp, "reg", i + 1); + dt_add_property_cells(gnp, "speed", card->pg[i].speed); + /* XX FIXME */ + dt_add_property_strings(gnp, "phy-type", "mdio"); + dt_add_property_cells(gnp, "phy-mdio-addr", card->pg[i].phy_id); + dt_add_property_cells(gnp, "phy-mdio-clause", clause); + dt_add_property_cells(gnp, "subports", card->pg[i].ports); + } + return np; +} + +static void io_parse_fru(const void *sp_iohubs) +{ + unsigned int i; + struct dt_node *hn; + int count; + + count = HDIF_get_iarray_size(sp_iohubs, CECHUB_FRU_IO_HUBS); + if (count < 1) { + prerror("CEC: IO FRU with no chips !\n"); + return; + } + + printf("CEC: %d chips in FRU\n", count); + + /* Iterate IO hub array */ + for (i = 0; i < count; i++) { + const struct cechub_io_hub *hub; + unsigned int size, hub_id; + + hub = HDIF_get_iarray_item(sp_iohubs, CECHUB_FRU_IO_HUBS, + i, &size); + if (!hub || size < CECHUB_IOHUB_MIN_SIZE) { + prerror("CEC: IO-HUB Chip %d bad idata\n", i); + continue; + } + printf("CEC: IO Hub Chip #%d:\n", i); + switch (hub->flags & CECHUB_HUB_FLAG_STATE_MASK) { + case CECHUB_HUB_FLAG_STATE_OK: + printf("CEC: OK\n"); + break; + case CECHUB_HUB_FLAG_STATE_FAILURES: + printf("CEC: OK with failures\n"); + break; + case CECHUB_HUB_FLAG_STATE_NOT_INST: + printf("CEC: Not installed\n"); + continue; + case CECHUB_HUB_FLAG_STATE_UNUSABLE: + printf("CEC: Unusable"); + continue; + } + + hub_id = be16_to_cpu(hub->iohub_id); + + /* GX BAR assignment */ + printf("CEC: PChip: %d HUB ID: %04x [EC=0x%x] Hub#=%d)\n", + be32_to_cpu(hub->proc_chip_id), hub_id, + be32_to_cpu(hub->ec_level), be32_to_cpu(hub->hub_num)); + + switch(hub_id) { + case CECHUB_HUB_P7IOC: + printf("CEC: P7IOC !\n"); + hn = io_add_p7ioc(hub, sp_iohubs); + io_add_common(hn, hub); + break; + case CECHUB_HUB_P5IOC2: + printf("CEC: P5IOC2 !\n"); + hn = io_add_p5ioc2(hub, sp_iohubs); + io_add_common(hn, hub); + io_add_hea(hub, sp_iohubs); + break; + case CECHUB_HUB_MURANO: + case CECHUB_HUB_MURANO_SEGU: + printf("CEC: Murano !\n"); + hn = io_add_murano(hub, sp_iohubs); + break; + default: + printf("CEC: Hub ID 0x%04x unsupported !\n", + hub_id); + hn = NULL; + } + } + + /* On P8, grab the CEC VPD */ + if (proc_gen == proc_gen_p8) + io_add_p8_cec_vpd(sp_iohubs); +} + +void io_parse(void) +{ + const struct HDIF_common_hdr *sp_iohubs; + unsigned int i, size; + + /* Look for IO Hubs */ + if (!get_hdif(&spira.ntuples.cec_iohub_fru, "IO HUB")) { + prerror("CEC: Cannot locate IO Hub FRU data !\n"); + return; + } + + /* + * Note about LXRn numbering ... + * + * I can't completely make sense of what that is supposed to be, so + * for now, what we do is look for the first one we can find and + * increment it for each chip. Works for the machines I have here + */ + + for_each_ntuple_idx(&spira.ntuples.cec_iohub_fru, sp_iohubs, i, + CECHUB_FRU_HDIF_SIG) { + const struct cechub_hub_fru_id *fru_id_data; + unsigned int type; + static const char *typestr[] = { + "Reservation", + "Card", + "CPU Card", + "Backplane", + "Backplane Extension" + }; + fru_id_data = HDIF_get_idata(sp_iohubs, CECHUB_FRU_ID_DATA_AREA, + &size); + if (!fru_id_data || size < sizeof(struct cechub_hub_fru_id)) { + prerror("CEC: IO-HUB FRU %d, bad ID data\n", i); + continue; + } + type = fru_id_data->card_type; + + printf("CEC: HUB FRU %d is %s\n", + i, type > 4 ? "Unknown" : typestr[type]); + + /* + * We currently only handle the backplane (Juno) and + * processor FRU (P8 machines) + */ + if (type != CECHUB_FRU_TYPE_CEC_BKPLANE && + type != CECHUB_FRU_TYPE_CPU_CARD) { + prerror("CEC: Unsupported type\n"); + continue; + } + + /* We don't support Hubs connected to pass-through ports */ + if (fru_id_data->flags & (CECHUB_FRU_FLAG_HEADLESS | + CECHUB_FRU_FLAG_PASSTHROUGH)) { + prerror("CEC: Headless or Passthrough unsupported\n"); + continue; + } + + /* Ok, we have a reasonable candidate */ + io_parse_fru(sp_iohubs); + } +} + 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(); + } +} + diff --git a/hdata/paca.c b/hdata/paca.c new file mode 100644 index 0000000..d4360e7 --- /dev/null +++ b/hdata/paca.c @@ -0,0 +1,322 @@ +/* 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 <skiboot.h> +#include "spira.h" +#include <cpu.h> +#include <fsp.h> +#include <opal.h> +#include <ccan/str/str.h> +#include <device.h> +#include <types.h> + +#include "hdata.h" + +#define PACA_MAX_THREADS 4 + +static unsigned int paca_index(const struct HDIF_common_hdr *paca) +{ + void *start = get_hdif(&spira.ntuples.paca, PACA_HDIF_SIG); + return ((void *)paca - start) + / be32_to_cpu(spira.ntuples.paca.alloc_len); +} + +static struct dt_node *add_cpu_node(struct dt_node *cpus, + const struct HDIF_common_hdr *paca, + const struct sppaca_cpu_id *id, + bool okay) +{ + const struct sppaca_cpu_timebase *timebase; + const struct sppaca_cpu_cache *cache; + const struct sppaca_cpu_attr *attr; + struct dt_node *cpu; + u32 no, size, ve_flags, l2_phandle, chip_id; + + /* We use the process_interrupt_line as the res id */ + no = be32_to_cpu(id->process_interrupt_line); + + ve_flags = be32_to_cpu(id->verify_exists_flags); + printf("CPU[%i]: PIR=%i RES=%i %s %s(%u threads)\n", + paca_index(paca), be32_to_cpu(id->pir), no, + ve_flags & CPU_ID_PACA_RESERVED + ? "**RESERVED**" : cpu_state(ve_flags), + ve_flags & CPU_ID_SECONDARY_THREAD + ? "[secondary] " : + (be32_to_cpu(id->pir) == boot_cpu->pir ? "[boot] " : ""), + ((ve_flags & CPU_ID_NUM_SECONDARY_THREAD_MASK) + >> CPU_ID_NUM_SECONDARY_THREAD_SHIFT) + 1); + + timebase = HDIF_get_idata(paca, SPPACA_IDATA_TIMEBASE, &size); + if (!timebase || size < sizeof(*timebase)) { + prerror("CPU[%i]: bad timebase size %u @ %p\n", + paca_index(paca), size, timebase); + return NULL; + } + + cache = HDIF_get_idata(paca, SPPACA_IDATA_CACHE_SIZE, &size); + if (!cache || size < sizeof(*cache)) { + prerror("CPU[%i]: bad cache size %u @ %p\n", + paca_index(paca), size, cache); + return NULL; + } + + cpu = add_core_common(cpus, cache, timebase, no, okay); + + /* Core attributes */ + attr = HDIF_get_idata(paca, SPPACA_IDATA_CPU_ATTR, &size); + if (attr) + add_core_attr(cpu, be32_to_cpu(attr->attr)); + + /* Add cache info */ + l2_phandle = add_core_cache_info(cpus, cache, no, okay); + dt_add_property_cells(cpu, "l2-cache", l2_phandle); + + /* We append the secondary cpus in __cpu_parse */ + dt_add_property_cells(cpu, "ibm,ppc-interrupt-server#s", no); + + dt_add_property_cells(cpu, DT_PRIVATE "hw_proc_id", + be32_to_cpu(id->hardware_proc_id)); + dt_add_property_cells(cpu, "ibm,pir", be32_to_cpu(id->pir)); + + chip_id = pcid_to_chip_id(be32_to_cpu(id->processor_chip_id)); + dt_add_property_cells(cpu, "ibm,chip-id", chip_id); + + return cpu; +} + +static struct dt_node *find_cpu_by_hardware_proc_id(struct dt_node *root, + u32 hw_proc_id) +{ + struct dt_node *i; + + dt_for_each_node(root, i) { + const struct dt_property *prop; + + if (!dt_has_node_property(i, "device_type", "cpu")) + continue; + + prop = dt_find_property(i, DT_PRIVATE "hw_proc_id"); + if (be32_to_cpu(*(u32 *)prop->prop) == hw_proc_id) + return i; + } + return NULL; +} + +/* Note that numbers are small. */ +static void add_be32_sorted(__be32 arr[], __be32 new, unsigned num) +{ + unsigned int i; + + /* Walk until we find where we belong (insertion sort). */ + for (i = 0; i < num; i++) { + if (be32_to_cpu(new) < be32_to_cpu(arr[i])) { + __be32 tmp = arr[i]; + arr[i] = new; + new = tmp; + } + } + arr[i] = new; +} + +static void add_icps(void) +{ + struct dt_node *cpu; + unsigned int i; + u64 reg[PACA_MAX_THREADS * 2]; + struct dt_node *icp; + + dt_for_each_node(dt_root, cpu) { + u32 irange[2], size, pir; + const struct dt_property *intsrv; + const struct HDIF_common_hdr *paca; + u64 ibase; + unsigned int num_threads; + bool found = false; + + if (!dt_has_node_property(cpu, "device_type", "cpu")) + continue; + + intsrv = dt_find_property(cpu, "ibm,ppc-interrupt-server#s"); + pir = dt_prop_get_u32(cpu, "ibm,pir"); + + /* Get ibase address */ + paca = get_hdif(&spira.ntuples.paca, PACA_HDIF_SIG); + for_each_paca(paca) { + const struct sppaca_cpu_id *id; + id = HDIF_get_idata(paca, SPPACA_IDATA_CPU_ID, &size); + + if (pir != be32_to_cpu(id->pir)) + continue; + ibase = cleanup_addr(be64_to_cpu(id->ibase)); + found = true; + break; + } + if (!found) + return; + + num_threads = intsrv->len / sizeof(u32); + assert(num_threads <= PACA_MAX_THREADS); + + icp = dt_new_addr(dt_root, "interrupt-controller", ibase); + if (!icp) + continue; + + dt_add_property_strings(icp, "compatible", + "IBM,ppc-xicp", + "IBM,power7-xicp"); + + irange[0] = dt_property_get_cell(intsrv, 0); /* Index */ + irange[1] = num_threads; /* num servers */ + dt_add_property(icp, "ibm,interrupt-server-ranges", + irange, sizeof(irange)); + dt_add_property(icp, "interrupt-controller", NULL, 0); + dt_add_property_cells(icp, "#address-cells", 0); + dt_add_property_cells(icp, "#interrupt-cells", 1); + dt_add_property_string(icp, "device_type", + "PowerPC-External-Interrupt-Presentation"); + for (i = 0; i < num_threads*2; i += 2) { + reg[i] = ibase; + /* One page is enough for a handful of regs. */ + reg[i+1] = 4096; + ibase += reg[i+1]; + } + dt_add_property(icp, "reg", reg, sizeof(reg)); + } +} + +static bool __paca_parse(void) +{ + const struct HDIF_common_hdr *paca; + struct dt_node *cpus; + + paca = get_hdif(&spira.ntuples.paca, PACA_HDIF_SIG); + if (!paca) { + prerror("Invalid PACA (PCIA = %p)\n", + ntuple_addr(&spira.ntuples.pcia)); + return false; + } + + if (be32_to_cpu(spira.ntuples.paca.act_len) < sizeof(*paca)) { + prerror("PACA: invalid size %u\n", + be32_to_cpu(spira.ntuples.paca.act_len)); + return false; + } + + cpus = dt_new(dt_root, "cpus"); + dt_add_property_cells(cpus, "#address-cells", 1); + dt_add_property_cells(cpus, "#size-cells", 0); + + for_each_paca(paca) { + const struct sppaca_cpu_id *id; + u32 size, ve_flags; + bool okay; + + id = HDIF_get_idata(paca, SPPACA_IDATA_CPU_ID, &size); + + /* The ID structure on Blade314 is only 0x54 long. We can + * cope with it as we don't use all the additional fields. + * The minimum size we support is 0x40 + */ + if (!id || size < SPIRA_CPU_ID_MIN_SIZE) { + prerror("CPU[%i]: bad id size %u @ %p\n", + paca_index(paca), size, id); + return false; + } + + ve_flags = be32_to_cpu(id->verify_exists_flags); + switch ((ve_flags&CPU_ID_VERIFY_MASK) >> CPU_ID_VERIFY_SHIFT) { + case CPU_ID_VERIFY_USABLE_NO_FAILURES: + case CPU_ID_VERIFY_USABLE_FAILURES: + okay = true; + break; + default: + okay = false; + } + + printf("CPU[%i]: PIR=%i RES=%i %s\n", + paca_index(paca), be32_to_cpu(id->pir), + be32_to_cpu(id->process_interrupt_line), + okay ? "OK" : "UNAVAILABLE"); + + /* Secondary threads don't get their own node. */ + if (ve_flags & CPU_ID_SECONDARY_THREAD) + continue; + + if (!add_cpu_node(cpus, paca, id, okay)) + return false; + } + + /* Now account for secondaries. */ + for_each_paca(paca) { + const struct dt_property *prop; + const struct sppaca_cpu_id *id; + u32 size, state, num, ve_flags; + struct dt_node *cpu; + __be32 *new_prop; + + id = HDIF_get_idata(paca, 2, &size); + ve_flags = be32_to_cpu(id->verify_exists_flags); + state = (ve_flags & CPU_ID_VERIFY_MASK) >> CPU_ID_VERIFY_SHIFT; + switch (state) { + case CPU_ID_VERIFY_USABLE_NO_FAILURES: + case CPU_ID_VERIFY_USABLE_FAILURES: + break; + default: + continue; + } + + /* Only interested in secondary threads. */ + if (!(ve_flags & CPU_ID_SECONDARY_THREAD)) + continue; + + cpu = find_cpu_by_hardware_proc_id(cpus, + be32_to_cpu(id->hardware_proc_id)); + if (!cpu) { + prerror("CPU[%i]: could not find primary hwid %i\n", + paca_index(paca), + be32_to_cpu(id->hardware_proc_id)); + return false; + } + + /* Add the cpu #. */ + prop = dt_find_property(cpu, "ibm,ppc-interrupt-server#s"); + num = prop->len / sizeof(u32); + new_prop = malloc((num + 1) * sizeof(u32)); + if (!new_prop) { + prerror("Property allocation length %zu failed\n", + (num + 1) * sizeof(u32)); + return false; + } + memcpy(new_prop, prop->prop, prop->len); + add_be32_sorted(new_prop, id->process_interrupt_line, num); + dt_del_property(cpu, (struct dt_property *)prop); + dt_add_property(cpu, "ibm,ppc-interrupt-server#s", + new_prop, (num + 1) * sizeof(__be32)); + free(new_prop); + } + + add_icps(); + + return true; +} + +void paca_parse(void) +{ + if (!__paca_parse()) { + prerror("CPU: Initial CPU parsing failed\n"); + abort(); + } +} diff --git a/hdata/pcia.c b/hdata/pcia.c new file mode 100644 index 0000000..d128a31 --- /dev/null +++ b/hdata/pcia.c @@ -0,0 +1,242 @@ +/* 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 <skiboot.h> +#include "spira.h" +#include <cpu.h> +#include <fsp.h> +#include <opal.h> +#include <ccan/str/str.h> +#include <device.h> + +#include "hdata.h" + +#define PCIA_MAX_THREADS 8 + +static unsigned int pcia_index(const void *pcia) +{ + return (pcia - (void *)get_hdif(&spira.ntuples.pcia, "SPPCIA")) + / spira.ntuples.pcia.alloc_len; +} + +static const struct sppcia_cpu_thread *find_tada(const void *pcia, + unsigned int thread) +{ + unsigned int count = HDIF_get_iarray_size(pcia, + SPPCIA_IDATA_THREAD_ARRAY); + unsigned int i; + + for (i = 0; i < count; i++) { + const struct sppcia_cpu_thread *t; + unsigned int size; + + t = HDIF_get_iarray_item(pcia, SPPCIA_IDATA_THREAD_ARRAY, + i, &size); + if (!t || size < sizeof(*t)) + continue; + if (be32_to_cpu(t->phys_thread_id) == thread) + return t; + } + return NULL; +} + +static void add_icp(const void *pcia, u32 tcount, const char *compat) +{ + const struct sppcia_cpu_thread *t; + struct dt_node *icp; + __be64 *reg; + u32 i, irange[2], rsize; + + rsize = tcount * 2 * sizeof(__be64); + reg = malloc(rsize); + assert(reg); + + /* Suppresses uninitialized warning from gcc */ + irange[0] = 0; + for (i = 0; i < tcount; i++) { + t = find_tada(pcia, i); + assert(t); + if (i == 0) + irange[0] = be32_to_cpu(t->proc_int_line); + reg[i * 2] = cpu_to_be64(cleanup_addr(be64_to_cpu(t->ibase))); + reg[i * 2 + 1] = cpu_to_be64(0x1000); + } + irange[1] = tcount; + + icp = dt_new_addr(dt_root, "interrupt-controller", be64_to_cpu(reg[0])); + if (!icp) { + free(reg); + return; + } + + if (compat) + dt_add_property_strings(icp, "compatible", "ibm,ppc-xicp", compat); + else + dt_add_property_strings(icp, "compatible", "ibm,ppc-xicp"); + dt_add_property_cells(icp, "ibm,interrupt-server-ranges", + irange[0], irange[1]); + dt_add_property(icp, "interrupt-controller", NULL, 0); + dt_add_property(icp, "reg", reg, rsize); + dt_add_property_cells(icp, "#address-cells", 0); + dt_add_property_cells(icp, "#interrupt-cells", 1); + dt_add_property_string(icp, "device_type", + "PowerPC-External-Interrupt-Presentation"); + free(reg); +} + +static struct dt_node *add_core_node(struct dt_node *cpus, + const void *pcia, + const struct sppcia_core_unique *id, + bool okay) +{ + const struct sppcia_cpu_thread *t; + const struct sppcia_cpu_timebase *timebase; + const struct sppcia_cpu_cache *cache; + const struct sppcia_cpu_attr *attr; + struct dt_node *cpu; + const char *icp_compat; + u32 i, size, threads, ve_flags, l2_phandle, chip_id; + __be32 iserv[PCIA_MAX_THREADS]; + + /* Look for thread 0 */ + t = find_tada(pcia, 0); + if (!t) { + prerror("CORE[%i]: Failed to find thread 0 !\n", + pcia_index(pcia)); + return NULL; + } + + ve_flags = be32_to_cpu(id->verif_exist_flags); + threads = ((ve_flags & CPU_ID_NUM_SECONDARY_THREAD_MASK) + >> CPU_ID_NUM_SECONDARY_THREAD_SHIFT) + 1; + assert(threads <= PCIA_MAX_THREADS); + + printf("CORE[%i]: PIR=%i RES=%i %s %s(%u threads)\n", + pcia_index(pcia), t->pir, t->proc_int_line, + ve_flags & CPU_ID_PACA_RESERVED + ? "**RESERVED**" : cpu_state(ve_flags), + be32_to_cpu(t->pir) == boot_cpu->pir ? "[boot] " : "", threads); + + timebase = HDIF_get_idata(pcia, SPPCIA_IDATA_TIMEBASE, &size); + if (!timebase || size < sizeof(*timebase)) { + prerror("CORE[%i]: bad timebase size %u @ %p\n", + pcia_index(pcia), size, timebase); + return NULL; + } + + cache = HDIF_get_idata(pcia, SPPCIA_IDATA_CPU_CACHE, &size); + if (!cache || size < sizeof(*cache)) { + prerror("CORE[%i]: bad cache size %u @ %p\n", + pcia_index(pcia), size, cache); + return NULL; + } + + cpu = add_core_common(cpus, cache, timebase, + be32_to_cpu(t->proc_int_line), okay); + + /* Core attributes */ + attr = HDIF_get_idata(pcia, SPPCIA_IDATA_CPU_ATTR, &size); + if (attr) + add_core_attr(cpu, be32_to_cpu(attr->attr)); + + /* Add cache info */ + l2_phandle = add_core_cache_info(cpus, cache, + be32_to_cpu(t->proc_int_line), okay); + dt_add_property_cells(cpu, "l2-cache", l2_phandle); + + if (proc_gen == proc_gen_p7) + icp_compat = "IBM,power7-icp"; + else + icp_compat = "IBM,power8-icp"; + + /* Get HW Chip ID */ + chip_id = pcid_to_chip_id(be32_to_cpu(id->proc_chip_id)); + + dt_add_property_cells(cpu, "ibm,pir", be32_to_cpu(t->pir)); + dt_add_property_cells(cpu, "ibm,chip-id", chip_id); + + /* Build ibm,ppc-interrupt-server#s with all threads */ + for (i = 0; i < threads; i++) { + t = find_tada(pcia, i); + if (!t) { + threads = i; + break; + } + iserv[i] = t->proc_int_line; + assert(t->proc_int_line == t->pir); + } + + dt_add_property(cpu, "ibm,ppc-interrupt-server#s", iserv, 4 * threads); + + /* Add the ICP node for this CPU */ + add_icp(pcia, threads, icp_compat); + + return cpu; +} + +bool pcia_parse(void) +{ + const void *pcia; + struct dt_node *cpus; + bool got_pcia = false; + + /* Check PCIA exists... if not, maybe we are getting a PACA ? */ + pcia = get_hdif(&spira.ntuples.pcia, "SPPCIA"); + if (!pcia) + return false; + + printf("Got PCIA !\n"); + + got_pcia = true; + + cpus = dt_new(dt_root, "cpus"); + dt_add_property_cells(cpus, "#address-cells", 1); + dt_add_property_cells(cpus, "#size-cells", 0); + + for_each_pcia(pcia) { + const struct sppcia_core_unique *id; + u32 size, ve_flags; + bool okay; + + id = HDIF_get_idata(pcia, SPPCIA_IDATA_CORE_UNIQUE, &size); + if (!id || size < sizeof(*id)) { + prerror("CORE[%i]: bad id size %u @ %p\n", + pcia_index(pcia), size, id); + return false; + } + ve_flags = be32_to_cpu(id->verif_exist_flags); + + switch ((ve_flags & CPU_ID_VERIFY_MASK) + >> CPU_ID_VERIFY_SHIFT) { + case CPU_ID_VERIFY_USABLE_NO_FAILURES: + case CPU_ID_VERIFY_USABLE_FAILURES: + okay = true; + break; + default: + okay = false; + } + + printf("CORE[%i]: HW_PROC_ID=%i PROC_CHIP_ID=%i EC=0x%x %s\n", + pcia_index(pcia), be32_to_cpu(id->hw_proc_id), + be32_to_cpu(id->proc_chip_id), + be32_to_cpu(id->chip_ec_level), + okay ? "OK" : "UNAVAILABLE"); + + if (!add_core_node(cpus, pcia, id, okay)) + break; + } + return got_pcia; +} diff --git a/hdata/slca.c b/hdata/slca.c new file mode 100644 index 0000000..a709aaa --- /dev/null +++ b/hdata/slca.c @@ -0,0 +1,89 @@ +/* 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 <device.h> +#include "spira.h" +#include "hdata.h" + +const struct slca_entry *slca_get_entry(uint16_t slca_index) +{ + struct HDIF_common_hdr *slca_hdr; + int count; + + slca_hdr = get_hdif(&spira.ntuples.slca, SLCA_HDIF_SIG); + if (!slca_hdr) { + prerror("SLCA Invalid\n"); + return NULL; + } + + count = HDIF_get_iarray_size(slca_hdr, SLCA_IDATA_ARRAY); + if (count < 0) { + prerror("SLCA: Can't find SLCA array size!\n"); + return NULL; + } + + if (slca_index < count) { + const struct slca_entry *s_entry; + unsigned int entry_sz; + s_entry = HDIF_get_iarray_item(slca_hdr, SLCA_IDATA_ARRAY, + slca_index, &entry_sz); + + if (s_entry && entry_sz >= sizeof(*s_entry)) + return s_entry; + } else + printf("SLCA: Can't find slca_entry for index %d\n", slca_index); + return NULL; +} + +const char *slca_get_vpd_name(uint16_t slca_index) +{ + const struct slca_entry *s_entry; + + s_entry = slca_get_entry(slca_index); + if (s_entry) + return (const char *)s_entry->fru_id; + else + printf("SLCA: Can't find fru_id for index %d\n", slca_index); + return NULL; +} + +const char *slca_get_loc_code_index(uint16_t slca_index) +{ + const struct slca_entry *s_entry; + + s_entry = slca_get_entry(slca_index); + if (s_entry) + return s_entry->loc_code; + else + printf("SLCA: Entry %d bad idata\n", slca_index); + + return NULL; +} + +void slca_vpd_add_loc_code(struct dt_node *node, uint16_t slca_index) +{ + const char *fru_loc_code; + char loc_code[LOC_CODE_SIZE + 1]; + + memset(loc_code, 0, sizeof(loc_code)); + fru_loc_code = slca_get_loc_code_index(slca_index); + if (!fru_loc_code) + return; + + strncpy(loc_code, fru_loc_code, LOC_CODE_SIZE); + dt_add_property(node, "ibm,loc-code", loc_code, strlen(loc_code) + 1); +} diff --git a/hdata/spira.c b/hdata/spira.c new file mode 100644 index 0000000..39a78e8 --- /dev/null +++ b/hdata/spira.c @@ -0,0 +1,965 @@ +/* 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 <device.h> +#include "spira.h" +#include <cpu.h> +#include <memory.h> +#include <vpd.h> +#include <interrupts.h> +#include <ccan/str/str.h> +#include <chip.h> +#include <fsp-mdst-table.h> + +#include "hdata.h" +#include "hostservices.h" + +/* Processor Initialization structure, contains + * the initial NIA and MSR values for the entry + * point + * + * Note: It appears to be ignoring the entry point + * and always going to 0x180 + */ + +static int cpu_type; + +__section(".procin.data") struct proc_init_data proc_init_data = { + .hdr = HDIF_SIMPLE_HDR("PROCIN", 1, struct proc_init_data), + .regs_ptr = HDIF_IDATA_PTR(offsetof(struct proc_init_data, regs), 0x10), + .regs = { + .nia = CPU_TO_BE64(0x180), + .msr = CPU_TO_BE64(0x9000000000000000ULL), /* SF | HV */ + }, +}; + +/* Populate MDST table + * + * Note that we only pass sapphire console buffer here so that we can + * capture early failure logs. Later dump component (fsp_dump_mdst_init) + * creates new table with all the memory sections we are interested and + * sends updated table to FSP via MBOX. + * + * To help the FSP distinguishing between TCE tokens and actual physical + * addresses, we set the top bit to 1 on physical addresses + */ +#define ADDR_TOP_BIT (1ul << 63) + +__section(".mdst.data") struct dump_mdst_table init_mdst_table[2] = { + { + .addr = CPU_TO_BE64(INMEM_CON_START | ADDR_TOP_BIT), + .type = CPU_TO_BE32(DUMP_SECTION_CONSOLE), + .size = CPU_TO_BE32(INMEM_CON_LEN), + }, + { + .addr = CPU_TO_BE64(HBRT_CON_START | ADDR_TOP_BIT), + .type = CPU_TO_BE32(DUMP_SECTION_HBRT_LOG), + .size = CPU_TO_BE32(HBRT_CON_LEN), + }, +}; + +/* SP Interface Root Array, aka SPIRA */ +__section(".spira.data") struct spira spira = { + .hdr = HDIF_SIMPLE_HDR("SPIRA ", SPIRA_VERSION, struct spira), + .ntuples_ptr = HDIF_IDATA_PTR(offsetof(struct spira, ntuples), + sizeof(struct spira_ntuples)), + .ntuples = { + .array_hdr = { + .offset = CPU_TO_BE32(HDIF_ARRAY_OFFSET), + .ecnt = CPU_TO_BE32(SPIRA_NTUPLES_COUNT), + .esize + = CPU_TO_BE32(sizeof(struct spira_ntuple)), + .eactsz = CPU_TO_BE32(0x18), + }, + /* We only populate some n-tuples */ + .proc_init = { + .addr = CPU_TO_BE64(PROCIN_OFF), + .alloc_cnt = CPU_TO_BE16(1), + .act_cnt = CPU_TO_BE16(1), + .alloc_len + = CPU_TO_BE32(sizeof(struct proc_init_data)), + }, + .heap = { + .addr = CPU_TO_BE64(SPIRA_HEAP_BASE), + .alloc_cnt = CPU_TO_BE16(1), + .alloc_len = CPU_TO_BE32(SPIRA_HEAP_SIZE), + }, + .mdump_src = { + .addr = CPU_TO_BE64(MDST_TABLE_OFF), + .alloc_cnt = CPU_TO_BE16(ARRAY_SIZE(init_mdst_table)), + .act_cnt = CPU_TO_BE16(ARRAY_SIZE(init_mdst_table)), + .alloc_len = + CPU_TO_BE32(sizeof(init_mdst_table)), + }, + }, +}; + +/* Overridden for testing. */ +#ifndef spira_check_ptr +bool spira_check_ptr(const void *ptr, const char *file, unsigned int line) +{ + if (!ptr) + return false; + if (((unsigned long)ptr) >= SPIRA_HEAP_BASE && + ((unsigned long)ptr) < (SPIRA_HEAP_BASE + SPIRA_HEAP_SIZE)) + return true; + + prerror("SPIRA: Bad pointer %p at %s line %d\n", ptr, file, line); + return false; +} +#endif + +struct HDIF_common_hdr *__get_hdif(struct spira_ntuple *n, const char id[], + const char *file, int line) +{ + struct HDIF_common_hdr *h = ntuple_addr(n); + if (!spira_check_ptr(h, file, line)) + return NULL; + + if (!HDIF_check(h, id)) { + prerror("SPIRA: bad tuple %p: expected %s at %s line %d\n", + h, id, file, line); + return NULL; + } + return h; +} + +static struct dt_node *add_xscom_node(uint64_t base, uint32_t hw_id, + uint32_t proc_chip_id) +{ + struct dt_node *node; + uint64_t addr, size; + + addr = base | ((uint64_t)hw_id << PPC_BITLSHIFT(28)); + size = (u64)1 << PPC_BITLSHIFT(28); + + printf("XSCOM: Found HW ID 0x%x (PCID 0x%x) @ 0x%llx\n", + hw_id, proc_chip_id, (long long)addr); + + node = dt_new_addr(dt_root, "xscom", addr); + if (!node) + return NULL; + + dt_add_property_cells(node, "ibm,chip-id", hw_id); + dt_add_property_cells(node, "ibm,proc-chip-id", proc_chip_id); + dt_add_property_cells(node, "#address-cells", 1); + dt_add_property_cells(node, "#size-cells", 1); + dt_add_property(node, "scom-controller", NULL, 0); + + switch(proc_gen) { + case proc_gen_p7: + dt_add_property_strings(node, "compatible", + "ibm,xscom", "ibm,power7-xscom"); + break; + case proc_gen_p8: + dt_add_property_strings(node, "compatible", + "ibm,xscom", "ibm,power8-xscom"); + break; + default: + dt_add_property_strings(node, "compatible", "ibm,xscom"); + } + dt_add_property_u64s(node, "reg", addr, size); + + return node; +} + +struct dt_node *find_xscom_for_chip(uint32_t chip_id) +{ + struct dt_node *node; + uint32_t id; + + dt_for_each_compatible(dt_root, node, "ibm,xscom") { + id = dt_get_chip_id(node); + if (id == chip_id) + return node; + } + + return NULL; +} + +static void add_psihb_node(struct dt_node *np) +{ + u32 psi_scom, psi_slen; + const char *psi_comp; + + /* + * We add a few things under XSCOM that aren't added + * by any other HDAT path + */ + + /* PSI host bridge */ + switch(proc_gen) { + case proc_gen_p7: + psi_scom = 0x2010c00; + psi_slen = 0x10; + psi_comp = "ibm,power7-psihb-x"; + break; + case proc_gen_p8: + psi_scom = 0x2010900; + psi_slen = 0x20; + psi_comp = "ibm,power8-psihb-x"; + break; + default: + psi_comp = NULL; + } + if (psi_comp) { + struct dt_node *psi_np; + + psi_np = dt_new_addr(np, "psihb", psi_scom); + if (!psi_np) + return; + + dt_add_property_cells(psi_np, "reg", psi_scom, psi_slen); + dt_add_property_strings(psi_np, "compatible", psi_comp, + "ibm,psihb-x"); + } +} + +static void add_xscom_add_pcia_assoc(struct dt_node *np, uint32_t pcid) +{ + const struct HDIF_common_hdr *hdr; + u32 size; + + + /* + * The SPPCRD doesn't contain all the affinity data, we have + * to dig it out of a core. I assume this is so that node + * affinity can be different for groups of cores within the + * chip, but for now we are going to ignore that + */ + hdr = get_hdif(&spira.ntuples.pcia, SPPCIA_HDIF_SIG); + if (!hdr) + return; + + for_each_pcia(hdr) { + const struct sppcia_core_unique *id; + + id = HDIF_get_idata(hdr, SPPCIA_IDATA_CORE_UNIQUE, &size); + if (!id || size < sizeof(*id)) + continue; + + if (be32_to_cpu(id->proc_chip_id) != pcid) + continue; + + dt_add_property_cells(np, "ibm,ccm-node-id", + be32_to_cpu(id->ccm_node_id)); + dt_add_property_cells(np, "ibm,hw-card-id", + be32_to_cpu(id->hw_card_id)); + dt_add_property_cells(np, "ibm,hw-module-id", + be32_to_cpu(id->hw_module_id)); + if (!dt_find_property(np, "ibm,dbob-id")) + dt_add_property_cells(np, "ibm,dbob-id", + be32_to_cpu(id->drawer_book_octant_blade_id)); + dt_add_property_cells(np, "ibm,mem-interleave-scope", + be32_to_cpu(id->memory_interleaving_scope)); + return; + } +} + +static bool add_xscom_sppcrd(uint64_t xscom_base) +{ + const struct HDIF_common_hdr *hdif; + unsigned int i, vpd_sz; + const void *vpd; + struct dt_node *np; + + for_each_ntuple_idx(&spira.ntuples.proc_chip, hdif, i, + SPPCRD_HDIF_SIG) { + const struct sppcrd_chip_info *cinfo; + u32 ve, version; + + cinfo = HDIF_get_idata(hdif, SPPCRD_IDATA_CHIP_INFO, NULL); + if (!CHECK_SPPTR(cinfo)) { + prerror("XSCOM: Bad ChipID data %d\n", i); + continue; + } + + ve = be32_to_cpu(cinfo->verif_exist_flags) & CHIP_VERIFY_MASK; + ve >>= CHIP_VERIFY_SHIFT; + if (ve == CHIP_VERIFY_NOT_INSTALLED || + ve == CHIP_VERIFY_UNUSABLE) + continue; + + /* Create the XSCOM node */ + np = add_xscom_node(xscom_base, + be32_to_cpu(cinfo->xscom_id), + be32_to_cpu(cinfo->proc_chip_id)); + if (!np) + continue; + + version = be16_to_cpu(hdif->version); + + /* Version 0A has additional OCC related stuff */ + if (version >= 0x000a) { + if (!dt_find_property(np, "ibm,dbob-id")) + dt_add_property_cells(np, "ibm,dbob-id", + be32_to_cpu(cinfo->dbob_id)); + dt_add_property_cells(np, "ibm,occ-functional-state", + be32_to_cpu(cinfo->occ_state)); + } + + /* Add chip VPD */ + dt_add_vpd_node(hdif, SPPCRD_IDATA_FRU_ID, SPPCRD_IDATA_KW_VPD); + + /* Add module VPD on version A and later */ + if (version >= 0x000a) { + vpd = HDIF_get_idata(hdif, SPPCRD_IDATA_MODULE_VPD, + &vpd_sz); + if (CHECK_SPPTR(vpd)) + dt_add_property(np, "ibm,module-vpd", vpd, + vpd_sz); + } + + /* + * Extract additional associativity information from + * the core data. Pick one core on that chip + */ + add_xscom_add_pcia_assoc(np, be32_to_cpu(cinfo->proc_chip_id)); + + /* Add PSI Host bridge */ + add_psihb_node(np); + } + + return i > 0; +} + +static void add_xscom_sppaca(uint64_t xscom_base) +{ + const struct HDIF_common_hdr *hdif; + unsigned int i; + struct dt_node *np; + + for_each_ntuple_idx(&spira.ntuples.paca, hdif, i, PACA_HDIF_SIG) { + const struct sppaca_cpu_id *id; + unsigned int chip_id, size; + int ve; + + /* We only suport old style PACA on P7 ! */ + assert(proc_gen == proc_gen_p7); + + id = HDIF_get_idata(hdif, SPPACA_IDATA_CPU_ID, &size); + + if (!CHECK_SPPTR(id)) { + prerror("XSCOM: Bad processor data %d\n", i); + continue; + } + + ve = be32_to_cpu(id->verify_exists_flags) & CPU_ID_VERIFY_MASK; + ve >>= CPU_ID_VERIFY_SHIFT; + if (ve == CPU_ID_VERIFY_NOT_INSTALLED || + ve == CPU_ID_VERIFY_UNUSABLE) + continue; + + /* Convert to HW chip ID */ + chip_id = P7_PIR2GCID(be32_to_cpu(id->pir)); + + /* do we already have an XSCOM for this chip? */ + if (find_xscom_for_chip(chip_id)) + continue; + + /* Create the XSCOM node */ + np = add_xscom_node(xscom_base, chip_id, + be32_to_cpu(id->processor_chip_id)); + if (!np) + continue; + + /* Add chip VPD */ + dt_add_vpd_node(hdif, SPPACA_IDATA_FRU_ID, SPPACA_IDATA_KW_VPD); + + /* Add chip associativity data */ + dt_add_property_cells(np, "ibm,ccm-node-id", + be32_to_cpu(id->ccm_node_id)); + if (size > SPIRA_CPU_ID_MIN_SIZE) { + dt_add_property_cells(np, "ibm,hw-card-id", + be32_to_cpu(id->hw_card_id)); + dt_add_property_cells(np, "ibm,hw-module-id", + be32_to_cpu(id->hardware_module_id)); + if (!dt_find_property(np, "ibm,dbob-id")) + dt_add_property_cells(np, "ibm,dbob-id", + be32_to_cpu(id->drawer_book_octant_blade_id)); + dt_add_property_cells(np, "ibm,mem-interleave-scope", + be32_to_cpu(id->memory_interleaving_scope)); + } + + /* Add PSI Host bridge */ + add_psihb_node(np); + } +} + +static void add_xscom(void) +{ + const void *ms_vpd; + const struct msvpd_pmover_bsr_synchro *pmbs; + unsigned int size; + uint64_t xscom_base; + + ms_vpd = get_hdif(&spira.ntuples.ms_vpd, MSVPD_HDIF_SIG); + if (!ms_vpd) { + prerror("XSCOM: Can't find MS VPD\n"); + return; + } + + pmbs = HDIF_get_idata(ms_vpd, MSVPD_IDATA_PMOVER_SYNCHRO, &size); + if (!CHECK_SPPTR(pmbs) || size < sizeof(*pmbs)) { + prerror("XSCOM: absent or bad PMBS size %u @ %p\n", size, pmbs); + return; + } + + if (!(be32_to_cpu(pmbs->flags) & MSVPD_PMS_FLAG_XSCOMBASE_VALID)) { + prerror("XSCOM: No XSCOM base in PMBS, using default\n"); + return; + } + + xscom_base = be64_to_cpu(pmbs->xscom_addr); + + /* Some FSP (on P7) give me a crap base address for XSCOM (it has + * spurious bits set as far as I can tell). Since only 5 bits 18:22 can + * be programmed in hardware, let's isolate these. This seems to give + * me the right value on VPL1 + */ + if (cpu_type == PVR_TYPE_P7) + xscom_base &= 0x80003e0000000000ul; + + /* Get rid of the top bits */ + xscom_base = cleanup_addr(xscom_base); + + /* First, try the new proc_chip ntuples for chip data */ + if (add_xscom_sppcrd(xscom_base)) + return; + + /* Otherwise, check the old-style PACA, looking for unique chips */ + add_xscom_sppaca(xscom_base); +} + +static void add_chiptod_node(unsigned int chip_id, int flags) +{ + struct dt_node *node, *xscom_node; + const char *compat_str; + uint32_t addr, len; + + if ((flags & CHIPTOD_ID_FLAGS_STATUS_MASK) != + CHIPTOD_ID_FLAGS_STATUS_OK) + return; + + xscom_node = find_xscom_for_chip(chip_id); + if (!xscom_node) { + prerror("CHIPTOD: No xscom for chiptod %d?\n", chip_id); + return; + } + + addr = 0x40000; + len = 0x34; + + switch(proc_gen) { + case proc_gen_p7: + compat_str = "ibm,power7-chiptod"; + break; + case proc_gen_p8: + compat_str = "ibm,power8-chiptod"; + break; + default: + return; + } + + printf("CHIPTOD: Found on chip 0x%x %s\n", chip_id, + (flags & CHIPTOD_ID_FLAGS_PRIMARY) ? "[primary]" : + ((flags & CHIPTOD_ID_FLAGS_SECONDARY) ? "[secondary]" : "")); + + node = dt_new_addr(xscom_node, "chiptod", addr); + if (!node) + return; + + dt_add_property_cells(node, "reg", addr, len); + dt_add_property_strings(node, "compatible", "ibm,power-chiptod", + compat_str); + + if (flags & CHIPTOD_ID_FLAGS_PRIMARY) + dt_add_property(node, "primary", NULL, 0); + if (flags & CHIPTOD_ID_FLAGS_SECONDARY) + dt_add_property(node, "secondary", NULL, 0); +} + +static bool add_chiptod_old(void) +{ + const void *hdif; + unsigned int i; + bool found = false; + + /* + * Locate chiptod ID structures in SPIRA + */ + if (!get_hdif(&spira.ntuples.chip_tod, "TOD ")) + return found; + + for_each_ntuple_idx(&spira.ntuples.chip_tod, hdif, i, "TOD ") { + const struct chiptod_chipid *id; + + id = HDIF_get_idata(hdif, CHIPTOD_IDATA_CHIPID, NULL); + if (!CHECK_SPPTR(id)) { + prerror("CHIPTOD: Bad ChipID data %d\n", i); + continue; + } + + add_chiptod_node(pcid_to_chip_id(be32_to_cpu(id->chip_id)), + be32_to_cpu(id->flags)); + found = true; + } + return found; +} + +static bool add_chiptod_new(uint32_t master_cpu) +{ + const void *hdif; + unsigned int i, master_chip; + bool found = false; + + /* + * Locate Proc Chip ID structures in SPIRA + */ + if (!get_hdif(&spira.ntuples.proc_chip, SPPCRD_HDIF_SIG)) + return found; + + master_chip = pir_to_chip_id(master_cpu); + + for_each_ntuple_idx(&spira.ntuples.proc_chip, hdif, i, + SPPCRD_HDIF_SIG) { + const struct sppcrd_chip_info *cinfo; + const struct sppcrd_chip_tod *tinfo; + unsigned int size; + u32 ve, flags; + + cinfo = HDIF_get_idata(hdif, SPPCRD_IDATA_CHIP_INFO, NULL); + if (!CHECK_SPPTR(cinfo)) { + prerror("CHIPTOD: Bad ChipID data %d\n", i); + continue; + } + + ve = be32_to_cpu(cinfo->verif_exist_flags) & CHIP_VERIFY_MASK; + ve >>= CHIP_VERIFY_SHIFT; + if (ve == CHIP_VERIFY_NOT_INSTALLED || + ve == CHIP_VERIFY_UNUSABLE) + continue; + + tinfo = HDIF_get_idata(hdif, SPPCRD_IDATA_CHIP_TOD, &size); + if (!CHECK_SPPTR(tinfo)) { + prerror("CHIPTOD: Bad TOD data %d\n", i); + continue; + } + + flags = be32_to_cpu(tinfo->flags); + + /* The FSP may strip the chiptod info from HDAT; if we find + * a zero-ed out entry, assume that the chiptod is + * present, but we don't have any primary/secondary info. In + * this case, pick the primary based on the CPU that was + * assigned master. + */ + if (!size) { + flags = CHIPTOD_ID_FLAGS_STATUS_OK; + if (be32_to_cpu(cinfo->xscom_id) == master_chip) + flags |= CHIPTOD_ID_FLAGS_PRIMARY; + } + + add_chiptod_node(be32_to_cpu(cinfo->xscom_id), flags); + found = true; + } + return found; +} + +static void add_nx_node(u32 gcid) +{ + struct dt_node *nx; + const char *cp_str; + u32 addr; + u32 size; + struct dt_node *xscom; + + xscom = find_xscom_for_chip(gcid); + if (xscom == NULL) { + prerror("NX%d: did not found xscom node.\n", gcid); + return; + } + + /* + * The NX register space is relatively self contained on P7+ but + * a bit more messy on P8. However it's all contained within the + * PB chiplet port 1 so we'll stick to that in the "reg" property + * and let the NX "driver" deal with the details. + */ + addr = 0x2010000; + size = 0x0004000; + + switch (proc_gen) { + case proc_gen_p7: + cp_str = "ibm,power7-nx"; + break; + case proc_gen_p8: + cp_str = "ibm,power8-nx"; + break; + default: + return; + } + nx = dt_new_addr(xscom, "nx", addr); + if (!nx) + return; + + dt_add_property_cells(nx, "reg", addr, size); + dt_add_property_strings(nx, "compatible", "ibm,power-nx", cp_str); +} + +static void add_nx(void) +{ + unsigned int i; + void *hdif; + + for_each_ntuple_idx(&spira.ntuples.proc_chip, hdif, i, + SPPCRD_HDIF_SIG) { + const struct sppcrd_chip_info *cinfo; + u32 ve; + + cinfo = HDIF_get_idata(hdif, SPPCRD_IDATA_CHIP_INFO, NULL); + if (!CHECK_SPPTR(cinfo)) { + prerror("NX: Bad ChipID data %d\n", i); + continue; + } + + ve = be32_to_cpu(cinfo->verif_exist_flags) & CHIP_VERIFY_MASK; + ve >>= CHIP_VERIFY_SHIFT; + if (ve == CHIP_VERIFY_NOT_INSTALLED || + ve == CHIP_VERIFY_UNUSABLE) + continue; + + if (cinfo->nx_state) + add_nx_node(be32_to_cpu(cinfo->xscom_id)); + } +} + + +static void add_iplparams_sys_params(const void *iplp, struct dt_node *node) +{ + const struct iplparams_sysparams *p; + u32 sys_type; + const char *sys_family; + + p = HDIF_get_idata(iplp, IPLPARAMS_SYSPARAMS, NULL); + if (!CHECK_SPPTR(p)) { + prerror("IPLPARAMS: No SYS Parameters\n"); + /* Create a generic compatible property */ + dt_add_property_string(dt_root, "compatible", "ibm,powernv"); + return; + } + + node = dt_new(node, "sys-params"); + assert(node); + dt_add_property_cells(node, "#address-cells", 0); + dt_add_property_cells(node, "#size-cells", 0); + + dt_add_property_nstr(node, "ibm,sys-model", p->sys_model, 4); + + /* Compatible is 2 entries: ibm,powernv and ibm,<platform> + */ + sys_type = be32_to_cpu(p->system_type); + switch(sys_type >> 28) { + case 0: + sys_family = "ibm,squadrons"; + break; + case 1: + sys_family = "ibm,eclipz"; + break; + case 2: + sys_family = "ibm,apollo"; + break; + case 3: + sys_family = "ibm,firenze"; + break; + default: + sys_family = NULL; + prerror("IPLPARAMS: Unknown system family\n"); + break; + } + dt_add_property_strings(dt_root, "compatible", "ibm,powernv", + sys_family); +} + +static void add_iplparams_ipl_params(const void *iplp, struct dt_node *node) +{ + const struct iplparams_iplparams *p; + + p = HDIF_get_idata(iplp, IPLPARAMS_IPLPARAMS, NULL); + if (!CHECK_SPPTR(p)) { + prerror("IPLPARAMS: No IPL Parameters\n"); + return; + } + + node = dt_new(node, "ipl-params"); + assert(node); + dt_add_property_cells(node, "#address-cells", 0); + dt_add_property_cells(node, "#size-cells", 0); + + /* On an ASM initiated factory reset, this bit will be set + * and the FSP expects the firmware to reset the PCI bus + * numbers and respond with a Power Down (CE,4D,02) message + */ + if (p->other_attrib & IPLPARAMS_OATTR_RST_PCI_BUSNO) + dt_add_property_cells(node, "pci-busno-reset-ipl", 1); + dt_add_property_strings(node, "cec-ipl-side", + (p->ipl_side & IPLPARAMS_CEC_FW_IPL_SIDE_TEMP) ? + "temp" : "perm"); + dt_add_property_strings(node, "fsp-ipl-side", + (p->ipl_side & IPLPARAMS_FSP_FW_IPL_SIDE_TEMP) ? + "temp" : "perm"); + dt_add_property_cells(node, "os-ipl-mode", p->os_ipl_mode); + dt_add_property_strings(node, "cec-major-type", + p->cec_ipl_maj_type ? "hot" : "cold"); +} + +static void add_iplparams_serials(const void *iplp, struct dt_node *node) +{ + const struct iplparms_serial *ipser; + struct dt_node *ser_node; + int count, i; + + count = HDIF_get_iarray_size(iplp, IPLPARMS_IDATA_SERIAL); + if (!count) { + prerror("IPLPARAMS: No serial ports\n"); + return; + } + prerror("IPLPARAMS: %d serial ports in array\n", count); + + node = dt_new(node, "fsp-serial"); + assert(node); + dt_add_property_cells(node, "#address-cells", 1); + dt_add_property_cells(node, "#size-cells", 0); + + for (i = 0; i < count; i++) { + u16 rsrc_id; + ipser = HDIF_get_iarray_item(iplp, IPLPARMS_IDATA_SERIAL, + i, NULL); + if (!CHECK_SPPTR(ipser)) + continue; + rsrc_id = be16_to_cpu(ipser->rsrc_id); + printf("IPLPARAMS: Serial %d rsrc: %04x loc: %s\n", + i, rsrc_id, ipser->loc_code); + ser_node = dt_new_addr(node, "serial", rsrc_id); + if (!ser_node) + continue; + + dt_add_property_cells(ser_node, "reg", rsrc_id); + dt_add_property_nstr(ser_node, "ibm,loc-code", + ipser->loc_code, LOC_CODE_SIZE); + dt_add_property_string(ser_node, "compatible", + "ibm,fsp-serial"); + /* XXX handle CALLHOME flag ? */ + } +} + +/* + * Check for platform dump, if present populate DT + */ +static void add_iplparams_platform_dump(const void *iplp, struct dt_node *node) +{ + const struct iplparams_dump *ipl_dump; + + ipl_dump = HDIF_get_idata(iplp, IPLPARAMS_PLATFORM_DUMP, NULL); + if (!CHECK_SPPTR(ipl_dump)) + return; + + node = dt_new(node, "platform-dump"); + assert(node); + + if (be32_to_cpu(ipl_dump->dump_id)) { + dt_add_property_cells(node, "dump-id", + be32_to_cpu(ipl_dump->dump_id)); + dt_add_property_u64(node, "total-size", + be64_to_cpu(ipl_dump->act_dump_sz)); + dt_add_property_u64(node, "hw-dump-size", + be32_to_cpu(ipl_dump->act_hw_dump_sz)); + dt_add_property_cells(node, "plog-id", + be32_to_cpu(ipl_dump->plid)); + } +} + +static void add_iplparams(void) +{ + struct dt_node *iplp_node; + const void *ipl_parms; + + ipl_parms = get_hdif(&spira.ntuples.ipl_parms, "IPLPMS"); + if (!ipl_parms) { + prerror("IPLPARAMS: Cannot find IPL Parms in SPIRA\n"); + return; + } + + iplp_node = dt_new(dt_root, "ipl-params"); + assert(iplp_node); + dt_add_property_cells(iplp_node, "#address-cells", 0); + dt_add_property_cells(iplp_node, "#size-cells", 0); + + add_iplparams_sys_params(ipl_parms, iplp_node); + add_iplparams_ipl_params(ipl_parms, iplp_node); + add_iplparams_serials(ipl_parms, iplp_node); + add_iplparams_platform_dump(ipl_parms, iplp_node); +} + +/* Various structure contain a "proc_chip_id" which is an arbitrary + * numbering used by HDAT to reference chips, which doesn't correspond + * to the HW IDs. We want to use the HW IDs everywhere in the DT so + * we convert using this. + * + * Note: On P7, the HW ID is the XSCOM "GCID" including the T bit which + * is *different* from the chip ID portion of the interrupt server# + * (or PIR). See the explanations in chip.h + */ +uint32_t pcid_to_chip_id(uint32_t proc_chip_id) +{ + unsigned int i; + const void *hdif; + + /* First, try the proc_chip ntuples for chip data */ + for_each_ntuple_idx(&spira.ntuples.proc_chip, hdif, i, + SPPCRD_HDIF_SIG) { + const struct sppcrd_chip_info *cinfo; + + cinfo = HDIF_get_idata(hdif, SPPCRD_IDATA_CHIP_INFO, + NULL); + if (!CHECK_SPPTR(cinfo)) { + prerror("XSCOM: Bad ChipID data %d\n", i); + continue; + } + if (proc_chip_id == be32_to_cpu(cinfo->proc_chip_id)) + return be32_to_cpu(cinfo->xscom_id); + } + + /* Otherwise, check the old-style PACA, looking for unique chips */ + for_each_ntuple_idx(&spira.ntuples.paca, hdif, i, PACA_HDIF_SIG) { + const struct sppaca_cpu_id *id; + + /* We only suport old style PACA on P7 ! */ + assert(proc_gen == proc_gen_p7); + + id = HDIF_get_idata(hdif, SPPACA_IDATA_CPU_ID, NULL); + + if (!CHECK_SPPTR(id)) { + prerror("XSCOM: Bad processor data %d\n", i); + continue; + } + + if (proc_chip_id == be32_to_cpu(id->processor_chip_id)) + return P7_PIR2GCID(be32_to_cpu(id->pir)); + } + + /* Not found, what to do ? Assert ? For now return a number + * guaranteed to not exist + */ + return (uint32_t)-1; +} + +static void dt_init_vpd_node(void) +{ + struct dt_node *dt_vpd; + + dt_vpd = dt_new(dt_root, "vpd"); + assert(dt_vpd); +} + +static void hostservices_parse(void) +{ + struct HDIF_common_hdr *hs_hdr; + const void *dt_blob; + unsigned int size; + unsigned int ntuples_size; + + ntuples_size = sizeof(struct HDIF_array_hdr) + + be32_to_cpu(spira.ntuples.array_hdr.ecnt) * + sizeof(struct spira_ntuple); + + if (offsetof(struct spira_ntuples, hs_data) >= ntuples_size) { + prerror("SPIRA: No host services data found\n"); + return; + } + + hs_hdr = get_hdif(&spira.ntuples.hs_data, HSERV_HDIF_SIG); + if (!hs_hdr) { + prerror("SPIRA: No host services data found\n"); + return; + } + + dt_blob = HDIF_get_idata(hs_hdr, 0, &size); + if (!dt_blob) { + prerror("SPIRA: No host services idata found\n"); + return; + } + hservices_from_hdat(dt_blob, size); +} + +void parse_hdat(bool is_opal, uint32_t master_cpu) +{ + cpu_type = PVR_TYPE(mfspr(SPR_PVR)); + + printf("\n"); + printf("-----------------------------------------------\n"); + printf("-------------- Parsing HDAT ... ---------------\n"); + printf("-----------------------------------------------\n"); + printf("\n"); + + dt_root = dt_new_root(""); + + /* + * Basic DT root stuff + */ + dt_add_property_cells(dt_root, "#address-cells", 2); + dt_add_property_cells(dt_root, "#size-cells", 2); + dt_add_property_string(dt_root, "lid-type", is_opal ? "opal" : "phyp"); + + /* Create /vpd node */ + dt_init_vpd_node(); + + /* Parse SPPACA and/or PCIA */ + if (!pcia_parse()) + paca_parse(); + + /* IPL params */ + add_iplparams(); + + /* Parse MS VPD */ + memory_parse(); + + /* Add XSCOM node (must be before chiptod & IO ) */ + add_xscom(); + + /* Add FSP */ + fsp_parse(); + + /* Add ChipTOD's */ + if (!add_chiptod_old() && !add_chiptod_new(master_cpu)) + prerror("CHIPTOD: No ChipTOD found !\n"); + + /* Add NX */ + add_nx(); + + /* Add IO HUBs and/or PHBs */ + io_parse(); + + /* Parse VPD */ + vpd_parse(); + + /* Host services information. */ + hostservices_parse(); + + printf("\n"); + printf("-----------------------------------------------\n"); + printf("\n"); +} diff --git a/hdata/spira.h b/hdata/spira.h new file mode 100644 index 0000000..93239d0 --- /dev/null +++ b/hdata/spira.h @@ -0,0 +1,864 @@ +/* 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. + */ + +#ifndef __SPIRA_H +#define __SPIRA_H + +#include "hdif.h" + +/* + * The SPIRA structure + * + * NOTE: This is one of the only HDIF structure that we layout entirely + * as a C struct because it's provided by us to the FSP. Almost everything + * else is generated by the FSP, and thus must be "parsed" since the various + * offsets and alignments might change. + */ + +#define SPIRA_VERSION 0x20 /* Like 730 ? */ + +struct spira_ntuple { + __be64 addr; + __be16 alloc_cnt; + __be16 act_cnt; + __be32 alloc_len; + __be32 act_len; + __be32 tce_off; + __be64 padding; +} __packed; + +#define SPIRA_NTUPLES_COUNT 0x18 + +struct spira_ntuples { + struct HDIF_array_hdr array_hdr; + struct spira_ntuple sp_subsys; /* 0x040 */ + struct spira_ntuple ipl_parms; /* 0x060 */ + struct spira_ntuple nt_enclosure_vpd; /* 0x080 */ + struct spira_ntuple slca; /* 0x0a0 */ + struct spira_ntuple backplane_vpd; /* 0x0c0 */ + struct spira_ntuple system_vpd; /* 0x0e0 */ + struct spira_ntuple chip_tod; /* 0x100 */ + struct spira_ntuple proc_init; /* 0x120 */ + struct spira_ntuple clock_vpd; /* 0x140 */ + struct spira_ntuple anchor_vpd; /* 0x160 */ + struct spira_ntuple op_panel_vpd; /* 0x180 */ + struct spira_ntuple ext_cache_fru_vpd; /* 0x1a0 */ + struct spira_ntuple misc_cec_fru_vpd; /* 0x1c0 */ + struct spira_ntuple paca; /* 0x1e0 */ + struct spira_ntuple ms_vpd; /* 0x200 */ + struct spira_ntuple cec_iohub_fru; /* 0x220 */ + struct spira_ntuple cpu_ctrl; /* 0x240 */ + struct spira_ntuple mdump_src; /* 0x260 */ + struct spira_ntuple mdump_dst; /* 0x280 */ + struct spira_ntuple mdump_res; /* 0x2a0 */ + struct spira_ntuple heap; /* 0x2c0 */ + struct spira_ntuple pcia; /* 0x2e0 */ + struct spira_ntuple proc_chip; /* 0x300 */ + struct spira_ntuple hs_data; /* 0x320 */ +}; + +struct spira { + struct HDIF_common_hdr hdr; + struct HDIF_idata_ptr ntuples_ptr; + __be64 pad; + struct spira_ntuples ntuples; + u8 reserved[0x4c0]; +} __packed __align(0x100); + +extern struct spira spira; + +/* This macro can be used to check the validity of a pointer returned + * by one of the HDIF API functions. It returns true if the pointer + * appears valid. If it's not valid and not NULL, it will print some + * error in the log as well. + */ +#define CHECK_SPPTR(_ptr) spira_check_ptr(_ptr, __FILE__, __LINE__) + +#define get_hdif(ntuple, id) __get_hdif((ntuple), (id), __FILE__, __LINE__) + +extern struct HDIF_common_hdr *__get_hdif(struct spira_ntuple *n, + const char id[], + const char *file, int line); + +#define for_each_ntuple_idx(_ntuples, _p, _idx, _id) \ + for (_p = get_hdif((_ntuples), _id ""), _idx = 0; \ + _p && _idx < be16_to_cpu((_ntuples)->act_cnt); \ + _p = (void *)_p + be32_to_cpu((_ntuples)->alloc_len), _idx++) + +#define for_each_ntuple(_ntuples, _p, _id) \ + for (_p = get_hdif((_ntuples), _id ""); \ + _p && (void *)_p < ntuple_addr(_ntuples) \ + + (be16_to_cpu((_ntuples)->act_cnt) * \ + be32_to_cpu((_ntuples)->alloc_len)); \ + _p = (void *)_p + be32_to_cpu((_ntuples)->alloc_len)) + +#define for_each_paca(p) for_each_ntuple(&spira.ntuples.paca, p, PACA_HDIF_SIG) + +#define for_each_pcia(p) for_each_ntuple(&spira.ntuples.pcia, p, SPPCIA_HDIF_SIG) + + +/* We override these for testing. */ +#ifndef ntuple_addr +#define ntuple_addr(_ntuples) ((void *)BE64_TO_CPU((_ntuples)->addr)) +#endif + +#ifndef spira_check_ptr +extern bool spira_check_ptr(const void *ptr, const char *file, + unsigned int line); +#endif + +struct proc_init_data { + struct HDIF_common_hdr hdr; + struct HDIF_idata_ptr regs_ptr; + struct { + __be64 nia; + __be64 msr; + } regs; +} __packed __align(0x10); + +/* + * The FRU ID structure is used in several tuples, so we + * define it generically here + */ +struct spira_fru_id { + __be16 slca_index; + __be16 rsrc_id; /* formerly VPD port number */ +}; + +/* + * The FRU operational status structure is used in several + * tuples, so we define it generically here + */ +struct spira_fru_op_status { + uint8_t flags; +#define FRU_OP_STATUS_FLAG_USED 0x02 /* If 0 -> not used (redundant) */ +#define FRU_OP_STATUS_FLAG_FUNCTIONAL 0x01 /* If 0 -> non-functional */ + uint8_t reserved[3]; +}; + +/* + * Move VPD related stuff to another file ... + */ +#define VPD_ID(_a, _b) ((_a) << 8 | (_b)) + +/* + * Service Processor Subsystem Structure + * + * This structure contains several internal data blocks + * describing the service processor(s) in the system + */ + +#define SPSS_HDIF_SIG "SPINFO" + +/* Idata index 0 : FRU ID Data */ +#define SPSS_IDATA_FRU_ID 0 + +/* Idata index 1 : Keyword VPD for the FSP instance */ +#define SPSS_IDATA_KEYWORD_VPD 1 + +/* Idata index 2 : SP Implementation */ +#define SPSS_IDATA_SP_IMPL 2 + +struct spss_sp_impl { + __be16 hw_version; + __be16 sw_version; + __be16 func_flags; +#define SPSS_SP_IMPL_FLAGS_INSTALLED 0x8000 +#define SPSS_SP_IMPL_FLAGS_FUNCTIONAL 0x4000 +#define SPSS_SP_IMPL_FLAGS_PRIMARY 0x2000 + u8 chip_version; + u8 reserved; +}; + +/* Idata index 3 is deprecated */ + +/* Idata index 4 : SP Memory Locator */ +#define SPSS_IDATA_SP_MEMLOC 4 + +/* Idata index 5 : SP I/O path array */ +#define SPSS_IDATA_SP_IOPATH 5 + +/* An HDIF array of IO path */ +struct spss_iopath { + __be16 iopath_type; +#define SPSS_IOPATH_TYPE_IOHUB_PHB 0x0001 +#define SPSS_IOPATH_TYPE_PSI 0x0002 + union { + struct { + __be16 iohub_chip_inst; + __be16 iohub_chip_port; + __be16 phb_id; + } __packed iohub_phb; + + struct { + __be16 link_status; +#define SPSS_IO_PATH_PSI_LINK_BAD_FRU 0x0000 +#define SPSS_IO_PATH_PSI_LINK_CURRENT 0x0001 +#define SPSS_IO_PATH_PSI_LINK_BACKUP 0x0002 + u8 ml2_version; + u8 reserved; + __be16 slca_count; + u8 slca_idx[16]; + __be32 proc_chip_id; + __be32 reserved2; + __be64 gxhb_base; + } __packed psi; + }; +} __packed; + +/* + * IPL Parms structure + * + */ + +/* Idata index 0: System Parameters */ +#define IPLPARAMS_SYSPARAMS 0 + +struct iplparams_sysparams { + char sys_model[4]; + char cpu_feature_code[4]; + __be32 effective_pvr; + __be32 system_type; + uint8_t num_lpar_oct[8]; + __be32 abc_bus_speed; + __be32 wxyz_bus_speed; + __be32 sys_eco_mode; + __be32 sys_attributes; + __be32 mem_scrubbing; + __be16 cur_spl_value; + uint8_t pump_mode; + uint8_t use_pore_sleep; + __be32 pore_image_size; +} __packed; + +/* Idata index 1: IPL parameters */ +#define IPLPARAMS_IPLPARAMS 1 + +struct iplparams_iplparams { + uint8_t reserved; + uint8_t hv_ipl_dest; + uint8_t ipl_side; +#define IPLPARAMS_CEC_FW_IPL_SIDE_TEMP 0x10 +#define IPLPARAMS_FSP_FW_IPL_SIDE_TEMP 0x01 + uint8_t ipl_speed; + __be16 cec_ipl_attrib; + uint8_t cec_ipl_maj_type; + uint8_t cec_ipl_min_type; + uint8_t os_ipl_mode; + uint8_t keylock_pos; + uint8_t lmb_size; + uint8_t deprecated; + __be32 max_hsl_opticonnect; + __be32 other_attrib; +#define IPLPARAMS_OATTR_RST_PCI_BUSNO 0x08000000 +#define IPLPARAMS_OATTR_CLEAR_NVRAM 0x04000000 + __be16 huge_page_count; + uint8_t huge_page_size; +#define IPLPARAMS_HUGE_PG_SIZE_16G 0 + uint8_t num_vlan_switches; + __be64 reserved2; +}; + +/* Idata index 4: Platform Dump Descriptor */ +#define IPLPARAMS_PLATFORM_DUMP 4 + +struct iplparams_dump { + __be16 flags; + uint8_t reserved1; + uint8_t policy; +#define HYP_DUMP_POLICY_NORMAL 0x00 + __be32 dump_id; + __be64 reserved2; + __be64 act_dump_sz; + __be32 max_hw_dump_sz; + __be32 act_hw_dump_sz; + __be32 max_sp_dump_sz; + __be32 plid; +}; + +/* Idata index 8: serial ports */ +#define IPLPARMS_IDATA_SERIAL 8 + +/* An HDIF array of serial descriptions */ +struct iplparms_serial { + uint8_t loc_code[LOC_CODE_SIZE]; + __be16 rsrc_id; + __be16 flags; +#define PLPARMS_SERIAL_FLAGS_CALLHOME 0x8000 +} __packed; + +/* + * Chip TOD structure + * + * This is an array of 32 entries (I assume per possible chip) + */ + +/* Idata index 0: Chip ID data (array) */ +#define CHIPTOD_IDATA_CHIPID 0 + +struct chiptod_chipid { + __be32 chip_id; + __be32 flags; +#define CHIPTOD_ID_FLAGS_PRIMARY 0x02 +#define CHIPTOD_ID_FLAGS_SECONDARY 0x01 +#define CHIPTOD_ID_FLAGS_STATUS_MASK 0x0c +#define CHIPTOD_ID_FLAGS_STATUS_OK 0x04 +#define CHIPTOD_ID_FLAGS_STATUS_NOK 0x08 +} __packed; + +/* Idata index 0: Chip Initialization data */ +#define CHIPTOD_IDATA_CHIPINIT 1 + +struct chiptod_chipinit { + __be32 ctrl_reg_internal; + __be32 tod_ctrl_reg; +} __packed; + +/* + * MS VPD - Memory Description Tree + * + * One such structure pointing to the various memory arrays + * along with other infos about the BCRs, Page Mover, XSCOM,... + */ +#define MSVPD_HDIF_SIG "MS VPD" + +/* Idata index 0: Mainstore address config */ +#define MSVPD_IDATA_MS_ADDR_CONFIG 0 + +/* Mainstore Address Configuration */ +struct msvpd_ms_addr_config { + __be64 max_configured_ms_address; + __be64 max_possible_ms_address; + __be32 deprecated; + __be64 mirrorable_memory_starting_address; +} __attribute__((packed)); + +/* Idata index 1: Total configured mainstore */ +#define MSVPD_IDATA_TOTAL_CONFIG_MS 1 + +struct msvpd_total_config_ms { + __be64 total_in_mb; +}; + +/* Idata index 2: Page mover and sync structure */ +#define MSVPD_IDATA_PMOVER_SYNCHRO 2 + +struct msvpd_pmover_bsr_synchro { + __be32 flags; +#define MSVPD_PMS_FLAG_HWLOCK_EN 0x80000000 +#define MSVPD_PMS_FLAG_PMOVER_EN 0x40000000 +#define MSVPD_PMS_FLAG_BSR_EN 0x20000000 +#define MSVPD_PMS_FLAG_XSCOMBASE_VALID 0x10000000 + /* P7 values for BSR mode */ +#define MSVPD_PMS_FLAG_P7BSR_1M_MODE 0x00000000 +#define MSVPD_PMS_FLAG_P7BSR_2M_MODE 0x02000000 +#define MSVPD_PMS_FLAG_P7BSR_4M_MODE 0x04000000 +#define MSVPD_PMS_FLAG_P7BSR_8M_MODE 0x06000000 + __be32 hwlocks_per_page; + __be64 hwlock_addr; + __be64 pmover_addr; + __be64 bsr_addr; + __be64 xscom_addr; + +}; + +/* Idata index 3: Memory Trace Array */ + +/* Idata index 4: UE Address Array */ + +/* Child index 0: MS area child structure */ +#define MSVPD_CHILD_MS_AREAS 0 + +/* + * CEC I/O Hub FRU + * + * This is an array of CEC Hub FRU HDIF structures + * + * Each of these has some idata pointers to generic info about the + * hub and a possible child pointer for daughter card. + * + * Actual ports are in the SLCA and need to be cross referenced + * + * Note that slots meant for the addition of GX+ adapters that + * are currently unpopulated but support hotplug will have a + * minimum "placeholder" entry, which will be fully populated + * when the array is rebuild during concurrent maintainance. + * This "placeholder" is called a "reservation". + * + * WARNING: The array rebuild by concurrent maintainance is not + * guaranteed to be in the same order as the IPL array, not is + * the order stable between concurrent maintainance operations. + * + * There's also a child pointer to daugher card structures but + * we aren't going to handle that just yet. + */ +#define CECHUB_FRU_HDIF_SIG "IO HUB" +#define IOKID_FRU_HDIF_SIG "IO KID" + +/* Idata index 0: FRU ID data + * + * This is a generic struct spira_fru_id defined above + */ +#define CECHUB_FRU_ID_DATA 0 + +/* Idata index 1: ASCII Keyword VPD */ +#define CECHUB_ASCII_KEYWORD_VPD 1 + +/* Idata index 2: Hub FRU ID data area */ +#define CECHUB_FRU_ID_DATA_AREA 2 + +struct cechub_hub_fru_id { + __be32 card_type; +#define CECHUB_FRU_TYPE_IOHUB_RSRV 0 +#define CECHUB_FRU_TYPE_IOHUB_CARD 1 +#define CECHUB_FRU_TYPE_CPU_CARD 2 +#define CECHUB_FRU_TYPE_CEC_BKPLANE 3 +#define CECHUB_FRU_TYPE_BKPLANE_EXT 4 + __be32 unused; + __be16 total_chips; + uint8_t flags; +#define CECHUB_FRU_FLAG_HEADLESS 0x80 /* not connected to CPU */ +#define CECHUB_FRU_FLAG_PASSTHROUGH 0x40 /* connected to passhtrough + port of another hub */ + uint8_t reserved; + __be16 parent_hub_id; /* chip instance number of the + hub that contains the passthrough + port this one is connected to */ + __be16 reserved2; +} __packed; + + +/* Idata index 3: IO HUB array */ + +#define CECHUB_FRU_IO_HUBS 3 + +/* This is an HDIF array of IO Hub structures */ +struct cechub_io_hub { + __be64 fmtc_address; + __be32 fmtc_tce_size; + __be16 hub_num; /* unique hub number (I/O Hub ID) */ + uint8_t flags; +#define CECHUB_HUB_FLAG_STATE_MASK 0xc0 +#define CECHUB_HUB_FLAG_STATE_OK 0x00 +#define CECHUB_HUB_FLAG_STATE_FAILURES 0x40 +#define CECHUB_HUB_FLAG_STATE_NOT_INST 0x80 +#define CECHUB_HUB_FLAG_STATE_UNUSABLE 0xc0 +#define CECHUB_HUB_FLAG_MASTER_HUB 0x20 /* HDAT < v9.x only */ +#define CECHUB_HUB_FLAG_GARD_MASK_VALID 0x08 /* HDAT < v9.x only */ +#define CECHUB_HUB_FLAG_SWITCH_MASK_PDT 0x04 /* HDAT < v9.x only */ +#define CECHUB_HUB_FLAG_FAB_BR0_PDT 0x02 /* HDAT < v9.x only */ +#define CECHUB_HUB_FLAG_FAB_BR1_PDT 0x01 /* HDAT < v9.x only */ + uint8_t nr_ports; /* HDAT < v9.x only */ + uint8_t fab_br0_pdt; /* p5ioc2 PCI-X or P8 PHB3's */ +#define CECHUB_HUB_FAB_BR0_PDT_PHB0 0x80 +#define CECHUB_HUB_FAB_BR0_PDT_PHB1 0x40 +#define CECHUB_HUB_FAB_BR0_PDT_PHB2 0x20 +#define CECHUB_HUB_FAB_BR0_PDT_PHB3 0x10 + uint8_t fab_br1_pdt; /* p5ioc2 & p7ioc PCI-E */ +#define CECHUB_HUB_FAB_BR1_PDT_PHB0 0x80 +#define CECHUB_HUB_FAB_BR1_PDT_PHB1 0x40 +#define CECHUB_HUB_FAB_BR1_PDT_PHB2 0x20 +#define CECHUB_HUB_FAB_BR1_PDT_PHB3 0x10 +#define CECHUB_HUB_FAB_BR1_PDT_PHB4 0x08 /* p7ioc only */ +#define CECHUB_HUB_FAB_BR1_PDT_PHB5 0x04 /* p7ioc only */ + __be16 iohub_id; /* the type of hub */ +#define CECHUB_HUB_P5IOC2 0x1061 /* from VPL1 */ +#define CECHUB_HUB_P7IOC 0x60e7 /* from VPL3 */ +#define CECHUB_HUB_MURANO 0x20ef /* Murano from spec */ +#define CECHUB_HUB_MURANO_SEGU 0x0001 /* Murano+Seguso from spec */ +#define CECHUB_HUB_VENICE_WYATT 0x0010 /* Venice+Wyatt from spec */ + __be32 ec_level; + __be32 aff_dom2; /* HDAT < v9.x only */ + __be32 aff_dom3; /* HDAT < v9.x only */ + __be64 reserved; + __be32 proc_chip_id; + + union { + /* HDAT < v9.x */ + struct { + __be32 gx_index; /* GX bus index on cpu */ + __be32 buid_ext; /* BUID Extension */ + __be32 xscom_chip_id; /* TORRENT ONLY */ + }; + /* HDAT >= v9.x */ + struct { + __be32 reserved1; + __be32 reserved2; + __be16 reserved3; + __be16 hw_topology; + }; + }; + __be32 mrid; + __be32 mem_map_vers; + union { + /* HDAT < v9.x */ + struct { + __be64 gx_ctrl_bar0; + __be64 gx_ctrl_bar1; + __be64 gx_ctrl_bar2; + __be64 gx_ctrl_bar3; + __be64 gx_ctrl_bar4; + __be32 sw_mask_pdt; + __be16 gard_mask; + __be16 gx_bus_speed; /* Version 0x58 */ + }; + + /* HDAT >= v9.x, HDIF version 0x6A or later */ + struct { + /* 4 values per PHB, 4 PHBs */ + __be64 phb_lane_eq[4][4]; + }; + }; +} __packed; + +/* We support structures as small as 0x68 bytes */ +#define CECHUB_IOHUB_MIN_SIZE 0x68 + +/* Child index 0: IO Daugther Card */ +#define CECHUB_CHILD_IO_KIDS 0 + +/* + * IO KID is a dauther card structure + */ +#define IOKID_FRU_ID_DATA 0 +#define IOKID_KW_VPD 1 + +/* + * Slot Location Code Array (aka SLCA) + * + * This is a pile of location codes referenced by various other + * structures such as the IO Hubs for things on the CEC. Not + * everything in there is a physical port. The SLCA is actually + * a tree which represent the topology of the system. + * + * The tree works as follow: A parent has a pointer to the first + * child. A child has a pointer to its parent. Siblings are + * consecutive entries. + * + * Note: If we ever support concurrent maintainance... this is + * completely rebuilt, invalidating all indices, though other + * structures that may reference SLCA by index will be rebuilt + * as well. + * + * Note that a lot of that stuff is based on VPD documentation + * such as the identification keywords. I will list the ones + * I manage to figure out without the doc separately. + */ +#define SLCA_HDIF_SIG "SLCA " + +/* Idata index 0 : SLCA root pointer + * + * The SLCA array is an HDIF array of all the entries. The tree + * structure is based on indices inside the entries and order of + * the entries + */ +#define SLCA_IDATA_ARRAY 0 + +/* Note: An "index" (or idx) is always an index into the SLCA array + * and "id" is a reference to some other object. + */ +struct slca_entry { + __be16 my_index; /* redundant, useful */ + __be16 rsrc_id; /* formerly VPD port number */ + uint8_t fru_id[2]; /* ASCII VPD ID */ +#define SLCA_ROOT_VPD_ID VPD_ID('V','V') +#define SLCA_SYSTEM_VPD_ID VPD_ID('S','V') + __be16 parent_index; /* Parent entry index */ + uint8_t flags; +#define SLCA_FLAG_NON_FUNCTIONAL 0x02 /* For redundant entries */ +#define SLCA_FLAG_IMBEDDED 0x01 /* not set => pluggable */ + uint8_t old_nr_child; /* Legacy: Nr of children */ + __be16 child_index; /* First child index */ + __be16 child_rsrc_id; /* Resource ID of first child */ + uint8_t loc_code_allen; /* Alloc len of loc code */ + uint8_t loc_code_len; /* Loc code len */ + uint8_t loc_code[LOC_CODE_SIZE]; /* NULL terminated (thus max 79 chr) */ + __be16 first_dup_idx; /* First redundant resource index */ + uint8_t nr_dups; /* Number of redundant entries */ + uint8_t reserved; + __be16 nr_child; /* New version */ + uint8_t install_indic; /* Installed indicator */ +#define SLCA_INSTALL_NO_HW_PDT 1 /* No HW presence detect */ +#define SLCA_INSTALL_INSTALLED 2 +#define SLCA_INSTALL_NOT_INSTALLED 3 + uint8_t vpd_collected; +#define SLCA_VPD_COLLECTED 2 +#define SLCA_VPD_NOT_COLLECTED 3 +} __packed; + +/* + * System VPD + */ +#define SYSVPD_HDIF_SIG "SYSVPD" + +/* Idata index 0 : FRU ID Data */ +#define SYSVPD_IDATA_FRU_ID 0 + +/* Idata index 1 : Keyword VPD */ +#define SYSVPD_IDATA_KW_VPD 1 + +/* Idata index 2 : Operational status */ +#define SYSVPD_IDATA_OP_STATUS 2 + +/* + * FRU keyword VPD structure + */ +#define FRUVPD_HDIF_SIG "FRUVPD" + +/* Idata index 0 : FRU ID Data */ +#define FRUVPD_IDATA_FRU_ID 0 + +/* Idata index 1 : Keyword VPD */ +#define FRUVPD_IDATA_KW_VPD 1 + +/* Idata index 2 : Operational status */ +#define FRUVPD_IDATA_OP_STATUS 2 + + +/* + * SPPACA structure. The SPIRA contain an array of these, one + * per processor thread + */ +#define PACA_HDIF_SIG "SPPACA" + +/* Idata index 0 : FRU ID Data */ +#define SPPACA_IDATA_FRU_ID 0 + +/* Idata index 1 : Keyword VPD */ +#define SPPACA_IDATA_KW_VPD 1 + +/* Idata index 2 : CPU ID data area */ +#define SPPACA_IDATA_CPU_ID 2 + +struct sppaca_cpu_id { + __be32 pir; + __be32 fru_id; + __be32 hardware_proc_id; +#define CPU_ID_VERIFY_MASK 0xC0000000 +#define CPU_ID_VERIFY_SHIFT 30 +#define CPU_ID_VERIFY_USABLE_NO_FAILURES 0 +#define CPU_ID_VERIFY_USABLE_FAILURES 1 +#define CPU_ID_VERIFY_NOT_INSTALLED 2 +#define CPU_ID_VERIFY_UNUSABLE 3 +#define CPU_ID_SECONDARY_THREAD 0x20000000 +#define CPU_ID_PACA_RESERVED 0x10000000 +#define CPU_ID_NUM_SECONDARY_THREAD_MASK 0x00FF0000 +#define CPU_ID_NUM_SECONDARY_THREAD_SHIFT 16 + __be32 verify_exists_flags; + __be32 chip_ec_level; + __be32 processor_chip_id; + __be32 logical_processor_id; + /* This is the resource number, too. */ + __be32 process_interrupt_line; + __be32 reserved1; + __be32 hardware_module_id; + __be64 ibase; + __be32 deprecated1; + __be32 physical_thread_id; + __be32 deprecated2; + __be32 ccm_node_id; + /* This fields are not always present, check struct size */ +#define SPIRA_CPU_ID_MIN_SIZE 0x40 + __be32 hw_card_id; + __be32 internal_drawer_node_id; + __be32 drawer_book_octant_blade_id; + __be32 memory_interleaving_scope; + __be32 lco_target; +} __packed; + +/* Idata index 3 : Timebase data */ +#define SPPACA_IDATA_TIMEBASE 3 + +struct sppaca_cpu_timebase { + __be32 cycle_time; + __be32 time_base; + __be32 actual_clock_speed; + __be32 memory_bus_frequency; +} __packed; + +/* Idata index 4 : Cache size structure */ +#define SPPACA_IDATA_CACHE_SIZE 4 + +struct sppaca_cpu_cache { + __be32 icache_size_kb; + __be32 icache_line_size; + __be32 l1_dcache_size_kb; + __be32 l1_dcache_line_size; + __be32 l2_dcache_size_kb; + __be32 l2_line_size; + __be32 l3_dcache_size_kb; + __be32 l3_line_size; + __be32 dcache_block_size; + __be32 icache_block_size; + __be32 dcache_assoc_sets; + __be32 icache_assoc_sets; + __be32 dtlb_entries; + __be32 dtlb_assoc_sets; + __be32 itlb_entries; + __be32 itlb_assoc_sets; + __be32 reservation_size; + __be32 l2_cache_assoc_sets; + __be32 l35_dcache_size_kb; + __be32 l35_cache_line_size; +}; + +/* Idata index 6 : CPU Attributes */ +#define SPPACA_IDATA_CPU_ATTR 6 + +#define sppaca_cpu_attr sppcia_cpu_attr + +/* + * SPPCIA structure. The SPIRA contain an array of these, one + * per processor core + */ +#define SPPCIA_HDIF_SIG "SPPCIA" + +/* Idata index 0 : Core unique data */ +#define SPPCIA_IDATA_CORE_UNIQUE 0 + +/* NOTE: This is the same layout as "struct sppaca_cpu_id", + * with essentially some fields removed and a reserved + * field added + */ +struct sppcia_core_unique { + __be32 reserved; + __be32 proc_fru_id; + __be32 hw_proc_id; + __be32 verif_exist_flags; /* Same as PACA */ + __be32 chip_ec_level; + __be32 proc_chip_id; + __be32 reserved2; + __be32 reserved3; + __be32 reserved4; + __be32 hw_module_id; + __be64 reserved5; + __be32 reserved6; + __be32 reserved7; + __be32 reserved8; + __be32 ccm_node_id; + __be32 hw_card_id; + __be32 internal_drawer_node_id; + __be32 drawer_book_octant_blade_id; + __be32 memory_interleaving_scope; + __be32 lco_target; + __be32 reserved9; +} __packed; + +/* Idata index 1 : CPU Time base structure */ +#define SPPCIA_IDATA_TIMEBASE 1 + +#define sppcia_cpu_timebase sppaca_cpu_timebase + +/* Idata index 2 : CPU Cache Size Structure */ +#define SPPCIA_IDATA_CPU_CACHE 2 + +#define sppcia_cpu_cache sppaca_cpu_cache + +/* Idata index 3 : Thread Array Data + * + * HDIF array of + */ +#define SPPCIA_IDATA_THREAD_ARRAY 3 + +struct sppcia_cpu_thread { + __be32 proc_int_line; + __be32 phys_thread_id; + __be64 ibase; + __be32 pir; +} __packed; + +/* Idata index 4 : CPU Attributes */ +#define SPPCIA_IDATA_CPU_ATTR 4 + +struct sppcia_cpu_attr { +#define CPU_ATTR_UNIFIED_PL1 0x80 +#define CPU_ATTR_SPLIT_TLB 0x40 +#define CPU_ATTR_TLBIA 0x20 +#define CPU_ATTR_PERF_MONITOR 0x10 +#define CPU_ATTR_EXTERN_CONT 0x02 + __be32 attr; +} __packed; + +/* + * Processor Chip Related Data. The SPIRA contain an array of these, one + * per chip + */ +#define SPPCRD_HDIF_SIG "SPPCRD" + +/* Idata index 0 : Chip info */ +#define SPPCRD_IDATA_CHIP_INFO 0 + +struct sppcrd_chip_info { + __be32 proc_chip_id; + __be32 verif_exist_flags; +#define CHIP_VERIFY_MASK 0xC0000000 +#define CHIP_VERIFY_SHIFT 30 +#define CHIP_VERIFY_USABLE_NO_FAILURES 0 +#define CHIP_VERIFY_USABLE_FAILURES 1 +#define CHIP_VERIFY_NOT_INSTALLED 2 +#define CHIP_VERIFY_UNUSABLE 3 + __be32 nx_state; + __be32 pore_state; + __be32 xscom_id; + /* Version 0xA */ + __be32 reserved; + __be32 dbob_id; + __be32 occ_state; +} __packed; + +/* Idata index 1 : Chip TOD */ +#define SPPCRD_IDATA_CHIP_TOD 1 + +struct sppcrd_chip_tod { + __be32 flags; + /* CHIPTOD_ID_... values */ + __be32 ctrl_reg_internal; + __be32 tod_ctrl_reg; +} __packed; + +/* Idata index 2 : FRU ID */ +#define SPPCRD_IDATA_FRU_ID 2 + +/* Idata index 3 : ASCII Keyword data */ +#define SPPCRD_IDATA_KW_VPD 3 + +/* Idata index 4 : Module VPD */ +#define SPPCRD_IDATA_MODULE_VPD 4 + + +/* + * Host Services Data. + */ +#define HSERV_HDIF_SIG "HOSTSR" + +/* Idata index 0 : System attribute data */ +#define HSERV_IDATA_SYS_ATTR 0 + +static inline const char *cpu_state(u32 flags) +{ + switch ((flags & CPU_ID_VERIFY_MASK) >> CPU_ID_VERIFY_SHIFT) { + case CPU_ID_VERIFY_USABLE_NO_FAILURES: + return "OK"; + case CPU_ID_VERIFY_USABLE_FAILURES: + return "FAILURES"; + case CPU_ID_VERIFY_NOT_INSTALLED: + return "NOT-INSTALLED"; + case CPU_ID_VERIFY_UNUSABLE: + return "UNUSABLE"; + } + return "**UNKNOWN**"; +} +#endif /* __SPIRA_H */ diff --git a/hdata/test/Makefile.check b/hdata/test/Makefile.check new file mode 100644 index 0000000..e03a021 --- /dev/null +++ b/hdata/test/Makefile.check @@ -0,0 +1,18 @@ +# -*-Makefile-*- + +check: hdata-check + +# Add some test ntuples for open source version... +hdata-check: hdata/test/hdata_to_dt +# $(VALGRIND) hdata/test/hdata_to_dt -q hdata/test/spira.bin hdata/test/ntuples.bin + +hdata/test/stubs.o: hdata/test/stubs.c + $(HOSTCC) $(HOSTCFLAGS) -g -c -o $@ $< + +hdata/test/hdata_to_dt: hdata/test/hdata_to_dt.c hdata/test/stubs.o + $(HOSTCC) $(HOSTCFLAGS) -O0 -g -I hdata -I include -I . -I libfdt -o $@ $< hdata/test/stubs.o + +clean: hdata-test-clean + +hdata-test-clean: + $(RM) hdata/test/*.o hdata/test/hdata_to_dt diff --git a/hdata/test/hdata_to_dt.c b/hdata/test/hdata_to_dt.c new file mode 100644 index 0000000..4215740 --- /dev/null +++ b/hdata/test/hdata_to_dt.c @@ -0,0 +1,215 @@ +/* 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. + */ +/* Given a hdata dump, output the device tree. */ +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdint.h> + +#include <interrupts.h> + +/* Our actual map. */ +static void *spira_heap; +static size_t spira_heap_size; +static uint64_t base_addr; + +/* Override ntuple_addr. */ +#define ntuple_addr ntuple_addr +struct spira_ntuple; +static void *ntuple_addr(const struct spira_ntuple *n); + +/* Stuff which core expects. */ +#define __this_cpu ((struct cpu_thread *)NULL) +#define zalloc(expr) calloc(1, (expr)) + +/* Don't include processor-specific stuff. */ +#define __PROCESSOR_H +#define PVR_TYPE(_pvr) _pvr + +/* PVR definitions */ +#define PVR_TYPE_P7 0x003f +#define PVR_TYPE_P7P 0x004a +#define PVR_TYPE_P8E 0x004b +#define PVR_TYPE_P8 0x004d + +#define SPR_PVR 0x11f /* RO: Processor version register */ + +#define __CPU_H +struct cpu_thread { + uint32_t pir; +}; + +struct cpu_thread __boot_cpu, *boot_cpu = &__boot_cpu; +static unsigned long fake_pvr_type = PVR_TYPE_P7; + +static inline unsigned long mfspr(unsigned int spr) +{ + assert(spr == SPR_PVR); + return fake_pvr_type; +} + +struct dt_node *add_ics_node(void) +{ + return NULL; +} + +#include <config.h> +#include <bitutils.h> + +/* Your pointers won't be correct, that's OK. */ +#define spira_check_ptr(ptr, file, line) ((ptr) != NULL) + +#include "../cpu-common.c" +#include "../fsp.c" +#include "../hdif.c" +#include "../iohub.c" +#include "../memory.c" +#include "../paca.c" +#include "../pcia.c" +#include "../spira.c" +#include "../vpd.c" +#include "../vpd-common.c" +#include "../slca.c" +#include "../hostservices.c" +#include "../../core/vpd.c" +#include "../../core/device.c" +#include "../../core/chip.c" + +#include <err.h> + +char __rodata_start[1], __rodata_end[1]; + +enum proc_gen proc_gen = proc_gen_p7; + +static void *ntuple_addr(const struct spira_ntuple *n) +{ + uint64_t addr = be64_to_cpu(n->addr); + if (n->addr == 0) + return NULL; + assert(addr >= base_addr); + assert(addr < base_addr + spira_heap_size); + return spira_heap + ((unsigned long)addr - base_addr); +} + +static void indent_num(unsigned indent) +{ + unsigned int i; + + for (i = 0; i < indent; i++) + putc(' ', stdout); +} + +static void dump_val(const void *prop, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) + printf("%02x ", ((unsigned char *)prop)[i]); +} + +/* Make sure valgrind knows these are undefined bytes. */ +static void undefined_bytes(void *p, size_t len) +{ + void *undef = malloc(len); + memcpy(p, undef, len); + free(undef); +} + +static void dump_dt(const struct dt_node *root, unsigned indent) +{ + const struct dt_node *i; + const struct dt_property *p; + + list_for_each(&root->properties, p, list) { + indent_num(indent); + printf("prop: %s size: %zu val: ", p->name, p->len); + dump_val(p->prop, p->len); + printf("\n"); + } + + list_for_each(&root->children, i, list) + dump_dt(i, indent + 2); +} + +int main(int argc, char *argv[]) +{ + int fd, r; + bool verbose = false, quiet = false; + + while (argv[1]) { + if (strcmp(argv[1], "-v") == 0) { + verbose = true; + argv++; + argc--; + } else if (strcmp(argv[1], "-q") == 0) { + quiet = true; + argv++; + argc--; + } else + break; + } + + if (argc != 3) + errx(1, "Usage: hdata [-v|-q] <spira-dump> <heap-dump>"); + + /* Copy in spira dump (assumes little has changed!). */ + fd = open(argv[1], O_RDONLY); + if (fd < 0) + err(1, "opening %s", argv[1]); + r = read(fd, &spira, sizeof(spira)); + if (r < sizeof(spira.hdr)) + err(1, "reading %s gave %i", argv[1], r); + if (verbose) + printf("verbose: read spira %u bytes\n", r); + close(fd); + + undefined_bytes((void *)&spira + r, sizeof(spira) - r); + + base_addr = be64_to_cpu(spira.ntuples.heap.addr); + if (!base_addr) + errx(1, "Invalid base addr"); + if (verbose) + printf("verbose: map.base_addr = %llx\n", (long long)base_addr); + + fd = open(argv[2], O_RDONLY); + if (fd < 0) + err(1, "opening %s", argv[2]); + spira_heap_size = lseek(fd, 0, SEEK_END); + spira_heap = mmap(NULL, spira_heap_size, PROT_READ, MAP_SHARED, fd, 0); + if (spira_heap == MAP_FAILED) + err(1, "mmaping %s", argv[3]); + if (verbose) + printf("verbose: mapped %zu at %p\n", + spira_heap_size, spira_heap); + close(fd); + + if (quiet) { + fclose(stdout); + fclose(stderr); + } + + parse_hdat(false, 0); + + if (!quiet) + dump_dt(dt_root, 0); + + dt_free(dt_root); + return 0; +} diff --git a/hdata/test/stubs.c b/hdata/test/stubs.c new file mode 100644 index 0000000..8e1bd1b --- /dev/null +++ b/hdata/test/stubs.c @@ -0,0 +1,47 @@ +/* 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. + */ +/* Add any stub functions required for linking here. */ +#include <stdlib.h> + +static void stub_function(void) +{ + abort(); +} + +#define STUB(fnname) \ + void fnname(void) __attribute__((weak, alias ("stub_function"))) + +STUB(fdt_begin_node); +STUB(fdt_property); +STUB(fdt_end_node); +STUB(fdt_create); +STUB(fdt_add_reservemap_entry); +STUB(fdt_finish_reservemap); +STUB(fdt_strerror); +STUB(fdt_check_header); +STUB(_fdt_check_node_offset); +STUB(fdt_next_tag); +STUB(fdt_string); +STUB(fdt_get_name); +STUB(dt_first); +STUB(dt_next); +STUB(dt_has_node_property); +STUB(dt_get_address); +STUB(op_display); +STUB(fsp_fetch_data); +STUB(get_ics_phandle); +STUB(get_psi_interrupt); +STUB(fsp_adjust_lid_side); diff --git a/hdata/vpd-common.c b/hdata/vpd-common.c new file mode 100644 index 0000000..eecda7e --- /dev/null +++ b/hdata/vpd-common.c @@ -0,0 +1,38 @@ +/* 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 <skiboot.h> +#include <vpd.h> +#include <string.h> +#include <device.h> + +static const struct machine_info machine_table[] = { + {"8247-21L", "IBM Power System S812L"}, + {"8247-22L", "IBM Power System S822L"}, + {"8247-24L", "IBM Power System S824L"}, + {"8286-41A", "IBM Power System S814"}, + {"8286-22A", "IBM Power System S822"}, + {"8286-42A", "IBM Power System S824"}, +}; + +const struct machine_info *machine_info_lookup(char *mtm) +{ + int i; + for(i = 0; i < ARRAY_SIZE(machine_table); i++) + if (!strcmp(machine_table[i].mtm, mtm)) + return &machine_table[i]; + return NULL; +} diff --git a/hdata/vpd.c b/hdata/vpd.c new file mode 100644 index 0000000..e568a06 --- /dev/null +++ b/hdata/vpd.c @@ -0,0 +1,851 @@ +/* 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 <skiboot.h> +#include <vpd.h> +#include <string.h> +#include "spira.h" +#include "hdata.h" +#include <device.h> +#include "hdata.h" + +struct card_info { + const char *ccin; /* Customer card identification number */ + const char *description; +}; + +static const struct card_info card_table[] = { + {"2B06", "System planar 2S4U"}, + {"2B07", "System planar 1S4U"}, + {"2B2E", "System planar 2S2U"}, + {"2B2F", "System planar 1S2U"}, + {"2CD4", "System planar 2S4U"}, + {"2CD5", "System planar 1S4U"}, + {"2CD6", "System planar 2S2U"}, + {"2CD7", "System planar 1S2U"}, + {"2CD7", "System planar 1S2U"}, + {"2B09", "Base JBOD, RAID and Backplane HD"}, + {"57D7", "Split JBOD, RAID Card"}, + {"2B0B", "Native I/O Card"}, + + /* Anchor cards */ + {"52FE", "System Anchor Card - IBM Power 824"}, + {"52F2", "System Anchor Card - IBM Power 814"}, + {"52F5", "System Anchor Card - IBM Power 822"}, + {"561A", "System Anchor Card - IBM Power 824L"}, + {"524D", "System Anchor Card - IBM Power 822L"}, + {"560F", "System Anchor Card - IBM Power 812L"}, + {"561C", "System Anchor Card - DS8870"}, + + /* Memory DIMMs */ + {"31E0", "16GB CDIMM"}, + {"31E8", "16GB CDIMM"}, + {"31E1", "32GB CDIMM"}, + {"31E9", "32GB CDIMM"}, + {"31E2", "64GB CDIMM"}, + {"31EA", "64GB CDIMM"}, + + /* Power supplies */ + {"2B1D", "Power Supply 900W AC"}, + {"2B1E", "Power Supply 1400W AC"}, + {"2B75", "Power Supply 1400W HVDC"}, + + /* Fans */ + {"2B1F", "Fan 4U (A1, A2, A3, A4)"}, + {"2B29", "Fan 2U (A1, A2, A3, A4, A5, A6)"}, + + /* Other cards */ +}; + +static const struct card_info *card_info_lookup(char *ccin) +{ + int i; + for(i = 0; i < ARRAY_SIZE(card_table); i++) + if (!strcmp(card_table[i].ccin, ccin)) + return &card_table[i]; + return NULL; +} + +static void vpd_vini_parse(struct dt_node *node, + const void *fruvpd, unsigned int fruvpd_sz) +{ + const void *kw; + char *str; + uint8_t kwsz; + const struct card_info *cinfo; + + /* FRU Stocking Part Number */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "FN", &kwsz); + if (kw) { + str = zalloc(kwsz + 1); + if (!str) + goto no_memory; + memcpy(str, kw, kwsz); + dt_add_property_string(node, "fru-number", str); + free(str); + } + + /* Serial Number */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "SN", &kwsz); + if (kw) { + str = zalloc(kwsz + 1); + if (!str) + goto no_memory; + memcpy(str, kw, kwsz); + dt_add_property_string(node, "serial-number", str); + free(str); + } + + /* Part Number */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "PN", &kwsz); + if (kw) { + str = zalloc(kwsz + 1); + if (!str) + goto no_memory; + memcpy(str, kw, kwsz); + dt_add_property_string(node, "part-number", str); + free(str); + } + + /* Customer Card Identification Number (CCIN) */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "CC", &kwsz); + if (kw) { + str = zalloc(kwsz + 1); + if (!str) + goto no_memory; + memcpy(str, kw, kwsz); + dt_add_property_string(node, "ccin", str); + cinfo = card_info_lookup(str); + if (cinfo) + dt_add_property_string(node, + "description", cinfo->description); + free(str); + } + return; +no_memory: + prerror("VPD: memory allocation failure in VINI parsing\n"); +} + +static const char *vpd_map_name(const char *vpd_name) +{ + /* vpd_name is a 2 char array */ + switch (vpd_name[0]) { + case 'A': + switch (vpd_name[1]) { + case 'A': + return "ac-power-supply"; + case 'M': + return "air-mover"; + case 'V': + return "anchor-card"; + } + break; + case 'B': + switch (vpd_name[1]) { + case 'A': + return "bus-adapter-card"; + case 'C': + return "battery-charger"; + case 'D': + return "bus-daughter-card"; + case 'E': + return "bus-expansion-card"; + case 'P': + return "backplane"; + case 'R': + return "backplane-riser"; + case 'X': + return "backplane-extender"; + } + break; + case 'C': + switch (vpd_name[1]) { + case 'A': + return "calgary-bridge"; + case 'B': + return "infiniband-connector"; + case 'C': + return "clock-card"; + case 'D': + return "card-connector"; + case 'E': + return "ethernet-connector"; + case 'L': + return "calgary-phb"; + case 'I': + return "capacity-card"; + case 'O': + return "sma-connector"; + case 'P': + return "processor-capacity-card"; + case 'R': + return "rio-connector"; + case 'S': + return "serial-connector"; + case 'U': + return "usb-connector"; + } + break; + case 'D': + switch (vpd_name[1]) { + case 'B': + return "dasd-backplane"; + case 'C': + return "drawer-card"; + case 'E': + return "drawer-extension"; + case 'I': + return "drawer-interposer"; + case 'L': + return "p7ih-dlink-connector"; + case 'T': + return "legacy-pci-card"; + case 'V': + return "media-drawer-led"; + } + break; + case 'E': + switch (vpd_name[1]) { + case 'I': + return "enclosure-led"; + case 'F': + return "enclosure-fault-led"; + case 'S': + return "embedded-sas"; + case 'T': + return "ethernet-riser"; + case 'V': + return "enclosure"; + } + break; + case 'F': + switch (vpd_name[1]) { + case 'M': + return "frame"; + } + break; + case 'H': + switch (vpd_name[1]) { + case 'B': + return "host-rio-pci-card"; + case 'D': + return "high-speed-card"; + case 'M': + return "hmc-connector"; + } + break; + case 'I': + switch (vpd_name[1]) { + case 'B': + return "io-backplane"; + case 'C': + return "io-card"; + case 'D': + return "ide-connector"; + case 'I': + return "io-drawer-led"; + case 'P': + return "interplane-card"; + case 'S': + return "smp-vbus-cable"; + case 'T': + return "enclosure-cable"; + case 'V': + return "io-enclosure"; + } + break; + case 'K': + switch (vpd_name[1]) { + case 'V': + return "keyboard-led"; + } + break; + case 'L': + switch (vpd_name[1]) { + case '2': + return "l2-cache-module"; + case '3': + return "l3-cache-module"; + case 'C': + return "squadrons-light-connector"; + case 'R': + return "p7ih-connector"; + case 'O': + return "system-locate-led"; + case 'T': + return "squadrons-light-strip"; + } + break; + case 'M': + switch (vpd_name[1]) { + case 'B': + return "media-backplane"; + case 'E': + return "map-extension"; + case 'M': + return "mip-meter"; + case 'S': + return "ms-dimm"; + } + break; + case 'N': + switch (vpd_name[1]) { + case 'B': + return "nvram-battery"; + case 'C': + return "sp-node-controller"; + case 'D': + return "numa-dimm"; + } + break; + case 'O': + switch (vpd_name[1]) { + case 'D': + return "cuod-card"; + case 'P': + return "op-panel"; + case 'S': + return "oscillator"; + } + break; + case 'P': + switch (vpd_name[1]) { + case '2': + return "ioc"; + case '5': + return "ioc-bridge"; + case 'B': + return "io-drawer-backplane"; + case 'C': + return "power-capacitor"; + case 'D': + return "processor-card"; + case 'F': + return "processor"; + case 'I': + return "ioc-phb"; + case 'O': + return "spcn"; + case 'N': + return "spcn-connector"; + case 'R': + return "pci-riser-card"; + case 'S': + return "power-supply"; + case 'T': + return "pass-through-card"; + case 'X': + return "psc-sync-card"; + case 'W': + return "power-connector"; + } + break; + case 'R': + switch (vpd_name[1]) { + case 'G': + return "regulator"; + case 'I': + return "riser"; + case 'K': + return "rack-indicator"; + case 'W': + return "riscwatch-connector"; + } + break; + case 'S': + switch (vpd_name[1]) { + case 'A': + return "sys-attn-led"; + case 'B': + return "backup-sysvpd"; + case 'C': + return "scsi-connector"; + case 'D': + return "sas-connector"; + case 'I': + return "scsi-ide-converter"; + case 'L': + return "phb-slot"; + case 'P': + return "service-processor"; + case 'R': + return "service-card"; + case 'S': + return "soft-switch"; + case 'V': + return "system-vpd"; + case 'Y': + return "legacy-sysvpd"; + } + break; + case 'T': + switch (vpd_name[1]) { + case 'D': + return "tod-clock"; + case 'I': + return "torrent-pcie-phb"; + case 'L': + return "torrent-riser"; + case 'M': + return "thermal-sensor"; + case 'P': + return "tpmd-adapter"; + case 'R': + return "torrent-bridge"; + } + break; + case 'V': + switch (vpd_name[1]) { + case 'V': + return "root-node-vpd"; + } + break; + case 'W': + switch (vpd_name[1]) { + case 'D': + return "water_device"; + } + break; + } + return "Unknown"; +} + +static bool valid_child_entry(const struct slca_entry *entry) +{ + if (!entry) + return false; + + /* + * Skip entries with independent ntuple FRUVPD/MSVPD, etc., + * representations, since they have a unique PN, FN, SN, et al. + * We add details for those devices via the ntuple walk. + */ + switch (entry->fru_id[0]) { + case 'A': + switch (entry->fru_id[1]) { + case 'V': /* AV */ + return false; + } + break; + case 'B': + switch (entry->fru_id[1]) { + case 'P': /* BP */ + case 'X': /* BX */ + return false; + } + break; + case 'C': + switch (entry->fru_id[1]) { + case 'C': /* CC */ + return false; + } + break; + case 'D': + switch (entry->fru_id[1]) { + case 'B': /* DB */ + return false; + } + break; + case 'E': + switch (entry->fru_id[1]) { + case 'V': /* EV */ + return false; + } + break; + case 'M': + switch (entry->fru_id[1]) { + case 'S': /* MS */ + return false; + } + break; + case 'O': + switch (entry->fru_id[1]) { + case 'P': /* OP */ + return false; + } + break; + case 'R': + switch (entry->fru_id[1]) { + case 'I': /* RI */ + return false; + } + break; + case 'P': + switch (entry->fru_id[1]) { + case '2': /* P2 */ + case '5': /* P5 */ + case 'F': /* PF */ + return false; + } + break; + case 'S': + switch (entry->fru_id[1]) { + case 'P': /* SP */ + return false; + } + break; + case 'T': + switch (entry->fru_id[1]) { + case 'P': /* TP */ + return false; + } + break; + default: + break; + } + + if ((entry->install_indic == SLCA_INSTALL_INSTALLED) && + (entry->vpd_collected == SLCA_VPD_COLLECTED)) + return true; + + return false; +} + +static void vpd_add_children(struct dt_node *parent, uint16_t slca_index) +{ + const struct slca_entry *s_entry, *child; + uint16_t current_child_index, max_index; + + s_entry = slca_get_entry(slca_index); + if (!s_entry || (s_entry->nr_child == 0)) + return; + + /* + * This slca_entry has children. Parse the children array + * and add nodes for valid entries. + * + * A child entry is valid if all of the following criteria is met + * a. SLCA_INSTALL_INSTALLED is set in s_entry->install_indic + * b. SLCA_VPD_COLLECTED is set in s_entry->vpd_collected + * c. The SLCA is not a duplicate entry. + */ + + /* current_index tracks where we are right now in the array */ + current_child_index = be16_to_cpu(s_entry->child_index); + + /* max_index tracks how far down the array we must traverse */ + max_index = be16_to_cpu(s_entry->child_index) + + be16_to_cpu(s_entry->nr_child); + + while (current_child_index < max_index) { + child = slca_get_entry(current_child_index); + if (!child) + return; + + if (valid_child_entry(child)) { + const char *name; + uint64_t addr; + struct dt_node *node; + + /* create new node, add location code */ + name = vpd_map_name(child->fru_id); + addr = (uint64_t)be16_to_cpu(child->rsrc_id); + node = dt_new_addr(parent, name, addr); + if (!node) { + prerror("VPD: Creating node at %s@%llx failed\n", + name, addr); + return; + } + slca_vpd_add_loc_code(node, be16_to_cpu(child->my_index)); + + /* Add child FRU type */ + dt_add_property(node, "fru-type", child->fru_id, 2); + + /* recursively add children */ + vpd_add_children(node, be16_to_cpu(child->my_index)); + } + + /* Skip dups -- currently we presume dups are contiguous */ + if (child->nr_dups > 0) + current_child_index += child->nr_dups; + current_child_index++; + } + return; +} + +struct dt_node *dt_add_vpd_node(const struct HDIF_common_hdr *hdr, + int indx_fru, int indx_vpd) +{ + const void *fruvpd; + unsigned int fruvpd_sz; + unsigned int fru_id_sz; + uint64_t addr; + struct dt_node *dt_vpd; + struct dt_node *node; + const struct spira_fru_id *fru_id; + const struct slca_entry *s_entry; + const char *vpd_name; + const char *name; + int len; + char *lname; + + fru_id = HDIF_get_idata(hdr, indx_fru, &fru_id_sz); + if (!fru_id) + return NULL; + + s_entry = slca_get_entry(be16_to_cpu(fru_id->slca_index)); + if (valid_child_entry(s_entry)) /* Don't populate child VPD here */ + return NULL; + + fruvpd = HDIF_get_idata(hdr, indx_vpd, &fruvpd_sz); + if (!CHECK_SPPTR(fruvpd)) + return NULL; + + vpd_name = slca_get_vpd_name(be16_to_cpu(fru_id->slca_index)); + if (!vpd_name) { + prerror("VPD: VPD name at index %d couldn't be found\n", + fru_id->slca_index); + return NULL; + } + + dt_vpd = dt_find_by_path(dt_root, "/vpd"); + if (!dt_vpd) + return NULL; + + /* Get node name */ + name = vpd_map_name(vpd_name); + addr = (uint64_t)be16_to_cpu(fru_id->rsrc_id); + len = strlen(name) + STR_MAX_CHARS(addr) + 2; + lname = zalloc(len); + if (!lname) { + prerror("VPD: Failed to allocate memory\n"); + return NULL; + } + snprintf(lname, len, "%s@%llx", name, (long long)addr); + + /* + * FRU can be a child of some other FRU. Make sure + * we have not added this node already. + */ + node = dt_find_by_path(dt_vpd, lname); + if (node) { + free(lname); + return NULL; + } + + node = dt_new(dt_vpd, lname); + if (!node) { + free(lname); + return NULL; + } + + /* Parse VPD fields */ + dt_add_property(node, "ibm,vpd", fruvpd, fruvpd_sz); + vpd_vini_parse(node, fruvpd, fruvpd_sz); + + /* Location code */ + slca_vpd_add_loc_code(node, be16_to_cpu(fru_id->slca_index)); + /* Add FRU label */ + dt_add_property(node, "fru-type", vpd_name, 2); + vpd_add_children(node, be16_to_cpu(fru_id->slca_index)); + + free(lname); + return node; +} + +static void sysvpd_parse(void) +{ + const char *model; + const char *system_id; + const char *brand; + char *str; + uint8_t sz; + const void *sysvpd; + unsigned int sysvpd_sz; + unsigned int fru_id_sz; + struct dt_node *dt_vpd; + const struct spira_fru_id *fru_id; + struct HDIF_common_hdr *sysvpd_hdr; + const struct machine_info *mi; + + sysvpd_hdr = get_hdif(&spira.ntuples.system_vpd, SYSVPD_HDIF_SIG); + if (!sysvpd_hdr) + goto no_sysvpd; + + fru_id = HDIF_get_idata(sysvpd_hdr, SYSVPD_IDATA_FRU_ID, &fru_id_sz); + if (!fru_id) + goto no_sysvpd;; + + sysvpd = HDIF_get_idata(sysvpd_hdr, SYSVPD_IDATA_KW_VPD, &sysvpd_sz); + if (!CHECK_SPPTR(sysvpd)) + goto no_sysvpd; + + /* Add system VPD */ + dt_vpd = dt_find_by_path(dt_root, "/vpd"); + if (dt_vpd) { + dt_add_property(dt_vpd, "ibm,vpd", sysvpd, sysvpd_sz); + slca_vpd_add_loc_code(dt_vpd, be16_to_cpu(fru_id->slca_index)); + } + + model = vpd_find(sysvpd, sysvpd_sz, "VSYS", "TM", &sz); + if (!model) + goto no_sysvpd; + str = zalloc(sz + 1); + if (!str) + goto no_sysvpd; + memcpy(str, model, sz); + dt_add_property_string(dt_root, "model", str); + mi = machine_info_lookup(str); + if (mi) + dt_add_property_string(dt_root, "model-name", mi->name); + free(str); + dt_add_property_string(dt_root, "vendor", "IBM"); + + system_id = vpd_find(sysvpd, sysvpd_sz, "VSYS", "SE", &sz); + if (!system_id) + goto no_sysid; + str = zalloc(sz + 1); + if (!str) + goto no_sysid; + memcpy(str, system_id, sz); + dt_add_property_string(dt_root, "system-id", str); + free(str); + + brand = vpd_find(sysvpd, sysvpd_sz, "VSYS", "BR", &sz); + if (!brand) + goto no_brand; + str = zalloc(sz + 1); + if (!str) + goto no_brand; + memcpy(str, brand, sz); + dt_add_property_string(dt_root, "system-brand", str); + free(str); + + return; + +no_brand: + dt_add_property_string(dt_root, "system-brand", "Unknown"); + return; + +no_sysid: + dt_add_property_string(dt_root, "system-id", "Unknown"); + return; + + no_sysvpd: + dt_add_property_string(dt_root, "model", "Unknown"); +} + +static void iokid_vpd_parse(const struct HDIF_common_hdr *iohub_hdr) +{ + const struct HDIF_child_ptr *iokids; + const struct HDIF_common_hdr *iokid; + unsigned int i; + + iokids = HDIF_child_arr(iohub_hdr, CECHUB_CHILD_IO_KIDS); + if (!CHECK_SPPTR(iokids)) { + prerror("VPD: No IOKID child array\n"); + return; + } + + for (i = 0; i < be32_to_cpu(iokids->count); i++) { + iokid = HDIF_child(iohub_hdr, iokids, i, + IOKID_FRU_HDIF_SIG); + /* IO KID VPD structure layout is similar to FRUVPD */ + if (iokid) + dt_add_vpd_node(iokid, + FRUVPD_IDATA_FRU_ID, FRUVPD_IDATA_KW_VPD); + } +} + +static void iohub_vpd_parse(void) +{ + const struct HDIF_common_hdr *iohub_hdr; + const struct cechub_hub_fru_id *fru_id_data; + unsigned int i, vpd_sz, fru_id_sz; + + if (!get_hdif(&spira.ntuples.cec_iohub_fru, CECHUB_FRU_HDIF_SIG)) { + prerror("VPD: Could not find IO HUB FRU data\n"); + return; + } + + for_each_ntuple_idx(&spira.ntuples.cec_iohub_fru, iohub_hdr, + i, CECHUB_FRU_HDIF_SIG) { + + fru_id_data = HDIF_get_idata(iohub_hdr, + CECHUB_FRU_ID_DATA_AREA, + &fru_id_sz); + + /* P8, IO HUB is on processor card and we have a + * daughter card array + */ + if (fru_id_data && + be32_to_cpu(fru_id_data->card_type) == CECHUB_FRU_TYPE_CPU_CARD) { + iokid_vpd_parse(iohub_hdr); + continue; + } + + /* On P7, the keyword VPD will not be NULL */ + if (HDIF_get_idata(iohub_hdr, + CECHUB_ASCII_KEYWORD_VPD, &vpd_sz)) + dt_add_vpd_node(iohub_hdr, CECHUB_FRU_ID_DATA, + CECHUB_ASCII_KEYWORD_VPD); + } +} + +static void _vpd_parse(struct spira_ntuple tuple) +{ + const struct HDIF_common_hdr *fruvpd_hdr; + unsigned int i; + + if (!get_hdif(&tuple, FRUVPD_HDIF_SIG)) + return; + + for_each_ntuple_idx(&tuple, fruvpd_hdr, i, FRUVPD_HDIF_SIG) + dt_add_vpd_node(fruvpd_hdr, + FRUVPD_IDATA_FRU_ID, FRUVPD_IDATA_KW_VPD); +} + +void vpd_parse(void) +{ + const struct HDIF_common_hdr *fruvpd_hdr; + + /* Enclosure */ + _vpd_parse(spira.ntuples.nt_enclosure_vpd); + + /* Backplane */ + _vpd_parse(spira.ntuples.backplane_vpd); + + /* System VPD uses the VSYS record, so its special */ + sysvpd_parse(); + + /* clock card -- does this use the FRUVPD sig? */ + _vpd_parse(spira.ntuples.clock_vpd); + + /* Anchor card */ + _vpd_parse(spira.ntuples.anchor_vpd); + + /* Op panel -- does this use the FRUVPD sig? */ + _vpd_parse(spira.ntuples.op_panel_vpd); + + /* External cache FRU vpd -- does this use the FRUVPD sig? */ + _vpd_parse(spira.ntuples.ext_cache_fru_vpd); + + /* Misc CEC FRU */ + _vpd_parse(spira.ntuples.misc_cec_fru_vpd); + + /* CEC IO HUB FRU */ + iohub_vpd_parse(); + + /* + * SP subsystem -- while the rest of the SPINFO structure is + * different, the FRU ID data and pointer pair to keyword VPD + * are the same offset as a FRUVPD entry. So reuse it + */ + fruvpd_hdr = get_hdif(&spira.ntuples.sp_subsys, SPSS_HDIF_SIG); + if (fruvpd_hdr) + dt_add_vpd_node(fruvpd_hdr, + FRUVPD_IDATA_FRU_ID, FRUVPD_IDATA_KW_VPD); +} |