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 * 21*20c794b3Sgavinm * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 227aec1d6eScindi * Use is subject to license terms. 237aec1d6eScindi */ 247aec1d6eScindi 257aec1d6eScindi #pragma ident "%Z%%M% %I% %E% SMI" 267aec1d6eScindi 277aec1d6eScindi /* 287aec1d6eScindi * Given a physical address and an optional syndrome, determine the 297aec1d6eScindi * name of the memory module that contains it. 307aec1d6eScindi */ 317aec1d6eScindi 327aec1d6eScindi #include <sys/errno.h> 337aec1d6eScindi #include <sys/types.h> 347aec1d6eScindi #include <sys/mc.h> 357aec1d6eScindi 367aec1d6eScindi #include <mcamd_api.h> 377aec1d6eScindi #include <mcamd_err.h> 387aec1d6eScindi 394156fc34Sgavinm #define MC_SYSADDR_MSB 39 404156fc34Sgavinm #define MC_SYSADDR_LSB 3 417aec1d6eScindi 428a40a695Sgavinm #define CSDIMM1 0x1 438a40a695Sgavinm #define CSDIMM2 0x2 447aec1d6eScindi 457aec1d6eScindi #define BITS(val, high, low) \ 467aec1d6eScindi ((val) & (((2ULL << (high)) - 1) & ~((1ULL << (low)) - 1))) 477aec1d6eScindi 488a40a695Sgavinm /* 498a40a695Sgavinm * iaddr_gen generates a "normalized" DRAM controller input address 508a40a695Sgavinm * from a system address (physical address) if it falls within the 518a40a695Sgavinm * mapped range for this memory controller. Normalisation is 528a40a695Sgavinm * performed by subtracting the node base address from the system address, 538a40a695Sgavinm * allowing from hoisting, and excising any bits being used in node 548a40a695Sgavinm * interleaving. 558a40a695Sgavinm */ 567aec1d6eScindi static int 577aec1d6eScindi iaddr_gen(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa, 587aec1d6eScindi uint64_t *iaddrp) 597aec1d6eScindi { 607aec1d6eScindi uint64_t orig = pa; 618a40a695Sgavinm uint64_t mcnum, base, lim, dramaddr, ilen, ilsel, top, holesz; 627aec1d6eScindi 638a40a695Sgavinm if (!mcamd_get_numprops(hdl, 648a40a695Sgavinm mc, MCAMD_PROP_NUM, &mcnum, 658a40a695Sgavinm mc, MCAMD_PROP_BASE_ADDR, &base, 668a40a695Sgavinm mc, MCAMD_PROP_LIM_ADDR, &lim, 678a40a695Sgavinm mc, MCAMD_PROP_ILEN, &ilen, 688a40a695Sgavinm mc, MCAMD_PROP_ILSEL, &ilsel, 698a40a695Sgavinm mc, MCAMD_PROP_DRAMHOLE_SIZE, &holesz, 708a40a695Sgavinm NULL)) { 717aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "iaddr_gen: failed to " 727aec1d6eScindi "lookup required properties"); 737aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 747aec1d6eScindi } 757aec1d6eScindi 768a40a695Sgavinm /* 778a40a695Sgavinm * A node with no mapped memory (no active chip-selects is usually 788a40a695Sgavinm * mapped with base and lim both zero. We'll cover that case and 798a40a695Sgavinm * any other where the range is 0. 808a40a695Sgavinm */ 818a40a695Sgavinm if (base == lim) 828a40a695Sgavinm return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 838a40a695Sgavinm 847aec1d6eScindi if (pa < base || pa > lim) { 857aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx not " 867aec1d6eScindi "in range [0x%llx, 0x%llx] of MC %d\n", pa, base, lim, 877aec1d6eScindi (int)mcnum); 887aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 897aec1d6eScindi } 907aec1d6eScindi 917aec1d6eScindi /* 927aec1d6eScindi * Rev E and later added the DRAM Hole Address Register for 937aec1d6eScindi * memory hoisting. In earlier revisions memory hoisting is 947aec1d6eScindi * achieved by following some algorithm to modify the CS bases etc, 957aec1d6eScindi * and this pa to unum algorithm will simply see those modified 967aec1d6eScindi * values. But if the Hole Address Register is being used then 977aec1d6eScindi * we need to reduce any address at or above 4GB by the size of 987aec1d6eScindi * the hole. 997aec1d6eScindi */ 1008a40a695Sgavinm if (holesz != 0 && pa >= 0x100000000) { 1018a40a695Sgavinm pa -= holesz; 1027aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: dram hole " 1037aec1d6eScindi "valid; pa decremented from 0x%llx to 0x%llx for " 1048a40a695Sgavinm "a dramhole size of 0x%llx\n", orig, pa, holesz); 1057aec1d6eScindi } 1067aec1d6eScindi 1077aec1d6eScindi dramaddr = BITS(pa, 39, 0) - BITS(base, 39, 24); 1087aec1d6eScindi 1097aec1d6eScindi if (ilen != 0) { 1107aec1d6eScindi int pailsel; 1117aec1d6eScindi 1127aec1d6eScindi if (ilen != 1 && ilen != 3 && ilen != 7) { 1137aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "Invalid intlven " 1147aec1d6eScindi "of %d for MC %d\n", (int)ilen, (int)mcnum); 1157aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 1167aec1d6eScindi } 1177aec1d6eScindi 1187aec1d6eScindi if ((pailsel = BITS(pa, 14, 12) >> 12 & ilen) != ilsel) { 1197aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: " 1207aec1d6eScindi "PA 0x%llx in a %d-way node interleave indicates " 1217aec1d6eScindi "selection %d, MC %d has ilsel of %d\n", 1227aec1d6eScindi pa, (int)ilen + 1, pailsel, (int)mcnum, (int)ilsel); 1237aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 1247aec1d6eScindi } 1257aec1d6eScindi 1267aec1d6eScindi if (ilen == 1) 1277aec1d6eScindi top = BITS(dramaddr, 36, 13) >> 1; 1287aec1d6eScindi else if (ilen == 3) 1297aec1d6eScindi top = BITS(dramaddr, 37, 14) >> 2; 1307aec1d6eScindi else if (ilen == 7) 1317aec1d6eScindi top = BITS(dramaddr, 38, 15) >> 3; 1327aec1d6eScindi } else { 1337aec1d6eScindi top = BITS(dramaddr, 35, 12); 1347aec1d6eScindi } 1357aec1d6eScindi 1367aec1d6eScindi *iaddrp = top | BITS(dramaddr, 11, 0); 1377aec1d6eScindi 1387aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx in range " 1397aec1d6eScindi "[0x%llx, 0x%llx] of MC %d; normalized address for cs compare " 1407aec1d6eScindi "is 0x%llx\n", pa, base, lim, (int)mcnum, *iaddrp); 1417aec1d6eScindi 1427aec1d6eScindi return (0); 1437aec1d6eScindi } 1447aec1d6eScindi 1458a40a695Sgavinm /* 1468a40a695Sgavinm * cs_match determines whether the given DRAM controller input address 1478a40a695Sgavinm * would be responded to by the given chip-select (which may or may not 1488a40a695Sgavinm * be interleaved with other chip-selects). Since we include nodes 1498a40a695Sgavinm * for spare chip-selects (if any) and those marked TestFail (if any) 1508a40a695Sgavinm * we must check chip-select-bank-enable. 1518a40a695Sgavinm */ 1527aec1d6eScindi static int 1537aec1d6eScindi cs_match(struct mcamd_hdl *hdl, uint64_t iaddr, mcamd_node_t *cs) 1547aec1d6eScindi { 1558a40a695Sgavinm uint64_t csnum, csbase, csmask, csbe; 1568a40a695Sgavinm int match = 0; 1577aec1d6eScindi 1588a40a695Sgavinm if (!mcamd_get_numprops(hdl, 1598a40a695Sgavinm cs, MCAMD_PROP_NUM, &csnum, 1608a40a695Sgavinm cs, MCAMD_PROP_BASE_ADDR, &csbase, 1618a40a695Sgavinm cs, MCAMD_PROP_MASK, &csmask, 1628a40a695Sgavinm cs, MCAMD_PROP_CSBE, &csbe, 1638a40a695Sgavinm NULL)) { 1647aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_match: failed to lookup " 1657aec1d6eScindi "required properties\n"); 1667aec1d6eScindi return (0); 1677aec1d6eScindi } 1687aec1d6eScindi 1698a40a695Sgavinm if (csbe) { 1707aec1d6eScindi match = ((iaddr & ~csmask) == (csbase & ~csmask)); 1717aec1d6eScindi 1728a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_match: iaddr 0x%llx " 1738a40a695Sgavinm "does %smatch CS %d (base 0x%llx, mask 0x%llx)\n", iaddr, 1747aec1d6eScindi match ? "" : "not ", (int)csnum, csbase, csmask); 1758a40a695Sgavinm } else { 1768a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_match: iaddr 0x%llx " 1778a40a695Sgavinm "does not match disabled CS %d\n", iaddr, (int)csnum); 1788a40a695Sgavinm } 1797aec1d6eScindi 1807aec1d6eScindi return (match); 1817aec1d6eScindi } 1827aec1d6eScindi 1838a40a695Sgavinm /* 1848a40a695Sgavinm * Given a chip-select node determine whether it has been substituted 1858a40a695Sgavinm * by the online spare chip-select. 1868a40a695Sgavinm */ 1878a40a695Sgavinm static mcamd_node_t * 1888a40a695Sgavinm cs_sparedto(struct mcamd_hdl *hdl, mcamd_node_t *cs, mcamd_node_t *mc) 1898a40a695Sgavinm { 1908a40a695Sgavinm uint64_t csnum, badcsnum, sparecsnum, tmpcsnum; 1918a40a695Sgavinm 1928a40a695Sgavinm if (!mcamd_get_numprops(hdl, 1938a40a695Sgavinm cs, MCAMD_PROP_NUM, &csnum, 1948a40a695Sgavinm mc, MCAMD_PROP_BADCS, &badcsnum, 1958a40a695Sgavinm mc, MCAMD_PROP_SPARECS, &sparecsnum, 1968a40a695Sgavinm NULL)) { 1978a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: failed to " 1988a40a695Sgavinm "lookup required properties\n"); 1998a40a695Sgavinm return (NULL); 2008a40a695Sgavinm } 2018a40a695Sgavinm 2028a40a695Sgavinm if ((badcsnum == MC_INVALNUM && sparecsnum == MC_INVALNUM) || 2038a40a695Sgavinm csnum != badcsnum) 2048a40a695Sgavinm return (NULL); 2058a40a695Sgavinm 2068a40a695Sgavinm for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL; 2078a40a695Sgavinm cs = mcamd_cs_next(hdl, mc, cs)) { 2088a40a695Sgavinm if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &tmpcsnum)) { 2098a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: " 2108a40a695Sgavinm "fail to lookup csnum - cannot reroute to spare\n"); 2118a40a695Sgavinm return (NULL); 2128a40a695Sgavinm } 2138a40a695Sgavinm if (tmpcsnum == sparecsnum) 2148a40a695Sgavinm break; 2158a40a695Sgavinm } 2168a40a695Sgavinm 2178a40a695Sgavinm if (cs != NULL) { 2188a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_sparedto: cs#%d is " 2198a40a695Sgavinm "redirected to active online spare of cs#%d\n", csnum, 2208a40a695Sgavinm sparecsnum); 2218a40a695Sgavinm } else { 2228a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: cs#%d is " 2238a40a695Sgavinm "redirected but cannot find spare cs# - cannout reroute to " 2248a40a695Sgavinm "cs#%d\n", csnum, sparecsnum); 2258a40a695Sgavinm } 2268a40a695Sgavinm 2278a40a695Sgavinm return (cs); 2288a40a695Sgavinm } 2298a40a695Sgavinm 2308a40a695Sgavinm /* 2318a40a695Sgavinm * Having determined which node and chip-select an address maps to, 2328a40a695Sgavinm * as well as whether it is a dimm1, dimm2 or dimm1/dimm2 pair 2338a40a695Sgavinm * involved, fill the unum structure including an optional dimm offset 2348a40a695Sgavinm * member. 2358a40a695Sgavinm */ 2367aec1d6eScindi static int 2377aec1d6eScindi unum_fill(struct mcamd_hdl *hdl, mcamd_node_t *cs, int which, 2388a40a695Sgavinm uint64_t iaddr, mc_unum_t *unump, int incloff) 2397aec1d6eScindi { 2408a40a695Sgavinm uint64_t chipnum, csnum, dimm1, dimm2, ranknum; 2417aec1d6eScindi mcamd_node_t *mc, *dimm; 2427aec1d6eScindi int offsetdimm; 2438a40a695Sgavinm int i; 2447aec1d6eScindi 2457aec1d6eScindi if ((mc = mcamd_cs_mc(hdl, cs)) == NULL || 2468a40a695Sgavinm !mcamd_get_numprops(hdl, 2478a40a695Sgavinm mc, MCAMD_PROP_NUM, &chipnum, 2488a40a695Sgavinm cs, MCAMD_PROP_NUM, &csnum, 2498a40a695Sgavinm cs, MCAMD_PROP_DIMMRANK, &ranknum, 2508a40a695Sgavinm NULL)) { 2517aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to " 2527aec1d6eScindi "lookup required properties\n"); 2537aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 2547aec1d6eScindi } 2557aec1d6eScindi 2568a40a695Sgavinm if ((which & CSDIMM1) && 2578a40a695Sgavinm !mcamd_get_numprop(hdl, cs, MCAMD_PROP_CSDIMM1, &dimm1) || 2588a40a695Sgavinm (which & CSDIMM2) && 2598a40a695Sgavinm !mcamd_get_numprop(hdl, cs, MCAMD_PROP_CSDIMM2, &dimm2)) { 2607aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to " 2618a40a695Sgavinm "lookup dimm1/dimm2 properties\n"); 2627aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 2637aec1d6eScindi } 2647aec1d6eScindi 2657aec1d6eScindi unump->unum_board = 0; 266*20c794b3Sgavinm unump->unum_chip = (int)chipnum; 2677aec1d6eScindi unump->unum_mc = 0; 268*20c794b3Sgavinm unump->unum_chan = MC_INVALNUM; 269*20c794b3Sgavinm unump->unum_cs = (int)csnum; 270*20c794b3Sgavinm unump->unum_rank = (int)ranknum; 2717aec1d6eScindi 2727aec1d6eScindi for (i = 0; i < MC_UNUM_NDIMM; i++) { 2738a40a695Sgavinm unump->unum_dimms[i] = MC_INVALNUM; 2747aec1d6eScindi } 2757aec1d6eScindi switch (which) { 2768a40a695Sgavinm case CSDIMM1: 277*20c794b3Sgavinm unump->unum_dimms[0] = (int)dimm1; 278*20c794b3Sgavinm offsetdimm = (int)dimm1; 2797aec1d6eScindi break; 2808a40a695Sgavinm case CSDIMM2: 281*20c794b3Sgavinm unump->unum_dimms[0] = (int)dimm2; 282*20c794b3Sgavinm offsetdimm = (int)dimm2; 2837aec1d6eScindi break; 2848a40a695Sgavinm case CSDIMM1 | CSDIMM2: 285*20c794b3Sgavinm unump->unum_dimms[0] = (int)dimm1; 286*20c794b3Sgavinm unump->unum_dimms[1] = (int)dimm2; 287*20c794b3Sgavinm offsetdimm = (int)dimm1; 2887aec1d6eScindi break; 2897aec1d6eScindi } 2907aec1d6eScindi 2917aec1d6eScindi if (!incloff) { 2927aec1d6eScindi unump->unum_offset = MCAMD_RC_INVALID_OFFSET; 2937aec1d6eScindi return (0); 2947aec1d6eScindi } 2957aec1d6eScindi 2967aec1d6eScindi /* 2977aec1d6eScindi * We wish to calculate a dimm offset. In the paired case we will 2988a40a695Sgavinm * lookup dimm1 (see offsetdimm above). 2997aec1d6eScindi */ 3007aec1d6eScindi for (dimm = mcamd_dimm_next(hdl, mc, NULL); dimm != NULL; 3017aec1d6eScindi dimm = mcamd_dimm_next(hdl, mc, dimm)) { 3027aec1d6eScindi uint64_t dnum; 3037aec1d6eScindi if (!mcamd_get_numprop(hdl, dimm, MCAMD_PROP_NUM, &dnum)) { 3047aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed " 3057aec1d6eScindi "to lookup dimm number property\n"); 3067aec1d6eScindi continue; 3077aec1d6eScindi } 3087aec1d6eScindi if (dnum == offsetdimm) 3097aec1d6eScindi break; 3107aec1d6eScindi } 3117aec1d6eScindi 3127aec1d6eScindi if (dimm == NULL) { 3137aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to " 3147aec1d6eScindi "find dimm with number %d for offset calculation\n", 3157aec1d6eScindi offsetdimm); 3167aec1d6eScindi unump->unum_offset = MCAMD_RC_INVALID_OFFSET; 3177aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 3187aec1d6eScindi } 3197aec1d6eScindi 3207aec1d6eScindi /* 3217aec1d6eScindi * mc_pa_to_offset sets the offset to an invalid value if 3227aec1d6eScindi * it hits an error. 3237aec1d6eScindi */ 3248a40a695Sgavinm (void) mc_pa_to_offset(hdl, mc, cs, iaddr, &unump->unum_offset); 3257aec1d6eScindi 3267aec1d6eScindi return (0); 3277aec1d6eScindi } 3287aec1d6eScindi 3297aec1d6eScindi /* 3308a40a695Sgavinm * We have translated a system address to a (node, chip-select), and wish 3318a40a695Sgavinm * to determine the associated dimm or dimms. 3328a40a695Sgavinm * 3338a40a695Sgavinm * A (node, chip-select) pair identifies one (in 64-bit MC mode) or two (in 3348a40a695Sgavinm * 128-bit MC mode) DIMMs. In the case of a single dimm it is usually in a 3358a40a695Sgavinm * lodimm (channel A) slot, but if mismatched dimm support is present it may 3368a40a695Sgavinm * be an updimm (channel B). 3378a40a695Sgavinm * 3388a40a695Sgavinm * Where just one dimm is associated with the chip-select we are done. 3398a40a695Sgavinm * Where there are two dimms associated with the chip-select we can 3408a40a695Sgavinm * use the ECC type and/or syndrome to determine which of the pair we 3418a40a695Sgavinm * resolve to, if the error is correctable. If the error is uncorrectable 3428a40a695Sgavinm * then in 64/8 ECC mode we can still resolve to a single dimm (since ECC 3438a40a695Sgavinm * is calculated and checked on each half of the data separately), but 3448a40a695Sgavinm * in ChipKill mode we cannot resolve down to a single dimm. 3457aec1d6eScindi */ 3467aec1d6eScindi static int 3478a40a695Sgavinm mc_whichdimm(struct mcamd_hdl *hdl, mcamd_node_t *cs, uint64_t pa, 3484156fc34Sgavinm uint8_t valid_lo, uint32_t synd, int syndtype) 3497aec1d6eScindi { 3507aec1d6eScindi int lobit, hibit, data, check; 3518a40a695Sgavinm uint64_t dimm1, dimm2; 3528a40a695Sgavinm uint_t sym, pat; 3538a40a695Sgavinm int ndimm; 3547aec1d6eScindi 3558a40a695Sgavinm /* 3568a40a695Sgavinm * Read the associated dimm instance numbers. The provider must 3578a40a695Sgavinm * assure that if there is just one dimm then it is in the first 3588a40a695Sgavinm * property, and if there are two then the first must be on 3598a40a695Sgavinm * channel A. 3608a40a695Sgavinm */ 3618a40a695Sgavinm if (!mcamd_get_numprops(hdl, 3628a40a695Sgavinm cs, MCAMD_PROP_CSDIMM1, &dimm1, 3638a40a695Sgavinm cs, MCAMD_PROP_CSDIMM2, &dimm2, 3648a40a695Sgavinm NULL)) { 3658a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_whichdimm: failed to " 3668a40a695Sgavinm "lookup required properties"); 3678a40a695Sgavinm return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 3688a40a695Sgavinm } 3698a40a695Sgavinm ndimm = (dimm1 != MC_INVALNUM) + (dimm2 != MC_INVALNUM); 3708a40a695Sgavinm if (ndimm == 0) { 3718a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_whichdimm: found no " 3728a40a695Sgavinm "dimms associated with chip-select"); 3737aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 3747aec1d6eScindi } 3757aec1d6eScindi 3768a40a695Sgavinm if (ndimm == 1) { 3778a40a695Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: just one " 3788a40a695Sgavinm "dimm associated with this chip-select"); 3798a40a695Sgavinm return (CSDIMM1); 3807aec1d6eScindi } 3817aec1d6eScindi 3827aec1d6eScindi /* 3837aec1d6eScindi * 64/8 ECC is checked separately for the upper and lower 3847aec1d6eScindi * halves, so even an uncorrectable error is contained within 3854156fc34Sgavinm * one of the two halves. If we have sufficient address resolution 3864156fc34Sgavinm * then we can determine which DIMM. 3877aec1d6eScindi */ 3888a40a695Sgavinm if (syndtype == AMD_SYNDTYPE_ECC) { 3894156fc34Sgavinm if (valid_lo <= MC_SYSADDR_LSB) { 3904156fc34Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: 64/8 " 3914156fc34Sgavinm "ECC in 128-bit mode, PA 0x%llx is in %s half\n", 3924156fc34Sgavinm pa, pa & 0x8 ? "upper" : "lower"); 3934156fc34Sgavinm return (pa & 0x8 ? CSDIMM2 : CSDIMM1); 3944156fc34Sgavinm } else { 3954156fc34Sgavinm mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: " 3964156fc34Sgavinm "64/8 ECC in 128-bit mode, PA 0x%llx with least " 3974156fc34Sgavinm "significant valid bit %d cannot be resolved to " 3984156fc34Sgavinm "a single DIMM\n", pa, valid_lo); 3994156fc34Sgavinm return (mcamd_set_errno(hdl, EMCAMD_INSUFF_RES)); 4004156fc34Sgavinm } 4017aec1d6eScindi } 4027aec1d6eScindi 4037aec1d6eScindi /* 4048a40a695Sgavinm * ChipKill ECC 4057aec1d6eScindi */ 4067aec1d6eScindi if (mcamd_cksynd_decode(hdl, synd, &sym, &pat)) { 4077aec1d6eScindi /* 4087aec1d6eScindi * A correctable ChipKill syndrome and we can tell 4097aec1d6eScindi * which half the error was in from the symbol number. 4107aec1d6eScindi */ 4117aec1d6eScindi if (mcamd_cksym_decode(hdl, sym, &lobit, &hibit, &data, 4127aec1d6eScindi &check) == 0) 4137aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID)); 4147aec1d6eScindi 4157aec1d6eScindi if (data && hibit <= 63 || check && hibit <= 7) { 4167aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: " 4177aec1d6eScindi "ChipKill symbol %d (%s %d..%d), so LODIMM\n", sym, 4187aec1d6eScindi data ? "data" : "check", lobit, hibit); 4198a40a695Sgavinm return (CSDIMM1); 4207aec1d6eScindi } else { 4217aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: " 4227aec1d6eScindi "ChipKill symbol %d (%s %d..%d), so UPDIMM\n", sym, 4237aec1d6eScindi data ? "data" : "check", lobit, hibit); 4248a40a695Sgavinm return (CSDIMM2); 4257aec1d6eScindi } 4267aec1d6eScindi } else { 4277aec1d6eScindi /* 4287aec1d6eScindi * An uncorrectable error while in ChipKill ECC mode - can't 4297aec1d6eScindi * tell which dimm or dimms the errors lie within. 4307aec1d6eScindi */ 4317aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichhdimm: " 4327aec1d6eScindi "uncorrectable ChipKill, could be either LODIMM " 4337aec1d6eScindi "or UPDIMM\n"); 4348a40a695Sgavinm return (CSDIMM1 | CSDIMM2); 4357aec1d6eScindi } 4367aec1d6eScindi } 4377aec1d6eScindi 4387aec1d6eScindi /* 4398a40a695Sgavinm * Brute-force BKDG pa to cs translation, coded to look as much like the 4407aec1d6eScindi * BKDG code as possible. 4417aec1d6eScindi */ 4427aec1d6eScindi static int 4437aec1d6eScindi mc_bkdg_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa, 4444156fc34Sgavinm uint8_t valid_lo, uint32_t synd, int syndtype, 4454156fc34Sgavinm mc_unum_t *unump) 4467aec1d6eScindi { 4477aec1d6eScindi int which; 4488a40a695Sgavinm uint64_t mcnum, rev; 4497aec1d6eScindi mcamd_node_t *cs; 4507aec1d6eScindi /* 4518a40a695Sgavinm * Raw registers as per BKDG 4528a40a695Sgavinm */ 4538a40a695Sgavinm uint32_t HoleEn; 4548a40a695Sgavinm uint32_t DramBase, DramLimit; 4558a40a695Sgavinm uint32_t CSBase, CSMask; 4568a40a695Sgavinm /* 4577aec1d6eScindi * Variables as per BKDG 4587aec1d6eScindi */ 4597aec1d6eScindi int Ilog; 4607aec1d6eScindi uint32_t SystemAddr = (uint32_t)(pa >> 8); 4617aec1d6eScindi uint64_t IntlvEn, IntlvSel; 4628a40a695Sgavinm uint32_t HoleOffset; 4637aec1d6eScindi uint32_t InputAddr, Temp; 4647aec1d6eScindi 4658a40a695Sgavinm if (!mcamd_get_numprops(hdl, 4668a40a695Sgavinm mc, MCAMD_PROP_NUM, &mcnum, 4678a40a695Sgavinm mc, MCAMD_PROP_REV, &rev, NULL) || !mcamd_get_cfgregs(hdl, 4688a40a695Sgavinm mc, MCAMD_REG_DRAMBASE, &DramBase, 4698a40a695Sgavinm mc, MCAMD_REG_DRAMLIMIT, &DramLimit, 4708a40a695Sgavinm mc, MCAMD_REG_DRAMHOLE, &HoleEn, NULL)) { 4717aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: failed " 4728a40a695Sgavinm "to lookup required properties and registers\n"); 4737aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 4747aec1d6eScindi } 4757aec1d6eScindi 4767aec1d6eScindi /* 4778a40a695Sgavinm * BKDG line to skip Why 4788a40a695Sgavinm * 4798a40a695Sgavinm * F1Offset = ... Register already read, 4808a40a695Sgavinm * DramBase = Get_PCI() and retrieved above. 4818a40a695Sgavinm * DramEn = ... Function only called for enabled nodes. 4827aec1d6eScindi */ 4837aec1d6eScindi IntlvEn = (DramBase & 0x00000700) >> 8; 4847aec1d6eScindi DramBase &= 0xffff0000; 4858a40a695Sgavinm /* DramLimit = Get_PCI() Retrieved above */ 4867aec1d6eScindi IntlvSel = (DramLimit & 0x00000700) >> 8; 4877aec1d6eScindi DramLimit |= 0x0000ffff; 4888a40a695Sgavinm /* HoleEn = ... Retrieved above */ 4897aec1d6eScindi HoleOffset = (HoleEn & 0x0000ff00) << 8; 4907aec1d6eScindi HoleEn &= 0x00000001; 4917aec1d6eScindi 4927aec1d6eScindi if (!(DramBase <= SystemAddr && SystemAddr <= DramLimit)) { 4937aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: " 4947aec1d6eScindi "SystemAddr 0x%x derived from PA 0x%llx is not in the " 4957aec1d6eScindi "address range [0x%x, 0x%x] of MC %d\n", 4967aec1d6eScindi SystemAddr, pa, DramBase, DramLimit, (int)mcnum); 4977aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 4987aec1d6eScindi } 4997aec1d6eScindi 5008a40a695Sgavinm if (HoleEn && SystemAddr > 0x00ffffff) 5018a40a695Sgavinm InputAddr = SystemAddr - HoleOffset; 5028a40a695Sgavinm 5038a40a695Sgavinm InputAddr = SystemAddr - DramBase; 5048a40a695Sgavinm 5057aec1d6eScindi if (IntlvEn) { 5067aec1d6eScindi if (IntlvSel == ((SystemAddr >> 4) & IntlvEn)) { 5077aec1d6eScindi switch (IntlvEn) { 5087aec1d6eScindi case 1: 5097aec1d6eScindi Ilog = 1; 5107aec1d6eScindi break; 5117aec1d6eScindi case 3: 5127aec1d6eScindi Ilog = 2; 5137aec1d6eScindi break; 5147aec1d6eScindi case 7: 5157aec1d6eScindi Ilog = 3; 5167aec1d6eScindi break; 5177aec1d6eScindi default: 5187aec1d6eScindi return (mcamd_set_errno(hdl, 5197aec1d6eScindi EMCAMD_TREEINVALID)); 5207aec1d6eScindi } 5218a40a695Sgavinm Temp = (InputAddr >> (4 + Ilog)) << 4; 5228a40a695Sgavinm InputAddr = (Temp | (SystemAddr & 0x0000000f)); 5237aec1d6eScindi } else { 5247aec1d6eScindi /* not this node */ 5257aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: " 5267aec1d6eScindi "Node interleaving, MC node %d not selected\n", 5277aec1d6eScindi (int)mcnum); 5287aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 5297aec1d6eScindi } 5307aec1d6eScindi } 5317aec1d6eScindi 532*20c794b3Sgavinm if (!MC_REV_MATCH(rev, MC_F_REVS_FG)) 5338a40a695Sgavinm InputAddr <<= 4; 5347aec1d6eScindi 5357aec1d6eScindi for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL; 5367aec1d6eScindi cs = mcamd_cs_next(hdl, mc, cs)) { 5378a40a695Sgavinm uint64_t csnum, CSEn; 5387aec1d6eScindi 5398a40a695Sgavinm if (!mcamd_get_cfgregs(hdl, 5408a40a695Sgavinm cs, MCAMD_REG_CSBASE, &CSBase, 5418a40a695Sgavinm cs, MCAMD_REG_CSMASK, &CSMask, 5428a40a695Sgavinm NULL) || 5438a40a695Sgavinm !mcamd_get_numprops(hdl, 5448a40a695Sgavinm cs, MCAMD_PROP_NUM, &csnum, 5458a40a695Sgavinm cs, MCAMD_PROP_CSBE, &CSEn, 5468a40a695Sgavinm NULL)) { 5477aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: " 5488a40a695Sgavinm "failed to read cs registers\n"); 5497aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID)); 5507aec1d6eScindi } 5517aec1d6eScindi 5528a40a695Sgavinm /* 5538a40a695Sgavinm * BKDG line to skip Why 5548a40a695Sgavinm * 5558a40a695Sgavinm * F2Offset = Register already read, 5568a40a695Sgavinm * F2MaskOffset (rev F) Register already read 5578a40a695Sgavinm * CSBase = Register already read 5588a40a695Sgavinm * CSEn = We only keep enabled cs. 5598a40a695Sgavinm */ 560*20c794b3Sgavinm if (MC_REV_MATCH(rev, MC_F_REVS_FG)) { 5618a40a695Sgavinm CSBase &= 0x1ff83fe0; 5628a40a695Sgavinm /* CSMask = Get_PCI() Retrieved above */ 5638a40a695Sgavinm CSMask = (CSMask | 0x0007c01f) & 0x1fffffff; 5648a40a695Sgavinm } else { 5657aec1d6eScindi CSBase &= 0xffe0fe00; 5668a40a695Sgavinm /* CSMask = Get_PCI() Retrieved above */ 5677aec1d6eScindi CSMask = (CSMask | 0x001f01ff) & 0x3fffffff; 5688a40a695Sgavinm } 5697aec1d6eScindi 5708a40a695Sgavinm if (CSEn && (InputAddr & ~CSMask) == (CSBase & ~CSMask)) { 5718a40a695Sgavinm mcamd_node_t *sparecs; 5728a40a695Sgavinm 5737aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: " 5747aec1d6eScindi "match for chip select %d of MC %d\n", (int)csnum, 5757aec1d6eScindi (int)mcnum); 5767aec1d6eScindi 5778a40a695Sgavinm if ((sparecs = cs_sparedto(hdl, cs, mc)) != NULL) 5788a40a695Sgavinm cs = sparecs; 5798a40a695Sgavinm 5804156fc34Sgavinm if ((which = mc_whichdimm(hdl, cs, pa, valid_lo, 5814156fc34Sgavinm synd, syndtype)) < 0) 5827aec1d6eScindi return (-1); /* errno is set for us */ 5837aec1d6eScindi 5847aec1d6eScindi /* 5857aec1d6eScindi * The BKDG algorithm drops low-order bits that 5867aec1d6eScindi * are unimportant in deriving chip-select but are 5877aec1d6eScindi * included in row/col/bank mapping, so do not 5887aec1d6eScindi * perform offset calculation in this case. 5897aec1d6eScindi */ 5907aec1d6eScindi if (unum_fill(hdl, cs, which, InputAddr, unump, 0) < 0) 5917aec1d6eScindi return (-1); /* errno is set for us */ 5927aec1d6eScindi 5937aec1d6eScindi return (0); 5947aec1d6eScindi } 5957aec1d6eScindi } 5967aec1d6eScindi 5977aec1d6eScindi mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounum: in range " 5987aec1d6eScindi "for MC %d but no cs responds\n", (int)mcnum); 5997aec1d6eScindi 6007aec1d6eScindi return (mcamd_set_errno(hdl, EMCAMD_NOADDR)); 6017aec1d6eScindi } 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 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 && 671*20c794b3Sgavinm 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 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