diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
commit | 1d880992fd8c8457a2d990ac6622cfd58fb1b261 (patch) | |
tree | c4c843b12e96b5612c315db5a23c5da1a900618c /hdata/vpd.c | |
download | talos-skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.gz talos-skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.zip |
Initial commit of Open Source release
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'hdata/vpd.c')
-rw-r--r-- | hdata/vpd.c | 851 |
1 files changed, 851 insertions, 0 deletions
diff --git a/hdata/vpd.c b/hdata/vpd.c new file mode 100644 index 00000000..e568a065 --- /dev/null +++ b/hdata/vpd.c @@ -0,0 +1,851 @@ +/* Copyright 2013-2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <skiboot.h> +#include <vpd.h> +#include <string.h> +#include "spira.h" +#include "hdata.h" +#include <device.h> +#include "hdata.h" + +struct card_info { + const char *ccin; /* Customer card identification number */ + const char *description; +}; + +static const struct card_info card_table[] = { + {"2B06", "System planar 2S4U"}, + {"2B07", "System planar 1S4U"}, + {"2B2E", "System planar 2S2U"}, + {"2B2F", "System planar 1S2U"}, + {"2CD4", "System planar 2S4U"}, + {"2CD5", "System planar 1S4U"}, + {"2CD6", "System planar 2S2U"}, + {"2CD7", "System planar 1S2U"}, + {"2CD7", "System planar 1S2U"}, + {"2B09", "Base JBOD, RAID and Backplane HD"}, + {"57D7", "Split JBOD, RAID Card"}, + {"2B0B", "Native I/O Card"}, + + /* Anchor cards */ + {"52FE", "System Anchor Card - IBM Power 824"}, + {"52F2", "System Anchor Card - IBM Power 814"}, + {"52F5", "System Anchor Card - IBM Power 822"}, + {"561A", "System Anchor Card - IBM Power 824L"}, + {"524D", "System Anchor Card - IBM Power 822L"}, + {"560F", "System Anchor Card - IBM Power 812L"}, + {"561C", "System Anchor Card - DS8870"}, + + /* Memory DIMMs */ + {"31E0", "16GB CDIMM"}, + {"31E8", "16GB CDIMM"}, + {"31E1", "32GB CDIMM"}, + {"31E9", "32GB CDIMM"}, + {"31E2", "64GB CDIMM"}, + {"31EA", "64GB CDIMM"}, + + /* Power supplies */ + {"2B1D", "Power Supply 900W AC"}, + {"2B1E", "Power Supply 1400W AC"}, + {"2B75", "Power Supply 1400W HVDC"}, + + /* Fans */ + {"2B1F", "Fan 4U (A1, A2, A3, A4)"}, + {"2B29", "Fan 2U (A1, A2, A3, A4, A5, A6)"}, + + /* Other cards */ +}; + +static const struct card_info *card_info_lookup(char *ccin) +{ + int i; + for(i = 0; i < ARRAY_SIZE(card_table); i++) + if (!strcmp(card_table[i].ccin, ccin)) + return &card_table[i]; + return NULL; +} + +static void vpd_vini_parse(struct dt_node *node, + const void *fruvpd, unsigned int fruvpd_sz) +{ + const void *kw; + char *str; + uint8_t kwsz; + const struct card_info *cinfo; + + /* FRU Stocking Part Number */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "FN", &kwsz); + if (kw) { + str = zalloc(kwsz + 1); + if (!str) + goto no_memory; + memcpy(str, kw, kwsz); + dt_add_property_string(node, "fru-number", str); + free(str); + } + + /* Serial Number */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "SN", &kwsz); + if (kw) { + str = zalloc(kwsz + 1); + if (!str) + goto no_memory; + memcpy(str, kw, kwsz); + dt_add_property_string(node, "serial-number", str); + free(str); + } + + /* Part Number */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "PN", &kwsz); + if (kw) { + str = zalloc(kwsz + 1); + if (!str) + goto no_memory; + memcpy(str, kw, kwsz); + dt_add_property_string(node, "part-number", str); + free(str); + } + + /* Customer Card Identification Number (CCIN) */ + kw = vpd_find(fruvpd, fruvpd_sz, "VINI", "CC", &kwsz); + if (kw) { + str = zalloc(kwsz + 1); + if (!str) + goto no_memory; + memcpy(str, kw, kwsz); + dt_add_property_string(node, "ccin", str); + cinfo = card_info_lookup(str); + if (cinfo) + dt_add_property_string(node, + "description", cinfo->description); + free(str); + } + return; +no_memory: + prerror("VPD: memory allocation failure in VINI parsing\n"); +} + +static const char *vpd_map_name(const char *vpd_name) +{ + /* vpd_name is a 2 char array */ + switch (vpd_name[0]) { + case 'A': + switch (vpd_name[1]) { + case 'A': + return "ac-power-supply"; + case 'M': + return "air-mover"; + case 'V': + return "anchor-card"; + } + break; + case 'B': + switch (vpd_name[1]) { + case 'A': + return "bus-adapter-card"; + case 'C': + return "battery-charger"; + case 'D': + return "bus-daughter-card"; + case 'E': + return "bus-expansion-card"; + case 'P': + return "backplane"; + case 'R': + return "backplane-riser"; + case 'X': + return "backplane-extender"; + } + break; + case 'C': + switch (vpd_name[1]) { + case 'A': + return "calgary-bridge"; + case 'B': + return "infiniband-connector"; + case 'C': + return "clock-card"; + case 'D': + return "card-connector"; + case 'E': + return "ethernet-connector"; + case 'L': + return "calgary-phb"; + case 'I': + return "capacity-card"; + case 'O': + return "sma-connector"; + case 'P': + return "processor-capacity-card"; + case 'R': + return "rio-connector"; + case 'S': + return "serial-connector"; + case 'U': + return "usb-connector"; + } + break; + case 'D': + switch (vpd_name[1]) { + case 'B': + return "dasd-backplane"; + case 'C': + return "drawer-card"; + case 'E': + return "drawer-extension"; + case 'I': + return "drawer-interposer"; + case 'L': + return "p7ih-dlink-connector"; + case 'T': + return "legacy-pci-card"; + case 'V': + return "media-drawer-led"; + } + break; + case 'E': + switch (vpd_name[1]) { + case 'I': + return "enclosure-led"; + case 'F': + return "enclosure-fault-led"; + case 'S': + return "embedded-sas"; + case 'T': + return "ethernet-riser"; + case 'V': + return "enclosure"; + } + break; + case 'F': + switch (vpd_name[1]) { + case 'M': + return "frame"; + } + break; + case 'H': + switch (vpd_name[1]) { + case 'B': + return "host-rio-pci-card"; + case 'D': + return "high-speed-card"; + case 'M': + return "hmc-connector"; + } + break; + case 'I': + switch (vpd_name[1]) { + case 'B': + return "io-backplane"; + case 'C': + return "io-card"; + case 'D': + return "ide-connector"; + case 'I': + return "io-drawer-led"; + case 'P': + return "interplane-card"; + case 'S': + return "smp-vbus-cable"; + case 'T': + return "enclosure-cable"; + case 'V': + return "io-enclosure"; + } + break; + case 'K': + switch (vpd_name[1]) { + case 'V': + return "keyboard-led"; + } + break; + case 'L': + switch (vpd_name[1]) { + case '2': + return "l2-cache-module"; + case '3': + return "l3-cache-module"; + case 'C': + return "squadrons-light-connector"; + case 'R': + return "p7ih-connector"; + case 'O': + return "system-locate-led"; + case 'T': + return "squadrons-light-strip"; + } + break; + case 'M': + switch (vpd_name[1]) { + case 'B': + return "media-backplane"; + case 'E': + return "map-extension"; + case 'M': + return "mip-meter"; + case 'S': + return "ms-dimm"; + } + break; + case 'N': + switch (vpd_name[1]) { + case 'B': + return "nvram-battery"; + case 'C': + return "sp-node-controller"; + case 'D': + return "numa-dimm"; + } + break; + case 'O': + switch (vpd_name[1]) { + case 'D': + return "cuod-card"; + case 'P': + return "op-panel"; + case 'S': + return "oscillator"; + } + break; + case 'P': + switch (vpd_name[1]) { + case '2': + return "ioc"; + case '5': + return "ioc-bridge"; + case 'B': + return "io-drawer-backplane"; + case 'C': + return "power-capacitor"; + case 'D': + return "processor-card"; + case 'F': + return "processor"; + case 'I': + return "ioc-phb"; + case 'O': + return "spcn"; + case 'N': + return "spcn-connector"; + case 'R': + return "pci-riser-card"; + case 'S': + return "power-supply"; + case 'T': + return "pass-through-card"; + case 'X': + return "psc-sync-card"; + case 'W': + return "power-connector"; + } + break; + case 'R': + switch (vpd_name[1]) { + case 'G': + return "regulator"; + case 'I': + return "riser"; + case 'K': + return "rack-indicator"; + case 'W': + return "riscwatch-connector"; + } + break; + case 'S': + switch (vpd_name[1]) { + case 'A': + return "sys-attn-led"; + case 'B': + return "backup-sysvpd"; + case 'C': + return "scsi-connector"; + case 'D': + return "sas-connector"; + case 'I': + return "scsi-ide-converter"; + case 'L': + return "phb-slot"; + case 'P': + return "service-processor"; + case 'R': + return "service-card"; + case 'S': + return "soft-switch"; + case 'V': + return "system-vpd"; + case 'Y': + return "legacy-sysvpd"; + } + break; + case 'T': + switch (vpd_name[1]) { + case 'D': + return "tod-clock"; + case 'I': + return "torrent-pcie-phb"; + case 'L': + return "torrent-riser"; + case 'M': + return "thermal-sensor"; + case 'P': + return "tpmd-adapter"; + case 'R': + return "torrent-bridge"; + } + break; + case 'V': + switch (vpd_name[1]) { + case 'V': + return "root-node-vpd"; + } + break; + case 'W': + switch (vpd_name[1]) { + case 'D': + return "water_device"; + } + break; + } + return "Unknown"; +} + +static bool valid_child_entry(const struct slca_entry *entry) +{ + if (!entry) + return false; + + /* + * Skip entries with independent ntuple FRUVPD/MSVPD, etc., + * representations, since they have a unique PN, FN, SN, et al. + * We add details for those devices via the ntuple walk. + */ + switch (entry->fru_id[0]) { + case 'A': + switch (entry->fru_id[1]) { + case 'V': /* AV */ + return false; + } + break; + case 'B': + switch (entry->fru_id[1]) { + case 'P': /* BP */ + case 'X': /* BX */ + return false; + } + break; + case 'C': + switch (entry->fru_id[1]) { + case 'C': /* CC */ + return false; + } + break; + case 'D': + switch (entry->fru_id[1]) { + case 'B': /* DB */ + return false; + } + break; + case 'E': + switch (entry->fru_id[1]) { + case 'V': /* EV */ + return false; + } + break; + case 'M': + switch (entry->fru_id[1]) { + case 'S': /* MS */ + return false; + } + break; + case 'O': + switch (entry->fru_id[1]) { + case 'P': /* OP */ + return false; + } + break; + case 'R': + switch (entry->fru_id[1]) { + case 'I': /* RI */ + return false; + } + break; + case 'P': + switch (entry->fru_id[1]) { + case '2': /* P2 */ + case '5': /* P5 */ + case 'F': /* PF */ + return false; + } + break; + case 'S': + switch (entry->fru_id[1]) { + case 'P': /* SP */ + return false; + } + break; + case 'T': + switch (entry->fru_id[1]) { + case 'P': /* TP */ + return false; + } + break; + default: + break; + } + + if ((entry->install_indic == SLCA_INSTALL_INSTALLED) && + (entry->vpd_collected == SLCA_VPD_COLLECTED)) + return true; + + return false; +} + +static void vpd_add_children(struct dt_node *parent, uint16_t slca_index) +{ + const struct slca_entry *s_entry, *child; + uint16_t current_child_index, max_index; + + s_entry = slca_get_entry(slca_index); + if (!s_entry || (s_entry->nr_child == 0)) + return; + + /* + * This slca_entry has children. Parse the children array + * and add nodes for valid entries. + * + * A child entry is valid if all of the following criteria is met + * a. SLCA_INSTALL_INSTALLED is set in s_entry->install_indic + * b. SLCA_VPD_COLLECTED is set in s_entry->vpd_collected + * c. The SLCA is not a duplicate entry. + */ + + /* current_index tracks where we are right now in the array */ + current_child_index = be16_to_cpu(s_entry->child_index); + + /* max_index tracks how far down the array we must traverse */ + max_index = be16_to_cpu(s_entry->child_index) + + be16_to_cpu(s_entry->nr_child); + + while (current_child_index < max_index) { + child = slca_get_entry(current_child_index); + if (!child) + return; + + if (valid_child_entry(child)) { + const char *name; + uint64_t addr; + struct dt_node *node; + + /* create new node, add location code */ + name = vpd_map_name(child->fru_id); + addr = (uint64_t)be16_to_cpu(child->rsrc_id); + node = dt_new_addr(parent, name, addr); + if (!node) { + prerror("VPD: Creating node at %s@%llx failed\n", + name, addr); + return; + } + slca_vpd_add_loc_code(node, be16_to_cpu(child->my_index)); + + /* Add child FRU type */ + dt_add_property(node, "fru-type", child->fru_id, 2); + + /* recursively add children */ + vpd_add_children(node, be16_to_cpu(child->my_index)); + } + + /* Skip dups -- currently we presume dups are contiguous */ + if (child->nr_dups > 0) + current_child_index += child->nr_dups; + current_child_index++; + } + return; +} + +struct dt_node *dt_add_vpd_node(const struct HDIF_common_hdr *hdr, + int indx_fru, int indx_vpd) +{ + const void *fruvpd; + unsigned int fruvpd_sz; + unsigned int fru_id_sz; + uint64_t addr; + struct dt_node *dt_vpd; + struct dt_node *node; + const struct spira_fru_id *fru_id; + const struct slca_entry *s_entry; + const char *vpd_name; + const char *name; + int len; + char *lname; + + fru_id = HDIF_get_idata(hdr, indx_fru, &fru_id_sz); + if (!fru_id) + return NULL; + + s_entry = slca_get_entry(be16_to_cpu(fru_id->slca_index)); + if (valid_child_entry(s_entry)) /* Don't populate child VPD here */ + return NULL; + + fruvpd = HDIF_get_idata(hdr, indx_vpd, &fruvpd_sz); + if (!CHECK_SPPTR(fruvpd)) + return NULL; + + vpd_name = slca_get_vpd_name(be16_to_cpu(fru_id->slca_index)); + if (!vpd_name) { + prerror("VPD: VPD name at index %d couldn't be found\n", + fru_id->slca_index); + return NULL; + } + + dt_vpd = dt_find_by_path(dt_root, "/vpd"); + if (!dt_vpd) + return NULL; + + /* Get node name */ + name = vpd_map_name(vpd_name); + addr = (uint64_t)be16_to_cpu(fru_id->rsrc_id); + len = strlen(name) + STR_MAX_CHARS(addr) + 2; + lname = zalloc(len); + if (!lname) { + prerror("VPD: Failed to allocate memory\n"); + return NULL; + } + snprintf(lname, len, "%s@%llx", name, (long long)addr); + + /* + * FRU can be a child of some other FRU. Make sure + * we have not added this node already. + */ + node = dt_find_by_path(dt_vpd, lname); + if (node) { + free(lname); + return NULL; + } + + node = dt_new(dt_vpd, lname); + if (!node) { + free(lname); + return NULL; + } + + /* Parse VPD fields */ + dt_add_property(node, "ibm,vpd", fruvpd, fruvpd_sz); + vpd_vini_parse(node, fruvpd, fruvpd_sz); + + /* Location code */ + slca_vpd_add_loc_code(node, be16_to_cpu(fru_id->slca_index)); + /* Add FRU label */ + dt_add_property(node, "fru-type", vpd_name, 2); + vpd_add_children(node, be16_to_cpu(fru_id->slca_index)); + + free(lname); + return node; +} + +static void sysvpd_parse(void) +{ + const char *model; + const char *system_id; + const char *brand; + char *str; + uint8_t sz; + const void *sysvpd; + unsigned int sysvpd_sz; + unsigned int fru_id_sz; + struct dt_node *dt_vpd; + const struct spira_fru_id *fru_id; + struct HDIF_common_hdr *sysvpd_hdr; + const struct machine_info *mi; + + sysvpd_hdr = get_hdif(&spira.ntuples.system_vpd, SYSVPD_HDIF_SIG); + if (!sysvpd_hdr) + goto no_sysvpd; + + fru_id = HDIF_get_idata(sysvpd_hdr, SYSVPD_IDATA_FRU_ID, &fru_id_sz); + if (!fru_id) + goto no_sysvpd;; + + sysvpd = HDIF_get_idata(sysvpd_hdr, SYSVPD_IDATA_KW_VPD, &sysvpd_sz); + if (!CHECK_SPPTR(sysvpd)) + goto no_sysvpd; + + /* Add system VPD */ + dt_vpd = dt_find_by_path(dt_root, "/vpd"); + if (dt_vpd) { + dt_add_property(dt_vpd, "ibm,vpd", sysvpd, sysvpd_sz); + slca_vpd_add_loc_code(dt_vpd, be16_to_cpu(fru_id->slca_index)); + } + + model = vpd_find(sysvpd, sysvpd_sz, "VSYS", "TM", &sz); + if (!model) + goto no_sysvpd; + str = zalloc(sz + 1); + if (!str) + goto no_sysvpd; + memcpy(str, model, sz); + dt_add_property_string(dt_root, "model", str); + mi = machine_info_lookup(str); + if (mi) + dt_add_property_string(dt_root, "model-name", mi->name); + free(str); + dt_add_property_string(dt_root, "vendor", "IBM"); + + system_id = vpd_find(sysvpd, sysvpd_sz, "VSYS", "SE", &sz); + if (!system_id) + goto no_sysid; + str = zalloc(sz + 1); + if (!str) + goto no_sysid; + memcpy(str, system_id, sz); + dt_add_property_string(dt_root, "system-id", str); + free(str); + + brand = vpd_find(sysvpd, sysvpd_sz, "VSYS", "BR", &sz); + if (!brand) + goto no_brand; + str = zalloc(sz + 1); + if (!str) + goto no_brand; + memcpy(str, brand, sz); + dt_add_property_string(dt_root, "system-brand", str); + free(str); + + return; + +no_brand: + dt_add_property_string(dt_root, "system-brand", "Unknown"); + return; + +no_sysid: + dt_add_property_string(dt_root, "system-id", "Unknown"); + return; + + no_sysvpd: + dt_add_property_string(dt_root, "model", "Unknown"); +} + +static void iokid_vpd_parse(const struct HDIF_common_hdr *iohub_hdr) +{ + const struct HDIF_child_ptr *iokids; + const struct HDIF_common_hdr *iokid; + unsigned int i; + + iokids = HDIF_child_arr(iohub_hdr, CECHUB_CHILD_IO_KIDS); + if (!CHECK_SPPTR(iokids)) { + prerror("VPD: No IOKID child array\n"); + return; + } + + for (i = 0; i < be32_to_cpu(iokids->count); i++) { + iokid = HDIF_child(iohub_hdr, iokids, i, + IOKID_FRU_HDIF_SIG); + /* IO KID VPD structure layout is similar to FRUVPD */ + if (iokid) + dt_add_vpd_node(iokid, + FRUVPD_IDATA_FRU_ID, FRUVPD_IDATA_KW_VPD); + } +} + +static void iohub_vpd_parse(void) +{ + const struct HDIF_common_hdr *iohub_hdr; + const struct cechub_hub_fru_id *fru_id_data; + unsigned int i, vpd_sz, fru_id_sz; + + if (!get_hdif(&spira.ntuples.cec_iohub_fru, CECHUB_FRU_HDIF_SIG)) { + prerror("VPD: Could not find IO HUB FRU data\n"); + return; + } + + for_each_ntuple_idx(&spira.ntuples.cec_iohub_fru, iohub_hdr, + i, CECHUB_FRU_HDIF_SIG) { + + fru_id_data = HDIF_get_idata(iohub_hdr, + CECHUB_FRU_ID_DATA_AREA, + &fru_id_sz); + + /* P8, IO HUB is on processor card and we have a + * daughter card array + */ + if (fru_id_data && + be32_to_cpu(fru_id_data->card_type) == CECHUB_FRU_TYPE_CPU_CARD) { + iokid_vpd_parse(iohub_hdr); + continue; + } + + /* On P7, the keyword VPD will not be NULL */ + if (HDIF_get_idata(iohub_hdr, + CECHUB_ASCII_KEYWORD_VPD, &vpd_sz)) + dt_add_vpd_node(iohub_hdr, CECHUB_FRU_ID_DATA, + CECHUB_ASCII_KEYWORD_VPD); + } +} + +static void _vpd_parse(struct spira_ntuple tuple) +{ + const struct HDIF_common_hdr *fruvpd_hdr; + unsigned int i; + + if (!get_hdif(&tuple, FRUVPD_HDIF_SIG)) + return; + + for_each_ntuple_idx(&tuple, fruvpd_hdr, i, FRUVPD_HDIF_SIG) + dt_add_vpd_node(fruvpd_hdr, + FRUVPD_IDATA_FRU_ID, FRUVPD_IDATA_KW_VPD); +} + +void vpd_parse(void) +{ + const struct HDIF_common_hdr *fruvpd_hdr; + + /* Enclosure */ + _vpd_parse(spira.ntuples.nt_enclosure_vpd); + + /* Backplane */ + _vpd_parse(spira.ntuples.backplane_vpd); + + /* System VPD uses the VSYS record, so its special */ + sysvpd_parse(); + + /* clock card -- does this use the FRUVPD sig? */ + _vpd_parse(spira.ntuples.clock_vpd); + + /* Anchor card */ + _vpd_parse(spira.ntuples.anchor_vpd); + + /* Op panel -- does this use the FRUVPD sig? */ + _vpd_parse(spira.ntuples.op_panel_vpd); + + /* External cache FRU vpd -- does this use the FRUVPD sig? */ + _vpd_parse(spira.ntuples.ext_cache_fru_vpd); + + /* Misc CEC FRU */ + _vpd_parse(spira.ntuples.misc_cec_fru_vpd); + + /* CEC IO HUB FRU */ + iohub_vpd_parse(); + + /* + * SP subsystem -- while the rest of the SPINFO structure is + * different, the FRU ID data and pointer pair to keyword VPD + * are the same offset as a FRUVPD entry. So reuse it + */ + fruvpd_hdr = get_hdif(&spira.ntuples.sp_subsys, SPSS_HDIF_SIG); + if (fruvpd_hdr) + dt_add_vpd_node(fruvpd_hdr, + FRUVPD_IDATA_FRU_ID, FRUVPD_IDATA_KW_VPD); +} |