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 #include <mem.h> 28 #include <fm/fmd_fmri.h> 29 #include <fm/libtopo.h> 30 #include <fm/fmd_agent.h> 31 32 #include <string.h> 33 #include <strings.h> 34 #include <sys/mem.h> 35 36 mem_t mem; 37 38 static int 39 mem_fmri_get_unum(nvlist_t *nvl, char **unump) 40 { 41 uint8_t version; 42 char *unum; 43 44 if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 || 45 version > FM_MEM_SCHEME_VERSION || 46 nvlist_lookup_string(nvl, FM_FMRI_MEM_UNUM, &unum) != 0) 47 return (fmd_fmri_set_errno(EINVAL)); 48 49 *unump = unum; 50 51 return (0); 52 } 53 54 static int 55 page_isretired(nvlist_t *fmri, int *errp) 56 { 57 fmd_agent_hdl_t *hdl; 58 int rc, err; 59 60 if ((hdl = fmd_agent_open(FMD_AGENT_VERSION)) == NULL) 61 return (-1); 62 rc = fmd_agent_page_isretired(hdl, fmri); 63 err = fmd_agent_errno(hdl); 64 fmd_agent_close(hdl); 65 66 if (errp != NULL) 67 *errp = err; 68 return (rc); 69 } 70 71 ssize_t 72 fmd_fmri_nvl2str(nvlist_t *nvl, char *buf, size_t buflen) 73 { 74 char format[64]; 75 ssize_t size, presz; 76 char *rawunum, *preunum, *escunum, *prefix; 77 uint64_t val; 78 int i; 79 80 if (mem_fmri_get_unum(nvl, &rawunum) < 0) 81 return (-1); /* errno is set for us */ 82 83 /* 84 * If we have a well-formed unum (hc-FMRI), use the string verbatim 85 * to form the initial mem:/// components. Otherwise use unum=%s. 86 */ 87 if (strncmp(rawunum, "hc://", 5) != 0) 88 prefix = FM_FMRI_MEM_UNUM "="; 89 else 90 prefix = ""; 91 92 /* 93 * If we have a DIMM offset, include it in the string. If we have a PA 94 * then use that. Otherwise just format the unum element. 95 */ 96 if (nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val) == 0) { 97 (void) snprintf(format, sizeof (format), 98 "%s:///%s%%1$s/%s=%%2$llx", 99 FM_FMRI_SCHEME_MEM, prefix, FM_FMRI_MEM_OFFSET); 100 } else if (nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val) == 0) { 101 (void) snprintf(format, sizeof (format), 102 "%s:///%s%%1$s/%s=%%2$llx", 103 FM_FMRI_SCHEME_MEM, prefix, FM_FMRI_MEM_PHYSADDR); 104 } else { 105 (void) snprintf(format, sizeof (format), 106 "%s:///%s%%1$s", FM_FMRI_SCHEME_MEM, prefix); 107 } 108 109 /* 110 * If we have a well-formed unum (hc-FMRI), we skip over the 111 * the scheme and authority prefix. 112 * Otherwise, the spaces and colons will be escaped, 113 * rendering the resulting FMRI pretty much unreadable. 114 * We're therefore going to do some escaping of our own first. 115 */ 116 if (strncmp(rawunum, "hc://", 5) == 0) { 117 rawunum += 5; 118 rawunum = strchr(rawunum, '/'); 119 ++rawunum; 120 /* LINTED: variable format specifier */ 121 size = snprintf(buf, buflen, format, rawunum, val); 122 } else { 123 preunum = fmd_fmri_strdup(rawunum); 124 presz = strlen(preunum) + 1; 125 126 for (i = 0; i < presz - 1; i++) { 127 if (preunum[i] == ':' && preunum[i + 1] == ' ') { 128 bcopy(preunum + i + 2, preunum + i + 1, 129 presz - (i + 2)); 130 } else if (preunum[i] == ' ') { 131 preunum[i] = ','; 132 } 133 } 134 135 escunum = fmd_fmri_strescape(preunum); 136 fmd_fmri_free(preunum, presz); 137 138 /* LINTED: variable format specifier */ 139 size = snprintf(buf, buflen, format, escunum, val); 140 fmd_fmri_strfree(escunum); 141 } 142 143 return (size); 144 } 145 146 int 147 fmd_fmri_expand(nvlist_t *nvl) 148 { 149 char *unum, **serids; 150 uint_t nnvlserids; 151 size_t nserids; 152 int rc, err = 0; 153 topo_hdl_t *thp; 154 155 if ((mem_fmri_get_unum(nvl, &unum) < 0) || (*unum == '\0')) 156 return (fmd_fmri_set_errno(EINVAL)); 157 158 /* 159 * If the mem-scheme topology exports this method expand(), invoke it. 160 */ 161 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) 162 return (fmd_fmri_set_errno(EINVAL)); 163 rc = topo_fmri_expand(thp, nvl, &err); 164 fmd_fmri_topo_rele(thp); 165 if (err != ETOPO_METHOD_NOTSUP) 166 return (rc); 167 168 if ((rc = nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, 169 &serids, &nnvlserids)) == 0) { /* already have serial #s */ 170 return (0); 171 } else if (rc != ENOENT) 172 return (fmd_fmri_set_errno(EINVAL)); 173 174 if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) { 175 /* errno is set for us */ 176 if (errno == ENOTSUP) 177 return (0); /* nothing to add - no s/n support */ 178 else 179 return (-1); 180 } 181 182 rc = nvlist_add_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, serids, 183 nserids); 184 185 mem_strarray_free(serids, nserids); 186 187 if (rc != 0) 188 return (fmd_fmri_set_errno(EINVAL)); 189 else 190 return (0); 191 } 192 193 #ifdef sparc 194 static int 195 serids_eq(char **serids1, uint_t nserids1, char **serids2, uint_t nserids2) 196 { 197 int i; 198 199 if (nserids1 != nserids2) 200 return (0); 201 202 for (i = 0; i < nserids1; i++) { 203 if (strcmp(serids1[i], serids2[i]) != 0) 204 return (0); 205 } 206 207 return (1); 208 } 209 #endif /* sparc */ 210 211 int 212 fmd_fmri_present(nvlist_t *nvl) 213 { 214 char *unum = NULL; 215 int rc, err = 0; 216 struct topo_hdl *thp; 217 #ifdef sparc 218 char **nvlserids, **serids; 219 uint_t nnvlserids; 220 size_t nserids; 221 #else 222 nvlist_t *unum_nvl; 223 nvlist_t *nvlcp = NULL; 224 uint64_t val; 225 #endif /* sparc */ 226 227 if (mem_fmri_get_unum(nvl, &unum) < 0) 228 return (-1); /* errno is set for us */ 229 230 #ifdef sparc 231 /* 232 * If the mem-scheme topology exports this method present(), invoke it. 233 */ 234 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) 235 return (fmd_fmri_set_errno(EINVAL)); 236 rc = topo_fmri_present(thp, nvl, &err); 237 fmd_fmri_topo_rele(thp); 238 if (err != ETOPO_METHOD_NOTSUP) 239 return (rc); 240 241 if (nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, &nvlserids, 242 &nnvlserids) != 0) { 243 /* 244 * Some mem scheme FMRIs don't have serial ids because 245 * either the platform does not support them, or because 246 * the FMRI was created before support for serial ids was 247 * introduced. If this is the case, assume it is there. 248 */ 249 if (mem.mem_dm == NULL) 250 return (1); 251 else 252 return (fmd_fmri_set_errno(EINVAL)); 253 } 254 255 if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) { 256 if (errno == ENOTSUP) 257 return (1); /* assume it's there, no s/n support here */ 258 if (errno != ENOENT) { 259 /* 260 * Errors are only signalled to the caller if they're 261 * the caller's fault. This isn't - it's a failure on 262 * our part to burst or read the serial numbers. We'll 263 * whine about it, and tell the caller the named 264 * module(s) isn't/aren't there. 265 */ 266 fmd_fmri_warn("failed to retrieve serial number for " 267 "unum %s", unum); 268 } 269 return (0); 270 } 271 272 rc = serids_eq(serids, nserids, nvlserids, nnvlserids); 273 274 mem_strarray_free(serids, nserids); 275 #else 276 /* 277 * On X86 we will invoke the topo is_present method passing in the 278 * unum, which is in hc scheme. The libtopo hc-scheme is_present method 279 * will invoke the node-specific is_present method, which is implemented 280 * by the chip enumerator for rank nodes. The rank node's is_present 281 * method will compare the serial number in the unum with the current 282 * serial to determine if the same DIMM is present. 283 */ 284 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) { 285 fmd_fmri_warn("failed to get handle to topology"); 286 return (-1); 287 } 288 if (topo_fmri_str2nvl(thp, unum, &unum_nvl, &err) == 0) { 289 rc = topo_fmri_present(thp, unum_nvl, &err); 290 nvlist_free(unum_nvl); 291 } else 292 rc = fmd_fmri_set_errno(EINVAL); 293 fmd_fmri_topo_rele(thp); 294 295 /* 296 * Need to check if this is a valid page too. if "isretired" returns 297 * EINVAL, assume page invalid and return not_present. 298 */ 299 if (rc == 1 && nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val) == 300 0 && nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val) == 0 && 301 mem_unum_rewrite(nvl, &nvlcp) == 0 && nvlcp != NULL) { 302 int page_err, rval = page_isretired(nvlcp, &page_err); 303 if (rval == FMD_AGENT_RETIRE_DONE && page_err == EINVAL) 304 rc = 0; 305 nvlist_free(nvlcp); 306 } 307 #endif /* sparc */ 308 return (rc); 309 } 310 311 int 312 fmd_fmri_replaced(nvlist_t *nvl) 313 { 314 char *unum = NULL; 315 int rc, err = 0; 316 struct topo_hdl *thp; 317 #ifdef sparc 318 char **nvlserids, **serids; 319 uint_t nnvlserids; 320 size_t nserids; 321 #else 322 nvlist_t *unum_nvl; 323 nvlist_t *nvlcp = NULL; 324 uint64_t val; 325 #endif /* sparc */ 326 327 if (mem_fmri_get_unum(nvl, &unum) < 0) 328 return (-1); /* errno is set for us */ 329 330 #ifdef sparc 331 /* 332 * If the mem-scheme topology exports this method replaced(), invoke it. 333 */ 334 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) 335 return (fmd_fmri_set_errno(EINVAL)); 336 rc = topo_fmri_replaced(thp, nvl, &err); 337 fmd_fmri_topo_rele(thp); 338 if (err != ETOPO_METHOD_NOTSUP) 339 return (rc); 340 341 if (nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, &nvlserids, 342 &nnvlserids) != 0) { 343 /* 344 * Some mem scheme FMRIs don't have serial ids because 345 * either the platform does not support them, or because 346 * the FMRI was created before support for serial ids was 347 * introduced. If this is the case, assume it is there. 348 */ 349 if (mem.mem_dm == NULL) 350 return (FMD_OBJ_STATE_UNKNOWN); 351 else 352 return (fmd_fmri_set_errno(EINVAL)); 353 } 354 355 if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) { 356 if (errno == ENOTSUP) 357 return (FMD_OBJ_STATE_UNKNOWN); 358 if (errno != ENOENT) { 359 /* 360 * Errors are only signalled to the caller if they're 361 * the caller's fault. This isn't - it's a failure on 362 * our part to burst or read the serial numbers. We'll 363 * whine about it, and tell the caller the named 364 * module(s) isn't/aren't there. 365 */ 366 fmd_fmri_warn("failed to retrieve serial number for " 367 "unum %s", unum); 368 } 369 return (FMD_OBJ_STATE_NOT_PRESENT); 370 } 371 372 rc = serids_eq(serids, nserids, nvlserids, nnvlserids) ? 373 FMD_OBJ_STATE_STILL_PRESENT : FMD_OBJ_STATE_REPLACED; 374 375 mem_strarray_free(serids, nserids); 376 #else 377 /* 378 * On X86 we will invoke the topo is_replaced method passing in the 379 * unum, which is in hc scheme. The libtopo hc-scheme is_replaced 380 * method will invoke the node-specific is_replaced method, which is 381 * implemented by the chip enumerator for rank nodes. The rank node's 382 * is_replaced method will compare the serial number in the unum with 383 * the current serial to determine if the same DIMM is replaced. 384 */ 385 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) { 386 fmd_fmri_warn("failed to get handle to topology"); 387 return (-1); 388 } 389 if (topo_fmri_str2nvl(thp, unum, &unum_nvl, &err) == 0) { 390 rc = topo_fmri_replaced(thp, unum_nvl, &err); 391 nvlist_free(unum_nvl); 392 } else 393 rc = fmd_fmri_set_errno(EINVAL); 394 fmd_fmri_topo_rele(thp); 395 396 /* 397 * Need to check if this is a valid page too. if "isretired" returns 398 * EINVAL, assume page invalid and return not_present. 399 */ 400 if ((rc == FMD_OBJ_STATE_STILL_PRESENT || 401 rc == FMD_OBJ_STATE_UNKNOWN) && 402 nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val) == 0 && 403 nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val) == 0 && 404 mem_unum_rewrite(nvl, &nvlcp) == 0 && nvlcp != NULL) { 405 int page_err, rval = page_isretired(nvlcp, &page_err); 406 if (rval == FMD_AGENT_RETIRE_DONE && page_err == EINVAL) 407 rc = FMD_OBJ_STATE_NOT_PRESENT; 408 nvlist_free(nvlcp); 409 } 410 #endif /* sparc */ 411 return (rc); 412 } 413 414 int 415 fmd_fmri_contains(nvlist_t *er, nvlist_t *ee) 416 { 417 int rc, err = 0; 418 struct topo_hdl *thp; 419 char *erunum, *eeunum; 420 uint64_t erval = 0, eeval = 0; 421 422 /* 423 * If the mem-scheme topology exports this method contains(), invoke it. 424 */ 425 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) 426 return (fmd_fmri_set_errno(EINVAL)); 427 rc = topo_fmri_contains(thp, er, ee, &err); 428 fmd_fmri_topo_rele(thp); 429 if (err != ETOPO_METHOD_NOTSUP) 430 return (rc); 431 432 if (mem_fmri_get_unum(er, &erunum) < 0 || 433 mem_fmri_get_unum(ee, &eeunum) < 0) 434 return (-1); /* errno is set for us */ 435 436 if (mem_unum_contains(erunum, eeunum) <= 0) 437 return (0); /* can't parse/match, so assume no containment */ 438 439 if (nvlist_lookup_uint64(er, FM_FMRI_MEM_OFFSET, &erval) == 0) { 440 return (nvlist_lookup_uint64(ee, 441 FM_FMRI_MEM_OFFSET, &eeval) == 0 && erval == eeval); 442 } 443 444 if (nvlist_lookup_uint64(er, FM_FMRI_MEM_PHYSADDR, &erval) == 0) { 445 return (nvlist_lookup_uint64(ee, 446 FM_FMRI_MEM_PHYSADDR, &eeval) == 0 && erval == eeval); 447 } 448 449 return (1); 450 } 451 452 /* 453 * We can only make a usable/unusable determination for pages. Mem FMRIs 454 * without page addresses will be reported as usable since Solaris has no 455 * way at present to dynamically disable an entire DIMM or DIMM pair. 456 */ 457 int 458 fmd_fmri_unusable(nvlist_t *nvl) 459 { 460 uint64_t val1, val2; 461 uint8_t version; 462 int rc, err1 = 0, err2; 463 nvlist_t *nvlcp = NULL; 464 int retval; 465 topo_hdl_t *thp; 466 467 if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 || 468 version > FM_MEM_SCHEME_VERSION) 469 return (fmd_fmri_set_errno(EINVAL)); 470 471 /* 472 * If the mem-scheme topology exports this method unusable(), invoke it. 473 */ 474 if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) 475 return (fmd_fmri_set_errno(EINVAL)); 476 rc = topo_fmri_unusable(thp, nvl, &err1); 477 fmd_fmri_topo_rele(thp); 478 if (err1 != ETOPO_METHOD_NOTSUP) 479 return (rc); 480 481 err1 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val1); 482 err2 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val2); 483 484 if (err1 == ENOENT && err2 == ENOENT) 485 return (0); /* no page, so assume it's still usable */ 486 487 if ((err1 != 0 && err1 != ENOENT) || (err2 != 0 && err2 != ENOENT)) 488 return (fmd_fmri_set_errno(EINVAL)); 489 490 if ((rc = mem_unum_rewrite(nvl, &nvlcp)) != 0) 491 return (fmd_fmri_set_errno(rc)); 492 493 /* 494 * Ask the kernel if the page is retired, using either the rewritten 495 * hc FMRI or the original mem FMRI with the specified offset or PA. 496 * Refer to the kernel's page_retire_check() for the error codes. 497 */ 498 rc = page_isretired(nvlcp ? nvlcp : nvl, NULL); 499 500 if (rc == FMD_AGENT_RETIRE_FAIL) { 501 /* 502 * The page is not retired and is not scheduled for retirement 503 * (i.e. no request pending and has not seen any errors) 504 */ 505 retval = 0; 506 } else if (rc == FMD_AGENT_RETIRE_DONE || 507 rc == FMD_AGENT_RETIRE_ASYNC) { 508 /* 509 * The page has been retired, is in the process of being 510 * retired, or doesn't exist. The latter is valid if the page 511 * existed in the past but has been DR'd out. 512 */ 513 retval = 1; 514 } else { 515 /* 516 * Errors are only signalled to the caller if they're the 517 * caller's fault. This isn't - it's a failure of the 518 * retirement-check code. We'll whine about it and tell 519 * the caller the page is unusable. 520 */ 521 fmd_fmri_warn("failed to determine page %s=%llx usability: " 522 "rc=%d errno=%d\n", err1 == 0 ? FM_FMRI_MEM_OFFSET : 523 FM_FMRI_MEM_PHYSADDR, err1 == 0 ? (u_longlong_t)val1 : 524 (u_longlong_t)val2, rc, errno); 525 retval = 1; 526 } 527 528 if (nvlcp) 529 nvlist_free(nvlcp); 530 531 return (retval); 532 } 533 534 int 535 fmd_fmri_init(void) 536 { 537 return (mem_discover()); 538 } 539 540 void 541 fmd_fmri_fini(void) 542 { 543 mem_dimm_map_t *dm, *em; 544 mem_bank_map_t *bm, *cm; 545 mem_grp_t *gm, *hm; 546 mem_seg_map_t *sm, *tm; 547 548 for (dm = mem.mem_dm; dm != NULL; dm = em) { 549 em = dm->dm_next; 550 fmd_fmri_strfree(dm->dm_label); 551 fmd_fmri_strfree(dm->dm_part); 552 fmd_fmri_strfree(dm->dm_device); 553 fmd_fmri_free(dm, sizeof (mem_dimm_map_t)); 554 } 555 for (bm = mem.mem_bank; bm != NULL; bm = cm) { 556 cm = bm->bm_next; 557 fmd_fmri_free(bm, sizeof (mem_bank_map_t)); 558 } 559 for (gm = mem.mem_group; gm != NULL; gm = hm) { 560 hm = gm->mg_next; 561 fmd_fmri_free(gm, sizeof (mem_grp_t)); 562 } 563 for (sm = mem.mem_seg; sm != NULL; sm = tm) { 564 tm = sm->sm_next; 565 fmd_fmri_free(sm, sizeof (mem_seg_map_t)); 566 } 567 } 568