| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| /* Copyright 2013-2019 IBM Corp. */ |
| |
| #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 <vpd.h> |
| #include <inttypes.h> |
| #include <string.h> |
| |
| #include "hdata.h" |
| |
| 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]; |
| beint32_t lxrbuf[2] = { 0, 0 }; |
| |
| /* 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) { |
| prlog(PR_DEBUG, "CEC: LXR%x not found !\n", lx_idx); |
| return false; |
| } |
| |
| memcpy(lxrbuf, lxr, sizeof(beint32_t)*2); |
| |
| prlog(PR_DEBUG, "CEC: LXRn=%d LXR=%08x%08x\n", lx_idx, be32_to_cpu(lxrbuf[0]), be32_to_cpu(lxrbuf[1])); |
| prlog(PR_DEBUG, "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, |
| be32_to_cpu(lxrbuf[0]), |
| be32_to_cpu(lxrbuf[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(be16_to_cpu(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); |
| } |
| prlog(PR_DEBUG, "CEC: %s: %s (SLCA rsrc 0x%x)\n", |
| prop_name, loc_code, |
| be16_to_cpu(fru_id->rsrc_id)); |
| } else { |
| prlog(PR_DEBUG, "CEC: SLCA Loc not found: " |
| "index %d\n", fru_id->slca_index); |
| } |
| } else { |
| prlog(PR_DEBUG, "CEC: Hub FRU ID not found...\n"); |
| } |
| } |
| |
| 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; |
| 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 |
| */ |
| dt_add_property_cells(pbcq, "reg", |
| pe_xscom, 0x20, |
| pci_xscom, 0x05, |
| spci_xscom, 0x15); |
| |
| /* 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_u64s(pbcq, "ibm,lane-eq", eq0, eq1, eq2, 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 *add_pec_stack(const struct cechub_io_hub *hub, |
| struct dt_node *pbcq, int stack_index, |
| int phb_index, u8 active_phbs) |
| { |
| struct dt_node *stack; |
| const char *compat; |
| u64 eq[12]; |
| u8 *ptr; |
| int i; |
| |
| stack = dt_new_addr(pbcq, "stack", stack_index); |
| assert(stack); |
| |
| if (proc_gen == proc_gen_p9) |
| compat = "ibm,power9-phb-stack"; |
| else |
| compat = "ibm,power10-phb-stack"; |
| |
| dt_add_property_cells(stack, "reg", stack_index); |
| dt_add_property_cells(stack, "ibm,phb-index", phb_index); |
| dt_add_property_string(stack, "compatible", compat); |
| |
| /* XXX: This should probably just return if the PHB is disabled |
| * rather than adding the extra properties. |
| */ |
| |
| if (active_phbs & (0x80 >> phb_index)) |
| dt_add_property_string(stack, "status", "okay"); |
| else |
| dt_add_property_string(stack, "status", "disabled"); |
| |
| for (i = 0; i < 4; i++) /* gen 3 eq settings */ |
| eq[i] = be64_to_cpu(hub->phb_lane_eq[phb_index][i]); |
| for (i = 0; i < 4; i++) /* gen 4 eq settings */ |
| eq[i+4] = be64_to_cpu(hub->phb4_lane_eq[phb_index][i]); |
| for (i = 0; i < 4; i++) /* gen 5 eq settings */ |
| eq[i+8] = be64_to_cpu(hub->phb5_lane_eq[phb_index][i]); |
| |
| /* Lane-eq settings are packed 2 bytes per lane for 16 lanes |
| * On P9 DD2 and P10, 1 byte per lane is used in the hardware |
| */ |
| |
| /* Repack 2 byte lane settings into 1 byte for gen 4 & 5 */ |
| ptr = (u8 *)&eq[4]; |
| for (i = 0; i < 32; i++) |
| ptr[i] = ptr[2*i]; |
| |
| if (proc_gen == proc_gen_p9) |
| dt_add_property_u64s(stack, "ibm,lane-eq", |
| eq[0], eq[1], eq[2], eq[3], |
| eq[4], eq[5]); |
| else |
| dt_add_property_u64s(stack, "ibm,lane-eq", |
| eq[0], eq[1], eq[2], eq[3], |
| eq[4], eq[5], |
| eq[6], eq[7]); |
| return stack; |
| } |
| |
| /* Add PHB4 on p9, PHB5 on p10 */ |
| static struct dt_node *io_add_phb4(const struct cechub_io_hub *hub, |
| const struct HDIF_common_hdr *sp_iohubs, |
| struct dt_node *xcom, |
| unsigned int pec_index, |
| int stacks, |
| int phb_base) |
| { |
| struct dt_node *pbcq; |
| uint8_t active_phb_mask = hub->fab_br0_pdt; |
| uint32_t pe_xscom; |
| uint32_t pci_xscom; |
| const char *compat; |
| int i; |
| |
| if (proc_gen == proc_gen_p9) { |
| pe_xscom = 0x4010c00 + (pec_index * 0x0000400); |
| pci_xscom = 0xd010800 + (pec_index * 0x1000000); |
| compat = "ibm,power9-pbcq"; |
| } else { |
| pe_xscom = 0x3011800 - (pec_index * 0x1000000); |
| pci_xscom = 0x8010800 + (pec_index * 0x1000000); |
| compat = "ibm,power10-pbcq"; |
| } |
| |
| /* Create PBCQ node under xscom */ |
| pbcq = dt_new_addr(xcom, "pbcq", pe_xscom); |
| if (!pbcq) |
| return NULL; |
| |
| /* "reg" property contains (in order) the PE and PCI XSCOM addresses */ |
| dt_add_property_cells(pbcq, "reg", |
| pe_xscom, 0x100, |
| pci_xscom, 0x200); |
| |
| /* The hubs themselves go under the stacks */ |
| dt_add_property_strings(pbcq, "compatible", compat); |
| dt_add_property_cells(pbcq, "ibm,pec-index", pec_index); |
| dt_add_property_cells(pbcq, "#address-cells", 1); |
| dt_add_property_cells(pbcq, "#size-cells", 0); |
| |
| for (i = 0; i < stacks; i++) |
| add_pec_stack(hub, pbcq, i, phb_base + i, active_phb_mask); |
| |
| 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"); |
| |
| prlog(PR_INFO, "CEC: Added PBCQ %d with %d stacks\n", |
| pec_index, stacks); |
| |
| /* the actual PHB nodes created later on by skiboot */ |
| return pbcq; |
| } |
| |
| static struct dt_node *io_add_p8(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)); |
| |
| prlog(PR_INFO, "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("P8: 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 are the same on Murano and Venice */ |
| 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; |
| } |
| |
| /* Add PBCQs for p9/p10 */ |
| static struct dt_node *io_add_p9(const struct cechub_io_hub *hub, |
| const struct HDIF_common_hdr *sp_iohubs) |
| { |
| struct dt_node *xscom; |
| unsigned int chip_id; |
| |
| chip_id = pcid_to_chip_id(be32_to_cpu(hub->proc_chip_id)); |
| |
| prlog(PR_INFO, "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("IOHUB: Can't find XSCOM for chip %d\n", chip_id); |
| return NULL; |
| } |
| |
| prlog(PR_DEBUG, "IOHUB: PHB active bridge mask %x\n", |
| (u32) hub->fab_br0_pdt); |
| |
| /* Create PBCQs */ |
| if (proc_gen == proc_gen_p9) { |
| io_add_phb4(hub, sp_iohubs, xscom, 0, 1, 0); |
| io_add_phb4(hub, sp_iohubs, xscom, 1, 2, 1); |
| io_add_phb4(hub, sp_iohubs, xscom, 2, 3, 3); |
| } else { /* p10 */ |
| io_add_phb4(hub, sp_iohubs, xscom, 0, 3, 0); |
| io_add_phb4(hub, sp_iohubs, xscom, 1, 3, 3); |
| } |
| |
| 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)) { |
| prlog(PR_WARNING, "CEC: No IOKID child array !\n"); |
| return; |
| } |
| if (!iokids->count) { |
| prlog(PR_WARNING, "CEC: IOKID count is 0 !\n"); |
| return; |
| } |
| if (be32_to_cpu(iokids->count) > 1) { |
| prlog(PR_WARNING, "CEC: WARNING ! More than 1 IO KID !!! (%d)\n", |
| be32_to_cpu(iokids->count)); |
| /* Ignoring the additional ones */ |
| } |
| |
| iokid = HDIF_child(sp_iohubs, iokids, 0, "IO KID"); |
| if (!iokid) { |
| prlog(PR_WARNING, "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) { |
| prlog(PR_WARNING, "CEC: No VPD entry in IO KID !\n"); |
| return; |
| } |
| |
| /* Grab LX load info */ |
| io_get_lx_info(kwvpd, kwvpd_sz, 0, dt_root); |
| } |
| |
| /* |
| * Assumptions: |
| * |
| * a) the IOSLOT index is the hub ID -CHECK |
| * |
| */ |
| |
| static struct dt_node *dt_slots; |
| |
| static void add_i2c_link(struct dt_node *node, const char *link_name, |
| u32 i2c_link) |
| { |
| /* FIXME: Do something not shit */ |
| dt_add_property_cells(node, link_name, i2c_link); |
| } |
| |
| /* |
| * the root of the slots node has #address-cells = 2, <hub-index, phb-index> |
| * can we ditch hub-index? |
| */ |
| |
| |
| static const struct slot_map_details *find_slot_details( |
| const struct HDIF_common_hdr *ioslot, int entry) |
| { |
| const struct slot_map_details *details = NULL; |
| const struct HDIF_array_hdr *arr; |
| unsigned int i; |
| |
| arr = HDIF_get_iarray(ioslot, IOSLOT_IDATA_DETAILS, NULL); |
| HDIF_iarray_for_each(arr, i, details) |
| if (be16_to_cpu(details->entry) == entry) |
| break; |
| |
| return details; |
| } |
| |
| static void parse_slot_details(struct dt_node *slot, |
| const struct slot_map_details *details) |
| { |
| u32 slot_caps; |
| |
| /* |
| * generic slot options |
| */ |
| |
| dt_add_property_cells(slot, "max-power", |
| be16_to_cpu(details->max_power)); |
| |
| if (details->perst_ctl_type == SLOT_PERST_PHB_OR_SW) |
| dt_add_property(slot, "pci-perst", NULL, 0); |
| else if (details->perst_ctl_type == SLOT_PERST_SW_GPIO) |
| dt_add_property_cells(slot, "gpio-perst", details->perst_gpio); |
| |
| if (details->presence_det_type == SLOT_PRESENCE_PCI) |
| dt_add_property(slot, "pci-presence-detect", NULL, 0); |
| |
| /* |
| * specific slot capabilities |
| */ |
| slot_caps = be32_to_cpu(details->slot_caps); |
| |
| if (slot_caps & SLOT_CAP_LSI) |
| dt_add_property(slot, "lsi", NULL, 0); |
| |
| if (slot_caps & SLOT_CAP_CAPI) { |
| /* XXX: should we be more specific here? |
| * |
| * Also we should double check that this slot |
| * is a root connected slot. |
| */ |
| dt_add_property(slot, "capi", NULL, 0); |
| } |
| |
| if (slot_caps & SLOT_CAP_CCARD) { |
| dt_add_property(slot, "cable-card", NULL, 0); |
| |
| if (details->presence_det_type == SLOT_PRESENCE_I2C) |
| add_i2c_link(slot, "i2c-presence-detect", |
| be32_to_cpu(details->i2c_cable_card)); |
| } |
| |
| if (slot_caps & SLOT_CAP_HOTPLUG) { |
| dt_add_property(slot, "hotplug", NULL, 0); |
| |
| /* |
| * Power control should only exist when the slot is hotplug |
| * capable |
| */ |
| if (details->power_ctrl_type == SLOT_PWR_I2C) |
| add_i2c_link(slot, "i2c-power-ctrl", |
| be32_to_cpu(details->i2c_power_ctl)); |
| } |
| |
| /* |
| * NB: Additional NVLink specific info is added to this node |
| * when the SMP Link structures are parsed later on. |
| */ |
| if (slot_caps & SLOT_CAP_NVLINK) |
| dt_add_property(slot, "nvlink", NULL, 0); |
| } |
| |
| struct dt_node *find_slot_entry_node(struct dt_node *root, u32 entry_id) |
| { |
| struct dt_node *node; |
| |
| for (node = dt_first(root); node; node = dt_next(root, node)) { |
| if (!dt_has_node_property(node, DT_PRIVATE "entry_id", NULL)) |
| continue; |
| |
| if (dt_prop_get_u32(node, DT_PRIVATE "entry_id") == entry_id) |
| return node; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * The internal HDAT representation of the various types of slot is kinda |
| * dumb, translate it into something more sensible |
| */ |
| enum slot_types { |
| st_root, |
| st_slot, |
| st_rc_slot, |
| st_sw_upstream, |
| st_sw_downstream, |
| st_builtin |
| }; |
| |
| static const char *st_name(enum slot_types type) |
| { |
| switch(type) { |
| case st_root: return "root-complex"; |
| case st_slot: return "pluggable"; |
| case st_rc_slot: return "pluggable"; /* differentiate? */ |
| case st_sw_upstream: return "switch-up"; |
| case st_sw_downstream: return "switch-down"; |
| case st_builtin: return "builtin"; |
| } |
| |
| return "(none)"; |
| } |
| |
| static enum slot_types xlate_type(uint8_t type, u32 features) |
| { |
| bool is_slot = features & SLOT_FEAT_SLOT; |
| |
| switch (type) { |
| case SLOT_TYPE_ROOT_COMPLEX: |
| return is_slot ? st_rc_slot : st_root; |
| case SLOT_TYPE_BUILTIN: |
| return st_builtin; |
| case SLOT_TYPE_SWITCH_UP: |
| return st_sw_upstream; |
| case SLOT_TYPE_SWITCH_DOWN: |
| return is_slot ? st_slot : st_sw_downstream; |
| } |
| |
| return -1; /* shouldn't happen */ |
| } |
| |
| static bool is_port(struct dt_node *n) |
| { |
| //return dt_node_is_compatible(n, "compatible", "ibm,pcie-port"); |
| return dt_node_is_compatible(n, "ibm,pcie-port"); |
| } |
| |
| /* this only works inside parse_one_ioslot() */ |
| #define SM_LOG(level, fmt, ...) \ |
| prlog(level, "SLOTMAP: %x:%d:%d " \ |
| fmt, /* user input */ \ |
| chip_id, entry->phb_index, eid, \ |
| ##__VA_ARGS__ /* user args */) |
| |
| #define SM_ERR(fmt, ...) SM_LOG(PR_ERR, fmt, ##__VA_ARGS__) |
| #define SM_DBG(fmt, ...) SM_LOG(PR_DEBUG, fmt, ##__VA_ARGS__) |
| |
| static void parse_one_slot(const struct slot_map_entry *entry, |
| const struct slot_map_details *details, int chip_id) |
| { |
| struct dt_node *node, *parent = NULL; |
| u16 eid, pid, vid, did; |
| u32 flags; |
| int type; |
| |
| flags = be32_to_cpu(entry->features); |
| type = xlate_type(entry->type, flags); |
| |
| eid = be16_to_cpu(entry->entry_id); |
| pid = be16_to_cpu(entry->parent_id); |
| |
| SM_DBG("%s - eid = %d, pid = %d, name = %8s\n", |
| st_name(type), eid, pid, |
| strnlen(entry->name, 8) ? entry->name : ""); |
| |
| /* empty slot, ignore it */ |
| if (eid == 0x0 && pid == 0x0) |
| return; |
| |
| if (type != st_root && type != st_rc_slot) { |
| parent = find_slot_entry_node(dt_slots, pid); |
| if (!parent) { |
| SM_ERR("Unable to find node for parent slot (id = %d)\n", |
| pid); |
| return; |
| } |
| } |
| |
| switch (type) { |
| case st_root: |
| case st_rc_slot: |
| node = dt_new_2addr(dt_slots, "root-complex", |
| chip_id, entry->phb_index); |
| if (!node) { |
| SM_ERR("Couldn't add DT node\n"); |
| return; |
| } |
| dt_add_property_cells(node, "reg", chip_id, entry->phb_index); |
| dt_add_property_cells(node, "#address-cells", 2); |
| dt_add_property_cells(node, "#size-cells", 0); |
| dt_add_property_strings(node, "compatible", |
| "ibm,pcie-port", "ibm,pcie-root-port"); |
| dt_add_property_cells(node, "ibm,chip-id", chip_id); |
| parent = node; |
| |
| /* |
| * The representation of slots attached directly to the |
| * root complex is a bit wierd. If this is just a root |
| * complex then stop here, otherwise fall through to create |
| * the slot node. |
| */ |
| if (type == st_root) |
| break; |
| |
| /* fallthrough*/ |
| case st_sw_upstream: |
| case st_builtin: |
| case st_slot: |
| if (!is_port(parent)) { |
| SM_ERR("%s connected to %s (%d), should be %s or %s!\n", |
| st_name(type), parent->name, pid, |
| st_name(st_root), st_name(st_sw_downstream)); |
| return; |
| } |
| |
| vid = (be32_to_cpu(entry->vendor_id) & 0xffff); |
| did = (be32_to_cpu(entry->device_id) & 0xffff); |
| |
| prlog(PR_DEBUG, "Found %s slot with %x:%x\n", |
| st_name(type), vid, did); |
| |
| /* The VID:DID is only meaningful for builtins and switches */ |
| if (type == st_sw_upstream && vid && did) { |
| node = dt_new_2addr(parent, st_name(type), vid, did); |
| dt_add_property_cells(node, "reg", vid, did); |
| } else { |
| /* |
| * If we get no vdid then create a "wildcard" slot |
| * that matches any device |
| */ |
| node = dt_new(parent, st_name(type)); |
| } |
| |
| if (type == st_sw_upstream) { |
| dt_add_property_cells(node, "#address-cells", 1); |
| dt_add_property_cells(node, "#size-cells", 0); |
| dt_add_property_cells(node, "upstream-port", |
| entry->up_port); |
| } |
| break; |
| |
| case st_sw_downstream: /* slot connected to switch output */ |
| node = dt_new_addr(parent, "down-port", entry->down_port); |
| dt_add_property_strings(node, "compatible", |
| "ibm,pcie-port"); |
| dt_add_property_cells(node, "reg", entry->down_port); |
| |
| break; |
| |
| default: |
| SM_ERR("Unknown slot map type %x\n", entry->type); |
| return; |
| } |
| |
| /* |
| * Now add any generic slot map properties. |
| */ |
| |
| /* private since we don't want hdat stuff leaking */ |
| dt_add_property_cells(node, DT_PRIVATE "entry_id", eid); |
| |
| if (entry->mrw_slot_id) |
| dt_add_property_cells(node, "mrw-slot-id", |
| be16_to_cpu(entry->mrw_slot_id)); |
| |
| if (entry->lane_mask) |
| dt_add_property_cells(node, "lane-mask", |
| be16_to_cpu(entry->lane_mask)); |
| |
| /* what is the difference between this and the lane reverse? */ |
| if (entry->lane_reverse) |
| dt_add_property_cells(node, "lanes-reversed", |
| be16_to_cpu(entry->lane_reverse)); |
| |
| if (strnlen(entry->name, sizeof(entry->name))) { |
| /* |
| * HACK: On some platforms (witherspoon) the slot label is |
| * applied to the device rather than the pcie downstream port |
| * that has the slot under it. Hack around this by moving the |
| * slot label up if the parent port doesn't have one. |
| */ |
| if (dt_node_is_compatible(node->parent, "ibm,pcie-port") && |
| !dt_find_property(node->parent, "ibm,slot-label")) { |
| dt_add_property_nstr(node->parent, "ibm,slot-label", |
| entry->name, sizeof(entry->name)); |
| } |
| |
| dt_add_property_nstr(node, "ibm,slot-label", |
| entry->name, sizeof(entry->name)); |
| } |
| |
| if (entry->type == st_slot || entry->type == st_rc_slot) |
| dt_add_property(node, "ibm,pluggable", NULL, 0); |
| |
| if (details) |
| parse_slot_details(node, details); |
| } |
| |
| /* |
| * Under the IOHUB structure we have and idata array describing |
| * the PHBs under each chip. The IOHUB structure also has a child |
| * array called IOSLOT which describes slot map. The i`th element |
| * of the IOSLOT array corresponds to the slot map of the i`th |
| * element of the iohubs idata array. |
| * |
| * Probably. |
| * |
| * Furthermore, arrayarrayarrayarrayarray. |
| */ |
| |
| static struct dt_node *get_slot_node(void) |
| { |
| struct dt_node *slots = dt_find_by_name(dt_root, "ibm,pcie-slots"); |
| |
| if (!slots) { |
| slots = dt_new(dt_root, "ibm,pcie-slots"); |
| dt_add_property_cells(slots, "#address-cells", 2); |
| dt_add_property_cells(slots, "#size-cells", 0); |
| } |
| |
| return slots; |
| } |
| |
| static void io_parse_slots(const struct HDIF_common_hdr *sp_iohubs, int hub_id) |
| { |
| const struct HDIF_child_ptr *ioslot_arr; |
| const struct HDIF_array_hdr *entry_arr; |
| const struct HDIF_common_hdr *ioslot; |
| const struct slot_map_entry *entry; |
| unsigned int i, count; |
| |
| if (be16_to_cpu(sp_iohubs->child_count) <= CECHUB_CHILD_IOSLOTS) |
| return; |
| |
| ioslot_arr = HDIF_child_arr(sp_iohubs, CECHUB_CHILD_IOSLOTS); |
| if (!ioslot_arr) |
| return; |
| |
| count = be32_to_cpu(ioslot_arr->count); /* should only be 1 */ |
| if (!count) |
| return; |
| |
| dt_slots = get_slot_node(); |
| |
| prlog(PR_DEBUG, "CEC: Found slot map for IOHUB %d\n", hub_id); |
| if (count > 1) |
| prerror("CEC: Multiple IOSLOTs found for IO HUB %d\n", hub_id); |
| |
| ioslot = HDIF_child(sp_iohubs, ioslot_arr, 0, "IOSLOT"); |
| if (!ioslot) |
| return; |
| |
| entry_arr = HDIF_get_iarray(ioslot, IOSLOT_IDATA_SLOTMAP, NULL); |
| HDIF_iarray_for_each(entry_arr, i, entry) { |
| const struct slot_map_details *details; |
| |
| details = find_slot_details(ioslot, |
| be16_to_cpu(entry->entry_id)); |
| parse_one_slot(entry, details, hub_id); |
| } |
| } |
| |
| static void io_parse_fru(const void *sp_iohubs) |
| { |
| unsigned int i; |
| 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; |
| } |
| |
| prlog(PR_INFO, "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; |
| uint32_t chip_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; |
| } |
| |
| switch (hub->flags & CECHUB_HUB_FLAG_STATE_MASK) { |
| case CECHUB_HUB_FLAG_STATE_OK: |
| prlog(PR_DEBUG, "CEC: IO Hub Chip #%d OK\n", i); |
| break; |
| case CECHUB_HUB_FLAG_STATE_FAILURES: |
| prlog(PR_WARNING, "CEC: IO Hub Chip #%d OK" |
| " with failures\n", i); |
| break; |
| case CECHUB_HUB_FLAG_STATE_NOT_INST: |
| prlog(PR_DEBUG, "CEC: IO Hub Chip #%d" |
| " Not installed\n", i); |
| continue; |
| case CECHUB_HUB_FLAG_STATE_UNUSABLE: |
| prlog(PR_DEBUG, "CEC: IO Hub Chip #%d Unusable\n", i); |
| continue; |
| } |
| |
| hub_id = be16_to_cpu(hub->iohub_id); |
| |
| /* GX BAR assignment */ |
| prlog(PR_DEBUG, "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), be16_to_cpu(hub->hub_num)); |
| |
| switch(hub_id) { |
| case CECHUB_HUB_MURANO: |
| case CECHUB_HUB_MURANO_SEGU: |
| prlog(PR_INFO, "CEC: Murano !\n"); |
| io_add_p8(hub, sp_iohubs); |
| break; |
| case CECHUB_HUB_VENICE_WYATT: |
| prlog(PR_INFO, "CEC: Venice !\n"); |
| io_add_p8(hub, sp_iohubs); |
| break; |
| case CECHUB_HUB_NIMBUS_SFORAZ: |
| case CECHUB_HUB_NIMBUS_MONZA: |
| case CECHUB_HUB_NIMBUS_LAGRANGE: |
| prlog(PR_INFO, "CEC: Nimbus !\n"); |
| io_add_p9(hub, sp_iohubs); |
| break; |
| case CECHUB_HUB_CUMULUS_DUOMO: |
| prlog(PR_INFO, "CEC: Cumulus !\n"); |
| io_add_p9(hub, sp_iohubs); |
| break; |
| case CECHUB_HUB_AXONE_HOPPER: |
| prlog(PR_INFO, "CEC: Axone !\n"); |
| io_add_p9(hub, sp_iohubs); |
| break; |
| case CECHUB_HUB_RAINIER: |
| prlog(PR_INFO, "CEC: Rainier !\n"); |
| io_add_p9(hub, sp_iohubs); |
| break; |
| case CECHUB_HUB_DENALI: |
| prlog(PR_INFO, "CEC: Denali !\n"); |
| io_add_p9(hub, sp_iohubs); |
| break; |
| default: |
| prlog(PR_ERR, "CEC: Hub ID 0x%04x unsupported !\n", |
| hub_id); |
| } |
| |
| chip_id = pcid_to_chip_id(be32_to_cpu(hub->proc_chip_id)); |
| |
| /* parse the slot map if we have one */ |
| io_parse_slots(sp_iohubs, chip_id); |
| } |
| |
| if (proc_gen == proc_gen_p8 || proc_gen == proc_gen_p9 || proc_gen == proc_gen_p10) |
| 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 = be32_to_cpu(fru_id_data->card_type); |
| |
| prlog(PR_INFO, "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); |
| } |
| } |
| |