xref: /illumos-gate/usr/src/common/mc/mc-amd/mcamd_patounum.c (revision 34a0f871d192b33b865455a8812a3d34c1866315)
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  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
22  * Use is subject to license terms.
23  */
24 
25 #pragma ident	"%Z%%M%	%I%	%E% SMI"
26 
27 /*
28  * Given a physical address and an optional syndrome, determine the
29  * name of the memory module that contains it.
30  */
31 
32 #include <sys/errno.h>
33 #include <sys/types.h>
34 #include <sys/mc.h>
35 
36 #include <mcamd_api.h>
37 #include <mcamd_err.h>
38 
39 #define	MC_SYSADDR_MSB	39
40 #define	MC_SYSADDR_LSB	3
41 
42 #define	CSDIMM1	0x1
43 #define	CSDIMM2	0x2
44 
45 #define	BITS(val, high, low) \
46 	((val) & (((2ULL << (high)) - 1) & ~((1ULL << (low)) - 1)))
47 
48 /*
49  * iaddr_gen generates a "normalized" DRAM controller input address
50  * from a system address (physical address) if it falls within the
51  * mapped range for this memory controller.  Normalisation is
52  * performed by subtracting the node base address from the system address,
53  * allowing from hoisting, and excising any bits being used in node
54  * interleaving.
55  */
56 static int
57 iaddr_gen(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
58     uint64_t *iaddrp)
59 {
60 	uint64_t orig = pa;
61 	uint64_t mcnum, base, lim, dramaddr, ilen, ilsel, top, holesz;
62 
63 	if (!mcamd_get_numprops(hdl,
64 	    mc, MCAMD_PROP_NUM, &mcnum,
65 	    mc, MCAMD_PROP_BASE_ADDR, &base,
66 	    mc, MCAMD_PROP_LIM_ADDR, &lim,
67 	    mc, MCAMD_PROP_ILEN, &ilen,
68 	    mc, MCAMD_PROP_ILSEL, &ilsel,
69 	    mc, MCAMD_PROP_DRAMHOLE_SIZE, &holesz,
70 	    NULL)) {
71 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "iaddr_gen: failed to "
72 		    "lookup required properties");
73 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
74 	}
75 
76 	/*
77 	 * A node with no mapped memory (no active chip-selects is usually
78 	 * mapped with base and lim both zero.  We'll cover that case and
79 	 * any other where the range is 0.
80 	 */
81 	if (base == lim)
82 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
83 
84 	if (pa < base || pa > lim) {
85 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx not "
86 		    "in range [0x%llx, 0x%llx] of MC %d\n", pa, base, lim,
87 		    (int)mcnum);
88 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
89 	}
90 
91 	/*
92 	 * Rev E and later added the DRAM Hole Address Register for
93 	 * memory hoisting.  In earlier revisions memory hoisting is
94 	 * achieved by following some algorithm to modify the CS bases etc,
95 	 * and this pa to unum algorithm will simply see those modified
96 	 * values.  But if the Hole Address Register is being used then
97 	 * we need to reduce any address at or above 4GB by the size of
98 	 * the hole.
99 	 */
100 	if (holesz != 0 && pa >= 0x100000000) {
101 		pa -= holesz;
102 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: dram hole "
103 		    "valid; pa decremented from 0x%llx to 0x%llx for "
104 		    "a dramhole size of 0x%llx\n", orig, pa, holesz);
105 	}
106 
107 	dramaddr = BITS(pa, 39, 0) - BITS(base, 39, 24);
108 
109 	if (ilen != 0) {
110 		int pailsel;
111 
112 		if (ilen != 1 && ilen != 3 && ilen != 7) {
113 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "Invalid intlven "
114 			    "of %d for MC %d\n", (int)ilen, (int)mcnum);
115 			return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
116 		}
117 
118 		if ((pailsel = BITS(pa, 14, 12) >> 12 & ilen) != ilsel) {
119 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: "
120 			    "PA 0x%llx in a %d-way node interleave indicates "
121 			    "selection %d, MC %d has ilsel of %d\n",
122 			    pa, (int)ilen + 1, pailsel, (int)mcnum, (int)ilsel);
123 			return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
124 		}
125 
126 		if (ilen == 1)
127 			top = BITS(dramaddr, 36, 13) >> 1;
128 		else if (ilen == 3)
129 			top = BITS(dramaddr, 37, 14) >> 2;
130 		else if (ilen == 7)
131 			top = BITS(dramaddr, 38, 15) >> 3;
132 	} else {
133 		top = BITS(dramaddr, 35, 12);
134 	}
135 
136 	*iaddrp = top | BITS(dramaddr, 11, 0);
137 
138 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx in range "
139 	    "[0x%llx, 0x%llx] of MC %d; normalized address for cs compare "
140 	    "is 0x%llx\n", pa, base, lim, (int)mcnum, *iaddrp);
141 
142 	return (0);
143 }
144 
145 /*
146  * cs_match determines whether the given DRAM controller input address
147  * would be responded to by the given chip-select (which may or may not
148  * be interleaved with other chip-selects).  Since we include nodes
149  * for spare chip-selects (if any) and those marked TestFail (if any)
150  * we must check chip-select-bank-enable.
151  */
152 static int
153 cs_match(struct mcamd_hdl *hdl, uint64_t iaddr, mcamd_node_t *cs)
154 {
155 	uint64_t csnum, csbase, csmask, csbe;
156 	int match = 0;
157 
158 	if (!mcamd_get_numprops(hdl,
159 	    cs, MCAMD_PROP_NUM, &csnum,
160 	    cs, MCAMD_PROP_BASE_ADDR, &csbase,
161 	    cs, MCAMD_PROP_MASK, &csmask,
162 	    cs, MCAMD_PROP_CSBE, &csbe,
163 	    NULL)) {
164 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_match: failed to lookup "
165 		    "required properties\n");
166 		return (0);
167 	}
168 
169 	if (csbe) {
170 		match = ((iaddr & ~csmask) == (csbase & ~csmask));
171 
172 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_match: iaddr 0x%llx "
173 		    "does %smatch CS %d (base 0x%llx, mask 0x%llx)\n", iaddr,
174 		    match ? "" : "not ", (int)csnum, csbase, csmask);
175 	} else {
176 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_match: iaddr 0x%llx "
177 		    "does not match disabled CS %d\n", iaddr, (int)csnum);
178 	}
179 
180 	return (match);
181 }
182 
183 /*
184  * Given a chip-select node determine whether it has been substituted
185  * by the online spare chip-select.
186  */
187 static mcamd_node_t *
188 cs_sparedto(struct mcamd_hdl *hdl, mcamd_node_t *cs, mcamd_node_t *mc)
189 {
190 	uint64_t csnum, badcsnum, sparecsnum, tmpcsnum;
191 
192 	if (!mcamd_get_numprops(hdl,
193 	    cs, MCAMD_PROP_NUM, &csnum,
194 	    mc, MCAMD_PROP_BADCS, &badcsnum,
195 	    mc, MCAMD_PROP_SPARECS, &sparecsnum,
196 	    NULL)) {
197 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: failed to "
198 		    "lookup required properties\n");
199 		return (NULL);
200 	}
201 
202 	if ((badcsnum == MC_INVALNUM && sparecsnum == MC_INVALNUM) ||
203 	    csnum != badcsnum)
204 		return (NULL);
205 
206 	for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
207 	    cs = mcamd_cs_next(hdl, mc, cs)) {
208 		if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &tmpcsnum)) {
209 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: "
210 			    "fail to lookup csnum - cannot reroute to spare\n");
211 			return (NULL);
212 		}
213 		if (tmpcsnum == sparecsnum)
214 			break;
215 	}
216 
217 	if (cs != NULL) {
218 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_sparedto: cs#%d is "
219 		    "redirected to active online spare of cs#%d\n", csnum,
220 		    sparecsnum);
221 	} else {
222 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_sparedto: cs#%d is "
223 		    "redirected but cannot find spare cs# - cannout reroute to "
224 		    "cs#%d\n", csnum, sparecsnum);
225 	}
226 
227 	return (cs);
228 }
229 
230 /*
231  * Having determined which node and chip-select an address maps to,
232  * as well as whether it is a dimm1, dimm2 or dimm1/dimm2 pair
233  * involved, fill the unum structure including an optional dimm offset
234  * member.
235  */
236 static int
237 unum_fill(struct mcamd_hdl *hdl, mcamd_node_t *cs, int which,
238     uint64_t iaddr, mc_unum_t *unump, int incloff)
239 {
240 	uint64_t chipnum, csnum, dimm1, dimm2, ranknum;
241 	mcamd_node_t *mc, *dimm;
242 	int offsetdimm;
243 	int i;
244 
245 	if ((mc = mcamd_cs_mc(hdl, cs)) == NULL ||
246 	    !mcamd_get_numprops(hdl,
247 	    mc, MCAMD_PROP_NUM, &chipnum,
248 	    cs, MCAMD_PROP_NUM, &csnum,
249 	    cs, MCAMD_PROP_DIMMRANK, &ranknum,
250 	    NULL)) {
251 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
252 		    "lookup required properties\n");
253 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
254 	}
255 
256 	if ((which & CSDIMM1) &&
257 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_CSDIMM1, &dimm1) ||
258 	    (which & CSDIMM2) &&
259 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_CSDIMM2, &dimm2)) {
260 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
261 		    "lookup dimm1/dimm2 properties\n");
262 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
263 	}
264 
265 	unump->unum_board = 0;
266 	unump->unum_chip = chipnum;
267 	unump->unum_mc = 0;
268 	unump->unum_cs = csnum;
269 	unump->unum_rank = ranknum;
270 
271 	for (i = 0; i < MC_UNUM_NDIMM; i++) {
272 		unump->unum_dimms[i] = MC_INVALNUM;
273 	}
274 	switch (which) {
275 	case CSDIMM1:
276 		unump->unum_dimms[0] = dimm1;
277 		offsetdimm = dimm1;
278 		break;
279 	case CSDIMM2:
280 		unump->unum_dimms[0] = dimm2;
281 		offsetdimm = dimm2;
282 		break;
283 	case CSDIMM1 | CSDIMM2:
284 		unump->unum_dimms[0] = dimm1;
285 		unump->unum_dimms[1] = dimm2;
286 		offsetdimm = dimm1;
287 		break;
288 	}
289 
290 	if (!incloff) {
291 		unump->unum_offset = MCAMD_RC_INVALID_OFFSET;
292 		return (0);
293 	}
294 
295 	/*
296 	 * We wish to calculate a dimm offset.  In the paired case we will
297 	 * lookup dimm1 (see offsetdimm above).
298 	 */
299 	for (dimm = mcamd_dimm_next(hdl, mc, NULL); dimm != NULL;
300 	    dimm = mcamd_dimm_next(hdl, mc, dimm)) {
301 		uint64_t dnum;
302 		if (!mcamd_get_numprop(hdl, dimm, MCAMD_PROP_NUM, &dnum)) {
303 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed "
304 			    "to lookup dimm number property\n");
305 			continue;
306 		}
307 		if (dnum == offsetdimm)
308 			break;
309 	}
310 
311 	if (dimm == NULL) {
312 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
313 		    "find dimm with number %d for offset calculation\n",
314 		    offsetdimm);
315 		unump->unum_offset = MCAMD_RC_INVALID_OFFSET;
316 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
317 	}
318 
319 	/*
320 	 * mc_pa_to_offset sets the offset to an invalid value if
321 	 * it hits an error.
322 	 */
323 	(void) mc_pa_to_offset(hdl, mc, cs, iaddr, &unump->unum_offset);
324 
325 	return (0);
326 }
327 
328 /*
329  * We have translated a system address to a (node, chip-select), and wish
330  * to determine the associated dimm or dimms.
331  *
332  * A (node, chip-select) pair identifies one (in 64-bit MC mode) or two (in
333  * 128-bit MC mode) DIMMs.  In the case of a single dimm it is usually in a
334  * lodimm (channel A) slot, but if mismatched dimm support is present it may
335  * be an updimm (channel B).
336  *
337  * Where just one dimm is associated with the chip-select we are done.
338  * Where there are two dimms associated with the chip-select we can
339  * use the ECC type and/or syndrome to determine which of the pair we
340  * resolve to, if the error is correctable.  If the error is uncorrectable
341  * then in 64/8 ECC mode we can still resolve to a single dimm (since ECC
342  * is calculated and checked on each half of the data separately), but
343  * in ChipKill mode we cannot resolve down to a single dimm.
344  */
345 static int
346 mc_whichdimm(struct mcamd_hdl *hdl, mcamd_node_t *cs, uint64_t pa,
347     uint8_t valid_lo, uint32_t synd, int syndtype)
348 {
349 	int lobit, hibit, data, check;
350 	uint64_t dimm1, dimm2;
351 	uint_t sym, pat;
352 	int ndimm;
353 
354 	/*
355 	 * Read the associated dimm instance numbers.  The provider must
356 	 * assure that if there is just one dimm then it is in the first
357 	 * property, and if there are two then the first must be on
358 	 * channel A.
359 	 */
360 	if (!mcamd_get_numprops(hdl,
361 	    cs, MCAMD_PROP_CSDIMM1, &dimm1,
362 	    cs, MCAMD_PROP_CSDIMM2, &dimm2,
363 	    NULL)) {
364 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_whichdimm: failed to "
365 		    "lookup required properties");
366 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
367 	}
368 	ndimm = (dimm1 != MC_INVALNUM) + (dimm2 != MC_INVALNUM);
369 	if (ndimm == 0) {
370 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_whichdimm: found no "
371 		    "dimms associated with chip-select");
372 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
373 	}
374 
375 	if (ndimm == 1) {
376 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: just one "
377 		    "dimm associated with this chip-select");
378 		return (CSDIMM1);
379 	}
380 
381 	/*
382 	 * 64/8 ECC is checked separately for the upper and lower
383 	 * halves, so even an uncorrectable error is contained within
384 	 * one of the two halves.  If we have sufficient address resolution
385 	 * then we can determine which DIMM.
386 	 */
387 	if (syndtype == AMD_SYNDTYPE_ECC) {
388 		if (valid_lo <= MC_SYSADDR_LSB) {
389 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: 64/8 "
390 			    "ECC in 128-bit mode, PA 0x%llx is in %s half\n",
391 			    pa, pa & 0x8 ? "upper" : "lower");
392 			return (pa & 0x8 ? CSDIMM2 : CSDIMM1);
393 		} else {
394 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: "
395 			    "64/8 ECC in 128-bit mode, PA 0x%llx with least "
396 			    "significant valid bit %d cannot be resolved to "
397 			    "a single DIMM\n", pa, valid_lo);
398 			return (mcamd_set_errno(hdl, EMCAMD_INSUFF_RES));
399 		}
400 	}
401 
402 	/*
403 	 * ChipKill ECC
404 	 */
405 	if (mcamd_cksynd_decode(hdl, synd, &sym, &pat)) {
406 		/*
407 		 * A correctable ChipKill syndrome and we can tell
408 		 * which half the error was in from the symbol number.
409 		 */
410 		if (mcamd_cksym_decode(hdl, sym, &lobit, &hibit, &data,
411 		    &check) == 0)
412 			return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID));
413 
414 		if (data && hibit <= 63 || check && hibit <= 7) {
415 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: "
416 			    "ChipKill symbol %d (%s %d..%d), so LODIMM\n", sym,
417 			    data ? "data" : "check", lobit, hibit);
418 			return (CSDIMM1);
419 		} else {
420 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: "
421 			    "ChipKill symbol %d (%s %d..%d), so UPDIMM\n", sym,
422 			    data ? "data" : "check", lobit, hibit);
423 			return (CSDIMM2);
424 		}
425 	} else {
426 		/*
427 		 * An uncorrectable error while in ChipKill ECC mode - can't
428 		 * tell which dimm or dimms the errors lie within.
429 		 */
430 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichhdimm: "
431 		    "uncorrectable ChipKill, could be either LODIMM "
432 		    "or UPDIMM\n");
433 		return (CSDIMM1 | CSDIMM2);
434 	}
435 }
436 
437 /*
438  * Brute-force BKDG pa to cs translation, coded to look as much like the
439  * BKDG code as possible.
440  */
441 static int
442 mc_bkdg_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
443     uint8_t valid_lo, uint32_t synd, int syndtype,
444     mc_unum_t *unump)
445 {
446 	int which;
447 	uint64_t mcnum, rev;
448 	mcamd_node_t *cs;
449 	/*
450 	 * Raw registers as per BKDG
451 	 */
452 	uint32_t HoleEn;
453 	uint32_t DramBase, DramLimit;
454 	uint32_t CSBase,  CSMask;
455 	/*
456 	 * Variables as per BKDG
457 	 */
458 	int Ilog;
459 	uint32_t SystemAddr = (uint32_t)(pa >> 8);
460 	uint64_t IntlvEn, IntlvSel;
461 	uint32_t HoleOffset;
462 	uint32_t InputAddr, Temp;
463 
464 	if (!mcamd_get_numprops(hdl,
465 	    mc, MCAMD_PROP_NUM, &mcnum,
466 	    mc, MCAMD_PROP_REV, &rev, NULL) || !mcamd_get_cfgregs(hdl,
467 	    mc, MCAMD_REG_DRAMBASE, &DramBase,
468 	    mc, MCAMD_REG_DRAMLIMIT, &DramLimit,
469 	    mc, MCAMD_REG_DRAMHOLE, &HoleEn, NULL)) {
470 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: failed "
471 		    "to lookup required properties and registers\n");
472 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
473 	}
474 
475 	/*
476 	 * BKDG line to skip		Why
477 	 *
478 	 * F1Offset = ...		Register already read,
479 	 * DramBase = Get_PCI()		and retrieved above.
480 	 * DramEn = ...			Function only called for enabled nodes.
481 	 */
482 	IntlvEn = (DramBase & 0x00000700) >> 8;
483 	DramBase &= 0xffff0000;
484 	/* DramLimit = Get_PCI()	Retrieved above */
485 	IntlvSel = (DramLimit & 0x00000700) >> 8;
486 	DramLimit |= 0x0000ffff;
487 	/* HoleEn = ...			Retrieved above */
488 	HoleOffset = (HoleEn & 0x0000ff00) << 8;
489 	HoleEn &= 0x00000001;
490 
491 	if (!(DramBase <= SystemAddr && SystemAddr <= DramLimit)) {
492 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
493 		    "SystemAddr 0x%x derived from PA 0x%llx is not in the "
494 		    "address range [0x%x, 0x%x] of MC %d\n",
495 		    SystemAddr, pa, DramBase, DramLimit, (int)mcnum);
496 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
497 	}
498 
499 	if (HoleEn && SystemAddr > 0x00ffffff)
500 		InputAddr = SystemAddr - HoleOffset;
501 
502 	InputAddr = SystemAddr - DramBase;
503 
504 	if (IntlvEn) {
505 		if (IntlvSel == ((SystemAddr >> 4) & IntlvEn)) {
506 			switch (IntlvEn) {
507 			case 1:
508 				Ilog = 1;
509 				break;
510 			case 3:
511 				Ilog = 2;
512 				break;
513 			case 7:
514 				Ilog = 3;
515 				break;
516 			default:
517 				return (mcamd_set_errno(hdl,
518 				    EMCAMD_TREEINVALID));
519 			}
520 			Temp = (InputAddr >> (4 + Ilog)) << 4;
521 			InputAddr = (Temp | (SystemAddr & 0x0000000f));
522 		} else {
523 			/* not this node */
524 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
525 			    "Node interleaving, MC node %d not selected\n",
526 			    (int)mcnum);
527 			return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
528 		}
529 	}
530 
531 	if (!MC_REV_MATCH(rev, MC_REVS_FG))
532 		InputAddr <<= 4;
533 
534 	for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
535 	    cs = mcamd_cs_next(hdl, mc, cs)) {
536 		uint64_t csnum, CSEn;
537 
538 		if (!mcamd_get_cfgregs(hdl,
539 		    cs, MCAMD_REG_CSBASE, &CSBase,
540 		    cs, MCAMD_REG_CSMASK, &CSMask,
541 		    NULL) ||
542 		    !mcamd_get_numprops(hdl,
543 		    cs, MCAMD_PROP_NUM, &csnum,
544 		    cs, MCAMD_PROP_CSBE, &CSEn,
545 		    NULL)) {
546 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: "
547 			    "failed to read cs registers\n");
548 			return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
549 		}
550 
551 		/*
552 		 * BKDG line to skip		Why
553 		 *
554 		 * F2Offset =			Register already read,
555 		 * F2MaskOffset (rev F)		Register already read
556 		 * CSBase =			Register already read
557 		 * CSEn =			We only keep enabled cs.
558 		 */
559 		if (MC_REV_MATCH(rev, MC_REVS_FG)) {
560 			CSBase &= 0x1ff83fe0;
561 			/* CSMask = Get_PCI()		Retrieved above */
562 			CSMask = (CSMask | 0x0007c01f) & 0x1fffffff;
563 		} else {
564 			CSBase &= 0xffe0fe00;
565 			/* CSMask = Get_PCI()		Retrieved above */
566 			CSMask = (CSMask | 0x001f01ff) & 0x3fffffff;
567 		}
568 
569 		if (CSEn && (InputAddr & ~CSMask) == (CSBase & ~CSMask)) {
570 			mcamd_node_t *sparecs;
571 
572 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
573 			    "match for chip select %d of MC %d\n", (int)csnum,
574 			    (int)mcnum);
575 
576 			if ((sparecs = cs_sparedto(hdl, cs, mc)) != NULL)
577 				cs = sparecs;
578 
579 			if ((which = mc_whichdimm(hdl, cs, pa, valid_lo,
580 			    synd, syndtype)) < 0)
581 				return (-1); /* errno is set for us */
582 
583 			/*
584 			 * The BKDG algorithm drops low-order bits that
585 			 * are unimportant in deriving chip-select but are
586 			 * included in row/col/bank mapping, so do not
587 			 * perform offset calculation in this case.
588 			 */
589 			if (unum_fill(hdl, cs, which, InputAddr, unump, 0) < 0)
590 				return (-1); /* errno is set for us */
591 
592 			return (0);
593 		}
594 	}
595 
596 	mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounum: in range "
597 	    "for MC %d but no cs responds\n", (int)mcnum);
598 
599 	return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
600 }
601 
602 /*
603  * Called for each memory controller to see if the given address is
604  * mapped to this node (as determined in iaddr_gen) and, if so, which
605  * chip-select on this node responds.
606  */
607 
608 /*ARGSUSED*/
609 static int
610 mc_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
611     uint8_t valid_lo, uint32_t synd, int syndtype, mc_unum_t *unump)
612 {
613 	uint64_t iaddr;
614 	mcamd_node_t *cs, *sparecs;
615 	int which;
616 #ifdef DEBUG
617 	mc_unum_t bkdg_unum;
618 	int bkdgres;
619 
620 	/*
621 	 * We perform the translation twice, once using the brute-force
622 	 * approach of the BKDG and again using a more elegant but more
623 	 * difficult to review against the BKDG approach.
624 	 */
625 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "BKDG brute-force method begins\n");
626 	bkdgres = mc_bkdg_patounum(hdl, mc, pa, valid_lo, synd,
627 	    syndtype, &bkdg_unum);
628 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "BKDG brute-force method ends\n");
629 #endif
630 
631 	if (iaddr_gen(hdl, mc, pa, &iaddr) < 0)
632 		return (-1); /* errno is set for us */
633 
634 	for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
635 	    cs = mcamd_cs_next(hdl, mc, cs)) {
636 		if (cs_match(hdl, iaddr, cs))
637 			break;
638 	}
639 
640 	if (cs == NULL)
641 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
642 
643 	/*
644 	 * If the spare chip-select has been swapped in for the one just
645 	 * matched then it is really the spare that we are after.  Note that
646 	 * when the swap is done the csbase, csmask and CSBE of the spare
647 	 * rank do not change - accesses to the bad rank (as nominated in
648 	 * the Online Spare Control Register) are redirect to the spare.
649 	 */
650 	if ((sparecs = cs_sparedto(hdl, cs, mc)) != NULL) {
651 		cs = sparecs;
652 	}
653 
654 	if ((which = mc_whichdimm(hdl, cs, pa, valid_lo, synd,
655 	    syndtype)) < 0)
656 		return (-1); /* errno is set for us */
657 
658 	if (unum_fill(hdl, cs, which, iaddr, unump, 1) < 0)
659 		return (-1); /* errno is set for us */
660 
661 #ifdef DEBUG
662 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "bkdgres=%d res=0\n", bkdgres);
663 	/* offset is not checked - see note in BKDG algorithm */
664 	if (bkdgres != 0) {
665 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "BKDG alg failed while "
666 		    "ours succeeded\n");
667 	} else if (!(unump->unum_board == bkdg_unum.unum_board &&
668 	    unump->unum_chip == bkdg_unum.unum_chip &&
669 	    unump->unum_mc == bkdg_unum.unum_mc &&
670 	    unump->unum_cs == bkdg_unum.unum_cs &&
671 	    unump->unum_dimms[0] == bkdg_unum.unum_dimms[0] &&
672 	    unump->unum_dimms[1] == bkdg_unum.unum_dimms[1])) {
673 		mcamd_dprintf(hdl, MCAMD_DBG_ERR,
674 		    "BKDG: node %d mc %d cs %d dimm(s) %d/%d\n"
675 		    "Ours: node 5d mc %d cs %d dimm(s) %d/%d\n",
676 		    bkdg_unum.unum_chip, bkdg_unum.unum_mc, bkdg_unum.unum_cs,
677 		    bkdg_unum.unum_dimms[0], bkdg_unum.unum_dimms[1],
678 		    unump->unum_chip, unump->unum_mc, unump->unum_cs,
679 		    unump->unum_dimms[0], unump->unum_dimms[1]);
680 	}
681 #endif /* DEBUG */
682 
683 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "Result: chip %d mc %d cs %d "
684 	    "offset 0x%llx\n", unump->unum_chip, unump->unum_mc,
685 	    unump->unum_cs, unump->unum_offset);
686 
687 	return (0);
688 }
689 
690 int
691 mcamd_patounum(struct mcamd_hdl *hdl, mcamd_node_t *root, uint64_t pa,
692     uint8_t valid_hi, uint8_t valid_lo, uint32_t synd, int syndtype,
693     mc_unum_t *unump)
694 {
695 	mcamd_node_t *mc;
696 
697 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mcamd_patounum: pa=0x%llx, "
698 	    "synd=0x%x, syndtype=%d\n", pa, synd, syndtype);
699 
700 	if (valid_hi < MC_SYSADDR_MSB) {
701 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mcamd_patounum: require "
702 		    "pa<%d> to be valid\n", MC_SYSADDR_MSB);
703 		return (mcamd_set_errno(hdl, EMCAMD_INSUFF_RES));
704 	}
705 
706 	if (!mcamd_synd_validate(hdl, synd, syndtype))
707 		return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID));
708 
709 	for (mc = mcamd_mc_next(hdl, root, NULL); mc != NULL;
710 	    mc = mcamd_mc_next(hdl, root, mc)) {
711 		if (mc_patounum(hdl, mc, pa, valid_lo, synd,
712 		    syndtype, unump) == 0)
713 			return (0);
714 
715 		if (mcamd_errno(hdl) != EMCAMD_NOADDR)
716 			break;
717 	}
718 
719 	return (-1); /* errno is set for us */
720 }
721