Home | History | Annotate | Download | only in cpumem-diagnosis
      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