1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 /* 27 * Copyright 2019 Joyent, Inc. 28 */ 29 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <stdarg.h> 33 #include <string.h> 34 #include <strings.h> 35 #include <libnvpair.h> 36 #include <sys/types.h> 37 #include <libipmi.h> 38 #include <fm/topo_mod.h> 39 #include <ctype.h> 40 #include "chip.h" 41 42 #define BUFSZ 128 43 #define JEDEC_TBL_SZ 5 44 45 /* 46 * The following table maps DIMM manufacturer names to a JEDEC ID as sourced 47 * from JEDEC publication JEP106W. This is (obviously) a sparse table which 48 * only contains entries for manufacturers whose DIMM's have been qualified 49 * for use on Sun platforms. 50 */ 51 static const char *jedec_tbl[JEDEC_TBL_SZ][2] = 52 { 53 { "HYUNDAI ELECTRONICS", "00AD" }, 54 { "INFINEON", "00C1" }, 55 { "MICRON TECHNOLOGY", "002C" }, 56 { "QIMONDA", "7F51" }, 57 { "SAMSUNG", "00CE" }, 58 }; 59 60 static int 61 ipmi_serial_lookup(topo_mod_t *mod, char *ipmi_tag, char *buf) 62 { 63 char *fru_data; 64 int i, found_id = 0, serial_len; 65 ipmi_handle_t *hdl; 66 ipmi_sdr_fru_locator_t *fru_loc; 67 ipmi_fru_prod_info_t prod_info; 68 69 topo_mod_dprintf(mod, "ipmi_serial_lookup() called\n"); 70 if ((hdl = topo_mod_ipmi_hold(mod)) == NULL) { 71 topo_mod_dprintf(mod, "Failed to get IPMI handle\n"); 72 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 73 } 74 75 topo_mod_dprintf(mod, "Looking up FRU data for %s ...\n", ipmi_tag); 76 if ((fru_loc = ipmi_sdr_lookup_fru(hdl, (const char *)ipmi_tag)) 77 == NULL) { 78 topo_mod_dprintf(mod, "Failed to lookup %s (%s)\n", ipmi_tag, 79 ipmi_errmsg(hdl)); 80 topo_mod_ipmi_rele(mod); 81 return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); 82 } 83 84 85 topo_mod_dprintf(mod, "Reading FRU data ...\n"); 86 if (ipmi_fru_read(hdl, fru_loc, &fru_data) < 0) { 87 topo_mod_dprintf(mod, "Failed to read FRU data (%s)\n", 88 ipmi_errmsg(hdl)); 89 topo_mod_ipmi_rele(mod); 90 return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); 91 } 92 93 topo_mod_dprintf(mod, "Parsing product info area ...\n"); 94 if (ipmi_fru_parse_product(hdl, fru_data, &prod_info) < 0) { 95 topo_mod_dprintf(mod, "Failed to read FRU product info (%s)\n", 96 ipmi_errmsg(hdl)); 97 free(fru_data); 98 topo_mod_ipmi_rele(mod); 99 return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); 100 } 101 free(fru_data); 102 topo_mod_ipmi_rele(mod); 103 104 topo_mod_dprintf(mod, "FRU Product Serial: %s\n", 105 prod_info.ifpi_product_serial); 106 topo_mod_dprintf(mod, "Manufacturer Name: \"%s\"\n", 107 prod_info.ifpi_manuf_name); 108 109 serial_len = strnlen(prod_info.ifpi_product_serial, FRU_INFO_MAXLEN); 110 111 /* 112 * Newer ILOM software that has the fix for CR 6607996 will have 113 * an 18-character serial number that has been synthesized using 114 * the recipe from the Sun SPD JEDEC DIMM specification. If we 115 * find an 18-character then we'll simply use it, as-is, and 116 * return. 117 */ 118 if (serial_len == 18) { 119 (void) memcpy(buf, prod_info.ifpi_product_serial, 18); 120 *(buf+18) = '\0'; 121 return (0); 122 } 123 /* 124 * Older ILOM software that DOESN'T have the fix for CR 6607996 will 125 * only provide the 8 character manufacturer serial number. 126 * 127 * However, if for some reason the product info area doesn't have the 128 * serial information or if the serial isn't 8 characters (we may 129 * encounter SP's that don't populate the serial field or are buggy and 130 * populate it with garbage), then we'll stop right now and just set the 131 * buf to an empty string. 132 */ 133 if (serial_len != 8) { 134 *buf = '\0'; 135 return (0); 136 } 137 138 /* 139 * What follows is a very crude adaptation of the recipe from the 140 * Sun SPD JEDEC DIMM specification for synthesizing globally unique 141 * serial numbers from the 8 character manufacturer serial number. 142 * 143 * The Sun serial number takes the following form: 144 * 145 * jjjjllyywwssssssss 146 * 147 * The components are: 148 * 149 * yyyy: JEDEC ID in hex (2 byte manufacture ID, 2 byte continuation 150 * code). 151 * 152 * ll: The memory module's manufacturing location. 153 * 154 * yyww: The module's manufacturing date (2-digit year/2-digit week) 155 * 156 * ssssssss: The 8 character maufacturer serial number 157 */ 158 /* 159 * First we need to normalize the manufacturer name we pulled out of 160 * the FRU product info area. Our normalization algorithm is fairly 161 * simple: 162 * - convert all alpha chars to uppercase 163 * - convert non-alphanumeric characters to a single space 164 * 165 * We use the normalized name to lookup the JEDEC ID from a static 166 * table. If the FRU area didn't have a manufacturer name or if the ID 167 * lookup fails we'll set jjjj to 0000. 168 */ 169 for (i = 0; prod_info.ifpi_manuf_name[i]; i++) { 170 prod_info.ifpi_manuf_name[i] = 171 toupper(prod_info.ifpi_manuf_name[i]); 172 if (!isalpha(prod_info.ifpi_manuf_name[i]) && 173 !isdigit(prod_info.ifpi_manuf_name[i])) 174 prod_info.ifpi_manuf_name[i] = (char)0x20; 175 } 176 topo_mod_dprintf(mod, "Normalized Manufacturer Name \"%s\"\n", 177 prod_info.ifpi_manuf_name); 178 179 for (i = 0; i < JEDEC_TBL_SZ; i++) 180 if (strcmp(prod_info.ifpi_manuf_name, jedec_tbl[i][0]) == 0) { 181 found_id = 1; 182 break; 183 } 184 185 if (found_id) 186 (void) memcpy(buf, jedec_tbl[i][1], 4); 187 else 188 (void) memcpy(buf, (char *)("0000"), 4); 189 190 /* 191 * The manufacturing location and date is not available via IPMI on 192 * Sun platforms, so we simply set these six digits to zeros. 193 */ 194 (void) memcpy((buf+4), (char *)("000000"), 6); 195 196 /* 197 * Finally, we just copy the 8 character product serial straight over 198 * and then NULL terminate the string. 199 */ 200 (void) memcpy((buf+10), prod_info.ifpi_product_serial, 8); 201 *(buf+18) = '\0'; 202 203 return (0); 204 } 205 206 /* ARGSUSED */ 207 int 208 get_dimm_serial(topo_mod_t *mod, tnode_t *node, topo_version_t vers, 209 nvlist_t *in, nvlist_t **out) 210 { 211 char **entity_refs, fru_serial[FRU_INFO_MAXLEN]; 212 int err, rv = 0, i; 213 uint_t nelems; 214 boolean_t found_serial = B_FALSE; 215 216 if (topo_prop_get_string_array(node, TOPO_PGROUP_IPMI, "entity_ref", 217 &entity_refs, &nelems, &err) != 0) { 218 topo_mod_dprintf(mod, "%s: Failed to lookup entity_ref property" 219 " (%s)", __func__, topo_strerror(err)); 220 return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); 221 } 222 223 for (i = 0; i < nelems; i++) { 224 if (ipmi_serial_lookup(mod, entity_refs[i], fru_serial) == 0) { 225 found_serial = B_TRUE; 226 break; 227 } else 228 topo_mod_dprintf(mod, "Failed to lookup serial for " 229 "%s\n", entity_refs[i]); 230 } 231 if (! found_serial) 232 (void) strcpy(fru_serial, ""); 233 234 if (store_prop_val(mod, fru_serial, "serial", out) != 0) { 235 topo_mod_dprintf(mod, "Failed to set serial\n"); 236 /* topo errno already set */ 237 rv = -1; 238 } 239 topo_mod_strfreev(mod, entity_refs, nelems); 240 241 return (rv); 242 } 243