Home | History | Annotate | Download | only in sparc
      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