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 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Copyright 2019 Peter Tribble. 29 */ 30 31 #include <mem.h> 32 #include <fm/fmd_fmri.h> 33 #include <fm/libtopo.h> 34 35 #include <string.h> 36 #include <strings.h> 37 #include <ctype.h> 38 39 #define ISHCUNUM(unum) (strncmp(unum, "hc:/", 4) == 0) 40 41 /* 42 * Given a DIMM or bank unum, mem_unum_burst will break it apart into individual 43 * DIMM names. If it's a DIMM, one name will be returned. If it's a bank, the 44 * unums for the individual DIMMs will be returned. 45 * 46 * Plain J-number DIMM and bank unums are simple. J DIMMs have one J number. J 47 * banks have multiple whitespace-separated J numbers. 48 * 49 * The others are more complex, and consist of a common portion c, a colon, and 50 * a DIMM-specific portion d. DIMMs are of the form "c: d", while banks are of 51 * the form "c: d d ...". The patterns are designed to handle the complex case, 52 * but also handle the simple ones as an afterthought. bd_pat is used to 53 * match specific styles of unum. In bd_pat, the first %n indicates the end of 54 * the common portion ("c" above). The second %n marks the beginning of the 55 * repetitive portion ("d" above). The third %n is used to determine whether or 56 * not the entire pattern matched. bd_reppat is used to match instances of the 57 * repetitive part. 58 * 59 * sscanf is your disturbingly powerful friend. 60 * 61 * The "bd_subst" element of the bank_dimm structure was added for Ontario 62 * in order to accommodate its bank string names. Previously, to convert 63 * from a bank representation <common piece> <dimm1> <dimm2> ... 64 * we concatenated the common piece with each dimm-specific piece in turn, 65 * possibly deleting some characters in between. Ontario is the first 66 * platform which requires that characters be substituted (like a vi s/1/2/) 67 * in place of characters deleted. "bd_subst" represents the character(s) 68 * to be substituted between the common piece and each dimm-specific piece 69 * as part of the bursting. For prior platforms, this value is skipped. 70 * 71 * Example: 72 * input: "MB/CMP0/CH3: R1/D0/J1901 R1/D1/J2001" 73 * outputs: "MB/CMP0/CH3/R1/D0/J1901", "MB/CMP0/CH3/R1/D1/J2001" 74 */ 75 76 typedef struct bank_dimm { 77 const char *bd_pat; 78 const char *bd_reppat; 79 const char *bd_subst; 80 } bank_dimm_t; 81 82 static const bank_dimm_t bank_dimm[] = { 83 { "%n%nJ%*4d%n", " J%*4d%n" }, 84 { "MB/P%*d/%nB%*d:%n%n", " B%*d/D%*d%n" }, 85 { "MB/P%*d/%nB%*d/D%*d:%n%n", " B%*d/D%*d%n" }, 86 { "C%*d/P%*d/%nB%*d:%n%n", " B%*d/D%*d%n" }, 87 { "C%*d/P%*d/%nB%*d/D%*d:%n%n", " B%*d/D%*d%n" }, 88 { "Slot %*c: %n%nJ%*4d%n", " J%*4d%n" }, 89 { "%n%nDIMM%*d%n", " DIMM%*d%n" }, 90 { "MB/%nDIMM%*d MB/DIMM%*d: %n%n", " DIMM%*d%n" }, 91 { "MB/%nDIMM%*d:%n%n", " DIMM%*d%n" }, 92 { "MB/CMP%*d/CH%*d%n:%n%n", " R%*d/D%*d/J%*4d%n", "/" }, 93 { "MB/CMP%*d/CH%*d%n%n%n", "/R%*d/D%*d/J%*4d%n" }, 94 { "MB/C%*d/P%*d/%nB%*d:%n%n", " B%*d/D%*d%n" }, 95 { "MB/C%*d/P%*d/%nB%*d/D%*d:%n%n", " B%*d/D%*d%n" }, 96 { "/MBU_A/MEMB%*d/%n%nMEM%*d%*1c%n", " MEM%*d%*1c%n" }, 97 { "/MBU_B/MEMB%*d/%n%nMEM%*d%*1c%n", " MEM%*d%*1c%n" }, 98 { "/MBU_A/%n%nMEM%*d%*1c%n", " MEM%*d%*1c%n" }, 99 { "/CMU%*2d/%n%nMEM%*2d%*1c%n", " MEM%*2d%*1c%n" }, 100 { "MB/CMP%*d/BR%*d%n:%n%n", " CH%*d/D%*d/J%*4d%n", "/" }, 101 { "%n%nMB/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n", 102 "MB/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n" }, 103 { "%n%nMB/CMP%*d/BR%*d/CH%*d/D%*d%n", "MB/CMP%*d/BR%*d/CH%*d/D%*d%n" }, 104 { "MB/CPU%*d/CMP%*d/BR%*d%n:%n%n", " CH%*d/D%*d/J%*4d%n", "/"}, 105 { "MB/MEM%*d/CMP%*d/BR%*d%n:%n%n", " CH%*d/D%*d/J%*4d%n", "/"}, 106 { "%n%nMB/MEM%*d/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n", 107 "MB/MEM%*d/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n" }, 108 { "%n%nMB/CPU%*d/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n", 109 "MB/CPU%*d/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n" }, 110 { "%n%nMB/MEM%*d/CMP%*d/BR%*d/CH%*d/D%*d%n", 111 "MB/MEM%*d/CMP%*d/BR%*d/CH%*d/D%*d%n" }, 112 { "%n%nMB/CPU%*d/CMP%*d/BR%*d/CH%*d/D%*d%n", 113 "MB/CPU%*d/CMP%*d/BR%*d/CH%*d/D%*d%n" }, 114 { NULL } 115 }; 116 117 /* 118 * Burst Serengeti-style unums. 119 * A DIMM unum string is expected to be in this form: 120 * "[/N0/]SB12/P0/B0/D2 [J13500]" 121 * A bank unum string is expected to be in this form: 122 * "[/N0/]SB12/P0/B0 [J13500, ...]" 123 */ 124 static int 125 mem_unum_burst_sgsc(const char *pat, char ***dimmsp, size_t *ndimmsp) 126 { 127 char buf[64]; 128 char **dimms; 129 char *base; 130 const char *c; 131 char *copy; 132 size_t copysz; 133 int i; 134 135 /* 136 * No expansion is required for a DIMM unum 137 */ 138 if (strchr(pat, 'D') != NULL) { 139 dimms = fmd_fmri_alloc(sizeof (char *)); 140 dimms[0] = fmd_fmri_strdup(pat); 141 *dimmsp = dimms; 142 *ndimmsp = 1; 143 return (0); 144 } 145 146 /* 147 * strtok is destructive so we need to work with 148 * a copy and keep track of the size allocated. 149 */ 150 copysz = strlen(pat) + 1; 151 copy = fmd_fmri_alloc(copysz); 152 (void) strcpy(copy, pat); 153 154 base = strtok(copy, " "); 155 156 /* There are four DIMMs in a bank */ 157 dimms = fmd_fmri_alloc(sizeof (char *) * 4); 158 159 for (i = 0; i < 4; i++) { 160 (void) snprintf(buf, sizeof (buf), "%s/D%d", base, i); 161 162 if ((c = strtok(NULL, " ")) != NULL) { 163 size_t len = strlen(buf); 164 165 (void) snprintf(buf + len, sizeof (buf) - len, 166 " %s", c); 167 } 168 169 dimms[i] = fmd_fmri_strdup(buf); 170 } 171 172 fmd_fmri_free(copy, copysz); 173 174 *dimmsp = dimms; 175 *ndimmsp = 4; 176 return (0); 177 } 178 179 180 /* 181 * Returns 0 (with dimmsp and ndimmsp set) if the unum could be bursted, -1 182 * otherwise. 183 */ 184 static int 185 mem_unum_burst_pattern(const char *pat, char ***dimmsp, size_t *ndimmsp) 186 { 187 const bank_dimm_t *bd; 188 char **dimms = NULL, **newdimms; 189 size_t ndimms = 0; 190 const char *c; 191 192 193 for (bd = bank_dimm; bd->bd_pat != NULL; bd++) { 194 int replace, start, matched; 195 char dimmname[64]; 196 197 replace = start = matched = -1; 198 (void) sscanf(pat, bd->bd_pat, &replace, &start, &matched); 199 if (matched == -1) 200 continue; 201 (void) strlcpy(dimmname, pat, sizeof (dimmname)); 202 if (bd->bd_subst != NULL) { 203 (void) strlcpy(dimmname+replace, bd->bd_subst, 204 sizeof (dimmname) - strlen(bd->bd_subst)); 205 replace += strlen(bd->bd_subst); 206 } 207 208 c = pat + start; 209 while (*c != '\0') { 210 int dimmlen = -1; 211 212 (void) sscanf(c, bd->bd_reppat, &dimmlen); 213 if (dimmlen == -1) 214 break; 215 216 while (*c == ' ') { 217 c++; 218 dimmlen--; 219 } 220 221 if (dimmlen > sizeof (dimmname) - replace) 222 break; 223 224 (void) strlcpy(dimmname + replace, c, dimmlen + 1); 225 226 newdimms = fmd_fmri_alloc(sizeof (char *) * 227 (ndimms + 1)); 228 if (ndimms != 0) { 229 bcopy(dimms, newdimms, sizeof (char *) * 230 ndimms); 231 fmd_fmri_free(dimms, sizeof (char *) * ndimms); 232 } 233 newdimms[ndimms++] = fmd_fmri_strdup(dimmname); 234 dimms = newdimms; 235 236 c += dimmlen; 237 238 if (*c != ' ' && *c != '\0') 239 break; 240 } 241 242 if (*c != '\0') 243 break; 244 245 *dimmsp = dimms; 246 *ndimmsp = ndimms; 247 248 return (0); 249 } 250 251 mem_strarray_free(dimms, ndimms); 252 253 /* 254 * Set errno to ENOTSUP and return -1. This allows support for DIMMs 255 * with unknown unum strings and/or serial numbers. The only consumer 256 * of mem_unum_burst_pattern() that cares/checks for the returned 257 * errno is fmd_fmri_expand(). 258 */ 259 return (fmd_fmri_set_errno(ENOTSUP)); 260 } 261 262 int 263 mem_unum_burst(const char *pat, char ***dimmsp, size_t *ndimmsp) 264 { 265 const char *platform = fmd_fmri_get_platform(); 266 267 /* 268 * Call mem_unum_burst_sgsc() for Serengeti and 269 * Lightweight 8 platforms. Call mem_unum_burst_pattern() 270 * for all other platforms. 271 */ 272 if (strcmp(platform, "SUNW,Sun-Fire") == 0 || 273 strcmp(platform, "SUNW,Netra-T12") == 0) 274 return (mem_unum_burst_sgsc(pat, dimmsp, ndimmsp)); 275 else 276 return (mem_unum_burst_pattern(pat, dimmsp, ndimmsp)); 277 } 278 279 /* 280 * The unum containership operation is designed to tell the caller whether a 281 * given FMRI contains another. In the case of this plugin, we tell the caller 282 * whether a given memory FMRI (usually a bank) contains another (usually a 283 * DIMM). We do this in one of two ways, depending on the platform. For most 284 * platforms, we can use the bursting routine to generate the list of member 285 * unums from the container unum. Membership can then be determined by 286 * searching the bursted list for the containee's unum. 287 * 288 * Some platforms, however, cannot be bursted, as their bank unums do not 289 * contain all of the information needed to generate the complete list of 290 * member DIMM unums. For these unums, we must make do with a substring 291 * comparison. 292 */ 293 294 static int 295 unum_contains_bypat(const char *erunum, const char *eeunum) 296 { 297 char **ernms, **eenms; 298 size_t nernms, neenms; 299 int i, j, rv = 1; 300 301 if (mem_unum_burst(erunum, &ernms, &nernms) < 0) 302 return (fmd_fmri_set_errno(EINVAL)); 303 if (mem_unum_burst(eeunum, &eenms, &neenms) < 0) { 304 mem_strarray_free(ernms, nernms); 305 return (fmd_fmri_set_errno(EINVAL)); 306 } 307 308 for (i = 0; i < neenms; i++) { 309 for (j = 0; j < nernms; j++) { 310 if (strcmp(eenms[i], ernms[j]) == 0) 311 break; 312 } 313 314 if (j == nernms) { 315 /* 316 * This DIMM was not found in the container. 317 */ 318 rv = 0; 319 break; 320 } 321 } 322 323 mem_strarray_free(ernms, nernms); 324 mem_strarray_free(eenms, neenms); 325 326 return (rv); 327 } 328 329 static int 330 unum_strip_one_jnum(const char *unum, uint_t *endp) 331 { 332 char *c; 333 int i; 334 335 if ((c = strrchr(unum, 'J')) == NULL) 336 return (0); 337 338 while (c > unum && isspace(c[-1])) 339 c--; 340 341 (void) sscanf(c, " J%*[0-9] %n", &i); 342 if (i == 0 || (uintptr_t)(c - unum) + i != strlen(unum)) 343 return (0); 344 345 *endp = (uint_t)(c - unum); 346 return (1); 347 } 348 349 350 static int 351 unum_contains_bysubstr(const char *erunum, const char *eeunum) 352 { 353 uint_t erlen, eelen; 354 int nojnumstrip = 0; 355 356 /* 357 * This comparison method is only known to work on specific types of 358 * unums. Check for those types here. 359 */ 360 if ((strncmp(erunum, "/N", 2) != 0 && strncmp(erunum, "/IO", 3) != 0 && 361 strncmp(erunum, "/SB", 3) != 0) || 362 (strncmp(eeunum, "/N", 2) != 0 && strncmp(eeunum, "/IO", 3) != 0 && 363 strncmp(eeunum, "/SB", 3) != 0)) { 364 if (ISHCUNUM(erunum) && ISHCUNUM(eeunum)) { 365 nojnumstrip = 1; 366 erlen = strlen(erunum); 367 eelen = strlen(eeunum); 368 } else { 369 return (fmd_fmri_set_errno(EINVAL)); 370 } 371 } 372 373 if (!nojnumstrip) { 374 erlen = unum_strip_one_jnum(erunum, &erlen) ? 375 erlen : strlen(erunum); 376 eelen = unum_strip_one_jnum(eeunum, &eelen) ? 377 eelen : strlen(eeunum); 378 } 379 380 return (strncmp(erunum, eeunum, MIN(erlen, eelen)) == 0); 381 } 382 383 typedef int unum_cmptor_f(const char *, const char *); 384 385 static unum_cmptor_f *const unum_cmptors[] = { 386 unum_contains_bypat, 387 unum_contains_bysubstr 388 }; 389 390 int 391 mem_unum_contains(const char *erunum, const char *eeunum) 392 { 393 static int cmptor = 0; 394 int rc; 395 396 while (isspace(*erunum)) 397 erunum++; 398 while (isspace(*eeunum)) 399 eeunum++; 400 401 if ((rc = unum_cmptors[cmptor](erunum, eeunum)) >= 0) 402 return (rc); 403 404 if ((rc = unum_cmptors[cmptor == 0](erunum, eeunum)) >= 0) { 405 /* 406 * We succeeded with the non-default comparator. Change the 407 * default so we use the correct one next time. 408 */ 409 cmptor = (cmptor == 0); 410 } 411 412 return (rc); 413 } 414 415 /* 416 * If an asru has a unum string that is an hc path string then return 417 * a new nvl (to be freed by the caller) that is a duplicate of the 418 * original but with an additional member of a reconstituted hc fmri. 419 */ 420 int 421 mem_unum_rewrite(nvlist_t *nvl, nvlist_t **rnvl) 422 { 423 int err; 424 char *unumstr; 425 nvlist_t *unum; 426 struct topo_hdl *thp; 427 428 if (nvlist_lookup_string(nvl, FM_FMRI_MEM_UNUM, &unumstr) != 0 || 429 !ISHCUNUM(unumstr)) 430 return (0); 431 432 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) 433 return (EINVAL); 434 435 if (topo_fmri_str2nvl(thp, unumstr, &unum, &err) != 0) { 436 fmd_fmri_topo_rele(thp); 437 return (EINVAL); 438 } 439 440 fmd_fmri_topo_rele(thp); 441 442 if ((err = nvlist_dup(nvl, rnvl, 0)) != 0) { 443 nvlist_free(unum); 444 return (err); 445 } 446 447 err = nvlist_add_nvlist(*rnvl, FM_FMRI_MEM_UNUM "-fmri", unum); 448 nvlist_free(unum); 449 450 if (err != 0) 451 nvlist_free(*rnvl); 452 453 return (err); 454 } 455