mem.c revision 6434:db66e522ebd7
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>
38ldom_hdl_t *mem_scheme_lhp;
39#else
40#include <fm/libtopo.h>
41#endif	/* sparc */
42
43mem_t mem;
44
45static int
46mem_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
61ssize_t
62fmd_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
136int
137fmd_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
175static int
176serids_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
192int
193fmd_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
285int
286fmd_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 */
316int
317fmd_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
380int
381fmd_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
389void
390fmd_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