xref: /illumos-gate/usr/src/cmd/fm/schemes/mem/mem_unum.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
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 /*
28  * Copyright 2019 Peter Tribble.
29  */
30 
31 #include <mem.h>
32 #include <fm/fmd_fmri.h>
33 #include <fm/libtopo.h>
34 
35 #include <string.h>
36 #include <strings.h>
37 #include <ctype.h>
38 
39 #define	ISHCUNUM(unum) (strncmp(unum, "hc:/", 4) == 0)
40 
41 /*
42  * Given a DIMM or bank unum, mem_unum_burst will break it apart into individual
43  * DIMM names.  If it's a DIMM, one name will be returned.  If it's a bank, the
44  * unums for the individual DIMMs will be returned.
45  *
46  * Plain J-number DIMM and bank unums are simple.  J DIMMs have one J number.  J
47  * banks have multiple whitespace-separated J numbers.
48  *
49  * The others are more complex, and consist of a common portion c, a colon, and
50  * a DIMM-specific portion d.  DIMMs are of the form "c: d", while banks are of
51  * the form "c: d d ...".  The patterns are designed to handle the complex case,
52  * but also handle the simple ones as an afterthought.  bd_pat is used to
53  * match specific styles of unum.  In bd_pat, the first %n indicates the end of
54  * the common portion ("c" above).  The second %n marks the beginning of the
55  * repetitive portion ("d" above).  The third %n is used to determine whether or
56  * not the entire pattern matched.  bd_reppat is used to match instances of the
57  * repetitive part.
58  *
59  * sscanf is your disturbingly powerful friend.
60  *
61  * The "bd_subst" element of the bank_dimm structure was added for Ontario
62  * in order to accommodate its bank string names.  Previously, to convert
63  * from a bank representation <common piece> <dimm1> <dimm2> ...
64  * we concatenated the common piece with each dimm-specific piece in turn,
65  * possibly deleting some characters in between.  Ontario is the first
66  * platform which requires that characters be substituted (like a vi s/1/2/)
67  * in place of characters deleted.  "bd_subst" represents the character(s)
68  * to be substituted between the common piece and each dimm-specific piece
69  * as part of the bursting.  For prior platforms, this value is skipped.
70  *
71  * Example:
72  * input: "MB/CMP0/CH3: R1/D0/J1901 R1/D1/J2001"
73  * outputs: "MB/CMP0/CH3/R1/D0/J1901", "MB/CMP0/CH3/R1/D1/J2001"
74  */
75 
76 typedef struct bank_dimm {
77 	const char *bd_pat;
78 	const char *bd_reppat;
79 	const char *bd_subst;
80 } bank_dimm_t;
81 
82 static const bank_dimm_t bank_dimm[] = {
83 	{ "%n%nJ%*4d%n",			" J%*4d%n" },
84 	{ "MB/P%*d/%nB%*d:%n%n",		" B%*d/D%*d%n" },
85 	{ "MB/P%*d/%nB%*d/D%*d:%n%n",		" B%*d/D%*d%n" },
86 	{ "C%*d/P%*d/%nB%*d:%n%n",		" B%*d/D%*d%n" },
87 	{ "C%*d/P%*d/%nB%*d/D%*d:%n%n",		" B%*d/D%*d%n" },
88 	{ "Slot %*c: %n%nJ%*4d%n",		" J%*4d%n" },
89 	{ "%n%nDIMM%*d%n",			" DIMM%*d%n" },
90 	{ "MB/%nDIMM%*d MB/DIMM%*d: %n%n",	" DIMM%*d%n" },
91 	{ "MB/%nDIMM%*d:%n%n",			" DIMM%*d%n" },
92 	{ "MB/CMP%*d/CH%*d%n:%n%n",		" R%*d/D%*d/J%*4d%n",	"/" },
93 	{ "MB/CMP%*d/CH%*d%n%n%n",		"/R%*d/D%*d/J%*4d%n" },
94 	{ "MB/C%*d/P%*d/%nB%*d:%n%n",		" B%*d/D%*d%n" },
95 	{ "MB/C%*d/P%*d/%nB%*d/D%*d:%n%n",	" B%*d/D%*d%n" },
96 	{ "/MBU_A/MEMB%*d/%n%nMEM%*d%*1c%n",	" MEM%*d%*1c%n" },
97 	{ "/MBU_B/MEMB%*d/%n%nMEM%*d%*1c%n",	" MEM%*d%*1c%n" },
98 	{ "/MBU_A/%n%nMEM%*d%*1c%n",		" MEM%*d%*1c%n" },
99 	{ "/CMU%*2d/%n%nMEM%*2d%*1c%n",		" MEM%*2d%*1c%n" },
100 	{ "MB/CMP%*d/BR%*d%n:%n%n",		" CH%*d/D%*d/J%*4d%n", "/" },
101 	{ "%n%nMB/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n",
102 	    "MB/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n" },
103 	{ "%n%nMB/CMP%*d/BR%*d/CH%*d/D%*d%n", "MB/CMP%*d/BR%*d/CH%*d/D%*d%n" },
104 	{ "MB/CPU%*d/CMP%*d/BR%*d%n:%n%n",	" CH%*d/D%*d/J%*4d%n", "/"},
105 	{ "MB/MEM%*d/CMP%*d/BR%*d%n:%n%n",	" CH%*d/D%*d/J%*4d%n", "/"},
106 	{ "%n%nMB/MEM%*d/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n",
107 	    "MB/MEM%*d/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n" },
108 	{ "%n%nMB/CPU%*d/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n",
109 	    "MB/CPU%*d/CMP%*d/BR%*d/CH%*d/D%*d/J%*4d%n" },
110 	{ "%n%nMB/MEM%*d/CMP%*d/BR%*d/CH%*d/D%*d%n",
111 	    "MB/MEM%*d/CMP%*d/BR%*d/CH%*d/D%*d%n"  },
112 	{ "%n%nMB/CPU%*d/CMP%*d/BR%*d/CH%*d/D%*d%n",
113 	    "MB/CPU%*d/CMP%*d/BR%*d/CH%*d/D%*d%n"  },
114 	{ NULL }
115 };
116 
117 /*
118  * Burst Serengeti-style unums.
119  * A DIMM unum string is expected to be in this form:
120  * "[/N0/]SB12/P0/B0/D2 [J13500]"
121  * A bank unum string is expected to be in this form:
122  * "[/N0/]SB12/P0/B0 [J13500, ...]"
123  */
124 static int
125 mem_unum_burst_sgsc(const char *pat, char ***dimmsp, size_t *ndimmsp)
126 {
127 	char buf[64];
128 	char **dimms;
129 	char *base;
130 	const char *c;
131 	char *copy;
132 	size_t copysz;
133 	int i;
134 
135 	/*
136 	 * No expansion is required for a DIMM unum
137 	 */
138 	if (strchr(pat, 'D') != NULL) {
139 		dimms = fmd_fmri_alloc(sizeof (char *));
140 		dimms[0] = fmd_fmri_strdup(pat);
141 		*dimmsp = dimms;
142 		*ndimmsp = 1;
143 		return (0);
144 	}
145 
146 	/*
147 	 * strtok is destructive so we need to work with
148 	 * a copy and keep track of the size allocated.
149 	 */
150 	copysz = strlen(pat) + 1;
151 	copy = fmd_fmri_alloc(copysz);
152 	(void) strcpy(copy, pat);
153 
154 	base = strtok(copy, " ");
155 
156 	/* There are four DIMMs in a bank */
157 	dimms = fmd_fmri_alloc(sizeof (char *) * 4);
158 
159 	for (i = 0; i < 4; i++) {
160 		(void) snprintf(buf, sizeof (buf), "%s/D%d", base, i);
161 
162 		if ((c = strtok(NULL, " ")) != NULL) {
163 			size_t len = strlen(buf);
164 
165 			(void) snprintf(buf + len, sizeof (buf) - len,
166 			    " %s", c);
167 		}
168 
169 		dimms[i] = fmd_fmri_strdup(buf);
170 	}
171 
172 	fmd_fmri_free(copy, copysz);
173 
174 	*dimmsp = dimms;
175 	*ndimmsp = 4;
176 	return (0);
177 }
178 
179 
180 /*
181  * Returns 0 (with dimmsp and ndimmsp set) if the unum could be bursted, -1
182  * otherwise.
183  */
184 static int
185 mem_unum_burst_pattern(const char *pat, char ***dimmsp, size_t *ndimmsp)
186 {
187 	const bank_dimm_t *bd;
188 	char **dimms = NULL, **newdimms;
189 	size_t ndimms = 0;
190 	const char *c;
191 
192 
193 	for (bd = bank_dimm; bd->bd_pat != NULL; bd++) {
194 		int replace, start, matched;
195 		char dimmname[64];
196 
197 		replace = start = matched = -1;
198 		(void) sscanf(pat, bd->bd_pat, &replace, &start, &matched);
199 		if (matched == -1)
200 			continue;
201 		(void) strlcpy(dimmname, pat, sizeof (dimmname));
202 		if (bd->bd_subst != NULL) {
203 			(void) strlcpy(dimmname+replace, bd->bd_subst,
204 			    sizeof (dimmname) - strlen(bd->bd_subst));
205 			replace += strlen(bd->bd_subst);
206 		}
207 
208 		c = pat + start;
209 		while (*c != '\0') {
210 			int dimmlen = -1;
211 
212 			(void) sscanf(c, bd->bd_reppat, &dimmlen);
213 			if (dimmlen == -1)
214 				break;
215 
216 			while (*c == ' ') {
217 				c++;
218 				dimmlen--;
219 			}
220 
221 			if (dimmlen > sizeof (dimmname) - replace)
222 				break;
223 
224 			(void) strlcpy(dimmname + replace, c, dimmlen + 1);
225 
226 			newdimms = fmd_fmri_alloc(sizeof (char *) *
227 			    (ndimms + 1));
228 			if (ndimms != 0) {
229 				bcopy(dimms, newdimms, sizeof (char *) *
230 				    ndimms);
231 				fmd_fmri_free(dimms, sizeof (char *) * ndimms);
232 			}
233 			newdimms[ndimms++] = fmd_fmri_strdup(dimmname);
234 			dimms = newdimms;
235 
236 			c += dimmlen;
237 
238 			if (*c != ' ' && *c != '\0')
239 				break;
240 		}
241 
242 		if (*c != '\0')
243 			break;
244 
245 		*dimmsp = dimms;
246 		*ndimmsp = ndimms;
247 
248 		return (0);
249 	}
250 
251 	mem_strarray_free(dimms, ndimms);
252 
253 	/*
254 	 * Set errno to ENOTSUP and return -1. This allows support for DIMMs
255 	 * with unknown unum strings and/or serial numbers. The only consumer
256 	 * of mem_unum_burst_pattern() that cares/checks for the returned
257 	 * errno is fmd_fmri_expand().
258 	 */
259 	return (fmd_fmri_set_errno(ENOTSUP));
260 }
261 
262 int
263 mem_unum_burst(const char *pat, char ***dimmsp, size_t *ndimmsp)
264 {
265 	const char *platform = fmd_fmri_get_platform();
266 
267 	/*
268 	 * Call mem_unum_burst_sgsc() for Serengeti and
269 	 * Lightweight 8 platforms.  Call mem_unum_burst_pattern()
270 	 * for all other platforms.
271 	 */
272 	if (strcmp(platform, "SUNW,Sun-Fire") == 0 ||
273 	    strcmp(platform, "SUNW,Netra-T12") == 0)
274 		return (mem_unum_burst_sgsc(pat, dimmsp, ndimmsp));
275 	else
276 		return (mem_unum_burst_pattern(pat, dimmsp, ndimmsp));
277 }
278 
279 /*
280  * The unum containership operation is designed to tell the caller whether a
281  * given FMRI contains another.  In the case of this plugin, we tell the caller
282  * whether a given memory FMRI (usually a bank) contains another (usually a
283  * DIMM).  We do this in one of two ways, depending on the platform.  For most
284  * platforms, we can use the bursting routine to generate the list of member
285  * unums from the container unum.  Membership can then be determined by
286  * searching the bursted list for the containee's unum.
287  *
288  * Some platforms, however, cannot be bursted, as their bank unums do not
289  * contain all of the information needed to generate the complete list of
290  * member DIMM unums.  For these unums, we must make do with a substring
291  * comparison.
292  */
293 
294 static int
295 unum_contains_bypat(const char *erunum, const char *eeunum)
296 {
297 	char **ernms, **eenms;
298 	size_t nernms, neenms;
299 	int i, j, rv = 1;
300 
301 	if (mem_unum_burst(erunum, &ernms, &nernms) < 0)
302 		return (fmd_fmri_set_errno(EINVAL));
303 	if (mem_unum_burst(eeunum, &eenms, &neenms) < 0) {
304 		mem_strarray_free(ernms, nernms);
305 		return (fmd_fmri_set_errno(EINVAL));
306 	}
307 
308 	for (i = 0; i < neenms; i++) {
309 		for (j = 0; j < nernms; j++) {
310 			if (strcmp(eenms[i], ernms[j]) == 0)
311 				break;
312 		}
313 
314 		if (j == nernms) {
315 			/*
316 			 * This DIMM was not found in the container.
317 			 */
318 			rv = 0;
319 			break;
320 		}
321 	}
322 
323 	mem_strarray_free(ernms, nernms);
324 	mem_strarray_free(eenms, neenms);
325 
326 	return (rv);
327 }
328 
329 static int
330 unum_strip_one_jnum(const char *unum, uint_t *endp)
331 {
332 	char *c;
333 	int i;
334 
335 	if ((c = strrchr(unum, 'J')) == NULL)
336 		return (0);
337 
338 	while (c > unum && isspace(c[-1]))
339 		c--;
340 
341 	(void) sscanf(c, " J%*[0-9] %n", &i);
342 	if (i == 0 || (uintptr_t)(c - unum) + i != strlen(unum))
343 		return (0);
344 
345 	*endp = (uint_t)(c - unum);
346 	return (1);
347 }
348 
349 
350 static int
351 unum_contains_bysubstr(const char *erunum, const char *eeunum)
352 {
353 	uint_t erlen, eelen;
354 	int nojnumstrip = 0;
355 
356 	/*
357 	 * This comparison method is only known to work on specific types of
358 	 * unums.  Check for those types here.
359 	 */
360 	if ((strncmp(erunum, "/N", 2) != 0 && strncmp(erunum, "/IO", 3) != 0 &&
361 	    strncmp(erunum, "/SB", 3) != 0) ||
362 	    (strncmp(eeunum, "/N", 2) != 0 && strncmp(eeunum, "/IO", 3) != 0 &&
363 	    strncmp(eeunum, "/SB", 3) != 0)) {
364 		if (ISHCUNUM(erunum) && ISHCUNUM(eeunum)) {
365 			nojnumstrip = 1;
366 			erlen = strlen(erunum);
367 			eelen = strlen(eeunum);
368 		} else {
369 			return (fmd_fmri_set_errno(EINVAL));
370 		}
371 	}
372 
373 	if (!nojnumstrip) {
374 		erlen = unum_strip_one_jnum(erunum, &erlen) ?
375 		    erlen : strlen(erunum);
376 		eelen = unum_strip_one_jnum(eeunum, &eelen) ?
377 		    eelen : strlen(eeunum);
378 	}
379 
380 	return (strncmp(erunum, eeunum, MIN(erlen, eelen)) == 0);
381 }
382 
383 typedef int unum_cmptor_f(const char *, const char *);
384 
385 static unum_cmptor_f *const unum_cmptors[] = {
386 	unum_contains_bypat,
387 	unum_contains_bysubstr
388 };
389 
390 int
391 mem_unum_contains(const char *erunum, const char *eeunum)
392 {
393 	static int cmptor = 0;
394 	int rc;
395 
396 	while (isspace(*erunum))
397 		erunum++;
398 	while (isspace(*eeunum))
399 		eeunum++;
400 
401 	if ((rc = unum_cmptors[cmptor](erunum, eeunum)) >= 0)
402 		return (rc);
403 
404 	if ((rc = unum_cmptors[cmptor == 0](erunum, eeunum)) >= 0) {
405 		/*
406 		 * We succeeded with the non-default comparator.  Change the
407 		 * default so we use the correct one next time.
408 		 */
409 		cmptor = (cmptor == 0);
410 	}
411 
412 	return (rc);
413 }
414 
415 /*
416  * If an asru has a unum string that is an hc path string then return
417  * a new nvl (to be freed by the caller) that is a duplicate of the
418  * original but with an additional member of a reconstituted hc fmri.
419  */
420 int
421 mem_unum_rewrite(nvlist_t *nvl, nvlist_t **rnvl)
422 {
423 	int err;
424 	char *unumstr;
425 	nvlist_t *unum;
426 	struct topo_hdl *thp;
427 
428 	if (nvlist_lookup_string(nvl, FM_FMRI_MEM_UNUM, &unumstr) != 0 ||
429 	    !ISHCUNUM(unumstr))
430 		return (0);
431 
432 	if ((thp = fmd_fmri_topo_hold(TOPO_VERSION)) == NULL)
433 		return (EINVAL);
434 
435 	if (topo_fmri_str2nvl(thp, unumstr, &unum, &err) != 0) {
436 		fmd_fmri_topo_rele(thp);
437 		return (EINVAL);
438 	}
439 
440 	fmd_fmri_topo_rele(thp);
441 
442 	if ((err = nvlist_dup(nvl, rnvl, 0)) != 0) {
443 		nvlist_free(unum);
444 		return (err);
445 	}
446 
447 	err = nvlist_add_nvlist(*rnvl, FM_FMRI_MEM_UNUM "-fmri", unum);
448 	nvlist_free(unum);
449 
450 	if (err != 0)
451 		nvlist_free(*rnvl);
452 
453 	return (err);
454 }
455