xref: /illumos-gate/usr/src/cmd/fm/schemes/mem/mem.c (revision 9db67a327daf1243e630c20b81978ffd2a7baad7)
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 #endif /* sparc */
208 
209 	if (mem_fmri_get_unum(nvl, &unum) < 0)
210 		return (-1); /* errno is set for us */
211 
212 #ifdef sparc
213 	/*
214 	 * If the mem-scheme topology exports this method present(), invoke it.
215 	 */
216 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
217 		return (fmd_fmri_set_errno(EINVAL));
218 	rc = topo_fmri_present(thp, nvl, &err);
219 	fmd_fmri_topo_rele(thp);
220 	if (err != ETOPO_METHOD_NOTSUP)
221 		return (rc);
222 
223 	if (nvlist_lookup_string_array(nvl, FM_FMRI_MEM_SERIAL_ID, &nvlserids,
224 	    &nnvlserids) != 0) {
225 		/*
226 		 * Some mem scheme FMRIs don't have serial ids because
227 		 * either the platform does not support them, or because
228 		 * the FMRI was created before support for serial ids was
229 		 * introduced.  If this is the case, assume it is there.
230 		 */
231 		if (mem.mem_dm == NULL)
232 			return (1);
233 		else
234 			return (fmd_fmri_set_errno(EINVAL));
235 	}
236 
237 	if (mem_get_serids_by_unum(unum, &serids, &nserids) < 0) {
238 		if (errno == ENOTSUP)
239 			return (1); /* assume it's there, no s/n support here */
240 		if (errno != ENOENT) {
241 			/*
242 			 * Errors are only signalled to the caller if they're
243 			 * the caller's fault.  This isn't - it's a failure on
244 			 * our part to burst or read the serial numbers.  We'll
245 			 * whine about it, and tell the caller the named
246 			 * module(s) isn't/aren't there.
247 			 */
248 			fmd_fmri_warn("failed to retrieve serial number for "
249 			    "unum %s", unum);
250 		}
251 		return (0);
252 	}
253 
254 	rc = serids_eq(serids, nserids, nvlserids, nnvlserids);
255 
256 	mem_strarray_free(serids, nserids);
257 #else
258 	/*
259 	 * On X86 we will invoke the topo is_present method passing in the
260 	 * unum, which is in hc scheme.  The libtopo hc-scheme is_present method
261 	 * will invoke the node-specific is_present method, which is implemented
262 	 * by the chip enumerator for rank nodes.  The rank node's is_present
263 	 * method will compare the serial number in the unum with the current
264 	 * serial to determine if the same DIMM is present.
265 	 */
266 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL) {
267 		fmd_fmri_warn("failed to get handle to topology");
268 		return (-1);
269 	}
270 	if (topo_fmri_str2nvl(thp, unum, &unum_nvl, &err) == 0) {
271 		rc = topo_fmri_present(thp, unum_nvl, &err);
272 		nvlist_free(unum_nvl);
273 	} else
274 		rc = fmd_fmri_set_errno(EINVAL);
275 	fmd_fmri_topo_rele(thp);
276 
277 #endif	/* sparc */
278 	return (rc);
279 }
280 
281 int
282 fmd_fmri_contains(nvlist_t *er, nvlist_t *ee)
283 {
284 	int rc, err = 0;
285 	struct topo_hdl *thp;
286 	char *erunum, *eeunum;
287 	uint64_t erval = 0, eeval = 0;
288 
289 	/*
290 	 * If the mem-scheme topology exports this method contains(), invoke it.
291 	 */
292 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
293 		return (fmd_fmri_set_errno(EINVAL));
294 	rc = topo_fmri_contains(thp, er, ee, &err);
295 	fmd_fmri_topo_rele(thp);
296 	if (err != ETOPO_METHOD_NOTSUP)
297 		return (rc);
298 
299 	if (mem_fmri_get_unum(er, &erunum) < 0 ||
300 	    mem_fmri_get_unum(ee, &eeunum) < 0)
301 		return (-1); /* errno is set for us */
302 
303 	if (mem_unum_contains(erunum, eeunum) <= 0)
304 		return (0); /* can't parse/match, so assume no containment */
305 
306 	if (nvlist_lookup_uint64(er, FM_FMRI_MEM_OFFSET, &erval) == 0) {
307 		return (nvlist_lookup_uint64(ee,
308 		    FM_FMRI_MEM_OFFSET, &eeval) == 0 && erval == eeval);
309 	}
310 
311 	if (nvlist_lookup_uint64(er, FM_FMRI_MEM_PHYSADDR, &erval) == 0) {
312 		return (nvlist_lookup_uint64(ee,
313 		    FM_FMRI_MEM_PHYSADDR, &eeval) == 0 && erval == eeval);
314 	}
315 
316 	return (1);
317 }
318 
319 /*
320  * We can only make a usable/unusable determination for pages.  Mem FMRIs
321  * without page addresses will be reported as usable since Solaris has no
322  * way at present to dynamically disable an entire DIMM or DIMM pair.
323  */
324 int
325 fmd_fmri_unusable(nvlist_t *nvl)
326 {
327 	uint64_t val;
328 	uint8_t version;
329 	int rc, err1 = 0, err2;
330 	nvlist_t *nvlcp = NULL;
331 	int retval;
332 	topo_hdl_t *thp;
333 
334 	if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 ||
335 	    version > FM_MEM_SCHEME_VERSION)
336 		return (fmd_fmri_set_errno(EINVAL));
337 
338 	/*
339 	 * If the mem-scheme topology exports this method unusable(), invoke it.
340 	 */
341 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
342 		return (fmd_fmri_set_errno(EINVAL));
343 	rc = topo_fmri_unusable(thp, nvl, &err1);
344 	fmd_fmri_topo_rele(thp);
345 	if (err1 != ETOPO_METHOD_NOTSUP)
346 		return (rc);
347 
348 	err1 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_OFFSET, &val);
349 	err2 = nvlist_lookup_uint64(nvl, FM_FMRI_MEM_PHYSADDR, &val);
350 
351 	if (err1 == ENOENT && err2 == ENOENT)
352 		return (0); /* no page, so assume it's still usable */
353 
354 	if ((err1 != 0 && err1 != ENOENT) || (err2 != 0 && err2 != ENOENT))
355 		return (fmd_fmri_set_errno(EINVAL));
356 
357 	if ((err1 = mem_unum_rewrite(nvl, &nvlcp)) != 0)
358 		return (fmd_fmri_set_errno(err1));
359 
360 	/*
361 	 * Ask the kernel if the page is retired, using either the rewritten
362 	 * hc FMRI or the original mem FMRI with the specified offset or PA.
363 	 * Refer to the kernel's page_retire_check() for the error codes.
364 	 */
365 	rc = mem_page_cmd(MEM_PAGE_FMRI_ISRETIRED, nvlcp ? nvlcp : nvl);
366 
367 	if (rc == -1 && errno == EIO) {
368 		/*
369 		 * The page is not retired and is not scheduled for retirement
370 		 * (i.e. no request pending and has not seen any errors)
371 		 */
372 		retval = 0;
373 	} else if (rc == 0 || errno == EAGAIN || errno == EINVAL) {
374 		/*
375 		 * The page has been retired, is in the process of being
376 		 * retired, or doesn't exist.  The latter is valid if the page
377 		 * existed in the past but has been DR'd out.
378 		 */
379 		retval = 1;
380 	} else {
381 		/*
382 		 * Errors are only signalled to the caller if they're the
383 		 * caller's fault.  This isn't - it's a failure of the
384 		 * retirement-check code.  We'll whine about it and tell
385 		 * the caller the page is unusable.
386 		 */
387 		fmd_fmri_warn("failed to determine page %s=%llx usability: "
388 		    "rc=%d errno=%d\n", err1 == 0 ? FM_FMRI_MEM_OFFSET :
389 		    FM_FMRI_MEM_PHYSADDR, (u_longlong_t)val, rc, errno);
390 		retval = 1;
391 	}
392 
393 	if (nvlcp)
394 		nvlist_free(nvlcp);
395 
396 	return (retval);
397 }
398 
399 int
400 fmd_fmri_init(void)
401 {
402 	return (mem_discover());
403 }
404 
405 void
406 fmd_fmri_fini(void)
407 {
408 	mem_dimm_map_t *dm, *em;
409 	mem_bank_map_t *bm, *cm;
410 	mem_grp_t *gm, *hm;
411 	mem_seg_map_t *sm, *tm;
412 
413 	for (dm = mem.mem_dm; dm != NULL; dm = em) {
414 		em = dm->dm_next;
415 		fmd_fmri_strfree(dm->dm_label);
416 		fmd_fmri_strfree(dm->dm_part);
417 		fmd_fmri_strfree(dm->dm_device);
418 		fmd_fmri_free(dm, sizeof (mem_dimm_map_t));
419 	}
420 	for (bm = mem.mem_bank; bm != NULL; bm = cm) {
421 		cm = bm->bm_next;
422 		fmd_fmri_free(bm, sizeof (mem_bank_map_t));
423 	}
424 	for (gm = mem.mem_group; gm != NULL; gm = hm) {
425 		hm = gm->mg_next;
426 		fmd_fmri_free(gm, sizeof (mem_grp_t));
427 	}
428 	for (sm = mem.mem_seg; sm != NULL; sm = tm) {
429 		tm = sm->sm_next;
430 		fmd_fmri_free(sm, sizeof (mem_seg_map_t));
431 	}
432 }
433