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 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * DIMM unum/device map construction 31 * 32 * The map is constructed from PICL configuration files, which contain a map 33 * between a form of the unum and the device to be used for serial number 34 * retrieval. We massage the PICL unum into a form that matches the one used 35 * by mem FMRIs, creating a map entry from the munged version. As described 36 * below, two configuration files must be correlated to determine the correct 37 * device path, and thus to build the mem_dimm_map_t list. While platforms 38 * without PICL configuration files are acceptable (some platforms, like 39 * Serengeti and Starcat, don't have configuration files as of this writing), 40 * platforms with only one or the other aren't. 41 * 42 * On Sun4v platforms, we read the 'mdesc' machine description file in order 43 * to obtain the mapping between dimm unum+jnum strings (which denote slot 44 * names) and the serial numbers of the dimms occupying those slots. 45 */ 46 47 #include <sys/param.h> 48 #include <sys/mdesc.h> 49 50 #include <mem.h> 51 #include <fm/fmd_fmri.h> 52 53 #include <fcntl.h> 54 #include <unistd.h> 55 #include <stdio.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <strings.h> 59 #include <errno.h> 60 #include <time.h> 61 #include <sys/mem.h> 62 #include <sys/fm/ldom.h> 63 64 extern ldom_hdl_t *mem_scheme_lhp; 65 66 #define PICL_FRUTREE_PATH \ 67 "%s/usr/platform/%s/lib/picl/plugins/piclfrutree.conf" 68 69 #define PICL_FRUDATA_PATH \ 70 "%s/usr/platform/%s/lib/picl/plugins/libpiclfrudata.conf" 71 72 typedef struct mem_path_map { 73 struct mem_path_map *pm_next; 74 char *pm_path; 75 char *pm_fullpath; 76 } mem_path_map_t; 77 78 typedef struct label_xlators { 79 const char *lx_infmt; 80 uint_t lx_matches; 81 const char *lx_outfmt; 82 } label_xlators_t; 83 84 /* 85 * PICL configuration files use a different format for the DIMM name (unum) 86 * than that used in mem FMRIs. The following patterns and routine are used 87 * to convert between the PICL and unum formats. 88 */ 89 static const label_xlators_t label_xlators[] = { 90 { "/system-board/mem-slot?Label=J%4d%5$n", 1, 91 "J%04d" }, 92 { "/system-board/mem-slot?Label=DIMM%1d%5$n", 1, 93 "DIMM%d" }, 94 { "/system-board/cpu-mem-slot?Label=%4$c/mem-slot?Label=J%1$4d%5$n", 2, 95 "Slot %4$c: J%1$4d" }, 96 { "/MB/system-board/mem-slot?Label=DIMM%1d%5$n", 1, 97 "DIMM%d" }, 98 { "/MB/system-board/P%1d/cpu/B%1d/bank/D%1d%5$n", 3, 99 "MB/P%d/B%d/D%d" }, 100 { "/MB/system-board/C%1d/cpu-module/P0/cpu/B%1d/bank/D%1d%5$n", 3, 101 "MB/C%d/P0/B%d/D%d" }, 102 { "/MB/system-board/DIMM%1d%5$n", 1, 103 "MB/DIMM%d" }, 104 { "/C%1d/system-board/P0/cpu/B%1d/bank/D%1d%5$n", 3, 105 "C%d/P0/B%d/D%d" }, 106 { NULL } 107 }; 108 109 static int 110 label_xlate(char *buf) 111 { 112 const label_xlators_t *xlator; 113 114 if (strncmp(buf, "/frutree/chassis", 16) != 0) 115 return (0); 116 117 for (xlator = label_xlators; xlator->lx_infmt != NULL; xlator++) { 118 uint_t len, a1, a2, a3; 119 char a4; 120 121 if (sscanf(buf + 16, xlator->lx_infmt, &a1, &a2, &a3, &a4, 122 &len) == xlator->lx_matches && len == strlen(buf + 16)) { 123 (void) sprintf(buf, xlator->lx_outfmt, a1, a2, a3, a4); 124 return (0); 125 } 126 } 127 128 return (fmd_fmri_set_errno(EINVAL)); 129 } 130 131 /* 132 * Match two paths taken from picl files. This is a normal component-based path 133 * comparison, but for the fact that components `foo' and `foo@1,2' are assumed 134 * to be equal. `foo@1,2' and `foo@3,4', however, are not assumed to be equal. 135 */ 136 static int 137 picl_path_eq(const char *p1, const char *p2) 138 { 139 for (;;) { 140 if (*p1 == *p2) { 141 if (*p1 == '\0') 142 return (1); 143 else { 144 p1++; 145 p2++; 146 continue; 147 } 148 } 149 150 if (*p1 == '@' && (*p2 == '/' || *p2 == '\0')) { 151 while (*p1 != '/' && *p1 != '\0') 152 p1++; 153 continue; 154 } 155 156 if ((*p1 == '/' || *p1 == '\0') && *p2 == '@') { 157 while (*p2 != '/' && *p2 != '\0') 158 p2++; 159 continue; 160 } 161 162 return (0); 163 } 164 } 165 166 /* 167 * PICL paths begin with `/platform' instead of `/devices', as they are 168 * intended to reference points in the PICL tree, rather than places in the 169 * device tree. Furthermore, some paths use the construct `?UnitAddress=a,b' 170 * instead of `@a,b' to indicate unit number and address. This routine 171 * replaces both constructs with forms more appropriate for /devices path 172 * lookup. 173 */ 174 static void 175 path_depicl(char *path) 176 { 177 char *c; 178 179 if (strncmp(path, "name:", 4) == 0) 180 bcopy(path + 5, path, strlen(path + 5) + 1); 181 182 for (c = path; (c = strstr(c, "?UnitAddress=")) != NULL; c++) { 183 uint_t len = 0; 184 185 (void) sscanf(c + 13, "%*x,%*x%n", &len); 186 if (len == 0) 187 continue; 188 189 *c = '@'; 190 bcopy(c + 13, c + 1, strlen(c + 13) + 1); 191 } 192 } 193 194 /* 195 * The libpiclfrudata configuration file contains a map between the generic 196 * (minor-less) device and the specific device to be used for SPD/SEEPROM 197 * data access. 198 * 199 * Entries are of the form: 200 * 201 * name:/platform/generic-path 202 * PROP FRUDevicePath string r 0 "full-path" 203 * 204 * Where `generic-path' is the path, sans minor name, to be used for DIMM 205 * data access, and `full-path' is the path with the minor name. 206 */ 207 static int 208 picl_frudata_parse(char *buf, char *path, void *arg) 209 { 210 mem_path_map_t **mapp = arg; 211 mem_path_map_t *pm = NULL; 212 char fullpath[BUFSIZ]; 213 uint_t len; 214 215 if (sscanf(buf, " PROP FRUDevicePath string r 0 \"%[^\"]\" \n%n", 216 fullpath, &len) != 1 || fullpath[0] == '\0' || len != strlen(buf)) 217 return (0); 218 219 path_depicl(path); 220 221 pm = fmd_fmri_alloc(sizeof (mem_path_map_t)); 222 pm->pm_path = fmd_fmri_strdup(path); 223 pm->pm_fullpath = fmd_fmri_strdup(fullpath); 224 225 pm->pm_next = *mapp; 226 *mapp = pm; 227 228 return (1); 229 } 230 231 /* 232 * The piclfrutree configuration file contains a map between a form of the 233 * DIMM's unum and the generic (minor-less) device used for SPD/SEEPROM data 234 * access. 235 * 236 * Entries are of the form: 237 * 238 * name:/frutree/chassis/picl-unum 239 * REFNODE mem-module fru WITH /platform/generic-path 240 * 241 * Where `picl-unum' is the PICL form of the unum, which we'll massage into 242 * the form compatible with FMRIs (see label_xlate), and `generic-path' is 243 * the minor-less path into the PICL tree for the device used to access the 244 * DIMM. It is this path that will be used as the key in the frudata 245 * configuration file to determine the proper /devices path. 246 */ 247 typedef struct dimm_map_arg { 248 mem_path_map_t *dma_pm; 249 mem_dimm_map_t *dma_dm; 250 } dimm_map_arg_t; 251 252 static int 253 picl_frutree_parse(char *buf, char *label, void *arg) 254 { 255 dimm_map_arg_t *dma = arg; 256 mem_dimm_map_t *dm = NULL; 257 mem_path_map_t *pm; 258 char path[BUFSIZ]; 259 uint_t len; 260 261 /* LINTED - sscanf cannot exceed sizeof (path) */ 262 if (sscanf(buf, " REFNODE mem-module fru WITH %s \n%n", 263 path, &len) != 1 || path[0] == '\0' || len != strlen(buf)) 264 return (0); 265 266 if (label_xlate(label) < 0) 267 return (-1); /* errno is set for us */ 268 269 path_depicl(path); 270 271 for (pm = dma->dma_pm; pm != NULL; pm = pm->pm_next) { 272 if (picl_path_eq(pm->pm_path, path)) { 273 (void) strcpy(path, pm->pm_fullpath); 274 break; 275 } 276 } 277 278 dm = fmd_fmri_zalloc(sizeof (mem_dimm_map_t)); 279 dm->dm_label = fmd_fmri_strdup(label); 280 dm->dm_device = fmd_fmri_strdup(path); 281 282 dm->dm_next = dma->dma_dm; 283 dma->dma_dm = dm; 284 285 return (1); 286 } 287 288 /* 289 * Both configuration files use the same format, thus allowing us to use the 290 * same parser to process them. 291 */ 292 static int 293 picl_conf_parse(const char *pathpat, int (*func)(char *, char *, void *), 294 void *arg) 295 { 296 char confpath[MAXPATHLEN]; 297 char buf[BUFSIZ], label[BUFSIZ]; 298 int line, len, rc; 299 FILE *fp; 300 301 (void) snprintf(confpath, sizeof (confpath), pathpat, 302 fmd_fmri_get_rootdir(), fmd_fmri_get_platform()); 303 304 if ((fp = fopen(confpath, "r")) == NULL) 305 return (-1); /* errno is set for us */ 306 307 label[0] = '\0'; 308 for (line = 1; fgets(buf, sizeof (buf), fp) != NULL; line++) { 309 if (buf[0] == '#') 310 continue; 311 312 if (buf[0] == '\n') { 313 label[0] = '\0'; 314 continue; 315 } 316 317 /* LINTED - label length cannot exceed length of buf */ 318 if (sscanf(buf, " name:%s \n%n", label, &len) == 1 && 319 label[0] != '\0' && len == strlen(buf)) 320 continue; 321 322 if (label[0] != '\0') { 323 if ((rc = func(buf, label, arg)) < 0) { 324 int err = errno; 325 (void) fclose(fp); 326 return (fmd_fmri_set_errno(err)); 327 } else if (rc != 0) { 328 label[0] = '\0'; 329 } 330 } 331 } 332 333 (void) fclose(fp); 334 return (0); 335 } 336 337 static void 338 path_map_destroy(mem_path_map_t *pm) 339 { 340 mem_path_map_t *next; 341 342 for (/* */; pm != NULL; pm = next) { 343 next = pm->pm_next; 344 345 fmd_fmri_strfree(pm->pm_path); 346 fmd_fmri_strfree(pm->pm_fullpath); 347 fmd_fmri_free(pm, sizeof (mem_path_map_t)); 348 } 349 } 350 351 int 352 mem_discover(void) 353 { 354 mem_path_map_t *path_map = NULL; 355 dimm_map_arg_t dma; 356 int rc; 357 358 if (picl_conf_parse(PICL_FRUDATA_PATH, picl_frudata_parse, 359 &path_map) < 0 && errno != ENOENT) 360 return (-1); /* errno is set for us */ 361 362 dma.dma_pm = path_map; 363 dma.dma_dm = NULL; 364 365 if ((rc = picl_conf_parse(PICL_FRUTREE_PATH, picl_frutree_parse, 366 &dma)) < 0 && errno == ENOENT && path_map == NULL) { 367 /* 368 * This platform doesn't support serial number retrieval via 369 * PICL mapping files. Unfortunate, but not an error. 370 */ 371 return (0); 372 } 373 374 path_map_destroy(path_map); 375 376 if (rc < 0) 377 return (-1); /* errno is set for us */ 378 379 if (dma.dma_dm == NULL) { 380 /* 381 * This platform should support DIMM serial numbers, but we 382 * weren't able to derive the paths. Return an error. 383 */ 384 return (fmd_fmri_set_errno(EIO)); 385 } 386 387 mem.mem_dm = dma.dma_dm; 388 return (0); 389 } 390 391 /* 392 * Retry values for handling the case where the kernel is not yet ready 393 * to provide DIMM serial ids. Some platforms acquire DIMM serial id 394 * information from their System Controller via a mailbox interface. 395 * The values chosen are for 10 retries 3 seconds apart to approximate the 396 * possible 30 second timeout length of a mailbox message request. 397 */ 398 #define MAX_MEM_SID_RETRIES 10 399 #define MEM_SID_RETRY_WAIT 3 400 401 /* 402 * The comparison is asymmetric. It compares up to the length of the 403 * argument unum. 404 */ 405 static mem_dimm_map_t * 406 dm_lookup(const char *name) 407 { 408 mem_dimm_map_t *dm; 409 410 for (dm = mem.mem_dm; dm != NULL; dm = dm->dm_next) { 411 if (strncmp(name, dm->dm_label, strlen(name)) == 0) 412 return (dm); 413 } 414 415 return (NULL); 416 } 417 418 /* 419 * Returns 0 with serial numbers if found, -1 (with errno set) for errors. If 420 * the unum (or a component of same) wasn't found, -1 is returned with errno 421 * set to ENOENT. If the kernel doesn't have support for serial numbers, 422 * -1 is returned with errno set to ENOTSUP. 423 */ 424 static int 425 mem_get_serids_from_kernel(const char *unum, char ***seridsp, size_t *nseridsp) 426 { 427 char **dimms, **serids; 428 size_t ndimms, nserids; 429 int i, rc = 0; 430 int fd; 431 int retries = MAX_MEM_SID_RETRIES; 432 mem_name_t mn; 433 struct timespec rqt; 434 435 if ((fd = open("/dev/mem", O_RDONLY)) < 0) 436 return (-1); 437 438 if (mem_unum_burst(unum, &dimms, &ndimms) < 0) { 439 (void) close(fd); 440 return (-1); /* errno is set for us */ 441 } 442 443 serids = fmd_fmri_zalloc(sizeof (char *) * ndimms); 444 nserids = ndimms; 445 446 bzero(&mn, sizeof (mn)); 447 448 for (i = 0; i < ndimms; i++) { 449 mn.m_namelen = strlen(dimms[i]) + 1; 450 mn.m_sidlen = MEM_SERID_MAXLEN; 451 452 mn.m_name = fmd_fmri_alloc(mn.m_namelen); 453 mn.m_sid = fmd_fmri_alloc(mn.m_sidlen); 454 455 (void) strcpy(mn.m_name, dimms[i]); 456 457 do { 458 rc = ioctl(fd, MEM_SID, &mn); 459 460 if (rc >= 0 || errno != EAGAIN) 461 break; 462 463 if (retries == 0) { 464 errno = ETIMEDOUT; 465 break; 466 } 467 468 /* 469 * EAGAIN indicates the kernel is 470 * not ready to provide DIMM serial 471 * ids. Sleep MEM_SID_RETRY_WAIT seconds 472 * and try again. 473 * nanosleep() is used instead of sleep() 474 * to avoid interfering with fmd timers. 475 */ 476 rqt.tv_sec = MEM_SID_RETRY_WAIT; 477 rqt.tv_nsec = 0; 478 (void) nanosleep(&rqt, NULL); 479 480 } while (retries--); 481 482 if (rc < 0) { 483 /* 484 * ENXIO can happen if the kernel memory driver 485 * doesn't have the MEM_SID ioctl (e.g. if the 486 * kernel hasn't been patched to provide the 487 * support). 488 * 489 * If the MEM_SID ioctl is available but the 490 * particular platform doesn't support providing 491 * serial ids, ENOTSUP will be returned by the ioctl. 492 */ 493 if (errno == ENXIO) 494 errno = ENOTSUP; 495 fmd_fmri_free(mn.m_name, mn.m_namelen); 496 fmd_fmri_free(mn.m_sid, mn.m_sidlen); 497 mem_strarray_free(serids, nserids); 498 mem_strarray_free(dimms, ndimms); 499 (void) close(fd); 500 return (-1); 501 } 502 503 serids[i] = fmd_fmri_strdup(mn.m_sid); 504 505 fmd_fmri_free(mn.m_name, mn.m_namelen); 506 fmd_fmri_free(mn.m_sid, mn.m_sidlen); 507 } 508 509 mem_strarray_free(dimms, ndimms); 510 511 (void) close(fd); 512 513 *seridsp = serids; 514 *nseridsp = nserids; 515 516 return (0); 517 } 518 519 /* 520 * Returns 0 with serial numbers if found, -1 (with errno set) for errors. If 521 * the unum (or a component of same) wasn't found, -1 is returned with errno 522 * set to ENOENT. 523 */ 524 static int 525 mem_get_serids_from_cache(const char *unum, char ***seridsp, size_t *nseridsp) 526 { 527 uint64_t drgen = fmd_fmri_get_drgen(); 528 char **dimms, **serids; 529 size_t ndimms, nserids; 530 mem_dimm_map_t *dm; 531 int i, rc = 0; 532 533 if (mem_unum_burst(unum, &dimms, &ndimms) < 0) 534 return (-1); /* errno is set for us */ 535 536 serids = fmd_fmri_zalloc(sizeof (char *) * ndimms); 537 nserids = ndimms; 538 539 for (i = 0; i < ndimms; i++) { 540 if ((dm = dm_lookup(dimms[i])) == NULL) { 541 rc = fmd_fmri_set_errno(EINVAL); 542 break; 543 } 544 545 if (*dm->dm_serid == '\0' || dm->dm_drgen != drgen) { 546 /* 547 * We don't have a cached copy, or the copy we've got is 548 * out of date. Look it up again. 549 */ 550 if (mem_get_serid(dm->dm_device, dm->dm_serid, 551 sizeof (dm->dm_serid)) < 0) { 552 rc = -1; /* errno is set for us */ 553 break; 554 } 555 556 dm->dm_drgen = drgen; 557 } 558 559 serids[i] = fmd_fmri_strdup(dm->dm_serid); 560 } 561 562 mem_strarray_free(dimms, ndimms); 563 564 if (rc == 0) { 565 *seridsp = serids; 566 *nseridsp = nserids; 567 } else { 568 mem_strarray_free(serids, nserids); 569 } 570 571 return (rc); 572 } 573 574 int 575 mem_get_serids_by_unum(const char *unum, char ***seridsp, size_t *nseridsp) 576 { 577 /* 578 * Some platforms do not support the caching of serial ids by the 579 * mem scheme plugin but instead support making serial ids available 580 * via the kernel. 581 */ 582 if (mem.mem_dm == NULL) 583 return (mem_get_serids_from_kernel(unum, seridsp, nseridsp)); 584 else 585 return (mem_get_serids_from_cache(unum, seridsp, nseridsp)); 586 } 587