17aec1d6eScindi /*
27aec1d6eScindi  * CDDL HEADER START
37aec1d6eScindi  *
47aec1d6eScindi  * The contents of this file are subject to the terms of the
58a40a695Sgavinm  * Common Development and Distribution License (the "License").
68a40a695Sgavinm  * You may not use this file except in compliance with the License.
77aec1d6eScindi  *
87aec1d6eScindi  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97aec1d6eScindi  * or http://www.opensolaris.org/os/licensing.
107aec1d6eScindi  * See the License for the specific language governing permissions
117aec1d6eScindi  * and limitations under the License.
127aec1d6eScindi  *
137aec1d6eScindi  * When distributing Covered Code, include this CDDL HEADER in each
147aec1d6eScindi  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157aec1d6eScindi  * If applicable, add the following below this CDDL HEADER, with the
167aec1d6eScindi  * fields enclosed by brackets "[]" replaced with your own identifying
177aec1d6eScindi  * information: Portions Copyright [yyyy] [name of copyright owner]
187aec1d6eScindi  *
197aec1d6eScindi  * CDDL HEADER END
207aec1d6eScindi  *
2120c794b3Sgavinm  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
227aec1d6eScindi  * Use is subject to license terms.
237aec1d6eScindi  */
247aec1d6eScindi 
257aec1d6eScindi /*
267aec1d6eScindi  * Given a physical address and an optional syndrome, determine the
277aec1d6eScindi  * name of the memory module that contains it.
287aec1d6eScindi  */
297aec1d6eScindi 
307aec1d6eScindi #include <sys/errno.h>
317aec1d6eScindi #include <sys/types.h>
327aec1d6eScindi #include <sys/mc.h>
337aec1d6eScindi 
347aec1d6eScindi #include <mcamd_api.h>
357aec1d6eScindi #include <mcamd_err.h>
367aec1d6eScindi 
374156fc34Sgavinm #define	MC_SYSADDR_MSB	39
384156fc34Sgavinm #define	MC_SYSADDR_LSB	3
397aec1d6eScindi 
408a40a695Sgavinm #define	CSDIMM1	0x1
418a40a695Sgavinm #define	CSDIMM2	0x2
427aec1d6eScindi 
437aec1d6eScindi #define	BITS(val, high, low) \
447aec1d6eScindi 	((val) & (((2ULL << (high)) - 1) & ~((1ULL << (low)) - 1)))
457aec1d6eScindi 
468a40a695Sgavinm /*
478a40a695Sgavinm  * iaddr_gen generates a "normalized" DRAM controller input address
488a40a695Sgavinm  * from a system address (physical address) if it falls within the
498a40a695Sgavinm  * mapped range for this memory controller.  Normalisation is
508a40a695Sgavinm  * performed by subtracting the node base address from the system address,
518a40a695Sgavinm  * allowing from hoisting, and excising any bits being used in node
528a40a695Sgavinm  * interleaving.
538a40a695Sgavinm  */
547aec1d6eScindi static int
iaddr_gen(struct mcamd_hdl * hdl,mcamd_node_t * mc,uint64_t pa,uint64_t * iaddrp)557aec1d6eScindi iaddr_gen(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
567aec1d6eScindi     uint64_t *iaddrp)
577aec1d6eScindi {
587aec1d6eScindi 	uint64_t orig = pa;
598a40a695Sgavinm 	uint64_t mcnum, base, lim, dramaddr, ilen, ilsel, top, holesz;
608a40a695Sgavinm 
618a40a695Sgavinm 	if (!mcamd_get_numprops(hdl,
628a40a695Sgavinm 	    mc, MCAMD_PROP_NUM, &mcnum,
638a40a695Sgavinm 	    mc, MCAMD_PROP_BASE_ADDR, &base,
648a40a695Sgavinm 	    mc, MCAMD_PROP_LIM_ADDR, &lim,
658a40a695Sgavinm 	    mc, MCAMD_PROP_ILEN, &ilen,
668a40a695Sgavinm 	    mc, MCAMD_PROP_ILSEL, &ilsel,
678a40a695Sgavinm 	    mc, MCAMD_PROP_DRAMHOLE_SIZE, &holesz,
688a40a695Sgavinm 	    NULL)) {
697aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "iaddr_gen: failed to "
707aec1d6eScindi 		    "lookup required properties");
717aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
727aec1d6eScindi 	}
737aec1d6eScindi 
748a40a695Sgavinm 	/*
758a40a695Sgavinm 	 * A node with no mapped memory (no active chip-selects is usually
768a40a695Sgavinm 	 * mapped with base and lim both zero.  We'll cover that case and
778a40a695Sgavinm 	 * any other where the range is 0.
788a40a695Sgavinm 	 */
798a40a695Sgavinm 	if (base == lim)
808a40a695Sgavinm 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
818a40a695Sgavinm 
827aec1d6eScindi 	if (pa < base || pa > lim) {
837aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx not "
847aec1d6eScindi 		    "in range [0x%llx, 0x%llx] of MC %d\n", pa, base, lim,
857aec1d6eScindi 		    (int)mcnum);
867aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
877aec1d6eScindi 	}
887aec1d6eScindi 
897aec1d6eScindi 	/*
907aec1d6eScindi 	 * Rev E and later added the DRAM Hole Address Register for
917aec1d6eScindi 	 * memory hoisting.  In earlier revisions memory hoisting is
927aec1d6eScindi 	 * achieved by following some algorithm to modify the CS bases etc,
937aec1d6eScindi 	 * and this pa to unum algorithm will simply see those modified
947aec1d6eScindi 	 * values.  But if the Hole Address Register is being used then
957aec1d6eScindi 	 * we need to reduce any address at or above 4GB by the size of
967aec1d6eScindi 	 * the hole.
977aec1d6eScindi 	 */
988a40a695Sgavinm 	if (holesz != 0 && pa >= 0x100000000) {
998a40a695Sgavinm 		pa -= holesz;
1007aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: dram hole "
1017aec1d6eScindi 		    "valid; pa decremented from 0x%llx to 0x%llx for "
1028a40a695Sgavinm 		    "a dramhole size of 0x%llx\n", orig, pa, holesz);
1037aec1d6eScindi 	}
1047aec1d6eScindi 
1057aec1d6eScindi 	dramaddr = BITS(pa, 39, 0) - BITS(base, 39, 24);
1067aec1d6eScindi 
1077aec1d6eScindi 	if (ilen != 0) {
1087aec1d6eScindi 		int pailsel;
1097aec1d6eScindi 
1107aec1d6eScindi 		if (ilen != 1 && ilen != 3 && ilen != 7) {
1117aec1d6eScindi 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "Invalid intlven "
1127aec1d6eScindi 			    "of %d for MC %d\n", (int)ilen, (int)mcnum);
1137aec1d6eScindi 			return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
1147aec1d6eScindi 		}
1157aec1d6eScindi 
1167aec1d6eScindi 		if ((pailsel = BITS(pa, 14, 12) >> 12 & ilen) != ilsel) {
1177aec1d6eScindi 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: "
1187aec1d6eScindi 			    "PA 0x%llx in a %d-way node interleave indicates "
1197aec1d6eScindi 			    "selection %d, MC %d has ilsel of %d\n",
1207aec1d6eScindi 			    pa, (int)ilen + 1, pailsel, (int)mcnum, (int)ilsel);
1217aec1d6eScindi 			return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
1227aec1d6eScindi 		}
1237aec1d6eScindi 
1247aec1d6eScindi 		if (ilen == 1)
1257aec1d6eScindi 			top = BITS(dramaddr, 36, 13) >> 1;
1267aec1d6eScindi 		else if (ilen == 3)
1277aec1d6eScindi 			top = BITS(dramaddr, 37, 14) >> 2;
1287aec1d6eScindi 		else if (ilen == 7)
1297aec1d6eScindi 			top = BITS(dramaddr, 38, 15) >> 3;
1307aec1d6eScindi 	} else {
1317aec1d6eScindi 		top = BITS(dramaddr, 35, 12);
1327aec1d6eScindi 	}
1337aec1d6eScindi 
1347aec1d6eScindi 	*iaddrp = top | BITS(dramaddr, 11, 0);
1357aec1d6eScindi 
1367aec1d6eScindi 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx in range "
1377aec1d6eScindi 	    "[0x%llx, 0x%llx] of MC %d; normalized address for cs compare "
1387aec1d6eScindi 	    "is 0x%llx\n", pa, base, lim, (int)mcnum, *iaddrp);
1397aec1d6eScindi 
1407aec1d6eScindi 	return (0);
1417aec1d6eScindi }
1427aec1d6eScindi 
1438a40a695Sgavinm /*
1448a40a695Sgavinm  * cs_match determines whether the given DRAM controller input address
1458a40a695Sgavinm  * would be responded to by the given chip-select (which may or may not
1468a40a695Sgavinm  * be interleaved with other chip-selects).  Since we include nodes
1478a40a695Sgavinm  * for spare chip-selects (if any) and those marked TestFail (if any)
1488a40a695Sgavinm  * we must check chip-select-bank-enable.
1498a40a695Sgavinm  */
1507aec1d6eScindi static int
cs_match(struct mcamd_hdl * hdl,uint64_t iaddr,mcamd_node_t * cs)1517aec1d6eScindi cs_match(struct mcamd_hdl *hdl, uint64_t iaddr, mcamd_node_t *cs)
1527aec1d6eScindi {
1538a40a695Sgavinm 	uint64_t csnum, csbase, csmask, csbe;
1548a40a695Sgavinm 	int match = 0;
1558a40a695Sgavinm 
1568a40a695Sgavinm 	if (!mcamd_get_numprops(hdl,
1578a40a695Sgavinm 	    cs, MCAMD_PROP_NUM, &csnum,
1588a40a695Sgavinm 	    cs, MCAMD_PROP_BASE_ADDR, &csbase,
1598a40a695Sgavinm 	    cs, MCAMD_PROP_MASK, &csmask,
1608a40a695Sgavinm 	    cs, MCAMD_PROP_CSBE, &csbe,
1618a40a695Sgavinm 	    NULL)) {
1627aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_match: failed to lookup "
1637aec1d6eScindi 		    "required properties\n");
1647aec1d6eScindi 		return (0);
1657aec1d6eScindi 	}
1667aec1d6eScindi 
1678a40a695Sgavinm 	if (csbe) {
1688a40a695Sgavinm 		match = ((iaddr & ~csmask) == (csbase & ~csmask));
1697aec1d6eScindi 
1708a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_match: iaddr 0x%llx "
1718a40a695Sgavinm 		    "does %smatch CS %d (base 0x%llx, mask 0x%llx)\n", iaddr,
1728a40a695Sgavinm 		    match ? "" : "not ", (int)csnum, csbase, csmask);
1738a40a695Sgavinm 	} else {
1748a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_match: iaddr 0x%llx "
1758a40a695Sgavinm 		    "does not match disabled CS %d\n", iaddr, (int)csnum);
1768a40a695Sgavinm 	}
1777aec1d6eScindi 
1787aec1d6eScindi 	return (match);
1797aec1d6eScindi }
1807aec1d6eScindi 
1818a40a695Sgavinm /*
1828a40a695Sgavinm  * Given a chip-select node determine whether it has been substituted
1838a40a695Sgavinm  * by the online spare chip-select.
1848a40a695Sgavinm  */
1858a40a695Sgavinm static mcamd_node_t *
cs_sparedto(struct mcamd_hdl * hdl,mcamd_node_t * cs,mcamd_node_t * mc)1868a40a695Sgavinm cs_sparedto(struct mcamd_hdl *hdl, mcamd_node_t *cs, mcamd_node_t *mc)
1878a40a695Sgavinm {
1888a40a695Sgavinm 	uint64_t csnum, badcsnum, sparecsnum, tmpcsnum;
1898a40a695Sgavinm 
1908a40a695Sgavinm 	if (!mcamd_get_numprops(hdl,
1918a40a695Sgavinm 	    cs, MCAMD_PROP_NUM, &csnum,
1928a40a695Sgavinm 	    mc, MCAMD_PROP_BADCS, &badcsnum,
1938a40a695Sgavinm 	    mc, MCAMD_PROP_SPARECS, &sparecsnum,
1948a40a695Sgavinm 	    NULL)) {
1958a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: failed to "
1968a40a695Sgavinm 		    "lookup required properties\n");
1978a40a695Sgavinm 		return (NULL);
1988a40a695Sgavinm 	}
1998a40a695Sgavinm 
2008a40a695Sgavinm 	if ((badcsnum == MC_INVALNUM && sparecsnum == MC_INVALNUM) ||
2018a40a695Sgavinm 	    csnum != badcsnum)
2028a40a695Sgavinm 		return (NULL);
2038a40a695Sgavinm 
2048a40a695Sgavinm 	for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
2058a40a695Sgavinm 	    cs = mcamd_cs_next(hdl, mc, cs)) {
2068a40a695Sgavinm 		if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &tmpcsnum)) {
2078a40a695Sgavinm 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: "
2088a40a695Sgavinm 			    "fail to lookup csnum - cannot reroute to spare\n");
2098a40a695Sgavinm 			return (NULL);
2108a40a695Sgavinm 		}
2118a40a695Sgavinm 		if (tmpcsnum == sparecsnum)
2128a40a695Sgavinm 			break;
2138a40a695Sgavinm 	}
2148a40a695Sgavinm 
2158a40a695Sgavinm 	if (cs != NULL) {
2168a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_sparedto: cs#%d is "
2178a40a695Sgavinm 		    "redirected to active online spare of cs#%d\n", csnum,
2188a40a695Sgavinm 		    sparecsnum);
2198a40a695Sgavinm 	} else {
2208a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: cs#%d is "
2218a40a695Sgavinm 		    "redirected but cannot find spare cs# - cannout reroute to "
2228a40a695Sgavinm 		    "cs#%d\n", csnum, sparecsnum);
2238a40a695Sgavinm 	}
2248a40a695Sgavinm 
2258a40a695Sgavinm 	return (cs);
2268a40a695Sgavinm }
2278a40a695Sgavinm 
2288a40a695Sgavinm /*
2298a40a695Sgavinm  * Having determined which node and chip-select an address maps to,
2308a40a695Sgavinm  * as well as whether it is a dimm1, dimm2 or dimm1/dimm2 pair
2318a40a695Sgavinm  * involved, fill the unum structure including an optional dimm offset
2328a40a695Sgavinm  * member.
2338a40a695Sgavinm  */
2347aec1d6eScindi static int
unum_fill(struct mcamd_hdl * hdl,mcamd_node_t * cs,int which,uint64_t iaddr,mc_unum_t * unump,int incloff)2357aec1d6eScindi unum_fill(struct mcamd_hdl *hdl, mcamd_node_t *cs, int which,
2368a40a695Sgavinm     uint64_t iaddr, mc_unum_t *unump, int incloff)
2377aec1d6eScindi {
2388a40a695Sgavinm 	uint64_t chipnum, csnum, dimm1, dimm2, ranknum;
2397aec1d6eScindi 	mcamd_node_t *mc, *dimm;
2407aec1d6eScindi 	int offsetdimm;
2418a40a695Sgavinm 	int i;
2427aec1d6eScindi 
2437aec1d6eScindi 	if ((mc = mcamd_cs_mc(hdl, cs)) == NULL ||
2448a40a695Sgavinm 	    !mcamd_get_numprops(hdl,
2458a40a695Sgavinm 	    mc, MCAMD_PROP_NUM, &chipnum,
2468a40a695Sgavinm 	    cs, MCAMD_PROP_NUM, &csnum,
2478a40a695Sgavinm 	    cs, MCAMD_PROP_DIMMRANK, &ranknum,
2488a40a695Sgavinm 	    NULL)) {
2497aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
2507aec1d6eScindi 		    "lookup required properties\n");
2517aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
2527aec1d6eScindi 	}
2537aec1d6eScindi 
2548a40a695Sgavinm 	if ((which & CSDIMM1) &&
2558a40a695Sgavinm 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_CSDIMM1, &dimm1) ||
2568a40a695Sgavinm 	    (which & CSDIMM2) &&
2578a40a695Sgavinm 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_CSDIMM2, &dimm2)) {
2587aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
2598a40a695Sgavinm 		    "lookup dimm1/dimm2 properties\n");
2607aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
2617aec1d6eScindi 	}
2627aec1d6eScindi 
2637aec1d6eScindi 	unump->unum_board = 0;
26420c794b3Sgavinm 	unump->unum_chip = (int)chipnum;
2657aec1d6eScindi 	unump->unum_mc = 0;
26620c794b3Sgavinm 	unump->unum_chan = MC_INVALNUM;
26720c794b3Sgavinm 	unump->unum_cs = (int)csnum;
26820c794b3Sgavinm 	unump->unum_rank = (int)ranknum;
2697aec1d6eScindi 
2707aec1d6eScindi 	for (i = 0; i < MC_UNUM_NDIMM; i++) {
2718a40a695Sgavinm 		unump->unum_dimms[i] = MC_INVALNUM;
2727aec1d6eScindi 	}
2737aec1d6eScindi 	switch (which) {
2748a40a695Sgavinm 	case CSDIMM1:
27520c794b3Sgavinm 		unump->unum_dimms[0] = (int)dimm1;
27620c794b3Sgavinm 		offsetdimm = (int)dimm1;
2777aec1d6eScindi 		break;
2788a40a695Sgavinm 	case CSDIMM2:
27920c794b3Sgavinm 		unump->unum_dimms[0] = (int)dimm2;
28020c794b3Sgavinm 		offsetdimm = (int)dimm2;
2817aec1d6eScindi 		break;
2828a40a695Sgavinm 	case CSDIMM1 | CSDIMM2:
28320c794b3Sgavinm 		unump->unum_dimms[0] = (int)dimm1;
28420c794b3Sgavinm 		unump->unum_dimms[1] = (int)dimm2;
28520c794b3Sgavinm 		offsetdimm = (int)dimm1;
2867aec1d6eScindi 		break;
2877aec1d6eScindi 	}
2887aec1d6eScindi 
2897aec1d6eScindi 	if (!incloff) {
2907aec1d6eScindi 		unump->unum_offset = MCAMD_RC_INVALID_OFFSET;
2917aec1d6eScindi 		return (0);
2927aec1d6eScindi 	}
2937aec1d6eScindi 
2947aec1d6eScindi 	/*
2957aec1d6eScindi 	 * We wish to calculate a dimm offset.  In the paired case we will
2968a40a695Sgavinm 	 * lookup dimm1 (see offsetdimm above).
2977aec1d6eScindi 	 */
2987aec1d6eScindi 	for (dimm = mcamd_dimm_next(hdl, mc, NULL); dimm != NULL;
2997aec1d6eScindi 	    dimm = mcamd_dimm_next(hdl, mc, dimm)) {
3007aec1d6eScindi 		uint64_t dnum;
3017aec1d6eScindi 		if (!mcamd_get_numprop(hdl, dimm, MCAMD_PROP_NUM, &dnum)) {
3027aec1d6eScindi 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed "
3037aec1d6eScindi 			    "to lookup dimm number property\n");
3047aec1d6eScindi 			continue;
3057aec1d6eScindi 		}
3067aec1d6eScindi 		if (dnum == offsetdimm)
3077aec1d6eScindi 			break;
3087aec1d6eScindi 	}
3097aec1d6eScindi 
3107aec1d6eScindi 	if (dimm == NULL) {
3117aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
3127aec1d6eScindi 		    "find dimm with number %d for offset calculation\n",
3137aec1d6eScindi 		    offsetdimm);
3147aec1d6eScindi 		unump->unum_offset = MCAMD_RC_INVALID_OFFSET;
3157aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
3167aec1d6eScindi 	}
3177aec1d6eScindi 
3187aec1d6eScindi 	/*
3197aec1d6eScindi 	 * mc_pa_to_offset sets the offset to an invalid value if
3207aec1d6eScindi 	 * it hits an error.
3217aec1d6eScindi 	 */
3228a40a695Sgavinm 	(void) mc_pa_to_offset(hdl, mc, cs, iaddr, &unump->unum_offset);
3237aec1d6eScindi 
3247aec1d6eScindi 	return (0);
3257aec1d6eScindi }
3267aec1d6eScindi 
3277aec1d6eScindi /*
3288a40a695Sgavinm  * We have translated a system address to a (node, chip-select), and wish
3298a40a695Sgavinm  * to determine the associated dimm or dimms.
3308a40a695Sgavinm  *
3318a40a695Sgavinm  * A (node, chip-select) pair identifies one (in 64-bit MC mode) or two (in
3328a40a695Sgavinm  * 128-bit MC mode) DIMMs.  In the case of a single dimm it is usually in a
3338a40a695Sgavinm  * lodimm (channel A) slot, but if mismatched dimm support is present it may
3348a40a695Sgavinm  * be an updimm (channel B).
3358a40a695Sgavinm  *
3368a40a695Sgavinm  * Where just one dimm is associated with the chip-select we are done.
3378a40a695Sgavinm  * Where there are two dimms associated with the chip-select we can
3388a40a695Sgavinm  * use the ECC type and/or syndrome to determine which of the pair we
3398a40a695Sgavinm  * resolve to, if the error is correctable.  If the error is uncorrectable
3408a40a695Sgavinm  * then in 64/8 ECC mode we can still resolve to a single dimm (since ECC
3418a40a695Sgavinm  * is calculated and checked on each half of the data separately), but
3428a40a695Sgavinm  * in ChipKill mode we cannot resolve down to a single dimm.
3437aec1d6eScindi  */
3447aec1d6eScindi static int
mc_whichdimm(struct mcamd_hdl * hdl,mcamd_node_t * cs,uint64_t pa,uint8_t valid_lo,uint32_t synd,int syndtype)3458a40a695Sgavinm mc_whichdimm(struct mcamd_hdl *hdl, mcamd_node_t *cs, uint64_t pa,
3464156fc34Sgavinm     uint8_t valid_lo, uint32_t synd, int syndtype)
3477aec1d6eScindi {
3487aec1d6eScindi 	int lobit, hibit, data, check;
3498a40a695Sgavinm 	uint64_t dimm1, dimm2;
3508a40a695Sgavinm 	uint_t sym, pat;
3518a40a695Sgavinm 	int ndimm;
3527aec1d6eScindi 
3538a40a695Sgavinm 	/*
3548a40a695Sgavinm 	 * Read the associated dimm instance numbers.  The provider must
3558a40a695Sgavinm 	 * assure that if there is just one dimm then it is in the first
3568a40a695Sgavinm 	 * property, and if there are two then the first must be on
3578a40a695Sgavinm 	 * channel A.
3588a40a695Sgavinm 	 */
3598a40a695Sgavinm 	if (!mcamd_get_numprops(hdl,
3608a40a695Sgavinm 	    cs, MCAMD_PROP_CSDIMM1, &dimm1,
3618a40a695Sgavinm 	    cs, MCAMD_PROP_CSDIMM2, &dimm2,
3628a40a695Sgavinm 	    NULL)) {
3638a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_whichdimm: failed to "
3648a40a695Sgavinm 		    "lookup required properties");
3658a40a695Sgavinm 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
3668a40a695Sgavinm 	}
3678a40a695Sgavinm 	ndimm = (dimm1 != MC_INVALNUM) + (dimm2 != MC_INVALNUM);
3688a40a695Sgavinm 	if (ndimm == 0) {
3698a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_whichdimm: found no "
3708a40a695Sgavinm 		    "dimms associated with chip-select");
3717aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
3727aec1d6eScindi 	}
3737aec1d6eScindi 
3748a40a695Sgavinm 	if (ndimm == 1) {
3758a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: just one "
3768a40a695Sgavinm 		    "dimm associated with this chip-select");
3778a40a695Sgavinm 		return (CSDIMM1);
3787aec1d6eScindi 	}
3797aec1d6eScindi 
3808a40a695Sgavinm 	/*
3818a40a695Sgavinm 	 * 64/8 ECC is checked separately for the upper and lower
3828a40a695Sgavinm 	 * halves, so even an uncorrectable error is contained within
3834156fc34Sgavinm 	 * one of the two halves.  If we have sufficient address resolution
3844156fc34Sgavinm 	 * then we can determine which DIMM.
3858a40a695Sgavinm 	 */
3867aec1d6eScindi 	if (syndtype == AMD_SYNDTYPE_ECC) {
3874156fc34Sgavinm 		if (valid_lo <= MC_SYSADDR_LSB) {
3884156fc34Sgavinm 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: 64/8 "
3894156fc34Sgavinm 			    "ECC in 128-bit mode, PA 0x%llx is in %s half\n",
3904156fc34Sgavinm 			    pa, pa & 0x8 ? "upper" : "lower");
3914156fc34Sgavinm 			return (pa & 0x8 ? CSDIMM2 : CSDIMM1);
3924156fc34Sgavinm 		} else {
3934156fc34Sgavinm 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: "
3944156fc34Sgavinm 			    "64/8 ECC in 128-bit mode, PA 0x%llx with least "
3954156fc34Sgavinm 			    "significant valid bit %d cannot be resolved to "
3964156fc34Sgavinm 			    "a single DIMM\n", pa, valid_lo);
3974156fc34Sgavinm 			return (mcamd_set_errno(hdl, EMCAMD_INSUFF_RES));
3984156fc34Sgavinm 		}
3997aec1d6eScindi 	}
4007aec1d6eScindi 
4017aec1d6eScindi 	/*
4028a40a695Sgavinm 	 * ChipKill ECC
4037aec1d6eScindi 	 */
4047aec1d6eScindi 	if (mcamd_cksynd_decode(hdl, synd, &sym, &pat)) {
4057aec1d6eScindi 		/*
4067aec1d6eScindi 		 * A correctable ChipKill syndrome and we can tell
4077aec1d6eScindi 		 * which half the error was in from the symbol number.
4087aec1d6eScindi 		 */
4097aec1d6eScindi 		if (mcamd_cksym_decode(hdl, sym, &lobit, &hibit, &data,
4107aec1d6eScindi 		    &check) == 0)
4117aec1d6eScindi 			return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID));
4127aec1d6eScindi 
4137aec1d6eScindi 		if (data && hibit <= 63 || check && hibit <= 7) {
4147aec1d6eScindi 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: "
4157aec1d6eScindi 			    "ChipKill symbol %d (%s %d..%d), so LODIMM\n", sym,
4167aec1d6eScindi 			    data ? "data" : "check", lobit, hibit);
4178a40a695Sgavinm 			return (CSDIMM1);
4187aec1d6eScindi 		} else {
4197aec1d6eScindi 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: "
4207aec1d6eScindi 			    "ChipKill symbol %d (%s %d..%d), so UPDIMM\n", sym,
4217aec1d6eScindi 			    data ? "data" : "check", lobit, hibit);
4228a40a695Sgavinm 			return (CSDIMM2);
4237aec1d6eScindi 		}
4247aec1d6eScindi 	} else {
4257aec1d6eScindi 		/*
4267aec1d6eScindi 		 * An uncorrectable error while in ChipKill ECC mode - can't
4277aec1d6eScindi 		 * tell which dimm or dimms the errors lie within.
4287aec1d6eScindi 		 */
4297aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichhdimm: "
4307aec1d6eScindi 		    "uncorrectable ChipKill, could be either LODIMM "
4317aec1d6eScindi 		    "or UPDIMM\n");
4328a40a695Sgavinm 		return (CSDIMM1 | CSDIMM2);
4337aec1d6eScindi 	}
4347aec1d6eScindi }
4357aec1d6eScindi 
436*ae5a8bedSAndy Fiddaman #ifdef DEBUG
4377aec1d6eScindi /*
4388a40a695Sgavinm  * Brute-force BKDG pa to cs translation, coded to look as much like the
4397aec1d6eScindi  * BKDG code as possible.
4407aec1d6eScindi  */
4417aec1d6eScindi static int
mc_bkdg_patounum(struct mcamd_hdl * hdl,mcamd_node_t * mc,uint64_t pa,uint8_t valid_lo,uint32_t synd,int syndtype,mc_unum_t * unump)4427aec1d6eScindi mc_bkdg_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
4434156fc34Sgavinm     uint8_t valid_lo, uint32_t synd, int syndtype,
4444156fc34Sgavinm     mc_unum_t *unump)
4457aec1d6eScindi {
4467aec1d6eScindi 	int which;
4478a40a695Sgavinm 	uint64_t mcnum, rev;
4487aec1d6eScindi 	mcamd_node_t *cs;
4498a40a695Sgavinm 	/*
4508a40a695Sgavinm 	 * Raw registers as per BKDG
4518a40a695Sgavinm 	 */
4528a40a695Sgavinm 	uint32_t HoleEn;
4538a40a695Sgavinm 	uint32_t DramBase, DramLimit;
4548a40a695Sgavinm 	uint32_t CSBase,  CSMask;
4557aec1d6eScindi 	/*
4567aec1d6eScindi 	 * Variables as per BKDG
4577aec1d6eScindi 	 */
4587aec1d6eScindi 	int Ilog;
4597aec1d6eScindi 	uint32_t SystemAddr = (uint32_t)(pa >> 8);
4607aec1d6eScindi 	uint64_t IntlvEn, IntlvSel;
4618a40a695Sgavinm 	uint32_t HoleOffset;
4627aec1d6eScindi 	uint32_t InputAddr, Temp;
4637aec1d6eScindi 
4648a40a695Sgavinm 	if (!mcamd_get_numprops(hdl,
4658a40a695Sgavinm 	    mc, MCAMD_PROP_NUM, &mcnum,
4668a40a695Sgavinm 	    mc, MCAMD_PROP_REV, &rev, NULL) || !mcamd_get_cfgregs(hdl,
4678a40a695Sgavinm 	    mc, MCAMD_REG_DRAMBASE, &DramBase,
4688a40a695Sgavinm 	    mc, MCAMD_REG_DRAMLIMIT, &DramLimit,
4698a40a695Sgavinm 	    mc, MCAMD_REG_DRAMHOLE, &HoleEn, NULL)) {
4707aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: failed "
4718a40a695Sgavinm 		    "to lookup required properties and registers\n");
4727aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
4737aec1d6eScindi 	}
4747aec1d6eScindi 
4757aec1d6eScindi 	/*
4768a40a695Sgavinm 	 * BKDG line to skip		Why
4778a40a695Sgavinm 	 *
4788a40a695Sgavinm 	 * F1Offset = ...		Register already read,
4798a40a695Sgavinm 	 * DramBase = Get_PCI()		and retrieved above.
4808a40a695Sgavinm 	 * DramEn = ...			Function only called for enabled nodes.
4817aec1d6eScindi 	 */
4827aec1d6eScindi 	IntlvEn = (DramBase & 0x00000700) >> 8;
4837aec1d6eScindi 	DramBase &= 0xffff0000;
4848a40a695Sgavinm 	/* DramLimit = Get_PCI()	Retrieved above */
4857aec1d6eScindi 	IntlvSel = (DramLimit & 0x00000700) >> 8;
4867aec1d6eScindi 	DramLimit |= 0x0000ffff;
4878a40a695Sgavinm 	/* HoleEn = ...			Retrieved above */
4887aec1d6eScindi 	HoleOffset = (HoleEn & 0x0000ff00) << 8;
4897aec1d6eScindi 	HoleEn &= 0x00000001;
4907aec1d6eScindi 
4917aec1d6eScindi 	if (!(DramBase <= SystemAddr && SystemAddr <= DramLimit)) {
4927aec1d6eScindi 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
4937aec1d6eScindi 		    "SystemAddr 0x%x derived from PA 0x%llx is not in the "
4947aec1d6eScindi 		    "address range [0x%x, 0x%x] of MC %d\n",
4957aec1d6eScindi 		    SystemAddr, pa, DramBase, DramLimit, (int)mcnum);
4967aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
4977aec1d6eScindi 	}
4987aec1d6eScindi 
4998a40a695Sgavinm 	if (HoleEn && SystemAddr > 0x00ffffff)
5008a40a695Sgavinm 		InputAddr = SystemAddr - HoleOffset;
5018a40a695Sgavinm 
5028a40a695Sgavinm 	InputAddr = SystemAddr - DramBase;
5038a40a695Sgavinm 
5047aec1d6eScindi 	if (IntlvEn) {
5057aec1d6eScindi 		if (IntlvSel == ((SystemAddr >> 4) & IntlvEn)) {
5067aec1d6eScindi 			switch (IntlvEn) {
5077aec1d6eScindi 			case 1:
5087aec1d6eScindi 				Ilog = 1;
5097aec1d6eScindi 				break;
5107aec1d6eScindi 			case 3:
5117aec1d6eScindi 				Ilog = 2;
5127aec1d6eScindi 				break;
5137aec1d6eScindi 			case 7:
5147aec1d6eScindi 				Ilog = 3;
5157aec1d6eScindi 				break;
5167aec1d6eScindi 			default:
5177aec1d6eScindi 				return (mcamd_set_errno(hdl,
5187aec1d6eScindi 				    EMCAMD_TREEINVALID));
5197aec1d6eScindi 			}
5208a40a695Sgavinm 			Temp = (InputAddr >> (4 + Ilog)) << 4;
5218a40a695Sgavinm 			InputAddr = (Temp | (SystemAddr & 0x0000000f));
5227aec1d6eScindi 		} else {
5237aec1d6eScindi 			/* not this node */
5247aec1d6eScindi 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
5257aec1d6eScindi 			    "Node interleaving, MC node %d not selected\n",
5267aec1d6eScindi 			    (int)mcnum);
5277aec1d6eScindi 			return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
5287aec1d6eScindi 		}
5297aec1d6eScindi 	}
5307aec1d6eScindi 
53120c794b3Sgavinm 	if (!MC_REV_MATCH(rev, MC_F_REVS_FG))
5328a40a695Sgavinm 		InputAddr <<= 4;
5337aec1d6eScindi 
5347aec1d6eScindi 	for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
5357aec1d6eScindi 	    cs = mcamd_cs_next(hdl, mc, cs)) {
5368a40a695Sgavinm 		uint64_t csnum, CSEn;
5378a40a695Sgavinm 
5388a40a695Sgavinm 		if (!mcamd_get_cfgregs(hdl,
5398a40a695Sgavinm 		    cs, MCAMD_REG_CSBASE, &CSBase,
5408a40a695Sgavinm 		    cs, MCAMD_REG_CSMASK, &CSMask,
5418a40a695Sgavinm 		    NULL) ||
5428a40a695Sgavinm 		    !mcamd_get_numprops(hdl,
5438a40a695Sgavinm 		    cs, MCAMD_PROP_NUM, &csnum,
5448a40a695Sgavinm 		    cs, MCAMD_PROP_CSBE, &CSEn,
5458a40a695Sgavinm 		    NULL)) {
5467aec1d6eScindi 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: "
5478a40a695Sgavinm 			    "failed to read cs registers\n");
5487aec1d6eScindi 			return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
5497aec1d6eScindi 		}
5507aec1d6eScindi 
5518a40a695Sgavinm 		/*
5528a40a695Sgavinm 		 * BKDG line to skip		Why
5538a40a695Sgavinm 		 *
5548a40a695Sgavinm 		 * F2Offset =			Register already read,
5558a40a695Sgavinm 		 * F2MaskOffset (rev F)		Register already read
5568a40a695Sgavinm 		 * CSBase =			Register already read
5578a40a695Sgavinm 		 * CSEn =			We only keep enabled cs.
5588a40a695Sgavinm 		 */
55920c794b3Sgavinm 		if (MC_REV_MATCH(rev, MC_F_REVS_FG)) {
5608a40a695Sgavinm 			CSBase &= 0x1ff83fe0;
5618a40a695Sgavinm 			/* CSMask = Get_PCI()		Retrieved above */
5628a40a695Sgavinm 			CSMask = (CSMask | 0x0007c01f) & 0x1fffffff;
5638a40a695Sgavinm 		} else {
5648a40a695Sgavinm 			CSBase &= 0xffe0fe00;
5658a40a695Sgavinm 			/* CSMask = Get_PCI()		Retrieved above */
5668a40a695Sgavinm 			CSMask = (CSMask | 0x001f01ff) & 0x3fffffff;
5678a40a695Sgavinm 		}
5688a40a695Sgavinm 
5698a40a695Sgavinm 		if (CSEn && (InputAddr & ~CSMask) == (CSBase & ~CSMask)) {
5708a40a695Sgavinm 			mcamd_node_t *sparecs;
5717aec1d6eScindi 
5727aec1d6eScindi 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
5737aec1d6eScindi 			    "match for chip select %d of MC %d\n", (int)csnum,
5747aec1d6eScindi 			    (int)mcnum);
5757aec1d6eScindi 
5768a40a695Sgavinm 			if ((sparecs = cs_sparedto(hdl, cs, mc)) != NULL)
5778a40a695Sgavinm 				cs = sparecs;
5788a40a695Sgavinm 
5794156fc34Sgavinm 			if ((which = mc_whichdimm(hdl, cs, pa, valid_lo,
5804156fc34Sgavinm 			    synd, syndtype)) < 0)
5817aec1d6eScindi 				return (-1); /* errno is set for us */
5827aec1d6eScindi 
5837aec1d6eScindi 			/*
5847aec1d6eScindi 			 * The BKDG algorithm drops low-order bits that
5857aec1d6eScindi 			 * are unimportant in deriving chip-select but are
5867aec1d6eScindi 			 * included in row/col/bank mapping, so do not
5877aec1d6eScindi 			 * perform offset calculation in this case.
5887aec1d6eScindi 			 */
5897aec1d6eScindi 			if (unum_fill(hdl, cs, which, InputAddr, unump, 0) < 0)
5907aec1d6eScindi 				return (-1); /* errno is set for us */
5917aec1d6eScindi 
5927aec1d6eScindi 			return (0);
5937aec1d6eScindi 		}
5947aec1d6eScindi 	}
5957aec1d6eScindi 
5967aec1d6eScindi 	mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounum: in range "
5977aec1d6eScindi 	    "for MC %d but no cs responds\n", (int)mcnum);
5987aec1d6eScindi 
5997aec1d6eScindi 	return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
6007aec1d6eScindi }
601*ae5a8bedSAndy Fiddaman #endif /* DEBUG */
6027aec1d6eScindi 
6038a40a695Sgavinm /*
6048a40a695Sgavinm  * Called for each memory controller to see if the given address is
6058a40a695Sgavinm  * mapped to this node (as determined in iaddr_gen) and, if so, which
6068a40a695Sgavinm  * chip-select on this node responds.
6078a40a695Sgavinm  */
6088a40a695Sgavinm 
6097aec1d6eScindi /*ARGSUSED*/
6107aec1d6eScindi static int
mc_patounum(struct mcamd_hdl * hdl,mcamd_node_t * mc,uint64_t pa,uint8_t valid_lo,uint32_t synd,int syndtype,mc_unum_t * unump)6117aec1d6eScindi mc_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
6124156fc34Sgavinm     uint8_t valid_lo, uint32_t synd, int syndtype, mc_unum_t *unump)
6137aec1d6eScindi {
6147aec1d6eScindi 	uint64_t iaddr;
6158a40a695Sgavinm 	mcamd_node_t *cs, *sparecs;
6167aec1d6eScindi 	int which;
6177aec1d6eScindi #ifdef DEBUG
6188a40a695Sgavinm 	mc_unum_t bkdg_unum;
6197aec1d6eScindi 	int bkdgres;
6207aec1d6eScindi 
6217aec1d6eScindi 	/*
6227aec1d6eScindi 	 * We perform the translation twice, once using the brute-force
6237aec1d6eScindi 	 * approach of the BKDG and again using a more elegant but more
6248a40a695Sgavinm 	 * difficult to review against the BKDG approach.
6257aec1d6eScindi 	 */
6267aec1d6eScindi 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "BKDG brute-force method begins\n");
6274156fc34Sgavinm 	bkdgres = mc_bkdg_patounum(hdl, mc, pa, valid_lo, synd,
6284156fc34Sgavinm 	    syndtype, &bkdg_unum);
6297aec1d6eScindi 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "BKDG brute-force method ends\n");
6307aec1d6eScindi #endif
6317aec1d6eScindi 
6327aec1d6eScindi 	if (iaddr_gen(hdl, mc, pa, &iaddr) < 0)
6337aec1d6eScindi 		return (-1); /* errno is set for us */
6347aec1d6eScindi 
6357aec1d6eScindi 	for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
6367aec1d6eScindi 	    cs = mcamd_cs_next(hdl, mc, cs)) {
6377aec1d6eScindi 		if (cs_match(hdl, iaddr, cs))
6387aec1d6eScindi 			break;
6397aec1d6eScindi 	}
6407aec1d6eScindi 
6417aec1d6eScindi 	if (cs == NULL)
6427aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
6437aec1d6eScindi 
6448a40a695Sgavinm 	/*
6458a40a695Sgavinm 	 * If the spare chip-select has been swapped in for the one just
6468a40a695Sgavinm 	 * matched then it is really the spare that we are after.  Note that
6478a40a695Sgavinm 	 * when the swap is done the csbase, csmask and CSBE of the spare
6488a40a695Sgavinm 	 * rank do not change - accesses to the bad rank (as nominated in
6498a40a695Sgavinm 	 * the Online Spare Control Register) are redirect to the spare.
6508a40a695Sgavinm 	 */
6518a40a695Sgavinm 	if ((sparecs = cs_sparedto(hdl, cs, mc)) != NULL) {
6528a40a695Sgavinm 		cs = sparecs;
6538a40a695Sgavinm 	}
6548a40a695Sgavinm 
6554156fc34Sgavinm 	if ((which = mc_whichdimm(hdl, cs, pa, valid_lo, synd,
6564156fc34Sgavinm 	    syndtype)) < 0)
6577aec1d6eScindi 		return (-1); /* errno is set for us */
6587aec1d6eScindi 
6597aec1d6eScindi 	if (unum_fill(hdl, cs, which, iaddr, unump, 1) < 0)
6607aec1d6eScindi 		return (-1); /* errno is set for us */
6617aec1d6eScindi 
6627aec1d6eScindi #ifdef DEBUG
6637aec1d6eScindi 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "bkdgres=%d res=0\n", bkdgres);
6647aec1d6eScindi 	/* offset is not checked - see note in BKDG algorithm */
6658a40a695Sgavinm 	if (bkdgres != 0) {
6668a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "BKDG alg failed while "
6678a40a695Sgavinm 		    "ours succeeded\n");
6688a40a695Sgavinm 	} else if (!(unump->unum_board == bkdg_unum.unum_board &&
6697aec1d6eScindi 	    unump->unum_chip == bkdg_unum.unum_chip &&
6707aec1d6eScindi 	    unump->unum_mc == bkdg_unum.unum_mc &&
67120c794b3Sgavinm 	    unump->unum_chan == bkdg_unum.unum_chan &&
6727aec1d6eScindi 	    unump->unum_cs == bkdg_unum.unum_cs &&
6737aec1d6eScindi 	    unump->unum_dimms[0] == bkdg_unum.unum_dimms[0] &&
6748a40a695Sgavinm 	    unump->unum_dimms[1] == bkdg_unum.unum_dimms[1])) {
6758a40a695Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_ERR,
6768a40a695Sgavinm 		    "BKDG: node %d mc %d cs %d dimm(s) %d/%d\n"
6778a40a695Sgavinm 		    "Ours: node 5d mc %d cs %d dimm(s) %d/%d\n",
6788a40a695Sgavinm 		    bkdg_unum.unum_chip, bkdg_unum.unum_mc, bkdg_unum.unum_cs,
6798a40a695Sgavinm 		    bkdg_unum.unum_dimms[0], bkdg_unum.unum_dimms[1],
6808a40a695Sgavinm 		    unump->unum_chip, unump->unum_mc, unump->unum_cs,
6818a40a695Sgavinm 		    unump->unum_dimms[0], unump->unum_dimms[1]);
6828a40a695Sgavinm 	}
6837aec1d6eScindi #endif /* DEBUG */
6847aec1d6eScindi 
6857aec1d6eScindi 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "Result: chip %d mc %d cs %d "
6867aec1d6eScindi 	    "offset 0x%llx\n", unump->unum_chip, unump->unum_mc,
6877aec1d6eScindi 	    unump->unum_cs, unump->unum_offset);
6887aec1d6eScindi 
6897aec1d6eScindi 	return (0);
6907aec1d6eScindi }
6917aec1d6eScindi 
6927aec1d6eScindi int
mcamd_patounum(struct mcamd_hdl * hdl,mcamd_node_t * root,uint64_t pa,uint8_t valid_hi,uint8_t valid_lo,uint32_t synd,int syndtype,mc_unum_t * unump)6937aec1d6eScindi mcamd_patounum(struct mcamd_hdl *hdl, mcamd_node_t *root, uint64_t pa,
6944156fc34Sgavinm     uint8_t valid_hi, uint8_t valid_lo, uint32_t synd, int syndtype,
6954156fc34Sgavinm     mc_unum_t *unump)
6967aec1d6eScindi {
6977aec1d6eScindi 	mcamd_node_t *mc;
6987aec1d6eScindi 
6997aec1d6eScindi 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mcamd_patounum: pa=0x%llx, "
7007aec1d6eScindi 	    "synd=0x%x, syndtype=%d\n", pa, synd, syndtype);
7017aec1d6eScindi 
7024156fc34Sgavinm 	if (valid_hi < MC_SYSADDR_MSB) {
7034156fc34Sgavinm 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mcamd_patounum: require "
7044156fc34Sgavinm 		    "pa<%d> to be valid\n", MC_SYSADDR_MSB);
7054156fc34Sgavinm 		return (mcamd_set_errno(hdl, EMCAMD_INSUFF_RES));
7064156fc34Sgavinm 	}
7074156fc34Sgavinm 
7087aec1d6eScindi 	if (!mcamd_synd_validate(hdl, synd, syndtype))
7097aec1d6eScindi 		return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID));
7107aec1d6eScindi 
7117aec1d6eScindi 	for (mc = mcamd_mc_next(hdl, root, NULL); mc != NULL;
7127aec1d6eScindi 	    mc = mcamd_mc_next(hdl, root, mc)) {
7134156fc34Sgavinm 		if (mc_patounum(hdl, mc, pa, valid_lo, synd,
7144156fc34Sgavinm 		    syndtype, unump) == 0)
7157aec1d6eScindi 			return (0);
7167aec1d6eScindi 
7177aec1d6eScindi 		if (mcamd_errno(hdl) != EMCAMD_NOADDR)
7187aec1d6eScindi 			break;
7197aec1d6eScindi 	}
7207aec1d6eScindi 
7217aec1d6eScindi 	return (-1); /* errno is set for us */
7227aec1d6eScindi }
723