xref: /illumos-gate/usr/src/common/mc/mc-amd/mcamd_patounum.c (revision 1a220b56b93ff1dc80855691548503117af4cc10)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  *
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Given a physical address and an optional syndrome, determine the
30  * name of the memory module that contains it.
31  */
32 
33 #include <sys/errno.h>
34 #include <sys/types.h>
35 #include <sys/mc.h>
36 
37 #include <mcamd_api.h>
38 #include <mcamd_err.h>
39 
40 extern int mc_pa_to_offset(struct mcamd_hdl *, mcamd_node_t *, mcamd_node_t *,
41     mcamd_node_t *, uint64_t, uint64_t *);
42 
43 #define	LO_DIMM		0x1
44 #define	UP_DIMM		0x2
45 
46 #define	BITS(val, high, low) \
47 	((val) & (((2ULL << (high)) - 1) & ~((1ULL << (low)) - 1)))
48 
49 static int
50 iaddr_gen(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
51     uint64_t *iaddrp)
52 {
53 	uint64_t orig = pa;
54 	uint64_t mcnum, base, lim, dramaddr, ilen, ilsel, top, dramhole;
55 
56 	if (!mcamd_get_numprop(hdl, mc, MCAMD_PROP_NUM, &mcnum) ||
57 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_BASE_ADDR, &base) ||
58 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_LIM_ADDR, &lim) ||
59 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILEN, &ilen) ||
60 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILSEL, &ilsel) ||
61 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_HOLE, &dramhole)) {
62 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "iaddr_gen: failed to "
63 		    "lookup required properties");
64 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
65 	}
66 
67 	if (pa < base || pa > lim) {
68 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx not "
69 		    "in range [0x%llx, 0x%llx] of MC %d\n", pa, base, lim,
70 		    (int)mcnum);
71 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
72 	}
73 
74 	/*
75 	 * Rev E and later added the DRAM Hole Address Register for
76 	 * memory hoisting.  In earlier revisions memory hoisting is
77 	 * achieved by following some algorithm to modify the CS bases etc,
78 	 * and this pa to unum algorithm will simply see those modified
79 	 * values.  But if the Hole Address Register is being used then
80 	 * we need to reduce any address at or above 4GB by the size of
81 	 * the hole.
82 	 */
83 	if (dramhole & MC_DC_HOLE_VALID && pa >= 0x100000000) {
84 		uint64_t holesize = (dramhole & MC_DC_HOLE_OFFSET_MASK) <<
85 		    MC_DC_HOLE_OFFSET_LSHIFT;
86 		pa -= holesize;
87 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: dram hole "
88 		    "valid; pa decremented from 0x%llx to 0x%llx for "
89 		    "a dramhole size of 0x%llx\n", orig, pa, holesize);
90 	}
91 
92 	dramaddr = BITS(pa, 39, 0) - BITS(base, 39, 24);
93 
94 	if (ilen != 0) {
95 		int pailsel;
96 
97 		if (ilen != 1 && ilen != 3 && ilen != 7) {
98 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "Invalid intlven "
99 			    "of %d for MC %d\n", (int)ilen, (int)mcnum);
100 			return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
101 		}
102 
103 		if ((pailsel = BITS(pa, 14, 12) >> 12 & ilen) != ilsel) {
104 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: "
105 			    "PA 0x%llx in a %d-way node interleave indicates "
106 			    "selection %d, MC %d has ilsel of %d\n",
107 			    pa, (int)ilen + 1, pailsel, (int)mcnum, (int)ilsel);
108 			return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
109 		}
110 
111 		if (ilen == 1)
112 			top = BITS(dramaddr, 36, 13) >> 1;
113 		else if (ilen == 3)
114 			top = BITS(dramaddr, 37, 14) >> 2;
115 		else if (ilen == 7)
116 			top = BITS(dramaddr, 38, 15) >> 3;
117 	} else {
118 		top = BITS(dramaddr, 35, 12);
119 	}
120 
121 	*iaddrp = top | BITS(dramaddr, 11, 0);
122 
123 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_gen: PA 0x%llx in range "
124 	    "[0x%llx, 0x%llx] of MC %d; normalized address for cs compare "
125 	    "is 0x%llx\n", pa, base, lim, (int)mcnum, *iaddrp);
126 
127 	return (0);
128 }
129 
130 static int
131 cs_match(struct mcamd_hdl *hdl, uint64_t iaddr, mcamd_node_t *cs)
132 {
133 	uint64_t csnum, csbase, csmask;
134 	int match;
135 
136 	if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &csnum) ||
137 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_BASE_ADDR, &csbase) ||
138 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_MASK, &csmask)) {
139 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "cs_match: failed to lookup "
140 		    "required properties\n");
141 		return (0);
142 	}
143 
144 	match = ((iaddr & ~csmask) == (csbase & ~csmask));
145 
146 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "cs_match: iaddr 0x%llx does "
147 	    "%smatch CS %d (base 0x%llx, mask 0x%llx)\n", iaddr,
148 	    match ? "" : "not ", (int)csnum, csbase, csmask);
149 
150 	return (match);
151 }
152 
153 static int
154 unum_fill(struct mcamd_hdl *hdl, mcamd_node_t *cs, int which,
155     uint64_t iaddr, struct mc_unum *unump, int incloff)
156 {
157 	mcamd_node_t *mc, *dimm;
158 	uint64_t chipnum, csnum, lonum, upnum;
159 	int i;
160 	int offsetdimm;
161 
162 	if ((mc = mcamd_cs_mc(hdl, cs)) == NULL ||
163 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_NUM, &chipnum) ||
164 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &csnum)) {
165 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
166 		    "lookup required properties\n");
167 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
168 	}
169 
170 	if ((which & LO_DIMM) &&
171 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_LODIMM, &lonum) ||
172 	    (which & UP_DIMM) &&
173 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_UPDIMM, &upnum)) {
174 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
175 		    "lookup lodimm/hidimm properties\n");
176 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
177 	}
178 
179 	unump->unum_board = 0;
180 	unump->unum_chip = chipnum;
181 	unump->unum_mc = 0;
182 	unump->unum_cs = csnum;
183 
184 	for (i = 0; i < MC_UNUM_NDIMM; i++) {
185 		unump->unum_dimms[i] = -1;
186 	}
187 	switch (which) {
188 	case LO_DIMM:
189 		unump->unum_dimms[0] = lonum;
190 		offsetdimm = lonum;
191 		break;
192 	case UP_DIMM:
193 		unump->unum_dimms[0] = upnum;
194 		offsetdimm = upnum;
195 		break;
196 	case LO_DIMM | UP_DIMM:
197 		unump->unum_dimms[0] = lonum;
198 		unump->unum_dimms[1] = upnum;
199 		offsetdimm = lonum;
200 		break;
201 	}
202 
203 	if (!incloff) {
204 		unump->unum_offset = MCAMD_RC_INVALID_OFFSET;
205 		return (0);
206 	}
207 
208 	/*
209 	 * We wish to calculate a dimm offset.  In the paired case we will
210 	 * lookup the lodimm (see offsetdimm above).
211 	 */
212 	for (dimm = mcamd_dimm_next(hdl, mc, NULL); dimm != NULL;
213 	    dimm = mcamd_dimm_next(hdl, mc, dimm)) {
214 		uint64_t dnum;
215 		if (!mcamd_get_numprop(hdl, dimm, MCAMD_PROP_NUM, &dnum)) {
216 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed "
217 			    "to lookup dimm number property\n");
218 			continue;
219 		}
220 		if (dnum == offsetdimm)
221 			break;
222 	}
223 
224 	if (dimm == NULL) {
225 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "unum_fill: failed to "
226 		    "find dimm with number %d for offset calculation\n",
227 		    offsetdimm);
228 		unump->unum_offset = MCAMD_RC_INVALID_OFFSET;
229 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
230 	}
231 
232 	/*
233 	 * mc_pa_to_offset sets the offset to an invalid value if
234 	 * it hits an error.
235 	 */
236 	(void) mc_pa_to_offset(hdl, mc, cs, dimm, iaddr, &unump->unum_offset);
237 
238 	return (0);
239 }
240 
241 /*
242  * We have translated a system address to a (node, chip-select).  That
243  * identifies one (in 64-bit MC mode) or two (in 128-bit MC mode DIMMs,
244  * either a lodimm or a lodimm/updimm pair.  For all cases except an
245  * uncorrectable ChipKill error we can interpret the address alignment and
246  * syndrome to deduce whether we are on the lodimm or updimm.
247  */
248 static int
249 mc_whichdimm(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
250     uint32_t synd, int syndtype)
251 {
252 	uint64_t accwidth;
253 	uint_t sym, pat;
254 	int lobit, hibit, data, check;
255 
256 	if (!mcamd_get_numprop(hdl, mc, MCAMD_PROP_ACCESS_WIDTH, &accwidth) ||
257 	    (accwidth != 64 && accwidth != 128)) {
258 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_whichdimm: failed "
259 		    "to lookup required properties\n");
260 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
261 	}
262 
263 	/*
264 	 * In 64 bit mode only LO dimms are occupied.
265 	 */
266 	if (accwidth == 64) {
267 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: 64-bit mode "
268 		    "therefore LO_DIMM\n");
269 		return (LO_DIMM);
270 	}
271 
272 	if (syndtype == AMD_SYNDTYPE_ECC) {
273 		/*
274 		 * 64/8 ECC is checked separately for the upper and lower
275 		 * halves, so even an uncorrectable error is contained within
276 		 * one of the two halves.  The error address is accurate to
277 		 * 8 bytes, so bit 4 distinguises upper from lower.
278 		 */
279 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: 64/8 ECC "
280 		    "and PA 0x%llx is in %s half\n", pa,
281 		    pa & 8 ? "lower" : "upper");
282 		return (pa & 8 ? UP_DIMM : LO_DIMM);
283 	}
284 
285 	/*
286 	 * ChipKill ECC (necessarily in 128-bit mode.
287 	 */
288 	if (mcamd_cksynd_decode(hdl, synd, &sym, &pat)) {
289 		/*
290 		 * A correctable ChipKill syndrome and we can tell
291 		 * which half the error was in from the symbol number.
292 		 */
293 		if (mcamd_cksym_decode(hdl, sym, &lobit, &hibit, &data,
294 		    &check) == 0)
295 			return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID));
296 
297 		if (data && hibit <= 63 || check && hibit <= 7) {
298 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: "
299 			    "ChipKill symbol %d (%s %d..%d), so LODIMM\n", sym,
300 			    data ? "data" : "check", lobit, hibit);
301 			return (LO_DIMM);
302 		} else {
303 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichdimm: "
304 			    "ChipKill symbol %d (%s %d..%d), so UPDIMM\n", sym,
305 			    data ? "data" : "check", lobit, hibit);
306 			return (UP_DIMM);
307 		}
308 	} else {
309 		/*
310 		 * An uncorrectable error while in ChipKill ECC mode - can't
311 		 * tell which dimm or dimms the errors lie within.
312 		 */
313 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_whichhdimm: "
314 		    "uncorrectable ChipKill, could be either LODIMM "
315 		    "or UPDIMM\n");
316 		return (LO_DIMM | UP_DIMM);
317 	}
318 }
319 
320 /*
321  * Brute-force BKDG pa to cs translation.  The following is from BKDG 3.29
322  * so is for revisions prior to F.  It is coded to look as much like the
323  * BKDG code as possible.
324  */
325 static int
326 mc_bkdg_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
327     uint32_t synd, int syndtype, struct mc_unum *unump)
328 {
329 	int which;
330 	uint64_t mcnum;
331 	mcamd_node_t *cs;
332 	/*
333 	 * Variables as per BKDG
334 	 */
335 	int Ilog;
336 	uint32_t SystemAddr = (uint32_t)(pa >> 8);
337 	uint64_t IntlvEn, IntlvSel;
338 	uint32_t DramBase, DramLimit;		/* assume DramEn */
339 	uint32_t HoleOffset, HoleEn;
340 	uint32_t CSBase,  CSMask;		/* assuume CSBE */
341 	uint32_t InputAddr, Temp;
342 
343 	/*
344 	 * Additional variables which we need since we will reading
345 	 * MC properties instead of PCI config space, and the MC properties
346 	 * are stored in a cooked state.
347 	 */
348 	uint64_t prop_drambase, prop_dramlimit, prop_dramhole;
349 	uint64_t prop_intlven, prop_intlvsel;
350 	uint64_t prop_csbase, prop_csmask;
351 
352 	if (!mcamd_get_numprop(hdl, mc, MCAMD_PROP_NUM, &mcnum) ||
353 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_BASE_ADDR, &prop_drambase) ||
354 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_LIM_ADDR, &prop_dramlimit) ||
355 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_HOLE, &prop_dramhole) ||
356 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILEN, &prop_intlven) ||
357 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILSEL,
358 	    &prop_intlvsel)) {
359 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: failed "
360 		    "to lookup required properties\n");
361 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
362 	}
363 
364 	/*
365 	 * Brute force deconstruction of the MC properties.  If we decide to
366 	 * keep this then we need some of the mcamd.g defines available to us.
367 	 */
368 	DramBase = ((prop_drambase >> 8) & 0xffff0000) | (prop_intlven << 8);
369 	IntlvEn = (DramBase & 0x00000700) >> 8;
370 	DramBase &= 0xffff0000;
371 	DramLimit = ((prop_dramlimit >> 8) & 0xffff0000) | (prop_intlvsel << 8);
372 	IntlvSel = (DramLimit & 0x00000700) >> 8;
373 	DramLimit |= 0x0000ffff;
374 	HoleEn = prop_dramhole;	/* uncooked */
375 	HoleOffset = (HoleEn & 0x0000ff00) << 8;
376 	HoleEn &= 0x00000001;
377 
378 	if (!(DramBase <= SystemAddr && SystemAddr <= DramLimit)) {
379 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
380 		    "SystemAddr 0x%x derived from PA 0x%llx is not in the "
381 		    "address range [0x%x, 0x%x] of MC %d\n",
382 		    SystemAddr, pa, DramBase, DramLimit, (int)mcnum);
383 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
384 	}
385 
386 	if (IntlvEn) {
387 		if (IntlvSel == ((SystemAddr >> 4) & IntlvEn)) {
388 			switch (IntlvEn) {
389 			case 1:
390 				Ilog = 1;
391 				break;
392 			case 3:
393 				Ilog = 2;
394 				break;
395 			case 7:
396 				Ilog = 3;
397 				break;
398 			default:
399 				return (mcamd_set_errno(hdl,
400 				    EMCAMD_TREEINVALID));
401 			}
402 			Temp = (SystemAddr >> (4 + Ilog)) << 4;
403 			InputAddr = (Temp | (SystemAddr & 0x0000000f)) << 4;
404 		} else {
405 			/* not this node */
406 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
407 			    "Node interleaving, MC node %d not selected\n",
408 			    (int)mcnum);
409 			return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
410 		}
411 	} else {
412 		/* No interleave */
413 		InputAddr = (SystemAddr - DramBase) << 4;
414 	}
415 
416 	if (HoleEn && SystemAddr > 0x00ffffff)
417 	    InputAddr -= HoleOffset;
418 
419 	for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
420 	    cs = mcamd_cs_next(hdl, mc, cs)) {
421 		uint64_t csnum;
422 
423 		if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_BASE_ADDR,
424 		    &prop_csbase) ||
425 		    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_MASK,
426 		    &prop_csmask) ||
427 		    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &csnum)) {
428 			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounm: "
429 			    "failed to read cs properties\n");
430 			return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
431 		}
432 
433 		CSBase = ((prop_csbase >> 4) & 0xffe00000) |
434 		    ((prop_csbase >> 4) & 0x0000fe00);
435 		CSBase &= 0xffe0fe00;
436 		CSMask = ((prop_csmask >> 4) & 0x3fe00000) |
437 		    ((prop_csmask >> 4) & 0x0000fe00);
438 		CSMask = (CSMask | 0x001f01ff) & 0x3fffffff;
439 
440 		if (((InputAddr & ~CSMask) == (CSBase & ~CSMask))) {
441 			mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_bkdg_patounum: "
442 			    "match for chip select %d of MC %d\n", (int)csnum,
443 			    (int)mcnum);
444 
445 			if ((which = mc_whichdimm(hdl, mc, pa, synd,
446 			    syndtype)) < 0)
447 				return (-1); /* errno is set for us */
448 
449 			/*
450 			 * The BKDG algorithm drops low-order bits that
451 			 * are unimportant in deriving chip-select but are
452 			 * included in row/col/bank mapping, so do not
453 			 * perform offset calculation in this case.
454 			 */
455 			if (unum_fill(hdl, cs, which, InputAddr, unump, 0) < 0)
456 				return (-1); /* errno is set for us */
457 
458 			return (0);
459 		}
460 	}
461 
462 	mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mc_bkdg_patounum: in range "
463 	    "for MC %d but no cs responds\n", (int)mcnum);
464 
465 	return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
466 }
467 
468 /*ARGSUSED*/
469 static int
470 mc_patounum(struct mcamd_hdl *hdl, mcamd_node_t *mc, uint64_t pa,
471     uint32_t synd, int syndtype, struct mc_unum *unump)
472 {
473 	uint64_t iaddr;
474 	mcamd_node_t *cs;
475 	int which;
476 #ifdef DEBUG
477 	struct mc_unum bkdg_unum;
478 	int bkdgres;
479 
480 	/*
481 	 * We perform the translation twice, once using the brute-force
482 	 * approach of the BKDG and again using a more elegant but more
483 	 * difficult to review against the BKDG approach.  Note that both
484 	 * approaches need to change for rev F since it increases max CS
485 	 * size and so iaddr calculation etc changes.
486 	 */
487 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "BKDG brute-force method begins\n");
488 	bkdgres = mc_bkdg_patounum(hdl, mc, pa, synd, syndtype, &bkdg_unum);
489 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "BKDG brute-force method ends\n");
490 #endif
491 
492 	if (iaddr_gen(hdl, mc, pa, &iaddr) < 0)
493 		return (-1); /* errno is set for us */
494 
495 	for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
496 	    cs = mcamd_cs_next(hdl, mc, cs)) {
497 		if (cs_match(hdl, iaddr, cs))
498 			break;
499 	}
500 
501 	if (cs == NULL)
502 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
503 
504 	if ((which = mc_whichdimm(hdl, mc, pa, synd, syndtype)) < 0)
505 		return (-1); /* errno is set for us */
506 
507 	if (unum_fill(hdl, cs, which, iaddr, unump, 1) < 0)
508 		return (-1); /* errno is set for us */
509 
510 #ifdef DEBUG
511 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "bkdgres=%d res=0\n", bkdgres);
512 #ifndef _KERNEL
513 	/* offset is not checked - see note in BKDG algorithm */
514 	assert(bkdgres == 0 && unump->unum_board == bkdg_unum.unum_board &&
515 	    unump->unum_chip == bkdg_unum.unum_chip &&
516 	    unump->unum_mc == bkdg_unum.unum_mc &&
517 	    unump->unum_cs == bkdg_unum.unum_cs &&
518 	    unump->unum_dimms[0] == bkdg_unum.unum_dimms[0] &&
519 	    unump->unum_dimms[1] == bkdg_unum.unum_dimms[1]);
520 #endif /* !_KERNEL */
521 #endif /* DEBUG */
522 
523 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "Result: chip %d mc %d cs %d "
524 	    "offset 0x%llx\n", unump->unum_chip, unump->unum_mc,
525 	    unump->unum_cs, unump->unum_offset);
526 
527 	return (0);
528 }
529 
530 int
531 mcamd_patounum(struct mcamd_hdl *hdl, mcamd_node_t *root, uint64_t pa,
532     uint32_t synd, int syndtype, struct mc_unum *unump)
533 {
534 	mcamd_node_t *mc;
535 
536 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mcamd_patounum: pa=0x%llx, "
537 	    "synd=0x%x, syndtype=%d\n", pa, synd, syndtype);
538 
539 	if (!mcamd_synd_validate(hdl, synd, syndtype))
540 		return (mcamd_set_errno(hdl, EMCAMD_SYNDINVALID));
541 
542 	for (mc = mcamd_mc_next(hdl, root, NULL); mc != NULL;
543 	    mc = mcamd_mc_next(hdl, root, mc)) {
544 		if (mc_patounum(hdl, mc, pa, synd, syndtype, unump) == 0)
545 			return (0);
546 
547 		if (mcamd_errno(hdl) != EMCAMD_NOADDR)
548 			break;
549 	}
550 
551 	return (-1); /* errno is set for us */
552 }
553