diff options
Diffstat (limited to 'roms/skiboot/hdata/spira.c')
-rw-r--r-- | roms/skiboot/hdata/spira.c | 1901 |
1 files changed, 1901 insertions, 0 deletions
diff --git a/roms/skiboot/hdata/spira.c b/roms/skiboot/hdata/spira.c new file mode 100644 index 000000000..baa23751d --- /dev/null +++ b/roms/skiboot/hdata/spira.c @@ -0,0 +1,1901 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2019 IBM Corp. */ + +#include <inttypes.h> +#include <device.h> +#include <cpu.h> +#include <vpd.h> +#include <interrupts.h> +#include <ccan/str/str.h> +#include <chip.h> +#include <opal-dump.h> +#include <fsp-attn.h> +#include <fsp-leds.h> +#include <skiboot.h> +#include <vas.h> + +#include "hdata.h" +#include "hostservices.h" +#include "naca.h" +#include "spira.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; + +extern struct proc_init_data proc_init_data; + +__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(MSR_SF | MSR_HV), + }, +}; + +extern struct cpu_ctl_init_data cpu_ctl_init_data; +extern struct sp_addr_table cpu_ctl_spat_area; +extern struct sp_attn_area cpu_ctl_sp_attn_area1; +extern struct sp_attn_area cpu_ctl_sp_attn_area2; +extern struct hsr_data_area cpu_ctl_hsr_area; + +/* + * cpuctrl.data begins at CPU_CTL_OFF - cpu_ctl_init_data is located there. + * + sizeof(struct cpu_ctl_init_data) - cpu_ctl_spat_area + * + sizeof(struct sp_addr_table) - cpu_ctl_sp_attn_area1 + * + sizeof(struct sp_attn_area) - cpu_ctl_sp_attn_area2 + * + sizeof(struct sp_attn_area) - cpu_ctl_hsr_area + * + * Can't use CPU_TO_BE64 directly on the labels as a constant initialiser. + * + * CPU_CTL_INIT_DATA_OFF is offset from 0, the others are addressed from the + * relocated address (+SKIBOOT_BASE) + */ +#define CPU_CTL_INIT_DATA_OFF (CPU_CTL_OFF) +#define CPU_CTL_SPAT_AREA_OFF (CPU_CTL_INIT_DATA_OFF + sizeof(struct cpu_ctl_init_data) + SKIBOOT_BASE) +#define CPU_CTL_SP_ATTN_AREA1_OFF (ALIGN_UP((CPU_CTL_SPAT_AREA_OFF + sizeof(struct sp_addr_table)), ATTN_AREA_SZ)) +#define CPU_CTL_SP_ATTN_AREA2_OFF (CPU_CTL_SP_ATTN_AREA1_OFF + sizeof(struct sp_attn_area)) +#define CPU_CTL_HSR_AREA_OFF (CPU_CTL_SP_ATTN_AREA2_OFF + sizeof(struct sp_attn_area)) + +__section(".cpuctrl.data") struct hsr_data_area cpu_ctl_hsr_area; +__section(".cpuctrl.data") struct sp_attn_area cpu_ctl_sp_attn_area2; +__section(".cpuctrl.data") struct sp_attn_area cpu_ctl_sp_attn_area1; +__section(".cpuctrl.data") struct sp_addr_table cpu_ctl_spat_area; + +__section(".cpuctrl.data") struct cpu_ctl_init_data cpu_ctl_init_data = { + .hdr = HDIF_SIMPLE_HDR(CPU_CTL_HDIF_SIG, 2, struct cpu_ctl_init_data), + .cpu_ctl = HDIF_IDATA_PTR(offsetof(struct cpu_ctl_init_data, cpu_ctl_lt), + sizeof(struct cpu_ctl_legacy_table)), + .cpu_ctl_lt = { + .spat = { + .addr = CPU_TO_BE64(CPU_CTL_SPAT_AREA_OFF), + .size = CPU_TO_BE64(sizeof(struct sp_addr_table)), + }, + .sp_attn_area1 = { + .addr = CPU_TO_BE64(CPU_CTL_SP_ATTN_AREA1_OFF), + .size = CPU_TO_BE64(sizeof(struct sp_attn_area)), + }, + .sp_attn_area2 = { + .addr = CPU_TO_BE64(CPU_CTL_SP_ATTN_AREA2_OFF), + .size = CPU_TO_BE64(sizeof(struct sp_attn_area)), + }, + .hsr_area = { + .addr = CPU_TO_BE64(CPU_CTL_HSR_AREA_OFF), + .size = CPU_TO_BE64(sizeof(struct hsr_data_area)), + }, + }, +}; + +/* 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 + */ + +extern struct mdst_table init_mdst_table[]; + +__section(".mdst.data") struct mdst_table init_mdst_table[2] = { + { + .addr = CPU_TO_BE64(INMEM_CON_START | HRMOR_BIT), + .data_region = DUMP_REGION_CONSOLE, + .dump_type = DUMP_TYPE_SYSDUMP, + .size = CPU_TO_BE32(INMEM_CON_LEN), + }, + { + .addr = CPU_TO_BE64(HBRT_CON_START | HRMOR_BIT), + .data_region = DUMP_REGION_HBRT_LOG, + .dump_type = DUMP_TYPE_SYSDUMP, + .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)), + }, + .cpu_ctrl = { + .addr = CPU_TO_BE64(CPU_CTL_INIT_DATA_OFF), + .alloc_cnt = CPU_TO_BE16(1), + .act_cnt = CPU_TO_BE16(1), + .alloc_len = CPU_TO_BE32(sizeof(cpu_ctl_init_data)), + }, + }, +}; + +/* The Hypervisor SPIRA-H Structure */ +__section(".spirah.data") struct spirah spirah = { + .hdr = HDIF_SIMPLE_HDR(SPIRAH_HDIF_SIG, SPIRAH_VERSION, struct spirah), + .ntuples_ptr = HDIF_IDATA_PTR(offsetof(struct spirah, ntuples), + sizeof(struct spirah_ntuples)), + .ntuples = { + .array_hdr = { + .offset = CPU_TO_BE32(HDIF_ARRAY_OFFSET), + .ecnt = CPU_TO_BE32(SPIRAH_NTUPLES_COUNT), + .esize + = CPU_TO_BE32(sizeof(struct spira_ntuple)), + .eactsz = CPU_TO_BE32(0x18), + }, + /* Host Data Areas */ + .hs_data_area = { + .addr = CPU_TO_BE64(SPIRA_HEAP_BASE), + .alloc_cnt = CPU_TO_BE16(1), + .alloc_len = CPU_TO_BE32(SPIRA_HEAP_SIZE), + }, + /* 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)), + }, + .cpu_ctrl = { + .addr = CPU_TO_BE64(CPU_CTL_INIT_DATA_OFF), + .alloc_cnt = CPU_TO_BE16(1), + .act_cnt = CPU_TO_BE16(1), + .alloc_len = + CPU_TO_BE32(sizeof(cpu_ctl_init_data)), + }, + .mdump_src = { + .addr = CPU_TO_BE64(MDST_TABLE_OFF), + .alloc_cnt = CPU_TO_BE16(MDST_TABLE_SIZE / sizeof(struct mdst_table)), + .act_cnt = CPU_TO_BE16(ARRAY_SIZE(init_mdst_table)), + .alloc_len = CPU_TO_BE32(sizeof(struct mdst_table)), + .act_len = CPU_TO_BE32(sizeof(struct mdst_table)), + }, + .mdump_dst = { + .addr = CPU_TO_BE64(MDDT_TABLE_OFF), + .alloc_cnt = CPU_TO_BE16(MDDT_TABLE_SIZE / sizeof(struct mddt_table)), + .act_cnt = CPU_TO_BE16(0), + .alloc_len = CPU_TO_BE32(sizeof(struct mddt_table)), + .act_len = CPU_TO_BE32(sizeof(struct mddt_table)), + }, + .mdump_res = { + .addr = CPU_TO_BE64(MDRT_TABLE_BASE), + .alloc_cnt = CPU_TO_BE16(MDRT_TABLE_SIZE / sizeof(struct mdrt_table)), + /* + * XXX: Ideally hostboot should use allocated count and + * length. But looks like hostboot uses actual count + * and length to get MDRT table size. And post dump + * hostboot will update act_cnt. Hence update both + * alloc_cnt and act_cnt. + */ + .act_cnt = CPU_TO_BE16(MDRT_TABLE_SIZE / sizeof(struct mdrt_table)), + .alloc_len = CPU_TO_BE32(sizeof(struct mdrt_table)), + .act_len = CPU_TO_BE32(sizeof(struct mdrt_table)), + }, + .proc_dump_area = { + .addr = CPU_TO_BE64(PROC_DUMP_AREA_OFF), + .alloc_cnt = CPU_TO_BE16(1), + .act_cnt = CPU_TO_BE16(1), + .alloc_len = CPU_TO_BE32(sizeof(struct proc_dump_area)), + .act_len = CPU_TO_BE32(sizeof(struct proc_dump_area)), + }, + }, +}; + +/* The service processor SPIRA-S structure */ +struct spiras *spiras; + +/* 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); + u16 act_cnt, alloc_cnt; + u32 act_len, alloc_len; + + if (!spira_check_ptr(h, file, line)) + return NULL; + + act_cnt = be16_to_cpu(n->act_cnt); + alloc_cnt = be16_to_cpu(n->alloc_cnt); + + if (act_cnt > alloc_cnt) { + prerror("SPIRA: bad ntuple, act_cnt > alloc_cnt (%u > %u)\n", + act_cnt, alloc_cnt); + return NULL; + } + + act_len = be32_to_cpu(n->act_len); + alloc_len = be32_to_cpu(n->alloc_len); + + if (act_len > alloc_len) { + prerror("SPIRA: bad ntuple, act_len > alloc_len (%u > %u)\n", + act_len, alloc_len); + 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; +} + +uint32_t get_xscom_id(const struct sppcrd_chip_info *cinfo) +{ + if (proc_gen <= proc_gen_p9) + return be32_to_cpu(cinfo->xscom_id); + + /* On P10 use Processor fabric topology id for chip id */ + return (uint32_t)(cinfo->fab_topology_id); +} + +static struct dt_node *add_xscom_node(uint64_t base, + const struct sppcrd_chip_info *cinfo) +{ + struct dt_node *node; + uint64_t addr, size; + uint64_t freq; + uint32_t hw_id = get_xscom_id(cinfo); + uint32_t proc_chip_id = be32_to_cpu(cinfo->proc_chip_id); + + switch (proc_gen) { + case proc_gen_p8: + /* On P8 all the chip SCOMs share single region */ + addr = base | ((uint64_t)hw_id << PPC_BITLSHIFT(28)); + break; + case proc_gen_p9: + /* On P9 we need to put the chip ID in the natural powerbus + * position. + */ + addr = base | (((uint64_t)hw_id) << 42); + break; + case proc_gen_p10: + default: + /* Use Primary topology table index for xscom address */ + addr = base | (((uint64_t)cinfo->topology_id_table[cinfo->primary_topology_loc]) << 44); + break; + }; + + size = (u64)1 << PPC_BITLSHIFT(28); + + prlog(PR_INFO, "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); + assert(node); + + 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_p8: + dt_add_property_strings(node, "compatible", + "ibm,xscom", "ibm,power8-xscom"); + break; + case proc_gen_p9: + dt_add_property_strings(node, "compatible", + "ibm,xscom", "ibm,power9-xscom"); + break; + case proc_gen_p10: + dt_add_property_strings(node, "compatible", + "ibm,xscom", "ibm,power10-xscom"); + break; + default: + dt_add_property_strings(node, "compatible", "ibm,xscom"); + } + dt_add_property_u64s(node, "reg", addr, size); + + /* + * The bus-frequency of the xscom node is actually the PIB/PCB + * frequency. It is derived from the nest-clock via a 4:1 divider + */ + freq = dt_prop_get_u64_def(dt_root, "nest-frequency", 0); + freq /= 4; + if (freq) + dt_add_property_u64(node, "bus-frequency", freq); + + return node; +} + +/* + * Given a xscom@ node this will return a pointer into the SPPCRD + * structure corresponding to that node + */ +#define GET_HDIF_HDR -1 +static const void *xscom_to_pcrd(struct dt_node *xscom, int idata_index) +{ + struct spira_ntuple *t = &spira.ntuples.proc_chip; + const struct HDIF_common_hdr *hdif; + const void *idata; + unsigned int size; + uint32_t i; + void *base; + + i = dt_prop_get_u32_def(xscom, DT_PRIVATE "sppcrd-index", 0xffffffff); + if (i == 0xffffffff) + return NULL; + + base = get_hdif(t, "SPPCRD"); + assert(base); + assert(i < be16_to_cpu(t->act_cnt)); + + hdif = base + i * be32_to_cpu(t->alloc_len); + assert(hdif); + + if (idata_index == GET_HDIF_HDR) + return hdif; + + idata = HDIF_get_idata(hdif, idata_index, &size); + if (!idata || !size) + return NULL; + + return idata; +} + +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_p8: + psi_scom = 0x2010900; + psi_slen = 0x20; + psi_comp = "ibm,power8-psihb-x"; + break; + case proc_gen_p9: + psi_scom = 0x5012900; + psi_slen = 0x100; + psi_comp = "ibm,power9-psihb-x"; + break; + case proc_gen_p10: + psi_scom = 0x3011d00; + psi_slen = 0x100; + psi_comp = "ibm,power10-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_xive_node(struct dt_node *np) +{ + struct dt_node *xive; + const char *comp; + u32 scom, slen; + + switch (proc_gen) { + case proc_gen_p9: + scom = 0x5013000; + slen = 0x300; + comp = "ibm,power9-xive-x"; + break; + case proc_gen_p10: + scom = 0x2010800; + slen = 0x400; + comp = "ibm,power10-xive-x"; + break; + default: + return; + } + + xive = dt_new_addr(np, "xive", scom); + dt_add_property_cells(xive, "reg", scom, slen); + dt_add_property_string(xive, "compatible", comp); + + /* HACK: required for simics */ + dt_add_property(xive, "force-assign-bars", NULL, 0); +} + +static void add_vas_node(struct dt_node *np, int idx) +{ + struct dt_node *vas; + const char *comp; + uint64_t base_addr; + + if (proc_gen == proc_gen_p9) { + base_addr = P9_VAS_SCOM_BASE_ADDR; + comp = "ibm,power9-vas-x"; + } else { + base_addr = VAS_SCOM_BASE_ADDR; + comp = "ibm,power10-vas-x"; + } + + vas = dt_new_addr(np, "vas", base_addr); + dt_add_property_cells(vas, "reg", base_addr, 0x300); + dt_add_property_string(vas, "compatible", comp); + dt_add_property_cells(vas, "ibm,vas-id", idx); +} + +static void add_ecid_data(const struct HDIF_common_hdr *hdr, + struct dt_node *xscom) +{ + char wafer_id[11]; + uint8_t tmp; + int i; + uint32_t size = 0; + struct sppcrd_ecid *ecid; + const struct HDIF_array_hdr *ec_hdr; + + ec_hdr = HDIF_get_idata(hdr, SPPCRD_IDATA_EC_LEVEL, &size); + if (!ec_hdr || !size) + return; + + ecid = (void *)ec_hdr + be32_to_cpu(ec_hdr->offset); + dt_add_property_u64s(xscom, "ecid", be64_to_cpu(ecid->low), + be64_to_cpu(ecid->high)); + + /* + * bits 4:63 of ECID data contains wafter ID data (ten 6 bit fields + * each containing a code). + */ + for (i = 0; i < 10; i++) { + tmp = (u8)((be64_to_cpu(ecid->low) >> (i * 6)) & 0x3f); + if (tmp <= 9) + wafer_id[9 - i] = tmp + '0'; + else if (tmp >= 0xA && tmp <= 0x23) + wafer_id[9 - i] = tmp + '0' + 7; + else if (tmp == 0x3D) + wafer_id[9 - i] = '-'; + else if (tmp == 0x3E) + wafer_id[9 - i] = '.'; + else if (tmp == 0x3F) + wafer_id[9 - i] = ' '; + else /* Unknown code */ + wafer_id[9 - i] = tmp + '0'; + } + wafer_id[10] = '\0'; + dt_add_property_nstr(xscom, "wafer-id", wafer_id, 10); + + dt_add_property_cells(xscom, "wafer-location", + (u32)((be64_to_cpu(ecid->high) >> 56) & 0xff), + (u32)((be64_to_cpu(ecid->high) >> 48) & 0xff)); +} + +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)); + if (proc_gen < proc_gen_p9) { + 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, *vpd_node; + + for_each_ntuple_idx(&spira.ntuples.proc_chip, hdif, i, + SPPCRD_HDIF_SIG) { + const struct sppcrd_chip_info *cinfo; + const struct spira_fru_id *fru_id = NULL; + unsigned int csize; + u32 ve, version; + + cinfo = HDIF_get_idata(hdif, SPPCRD_IDATA_CHIP_INFO, &csize); + 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, cinfo); + if (!np) + continue; + + + dt_add_property_cells(np, DT_PRIVATE "sppcrd-index", i); + + 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 */ + vpd_node = dt_add_vpd_node(hdif, SPPCRD_IDATA_FRU_ID, + SPPCRD_IDATA_KW_VPD); + if (vpd_node) + dt_add_property_cells(vpd_node, "ibm,chip-id", + get_xscom_id(cinfo)); + + fru_id = HDIF_get_idata(hdif, SPPCRD_IDATA_FRU_ID, NULL); + if (fru_id) + slca_vpd_add_loc_code(np, be16_to_cpu(fru_id->slca_index)); + + /* 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); + vpd_data_parse(np, vpd, vpd_sz); + if (vpd_node) + dt_add_proc_vendor(vpd_node, 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); + + if (proc_gen >= proc_gen_p9) { + add_xive_node(np); + parse_i2c_devs(hdif, SPPCRD_IDATA_HOST_I2C, np); + add_vas_node(np, i); + add_ecid_data(hdif, np); + + if (be32_to_cpu(cinfo->verif_exist_flags) & CHIP_VERIFY_MASTER_PROC) + dt_add_property(np, "primary", NULL, 0); + } + + /* + * Add sw checkstop scom address (ibm,sw-checkstop-fir) + * + * The latest HDAT versions have sw checkstop scom address + * info. But not sure from which version onwards (at least + * HDAT spec do not mention that explicitly). Hence use the + * sppcrd struct size returned by HDIF_get_idata to figure out + * whether it contains sw checkstop scom address info. Also + * check if sw_xstop_fir_scom address is non-zero. + */ + if ((csize >= (offsetof(struct sppcrd_chip_info, + sw_xstop_fir_bitpos) + 1)) && + cinfo->sw_xstop_fir_scom) { + uint8_t fir_bit = cinfo->sw_xstop_fir_bitpos; + + if (!dt_find_property(dt_root, "ibm,sw-checkstop-fir")) + dt_add_property_cells(dt_root, + "ibm,sw-checkstop-fir", + be32_to_cpu(cinfo->sw_xstop_fir_scom), + fir_bit); + } + + if (proc_gen >= proc_gen_p10) { + uint8_t primary_loc = cinfo->primary_topology_loc; + + if (primary_loc >= CHIP_MAX_TOPOLOGY_ENTRIES) { + prerror("XSCOM: Invalid primary topology index %d\n", + primary_loc); + continue; + } + dt_add_property_cells(np, "ibm,primary-topology-index", + cinfo->topology_id_table[primary_loc]); + } + } + + return i > 0; +} + +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); + + /* 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; +} + +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_p8: + compat_str = "ibm,power8-chiptod"; + break; + case proc_gen_p9: + compat_str = "ibm,power9-chiptod"; + break; + case proc_gen_p10: + compat_str = "ibm,power10-chiptod"; + break; + default: + return; + } + + prlog(PR_DEBUG, "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(void) +{ + const void *hdif; + unsigned int i; + bool found = false; + + /* + * Locate Proc Chip ID structures in SPIRA + */ + if (!get_hdif(&spira.ntuples.proc_chip, SPPCRD_HDIF_SIG)) + return found; + + 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 chip zero as the master. + */ + if (!size) { + flags = CHIPTOD_ID_FLAGS_STATUS_OK; + if (be32_to_cpu(cinfo->xscom_id) == 0x0) + flags |= CHIPTOD_ID_FLAGS_PRIMARY; + } + + add_chiptod_node(get_xscom_id(cinfo), flags); + found = true; + } + return found; +} + +static void add_nx_node(u32 gcid) +{ + struct dt_node *nx; + 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; + + nx = dt_new_addr(xscom, "nx", addr); + if (!nx) + return; + + switch (proc_gen) { + case proc_gen_p8: + dt_add_property_strings(nx, "compatible", "ibm,power-nx", + "ibm,power8-nx"); + break; + case proc_gen_p9: + case proc_gen_p10: + /* POWER9 NX is not software compatible with P8 NX */ + dt_add_property_strings(nx, "compatible", "ibm,power9-nx"); + break; + default: + return; + } + + dt_add_property_cells(nx, "reg", addr, size); +} + +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(get_xscom_id(cinfo)); + } +} + +static void add_nmmu(void) +{ + struct dt_node *xscom, *nmmu; + u32 scom; + + /* Nest MMU only exists on POWER9 or later */ + if (proc_gen < proc_gen_p9) + return; + + if (proc_gen == proc_gen_p9) + scom = 0x5012c40; + else + scom = 0x2010c40; + + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + nmmu = dt_new_addr(xscom, "nmmu", scom); + dt_add_property_strings(nmmu, "compatible", "ibm,power9-nest-mmu"); + dt_add_property_cells(nmmu, "reg", scom, 0x20); + } +} + +static void dt_init_secureboot_node(const struct iplparams_sysparams *sysparams) +{ + struct dt_node *node; + u16 sys_sec_setting; + u16 hw_key_hash_size; + u16 host_fw_key_clear; + + node = dt_new(dt_root, "ibm,secureboot"); + assert(node); + + dt_add_property_strings(node, "compatible", + "ibm,secureboot", "ibm,secureboot-v2"); + + sys_sec_setting = be16_to_cpu(sysparams->sys_sec_setting); + if (sys_sec_setting & SEC_CONTAINER_SIG_CHECKING) + dt_add_property(node, "secure-enabled", NULL, 0); + if (sys_sec_setting & SEC_HASHES_EXTENDED_TO_TPM) + dt_add_property(node, "trusted-enabled", NULL, 0); + if (sys_sec_setting & PHYSICAL_PRESENCE_ASSERTED) + dt_add_property(node, "physical-presence-asserted", NULL, 0); + + host_fw_key_clear = be16_to_cpu(sysparams->host_fw_key_clear); + if (host_fw_key_clear & KEY_CLEAR_OS_KEYS) + dt_add_property(node, "clear-os-keys", NULL, 0); + if (host_fw_key_clear & KEY_CLEAR_MFG) + dt_add_property(node, "clear-mfg-keys", NULL, 0); + if (host_fw_key_clear & KEY_CLEAR_ALL) + dt_add_property(node, "clear-all-keys", NULL, 0); + + hw_key_hash_size = be16_to_cpu(sysparams->hw_key_hash_size); + + /* Prevent hw-key-hash buffer overflow by truncating hw-key-hash-size if + * it is bigger than the hw-key-hash buffer. + * Secure boot will be enforced later in skiboot, if the hw-key-hash-size + * was not supposed to be SYSPARAMS_HW_KEY_HASH_MAX. + */ + if (hw_key_hash_size > SYSPARAMS_HW_KEY_HASH_MAX) { + prlog(PR_ERR, "IPLPARAMS: hw-key-hash-size=%d too big, " + "truncating to %d\n", hw_key_hash_size, + SYSPARAMS_HW_KEY_HASH_MAX); + hw_key_hash_size = SYSPARAMS_HW_KEY_HASH_MAX; + } + + dt_add_property(node, "hw-key-hash", sysparams->hw_key_hash, + hw_key_hash_size); + dt_add_property_cells(node, "hw-key-hash-size", hw_key_hash_size); +} + +static void opal_dump_add_mpipl_boot(const struct iplparams_iplparams *p) +{ + u32 mdrt_cnt = be16_to_cpu(spira.ntuples.mdump_res.act_cnt); + u32 mdrt_max_cnt = MDRT_TABLE_SIZE / sizeof(struct mdrt_table); + struct dt_node *dump_node; + + dump_node = dt_find_by_path(opal_node, "dump"); + if (!dump_node) + return; + + /* Check boot params to detect MPIPL boot or not */ + if (p->cec_ipl_maj_type != IPLPARAMS_MAJ_TYPE_REIPL) + return; + + /* + * On FSP system we get minor type as post dump IPL and on BMC system + * we get platform reboot. Hence lets check for both values. + */ + if (p->cec_ipl_min_type != IPLPARAMS_MIN_TYPE_POST_DUMP && + p->cec_ipl_min_type != IPLPARAMS_MIN_TYPE_PLAT_REBOOT) { + prlog(PR_NOTICE, "DUMP: Non MPIPL reboot " + "[minor type = 0x%x]\n", p->cec_ipl_min_type); + return; + } + + if (be16_to_cpu(p->cec_ipl_attrib) != IPLPARAMS_ATTRIB_MEM_PRESERVE) { + prlog(PR_DEBUG, "DUMP: Memory not preserved\n"); + return; + } + + if (mdrt_cnt == 0 || mdrt_cnt >= mdrt_max_cnt) { + prlog(PR_DEBUG, "DUMP: Invalid MDRT count : %x\n", mdrt_cnt); + return; + } + + prlog(PR_NOTICE, "DUMP: Dump found, MDRT count = 0x%x\n", mdrt_cnt); + + dt_add_property(dump_node, "mpipl-boot", NULL, 0); +} + +static void add_opal_dump_node(void) +{ + __be64 fw_load_area[4]; + struct dt_node *node; + + opal_node = dt_new_check(dt_root, "ibm,opal"); + node = dt_new(opal_node, "dump"); + assert(node); + dt_add_property_string(node, "compatible", "ibm,opal-dump"); + + fw_load_area[0] = cpu_to_be64((u64)KERNEL_LOAD_BASE); + fw_load_area[1] = cpu_to_be64(KERNEL_LOAD_SIZE); + fw_load_area[2] = cpu_to_be64((u64)INITRAMFS_LOAD_BASE); + fw_load_area[3] = cpu_to_be64(INITRAMFS_LOAD_SIZE); + dt_add_property(node, "fw-load-area", fw_load_area, sizeof(fw_load_area)); +} + +static void add_iplparams_sys_params(const void *iplp, struct dt_node *node) +{ + const struct iplparams_sysparams *p; + const struct HDIF_common_hdr *hdif = iplp; + u16 version = be16_to_cpu(hdif->version); + const char *vendor = NULL; + u32 sys_attributes; + u64 bus_speed; + + 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 has up to three entries: + * "ibm,powernv", the system family and system type. + * + * On P9 and above the family and type strings come from the HDAT + * directly. On P8 we find it from the system ID numbers. + */ + if (proc_gen >= proc_gen_p9) { + dt_add_property_strings(dt_root, "compatible", "ibm,powernv", + p->sys_family_str, p->sys_type_str); + + prlog(PR_INFO, "IPLPARAMS: v0x70 Platform family/type: %s/%s\n", + p->sys_family_str, p->sys_type_str); + } else { + u32 sys_type = be32_to_cpu(p->system_type); + const char *sys_family; + + 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); + prlog(PR_INFO, + "IPLPARAMS: Legacy platform family: %s" + " (sys_type=0x%08x)\n", sys_family, sys_type); + } + + /* Grab nest frequency when available */ + if (version >= 0x005b) { + u64 freq = be32_to_cpu(p->nest_freq_mhz); + + freq *= 1000000; + dt_add_property_u64(dt_root, "nest-frequency", freq); + } + + /* Grab ABC bus speed */ + bus_speed = be32_to_cpu(p->abc_bus_speed); + if (bus_speed) + dt_add_property_u64(node, "abc-bus-freq-mhz", bus_speed); + + /* Grab WXYZ bus speed */ + bus_speed = be32_to_cpu(p->wxyz_bus_speed); + if (bus_speed) + dt_add_property_u64(node, "wxyz-bus-freq-mhz", bus_speed); + + if (version >= 0x5f) + vendor = p->sys_vendor; + + /* Workaround a bug where we have NULL vendor */ + if (!vendor || vendor[0] == '\0') + vendor = "IBM"; + + dt_add_property_string(dt_root, "vendor", vendor); + + sys_attributes = be32_to_cpu(p->sys_attributes); + if (sys_attributes & SYS_ATTR_RISK_LEVEL) + dt_add_property(node, "elevated-risk-level", NULL, 0); + + /* Populate OPAL dump node */ + if (sys_attributes & SYS_ATTR_MPIPL_SUPPORTED) + add_opal_dump_node(); + + if (version >= 0x60 && proc_gen >= proc_gen_p9) + dt_init_secureboot_node(p); +} + +static void add_iplparams_ipl_params(const void *iplp, struct dt_node *node) +{ + const struct iplparams_iplparams *p; + struct dt_node *led_node; + + 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 (be32_to_cpu(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"); + if (proc_gen >= proc_gen_p9) { + dt_add_property_strings(node, "sp-ipl-side", + (p->ipl_side & IPLPARAMS_FSP_FW_IPL_SIDE_TEMP) ? + "temp" : "perm"); + } else { + 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"); + + /* Add LED type info under '/ibm,opal/led' node */ + led_node = dt_find_by_path(opal_node, DT_PROPERTY_LED_NODE); + assert(led_node); + + if (be32_to_cpu(p->other_attrib) & IPLPARAMS_OATRR_LIGHT_PATH) + dt_add_property_strings(led_node, DT_PROPERTY_LED_MODE, + LED_MODE_LIGHT_PATH); + else + dt_add_property_strings(led_node, DT_PROPERTY_LED_MODE, + LED_MODE_GUIDING_LIGHT); + + /* Populate opal dump result table */ + opal_dump_add_mpipl_boot(p); +} + +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 <= 0) + return; + prlog(PR_INFO, "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); + prlog(PR_INFO, "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_features(const struct HDIF_common_hdr *iplp) +{ + const struct iplparams_feature *feature; + const struct HDIF_array_hdr *array; + struct dt_node *fw_features; + unsigned int count, i; + char name[65]; + + array = HDIF_get_iarray(iplp, IPLPARAMS_FEATURES, &count); + if (!array || !count) + return; + + opal_node = dt_new_check(dt_root, "ibm,opal"); + fw_features = dt_new(opal_node, "fw-features"); + if (!fw_features) + return; + + HDIF_iarray_for_each(array, i, feature) { + struct dt_node *n; + uint64_t flags; + + /* the name field isn't necessarily null terminated */ + BUILD_ASSERT(sizeof(name) > sizeof(feature->name)); + strncpy(name, feature->name, sizeof(name)-1); + name[sizeof(name)-1] = '\0'; + flags = be64_to_cpu(feature->flags); + + if (strlen(name) == 0) { + prlog(PR_DEBUG, "IPLPARAMS: FW feature name is NULL\n"); + continue; + } + + prlog(PR_DEBUG, "IPLPARAMS: FW feature %s = %016"PRIx64"\n", + name, flags); + + /* get rid of tm-suspend-mode-enabled being disabled */ + if (strcmp(name, "tm-suspend-mode-enabled") == 0) + strcpy(name, "tm-suspend-mode"); + + n = dt_new(fw_features, name); + + /* + * This is a bit overkill, but we'll want seperate properties + * for each flag bit(s). + */ + if (flags & PPC_BIT(0)) + dt_add_property(n, "enabled", NULL, 0); + else + dt_add_property(n, "disabled", NULL, 0); + } +} + +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); + add_iplparams_features(ipl_parms); +} + +/* 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. + */ +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 get_xscom_id(cinfo); + } + + /* Not found, what to do ? Assert ? For now return a number + * guaranteed to not exist + */ + return (uint32_t)-1; +} + +uint32_t pcid_to_topology_idx(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)) { + if (proc_gen <= proc_gen_p9) + return get_xscom_id(cinfo); + else + return ((u32)cinfo->topology_id_table[cinfo->primary_topology_loc]); + } + } + + /* Not found, what to do ? Assert ? For now return a number + * guaranteed to not exist + */ + return (uint32_t)-1; +} +/* Create '/ibm,opal/led' node */ +static void dt_init_led_node(void) +{ + struct dt_node *led_node; + + /* Create /ibm,opal node, if its not created already */ + if (!opal_node) { + opal_node = dt_new(dt_root, "ibm,opal"); + assert(opal_node); + } + + /* Crete LED parent node */ + led_node = dt_new(opal_node, DT_PROPERTY_LED_NODE); + assert(led_node); +} + +static void hostservices_parse(void) +{ + struct HDIF_common_hdr *hs_hdr; + const void *dt_blob; + unsigned int size; + unsigned int ntuples_size; + + /* Deprecated on P9 */ + if (proc_gen >= proc_gen_p9) + return; + + 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); +} + +static void add_stop_levels(void) +{ + struct spira_ntuple *t = &spira.ntuples.proc_chip; + struct HDIF_common_hdr *hdif; + u32 stop_levels = ~0; + bool valid = false; + int i; + + if (proc_gen < proc_gen_p9) + return; + + /* + * OPAL only exports a single set of flags to indicate the supported + * STOP modes while the HDAT descibes the support top levels *per chip* + * We parse the list of chips to find a common set of STOP levels to + * export. + */ + for_each_ntuple_idx(t, hdif, i, SPPCRD_HDIF_SIG) { + unsigned int size; + const struct sppcrd_chip_info *cinfo = + HDIF_get_idata(hdif, SPPCRD_IDATA_CHIP_INFO, &size); + u32 ve, chip_levels; + + if (!cinfo) + continue; + + /* + * If the chip info field is too small then assume we have no + * STOP level information. + */ + if (size < 0x44) { + stop_levels = 0; + break; + } + + ve = be32_to_cpu(cinfo->verif_exist_flags) & CPU_ID_VERIFY_MASK; + ve >>= CPU_ID_VERIFY_SHIFT; + if (ve == CHIP_VERIFY_NOT_INSTALLED || + ve == CHIP_VERIFY_UNUSABLE) + continue; + + chip_levels = be32_to_cpu(cinfo->stop_levels); + + prlog(PR_INSANE, "CHIP[%x] supported STOP mask 0x%.8x\n", + be32_to_cpu(cinfo->proc_chip_id), chip_levels); + + stop_levels &= chip_levels; + valid = true; + } + + if (!valid) + stop_levels = 0; + + dt_add_property_cells(dt_new_check(opal_node, "power-mgt"), + "ibm,enabled-stop-levels", stop_levels); +} + +#define NPU_BASE 0x5011000 +#define NPU_SIZE 0x2c +#define NPU_INDIRECT0 0x8000000009010c3fULL +#define NPU_INDIRECT1 0x800000000c010c3fULL + +static void add_npu(struct dt_node *xscom, const struct HDIF_array_hdr *links, + int npu_index) +{ + const struct sppcrd_smp_link *link; + struct dt_node *npu; + int group_target[6]; /* Tracks the PCI slot targeted each link group */ + int group_count = 0; + int link_count = 0; + uint32_t size, chip_id; + int i; + + size = be32_to_cpu(links->esize); + chip_id = dt_get_chip_id(xscom); + + memset(group_target, 0, sizeof(group_target)); + + npu = dt_new_addr(xscom, "npu", NPU_BASE); + dt_add_property_cells(npu, "reg", NPU_BASE, NPU_SIZE); + dt_add_property_cells(npu, "#size-cells", 0); + dt_add_property_cells(npu, "#address-cells", 1); + + dt_add_property_strings(npu, "compatible", "ibm,power9-npu"); + dt_add_property_cells(npu, "ibm,npu-index", npu_index); + + HDIF_iarray_for_each(links, i, link) { + uint16_t slot_id = be16_to_cpu(link->pci_slot_idx); + uint32_t link_id = be32_to_cpu(link->link_id); + uint64_t speed = 0, nvlink_speed = 0; + struct dt_node *node; + + /* + * Only add a link node if this link is targeted at a + * GPU device. + * + * If we ever activate it for an opencapi device, we + * should revisit the link definitions hard-coded + * on ZZ. + */ + if (be32_to_cpu(link->usage) != SMP_LINK_USE_GPU) + continue; + + /* + * XXX: The link_id that we get from HDAT is essentially an + * arbitrary ID number so we can't use it as the reg for the + * link node. + * + * a) There's a 1-1 mapping between entries in the SMP link + * structure and the NPU links. + * + * b) The SMP link array contains them in ascending order. + * + * We have some assurances that b) is correct, but if we get + * broken link numbering it's something to watch for. + * + * If we every have actual A-Bus (SMP) link info in here + * this is going to break. + */ + + prlog(PR_DEBUG, "NPU: %04x:%d: Link (%d) targets slot %u\n", + chip_id, link_count, link_count, slot_id); + + if (link_count >= 6) { + prerror("NPU: %04x:%d: Ignoring extra link (max 6)\n", + chip_id, link_count); + break; + } + + node = dt_new_addr(npu, "link", link_count); + if (!node) { + prerror("NPU: %04x:%d: Creating link node failed\n", + chip_id, link_count); + continue; + } + + dt_add_property_string(node, "compatible", "ibm,npu-link"); + dt_add_property_cells(node, "reg", link_count); + dt_add_property_cells(node, "ibm,npu-link-index", link_count); + dt_add_property_cells(node, "ibm,workbook-link-id", link_id); + + dt_add_property_u64s(node, "ibm,npu-phy", + link_count < 3 ? NPU_INDIRECT0 : NPU_INDIRECT1); + dt_add_property_cells(node, "ibm,npu-lane-mask", + be32_to_cpu(link->lane_mask)); + dt_add_property_cells(node, "ibm,npu-brick-id", + be32_to_cpu(link->brick_id)); + + link_count++; + + /* + * Add the group details if this is an NVlink. + * + * TODO: Cable card stuff. + */ + if (slot_id) { + struct dt_node *slot; + const char *name; + int group; + + /* + * Search the existing groups for one targeting + * this PCI slot + */ + for (group = 0; group < group_count; group++) + if (group_target[group] == slot_id) + break; + + /* no group, make a new one */ + if (group == group_count) { + group_target[group] = slot_id; + group_count++; + } + + dt_add_property_cells(node, "ibm,npu-group-id", group); + + slot = find_slot_entry_node(dt_root, slot_id); + if (!slot) { + prerror("NPU: %04x:%d: Unable find node for targeted PCIe slot\n", + chip_id, link_count - 1); + continue; + } + + /* + * The slot_id points to a node that indicates that + * this GPU should appear under the slot. Grab the + * slot-label from the parent node that represents + * the actual slot. + */ + name = dt_prop_get_def(slot->parent, "ibm,slot-label", + (char *)"<SLOT NAME MISSING>"); + + prlog(PR_DEBUG, "NPU: %04x:%d: Target slot %s\n", + chip_id, link_count - 1, name); + + dt_add_property_string(node, "ibm,slot-label", name); + dt_add_property_cells(node, "ibm,pcie-slot", + slot->phandle); + } + + /* Newer fields which might not be populated */ + if (size <= 0x24) + continue; + + switch (link->link_speed) { + case 0: /* 20Gbps */ + speed = 20000000000ul; + nvlink_speed = 0x3; + break; + case 1: /* 25Gbps */ + speed = 25000000000ul; + nvlink_speed = 0x9; + break; + case 2: /* 25.78125 Gbps */ + nvlink_speed = 0x8; + speed = 25781250000ul; + break; + } + + /* ibm,link-speed is in bps and nvidia,link-speed is ~magic~ */ + dt_add_property_u64s(node, "ibm,link-speed", speed); + dt_add_property_cells(node, "nvidia,link-speed", nvlink_speed); + + dt_add_property_cells(node, DT_PRIVATE "occ-flag-pos", + PPC_BIT(link->occ_flag_bit)); + } + + dt_add_property_cells(npu, "ibm,npu-links", link_count); +} + +static void add_npus(void) +{ + struct dt_node *xscom; + int npu_index = 0; + + /* Only consult HDAT for npu2 */ + if (cpu_type != PVR_TYPE_P9) + return; + + dt_for_each_compatible(dt_root, xscom, "ibm,xscom") { + const struct HDIF_array_hdr *links; + + links = xscom_to_pcrd(xscom, SPPCRD_IDATA_SMP_LINK); + if (!links) { + prerror("NPU: Unable to find matching SPPCRD for %s\n", + xscom->name); + continue; + } + + /* should never happen, but stranger things have */ + if (!dt_find_by_name(dt_root, "ibm,pcie-slots")) { + prerror("PCIe slot information missing, can't add npu"); + continue; + } + + /* some hostboots will give us an empty array */ + if (be32_to_cpu(links->ecnt)) + add_npu(xscom, links, npu_index++); + } +} + +/* + * Legacy SPIRA is being deprecated and we have new SPIRA-H/S structures. + * But on older system (p7?) we will continue to get legacy SPIRA. + * + * SPIRA-S is initialized and provided by FSP. We use SPIRA-S signature + * to identify supported format. Also if required adjust spira pointer. + */ +static void fixup_spira(void) +{ +#if !defined(TEST) + spiras = (struct spiras *)SPIRA_HEAP_BASE; +#endif + + /* Validate SPIRA-S signature */ + if (!spiras) + return; + if (!HDIF_check(&spiras->hdr, SPIRAS_HDIF_SIG)) + return; + + prlog(PR_DEBUG, "SPIRA-S found.\n"); + + spira.ntuples.sp_subsys = spiras->ntuples.sp_subsys; + spira.ntuples.ipl_parms = spiras->ntuples.ipl_parms; + spira.ntuples.nt_enclosure_vpd = spiras->ntuples.nt_enclosure_vpd; + spira.ntuples.slca = spiras->ntuples.slca; + spira.ntuples.backplane_vpd = spiras->ntuples.backplane_vpd; + spira.ntuples.system_vpd = spiras->ntuples.system_vpd; + spira.ntuples.proc_init = spirah.ntuples.proc_init; + spira.ntuples.clock_vpd = spiras->ntuples.clock_vpd; + spira.ntuples.anchor_vpd = spiras->ntuples.anchor_vpd; + spira.ntuples.op_panel_vpd = spiras->ntuples.op_panel_vpd; + spira.ntuples.misc_cec_fru_vpd = spiras->ntuples.misc_cec_fru_vpd; + spira.ntuples.ms_vpd = spiras->ntuples.ms_vpd; + spira.ntuples.cec_iohub_fru = spiras->ntuples.cec_iohub_fru; + spira.ntuples.cpu_ctrl = spirah.ntuples.cpu_ctrl; + spira.ntuples.mdump_src = spirah.ntuples.mdump_src; + spira.ntuples.mdump_dst = spirah.ntuples.mdump_dst; + spira.ntuples.mdump_res = spirah.ntuples.mdump_res; + spira.ntuples.proc_dump_area = spirah.ntuples.proc_dump_area; + spira.ntuples.pcia = spiras->ntuples.pcia; + spira.ntuples.proc_chip = spiras->ntuples.proc_chip; + spira.ntuples.hs_data = spiras->ntuples.hs_data; + spira.ntuples.ipmi_sensor = spiras->ntuples.ipmi_sensor; + spira.ntuples.node_stb_data = spiras->ntuples.node_stb_data; +} + +/* + * All the data structure addresses are relative to payload base. Hence adjust + * structures that are needed to capture OPAL dump during MPIPL. + */ +static void update_spirah_addr(void) +{ +#if !defined(TEST) + if (proc_gen < proc_gen_p9) + return; + + naca.spirah_addr = CPU_TO_BE64(SPIRAH_OFF); + naca.spira_addr = CPU_TO_BE64(SPIRA_OFF); + spirah.ntuples.hs_data_area.addr = CPU_TO_BE64(SPIRA_HEAP_BASE - SKIBOOT_BASE); + spirah.ntuples.mdump_res.addr = CPU_TO_BE64(MDRT_TABLE_BASE - SKIBOOT_BASE); +#endif +} + +int parse_hdat(bool is_opal) +{ + cpu_type = PVR_TYPE(mfspr(SPR_PVR)); + + prlog(PR_DEBUG, "Parsing HDAT...\n"); + + fixup_spira(); + + update_spirah_addr(); + + /* + * Basic DT root stuff + */ + dt_add_property_cells(dt_root, "#address-cells", 2); + dt_add_property_cells(dt_root, "#size-cells", 2); + + if (proc_gen < proc_gen_p9) + dt_add_property_string(dt_root, "lid-type", is_opal ? "opal" : "phyp"); + + /* Add any BMCs and enable the LPC UART */ + bmc_parse(); + + /* Create and populate /vpd node */ + dt_init_vpd_node(); + + /* Create /ibm,opal/led node */ + dt_init_led_node(); + + /* Parse PCIA */ + if (!pcia_parse()) + return -1; + + /* IPL params */ + add_iplparams(); + + /* Add XSCOM node (must be before chiptod, IO and FSP) */ + add_xscom(); + + /* Parse MS VPD */ + memory_parse(); + + /* Add any FSPs */ + fsp_parse(); + + /* Add ChipTOD's */ + if (!add_chiptod_old() && !add_chiptod_new()) + prerror("CHIPTOD: No ChipTOD found !\n"); + + /* Add NX */ + add_nx(); + + /* Add nest mmu */ + add_nmmu(); + + /* Add IO HUBs and/or PHBs */ + io_parse(); + + /* Add NPU nodes */ + add_npus(); + + /* Parse VPD */ + vpd_parse(); + + /* Host services information. */ + hostservices_parse(); + + /* Parse System Attention Indicator inforamtion */ + slca_dt_add_sai_node(); + + add_stop_levels(); + + /* Parse node secure and trusted boot data */ + if (proc_gen >= proc_gen_p9) + node_stb_parse(); + + prlog(PR_DEBUG, "Parsing HDAT...done\n"); + + return 0; +} |