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 <stdio.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <libnvpair.h>
35 #include <sys/types.h>
36 #include <fm/topo_mod.h>
37 
38 #define	BUFSZ	128
39 
40 static char *
41 get_fmtstr(topo_mod_t *mod, nvlist_t *in)
42 {
43 	char *fmtstr;
44 	nvlist_t *args;
45 	int ret;
46 
47 	topo_mod_dprintf(mod, "get_fmtstr() called\n");
48 
49 	if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
50 		topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
51 		    strerror(ret));
52 		(void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
53 		return (NULL);
54 	}
55 	if ((ret = nvlist_lookup_string(args, "format", &fmtstr)) != 0) {
56 		topo_mod_dprintf(mod, "Failed to lookup 'format' arg (%s)\n",
57 		    strerror(ret));
58 		(void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
59 		return (NULL);
60 	}
61 	return (fmtstr);
62 }
63 
64 static int
65 store_prop_val(topo_mod_t *mod, char *buf, char *propname, nvlist_t **out)
66 {
67 	if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) != 0) {
68 		topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n");
69 		return (topo_mod_seterrno(mod, EMOD_NOMEM));
70 	}
71 	if (nvlist_add_string(*out, TOPO_PROP_VAL_NAME, propname) != 0) {
72 		topo_mod_dprintf(mod, "Failed to set '%s'\n",
73 		    TOPO_PROP_VAL_NAME);
74 		nvlist_free(*out);
75 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
76 	}
77 	if (nvlist_add_uint32(*out, TOPO_PROP_VAL_TYPE, TOPO_TYPE_STRING)
78 	    != 0) {
79 		topo_mod_dprintf(mod, "Failed to set '%s'\n",
80 		    TOPO_PROP_VAL_TYPE);
81 		nvlist_free(*out);
82 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
83 	}
84 	if (nvlist_add_string(*out, TOPO_PROP_VAL_VAL, buf) != 0) {
85 		topo_mod_dprintf(mod, "Failed to set '%s'\n",
86 		    TOPO_PROP_VAL_VAL);
87 		nvlist_free(*out);
88 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
89 	}
90 	return (0);
91 }
92 
93 /*
94  * This is a somewhat generic property method for labelling the dimm slots on
95  * uni-socket x86/x64 platforms.  This method assumes a direct linear
96  * correlation between the dimm topo node instance number and the dimm slot
97  * label number.  It takes the following two arguments:
98  *
99  * format:	a string containing a printf-like format with a single %d token
100  *              which this method computes
101  *
102  *              i.e.: DIMM %d
103  *
104  * offset:      a numeric offset that we'll number the dimms from.  This is to
105  *              allow for the fact that some systems number the dimm slots
106  *              from zero and others start from one (like the Ultra 20)
107  */
108 /* ARGSUSED */
109 int
110 simple_dimm_label(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
111     nvlist_t *in, nvlist_t **out)
112 {
113 	char *fmtstr, buf[BUFSZ];
114 	int ret;
115 	uint32_t offset;
116 	nvlist_t *args;
117 
118 	topo_mod_dprintf(mod, "simple_dimm_label() called\n");
119 	if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
120 		topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
121 		    strerror(ret));
122 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
123 	}
124 	if ((ret = nvlist_lookup_uint32(args, "offset", &offset)) != 0) {
125 		topo_mod_dprintf(mod, "Failed to lookup 'offset' arg (%s)\n",
126 		    strerror(ret));
127 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
128 	}
129 
130 	if ((fmtstr = get_fmtstr(mod, in)) == NULL) {
131 		topo_mod_dprintf(mod, "Failed to retrieve 'format' arg\n");
132 		/* topo errno already set */
133 		return (-1);
134 	}
135 
136 	/* LINTED: E_SEC_PRINTF_VAR_FMT */
137 	(void) snprintf(buf, BUFSZ, fmtstr,
138 	    (topo_node_instance(node) + offset));
139 
140 	if (store_prop_val(mod, buf, "label", out) != 0) {
141 		topo_mod_dprintf(mod, "Failed to set label\n");
142 		/* topo errno already set */
143 		return (-1);
144 	}
145 
146 	return (0);
147 }
148 
149 
150 /*
151  * This is a somewhat generic property method for labelling the dimm slots on
152  * multi-socket x86/x64 platforms.  It takes the following two arguments:
153  *
154  * format:	a string containing a printf-like format with a two %d tokens
155  *              for the cpu and dimm slot label numbers, which this method
156  *              computes
157  *
158  *              i.e.: CPU %d DIMM %d
159  *
160  * offset:      a numeric offset that we'll number the dimms from.  This is to
161  *              allow for the fact that some systems number the dimm slots
162  *              from zero while others may start from one
163  *
164  * order:	"reverse" or "forward" - sets the direction of the correlation
165  *              between dimm topo node instance number and DIMM slot number
166  *
167  * dimms_per_chip:  the number of DIMM slots per chip
168  */
169 /* ARGSUSED */
170 int
171 simple_dimm_label_mp(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
172     nvlist_t *in, nvlist_t **out)
173 {
174 	char *fmtstr, *order, buf[BUFSZ];
175 	tnode_t *chip;
176 	int ret;
177 	uint32_t offset, dimms_per_chip;
178 	nvlist_t *args;
179 
180 	topo_mod_dprintf(mod, "simple_dimm_label_mp() called\n");
181 
182 	if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
183 		topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
184 		    strerror(ret));
185 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
186 	}
187 	if ((ret = nvlist_lookup_uint32(args, "offset", &offset)) != 0) {
188 		topo_mod_dprintf(mod, "Failed to lookup 'offset' arg (%s)\n",
189 		    strerror(ret));
190 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
191 	}
192 	if ((ret = nvlist_lookup_uint32(args, "dimms_per_chip",
193 	    &dimms_per_chip)) != 0) {
194 		topo_mod_dprintf(mod, "Failed to lookup 'dimms_per_chip' arg "
195 		    "(%s)\n", strerror(ret));
196 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
197 	}
198 	if ((ret = nvlist_lookup_string(args, "order", &order)) != 0) {
199 		topo_mod_dprintf(mod, "Failed to lookup 'order' arg (%s)\n",
200 		    strerror(ret));
201 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
202 	}
203 	if ((fmtstr = get_fmtstr(mod, in)) == NULL) {
204 		topo_mod_dprintf(mod, "Failed to retrieve 'format' arg\n");
205 		topo_mod_free(mod, order, BUFSZ);
206 		/* topo errno already set */
207 		return (-1);
208 	}
209 
210 	chip = topo_node_parent(topo_node_parent(node));
211 
212 	if (strcasecmp(order, "forward") == 0)
213 		/* LINTED: E_SEC_PRINTF_VAR_FMT */
214 		(void) snprintf(buf, BUFSZ, fmtstr, topo_node_instance(chip),
215 		    (topo_node_instance(node) + offset));
216 	else if (strcasecmp(order, "reverse") == 0)
217 		/* LINTED: E_SEC_PRINTF_VAR_FMT */
218 		(void) snprintf(buf, BUFSZ, fmtstr, topo_node_instance(chip),
219 		    (((topo_node_instance(chip) + 1) * dimms_per_chip)
220 		    - (topo_node_instance(node)) - 1 + offset));
221 	else {
222 		topo_mod_dprintf(mod, "Invalid value for order arg\n");
223 		topo_mod_free(mod, order, BUFSZ);
224 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
225 	}
226 
227 	if (store_prop_val(mod, buf, "label", out) != 0) {
228 		topo_mod_dprintf(mod, "Failed to set label\n");
229 		topo_mod_free(mod, order, BUFSZ);
230 		/* topo errno already set */
231 		return (-1);
232 	}
233 
234 	return (0);
235 }
236 
237 /*
238  * This method assumes a correspondence between the dimm topo node instance
239  * number and the dimm slot label number, but unlike simple_chip_label_mp, the
240  * slot numbers aren't reused between CPU's.  This method assumes there
241  * are 4 DIMM slots per chip.  It takes the following two arguments:
242  *
243  * format:	a string containing a printf-like format with a single %d token
244  *              which this method computes
245  *
246  *              i.e.: DIMM %d
247  *
248  * offset:      a numeric offset that we'll number the dimms from.  This is to
249  *              allow for the fact that some systems number the dimm slots
250  *              from zero and others may start from one
251  *
252  * order:	"reverse" or "forward" - sets the direction of the correlation
253  *              between dimm topo node instance number and DIMM slot number
254  */
255 /* ARGSUSED */
256 int
257 seq_dimm_label(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
258     nvlist_t *in, nvlist_t **out)
259 {
260 	char *fmtstr, *order, buf[BUFSZ];
261 	int ret;
262 	uint32_t offset;
263 	nvlist_t *args;
264 	tnode_t *chip;
265 
266 	topo_mod_dprintf(mod, "seq_dimm_label() called\n");
267 	if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
268 		topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
269 		    strerror(ret));
270 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
271 	}
272 	if ((ret = nvlist_lookup_uint32(args, "offset", &offset)) != 0) {
273 		topo_mod_dprintf(mod, "Failed to lookup 'offset' arg (%s)\n",
274 		    strerror(ret));
275 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
276 	}
277 	if ((ret = nvlist_lookup_string(args, "order", &order)) != 0) {
278 		topo_mod_dprintf(mod, "Failed to lookup 'order' arg (%s)\n",
279 		    strerror(ret));
280 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
281 	}
282 
283 	if ((fmtstr = get_fmtstr(mod, in)) == NULL) {
284 		topo_mod_dprintf(mod, "Failed to retrieve 'format' arg\n");
285 		topo_mod_free(mod, order, BUFSZ);
286 		/* topo errno already set */
287 		return (-1);
288 	}
289 
290 	chip = topo_node_parent(topo_node_parent(node));
291 
292 	if (strcasecmp(order, "forward") == 0)
293 		/* LINTED: E_SEC_PRINTF_VAR_FMT */
294 		(void) snprintf(buf, BUFSZ, fmtstr, ((topo_node_instance(node))
295 		    + (topo_node_instance(chip) * 4) + offset));
296 	else if (strcasecmp(order, "reverse") == 0)
297 		/* LINTED: E_SEC_PRINTF_VAR_FMT */
298 		(void) snprintf(buf, BUFSZ, fmtstr,
299 		    (((topo_node_instance(chip) + 1) * 4)
300 		    - (topo_node_instance(node)) - 1 + offset));
301 	else {
302 		topo_mod_dprintf(mod, "Invalid value for order arg\n");
303 		topo_mod_free(mod, order, BUFSZ);
304 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
305 	}
306 
307 	if (store_prop_val(mod, buf, "label", out) != 0) {
308 		topo_mod_dprintf(mod, "Failed to set label\n");
309 		topo_mod_free(mod, order, BUFSZ);
310 		/* topo errno already set */
311 		return (-1);
312 	}
313 
314 	return (0);
315 }
316 
317 
318 /*
319  * This is a somewhat generic property method for labelling the CPU sockets on
320  * x86/x64 platforms.  This method assumes a correspondence between
321  * the chip topo node instance number and the CPU socket label number.  It takes
322  * the following two arguments:
323  *
324  * format:	a string containing a printf-like format with a single %d token
325  *              which this method computes
326  *
327  *              i.e.: CPU %d
328  *
329  * offset:      a numeric offset that we'll number the CPU's from.  This is to
330  *              allow for the fact that some systems number the CPU sockets
331  *              from zero and others start from one (like the X4X00-M2 systems)
332  */
333 /* ARGSUSED */
334 int
335 simple_chip_label(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
336     nvlist_t *in, nvlist_t **out)
337 {
338 	char *fmtstr, buf[BUFSZ];
339 	int ret;
340 	uint32_t offset;
341 	nvlist_t *args;
342 
343 	topo_mod_dprintf(mod, "simple_chip_label() called\n");
344 	if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
345 		topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
346 		    strerror(ret));
347 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
348 	}
349 	if ((ret = nvlist_lookup_uint32(args, "offset", &offset)) != 0) {
350 		topo_mod_dprintf(mod, "Failed to lookup 'offset' arg (%s)\n",
351 		    strerror(ret));
352 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
353 	}
354 
355 	if ((fmtstr = get_fmtstr(mod, in)) == NULL) {
356 		topo_mod_dprintf(mod, "Failed to retrieve 'format' arg\n");
357 		/* topo errno already set */
358 		return (-1);
359 	}
360 
361 	/* LINTED: E_SEC_PRINTF_VAR_FMT */
362 	(void) snprintf(buf, BUFSZ, fmtstr,
363 	    (topo_node_instance(node) + offset));
364 
365 	if (store_prop_val(mod, buf, "label", out) != 0) {
366 		topo_mod_dprintf(mod, "Failed to set label\n");
367 		/* topo errno already set */
368 		return (-1);
369 	}
370 
371 	return (0);
372 }
373 
374 
375 /*
376  * This is a custom property method for generating the CPU slot label for the
377  * Galaxy 4E/4F platforms.
378  *
379  * format:	a string containing a printf-like format with a single %c token
380  *              which this method computes
381  *
382  *              i.e.: CPU %c
383  */
384 /* ARGSUSED */
385 int
386 g4_chip_label(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
387     nvlist_t *in, nvlist_t **out)
388 {
389 	char *fmtstr, buf[BUFSZ], slot_id;
390 	int err, htid, mapidx;
391 	uint32_t num_nodes;
392 	/*
393 	 * G4 HT node ID to FRU label translation.  The g4map array
394 	 * is indexed by (number of coherent nodes) / 2 - 1.
395 	 * The value for a given number of nodes is a char array
396 	 * indexed by node ID.
397 	 */
398 	const char *g4map[] = {
399 	    "AB",	/* 2 nodes */
400 	    "ADEH",	/* 4 nodes */
401 	    "ABDEFH",	/* 6 nodes */
402 	    "ACBDEFGH"	/* 8 nodes */
403 	};
404 
405 	topo_mod_dprintf(mod, "g4_chip_label() called\n");
406 	if ((fmtstr = get_fmtstr(mod, in)) == NULL) {
407 		topo_mod_dprintf(mod, "Failed to retrieve 'format' arg\n");
408 		/* topo errno already set */
409 		return (-1);
410 	}
411 	/*
412 	 * The chip-properties property will not exist if this platform has
413 	 * AMD family 0x10 modules.  In that case we don't want to treat it as a
414 	 * fatal error as that will cause calls like topo_prop_getprops to fail
415 	 * to return any properties on this node.  Therefore, if the topo errno
416 	 * is set to ETOPO_PROP_NOENT, then we'll just set an empty label
417 	 * and return 0.  If the topo errno is set to anything else we'll
418 	 * return -1.
419 	 */
420 	if (topo_prop_get_uint32(node, "chip-properties", "CoherentNodes",
421 	    &num_nodes, &err) != 0) {
422 		if (err == ETOPO_PROP_NOENT) {
423 			if (store_prop_val(mod, "", "label", out) != 0) {
424 				topo_mod_dprintf(mod, "Failed to set label\n");
425 				/* topo errno already set */
426 				return (-1);
427 			}
428 			return (0);
429 		} else {
430 			topo_mod_dprintf(mod, "Failed to lookup 'CoherentNodes'"
431 			    "property\n");
432 			return (topo_mod_seterrno(mod, err));
433 		}
434 	}
435 
436 	mapidx = num_nodes / 2 - 1;
437 	htid = topo_node_instance(node);
438 
439 	/* HT nodes must number 0 .. num_nodes - 1 */
440 	if (htid >= num_nodes) {
441 		topo_mod_dprintf(mod, "chip node instance range check failed:"
442 		    "num_nodes=%d, instance=%d\n", num_nodes, htid);
443 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
444 	}
445 
446 	switch (num_nodes) {
447 		case (2):
448 		case (4):
449 		case (6):
450 		case (8):
451 			/* htid is already range-checked */
452 			mapidx = num_nodes / 2 - 1;
453 			slot_id = g4map[mapidx][htid];
454 			break;
455 		default:
456 			topo_mod_dprintf(mod, "Invalid number of CoherentNodes:"
457 			    " %d\n", num_nodes);
458 			return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
459 	}
460 
461 	/* LINTED: E_SEC_PRINTF_VAR_FMT */
462 	(void) snprintf(buf, BUFSZ, fmtstr, slot_id);
463 
464 	if (store_prop_val(mod, buf, "label", out) != 0) {
465 		topo_mod_dprintf(mod, "Failed to set label\n");
466 		/* topo errno already set */
467 		return (-1);
468 	}
469 
470 	return (0);
471 }
472 
473 /*
474  * This is a somewhat generic property method for labelling the chip-select
475  * nodes on multi-socket AMD family 0x10 platforms.  This is necessary because
476  * these platforms are not supported by the current AMD memory controller driver
477  * and therefore we're not able to discover the memory topology on AMD family
478  * 0x10 systems.  As a result, instead of enumerating the installed dimms and
479  * their ranks, the chip enumerator generically enumerates all of the possible
480  * chip-selects beneath each dram channel.
481  *
482  * When we diagnose a dimm fault, the FRU fmri will be for the chip-select node,
483  * so we need to attach FRU labels to the chip-select nodes.
484  *
485  * format:	a string containing a printf-like format with a two %d tokens
486  *              for the cpu and dimm slot label numbers, which this method
487  *              computes
488  *
489  *              i.e.: CPU %d DIMM %d
490  *
491  * offset:      a numeric offset that we'll number the dimms from.  This is to
492  *              allow for the fact that some systems may number the dimm slots
493  *              from zero while others may start from one
494  *
495  * This function computes the DIMM slot number using the following formula:
496  *
497  * 	slot = cs - (cs % 2) + channel + offset
498  */
499 /* ARGSUSED */
500 int
501 simple_cs_label_mp(topo_mod_t *mod, tnode_t *node, topo_version_t vers,
502     nvlist_t *in, nvlist_t **out)
503 {
504 	char *fmtstr, buf[BUFSZ];
505 	tnode_t *chip, *chan;
506 	int dimm_num, ret;
507 	uint32_t offset;
508 	nvlist_t *args;
509 
510 	topo_mod_dprintf(mod, "simple_cs_label_mp() called\n");
511 
512 	if ((ret = nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args)) != 0) {
513 		topo_mod_dprintf(mod, "Failed to lookup 'args' list (%s)\n",
514 		    strerror(ret));
515 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
516 	}
517 	if ((ret = nvlist_lookup_uint32(args, "offset", &offset)) != 0) {
518 		topo_mod_dprintf(mod, "Failed to lookup 'offset' arg (%s)\n",
519 		    strerror(ret));
520 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
521 	}
522 	if ((fmtstr = get_fmtstr(mod, in)) == NULL) {
523 		topo_mod_dprintf(mod, "Failed to retrieve 'format' arg\n");
524 		/* topo errno already set */
525 		return (-1);
526 	}
527 
528 	chip = topo_node_parent(topo_node_parent(topo_node_parent(node)));
529 	chan = topo_node_parent(node);
530 
531 	dimm_num = topo_node_instance(node) - (topo_node_instance(node) % 2)
532 	    + topo_node_instance(chan) + offset;
533 	/* LINTED: E_SEC_PRINTF_VAR_FMT */
534 	(void) snprintf(buf, BUFSZ, fmtstr, topo_node_instance(chip),
535 	    dimm_num);
536 
537 	if (store_prop_val(mod, buf, "label", out) != 0) {
538 		topo_mod_dprintf(mod, "Failed to set label\n");
539 		/* topo errno already set */
540 		return (-1);
541 	}
542 
543 	return (0);
544 }
545