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