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 }