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 #include <mcamd_api.h>
29 #include <mcamd_err.h>
30 #include <mcamd_rowcol_impl.h>
31 
32 /*
33  * Convenience structures to stash MC and CS properties in.  Some of these
34  * are read directly, while others are then calculated.
35  */
36 struct rcp_mc {
37 	uint64_t num;		/* corresponding chip number */
38 	uint64_t rev;		/* revision */
39 	uint64_t width;		/* access width */
40 	uint64_t base;		/* MC base address */
41 	uint64_t lim;		/* MC limit address */
42 	uint64_t csbnkmap;	/* chip-select bank map */
43 	uint64_t intlven;	/* Node-interleave mask */
44 	uint64_t intlvsel;	/* Node-interleave selection for this node */
45 	uint64_t csintlvfctr;	/* chip-select interleave factor on this node */
46 	int bnkswzl;		/* bank-swizzle mode - derived */
47 };
48 
49 struct rcp_cs {
50 	uint64_t num;		/* chip-select number */
51 	uint64_t base;		/* chip-select base address */
52 	uint64_t mask;		/* chip-select mask */
53 };
54 
55 static int
56 getmcprops(struct mcamd_hdl *hdl, mcamd_node_t *mc, const char *caller,
57     struct rcp_mc *pp)
58 {
59 	if (!mcamd_get_numprop(hdl, mc, MCAMD_PROP_NUM, &pp->num) ||
60 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_REV, &pp->rev) ||
61 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_ACCESS_WIDTH, &pp->width) ||
62 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_BASE_ADDR, &pp->base) ||
63 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_LIM_ADDR, &pp->lim) ||
64 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_CSBANKMAP, &pp->csbnkmap) ||
65 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILEN, &pp->intlven) ||
66 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_DRAM_ILSEL, &pp->intlvsel) ||
67 	    !mcamd_get_numprop(hdl, mc, MCAMD_PROP_CSBANK_INTLV,
68 	    &pp->csintlvfctr)) {
69 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "%s: failed to read mc "
70 		    "props for mc 0x%p\n", caller, mc);
71 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
72 	}
73 
74 	pp->bnkswzl = ((pp->csbnkmap & MC_DC_BAM_CSBANK_SWIZZLE) != 0);
75 
76 	return (0);
77 }
78 
79 static int
80 getcsprops(struct mcamd_hdl *hdl, mcamd_node_t *cs, const char *caller,
81     struct rcp_cs *csp)
82 {
83 	if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM, &csp->num) ||
84 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_BASE_ADDR, &csp->base) ||
85 	    !mcamd_get_numprop(hdl, cs, MCAMD_PROP_MASK, &csp->mask))  {
86 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "%s: failed to read cs "
87 		    "props for cs 0x%p\n", caller, cs);
88 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
89 	    }
90 
91 	return (0);
92 }
93 
94 static int
95 gettbls(struct mcamd_hdl *hdl, uint_t csmode, struct rcp_mc *mcpp,
96     const struct bankaddr_mode **bamp, const struct csrcb_map **rcbmp,
97     struct csintlv_desc *csid, const char *caller)
98 {
99 	if (bamp && (*bamp = rct_bankaddr_mode(mcpp->rev, csmode)) == NULL) {
100 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: no bank address mode "
101 		    "table for MC rev %d csmode %d\n", caller,
102 		    (int)mcpp->rev, csmode);
103 		return (mcamd_set_errno(hdl, EMCAMD_NOTSUP));
104 	}
105 
106 	if (rcbmp && (*rcbmp = rct_rcbmap(mcpp->rev, mcpp->width,
107 	    csmode)) == NULL) {
108 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: no dram address map "
109 		    "table for MC rev %d csmode %d\n", caller,
110 		    (int)mcpp->rev, csmode);
111 		return (mcamd_set_errno(hdl, EMCAMD_NOTSUP));
112 	}
113 
114 	if (csid) {
115 		if (mcpp->csintlvfctr != 0) {
116 			rct_csintlv_bits(mcpp->rev, mcpp->width, csmode,
117 			    mcpp->csintlvfctr, csid);
118 			if (csid->csi_factor == 0) {
119 				mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: "
120 				    "could not work out cs interleave "
121 				    "paramters for MC rev %d, width %d, "
122 				    "csmode %d, factor %d\n", caller,
123 				    (int)mcpp->rev, (int)mcpp->width, csmode,
124 				    (int)mcpp->csintlvfctr);
125 				return (mcamd_set_errno(hdl, EMCAMD_NOTSUP));
126 			}
127 		} else {
128 			csid->csi_factor = 0;
129 		}
130 	}
131 
132 	return (0);
133 }
134 
135 static uint64_t
136 iaddr_add(struct mcamd_hdl *hdl, uint64_t in, uint64_t add, const char *what)
137 {
138 	uint64_t new = in | add;
139 
140 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: 0x%llx | 0x%llx --> 0x%llx",
141 	    what, in, add, new);
142 
143 	return (add);
144 }
145 
146 /*
147  * Where the number of row/col address bits is ambiguous (affects CG and
148  * earlier only) we will assign the "floating" bit to row address.  If
149  * we adopt the same convention in address reconstruction then all should work.
150  */
151 static uint32_t
152 iaddr_to_row(struct mcamd_hdl *hdl, const struct bankaddr_mode *bamp,
153     const struct csrcb_map *rcbm, struct csintlv_desc *csid, uint64_t iaddr)
154 {
155 	uint32_t addr = 0;
156 	int abitno, ibitno;
157 	int nbits = bamp->bam_nrows;
158 	int swapped = 0;
159 
160 	for (abitno = 0; abitno < nbits; abitno++) {
161 		ibitno = rcbm->csrcb_rowbits[abitno];
162 		if (MC_RC_CSI_SWAPPED_BIT(csid, ibitno)) {
163 			ibitno = MC_RC_CSI_BITSWAP(csid, ibitno);
164 			swapped++;
165 		}
166 		if (iaddr & (1 << ibitno))
167 			addr |= (1 << abitno);
168 	}
169 
170 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_to_row: iaddr 0x%llx --> "
171 	    "row 0x%x (%d bits swapped for cs intlv)\n", iaddr, addr, swapped);
172 
173 	return (addr);
174 }
175 
176 /*ARGSUSED*/
177 static uint64_t
178 row_to_iaddr(struct mcamd_hdl *hdl, const struct bankaddr_mode *bamp,
179     const struct csrcb_map *rcbm, struct csintlv_desc *csid, uint32_t rowaddr)
180 {
181 	uint64_t iaddr = 0;
182 	int abitno, ibitno;
183 	int nbits = bamp->bam_nrows;
184 
185 	for (abitno = 0; abitno < nbits; abitno++) {
186 		if (BIT(rowaddr, abitno) == 0)
187 			continue;
188 		ibitno = rcbm->csrcb_rowbits[abitno];
189 		if (MC_RC_CSI_SWAPPED_BIT(csid, ibitno)) {
190 			ibitno = MC_RC_CSI_BITSWAP(csid, ibitno);
191 		}
192 		SETBIT(iaddr, ibitno);
193 	}
194 
195 	return (iaddr);
196 }
197 
198 
199 static uint32_t
200 iaddr_to_col(struct mcamd_hdl *hdl, const struct bankaddr_mode *bamp,
201     const struct csrcb_map *rcbm, uint64_t iaddr)
202 {
203 	uint32_t addr = 0;
204 	int abitno, ibitno, bias = 0;
205 	int nbits = bamp->bam_ncols;
206 
207 	/*
208 	 * Knock off a column bit if the numbers are ambiguous
209 	 */
210 	if (bamp->bam_ambig)
211 		nbits--;
212 
213 	for (abitno = 0; abitno < nbits; abitno++) {
214 		if (abitno == MC_PC_COLADDRBIT)
215 			bias = 1;
216 
217 		ibitno = rcbm->csrcb_colbits[abitno + bias];
218 
219 		if (iaddr & (1 << ibitno))
220 			SETBIT(addr, abitno);
221 	}
222 
223 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_to_col: iaddr 0x%llx --> "
224 	    "col 0x%x\n", iaddr, addr);
225 
226 	return (addr);
227 }
228 
229 /*ARGSUSED*/
230 static uint64_t
231 col_to_iaddr(struct mcamd_hdl *hdl, const struct bankaddr_mode *bamp,
232     const struct csrcb_map *rcbm, uint32_t coladdr)
233 {
234 	uint64_t iaddr = 0;
235 	int abitno, ibitno, bias = 0;
236 	int nbits = bamp->bam_ncols;
237 
238 	/*
239 	 * Knock off a column bit if the numbers are ambiguous
240 	 */
241 	if (bamp->bam_ambig)
242 		nbits--;
243 
244 	for (abitno = 0; abitno < nbits; abitno++) {
245 		if (BIT(coladdr, abitno) == 0)
246 			continue;
247 
248 		if (abitno == MC_PC_COLADDRBIT)
249 			bias = 1;
250 
251 		ibitno = rcbm->csrcb_colbits[abitno + bias];
252 		SETBIT(iaddr, ibitno);
253 	}
254 
255 	return (iaddr);
256 }
257 
258 /*
259  * Extract bank bit arguments and xor them together.  Tables for
260  * non bank-swizzling should have all but the first argument zero.
261  */
262 static uint32_t
263 iaddr_to_bank(struct mcamd_hdl *hdl, const struct csrcb_map *rcbm,
264     int bnkswzl, uint64_t iaddr)
265 {
266 	uint32_t addr = 0;
267 	int abitno, ibitno, i;
268 	int bnkargs = bnkswzl ? MC_RC_BANKARGS : 1;
269 
270 	for (abitno = 0; abitno < MC_RC_BANKBITS; abitno++) {
271 		uint32_t val = 0;
272 		for (i = 0; i < bnkargs; i++) {
273 			ibitno = rcbm->csrcb_bankargs[abitno][i];
274 			val ^= ((iaddr >> ibitno) & 0x1);
275 		}
276 		addr |= (val << abitno);
277 	}
278 
279 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_to_bank: iaddr 0x%llx --> "
280 	    "bank 0x%x\n", iaddr, addr);
281 
282 	return (addr);
283 }
284 
285 /*
286  * bank_to_iaddr requires the iaddr reconstructed thus far with at least the
287  * row bits repopulated.  That's because in bank swizzle mode
288  * the bank bits are the result of xor'ing three original iaddr bits
289  * together - two of which come from the row address and the third we
290  * can reconstruct here.  Note that a zero bankaddr bit *can* result
291  * in a nonzero iaddr bit (unlike in row and col reconstruction).
292  */
293 /*ARGSUSED*/
294 static uint64_t
295 bank_to_iaddr(struct mcamd_hdl *hdl, const struct csrcb_map *rcbm,
296     int bnkswzl, uint64_t partiaddr, uint32_t bankaddr)
297 {
298 	uint64_t iaddr = 0;
299 	int abitno, pibitno, i;
300 
301 	for (abitno = 0; abitno < MC_RC_BANKBITS; abitno++) {
302 		uint32_t val = BITVAL(bankaddr, abitno);
303 		if (bnkswzl) {
304 			for (i = 1; i < MC_RC_BANKARGS; i++) {
305 				pibitno = rcbm->csrcb_bankargs[abitno][i];
306 				val ^= BITVAL(partiaddr, pibitno);
307 			}
308 		}
309 		if (val)
310 			SETBIT(iaddr, rcbm->csrcb_bankargs[abitno][0]);
311 	}
312 
313 	return (iaddr);
314 }
315 
316 static int
317 iaddr_to_rcb(struct mcamd_hdl *hdl, uint_t csmode, struct rcp_mc *mcpp,
318     uint64_t iaddr, uint32_t *rowp, uint32_t *colp, uint32_t *bankp)
319 {
320 	const struct bankaddr_mode *bamp;
321 	const struct csrcb_map *rcbm;
322 	struct csintlv_desc csi;
323 
324 	if (gettbls(hdl, csmode, mcpp, &bamp, &rcbm, &csi, "iaddr_to_rcb") < 0)
325 		return (-1);	/* errno already set */
326 
327 	*rowp = iaddr_to_row(hdl, bamp, rcbm, &csi, iaddr);
328 	*colp = iaddr_to_col(hdl, bamp, rcbm, iaddr);
329 	*bankp = iaddr_to_bank(hdl, rcbm, mcpp->bnkswzl, iaddr);
330 
331 	return (0);
332 }
333 
334 /*
335  * Take a reconstructed InputAddr and undo the normalization described in
336  * BKDG 3.29 3.4.4 to include the base address of the MC if no node
337  * interleave or to insert the node interleave selection bits.
338  */
339 static int
340 iaddr_unnormalize(struct mcamd_hdl *hdl, struct rcp_mc *mcpp, uint64_t iaddr,
341     uint64_t *rsltp)
342 {
343 	uint64_t dramaddr;
344 	int intlvbits;
345 
346 	switch (mcpp->intlven) {
347 	case 0x0:
348 		intlvbits = 0;
349 		break;
350 	case 0x1:
351 		intlvbits = 1;
352 		break;
353 	case 0x3:
354 		intlvbits = 2;
355 		break;
356 	case 0x7:
357 		intlvbits = 3;
358 		break;
359 	default:
360 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "iaddr_unnormalize: "
361 		    "illegal IntlvEn of %d for MC 0x%p\n",
362 		    (int)mcpp->intlven, (int)mcpp->num);
363 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
364 	}
365 
366 	if (intlvbits != 0) {
367 		/*
368 		 * For a 2/4/8 way interleave iaddr was formed by excising
369 		 * 1, 2, or 3 bits 12:12, 13:12, or 14:12 from dramaddr,
370 		 * the removed bits having done their job by selecting the
371 		 * responding node.  So we must move bits 35:12 of the
372 		 * reconstructed iaddr up to make a 1, 2 or 3 bit hole and
373 		 * then fill those bits with the current IntlvSel value for
374 		 * this node.  The node base address must be zero if nodes
375 		 * are interleaved.
376 		 */
377 		dramaddr = (BITS(iaddr, 35, 12) << intlvbits) |
378 		    (mcpp->intlvsel << 12) | BITS(iaddr, 11, 0);
379 	} else {
380 		dramaddr = iaddr + mcpp->base;
381 	}
382 
383 	*rsltp = dramaddr;
384 
385 	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_unnormalize: iaddr 0x%llx "
386 	    "intlven 0x%x intlvsel 0x%x MC base 0x%llx --> 0x%llx\n",
387 	    iaddr, (int)mcpp->intlven, (int)mcpp->intlvsel, (int)mcpp->base,
388 	    dramaddr);
389 
390 	return (0);
391 }
392 
393 int
394 mc_pa_to_offset(struct mcamd_hdl *hdl, mcamd_node_t *mc, mcamd_node_t *cs,
395     mcamd_node_t *dimm, uint64_t iaddr, uint64_t *offsetp)
396 {
397 	mcamd_dimm_offset_un_t offset_un;
398 	uint_t csmode;
399 	uint32_t bankaddr, rowaddr, coladdr;
400 	int rank;
401 	mcamd_node_t *tcs;
402 	struct rcp_mc mcp;
403 	struct rcp_cs csp;
404 
405 	*offsetp = MCAMD_RC_INVALID_OFFSET;
406 
407 	if (getmcprops(hdl, mc, "mc_dimm_offset", &mcp) < 0 ||
408 	    getcsprops(hdl, cs, "mc_dimm_offset", &csp) < 0)
409 		return (-1);	/* errno already set */
410 
411 	csmode = MC_CS_MODE(mcp.csbnkmap, csp.num);
412 
413 	/*
414 	 * Convert chip-select number 0 .. 7 to a DIMM rank 0 .. 3.  The
415 	 * rank is the index of the member of the dimm mcd_cs array which
416 	 * matches cs.
417 	 */
418 	for (rank = 0, tcs = mcamd_cs_next(hdl, (mcamd_node_t *)dimm, NULL);
419 	    tcs != NULL;
420 	    rank++, tcs = mcamd_cs_next(hdl, (mcamd_node_t *)dimm, tcs)) {
421 		struct rcp_cs tcsp;
422 
423 		if (getcsprops(hdl, tcs, "mc_dimm_offset", &tcsp) < 0)
424 			return (-1);	/* errno already set */
425 		if (tcsp.num == csp.num)
426 			break;
427 	}
428 	if (rank == MC_CHIP_DIMMRANKMAX) {
429 		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mcamd_dimm_offset: "
430 		    "iteration over chip-selects of dimm 0x%p failed "
431 		    "to match on expected csnum %d\n", dimm, (int)csp.num);
432 		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
433 	}
434 
435 	if (iaddr_to_rcb(hdl, csmode, &mcp, iaddr, &rowaddr,
436 	    &coladdr, &bankaddr) < 0)
437 		return (-1);	/* errno already set */
438 
439 	offset_un.do_offset = 0;
440 
441 	offset_un.do_valid = 1;
442 	offset_un.do_version = MCAMD_OFFSET_VERSION;
443 	offset_un.do_rank = rank;
444 	offset_un.do_row = rowaddr;
445 	offset_un.do_bank = bankaddr;
446 	offset_un.do_col = coladdr;
447 
448 	*offsetp = offset_un.do_offset;
449 
450 	return (0);
451 }
452 
453 /*
454  * Given a MC and DIMM and offset (dimm rank, row, col, internal bank) we
455  * find the corresponding chip-select for the rank and then reconstruct
456  * a system address.  In the absence of serial number support it is possible
457  * that we may be asked to perform this operation on a dimm which has been
458  * swapped, perhaps even for a dimm of different size and number of ranks.
459  * This may happen if fmadm repair has not been used.  There are some
460  * unused bits in the offset and we could guard against this a little
461  * by recording in those bit some of the physical characteristic of the
462  * original DIMM such as size, number of ranks etc.
463  */
464 int
465 mc_offset_to_pa(struct mcamd_hdl *hdl, mcamd_node_t *mc, mcamd_node_t *dimm,
466     uint64_t offset, uint64_t *pap)
467 {
468 	mcamd_node_t *cs;
469 	mcamd_dimm_offset_un_t off_un;
470 	uint32_t rank, rowaddr, bankaddr, coladdr;
471 	int i;
472 	uint64_t iaddr = 0;
473 	const struct bankaddr_mode *bamp;
474 	const struct csrcb_map *rcbm;
475 	struct csintlv_desc csi;
476 	struct rcp_mc mcp;
477 	struct rcp_cs csp;
478 	uint64_t csmode;
479 	int maskhi_hi = MC_DC_CSM_MASKHI_HIBIT;
480 	int maskhi_lo = MC_DC_CSM_MASKHI_LOBIT;
481 	int masklo_hi = MC_DC_CSM_MASKLO_HIBIT;
482 	int masklo_lo = MC_DC_CSM_MASKLO_LOBIT;
483 
484 	off_un.do_offset = offset;
485 	rank = off_un.do_rank;
486 	bankaddr = off_un.do_bank;
487 	rowaddr = off_un.do_row;
488 	coladdr = off_un.do_col;
489 
490 	if (getmcprops(hdl, mc, "mc_offset_to_pa", &mcp) < 0)
491 		return (-1);	/* errno already set */
492 
493 	/*
494 	 * Find the rank'th chip-select on this dimm.
495 	 */
496 	i = 0;
497 	cs = mcamd_cs_next(hdl, dimm, NULL);
498 	while (i != rank && cs != NULL) {
499 		cs = mcamd_cs_next(hdl, dimm, cs);
500 		i++;
501 	}
502 	if (i != rank || cs == NULL) {
503 		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_offset_to_pa: Current "
504 		    "dimm in this slot does not have an %d'th cs\n",
505 		    rank);
506 		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
507 	}
508 
509 	if (getcsprops(hdl, cs, "mc_offset_to_pa", &csp) < 0)
510 		return (-1);	/* errno already set */
511 
512 	csmode = MC_CS_MODE(mcp.csbnkmap, csp.num);
513 
514 	if (gettbls(hdl, csmode, &mcp, &bamp, &rcbm, &csi,
515 	    "mc_offset_to_pa") < 0)
516 		return (-1);	/* errno already set */
517 
518 	/*CONSTANTCONDITION*/
519 	if (MC_DC_CSM_UNMASKED_BITS != 0) {
520 		iaddr |= iaddr_add(hdl, iaddr,
521 		    BITS(csp.base, maskhi_hi + MC_DC_CSM_UNMASKED_BITS,
522 		    maskhi_hi + 1), "unmaskable cs basehi bits");
523 	}
524 
525 	iaddr |= iaddr_add(hdl, iaddr,
526 	    BITS(csp.base, maskhi_hi, maskhi_lo) &
527 	    ~BITS(csp.mask, maskhi_hi, maskhi_lo),
528 	    "cs basehi bits not being masked");
529 
530 	if (mcp.csintlvfctr != 0) {
531 		iaddr |= iaddr_add(hdl, iaddr,
532 		    BITS(csp.base, masklo_hi, masklo_lo) &
533 		    ~BITS(csp.mask, masklo_hi, masklo_lo),
534 		    "cs baselo bits not being masked");
535 	}
536 
537 	iaddr |= iaddr_add(hdl, iaddr,
538 	    row_to_iaddr(hdl, bamp, rcbm, &csi, rowaddr),
539 	    "add iaddr bits from row");
540 
541 	iaddr |= iaddr_add(hdl, iaddr,
542 	    col_to_iaddr(hdl, bamp, rcbm, coladdr),
543 	    "add iaddr bits from col");
544 
545 	iaddr |= iaddr_add(hdl, iaddr,
546 	    bank_to_iaddr(hdl, rcbm, mcp.bnkswzl, iaddr, bankaddr),
547 	    "add iaddr bits from bank");
548 
549 	if (iaddr_unnormalize(hdl, &mcp, iaddr, pap) < 0)
550 		return (-1);	/* errno already set */
551 
552 	return (0);
553 }
554 
555 int
556 mcamd_cs_size(struct mcamd_hdl *hdl, mcamd_node_t *mc, int csnum, size_t *szp)
557 {
558 	uint_t csmode;
559 	struct rcp_mc mcp;
560 	const struct bankaddr_mode *bamp;
561 
562 	if (getmcprops(hdl, mc, "mcamd_cs_size", &mcp) < 0)
563 		return (-1);	/* errno already set */
564 
565 	csmode = MC_CS_MODE(mcp.csbnkmap, csnum);
566 
567 	if (gettbls(hdl, csmode, &mcp, &bamp, NULL, NULL, "mcamd_cs_size") < 0)
568 		return (-1);	/* errno already set */
569 
570 	*szp = MC_CS_SIZE(bamp, mcp.width);
571 
572 	return (0);
573 }
574