xref: /illumos-gate/usr/src/cmd/fm/schemes/mem/mem.c (revision c51cb4bc539e1650eb5bb4f805cc779bfce99c06)
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 #include <mem.h>
30 #include <fm/fmd_fmri.h>
31 #include <fm/libtopo.h>
32 
33 #include <string.h>
34 #include <strings.h>
35 #include <sys/mem.h>
36 
37 mem_t mem;
38 
39 static int
40 mem_fmri_get_unum(nvlist_t *nvl, char **unump)
41 {
42 	uint8_t version;
43 	char *unum;
44 
45 	if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 ||
46 	    version > FM_MEM_SCHEME_VERSION ||
47 	    nvlist_lookup_string(nvl, FM_FMRI_MEM_UNUM, &unum) != 0)
48 		return (fmd_fmri_set_errno(EINVAL));
49 
50 	*unump = unum;
51 
52 	return (0);
53 }
54 
55 ssize_t
56 fmd_fmri_nvl2str(nvlist_t *nvl, char *buf, size_t buflen)
57 {
58 	char format[64];
59 	ssize_t size, presz;
60 	char *rawunum, *preunum, *escunum, *prefix;
61 	uint64_t val;
62 	int i;
63 
64 	if (mem_fmri_get_unum(nvl, &rawunum) < 0)
65 		return (-1); /* errno is set for us */
66 
67 	/*
68 	 * If we have a well-formed unum (hc-FMRI), use the string verbatim
69 	 * to form the initial mem:/// components.  Otherwise use unum=%s.
70 	 */
71 	if (strncmp(rawunum, "hc://", 5) != 0)
72 		prefix = FM_FMRI_MEM_UNUM "=";
73 	else
74 		prefix = "";
75 
76 	/*
77 	 * If we have a DIMM offset, include it in the string.  If we have a PA
78 	 * then use that.  Otherwise just format the unum element.
79 	 */
80 	if (nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val) == 0) {
81 		(void) snprintf(format, sizeof (format),
82 		    "%s:///%s%%1$s/%s=%%2$llx",
83 		    FM_FMRI_SCHEME_MEM, prefix, FM_FMRI_MEM_OFFSET);
84 	} else if (nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val) == 0) {
85 		(void) snprintf(format, sizeof (format),
86 		    "%s:///%s%%1$s/%s=%%2$llx",
87 		    FM_FMRI_SCHEME_MEM, prefix, FM_FMRI_MEM_PHYSADDR);
88 	} else {
89 		(void) snprintf(format, sizeof (format),
90 		    "%s:///%s%%1$s", FM_FMRI_SCHEME_MEM, prefix);
91 	}
92 
93 	/*
94 	 * If we have a well-formed unum (hc-FMRI), we skip over the
95 	 * the scheme and authority prefix.
96 	 * Otherwise, the spaces and colons will be escaped,
97 	 * rendering the resulting FMRI pretty much unreadable.
98 	 * We're therefore going to do some escaping of our own first.
99 	 */
100 	if (strncmp(rawunum, "hc://", 5) == 0) {
101 		rawunum += 5;
102 		rawunum = strchr(rawunum, '/');
103 		++rawunum;
104 		/* LINTED: variable format specifier */
105 		size = snprintf(buf, buflen, format, rawunum, val);
106 	} else {
107 		preunum = fmd_fmri_strdup(rawunum);
108 		presz = strlen(preunum) + 1;
109 
110 		for (i = 0; i < presz - 1; i++) {
111 			if (preunum[i] == ':' && preunum[i + 1] == ' ') {
112 				bcopy(preunum + i + 2, preunum + i + 1,
113 				    presz - (i + 2));
114 			} else if (preunum[i] == ' ') {
115 				preunum[i] = ',';
116 			}
117 		}
118 
119 		escunum = fmd_fmri_strescape(preunum);
120 		fmd_fmri_free(preunum, presz);
121 
122 		/* LINTED: variable format specifier */
123 		size = snprintf(buf, buflen, format, escunum, val);
124 		fmd_fmri_strfree(escunum);
125 	}
126 
127 	return (size);
128 }
129 
130 int
131 fmd_fmri_expand(nvlist_t *nvl)
132 {
133 	char *unum, **serids;
134 	uint_t nnvlserids;
135 	size_t nserids;
136 	int rc, err = 0;
137 	topo_hdl_t *thp;
138 
139 	if ((mem_fmri_get_unum(nvl, &unum) < 0) || (*unum == '\0'))
140 		return (fmd_fmri_set_errno(EINVAL));
141 
142 	/*
143 	 * If the mem-scheme topology exports this method expand(), invoke it.
144 	 */
145 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
146 		return (fmd_fmri_set_errno(EINVAL));
147 	rc = topo_fmri_expand(thp, nvl, &err);
148 	fmd_fmri_topo_rele(thp);
149 	if (err != ETOPO_METHOD_NOTSUP)
150 		return (rc);
151 
152 	if ((rc = nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID,
153 	    &serids, &nnvlserids)) == 0) { /* already have serial #s */
154 		return (0);
155 	} else if (rc != ENOENT)
156 		return (fmd_fmri_set_errno(EINVAL));
157 
158 	if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) {
159 		/* errno is set for us */
160 		if (errno == ENOTSUP)
161 			return (0); /* nothing to add - no s/n support */
162 		else
163 			return (-1);
164 	}
165 
166 	rc = nvlist_add_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, serids,
167 	    nserids);
168 
169 	mem_strarray_free(serids, nserids);
170 
171 	if (rc != 0)
172 		return (fmd_fmri_set_errno(EINVAL));
173 	else
174 		return (0);
175 }
176 
177 #ifdef sparc
178 static int
179 serids_eq(char **serids1, uint_t nserids1, char **serids2, uint_t nserids2)
180 {
181 	int i;
182 
183 	if (nserids1 != nserids2)
184 		return (0);
185 
186 	for (i = 0; i < nserids1; i++) {
187 		if (strcmp(serids1[i], serids2[i]) != 0)
188 			return (0);
189 	}
190 
191 	return (1);
192 }
193 #endif /* sparc */
194 
195 int
196 fmd_fmri_present(nvlist_t *nvl)
197 {
198 	char *unum = NULL;
199 	int rc, err = 0;
200 	struct topo_hdl *thp;
201 #ifdef sparc
202 	char **nvlserids, **serids;
203 	uint_t nnvlserids;
204 	size_t nserids;
205 #else
206 	nvlist_t *unum_nvl;
207 	nvlist_t *nvlcp = NULL;
208 	uint64_t val;
209 #endif /* sparc */
210 
211 	if (mem_fmri_get_unum(nvl, &unum) < 0)
212 		return (-1); /* errno is set for us */
213 
214 #ifdef sparc
215 	/*
216 	 * If the mem-scheme topology exports this method present(), invoke it.
217 	 */
218 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
219 		return (fmd_fmri_set_errno(EINVAL));
220 	rc = topo_fmri_present(thp, nvl, &err);
221 	fmd_fmri_topo_rele(thp);
222 	if (err != ETOPO_METHOD_NOTSUP)
223 		return (rc);
224 
225 	if (nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, &nvlserids,
226 	    &nnvlserids) != 0) {
227 		/*
228 		 * Some mem scheme FMRIs don't have serial ids because
229 		 * either the platform does not support them, or because
230 		 * the FMRI was created before support for serial ids was
231 		 * introduced.  If this is the case, assume it is there.
232 		 */
233 		if (mem.mem_dm == NULL)
234 			return (1);
235 		else
236 			return (fmd_fmri_set_errno(EINVAL));
237 	}
238 
239 	if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) {
240 		if (errno == ENOTSUP)
241 			return (1); /* assume it's there, no s/n support here */
242 		if (errno != ENOENT) {
243 			/*
244 			 * Errors are only signalled to the caller if they're
245 			 * the caller's fault.  This isn't - it's a failure on
246 			 * our part to burst or read the serial numbers.  We'll
247 			 * whine about it, and tell the caller the named
248 			 * module(s) isn't/aren't there.
249 			 */
250 			fmd_fmri_warn("failed to retrieve serial number for "
251 			    "unum %s", unum);
252 		}
253 		return (0);
254 	}
255 
256 	rc = serids_eq(serids, nserids, nvlserids, nnvlserids);
257 
258 	mem_strarray_free(serids, nserids);
259 #else
260 	/*
261 	 * On X86 we will invoke the topo is_present method passing in the
262 	 * unum, which is in hc scheme.  The libtopo hc-scheme is_present method
263 	 * will invoke the node-specific is_present method, which is implemented
264 	 * by the chip enumerator for rank nodes.  The rank node's is_present
265 	 * method will compare the serial number in the unum with the current
266 	 * serial to determine if the same DIMM is present.
267 	 */
268 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) {
269 		fmd_fmri_warn("failed to get handle to topology");
270 		return (-1);
271 	}
272 	if (topo_fmri_str2nvl(thp, unum, &unum_nvl, &err) == 0) {
273 		rc = topo_fmri_present(thp, unum_nvl, &err);
274 		nvlist_free(unum_nvl);
275 	} else
276 		rc = fmd_fmri_set_errno(EINVAL);
277 	fmd_fmri_topo_rele(thp);
278 
279 	/*
280 	 * Need to check if this is a valid page too. if "isretired" returns
281 	 * EINVAL, assume page invalid and return not_present.
282 	 */
283 	if (rc == 1 && nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val) ==
284 	    0 && nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val) == 0 &&
285 	    mem_unum_rewrite(nvl, &nvlcp) == 0 && nvlcp != NULL) {
286 		int rval = mem_page_cmd(MEM_PAGE_FMRI_ISRETIRED, nvlcp);
287 		if (rval == -1 && errno == EINVAL)
288 			rc = 0;
289 		nvlist_free(nvlcp);
290 	}
291 #endif	/* sparc */
292 	return (rc);
293 }
294 
295 int
296 fmd_fmri_replaced(nvlist_t *nvl)
297 {
298 	char *unum = NULL;
299 	int rc, err = 0;
300 	struct topo_hdl *thp;
301 #ifdef sparc
302 	char **nvlserids, **serids;
303 	uint_t nnvlserids;
304 	size_t nserids;
305 #else
306 	nvlist_t *unum_nvl;
307 	nvlist_t *nvlcp = NULL;
308 	uint64_t val;
309 #endif /* sparc */
310 
311 	if (mem_fmri_get_unum(nvl, &unum) < 0)
312 		return (-1); /* errno is set for us */
313 
314 #ifdef sparc
315 	/*
316 	 * If the mem-scheme topology exports this method replaced(), invoke it.
317 	 */
318 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
319 		return (fmd_fmri_set_errno(EINVAL));
320 	rc = topo_fmri_replaced(thp, nvl, &err);
321 	fmd_fmri_topo_rele(thp);
322 	if (err != ETOPO_METHOD_NOTSUP)
323 		return (rc);
324 
325 	if (nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, &nvlserids,
326 	    &nnvlserids) != 0) {
327 		/*
328 		 * Some mem scheme FMRIs don't have serial ids because
329 		 * either the platform does not support them, or because
330 		 * the FMRI was created before support for serial ids was
331 		 * introduced.  If this is the case, assume it is there.
332 		 */
333 		if (mem.mem_dm == NULL)
334 			return (FMD_OBJ_STATE_UNKNOWN);
335 		else
336 			return (fmd_fmri_set_errno(EINVAL));
337 	}
338 
339 	if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) {
340 		if (errno == ENOTSUP)
341 			return (FMD_OBJ_STATE_UNKNOWN);
342 		if (errno != ENOENT) {
343 			/*
344 			 * Errors are only signalled to the caller if they're
345 			 * the caller's fault.  This isn't - it's a failure on
346 			 * our part to burst or read the serial numbers.  We'll
347 			 * whine about it, and tell the caller the named
348 			 * module(s) isn't/aren't there.
349 			 */
350 			fmd_fmri_warn("failed to retrieve serial number for "
351 			    "unum %s", unum);
352 		}
353 		return (FMD_OBJ_STATE_NOT_PRESENT);
354 	}
355 
356 	rc = serids_eq(serids, nserids, nvlserids, nnvlserids) ?
357 	    FMD_OBJ_STATE_STILL_PRESENT : FMD_OBJ_STATE_REPLACED;
358 
359 	mem_strarray_free(serids, nserids);
360 #else
361 	/*
362 	 * On X86 we will invoke the topo is_replaced method passing in the
363 	 * unum, which is in hc scheme.  The libtopo hc-scheme is_replaced
364 	 * method will invoke the node-specific is_replaced method, which is
365 	 * implemented by the chip enumerator for rank nodes.  The rank node's
366 	 * is_replaced method will compare the serial number in the unum with
367 	 * the current serial to determine if the same DIMM is replaced.
368 	 */
369 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) {
370 		fmd_fmri_warn("failed to get handle to topology");
371 		return (-1);
372 	}
373 	if (topo_fmri_str2nvl(thp, unum, &unum_nvl, &err) == 0) {
374 		rc = topo_fmri_replaced(thp, unum_nvl, &err);
375 		nvlist_free(unum_nvl);
376 	} else
377 		rc = fmd_fmri_set_errno(EINVAL);
378 	fmd_fmri_topo_rele(thp);
379 
380 	/*
381 	 * Need to check if this is a valid page too. if "isretired" returns
382 	 * EINVAL, assume page invalid and return not_present.
383 	 */
384 	if ((rc == FMD_OBJ_STATE_STILL_PRESENT ||
385 	    rc == FMD_OBJ_STATE_UNKNOWN) &&
386 	    nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val) == 0 &&
387 	    nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val) == 0 &&
388 	    mem_unum_rewrite(nvl, &nvlcp) == 0 && nvlcp != NULL) {
389 		int rval = mem_page_cmd(MEM_PAGE_FMRI_ISRETIRED, nvlcp);
390 		if (rval == -1 && errno == EINVAL)
391 			rc = FMD_OBJ_STATE_NOT_PRESENT;
392 		nvlist_free(nvlcp);
393 	}
394 #endif	/* sparc */
395 	return (rc);
396 }
397 
398 int
399 fmd_fmri_contains(nvlist_t *er, nvlist_t *ee)
400 {
401 	int rc, err = 0;
402 	struct topo_hdl *thp;
403 	char *erunum, *eeunum;
404 	uint64_t erval = 0, eeval = 0;
405 
406 	/*
407 	 * If the mem-scheme topology exports this method contains(), invoke it.
408 	 */
409 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
410 		return (fmd_fmri_set_errno(EINVAL));
411 	rc = topo_fmri_contains(thp, er, ee, &err);
412 	fmd_fmri_topo_rele(thp);
413 	if (err != ETOPO_METHOD_NOTSUP)
414 		return (rc);
415 
416 	if (mem_fmri_get_unum(er, &erunum) < 0 ||
417 	    mem_fmri_get_unum(ee, &eeunum) < 0)
418 		return (-1); /* errno is set for us */
419 
420 	if (mem_unum_contains(erunum, eeunum) <= 0)
421 		return (0); /* can't parse/match, so assume no containment */
422 
423 	if (nvlist_lookup_uint64(er, FM_FMRI_MEM_OFFSET, &erval) == 0) {
424 		return (nvlist_lookup_uint64(ee,
425 		    FM_FMRI_MEM_OFFSET, &eeval) == 0 && erval == eeval);
426 	}
427 
428 	if (nvlist_lookup_uint64(er, FM_FMRI_MEM_PHYSADDR, &erval) == 0) {
429 		return (nvlist_lookup_uint64(ee,
430 		    FM_FMRI_MEM_PHYSADDR, &eeval) == 0 && erval == eeval);
431 	}
432 
433 	return (1);
434 }
435 
436 /*
437  * We can only make a usable/unusable determination for pages.  Mem FMRIs
438  * without page addresses will be reported as usable since Solaris has no
439  * way at present to dynamically disable an entire DIMM or DIMM pair.
440  */
441 int
442 fmd_fmri_unusable(nvlist_t *nvl)
443 {
444 	uint64_t val;
445 	uint8_t version;
446 	int rc, err1 = 0, err2;
447 	nvlist_t *nvlcp = NULL;
448 	int retval;
449 	topo_hdl_t *thp;
450 
451 	if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 ||
452 	    version > FM_MEM_SCHEME_VERSION)
453 		return (fmd_fmri_set_errno(EINVAL));
454 
455 	/*
456 	 * If the mem-scheme topology exports this method unusable(), invoke it.
457 	 */
458 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
459 		return (fmd_fmri_set_errno(EINVAL));
460 	rc = topo_fmri_unusable(thp, nvl, &err1);
461 	fmd_fmri_topo_rele(thp);
462 	if (err1 != ETOPO_METHOD_NOTSUP)
463 		return (rc);
464 
465 	err1 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val);
466 	err2 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val);
467 
468 	if (err1 == ENOENT && err2 == ENOENT)
469 		return (0); /* no page, so assume it's still usable */
470 
471 	if ((err1 != 0 && err1 != ENOENT) || (err2 != 0 && err2 != ENOENT))
472 		return (fmd_fmri_set_errno(EINVAL));
473 
474 	if ((err1 = mem_unum_rewrite(nvl, &nvlcp)) != 0)
475 		return (fmd_fmri_set_errno(err1));
476 
477 	/*
478 	 * Ask the kernel if the page is retired, using either the rewritten
479 	 * hc FMRI or the original mem FMRI with the specified offset or PA.
480 	 * Refer to the kernel's page_retire_check() for the error codes.
481 	 */
482 	rc = mem_page_cmd(MEM_PAGE_FMRI_ISRETIRED, nvlcp ? nvlcp : nvl);
483 
484 	if (rc == -1 && errno == EIO) {
485 		/*
486 		 * The page is not retired and is not scheduled for retirement
487 		 * (i.e. no request pending and has not seen any errors)
488 		 */
489 		retval = 0;
490 	} else if (rc == 0 || errno == EAGAIN || errno == EINVAL) {
491 		/*
492 		 * The page has been retired, is in the process of being
493 		 * retired, or doesn't exist.  The latter is valid if the page
494 		 * existed in the past but has been DR'd out.
495 		 */
496 		retval = 1;
497 	} else {
498 		/*
499 		 * Errors are only signalled to the caller if they're the
500 		 * caller's fault.  This isn't - it's a failure of the
501 		 * retirement-check code.  We'll whine about it and tell
502 		 * the caller the page is unusable.
503 		 */
504 		fmd_fmri_warn("failed to determine page %s=%llx usability: "
505 		    "rc=%d errno=%d\n", err1 == 0 ? FM_FMRI_MEM_OFFSET :
506 		    FM_FMRI_MEM_PHYSADDR, (u_longlong_t)val, rc, errno);
507 		retval = 1;
508 	}
509 
510 	if (nvlcp)
511 		nvlist_free(nvlcp);
512 
513 	return (retval);
514 }
515 
516 int
517 fmd_fmri_init(void)
518 {
519 	return (mem_discover());
520 }
521 
522 void
523 fmd_fmri_fini(void)
524 {
525 	mem_dimm_map_t *dm, *em;
526 	mem_bank_map_t *bm, *cm;
527 	mem_grp_t *gm, *hm;
528 	mem_seg_map_t *sm, *tm;
529 
530 	for (dm = mem.mem_dm; dm != NULL; dm = em) {
531 		em = dm->dm_next;
532 		fmd_fmri_strfree(dm->dm_label);
533 		fmd_fmri_strfree(dm->dm_part);
534 		fmd_fmri_strfree(dm->dm_device);
535 		fmd_fmri_free(dm, sizeof (mem_dimm_map_t));
536 	}
537 	for (bm = mem.mem_bank; bm != NULL; bm = cm) {
538 		cm = bm->bm_next;
539 		fmd_fmri_free(bm, sizeof (mem_bank_map_t));
540 	}
541 	for (gm = mem.mem_group; gm != NULL; gm = hm) {
542 		hm = gm->mg_next;
543 		fmd_fmri_free(gm, sizeof (mem_grp_t));
544 	}
545 	for (sm = mem.mem_seg; sm != NULL; sm = tm) {
546 		tm = sm->sm_next;
547 		fmd_fmri_free(sm, sizeof (mem_seg_map_t));
548 	}
549 }
550