xref: /titanic_52/usr/src/cmd/fm/modules/sun4v/cpumem-diagnosis/cmd_branch.c (revision 2d6aa547f868d751d00f1f2e70171d9dc9317af1)
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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <cmd_mem.h>
27 #include <cmd_branch.h>
28 #include <cmd_dimm.h>
29 #include <cmd.h>
30 #include <cmd_hc_sun4v.h>
31 
32 #include <errno.h>
33 #include <string.h>
34 #include <strings.h>
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <fm/fmd_api.h>
38 #include <fm/libtopo.h>
39 #include <sys/fm/protocol.h>
40 #include <sys/mem.h>
41 #include <sys/nvpair.h>
42 
43 #define	BUF_SIZE	120
44 #define	LEN_CMP		6
45 
46 /*
47  * mbd_label: If a DIMM associated with this branch is located on a memory
48  * expansion board or riser board, return (pointer to) the label of that board;
49  * otherwise return NULL.
50  * We assume that there will be at most one such board for any branch.
51  */
52 
53 char *
54 mbd_label(fmd_hdl_t *hdl, cmd_branch_t *branch, const char *nacname)
55 {
56 	cmd_dimm_t *dimm;
57 	cmd_branch_memb_t *bm;
58 	char *p;
59 	size_t s;
60 
61 	for (bm = cmd_list_next(&branch->branch_dimms); bm != NULL;
62 	    bm = cmd_list_next(bm)) {
63 		dimm = bm->dimm;
64 		if ((p = strstr(dimm->dimm_unum, nacname)) != NULL) {
65 			p = strchr(p, '/');	/* include instance number */
66 			s = p - dimm->dimm_unum;
67 			p = fmd_hdl_zalloc(hdl, s+1, FMD_SLEEP);
68 			(void) strncpy(p, dimm->dimm_unum, s);
69 			*(p + s) = '\0';
70 			return (p);
71 		}
72 	}
73 	return (NULL);
74 }
75 
76 void
77 cmd_branch_add_dimm(fmd_hdl_t *hdl, cmd_branch_t *branch, cmd_dimm_t *dimm)
78 {
79 	cmd_branch_memb_t *bm;
80 
81 	if (dimm == NULL)
82 		return;
83 
84 	fmd_hdl_debug(hdl, "Attaching dimm %s to branch %s\n",
85 	    dimm->dimm_unum, branch->branch_unum);
86 	bm = fmd_hdl_zalloc(hdl, sizeof (cmd_branch_memb_t), FMD_SLEEP);
87 	bm->dimm = dimm;
88 	cmd_list_append(&branch->branch_dimms, bm);
89 }
90 
91 void
92 cmd_branch_remove_dimm(fmd_hdl_t *hdl, cmd_branch_t *branch, cmd_dimm_t *dimm)
93 {
94 	cmd_branch_memb_t *bm;
95 
96 	fmd_hdl_debug(hdl, "Detaching dimm %s from branch %s\n",
97 	    dimm->dimm_unum, branch->branch_unum);
98 
99 	for (bm = cmd_list_next(&branch->branch_dimms); bm != NULL;
100 	    bm = cmd_list_next(bm)) {
101 		if (bm->dimm == dimm) {
102 			cmd_list_delete(&branch->branch_dimms, bm);
103 			fmd_hdl_free(hdl, bm, sizeof (cmd_branch_memb_t));
104 			return;
105 		}
106 	}
107 
108 	fmd_hdl_abort(hdl,
109 	    "Attempt to disconnect dimm from non-parent branch\n");
110 }
111 
112 static cmd_dimm_t *
113 branch_dimm_create(fmd_hdl_t *hdl, char *dimm_unum, char **serids,
114     size_t nserids)
115 {
116 	nvlist_t *fmri;
117 	cmd_dimm_t *dimm;
118 
119 	fmri = cmd_mem_fmri_create(dimm_unum, serids, nserids);
120 
121 	if (fmri != NULL && (fmd_nvl_fmri_expand(hdl, fmri) == 0)) {
122 		dimm = cmd_dimm_create(hdl, fmri);
123 		if (dimm != NULL) {
124 			nvlist_free(fmri);
125 			return (dimm);
126 		}
127 	}
128 
129 	nvlist_free(fmri);
130 	return (NULL);
131 }
132 
133 static fmd_hdl_t *br_hdl; /* for use by callbacks */
134 static int br_dimmcount;
135 static nvlist_t *br_memb_nvl;
136 
137 /*ARGSUSED*/
138 static int
139 branch_dimm_cb(topo_hdl_t *thp, tnode_t *node, void *arg)
140 {
141 	char *lbl, *p, *q;
142 	char cx[BUF_SIZE], cy[BUF_SIZE];
143 	nvlist_t *rsrc;
144 	int err;
145 	cmd_branch_t *branch = (cmd_branch_t *)arg;
146 	cmd_dimm_t *dimm;
147 	size_t nserids;
148 	char **serids;
149 
150 	if (topo_node_resource(node, &rsrc, &err) < 0)
151 		return (TOPO_WALK_NEXT);	/* no label, try next */
152 
153 	if ((nvlist_lookup_string(rsrc, FM_FMRI_MEM_UNUM, &lbl) != 0) ||
154 	    (nvlist_lookup_string_array(rsrc, FM_FMRI_MEM_SERIAL_ID,
155 	    &serids, &nserids) != 0)) {
156 		nvlist_free(rsrc);
157 		return (TOPO_WALK_NEXT);
158 	}
159 
160 	/*
161 	 * Massage the unum of the candidate DIMM as follows:
162 	 * a) remove any trailing J number.  Use result for cmd_dimm_t.
163 	 * b) for branch membership purposes only, remove reference to
164 	 * a riser card (MR%d) if one exists.
165 	 */
166 	if ((p = strstr(lbl, "/J")) != NULL) {
167 		(void) strncpy(cx, lbl, p - lbl);
168 		cx[p - lbl] = '\0';
169 	} else {
170 		(void) strcpy(cx, lbl);
171 	}
172 	(void) strcpy(cy, cx);
173 	if ((p = strstr(cy, "/MR")) != NULL) {
174 		if ((q = strchr(p + 1, '/')) != NULL)
175 			(void) strcpy(p, q);
176 		else
177 			*p = '\0';
178 	}
179 
180 	/*
181 	 * For benefit of Batoka-like platforms, start comparison with
182 	 * "CMP", so that any leading "MEM" or "CPU" makes no difference.
183 	 */
184 
185 	p = strstr(branch->branch_unum, "CMP");
186 	q = strstr(cy, "CMP");
187 
188 	if ((p != NULL) && (q != NULL) && strncmp(p, q, strlen(p)) == 0) {
189 		dimm = branch_dimm_create(br_hdl, cx, serids, nserids);
190 		if (dimm != NULL)
191 			cmd_branch_add_dimm(br_hdl, branch, dimm);
192 	}
193 	nvlist_free(rsrc);
194 	return (TOPO_WALK_NEXT);
195 }
196 
197 
198 /*
199  * The cmd_dimm_t structure created for a DIMM in a branch never has a
200  * Jxxx in its unum; the cmd_dimm_t structure created for a DIMM containing
201  * a page, or in a bank (i.e. for ECC errors)-always-has a Jxxx in its
202  * unum. Therefore the set of cmd_dimm_t's created for a branch is always
203  * disjoint from the set of cmd_dimm_t's created for pages and/or banks, so
204  * the cmd_dimm_create will never link a 'branch' cmd_dimm_t into bank.
205  * Faulting a DIMM for ECC will not prevent subsequent faulting of "same"
206  * dimm for FBR/FBU and vice versa
207  */
208 static int
209 branch_dimmlist_create(fmd_hdl_t *hdl, cmd_branch_t *branch)
210 {
211 	topo_hdl_t *thp;
212 	topo_walk_t *twp;
213 	int err, dimm_count;
214 	cmd_list_t *bp;
215 
216 	if ((thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION)) == NULL)
217 		return (0);
218 	if ((twp = topo_walk_init(thp,
219 	    FM_FMRI_SCHEME_MEM, branch_dimm_cb, branch, &err))
220 	    == NULL) {
221 		fmd_hdl_topo_rele(hdl, thp);
222 		return (0);
223 	}
224 	br_hdl = hdl;
225 	(void) topo_walk_step(twp, TOPO_WALK_CHILD);
226 	topo_walk_fini(twp);
227 	fmd_hdl_topo_rele(hdl, thp);
228 
229 	for (dimm_count = 0, bp = cmd_list_next(&branch->branch_dimms);
230 	    bp != NULL; bp = cmd_list_next(bp), dimm_count++)
231 		;
232 	return (dimm_count);
233 }
234 
235 /*ARGSUSED*/
236 static int
237 fru_by_label_cb(topo_hdl_t *thp, tnode_t *node, void *arg)
238 {
239 	char *lbl;
240 	int err;
241 	char *target = (char *)arg;
242 
243 	if (topo_node_label(node, &lbl, &err) < 0)
244 		return (TOPO_WALK_NEXT);	/* no label, try next */
245 
246 	if ((strcmp(target, lbl) == 0) &&
247 	    (topo_node_fru(node, &br_memb_nvl, NULL, &err) == 0)) {
248 		topo_hdl_strfree(thp, lbl);
249 		return (TOPO_WALK_TERMINATE);
250 	}
251 	topo_hdl_strfree(thp, lbl);
252 	return (TOPO_WALK_NEXT);
253 }
254 
255 static nvlist_t *
256 fru_by_label(fmd_hdl_t *hdl, const char *target)
257 {
258 	topo_hdl_t *thp;
259 	topo_walk_t *twp;
260 	int err;
261 
262 	br_memb_nvl = NULL;
263 	if (((thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION)) != NULL) &&
264 	    ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC,
265 	    fru_by_label_cb, (void *)target, &err)) != NULL)) {
266 		br_hdl = hdl;
267 		(void) topo_walk_step(twp, TOPO_WALK_CHILD);
268 		topo_walk_fini(twp);
269 	}
270 	fmd_hdl_topo_rele(hdl, thp);
271 	return (br_memb_nvl);
272 }
273 
274 static void
275 add_bdflt_to_case(fmd_hdl_t *hdl, char *label, const char *fltnm,
276     uint8_t board_cert, fmd_case_t *cp)
277 {
278 	nvlist_t *memb_nvl, *flt;
279 
280 	memb_nvl = fru_by_label(hdl, label);
281 	if (memb_nvl != NULL) {
282 		flt = cmd_nvl_create_fault(hdl, fltnm, board_cert,
283 		    memb_nvl, memb_nvl, NULL);
284 		flt = cmd_fault_add_location(hdl, flt, label);
285 		if (flt != NULL) {
286 			fmd_case_add_suspect(hdl, cp, flt);
287 		}
288 		nvlist_free(memb_nvl);
289 	}
290 }
291 
292 /*
293  * For t5440, the memory channel goes like this:
294  * VF -> cpuboard -> D0 -> motherboard -> memboard -> D[1..3]
295  * If there is a dimm on the memory board, the memory board,
296  * motherboard, cpuboard, and dimms are in the suspect list.
297  * If there is no dimm on the memory board, the cpu board and
298  * the dimms are in the suspect list
299  * The board certainty = total board certainty / number of
300  * the faulty boards in the suspect list.
301  */
302 void
303 cmd_branch_create_fault(fmd_hdl_t *hdl, cmd_branch_t *branch,
304     const char *fltnm, nvlist_t *asru)
305 {
306 	nvlist_t *flt;
307 	cmd_branch_memb_t *bm;
308 	cmd_dimm_t *dimm;
309 	int dimm_count = 0;
310 	uint_t cert = 0;
311 	uint_t board_cert = 0;
312 	char *fruloc = NULL, *membd_label;
313 
314 	/* attach the dimms to the branch */
315 	dimm_count = branch_dimmlist_create(hdl, branch);
316 
317 	if ((membd_label = mbd_label(hdl, branch, "MEM")) != NULL) {
318 		board_cert = CMD_BOARDS_CERT / 3; /* CPU, MEM, MB */
319 
320 		/*
321 		 * Batoka with memory expansion.  CPU expansion board will
322 		 * be added below.  Add memory expansion board and motherboard
323 		 * FRUs here.
324 		 */
325 
326 		add_bdflt_to_case(hdl, membd_label, fltnm, board_cert,
327 		    branch->branch_case.cc_cp);
328 		fmd_hdl_strfree(hdl, membd_label);
329 		add_bdflt_to_case(hdl, "MB", fltnm, board_cert,
330 		    branch->branch_case.cc_cp);
331 
332 	} else if ((membd_label = mbd_label(hdl, branch, "MR")) != NULL) {
333 
334 		board_cert = CMD_BOARDS_CERT / 2; /* MB, MR */
335 
336 		/*
337 		 * Maramba or similar platform with mezzanine board.
338 		 * Motherboard FRU will be added below.  Add the mezzanine
339 		 * board here.
340 		 */
341 
342 		add_bdflt_to_case(hdl, membd_label, fltnm, board_cert,
343 		    branch->branch_case.cc_cp);
344 		fmd_hdl_strfree(hdl, membd_label);
345 	} else {
346 		board_cert = CMD_BOARDS_CERT; /* only MB or CPU */
347 	}
348 
349 	/*
350 	 * The code which follows adds to the suspect list the FRU which
351 	 * contains the ereport 'detector'.  This can be either a CPU
352 	 * expansion board (Batoka), or motherboard (Huron, Maramba, or
353 	 * derivative).
354 	 */
355 
356 	fruloc = cmd_getfru_loc(hdl, asru);
357 	flt = cmd_boardfru_create_fault(hdl, asru, fltnm, board_cert, fruloc);
358 	if (flt != NULL)
359 		fmd_case_add_suspect(hdl, branch->branch_case.cc_cp, flt);
360 
361 	if (dimm_count != 0)
362 		cert = (100 - CMD_BOARDS_CERT) / dimm_count;
363 
364 	/* create dimm faults */
365 	for (bm = cmd_list_next(&branch->branch_dimms); bm != NULL;
366 	    bm = cmd_list_next(bm)) {
367 		dimm = bm->dimm;
368 		if (dimm != NULL) {
369 			dimm->dimm_flags |= CMD_MEM_F_FAULTING;
370 			cmd_dimm_dirty(hdl, dimm);
371 			flt = cmd_dimm_create_fault(hdl, dimm, fltnm, cert);
372 			fmd_case_add_suspect(hdl, branch->branch_case.cc_cp,
373 			    flt);
374 		}
375 	}
376 	if (fruloc != NULL)
377 		fmd_hdl_strfree(hdl, fruloc);
378 }
379 
380 cmd_branch_t *
381 cmd_branch_create(fmd_hdl_t *hdl, nvlist_t *asru)
382 {
383 	cmd_branch_t *branch;
384 	const char *b_unum;
385 
386 	if ((b_unum = cmd_fmri_get_unum(asru)) == NULL) {
387 		CMD_STAT_BUMP(bad_mem_asru);
388 		return (NULL);
389 	}
390 
391 	fmd_hdl_debug(hdl, "branch_create: creating new branch %s\n", b_unum);
392 	CMD_STAT_BUMP(branch_creat);
393 
394 	branch = fmd_hdl_zalloc(hdl, sizeof (cmd_branch_t), FMD_SLEEP);
395 	branch->branch_nodetype = CMD_NT_BRANCH;
396 	branch->branch_version = CMD_BRANCH_VERSION;
397 
398 	cmd_bufname(branch->branch_bufname, sizeof (branch->branch_bufname),
399 	    "branch_%s", b_unum);
400 	cmd_fmri_init(hdl, &branch->branch_asru, asru, "branch_asru_%s",
401 	    b_unum);
402 
403 	(void) nvlist_lookup_string(branch->branch_asru_nvl, FM_FMRI_MEM_UNUM,
404 	    (char **)&branch->branch_unum);
405 
406 	cmd_list_append(&cmd.cmd_branches, branch);
407 	cmd_branch_dirty(hdl, branch);
408 
409 	return (branch);
410 }
411 
412 cmd_branch_t *
413 cmd_branch_lookup_by_unum(fmd_hdl_t *hdl, const char *unum)
414 {
415 	cmd_branch_t *branch;
416 
417 	fmd_hdl_debug(hdl, "branch_lookup: dimm_unum %s", unum);
418 	/*
419 	 * fbr/fbu unum dimm does not have a J number
420 	 */
421 	if (strstr(unum, "J") != NULL)
422 		return (NULL);
423 
424 	for (branch = cmd_list_next(&cmd.cmd_branches); branch != NULL;
425 	    branch = cmd_list_next(branch)) {
426 		if (strcmp(branch->branch_unum, unum) == 0)
427 			return (branch);
428 	}
429 
430 	fmd_hdl_debug(hdl, "branch_lookup_by_unum: no branch is found\n");
431 	return (NULL);
432 }
433 
434 cmd_branch_t *
435 cmd_branch_lookup(fmd_hdl_t *hdl, nvlist_t *asru)
436 {
437 	cmd_branch_t *branch;
438 	const char *unum;
439 
440 	if ((unum = cmd_fmri_get_unum(asru)) == NULL) {
441 		CMD_STAT_BUMP(bad_mem_asru);
442 		return (NULL);
443 	}
444 
445 	for (branch = cmd_list_next(&cmd.cmd_branches); branch != NULL;
446 	    branch = cmd_list_next(branch)) {
447 		if (strcmp(branch->branch_unum, unum) == 0)
448 			return (branch);
449 	}
450 
451 	fmd_hdl_debug(hdl, "cmd_branch_lookup: discarding old \n");
452 	return (NULL);
453 }
454 
455 static cmd_branch_t *
456 branch_wrapv0(fmd_hdl_t *hdl, cmd_branch_pers_t *pers, size_t psz)
457 {
458 	cmd_branch_t *branch;
459 
460 	if (psz != sizeof (cmd_branch_pers_t)) {
461 		fmd_hdl_abort(hdl, "size of state doesn't match size of "
462 		    "version 0 state (%u bytes).\n",
463 		    sizeof (cmd_branch_pers_t));
464 	}
465 
466 	branch = fmd_hdl_zalloc(hdl, sizeof (cmd_branch_t), FMD_SLEEP);
467 	bcopy(pers, branch, sizeof (cmd_branch_pers_t));
468 	fmd_hdl_free(hdl, pers, psz);
469 	return (branch);
470 }
471 
472 void *
473 cmd_branch_restore(fmd_hdl_t *hdl, fmd_case_t *cp, cmd_case_ptr_t *ptr)
474 {
475 	cmd_branch_t *branch;
476 	size_t branchsz;
477 
478 
479 	for (branch = cmd_list_next(&cmd.cmd_branches); branch != NULL;
480 	    branch = cmd_list_next(branch)) {
481 		if (strcmp(branch->branch_bufname, ptr->ptr_name) == 0)
482 			break;
483 	}
484 
485 	if (branch == NULL) {
486 		fmd_hdl_debug(hdl, "restoring branch from %s\n", ptr->ptr_name);
487 
488 		if ((branchsz = fmd_buf_size(hdl, NULL, ptr->ptr_name)) == 0) {
489 			fmd_hdl_abort(hdl, "branch referenced by case %s does "
490 			    "not exist in saved state\n",
491 			    fmd_case_uuid(hdl, cp));
492 		} else if (branchsz > CMD_BRANCH_MAXSIZE ||
493 		    branchsz < CMD_BRANCH_MINSIZE) {
494 			fmd_hdl_abort(hdl,
495 			    "branch buffer referenced by case %s "
496 			    "is out of bounds (is %u bytes, max %u, min %u)\n",
497 			    fmd_case_uuid(hdl, cp), branchsz,
498 			    CMD_BRANCH_MAXSIZE, CMD_BRANCH_MINSIZE);
499 		}
500 
501 		if ((branch = cmd_buf_read(hdl, NULL, ptr->ptr_name,
502 		    branchsz)) == NULL) {
503 			fmd_hdl_abort(hdl, "failed to read branch buf %s",
504 			    ptr->ptr_name);
505 		}
506 
507 		fmd_hdl_debug(hdl, "found %d in version field\n",
508 		    branch->branch_version);
509 
510 		switch (branch->branch_version) {
511 		case CMD_BRANCH_VERSION_0:
512 			branch = branch_wrapv0(hdl,
513 			    (cmd_branch_pers_t *)branch, branchsz);
514 			break;
515 		default:
516 			fmd_hdl_abort(hdl, "unknown version (found %d) "
517 			    "for branch state referenced by case %s.\n",
518 			    branch->branch_version, fmd_case_uuid(hdl,
519 			    cp));
520 			break;
521 		}
522 
523 		cmd_fmri_restore(hdl, &branch->branch_asru);
524 
525 		if ((errno = nvlist_lookup_string(branch->branch_asru_nvl,
526 		    FM_FMRI_MEM_UNUM, (char **)&branch->branch_unum)) != 0)
527 			fmd_hdl_abort(hdl, "failed to retrieve unum from asru");
528 
529 
530 		cmd_list_append(&cmd.cmd_branches, branch);
531 	}
532 
533 	switch (ptr->ptr_subtype) {
534 	case CMD_PTR_BRANCH_CASE:
535 		cmd_mem_case_restore(hdl, &branch->branch_case, cp, "branch",
536 		    branch->branch_unum);
537 		break;
538 	default:
539 		fmd_hdl_abort(hdl, "invalid %s subtype %d\n",
540 		    ptr->ptr_name, ptr->ptr_subtype);
541 	}
542 
543 	return (branch);
544 }
545 
546 void
547 cmd_branch_dirty(fmd_hdl_t *hdl, cmd_branch_t *branch)
548 {
549 	if (fmd_buf_size(hdl, NULL, branch->branch_bufname) !=
550 	    sizeof (cmd_branch_pers_t))
551 		fmd_buf_destroy(hdl, NULL, branch->branch_bufname);
552 
553 	/* No need to rewrite the FMRIs in the branch - they don't change */
554 	fmd_buf_write(hdl, NULL, branch->branch_bufname, &branch->branch_pers,
555 	    sizeof (cmd_branch_pers_t));
556 }
557 
558 static void
559 branch_dimmlist_free(fmd_hdl_t *hdl, cmd_branch_t *branch)
560 {
561 	cmd_branch_memb_t *bm;
562 
563 	while ((bm = cmd_list_next(&branch->branch_dimms)) != NULL) {
564 		cmd_list_delete(&branch->branch_dimms, bm);
565 		fmd_hdl_free(hdl, bm, sizeof (cmd_branch_memb_t));
566 	}
567 }
568 
569 static void
570 branch_free(fmd_hdl_t *hdl, cmd_branch_t *branch, int destroy)
571 {
572 	fmd_hdl_debug(hdl, "Free branch %s\n", branch->branch_unum);
573 	if (branch->branch_case.cc_cp != NULL) {
574 		if (destroy) {
575 			if (branch->branch_case.cc_serdnm != NULL) {
576 				fmd_serd_destroy(hdl,
577 				    branch->branch_case.cc_serdnm);
578 				fmd_hdl_strfree(hdl,
579 				    branch->branch_case.cc_serdnm);
580 				branch->branch_case.cc_serdnm = NULL;
581 			}
582 		}
583 		cmd_case_fini(hdl, branch->branch_case.cc_cp, destroy);
584 	}
585 
586 	branch_dimmlist_free(hdl, branch);
587 	cmd_fmri_fini(hdl, &branch->branch_asru, destroy);
588 
589 	if (destroy)
590 		fmd_buf_destroy(hdl, NULL, branch->branch_bufname);
591 	cmd_list_delete(&cmd.cmd_branches, branch);
592 	fmd_hdl_free(hdl, branch, sizeof (cmd_branch_t));
593 }
594 
595 void
596 cmd_branch_destroy(fmd_hdl_t *hdl, cmd_branch_t *branch)
597 {
598 	branch_free(hdl, branch, FMD_B_TRUE);
599 }
600 
601 /*ARGSUSED*/
602 static int
603 branch_exist_cb(topo_hdl_t *thp, tnode_t *node, void *arg)
604 {
605 	char *lbl, *p, *q;
606 	char cy[BUF_SIZE];
607 	nvlist_t *rsrc;
608 	int err;
609 
610 	cmd_branch_t *branch = (cmd_branch_t *)arg;
611 
612 	if (topo_node_resource(node, &rsrc, &err) < 0)
613 		return (TOPO_WALK_NEXT);	/* no label, try next */
614 
615 	if (nvlist_lookup_string(rsrc, "unum", &lbl) != 0) {
616 		nvlist_free(rsrc);
617 		return (TOPO_WALK_NEXT);
618 	}
619 	/*
620 	 * for branch membership purposes only, remove reference to
621 	 * a riser card (MR%d) if one exists.
622 	 */
623 	(void) strcpy(cy, lbl);
624 	if ((p = strstr(cy, "/MR")) != NULL) {
625 		if ((q = strchr(p + 1, '/')) != NULL)
626 			(void) strcpy(p, q);
627 		else
628 			*p = '\0';
629 	}
630 	if (strncmp(branch->branch_unum, cy,
631 	    strlen(branch->branch_unum)) == 0) {
632 		br_dimmcount++;
633 		nvlist_free(rsrc);
634 		return (TOPO_WALK_TERMINATE);
635 	}
636 	nvlist_free(rsrc);
637 	return (TOPO_WALK_NEXT);
638 }
639 
640 static int
641 branch_exist(fmd_hdl_t *hdl, cmd_branch_t *branch)
642 {
643 	topo_hdl_t *thp;
644 	topo_walk_t *twp;
645 	int err;
646 
647 	if ((thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION)) == NULL)
648 		return (0);
649 	if ((twp = topo_walk_init(thp,
650 	    FM_FMRI_SCHEME_MEM, branch_exist_cb, branch, &err))
651 	    == NULL) {
652 		fmd_hdl_topo_rele(hdl, thp);
653 		return (0);
654 	}
655 	br_dimmcount = 0;
656 	(void) topo_walk_step(twp, TOPO_WALK_CHILD);
657 	topo_walk_fini(twp);
658 	fmd_hdl_topo_rele(hdl, thp);
659 
660 	return (br_dimmcount);
661 }
662 
663 /*
664  * If the case has been solved, don't need to check the dimmlist
665  * If the case has not been solved, the branch is valid if there is least one
666  * existing dimm in the branch
667  */
668 void
669 cmd_branch_validate(fmd_hdl_t *hdl)
670 {
671 	cmd_branch_t *branch, *next;
672 
673 	fmd_hdl_debug(hdl, "cmd_branch_validate\n");
674 
675 	for (branch = cmd_list_next(&cmd.cmd_branches); branch != NULL;
676 	    branch = next) {
677 		next = cmd_list_next(branch);
678 		if (branch->branch_case.cc_cp != NULL &&
679 		    fmd_case_solved(hdl, branch->branch_case.cc_cp))
680 			continue;
681 		if (branch_exist(hdl, branch))
682 			continue;
683 		cmd_branch_destroy(hdl, branch);
684 	}
685 }
686 
687 void
688 cmd_branch_gc(fmd_hdl_t *hdl)
689 {
690 	fmd_hdl_debug(hdl, "cmd_branch_gc\n");
691 	cmd_branch_validate(hdl);
692 }
693 
694 void
695 cmd_branch_fini(fmd_hdl_t *hdl)
696 {
697 	cmd_branch_t *branch;
698 	fmd_hdl_debug(hdl, "cmd_branch_fini\n");
699 
700 	while ((branch = cmd_list_next(&cmd.cmd_branches)) != NULL)
701 		branch_free(hdl, branch, FMD_B_FALSE);
702 }
703