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