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 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright (c) 2018, Joyent, Inc.
26  */
27 
28 /*
29  * Support function for the i86pc chip enumerator
30  */
31 
32 #include <sys/types.h>
33 #include <stdarg.h>
34 #include <strings.h>
35 #include <fm/fmd_fmri.h>
36 #include <sys/systeminfo.h>
37 #include <sys/fm/protocol.h>
38 #include <fm/topo_mod.h>
39 #include <fm/fmd_agent.h>
40 
41 #include "chip.h"
42 
43 static void fmri_dprint(topo_mod_t *, const char *, uint32_t, nvlist_t *);
44 static boolean_t is_page_fmri(nvlist_t *);
45 
46 /*
47  * Whinge a debug message via topo_mod_dprintf and increment the
48  * given error counter.
49  */
50 void
51 whinge(topo_mod_t *mod, int *nerr, const char *fmt, ...)
52 {
53 	va_list ap;
54 	char buf[160];
55 
56 	if (nerr != NULL)
57 		++*nerr;
58 
59 	va_start(ap, fmt);
60 	(void) vsnprintf(buf, sizeof (buf), fmt, ap);
61 	va_end(ap);
62 
63 	topo_mod_dprintf(mod, "%s", buf);
64 }
65 
66 /*
67  * Given an nvpair of a limited number of data types, extract the property
68  * name and value and add that combination to the given node in the
69  * specified property group using the corresponding topo_prop_set_* function
70  * for the data type.  Return 1 on success, otherwise 0.
71  */
72 int
73 nvprop_add(topo_mod_t *mod, nvpair_t *nvp, const char *pgname, tnode_t *node)
74 {
75 	int success = 0;
76 	int err;
77 	char *pname = nvpair_name(nvp);
78 
79 	switch (nvpair_type(nvp)) {
80 	case DATA_TYPE_BOOLEAN_VALUE: {
81 		boolean_t val;
82 
83 		if (nvpair_value_boolean_value(nvp, &val) == 0 &&
84 		    topo_prop_set_string(node, pgname, pname,
85 		    TOPO_PROP_IMMUTABLE, val ? "true" : "false", &err) == 0)
86 			success = 1;
87 		break;
88 	}
89 
90 	case DATA_TYPE_UINT32: {
91 		uint32_t val;
92 
93 		if (nvpair_value_uint32(nvp, &val) == 0 &&
94 		    topo_prop_set_uint32(node, pgname, pname,
95 		    TOPO_PROP_IMMUTABLE, val, &err) == 0)
96 			success = 1;
97 		break;
98 	}
99 
100 	case DATA_TYPE_UINT64: {
101 		uint64_t val;
102 
103 		if (nvpair_value_uint64(nvp, &val) == 0 &&
104 		    topo_prop_set_uint64(node, pgname, pname,
105 		    TOPO_PROP_IMMUTABLE, val, &err) == 0)
106 			success = 1;
107 		break;
108 	}
109 
110 	case DATA_TYPE_UINT32_ARRAY: {
111 		uint32_t *arrp;
112 		uint_t nelem;
113 
114 		if (nvpair_value_uint32_array(nvp, &arrp, &nelem) == 0 &&
115 		    nelem > 0 && topo_prop_set_uint32_array(node, pgname, pname,
116 		    TOPO_PROP_IMMUTABLE, arrp, nelem, &err) == 0)
117 			success = 1;
118 		break;
119 	}
120 
121 	case DATA_TYPE_STRING: {
122 		char *str;
123 
124 		if (nvpair_value_string(nvp, &str) == 0 &&
125 		    topo_prop_set_string(node, pgname, pname,
126 		    TOPO_PROP_IMMUTABLE, str, &err) == 0)
127 			success = 1;
128 		break;
129 	}
130 
131 	default:
132 		whinge(mod, &err, "nvprop_add: Can't handle type %d for "
133 		    "'%s' in property group %s of %s node\n",
134 		    nvpair_type(nvp), pname, pgname, topo_node_name(node));
135 		break;
136 	}
137 
138 	return (success ? 0 : 1);
139 }
140 
141 /*
142  * Lookup string data named pname in the given nvlist and add that
143  * as property named pname in the given property group pgname on the indicated
144  * topo node.  Fill pvalp with a pointer to the string value, valid until
145  * nvlist_free is called.
146  */
147 int
148 add_nvlist_strprop(topo_mod_t *mod, tnode_t *node, nvlist_t *nvl,
149     const char *pgname, const char *pname, const char **pvalp)
150 {
151 	char *pval;
152 	int err = 0;
153 
154 	if (nvlist_lookup_string(nvl, pname, &pval) != 0)
155 		return (-1);
156 
157 	if (topo_prop_set_string(node, pgname, pname,
158 	    TOPO_PROP_IMMUTABLE, pval, &err) == 0) {
159 		if (pvalp)
160 			*pvalp = pval;
161 		return (0);
162 	} else {
163 		whinge(mod, &err, "add_nvlist_strprop: failed to add '%s'\n",
164 		    pname);
165 		return (-1);
166 	}
167 }
168 
169 /*
170  * Lookup an int32 item named pname in the given nvlist and add that
171  * as property named pname in the given property group pgname on the indicated
172  * topo node.  Fill pvalp with the property value.
173  */
174 int
175 add_nvlist_longprop(topo_mod_t *mod, tnode_t *node, nvlist_t *nvl,
176     const char *pgname, const char *pname, int32_t *pvalp)
177 {
178 	int32_t pval;
179 	int err;
180 
181 	if ((nvlist_lookup_int32(nvl, pname, &pval)) != 0)
182 		return (-1);
183 
184 	if (topo_prop_set_int32(node, pgname, pname,
185 	    TOPO_PROP_IMMUTABLE, pval, &err) == 0) {
186 		if (pvalp)
187 			*pvalp = pval;
188 		return (0);
189 	} else {
190 		whinge(mod, &err, "add_nvlist_longprop: failed to add '%s'\n",
191 		    pname);
192 		return (-1);
193 	}
194 }
195 
196 /*
197  * In a given nvlist lookup a variable number of int32 properties named in
198  * const char * varargs and each each in the given property group on the
199  * node.  Fill an array of the retrieved values.
200  */
201 int
202 add_nvlist_longprops(topo_mod_t *mod, tnode_t *node, nvlist_t *nvl,
203     const char *pgname, int32_t *pvalap, ...)
204 {
205 	const char *pname;
206 	va_list ap;
207 	int nerr = 0;
208 
209 	va_start(ap, pvalap);
210 	while ((pname = va_arg(ap, const char *)) != NULL) {
211 		if (add_nvlist_longprop(mod, node, nvl, pgname, pname,
212 		    pvalap) != 0)
213 			nerr++;		/* have whinged elsewhere */
214 
215 		if (pvalap != NULL)
216 			++pvalap;
217 	}
218 	va_end(ap);
219 
220 	return (nerr == 0 ? 0 : -1);
221 }
222 
223 /*
224  * Construct an hc scheme resource FMRI for a node named name with
225  * instance number inst, parented by the given parent node pnode.
226  */
227 int
228 mkrsrc(topo_mod_t *mod, tnode_t *pnode, const char *name, int inst,
229     nvlist_t *auth, nvlist_t **nvl)
230 {
231 	*nvl = topo_mod_hcfmri(mod, pnode, FM_HC_SCHEME_VERSION, name,
232 	    inst, NULL, auth, NULL, NULL, NULL);
233 	return (nvl != NULL ? 0 : -1);	/* caller must free nvlist */
234 }
235 
236 /*
237  * Construct a cpu scheme FMRI with the given data; the caller must free
238  * the allocated nvlist with nvlist_free().
239  */
240 nvlist_t *
241 cpu_fmri_create(topo_mod_t *mod, uint32_t cpuid, char *s, uint8_t cpumask)
242 {
243 	int err;
244 	nvlist_t *asru;
245 
246 	if (topo_mod_nvalloc(mod, &asru, NV_UNIQUE_NAME) != 0)
247 		return (NULL);
248 
249 	err = nvlist_add_uint8(asru, FM_VERSION, FM_CPU_SCHEME_VERSION);
250 	err |= nvlist_add_string(asru, FM_FMRI_SCHEME, FM_FMRI_SCHEME_CPU);
251 	err |= nvlist_add_uint32(asru, FM_FMRI_CPU_ID, cpuid);
252 	err |= nvlist_add_uint8(asru, FM_FMRI_CPU_MASK, cpumask);
253 	if (s != NULL)
254 		err |= nvlist_add_string(asru, FM_FMRI_CPU_SERIAL_ID, s);
255 	if (err != 0) {
256 		nvlist_free(asru);
257 		(void) topo_mod_seterrno(mod, EMOD_FMRI_NVL);
258 		return (NULL);
259 	}
260 
261 	return (asru);
262 }
263 
264 /*ARGSUSED*/
265 int
266 mem_asru_compute(topo_mod_t *mod, tnode_t *node, topo_version_t version,
267     nvlist_t *in, nvlist_t **out)
268 {
269 	nvlist_t *asru, *args, *pargs, *hcsp;
270 	int err;
271 	uint64_t pa, offset;
272 
273 	if (strcmp(topo_node_name(node), RANK_NODE_NAME) != 0 &&
274 	    strcmp(topo_node_name(node), DIMM_NODE_NAME) != 0 &&
275 	    strcmp(topo_node_name(node), CS_NODE_NAME) != 0)
276 		return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
277 
278 	if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0)
279 		return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
280 
281 	if ((err = nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs)) != 0) {
282 		if (err == ENOENT) {
283 			pargs = args;
284 		} else {
285 			return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
286 		}
287 	}
288 
289 	if (topo_mod_nvdup(mod, pargs, &asru) != 0)
290 		return (topo_mod_seterrno(mod, EMOD_NOMEM));
291 
292 	err = 0;
293 
294 	/*
295 	 * if 'in' includes an hc-specific member which specifies asru-physaddr
296 	 * or asru-offset then rename them to asru and physaddr respectively.
297 	 */
298 	if (nvlist_lookup_nvlist(asru, FM_FMRI_HC_SPECIFIC, &hcsp) == 0) {
299 		if (nvlist_lookup_uint64(hcsp,
300 		    "asru-"FM_FMRI_HC_SPECIFIC_PHYSADDR, &pa) == 0) {
301 			err += nvlist_remove(hcsp,
302 			    "asru-"FM_FMRI_HC_SPECIFIC_PHYSADDR,
303 			    DATA_TYPE_UINT64);
304 			err += nvlist_add_uint64(hcsp,
305 			    FM_FMRI_HC_SPECIFIC_PHYSADDR,
306 			    pa);
307 		}
308 
309 		if (nvlist_lookup_uint64(hcsp,
310 		    "asru-"FM_FMRI_HC_SPECIFIC_OFFSET, &offset) == 0) {
311 			err += nvlist_remove(hcsp,
312 			    "asru-"FM_FMRI_HC_SPECIFIC_OFFSET,
313 			    DATA_TYPE_UINT64);
314 			err += nvlist_add_uint64(hcsp,
315 			    FM_FMRI_HC_SPECIFIC_OFFSET,
316 			    offset);
317 		}
318 	}
319 
320 	if (err != 0 || topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) < 0) {
321 		nvlist_free(asru);
322 		return (topo_mod_seterrno(mod, EMOD_NOMEM));
323 	}
324 
325 	err = nvlist_add_string(*out, TOPO_PROP_VAL_NAME, TOPO_PROP_ASRU);
326 	err |= nvlist_add_uint32(*out, TOPO_PROP_VAL_TYPE, TOPO_TYPE_FMRI);
327 	err |= nvlist_add_nvlist(*out, TOPO_PROP_VAL_VAL, asru);
328 	if (err != 0) {
329 		nvlist_free(asru);
330 		nvlist_free(*out);
331 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
332 	}
333 
334 	nvlist_free(asru);
335 
336 	return (0);
337 }
338 
339 static int
340 set_retnvl(topo_mod_t *mod, nvlist_t **out, const char *retname, uint32_t ret)
341 {
342 	nvlist_t *nvl;
343 
344 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) < 0)
345 		return (topo_mod_seterrno(mod, EMOD_NOMEM));
346 
347 	if (nvlist_add_uint32(nvl, retname, ret) != 0) {
348 		nvlist_free(nvl);
349 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
350 	}
351 
352 	*out = nvl;
353 	return (0);
354 }
355 
356 /*
357  * If we're getting called then the question of whether this dimm is plugged
358  * in has already been answered.  What we don't know for sure is whether it's
359  * the same dimm or a different one plugged in the same slot.  To check, we
360  * try and compare the serial numbers on the dimm in the current topology with
361  * the serial num from the unum fmri that got passed into this function as the
362  * argument.
363  *
364  */
365 static int
366 fmri_replaced(topo_mod_t *mod, tnode_t *node, nvlist_t *unum, int *errp)
367 {
368 	tnode_t *dimmnode;
369 	nvlist_t *resource;
370 	int rc, err;
371 	char *old_serial, *curr_serial;
372 	fmd_agent_hdl_t *hdl;
373 
374 	/*
375 	 * If input is a page, return "replaced" if the offset is invalid.
376 	 */
377 	if (is_page_fmri(unum) &&
378 	    (hdl = fmd_agent_open(FMD_AGENT_VERSION)) != NULL) {
379 		rc = fmd_agent_page_isretired(hdl, unum);
380 		err = fmd_agent_errno(hdl);
381 		fmd_agent_close(hdl);
382 
383 		if (rc == FMD_AGENT_RETIRE_DONE &&
384 		    err == EINVAL)
385 			return (FMD_OBJ_STATE_NOT_PRESENT);
386 	}
387 
388 	/*
389 	 * If a serial number for the dimm was available at the time of the
390 	 * fault, it will have been added as a string to the unum nvlist
391 	 */
392 	if (nvlist_lookup_string(unum, FM_FMRI_HC_SERIAL_ID, &old_serial))
393 		return (FMD_OBJ_STATE_UNKNOWN);
394 
395 	/*
396 	 * If the current serial number is available for the DIMM that this rank
397 	 * belongs to, it will be accessible as a property on the parent (dimm)
398 	 * node. If there is a serial id in the resource fmri, then use that.
399 	 * Otherwise fall back to looking for a serial id property in the
400 	 * protocol group.
401 	 */
402 	dimmnode = topo_node_parent(node);
403 	if (topo_node_resource(dimmnode, &resource, &err) != -1) {
404 		if (nvlist_lookup_string(resource, FM_FMRI_HC_SERIAL_ID,
405 		    &curr_serial) == 0) {
406 			if (strcmp(old_serial, curr_serial) != 0) {
407 				nvlist_free(resource);
408 				return (FMD_OBJ_STATE_REPLACED);
409 			} else {
410 				nvlist_free(resource);
411 				return (FMD_OBJ_STATE_STILL_PRESENT);
412 			}
413 		}
414 		nvlist_free(resource);
415 	}
416 	if (topo_prop_get_string(dimmnode, TOPO_PGROUP_PROTOCOL,
417 	    FM_FMRI_HC_SERIAL_ID, &curr_serial, &err) != 0) {
418 		if (err == ETOPO_PROP_NOENT) {
419 			return (FMD_OBJ_STATE_UNKNOWN);
420 		} else {
421 			*errp = EMOD_NVL_INVAL;
422 			whinge(mod, NULL, "rank_fmri_present: Unexpected "
423 			    "error retrieving serial from node");
424 			return (-1);
425 		}
426 	}
427 
428 	if (strcmp(old_serial, curr_serial) != 0) {
429 		topo_mod_strfree(mod, curr_serial);
430 		return (FMD_OBJ_STATE_REPLACED);
431 	}
432 
433 	topo_mod_strfree(mod, curr_serial);
434 
435 	return (FMD_OBJ_STATE_STILL_PRESENT);
436 }
437 
438 /*
439  * In the event we encounter problems comparing serials or if a comparison isn't
440  * possible, we err on the side of caution and set is_present to TRUE.
441  */
442 int
443 rank_fmri_present(topo_mod_t *mod, tnode_t *node, topo_version_t version,
444     nvlist_t *in, nvlist_t **out)
445 {
446 	int is_present, err;
447 
448 	if (version > TOPO_METH_PRESENT_VERSION)
449 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
450 
451 	switch (fmri_replaced(mod, node, in, &err)) {
452 	case FMD_OBJ_STATE_REPLACED:
453 	case FMD_OBJ_STATE_NOT_PRESENT:
454 		is_present = 0;
455 		break;
456 
457 	case FMD_OBJ_STATE_UNKNOWN:
458 	case FMD_OBJ_STATE_STILL_PRESENT:
459 		is_present = 1;
460 		break;
461 
462 	default:
463 		return (topo_mod_seterrno(mod,  err));
464 	}
465 
466 	fmri_dprint(mod, "rank_fmri_present", is_present, in);
467 
468 	return (set_retnvl(mod, out, TOPO_METH_PRESENT_RET, is_present));
469 }
470 
471 int
472 rank_fmri_replaced(topo_mod_t *mod, tnode_t *node, topo_version_t version,
473     nvlist_t *in, nvlist_t **out)
474 {
475 	int is_replaced, err;
476 
477 	if (version > TOPO_METH_REPLACED_VERSION)
478 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
479 
480 	is_replaced = fmri_replaced(mod, node, in, &err);
481 	if (is_replaced == -1)
482 		return (topo_mod_seterrno(mod,  err));
483 
484 	fmri_dprint(mod, "rank_fmri_replaced", is_replaced, in);
485 
486 	return (set_retnvl(mod, out, TOPO_METH_REPLACED_RET, is_replaced));
487 }
488 
489 static void
490 fmri_dprint(topo_mod_t *mod, const char *op, uint32_t rc, nvlist_t *fmri)
491 {
492 	char *fmristr;
493 	const char *status;
494 
495 	if (getenv("TOPOCHIPDBG") == NULL)
496 		return;
497 
498 	switch (rc) {
499 	case FMD_AGENT_RETIRE_DONE:
500 		status = "sync success";
501 		break;
502 	case FMD_AGENT_RETIRE_ASYNC:
503 		status = "async retiring";
504 		break;
505 	case FMD_AGENT_RETIRE_FAIL:
506 		status = "not retired";
507 		break;
508 	default:
509 		status = "unknown status";
510 	}
511 	if (fmri != NULL && topo_mod_nvl2str(mod, fmri, &fmristr) == 0) {
512 		topo_mod_dprintf(mod, "[%s]: %s => %d (\"%s\")\n", fmristr,
513 		    op, rc, status);
514 		topo_mod_strfree(mod, fmristr);
515 	}
516 }
517 
518 struct strand_walk_data {
519 	tnode_t		*parent;
520 	fmd_agent_hdl_t	*hdl;
521 	int		(*func)(fmd_agent_hdl_t *, int, int, int);
522 	int		err;
523 	int		done;
524 	int		fail;
525 	int		async;
526 };
527 
528 static int
529 strand_walker(topo_mod_t *mod, tnode_t *node, void *pdata)
530 {
531 	struct strand_walk_data *swdp = pdata;
532 	int32_t chipid, coreid, strandid;
533 	int err, rc;
534 
535 	/*
536 	 * Terminate the walk if we reach start-node's sibling
537 	 */
538 	if (node != swdp->parent &&
539 	    topo_node_parent(node) == topo_node_parent(swdp->parent))
540 		return (TOPO_WALK_TERMINATE);
541 
542 	if (strcmp(topo_node_name(node), STRAND) != 0)
543 		return (TOPO_WALK_NEXT);
544 
545 	if (topo_prop_get_int32(node, PGNAME(STRAND), STRAND_CHIP_ID,
546 	    &chipid, &err) < 0 ||
547 	    topo_prop_get_int32(node, PGNAME(STRAND), STRAND_CORE_ID,
548 	    &coreid, &err) < 0) {
549 		swdp->err++;
550 		return (TOPO_WALK_NEXT);
551 	}
552 	strandid = topo_node_instance(node);
553 	rc = swdp->func(swdp->hdl, chipid, coreid, strandid);
554 
555 	if (rc == FMD_AGENT_RETIRE_DONE)
556 		swdp->done++;
557 	else if (rc == FMD_AGENT_RETIRE_FAIL)
558 		swdp->fail++;
559 	else if (rc == FMD_AGENT_RETIRE_ASYNC)
560 		swdp->async++;
561 	else
562 		swdp->err++;
563 
564 	if (getenv("TOPOCHIPDBG") != NULL) {
565 		const char *op;
566 
567 		if (swdp->func == fmd_agent_cpu_retire)
568 			op = "retire";
569 		else if (swdp->func == fmd_agent_cpu_unretire)
570 			op = "unretire";
571 		else if (swdp->func == fmd_agent_cpu_isretired)
572 			op = "check status";
573 		else
574 			op = "unknown op";
575 
576 		topo_mod_dprintf(mod, "%s cpu (%d:%d:%d): rc = %d, err = %s\n",
577 		    op, (int)chipid, (int)coreid, (int)strandid, rc,
578 		    fmd_agent_errmsg(swdp->hdl));
579 	}
580 
581 	return (TOPO_WALK_NEXT);
582 }
583 
584 static int
585 walk_strands(topo_mod_t *mod, struct strand_walk_data *swdp, tnode_t *parent,
586     int (*func)(fmd_agent_hdl_t *, int, int, int))
587 {
588 	topo_walk_t *twp;
589 	int err;
590 
591 	swdp->parent = parent;
592 	swdp->func = func;
593 	swdp->err = swdp->done = swdp->fail = swdp->async = 0;
594 	if ((swdp->hdl = fmd_agent_open(FMD_AGENT_VERSION)) == NULL) {
595 		swdp->fail++;
596 		return (0);
597 	}
598 
599 	twp = topo_mod_walk_init(mod, parent, strand_walker, swdp, &err);
600 	if (twp == NULL) {
601 		fmd_agent_close(swdp->hdl);
602 		return (-1);
603 	}
604 
605 	err = topo_walk_step(twp, TOPO_WALK_CHILD);
606 	topo_walk_fini(twp);
607 	fmd_agent_close(swdp->hdl);
608 
609 	if (err == TOPO_WALK_ERR || swdp->err > 0)
610 		return (-1);
611 
612 	return (0);
613 }
614 
615 /* ARGSUSED */
616 int
617 retire_strands(topo_mod_t *mod, tnode_t *node, topo_version_t version,
618     nvlist_t *in, nvlist_t **out)
619 {
620 	struct strand_walk_data swd;
621 	uint32_t rc;
622 
623 	if (version > TOPO_METH_RETIRE_VERSION)
624 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
625 
626 	if (walk_strands(mod, &swd, node, fmd_agent_cpu_retire) == -1)
627 		return (-1);
628 
629 	if (swd.fail > 0)
630 		rc = FMD_AGENT_RETIRE_FAIL;
631 	else if (swd.async > 0)
632 		rc = FMD_AGENT_RETIRE_ASYNC;
633 	else
634 		rc = FMD_AGENT_RETIRE_DONE;
635 
636 	return (set_retnvl(mod, out, TOPO_METH_RETIRE_RET, rc));
637 }
638 
639 /* ARGSUSED */
640 int
641 unretire_strands(topo_mod_t *mod, tnode_t *node, topo_version_t version,
642     nvlist_t *in, nvlist_t **out)
643 {
644 	struct strand_walk_data swd;
645 	uint32_t rc;
646 
647 	if (version > TOPO_METH_UNRETIRE_VERSION)
648 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
649 
650 	if (walk_strands(mod, &swd, node, fmd_agent_cpu_unretire) == -1)
651 		return (-1);
652 
653 	if (swd.fail > 0)
654 		rc = FMD_AGENT_RETIRE_FAIL;
655 	else if (swd.async > 0)
656 		rc = FMD_AGENT_RETIRE_ASYNC;
657 	else
658 		rc = FMD_AGENT_RETIRE_DONE;
659 
660 	return (set_retnvl(mod, out, TOPO_METH_UNRETIRE_RET, rc));
661 }
662 
663 /* ARGSUSED */
664 int
665 service_state_strands(topo_mod_t *mod, tnode_t *node, topo_version_t version,
666     nvlist_t *in, nvlist_t **out)
667 {
668 	struct strand_walk_data swd;
669 	uint32_t rc;
670 
671 	if (version > TOPO_METH_SERVICE_STATE_VERSION)
672 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
673 
674 	if (walk_strands(mod, &swd, node, fmd_agent_cpu_isretired) == -1)
675 		return (-1);
676 
677 	if (swd.done > 0)
678 		rc = (swd.fail + swd.async > 0) ? FMD_SERVICE_STATE_DEGRADED :
679 		    FMD_SERVICE_STATE_UNUSABLE;
680 	else if (swd.async > 0)
681 		rc = FMD_SERVICE_STATE_ISOLATE_PENDING;
682 	else if (swd.fail > 0)
683 		rc = FMD_SERVICE_STATE_OK;
684 	else
685 		rc = FMD_SERVICE_STATE_UNKNOWN;
686 
687 	return (set_retnvl(mod, out, TOPO_METH_SERVICE_STATE_RET, rc));
688 }
689 
690 /* ARGSUSED */
691 int
692 unusable_strands(topo_mod_t *mod, tnode_t *node, topo_version_t version,
693     nvlist_t *in, nvlist_t **out)
694 {
695 	struct strand_walk_data swd;
696 	uint32_t rc;
697 
698 	if (version > TOPO_METH_UNUSABLE_VERSION)
699 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
700 
701 	if (walk_strands(mod, &swd, node, fmd_agent_cpu_isretired) == -1)
702 		return (-1);
703 
704 	rc = (swd.fail + swd.async > 0 || swd.done == 0) ? 0 : 1;
705 
706 	return (set_retnvl(mod, out, TOPO_METH_UNUSABLE_RET, rc));
707 }
708 
709 static boolean_t
710 is_page_fmri(nvlist_t *nvl)
711 {
712 	nvlist_t *hcsp;
713 	uint64_t val;
714 
715 	if (nvlist_lookup_nvlist(nvl, FM_FMRI_HC_SPECIFIC, &hcsp) == 0 &&
716 	    (nvlist_lookup_uint64(hcsp, FM_FMRI_HC_SPECIFIC_OFFSET,
717 	    &val) == 0 ||
718 	    nvlist_lookup_uint64(hcsp, "asru-" FM_FMRI_HC_SPECIFIC_OFFSET,
719 	    &val) == 0 ||
720 	    nvlist_lookup_uint64(hcsp, FM_FMRI_HC_SPECIFIC_PHYSADDR,
721 	    &val) == 0 ||
722 	    nvlist_lookup_uint64(hcsp, "asru-" FM_FMRI_HC_SPECIFIC_PHYSADDR,
723 	    &val) == 0))
724 		return (B_TRUE);
725 
726 	return (B_FALSE);
727 }
728 
729 /* ARGSUSED */
730 int
731 ntv_page_retire(topo_mod_t *mod, tnode_t *node, topo_version_t version,
732     nvlist_t *in, nvlist_t **out)
733 {
734 	fmd_agent_hdl_t *hdl;
735 	uint32_t rc = FMD_AGENT_RETIRE_FAIL;
736 
737 	if (version > TOPO_METH_RETIRE_VERSION)
738 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
739 	if (is_page_fmri(in)) {
740 		if ((hdl = fmd_agent_open(FMD_AGENT_VERSION)) != NULL) {
741 			rc = fmd_agent_page_retire(hdl, in);
742 			fmd_agent_close(hdl);
743 		}
744 	}
745 	fmri_dprint(mod, "ntv_page_retire", rc, in);
746 	return (set_retnvl(mod, out, TOPO_METH_RETIRE_RET, rc));
747 }
748 
749 /* ARGSUSED */
750 int
751 ntv_page_unretire(topo_mod_t *mod, tnode_t *node, topo_version_t version,
752     nvlist_t *in, nvlist_t **out)
753 {
754 	fmd_agent_hdl_t *hdl;
755 	uint32_t rc = FMD_AGENT_RETIRE_FAIL;
756 
757 	if (version > TOPO_METH_UNRETIRE_VERSION)
758 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
759 	if (is_page_fmri(in)) {
760 		if ((hdl = fmd_agent_open(FMD_AGENT_VERSION)) != NULL) {
761 			rc = fmd_agent_page_unretire(hdl, in);
762 			fmd_agent_close(hdl);
763 		}
764 	}
765 	fmri_dprint(mod, "ntv_page_unretire", rc, in);
766 	return (set_retnvl(mod, out, TOPO_METH_UNRETIRE_RET, rc));
767 }
768 
769 /* ARGSUSED */
770 int
771 ntv_page_service_state(topo_mod_t *mod, tnode_t *node, topo_version_t version,
772     nvlist_t *in, nvlist_t **out)
773 {
774 	fmd_agent_hdl_t *hdl;
775 	uint32_t rc = FMD_SERVICE_STATE_UNKNOWN;
776 
777 	if (version > TOPO_METH_SERVICE_STATE_VERSION)
778 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
779 	if (is_page_fmri(in)) {
780 		if ((hdl = fmd_agent_open(FMD_AGENT_VERSION)) != NULL) {
781 			rc = fmd_agent_page_isretired(hdl, in);
782 			fmd_agent_close(hdl);
783 			if (rc == FMD_AGENT_RETIRE_DONE)
784 				rc = FMD_SERVICE_STATE_UNUSABLE;
785 			else if (rc == FMD_AGENT_RETIRE_FAIL)
786 				rc = FMD_SERVICE_STATE_OK;
787 			else if (rc == FMD_AGENT_RETIRE_ASYNC)
788 				rc = FMD_SERVICE_STATE_ISOLATE_PENDING;
789 		}
790 	}
791 
792 	topo_mod_dprintf(mod, "ntv_page_service_state: rc = %u\n", rc);
793 	return (set_retnvl(mod, out, TOPO_METH_SERVICE_STATE_RET, rc));
794 }
795 
796 /* ARGSUSED */
797 int
798 ntv_page_unusable(topo_mod_t *mod, tnode_t *node, topo_version_t version,
799     nvlist_t *in, nvlist_t **out)
800 {
801 	fmd_agent_hdl_t *hdl;
802 	uint32_t rc = FMD_AGENT_RETIRE_FAIL;
803 
804 	if (version > TOPO_METH_UNUSABLE_VERSION)
805 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
806 	if (is_page_fmri(in)) {
807 		if ((hdl = fmd_agent_open(FMD_AGENT_VERSION)) != NULL) {
808 			rc = fmd_agent_page_isretired(hdl, in);
809 			fmd_agent_close(hdl);
810 		}
811 	}
812 	topo_mod_dprintf(mod, "ntv_page_unusable: rc = %u\n", rc);
813 	return (set_retnvl(mod, out, TOPO_METH_UNUSABLE_RET,
814 	    rc == FMD_AGENT_RETIRE_DONE ? 1 : 0));
815 }
816 
817 /*
818  * Determine whether or not we believe a chip has been replaced. While it's
819  * tempting to just do a straight up comparison of the FMRI and its serial
820  * number, things are not that straightforward.
821  *
822  * The presence of a serial number on the CPU is not always guaranteed. It is
823  * possible that systems firmware can hide the information required to generate
824  * a synthesized serial number or that it is strictly not present. As such, we
825  * will only declare something replaced when both the old and current resource
826  * have a serial number present. If it is missing for whatever reason, then we
827  * cannot assume anything about a replacement having occurred.
828  *
829  * This logic applies regardless of whether or not we have an FM-aware SMBIOS.
830  */
831 int
832 chip_fmri_replaced(topo_mod_t *mod, tnode_t *node, topo_version_t version,
833     nvlist_t *in, nvlist_t **out)
834 {
835 	nvlist_t *rsrc = NULL;
836 	int err, ret;
837 	char *old_serial, *new_serial;
838 
839 	if (version > TOPO_METH_REPLACED_VERSION)
840 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
841 
842 	if (topo_node_resource(node, &rsrc, &err) == -1) {
843 		return (topo_mod_seterrno(mod, err));
844 	}
845 
846 	if (nvlist_lookup_string(rsrc, FM_FMRI_HC_SERIAL_ID,
847 	    &new_serial) != 0) {
848 		ret = FMD_OBJ_STATE_UNKNOWN;
849 		goto out;
850 	}
851 
852 	if (nvlist_lookup_string(in, FM_FMRI_HC_SERIAL_ID, &old_serial) != 0) {
853 		ret = FMD_OBJ_STATE_UNKNOWN;
854 		goto out;
855 	}
856 
857 	if (strcmp(old_serial, new_serial) == 0) {
858 		ret = FMD_OBJ_STATE_STILL_PRESENT;
859 	} else {
860 		ret = FMD_OBJ_STATE_REPLACED;
861 	}
862 
863 out:
864 	nvlist_free(rsrc);
865 	return (set_retnvl(mod, out, TOPO_METH_REPLACED_RET, ret));
866 }
867 
868 const char *
869 get_chip_brand(topo_mod_t *mod, kstat_ctl_t *kc, int32_t chipid)
870 {
871 	kstat_t *ksp;
872 	kstat_named_t *ks;
873 
874 	if ((ksp = kstat_lookup(kc, "cpu_info", chipid, NULL)) == NULL ||
875 	    kstat_read(kc, ksp, NULL) == -1 ||
876 	    (ks = kstat_data_lookup(ksp, "brand")) == NULL) {
877 		topo_mod_dprintf(mod, "failed to read stat cpu_info:%d:brand",
878 		    chipid);
879 		return (NULL);
880 	}
881 	return (topo_mod_strdup(mod, ks->value.str.addr.ptr));
882 }
883