| // SPDX-License-Identifier: Apache-2.0 |
| /* Copyright 2013-2019 IBM Corp. */ |
| |
| #include <skiboot.h> |
| #include <vpd.h> |
| #include <string.h> |
| #include "spira.h" |
| #include "hdata.h" |
| #include <device.h> |
| #include "hdata.h" |
| #include <inttypes.h> |
| #include <mem_region-malloc.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 */ |
| }; |
| |
| struct vpd_key_map { |
| const char *keyword; /* 2 char keyword */ |
| const char *description; |
| }; |
| |
| static const struct vpd_key_map vpd_key_table[] = { |
| {"AA", "ac-power-supply"}, |
| {"AM", "air-mover"}, |
| {"AV", "anchor-card"}, |
| |
| {"BA", "bus-adapter-card"}, |
| {"BC", "battery-charger"}, |
| {"BD", "bus-daughter-card"}, |
| {"BE", "bus-expansion-card"}, |
| {"BP", "backplane"}, |
| {"BR", "backplane-riser"}, |
| {"BX", "backplane-extender"}, |
| |
| {"CA", "calgary-bridge"}, |
| {"CB", "infiniband-connector"}, |
| {"CC", "clock-card"}, |
| {"CD", "card-connector"}, |
| {"CE", "ethernet-connector"}, |
| {"CL", "calgary-phb"}, |
| {"CI", "capacity-card"}, |
| {"CO", "sma-connector"}, |
| {"CP", "processor-capacity-card"}, |
| {"CR", "rio-connector"}, |
| {"CS", "serial-connector"}, |
| {"CU", "usb-connector"}, |
| |
| {"DB", "dasd-backplane"}, |
| {"DC", "drawer-card"}, |
| {"DE", "drawer-extension"}, |
| {"DI", "drawer-interposer"}, |
| {"DL", "p7ih-dlink-connector"}, |
| {"DT", "legacy-pci-card"}, |
| {"DV", "media-drawer-led"}, |
| |
| {"EI", "enclosure-led"}, |
| {"EF", "enclosure-fault-led"}, |
| {"ES", "embedded-sas"}, |
| {"ET", "ethernet-riser"}, |
| {"EV", "enclosure"}, |
| |
| {"FM", "frame"}, |
| {"FN", "fru-stocking-part-number"}, |
| |
| {"HB", "host-rio-pci-card"}, |
| {"HD", "high-speed-card"}, |
| {"HM", "hmc-connector"}, |
| |
| {"IB", "io-backplane"}, |
| {"IC", "io-card"}, |
| {"ID", "ide-connector"}, |
| {"II", "io-drawer-led"}, |
| {"IP", "interplane-card"}, |
| {"IS", "smp-vbus-cable"}, |
| {"IT", "enclosure-cable"}, |
| {"IV", "io-enclosure"}, |
| |
| {"KV", "keyboard-led"}, |
| |
| {"L2", "l2-cache-module"}, |
| {"L3", "l3-cache-module"}, |
| {"LC", "squadrons-light-connector"}, |
| {"LR", "p7ih-connector"}, |
| {"LO", "system-locate-led"}, |
| {"LT", "squadrons-light-strip"}, |
| |
| {"MB", "media-backplane"}, |
| {"ME", "map-extension"}, |
| {"MM", "mip-meter"}, |
| {"MS", "ms-dimm"}, |
| |
| {"NB", "nvram-battery"}, |
| {"NC", "sp-node-controller"}, |
| {"ND", "numa-dimm"}, |
| |
| {"OD", "cuod-card"}, |
| {"OP", "op-panel"}, |
| {"OS", "oscillator"}, |
| |
| {"P2", "ioc"}, |
| {"P5", "ioc-bridge"}, |
| {"PB", "io-drawer-backplane"}, |
| {"PC", "power-capacitor"}, |
| {"PD", "processor-card"}, |
| {"PF", "processor"}, |
| {"PI", "ioc-phb"}, |
| {"PO", "spcn"}, |
| {"PN", "spcn-connector"}, |
| {"PR", "pci-riser-card"}, |
| {"PS", "power-supply"}, |
| {"PT", "pass-through-card"}, |
| {"PX", "psc-sync-card"}, |
| {"PW", "power-connector"}, |
| |
| {"RG", "regulator"}, |
| {"RI", "riser"}, |
| {"RK", "rack-indicator"}, |
| {"RW", "riscwatch-connector"}, |
| |
| {"SA", "sys-attn-led"}, |
| {"SB", "backup-sysvpd"}, |
| {"SC", "scsi-connector"}, |
| {"SD", "sas-connector"}, |
| {"SI", "scsi-ide-converter"}, |
| {"SL", "phb-slot"}, |
| {"SN", "smp-cable-connector"}, |
| {"SP", "service-processor"}, |
| {"SR", "service-card"}, |
| {"SS", "soft-switch"}, |
| {"SV", "system-vpd"}, |
| {"SY", "legacy-sysvpd"}, |
| |
| {"TD", "tod-clock"}, |
| {"TI", "torrent-pcie-phb"}, |
| {"TL", "torrent-riser"}, |
| {"TM", "thermal-sensor"}, |
| {"TP", "tpmd-adapter"}, |
| {"TR", "torrent-bridge"}, |
| |
| {"VV", "root-node-vpd"}, |
| |
| {"WD", "water_device"}, |
| }; |
| |
| static const char *vpd_map_name(const char *vpd_name) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(vpd_key_table); i++) |
| if (!strcmp(vpd_key_table[i].keyword, vpd_name)) |
| return vpd_key_table[i].description; |
| |
| prlog(PR_WARNING, "VPD: Could not map FRU ID %s to a known name\n", |
| vpd_name); |
| |
| return "Unknown"; |
| } |
| |
| static const struct card_info *card_info_lookup(char *ccin) |
| { |
| int i; |
| for(i = 0; i < ARRAY_SIZE(card_table); i++) |
| /* CCIN is always 4 bytes in size */ |
| if (!strncmp(card_table[i].ccin, ccin, 4)) |
| return &card_table[i]; |
| return NULL; |
| } |
| |
| /* Discard trailing spaces and populate device tree */ |
| static struct dt_property *dt_add_prop_sanitize_val(struct dt_node *node, |
| const char *name, const char *val, int vlen) |
| { |
| char *prop = zalloc(vlen + 1); |
| int i; |
| struct dt_property *p = NULL; |
| |
| if (!prop) |
| return p; |
| |
| memcpy(prop, val, vlen); |
| for (i = vlen - 1; i >= 0; i--) { |
| if (prop[i] != 0x20) { |
| prop[i + 1] = '\0'; |
| break; |
| } |
| } |
| |
| if (i >= 0 && !dt_find_property(node, name)) |
| p = dt_add_property_string(node, name, prop); |
| |
| free(prop); |
| return p; |
| } |
| |
| /* |
| * OpenPower system does not provide processor vendor name under FRU VPD. |
| * Parse processor module VPD to get vendor detail |
| */ |
| void dt_add_proc_vendor(struct dt_node *proc_node, |
| const void *mvpd, unsigned int mvpd_sz) |
| { |
| const void *kw; |
| uint8_t sz; |
| |
| kw = vpd_find(mvpd, mvpd_sz, "VINI", "VN", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(proc_node, "vendor", kw, sz); |
| } |
| |
| /* |
| * For OpenPOWER, we only decipher OPFR records. While OP HDAT have VINI |
| * records too, populating the fields in there is optional. Also, there |
| * is an overlap in the fields contained therein. |
| */ |
| static void vpd_opfr_parse(struct dt_node *node, |
| const void *fruvpd, unsigned int fruvpd_sz) |
| { |
| const void *kw; |
| uint8_t sz; |
| |
| /* Vendor Name */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "OPFR", "VN", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "vendor", kw, sz); |
| |
| /* FRU Description */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "OPFR", "DR", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "description", kw, sz); |
| |
| /* Part number */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "OPFR", "VP", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "part-number", kw, sz); |
| |
| /* Serial number */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "OPFR", "VS", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "serial-number", kw, sz); |
| |
| /* Build date in BCD */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "OPFR", "MB", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "build-date", kw, sz); |
| |
| return; |
| } |
| |
| /* |
| * For CPUs, parse the VRML data. |
| */ |
| static void vpd_vrml_parse(struct dt_node *node, |
| const void *fruvpd, unsigned int fruvpd_sz) |
| { |
| const void *kw; |
| uint8_t sz; |
| |
| /* Part number */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VRML", "PN", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "part-number", kw, sz); |
| |
| /* Serial number */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VRML", "SN", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "serial-number", kw, sz); |
| |
| return; |
| } |
| |
| static void vpd_vini_parse(struct dt_node *node, |
| const void *fruvpd, unsigned int fruvpd_sz) |
| { |
| const void *kw; |
| const char *desc; |
| uint8_t sz; |
| const struct card_info *cinfo; |
| |
| /* FRU Stocking Part Number */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "FN", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "fru-number", kw, sz); |
| |
| /* Serial Number */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "SN", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "serial-number", kw, sz); |
| |
| /* Part Number */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "PN", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "part-number", kw, sz); |
| |
| /* Vendor Name */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "VN", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "vendor", kw, sz); |
| |
| /* CCIN Extension */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "CE", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "ccin-extension", kw, sz); |
| |
| /* HW Version info */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "HW", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "hw-version", kw, sz); |
| |
| /* Card type info */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "CT", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "card-type", kw, sz); |
| |
| /* HW characteristics info */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "B3", &sz); |
| if (kw) |
| dt_add_prop_sanitize_val(node, "hw-characteristics", kw, sz); |
| |
| /* Customer Card Identification Number (CCIN) */ |
| kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "CC", &sz); |
| if (kw) { |
| dt_add_prop_sanitize_val(node, "ccin", kw, sz); |
| |
| cinfo = card_info_lookup((char *)kw); |
| if (cinfo) { |
| dt_add_property_string(node, |
| "description", cinfo->description); |
| } else { |
| desc = vpd_find(fruvpd, fruvpd_sz, "VINI", "DR", &sz); |
| if (desc) { |
| dt_add_prop_sanitize_val(node, |
| "description", desc, sz); |
| } else { |
| dt_add_property_string(node, "description", "Unknown"); |
| prlog(PR_WARNING, |
| "VPD: CCIN desc not available for: %s\n", |
| (char*)kw); |
| } |
| } |
| } |
| |
| return; |
| } |
| |
| static bool valid_child_entry(const struct slca_entry *entry) |
| { |
| if ((entry->install_indic == SLCA_INSTALL_INSTALLED) && |
| (entry->vpd_collected == SLCA_VPD_COLLECTED)) |
| return true; |
| |
| return false; |
| } |
| |
| void vpd_data_parse(struct dt_node *node, const void *fruvpd, u32 fruvpd_sz) |
| { |
| if (vpd_find_record(fruvpd, fruvpd_sz, "OPFR", NULL)) |
| vpd_opfr_parse(node, fruvpd, fruvpd_sz); |
| else if (vpd_find_record(fruvpd, fruvpd_sz, "VRML", NULL)) |
| vpd_vrml_parse(node, fruvpd, fruvpd_sz); |
| else |
| vpd_vini_parse(node, fruvpd, fruvpd_sz); |
| } |
| |
| /* Create the /vpd node and add its children */ |
| void dt_init_vpd_node(void) |
| { |
| const char *name, *p_name; |
| int count, index; |
| uint64_t addr, p_addr; |
| struct dt_node *dt_vpd; |
| struct HDIF_common_hdr *slca_hdr; |
| struct dt_node *parent, *node; |
| const struct slca_entry *entry, *p_entry; |
| |
| dt_vpd = dt_new(dt_root, "vpd"); |
| assert(dt_vpd); |
| dt_add_property_string(dt_vpd, "compatible", "ibm,opal-v3-vpd"); |
| dt_add_property_cells(dt_vpd, "#size-cells", 0); |
| dt_add_property_cells(dt_vpd, "#address-cells", 1); |
| |
| slca_hdr = get_hdif(&spiras->ntuples.slca, SLCA_HDIF_SIG); |
| if (!slca_hdr) { |
| prerror("SLCA Invalid\n"); |
| return; |
| } |
| |
| count = HDIF_get_iarray_size(slca_hdr, SLCA_IDATA_ARRAY); |
| if (count < 0) { |
| prerror("SLCA: Can't find SLCA array size!\n"); |
| return; |
| } |
| |
| for (index = 0; index < count; index++) { |
| /* Get SLCA entry */ |
| entry = slca_get_entry(index); |
| if (!entry) |
| continue; |
| |
| /* |
| * 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. |
| */ |
| if (!valid_child_entry(entry)) |
| goto next_entry; |
| |
| name = vpd_map_name(entry->fru_id); |
| addr = be16_to_cpu(entry->rsrc_id); |
| /* Check node is already created or not */ |
| if (dt_find_by_name_addr(dt_vpd, name, addr)) |
| goto next_entry; |
| |
| /* Get parent node */ |
| if (index == SLCA_ROOT_INDEX) { |
| parent = dt_vpd; |
| } else { |
| p_entry = slca_get_entry(be16_to_cpu(entry->parent_index)); |
| if (!p_entry) |
| goto next_entry; |
| p_name = vpd_map_name(p_entry->fru_id); |
| p_addr = be16_to_cpu(p_entry->rsrc_id); |
| parent = dt_find_by_name_addr(dt_vpd, p_name, p_addr); |
| } |
| if (!parent) |
| goto next_entry; |
| |
| node = dt_new_addr(parent, name, addr); |
| if (!node) { |
| prerror("VPD: Creating node at %s@%"PRIx64" failed\n", |
| name, addr); |
| goto next_entry; |
| } |
| |
| /* Add location code */ |
| slca_vpd_add_loc_code(node, be16_to_cpu(entry->my_index)); |
| /* Add FRU label */ |
| dt_add_property(node, "fru-type", entry->fru_id, 2); |
| dt_add_property_cells(node, "reg", addr); |
| dt_add_property_cells(node, "#size-cells", 0); |
| dt_add_property_cells(node, "#address-cells", 1); |
| |
| next_entry: |
| /* Skip dups -- dups are contiguous */ |
| index += entry->nr_dups; |
| } |
| } |
| |
| struct dt_node *dt_add_vpd_node(const struct HDIF_common_hdr *hdr, |
| int indx_fru, int indx_vpd) |
| { |
| const struct spira_fru_id *fru_id; |
| unsigned int fruvpd_sz, fru_id_sz; |
| const struct slca_entry *entry; |
| struct dt_node *dt_vpd, *node; |
| const void *fruvpd; |
| const char *name; |
| uint64_t addr; |
| |
| fru_id = HDIF_get_idata(hdr, indx_fru, &fru_id_sz); |
| if (!fru_id) |
| return NULL; |
| |
| fruvpd = HDIF_get_idata(hdr, indx_vpd, &fruvpd_sz); |
| if (!CHECK_SPPTR(fruvpd)) |
| return NULL; |
| |
| dt_vpd = dt_find_by_path(dt_root, "/vpd"); |
| if (!dt_vpd) |
| return NULL; |
| |
| entry = slca_get_entry(be16_to_cpu(fru_id->slca_index)); |
| if (!entry) |
| return NULL; |
| |
| name = vpd_map_name(entry->fru_id); |
| addr = be16_to_cpu(entry->rsrc_id); |
| /* Get the node already created */ |
| node = dt_find_by_name_addr(dt_vpd, name, addr); |
| /* |
| * It is unlikely that node not found because vpd nodes have the |
| * corresponding slca entry which we would have used to populate the vpd |
| * tree during the 'first' pass above so that we just need to perform |
| * VINI parse and add the vpd data.. |
| */ |
| if (!node) |
| return NULL; |
| |
| /* Parse VPD fields, ensure that it has not been added already */ |
| if (vpd_valid(fruvpd, fruvpd_sz) |
| && !dt_find_property(node, "ibm,vpd")) { |
| dt_add_property(node, "ibm,vpd", fruvpd, fruvpd_sz); |
| vpd_data_parse(node, fruvpd, fruvpd_sz); |
| } |
| |
| return node; |
| } |
| |
| static void dt_add_model_name(void) |
| { |
| const char *model_name = NULL; |
| const struct machine_info *mi; |
| const struct iplparams_sysparams *p; |
| const struct HDIF_common_hdr *iplp; |
| const struct dt_property *model; |
| |
| model = dt_find_property(dt_root, "model"); |
| |
| iplp = get_hdif(&spiras->ntuples.ipl_parms, "IPLPMS"); |
| if (!iplp) |
| goto def_model; |
| |
| p = HDIF_get_idata(iplp, IPLPARAMS_SYSPARAMS, NULL); |
| if (!CHECK_SPPTR(p)) |
| goto def_model; |
| |
| if (be16_to_cpu(iplp->version) >= 0x60) |
| model_name = p->sys_type_str; |
| |
| def_model: |
| if ((!model_name || model_name[0] == '\0') && model) { |
| mi = machine_info_lookup(model->prop); |
| if (mi) |
| model_name = mi->name; |
| } |
| |
| if(model_name) |
| dt_add_property_string(dt_root, "model-name", model_name); |
| } |
| |
| static void sysvpd_parse_opp(const void *sysvpd, unsigned int sysvpd_sz) |
| { |
| const char *v; |
| uint8_t sz; |
| |
| v = vpd_find(sysvpd, sysvpd_sz, "OSYS", "MM", &sz); |
| if (v) |
| dt_add_prop_sanitize_val(dt_root, "model", v, sz); |
| else |
| dt_add_property_string(dt_root, "model", "Unknown"); |
| |
| v = vpd_find(sysvpd, sysvpd_sz, "OSYS", "SS", &sz); |
| if (v) |
| dt_add_prop_sanitize_val(dt_root, "system-id", v, sz); |
| else |
| dt_add_property_string(dt_root, "system-id", "Unknown"); |
| } |
| |
| |
| static void sysvpd_parse_legacy(const void *sysvpd, unsigned int sysvpd_sz) |
| { |
| const char *model; |
| const char *system_id; |
| const char *brand; |
| uint8_t sz; |
| |
| model = vpd_find(sysvpd, sysvpd_sz, "VSYS", "TM", &sz); |
| if (model) |
| dt_add_prop_sanitize_val(dt_root, "model", model, sz); |
| else |
| dt_add_property_string(dt_root, "model", "Unknown"); |
| |
| system_id = vpd_find(sysvpd, sysvpd_sz, "VSYS", "SE", &sz); |
| if (system_id) |
| dt_add_prop_sanitize_val(dt_root, "system-id", system_id, sz); |
| else |
| dt_add_property_string(dt_root, "system-id", "Unknown"); |
| |
| brand = vpd_find(sysvpd, sysvpd_sz, "VSYS", "BR", &sz); |
| if (brand) |
| dt_add_prop_sanitize_val(dt_root, "system-brand", brand, sz); |
| else |
| dt_add_property_string(dt_root, "brand", "Unknown"); |
| } |
| |
| static void sysvpd_parse(void) |
| { |
| 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; |
| |
| sysvpd_hdr = get_hdif(&spiras->ntuples.system_vpd, SYSVPD_HDIF_SIG); |
| if (!sysvpd_hdr) |
| return; |
| |
| fru_id = HDIF_get_idata(sysvpd_hdr, SYSVPD_IDATA_FRU_ID, &fru_id_sz); |
| if (!fru_id) |
| return; |
| |
| sysvpd = HDIF_get_idata(sysvpd_hdr, SYSVPD_IDATA_KW_VPD, &sysvpd_sz); |
| if (!CHECK_SPPTR(sysvpd)) |
| return; |
| |
| /* 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)); |
| } |
| |
| /* Look for the new OpenPower "OSYS" first */ |
| if (vpd_find_record(sysvpd, sysvpd_sz, "OSYS", NULL)) |
| sysvpd_parse_opp(sysvpd, sysvpd_sz); |
| else |
| sysvpd_parse_legacy(sysvpd, sysvpd_sz); |
| |
| dt_add_model_name(); |
| } |
| |
| 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(&spiras->ntuples.cec_iohub_fru, CECHUB_FRU_HDIF_SIG)) { |
| prerror("VPD: Could not find IO HUB FRU data\n"); |
| return; |
| } |
| |
| for_each_ntuple_idx(&spiras->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; |
| } |
| |
| 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; |
| |
| /* System VPD uses the VSYS record, so its special */ |
| sysvpd_parse(); |
| |
| /* Enclosure */ |
| _vpd_parse(spiras->ntuples.nt_enclosure_vpd); |
| |
| /* Backplane */ |
| _vpd_parse(spiras->ntuples.backplane_vpd); |
| |
| /* clock card -- does this use the FRUVPD sig? */ |
| _vpd_parse(spiras->ntuples.clock_vpd); |
| |
| /* Anchor card */ |
| _vpd_parse(spiras->ntuples.anchor_vpd); |
| |
| /* Op panel -- does this use the FRUVPD sig? */ |
| _vpd_parse(spiras->ntuples.op_panel_vpd); |
| |
| /* Misc CEC FRU */ |
| _vpd_parse(spiras->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(&spiras->ntuples.sp_subsys, SPSS_HDIF_SIG); |
| if (fruvpd_hdr) |
| dt_add_vpd_node(fruvpd_hdr, |
| FRUVPD_IDATA_FRU_ID, FRUVPD_IDATA_KW_VPD); |
| } |