xref: /titanic_41/usr/src/cmd/fm/schemes/mem/sparc/mem_disc.c (revision 70818f5837509317d1f5dac4d82d7b5a2d547c29)
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
label_xlate(char * buf)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
picl_path_eq(const char * p1,const char * p2)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
path_depicl(char * path)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
picl_frudata_parse(char * buf,char * path,void * arg)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
picl_frutree_parse(char * buf,char * label,void * arg)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
picl_conf_parse(const char * pathpat,int (* func)(char *,char *,void *),void * arg)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
path_map_destroy(mem_path_map_t * pm)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
mem_discover(void)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 *
dm_lookup(const char * name)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
mem_get_serids_from_kernel(const char * unum,char *** seridsp,size_t * nseridsp)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
mem_get_serids_from_cache(const char * unum,char *** seridsp,size_t * nseridsp)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
mem_get_serids_by_unum(const char * unum,char *** seridsp,size_t * nseridsp)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