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 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <mem.h> 30 #include <fm/fmd_fmri.h> 31 32 #include <fcntl.h> 33 #include <unistd.h> 34 #include <string.h> 35 #include <strings.h> 36 #include <time.h> 37 #include <sys/mem.h> 38 39 #ifdef sparc 40 #include <sys/fm/ldom.h> 41 ldom_hdl_t *mem_scheme_lhp; 42 #endif /* sparc */ 43 44 mem_t mem; 45 46 #ifdef sparc 47 48 extern int mem_update_mdesc(void); 49 50 /* 51 * Retry values for handling the case where the kernel is not yet ready 52 * to provide DIMM serial ids. Some platforms acquire DIMM serial id 53 * information from their System Controller via a mailbox interface. 54 * The values chosen are for 10 retries 3 seconds apart to approximate the 55 * possible 30 second timeout length of a mailbox message request. 56 */ 57 #define MAX_MEM_SID_RETRIES 10 58 #define MEM_SID_RETRY_WAIT 3 59 60 static mem_dimm_map_t * 61 dm_lookup(const char *name) 62 { 63 mem_dimm_map_t *dm; 64 65 for (dm = mem.mem_dm; dm != NULL; dm = dm->dm_next) { 66 if (strcmp(name, dm->dm_label) == 0) 67 return (dm); 68 } 69 70 return (NULL); 71 } 72 73 /* 74 * Returns 0 with serial numbers if found, -1 (with errno set) for errors. If 75 * the unum (or a component of same) wasn't found, -1 is returned with errno 76 * set to ENOENT. If the kernel doesn't have support for serial numbers, 77 * -1 is returned with errno set to ENOTSUP. 78 */ 79 static int 80 mem_get_serids_from_kernel(const char *unum, char ***seridsp, size_t *nseridsp) 81 { 82 char **dimms, **serids; 83 size_t ndimms, nserids; 84 int i, rc = 0; 85 int fd; 86 int retries = MAX_MEM_SID_RETRIES; 87 mem_name_t mn; 88 struct timespec rqt; 89 90 if ((fd = open("/dev/mem", O_RDONLY)) < 0) 91 return (-1); 92 93 if (mem_unum_burst(unum, &dimms, &ndimms) < 0) { 94 (void) close(fd); 95 return (-1); /* errno is set for us */ 96 } 97 98 serids = fmd_fmri_zalloc(sizeof (char *) * ndimms); 99 nserids = ndimms; 100 101 bzero(&mn, sizeof (mn)); 102 103 for (i = 0; i < ndimms; i++) { 104 mn.m_namelen = strlen(dimms[i]) + 1; 105 mn.m_sidlen = MEM_SERID_MAXLEN; 106 107 mn.m_name = fmd_fmri_alloc(mn.m_namelen); 108 mn.m_sid = fmd_fmri_alloc(mn.m_sidlen); 109 110 (void) strcpy(mn.m_name, dimms[i]); 111 112 do { 113 rc = ioctl(fd, MEM_SID, &mn); 114 115 if (rc >= 0 || errno != EAGAIN) 116 break; 117 118 if (retries == 0) { 119 errno = ETIMEDOUT; 120 break; 121 } 122 123 /* 124 * EAGAIN indicates the kernel is 125 * not ready to provide DIMM serial 126 * ids. Sleep MEM_SID_RETRY_WAIT seconds 127 * and try again. 128 * nanosleep() is used instead of sleep() 129 * to avoid interfering with fmd timers. 130 */ 131 rqt.tv_sec = MEM_SID_RETRY_WAIT; 132 rqt.tv_nsec = 0; 133 (void) nanosleep(&rqt, NULL); 134 135 } while (retries--); 136 137 if (rc < 0) { 138 /* 139 * ENXIO can happen if the kernel memory driver 140 * doesn't have the MEM_SID ioctl (e.g. if the 141 * kernel hasn't been patched to provide the 142 * support). 143 * 144 * If the MEM_SID ioctl is available but the 145 * particular platform doesn't support providing 146 * serial ids, ENOTSUP will be returned by the ioctl. 147 */ 148 if (errno == ENXIO) 149 errno = ENOTSUP; 150 fmd_fmri_free(mn.m_name, mn.m_namelen); 151 fmd_fmri_free(mn.m_sid, mn.m_sidlen); 152 mem_strarray_free(serids, nserids); 153 mem_strarray_free(dimms, ndimms); 154 (void) close(fd); 155 return (-1); 156 } 157 158 serids[i] = fmd_fmri_strdup(mn.m_sid); 159 160 fmd_fmri_free(mn.m_name, mn.m_namelen); 161 fmd_fmri_free(mn.m_sid, mn.m_sidlen); 162 } 163 164 mem_strarray_free(dimms, ndimms); 165 166 (void) close(fd); 167 168 *seridsp = serids; 169 *nseridsp = nserids; 170 171 return (0); 172 } 173 174 /* 175 * Returns 0 with serial numbers if found, -1 (with errno set) for errors. If 176 * the unum (or a component of same) wasn't found, -1 is returned with errno 177 * set to ENOENT. 178 */ 179 static int 180 mem_get_serids_from_cache(const char *unum, char ***seridsp, size_t *nseridsp) 181 { 182 uint64_t drgen = fmd_fmri_get_drgen(); 183 char **dimms, **serids; 184 size_t ndimms, nserids; 185 mem_dimm_map_t *dm; 186 int i, rc = 0; 187 188 if (mem_unum_burst(unum, &dimms, &ndimms) < 0) 189 return (-1); /* errno is set for us */ 190 191 serids = fmd_fmri_zalloc(sizeof (char *) * ndimms); 192 nserids = ndimms; 193 194 for (i = 0; i < ndimms; i++) { 195 if ((dm = dm_lookup(dimms[i])) == NULL) { 196 rc = fmd_fmri_set_errno(EINVAL); 197 break; 198 } 199 200 if (*dm->dm_serid == '\0' || dm->dm_drgen != drgen) { 201 /* 202 * We don't have a cached copy, or the copy we've got is 203 * out of date. Look it up again. 204 */ 205 if (mem_get_serid(dm->dm_device, dm->dm_serid, 206 sizeof (dm->dm_serid)) < 0) { 207 rc = -1; /* errno is set for us */ 208 break; 209 } 210 211 dm->dm_drgen = drgen; 212 } 213 214 serids[i] = fmd_fmri_strdup(dm->dm_serid); 215 } 216 217 mem_strarray_free(dimms, ndimms); 218 219 if (rc == 0) { 220 *seridsp = serids; 221 *nseridsp = nserids; 222 } else { 223 mem_strarray_free(serids, nserids); 224 } 225 226 return (rc); 227 } 228 229 /* 230 * Returns 0 with serial numbers if found, -1 (with errno set) for errors. If 231 * the unum (or a component of same) wasn't found, -1 is returned with errno 232 * set to ENOENT. 233 */ 234 static int 235 mem_get_serids_from_mdesc(const char *unum, char ***seridsp, size_t *nseridsp) 236 { 237 uint64_t drgen = fmd_fmri_get_drgen(); 238 char **dimms, **serids; 239 size_t ndimms, nserids; 240 mem_dimm_map_t *dm; 241 int i, rc = 0; 242 243 if (mem_unum_burst(unum, &dimms, &ndimms) < 0) 244 return (-1); /* errno is set for us */ 245 246 serids = fmd_fmri_zalloc(sizeof (char *) * ndimms); 247 nserids = ndimms; 248 249 /* 250 * first go through dimms and see if dm_drgen entries are outdated 251 */ 252 for (i = 0; i < ndimms; i++) { 253 if ((dm = dm_lookup(dimms[i])) == NULL || 254 dm->dm_drgen != drgen) 255 break; 256 } 257 258 if (i < ndimms && mem_update_mdesc() != 0) { 259 mem_strarray_free(dimms, ndimms); 260 return (-1); 261 } 262 263 /* 264 * get to this point if an up-to-date mdesc (and corresponding 265 * entries in the global mem list) exists 266 */ 267 for (i = 0; i < ndimms; i++) { 268 if ((dm = dm_lookup(dimms[i])) == NULL) { 269 rc = fmd_fmri_set_errno(EINVAL); 270 break; 271 } 272 273 if (dm->dm_drgen != drgen) 274 dm->dm_drgen = drgen; 275 276 /* 277 * mdesc and dm entry was updated by an earlier call to 278 * mem_update_mdesc, so we go ahead and dup the serid 279 */ 280 serids[i] = fmd_fmri_strdup(dm->dm_serid); 281 } 282 283 mem_strarray_free(dimms, ndimms); 284 285 if (rc == 0) { 286 *seridsp = serids; 287 *nseridsp = nserids; 288 } else { 289 mem_strarray_free(serids, nserids); 290 } 291 292 return (rc); 293 } 294 295 /* 296 * Returns 0 with part numbers if found, returns -1 for errors. 297 */ 298 static int 299 mem_get_parts_from_mdesc(const char *unum, char ***partsp, size_t *npartsp) 300 { 301 uint64_t drgen = fmd_fmri_get_drgen(); 302 char **dimms, **parts; 303 size_t ndimms, nparts; 304 mem_dimm_map_t *dm; 305 int i, rc = 0; 306 307 if (mem_unum_burst(unum, &dimms, &ndimms) < 0) 308 return (-1); /* errno is set for us */ 309 310 parts = fmd_fmri_zalloc(sizeof (char *) * ndimms); 311 nparts = ndimms; 312 313 /* 314 * first go through dimms and see if dm_drgen entries are outdated 315 */ 316 for (i = 0; i < ndimms; i++) { 317 if ((dm = dm_lookup(dimms[i])) == NULL || 318 dm->dm_drgen != drgen) 319 break; 320 } 321 322 if (i < ndimms && mem_update_mdesc() != 0) { 323 mem_strarray_free(dimms, ndimms); 324 mem_strarray_free(parts, nparts); 325 return (-1); 326 } 327 328 /* 329 * get to this point if an up-to-date mdesc (and corresponding 330 * entries in the global mem list) exists 331 */ 332 for (i = 0; i < ndimms; i++) { 333 if ((dm = dm_lookup(dimms[i])) == NULL) { 334 rc = fmd_fmri_set_errno(EINVAL); 335 break; 336 } 337 338 if (dm->dm_drgen != drgen) 339 dm->dm_drgen = drgen; 340 341 /* 342 * mdesc and dm entry was updated by an earlier call to 343 * mem_update_mdesc, so we go ahead and dup the part 344 */ 345 if (dm->dm_part == NULL) { 346 rc = -1; 347 break; 348 } 349 parts[i] = fmd_fmri_strdup(dm->dm_part); 350 } 351 352 mem_strarray_free(dimms, ndimms); 353 354 if (rc == 0) { 355 *partsp = parts; 356 *npartsp = nparts; 357 } else { 358 mem_strarray_free(parts, nparts); 359 } 360 361 return (rc); 362 } 363 364 static int 365 mem_get_parts_by_unum(const char *unum, char ***partp, size_t *npartp) 366 { 367 if (mem.mem_dm == NULL) 368 return (-1); 369 else 370 return (mem_get_parts_from_mdesc(unum, partp, npartp)); 371 } 372 373 #endif /* sparc */ 374 375 /*ARGSUSED*/ 376 377 static int 378 mem_get_serids_by_unum(const char *unum, char ***seridsp, size_t *nseridsp) 379 { 380 /* 381 * Some platforms do not support the caching of serial ids by the 382 * mem scheme plugin but instead support making serial ids available 383 * via the kernel. 384 */ 385 #ifdef sparc 386 if (mem.mem_dm == NULL) 387 return (mem_get_serids_from_kernel(unum, seridsp, nseridsp)); 388 else if (mem_get_serids_from_mdesc(unum, seridsp, nseridsp) == 0) 389 return (0); 390 else 391 return (mem_get_serids_from_cache(unum, seridsp, nseridsp)); 392 #else 393 errno = ENOTSUP; 394 return (-1); 395 #endif /* sparc */ 396 } 397 398 static int 399 mem_fmri_get_unum(nvlist_t *nvl, char **unump) 400 { 401 uint8_t version; 402 char *unum; 403 404 if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 || 405 version > FM_MEM_SCHEME_VERSION || 406 nvlist_lookup_string(nvl, FM_FMRI_MEM_UNUM, &unum) != 0) 407 return (fmd_fmri_set_errno(EINVAL)); 408 409 *unump = unum; 410 411 return (0); 412 } 413 414 ssize_t 415 fmd_fmri_nvl2str(nvlist_t *nvl, char *buf, size_t buflen) 416 { 417 char format[64]; 418 ssize_t size, presz; 419 char *rawunum, *preunum, *escunum, *prefix; 420 uint64_t val; 421 int i; 422 423 if (mem_fmri_get_unum(nvl, &rawunum) < 0) 424 return (-1); /* errno is set for us */ 425 426 /* 427 * If we have a well-formed unum (hc-FMRI), use the string verbatim 428 * to form the initial mem:/// components. Otherwise use unum=%s. 429 */ 430 if (strncmp(rawunum, "hc://", 5) != 0) 431 prefix = FM_FMRI_MEM_UNUM "="; 432 else 433 prefix = ""; 434 435 /* 436 * If we have a DIMM offset, include it in the string. If we have a PA 437 * then use that. Otherwise just format the unum element. 438 */ 439 if (nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val) == 0) { 440 (void) snprintf(format, sizeof (format), 441 "%s:///%s%%1$s/%s=%%2$llx", 442 FM_FMRI_SCHEME_MEM, prefix, FM_FMRI_MEM_OFFSET); 443 } else if (nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val) == 0) { 444 (void) snprintf(format, sizeof (format), 445 "%s:///%s%%1$s/%s=%%2$llx", 446 FM_FMRI_SCHEME_MEM, prefix, FM_FMRI_MEM_PHYSADDR); 447 } else { 448 (void) snprintf(format, sizeof (format), 449 "%s:///%s%%1$s", FM_FMRI_SCHEME_MEM, prefix); 450 } 451 452 /* 453 * If we have a well-formed unum (hc-FMRI), we skip over the 454 * the scheme and authority prefix. 455 * Otherwise, the spaces and colons will be escaped, 456 * rendering the resulting FMRI pretty much unreadable. 457 * We're therefore going to do some escaping of our own first. 458 */ 459 if (strncmp(rawunum, "hc://", 5) == 0) { 460 rawunum += 5; 461 rawunum = strchr(rawunum, '/'); 462 ++rawunum; 463 /* LINTED: variable format specifier */ 464 size = snprintf(buf, buflen, format, rawunum, val); 465 } else { 466 preunum = fmd_fmri_strdup(rawunum); 467 presz = strlen(preunum) + 1; 468 469 for (i = 0; i < presz - 1; i++) { 470 if (preunum[i] == ':' && preunum[i + 1] == ' ') { 471 bcopy(preunum + i + 2, preunum + i + 1, 472 presz - (i + 2)); 473 } else if (preunum[i] == ' ') { 474 preunum[i] = ','; 475 } 476 } 477 478 escunum = fmd_fmri_strescape(preunum); 479 fmd_fmri_free(preunum, presz); 480 481 /* LINTED: variable format specifier */ 482 size = snprintf(buf, buflen, format, escunum, val); 483 fmd_fmri_strfree(escunum); 484 } 485 486 return (size); 487 } 488 489 int 490 fmd_fmri_expand(nvlist_t *nvl) 491 { 492 char *unum, **serids; 493 uint_t nnvlserids; 494 size_t nserids; 495 #ifdef sparc 496 char **parts; 497 size_t nparts; 498 #endif 499 int rc; 500 501 if (mem_fmri_get_unum(nvl, &unum) < 0) 502 return (fmd_fmri_set_errno(EINVAL)); 503 504 if ((rc = nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, 505 &serids, &nnvlserids)) == 0) 506 return (0); /* fmri is already expanded */ 507 else if (rc != ENOENT) 508 return (fmd_fmri_set_errno(EINVAL)); 509 510 if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) { 511 /* errno is set for us */ 512 if (errno == ENOTSUP) 513 return (0); /* nothing to add - no s/n support */ 514 else 515 return (-1); 516 } 517 518 rc = nvlist_add_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, serids, 519 nserids); 520 521 mem_strarray_free(serids, nserids); 522 523 if (rc != 0) 524 return (fmd_fmri_set_errno(EINVAL)); 525 526 #ifdef sparc 527 /* 528 * Continue with the process if there are no part numbers. 529 */ 530 if (mem_get_parts_by_unum(unum, &parts, &nparts) < 0) 531 return (0); 532 533 rc = nvlist_add_string_array(nvl, FM_FMRI_HC_PART, parts, nparts); 534 535 mem_strarray_free(parts, nparts); 536 #endif 537 return (0); 538 } 539 540 static int 541 serids_eq(char **serids1, uint_t nserids1, char **serids2, uint_t nserids2) 542 { 543 int i; 544 545 if (nserids1 != nserids2) 546 return (0); 547 548 for (i = 0; i < nserids1; i++) { 549 if (strcmp(serids1[i], serids2[i]) != 0) 550 return (0); 551 } 552 553 return (1); 554 } 555 556 int 557 fmd_fmri_present(nvlist_t *nvl) 558 { 559 char *unum, **nvlserids, **serids; 560 uint_t nnvlserids; 561 size_t nserids; 562 uint64_t memconfig; 563 int rc; 564 565 if (mem_fmri_get_unum(nvl, &unum) < 0) 566 return (-1); /* errno is set for us */ 567 568 if (nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, &nvlserids, 569 &nnvlserids) != 0) { 570 /* 571 * Some mem scheme FMRIs don't have serial ids because 572 * either the platform does not support them, or because 573 * the FMRI was created before support for serial ids was 574 * introduced. If this is the case, assume it is there. 575 */ 576 if (mem.mem_dm == NULL) 577 return (1); 578 else 579 return (fmd_fmri_set_errno(EINVAL)); 580 } 581 582 /* 583 * Hypervisor will change the memconfig value when the mapping of 584 * pages to DIMMs changes, e.g. for change in DIMM size or interleave. 585 * If we detect such a change, we discard ereports associated with a 586 * previous memconfig value as invalid. 587 * 588 * The test (mem.mem_memconfig != 0) means we run on a system that 589 * actually suplies a memconfig value. 590 */ 591 592 if ((nvlist_lookup_uint64(nvl, FM_FMRI_MEM_MEMCONFIG, 593 &memconfig) == 0) && (mem.mem_memconfig != 0) && 594 (memconfig != mem.mem_memconfig)) 595 return (0); 596 597 if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) { 598 if (errno == ENOTSUP) 599 return (1); /* assume it's there, no s/n support here */ 600 if (errno != ENOENT) { 601 /* 602 * Errors are only signalled to the caller if they're 603 * the caller's fault. This isn't - it's a failure on 604 * our part to burst or read the serial numbers. We'll 605 * whine about it, and tell the caller the named 606 * module(s) isn't/aren't there. 607 */ 608 fmd_fmri_warn("failed to retrieve serial number for " 609 "unum %s", unum); 610 } 611 return (0); 612 } 613 614 rc = serids_eq(serids, nserids, nvlserids, nnvlserids); 615 616 mem_strarray_free(serids, nserids); 617 618 return (rc); 619 } 620 621 int 622 fmd_fmri_contains(nvlist_t *er, nvlist_t *ee) 623 { 624 char *erunum, *eeunum; 625 uint64_t erval = 0, eeval = 0; 626 627 if (mem_fmri_get_unum(er, &erunum) < 0 || 628 mem_fmri_get_unum(ee, &eeunum) < 0) 629 return (-1); /* errno is set for us */ 630 631 if (mem_unum_contains(erunum, eeunum) <= 0) 632 return (0); /* can't parse/match, so assume no containment */ 633 634 if (nvlist_lookup_uint64(er, FM_FMRI_MEM_OFFSET, &erval) == 0) { 635 return (nvlist_lookup_uint64(ee, 636 FM_FMRI_MEM_OFFSET, &eeval) == 0 && erval == eeval); 637 } 638 639 if (nvlist_lookup_uint64(er, FM_FMRI_MEM_PHYSADDR, &erval) == 0) { 640 return (nvlist_lookup_uint64(ee, 641 FM_FMRI_MEM_PHYSADDR, &eeval) == 0 && erval == eeval); 642 } 643 644 return (1); 645 } 646 647 /* 648 * We can only make a usable/unusable determination for pages. Mem FMRIs 649 * without page addresses will be reported as usable since Solaris has no 650 * way at present to dynamically disable an entire DIMM or DIMM pair. 651 */ 652 int 653 fmd_fmri_unusable(nvlist_t *nvl) 654 { 655 uint64_t val; 656 uint8_t version; 657 int rc, err1, err2; 658 nvlist_t *nvlcp = NULL; 659 int retval; 660 661 if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 || 662 version > FM_MEM_SCHEME_VERSION) 663 return (fmd_fmri_set_errno(EINVAL)); 664 665 err1 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val); 666 err2 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val); 667 668 if (err1 == ENOENT && err2 == ENOENT) 669 return (0); /* no page, so assume it's still usable */ 670 671 if ((err1 != 0 && err1 != ENOENT) || (err2 != 0 && err2 != ENOENT)) 672 return (fmd_fmri_set_errno(EINVAL)); 673 674 if ((err1 = mem_unum_rewrite(nvl, &nvlcp)) != 0) 675 return (fmd_fmri_set_errno(err1)); 676 677 /* 678 * Ask the kernel if the page is retired, using either the rewritten 679 * hc FMRI or the original mem FMRI with the specified offset or PA. 680 * Refer to the kernel's page_retire_check() for the error codes. 681 */ 682 rc = mem_page_cmd(MEM_PAGE_FMRI_ISRETIRED, nvlcp ? nvlcp : nvl); 683 684 if (rc == -1 && errno == EIO) { 685 /* 686 * The page is not retired and is not scheduled for retirement 687 * (i.e. no request pending and has not seen any errors) 688 */ 689 retval = 0; 690 } else if (rc == 0 || errno == EAGAIN || errno == EINVAL) { 691 /* 692 * The page has been retired, is in the process of being 693 * retired, or doesn't exist. The latter is valid if the page 694 * existed in the past but has been DR'd out. 695 */ 696 retval = 1; 697 } else { 698 /* 699 * Errors are only signalled to the caller if they're the 700 * caller's fault. This isn't - it's a failure of the 701 * retirement-check code. We'll whine about it and tell 702 * the caller the page is unusable. 703 */ 704 fmd_fmri_warn("failed to determine page %s=%llx usability: " 705 "rc=%d errno=%d\n", err1 == 0 ? FM_FMRI_MEM_OFFSET : 706 FM_FMRI_MEM_PHYSADDR, (u_longlong_t)val, rc, errno); 707 retval = 1; 708 } 709 710 if (nvlcp) 711 nvlist_free(nvlcp); 712 713 return (retval); 714 } 715 716 int 717 fmd_fmri_init(void) 718 { 719 #ifdef sparc 720 mem_scheme_lhp = ldom_init(fmd_fmri_alloc, fmd_fmri_free); 721 #endif /* sparc */ 722 return (mem_discover()); 723 } 724 725 void 726 fmd_fmri_fini(void) 727 { 728 mem_dimm_map_t *dm, *em; 729 730 for (dm = mem.mem_dm; dm != NULL; dm = em) { 731 em = dm->dm_next; 732 fmd_fmri_strfree(dm->dm_label); 733 fmd_fmri_strfree(dm->dm_device); 734 fmd_fmri_free(dm, sizeof (mem_dimm_map_t)); 735 } 736 #ifdef sparc 737 ldom_fini(mem_scheme_lhp); 738 #endif /* sparc */ 739 } 740