OpenSolaris_b135/common/mc/mc-amd/mcamd_rowcol.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <mcamd_api.h>
#include <mcamd_err.h>
#include <mcamd_rowcol_impl.h>

/*
 * Convenience structures to stash MC and CS properties in.
 */
struct mcprops {
	mcamd_prop_t num;		/* corresponding chip number */
	mcamd_prop_t rev;		/* revision */
	mcamd_prop_t width;		/* access width */
	mcamd_prop_t base;		/* MC base address */
	mcamd_prop_t lim;		/* MC limit address */
	mcamd_prop_t csbnkmap_reg;	/* chip-select bank map */
	mcamd_prop_t intlven;		/* Node-intlv mask */
	mcamd_prop_t intlvsel;		/* Node-intlv selection for this node */
	mcamd_prop_t csintlvfctr;	/* cs intlv factor on this node */
	mcamd_prop_t bnkswzl;		/* bank-swizzle mode */
	mcamd_prop_t sparecs;		/* spare cs#, if any */
	mcamd_prop_t badcs;		/* substituted cs#, if any */
};

struct csprops {
	mcamd_prop_t num;		/* chip-select number */
	mcamd_prop_t base;		/* chip-select base address */
	mcamd_prop_t mask;		/* chip-select mask */
	mcamd_prop_t testfail;		/* marked testFail */
	mcamd_prop_t dimmrank;		/* rank number on dimm(s) */
};

static int
getmcprops(struct mcamd_hdl *hdl, mcamd_node_t *mc, const char *caller,
    struct mcprops *pp)
{
	if (!mcamd_get_numprops(hdl,
	    mc, MCAMD_PROP_NUM, &pp->num,
	    mc, MCAMD_PROP_REV, &pp->rev,
	    mc, MCAMD_PROP_ACCESS_WIDTH, &pp->width,
	    mc, MCAMD_PROP_BASE_ADDR, &pp->base,
	    mc, MCAMD_PROP_LIM_ADDR, &pp->lim,
	    mc, MCAMD_PROP_CSBANKMAPREG, &pp->csbnkmap_reg,
	    mc, MCAMD_PROP_ILEN, &pp->intlven,
	    mc, MCAMD_PROP_ILSEL, &pp->intlvsel,
	    mc, MCAMD_PROP_CSINTLVFCTR, &pp->csintlvfctr,
	    mc, MCAMD_PROP_BANKSWZL, &pp->bnkswzl,
	    mc, MCAMD_PROP_SPARECS, &pp->sparecs,
	    mc, MCAMD_PROP_BADCS, &pp->badcs,
	    NULL)) {
		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "%s: failed to read mc "
		    "props for mc 0x%p\n", caller, mc);
		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
	}

	return (0);
}

static int
getcsprops(struct mcamd_hdl *hdl, mcamd_node_t *cs, const char *caller,
    struct csprops *csp)
{
	if (!mcamd_get_numprops(hdl,
	    cs, MCAMD_PROP_NUM, &csp->num,
	    cs, MCAMD_PROP_BASE_ADDR, &csp->base,
	    cs, MCAMD_PROP_MASK, &csp->mask,
	    cs, MCAMD_PROP_TESTFAIL, &csp->testfail,
	    cs, MCAMD_PROP_DIMMRANK, &csp->dimmrank,
	    NULL))  {
		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "%s: failed to read cs "
		    "props for cs 0x%p\n", caller, cs);
		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
	}

	return (0);
}

static int
gettbls(struct mcamd_hdl *hdl, uint_t csmode, struct mcprops *mcpp,
    const struct rct_bnkaddrmode **bamp, const struct rct_rcbmap **rcbmp,
    const struct rct_bnkswzlinfo **swzlp, struct rct_csintlv *csid,
    const char *caller)
{
	uint_t rev = (uint_t)mcpp->rev;
	int width = (int)mcpp->width;

	if (bamp && (*bamp = rct_bnkaddrmode(rev, csmode)) == NULL) {
		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: no bank address mode "
		    "table for MC rev %d csmode %d\n", caller, rev, csmode);
		return (mcamd_set_errno(hdl, EMCAMD_NOTSUP));
	}

	if (rcbmp && (*rcbmp = rct_rcbmap(rev, width, csmode)) == NULL) {
		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: no dram address map "
		    "table for MC rev %d csmode %d\n", caller,
		    rev, csmode);
		return (mcamd_set_errno(hdl, EMCAMD_NOTSUP));
	}

	if (swzlp && (*swzlp = rct_bnkswzlinfo(rev, width)) == NULL) {
		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: no bank swizzling "
		    "table for MC rev %d width %d\n", caller, rev, width);
		return (mcamd_set_errno(hdl, EMCAMD_NOTSUP));
	}

	if (csid) {
		if (mcpp->csintlvfctr > 1) {
			rct_csintlv_bits(rev, width, csmode,
			    mcpp->csintlvfctr, csid);
			if (csid->csi_factor == 0) {
				mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: "
				    "could not work out cs interleave "
				    "paramters for MC rev %d, width %d, "
				    "csmode %d, factor %d\n", caller,
				    rev, width, csmode,
				    (int)mcpp->csintlvfctr);
				return (mcamd_set_errno(hdl, EMCAMD_NOTSUP));
			}
		} else {
			csid->csi_factor = 0;
		}
	}

	return (0);
}

static uint64_t
iaddr_add(struct mcamd_hdl *hdl, uint64_t in, uint64_t add, const char *what)
{
	uint64_t new = in | add;

	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "%s: 0x%llx | 0x%llx --> 0x%llx",
	    what, in, add, new);

	return (add);
}

/*
 * Where the number of row/col address bits is ambiguous (affects CG and
 * earlier only) we will assign the "floating" bit to row address.  If
 * we adopt the same convention in address reconstruction then all should work.
 */
static uint32_t
iaddr_to_row(struct mcamd_hdl *hdl, const struct rct_bnkaddrmode *bamp,
    const struct rct_rcbmap *rcbm, struct rct_csintlv *csid, uint64_t iaddr)
{
	uint32_t addr = 0;
	int abitno, ibitno;
	int nbits = bamp->bam_nrows;
	int swapped = 0;

	for (abitno = 0; abitno < nbits; abitno++) {
		ibitno = rcbm->rcb_rowbit[abitno];
		if (MC_RC_CSI_SWAPPED_BIT(csid, ibitno)) {
			ibitno = MC_RC_CSI_BITSWAP(csid, ibitno);
			swapped++;
		}
		if (BITVAL(iaddr, ibitno) != 0)
			SETBIT(addr, abitno);
	}

	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_to_row: iaddr 0x%llx --> "
	    "row 0x%x (%d bits swapped for cs intlv)\n", iaddr, addr, swapped);

	return (addr);
}

/*ARGSUSED*/
static uint64_t
row_to_iaddr(struct mcamd_hdl *hdl, const struct rct_bnkaddrmode *bamp,
    const struct rct_rcbmap *rcbm, struct rct_csintlv *csid, uint32_t rowaddr)
{
	uint64_t iaddr = 0;
	int abitno, ibitno;
	int nbits = bamp->bam_nrows;

	for (abitno = 0; abitno < nbits; abitno++) {
		if (BIT(rowaddr, abitno) == 0)
			continue;
		ibitno = rcbm->rcb_rowbit[abitno];
		if (MC_RC_CSI_SWAPPED_BIT(csid, ibitno)) {
			ibitno = MC_RC_CSI_BITSWAP(csid, ibitno);
		}
		SETBIT(iaddr, ibitno);
	}

	return (iaddr);
}


static uint32_t
iaddr_to_col(struct mcamd_hdl *hdl, const struct rct_bnkaddrmode *bamp,
    const struct rct_rcbmap *rcbm, uint64_t iaddr)
{
	uint32_t addr = 0;
	int abitno, ibitno, bias = 0;
	int nbits = bamp->bam_ncols;

	/*
	 * Knock off a column bit if the numbers are ambiguous
	 */
	if (bamp->bam_ambig)
		nbits--;

	for (abitno = 0; abitno < nbits; abitno++) {
		if (abitno == MC_PC_COLADDRBIT)
			bias = 1;

		ibitno = rcbm->rcb_colbit[abitno + bias];

		if (BITVAL(iaddr, ibitno) != 0)
			SETBIT(addr, abitno);
	}

	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_to_col: iaddr 0x%llx --> "
	    "col 0x%x\n", iaddr, addr);

	return (addr);
}

/*ARGSUSED*/
static uint64_t
col_to_iaddr(struct mcamd_hdl *hdl, const struct rct_bnkaddrmode *bamp,
    const struct rct_rcbmap *rcbm, uint32_t coladdr)
{
	uint64_t iaddr = 0;
	int abitno, ibitno, bias = 0;
	int nbits = bamp->bam_ncols;

	/*
	 * Knock off a column bit if the numbers are ambiguous
	 */
	if (bamp->bam_ambig)
		nbits--;

	for (abitno = 0; abitno < nbits; abitno++) {
		if (BIT(coladdr, abitno) == 0)
			continue;

		if (abitno == MC_PC_COLADDRBIT)
			bias = 1;

		ibitno = rcbm->rcb_colbit[abitno + bias];
		SETBIT(iaddr, ibitno);
	}

	return (iaddr);
}

/*
 * Extract bank bit arguments and swizzle if requested.
 */
static uint32_t
iaddr_to_bank(struct mcamd_hdl *hdl, const struct rct_rcbmap *rcbm,
    const struct rct_bnkswzlinfo *swzlp, uint64_t iaddr)
{
	uint32_t addr = 0;
	int abitno, ibitno, i;

	for (abitno = 0; abitno < rcbm->rcb_nbankbits; abitno++) {
		uint32_t val;

		/*
		 * rcb_bankbit[abitno] tells us which iaddr bit number
		 * will form bit abitno of the bank address
		 */
		ibitno = rcbm->rcb_bankbit[abitno];
		val = BITVAL(iaddr, ibitno);

		/*
		 * If bank swizzling is in operation then xor the bit value
		 * obtained above with other iaddr bits.
		 */
		if (swzlp) {
			for (i = 0; i < MC_RC_SWZLBITS; i++) {
				ibitno = swzlp->bswz_rowbits[abitno][i];
				val ^= BITVAL(iaddr, ibitno);
			}
		}

		if (val)
			SETBIT(addr, abitno);
	}

	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_to_bank: iaddr 0x%llx --> "
	    "bank 0x%x\n", iaddr, addr);

	return (addr);
}

/*
 * bank_to_iaddr requires the iaddr reconstructed thus far with at least the
 * row bits repopulated.  That's because in bank swizzle mode
 * the bank bits are the result of xor'ing three original iaddr bits
 * together - two of which come from the row address and the third we
 * can reconstruct here.  Note that a zero bankaddr bit *can* result
 * in a nonzero iaddr bit (unlike in row and col reconstruction).
 */
/*ARGSUSED*/
static uint64_t
bank_to_iaddr(struct mcamd_hdl *hdl, const struct rct_rcbmap *rcbm,
    const struct rct_bnkswzlinfo *swzlp, uint64_t partiaddr, uint32_t bankaddr)
{
	uint64_t iaddr = 0;
	int abitno, pibitno, i;

	for (abitno = 0; abitno < rcbm->rcb_nbankbits; abitno++) {
		uint32_t val = BITVAL(bankaddr, abitno);
		if (swzlp) {
			for (i = 0; i < MC_RC_SWZLBITS; i++) {
				pibitno = swzlp->bswz_rowbits[abitno][i];
				val ^= BITVAL(partiaddr, pibitno);
			}
		}
		if (val)
			SETBIT(iaddr, rcbm->rcb_bankbit[abitno]);
	}

	return (iaddr);
}

static int
iaddr_to_rcb(struct mcamd_hdl *hdl, uint_t csmode, struct mcprops *mcpp,
    uint64_t iaddr, uint32_t *rowp, uint32_t *colp, uint32_t *bankp)
{
	const struct rct_bnkaddrmode *bamp;
	const struct rct_rcbmap *rcbmp;
	const struct rct_bnkswzlinfo *swzlp = NULL;
	struct rct_csintlv csi;

	if (gettbls(hdl, csmode, mcpp, &bamp, &rcbmp,
	    mcpp->bnkswzl ? &swzlp : NULL, &csi,
	    "iaddr_to_rcb") < 0)
		return (-1);	/* errno already set */

	*rowp = iaddr_to_row(hdl, bamp, rcbmp, &csi, iaddr);
	*colp = iaddr_to_col(hdl, bamp, rcbmp, iaddr);
	*bankp = iaddr_to_bank(hdl, rcbmp, swzlp, iaddr);

	return (0);
}

/*
 * Take a reconstructed InputAddr and undo the normalization described in
 * BKDG 3.29 3.4.4 to include the base address of the MC if no node
 * interleave or to insert the node interleave selection bits.
 */
static int
iaddr_unnormalize(struct mcamd_hdl *hdl, struct mcprops *mcpp, uint64_t iaddr,
    uint64_t *rsltp)
{
	uint64_t dramaddr;
	int intlvbits;

	switch (mcpp->intlven) {
	case 0x0:
		intlvbits = 0;
		break;
	case 0x1:
		intlvbits = 1;
		break;
	case 0x3:
		intlvbits = 2;
		break;
	case 0x7:
		intlvbits = 3;
		break;
	default:
		mcamd_dprintf(hdl, MCAMD_DBG_ERR, "iaddr_unnormalize: "
		    "illegal IntlvEn of %d for MC 0x%p\n",
		    (int)mcpp->intlven, (int)mcpp->num);
		return (mcamd_set_errno(hdl, EMCAMD_TREEINVALID));
	}

	if (intlvbits != 0) {
		/*
		 * For a 2/4/8 way interleave iaddr was formed by excising
		 * 1, 2, or 3 bits 12:12, 13:12, or 14:12 from dramaddr,
		 * the removed bits having done their job by selecting the
		 * responding node.  So we must move bits 35:12 of the
		 * reconstructed iaddr up to make a 1, 2 or 3 bit hole and
		 * then fill those bits with the current IntlvSel value for
		 * this node.  The node base address must be zero if nodes
		 * are interleaved.
		 *
		 * Note that the DRAM controller InputAddr is still 36 bits
		 * 35:0 on rev F.
		 */
		dramaddr = (BITS(iaddr, 35, 12) << intlvbits) |
		    (mcpp->intlvsel << 12) | BITS(iaddr, 11, 0);
	} else {
		dramaddr = iaddr + mcpp->base;
	}

	*rsltp = dramaddr;

	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "iaddr_unnormalize: iaddr 0x%llx "
	    "intlven 0x%x intlvsel 0x%x MC base 0x%llx --> 0x%llx\n",
	    iaddr, (int)mcpp->intlven, (int)mcpp->intlvsel, (int)mcpp->base,
	    dramaddr);

	return (0);
}

int
mc_pa_to_offset(struct mcamd_hdl *hdl, mcamd_node_t *mc, mcamd_node_t *cs,
    uint64_t iaddr, uint64_t *offsetp)
{
	mcamd_dimm_offset_un_t offset_un;
	uint_t csmode;
	uint32_t bankaddr, rowaddr, coladdr;
	struct mcprops mcp;
	struct csprops csp;

	*offsetp = MCAMD_RC_INVALID_OFFSET;

	if (getmcprops(hdl, mc, "mc_dimm_offset", &mcp) < 0 ||
	    getcsprops(hdl, cs, "mc_dimm_offset", &csp) < 0)
		return (-1);	/* errno already set */

	csmode = MC_CS_MODE(mcp.csbnkmap_reg, csp.num);

	if (iaddr_to_rcb(hdl, csmode, &mcp, iaddr, &rowaddr,
	    &coladdr, &bankaddr) < 0)
		return (-1);	/* errno already set */

	offset_un.do_offset = 0;

	offset_un.do_valid = 1;
	offset_un.do_version = MCAMD_OFFSET_VERSION;
	offset_un.do_rank = (uint32_t)csp.dimmrank;
	offset_un.do_row = rowaddr;
	offset_un.do_bank = bankaddr;
	offset_un.do_col = coladdr;

	*offsetp = offset_un.do_offset;

	return (0);
}

/*
 * Given an MC, DIMM and offset (dimm rank, row, col, internal bank) we
 * find the corresponding chip-select for the rank and then reconstruct
 * a system address.  In the absence of serial number support it is possible
 * that we may be asked to perform this operation on a dimm which has been
 * swapped, perhaps even for a dimm of different size and number of ranks.
 * This may happen if fmadm repair has not been used.  There are some
 * unused bits in the offset and we could guard against this a little
 * by recording in those bit some of the physical characteristic of the
 * original DIMM such as size, number of ranks etc.
 */
int
mc_offset_to_pa(struct mcamd_hdl *hdl, mcamd_node_t *mc, mcamd_node_t *dimm,
    uint64_t offset, uint64_t *pap)
{
	mcamd_node_t *cs;
	mcamd_dimm_offset_un_t off_un;
	uint32_t rank, rowaddr, bankaddr, coladdr;
	uint64_t iaddr = 0;
	const struct rct_bnkaddrmode *bamp;
	const struct rct_rcbmap *rcbmp;
	const struct rct_bnkswzlinfo *swzlp = NULL;
	struct rct_csintlv csi;
	struct mcprops mcp;
	struct csprops csp;
	uint64_t csmode;
	int maskhi_hi, maskhi_lo, masklo_hi, masklo_lo;

	off_un.do_offset = offset;
	rank = off_un.do_rank;
	bankaddr = off_un.do_bank;
	rowaddr = off_un.do_row;
	coladdr = off_un.do_col;

	mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_offset_to_pa: offset 0x%llx "
	    "-> rank %d bank %d row 0x%x col 0x%x\n", offset,
	    rank, bankaddr, rowaddr, coladdr);

	if (getmcprops(hdl, mc, "mc_offset_to_pa", &mcp) < 0)
		return (-1);	/* errno already set */

	maskhi_hi = MC_CSMASKHI_HIBIT(mcp.rev);
	maskhi_lo = MC_CSMASKHI_LOBIT(mcp.rev);
	masklo_hi = MC_CSMASKLO_HIBIT(mcp.rev);
	masklo_lo = MC_CSMASKLO_LOBIT(mcp.rev);

	/*
	 * Find the chip-select on this dimm using the given rank.
	 */
	for (cs = mcamd_cs_next(hdl, dimm, NULL); cs != NULL;
	    cs = mcamd_cs_next(hdl, dimm, cs)) {
		if (getcsprops(hdl, cs, "mc_offset_to_pa", &csp) < 0)
			return (-1);	/* errno already set */

		if (csp.dimmrank == rank)
			break;
	}

	if (cs == NULL) {
		mcamd_dprintf(hdl, MCAMD_DBG_FLOW, "mc_offset_to_pa: Current "
		    "dimm in this slot does not have a cs using rank %d\n",
		    rank);
		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
	}

	/*
	 * If the cs# has been substituted by the online spare then the
	 * given unum is not actually contributing to the system address
	 * map since all accesses to it are redirected.
	 *
	 * If the cs# failed BIOS test it is not in the address map.
	 *
	 * If the cs# is the online spare cs# then it is contributing to
	 * the system address map only if swapped in, and the csbase etc
	 * parameters to use must be those of the bad cs#.
	 */
	if (mcp.badcs != MC_INVALNUM && csp.num == mcp.badcs) {
		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
	} else if (csp.testfail) {
		return (mcamd_set_errno(hdl, EMCAMD_NOADDR));
	} else if (mcp.sparecs != MC_INVALNUM && csp.num == mcp.sparecs &&
	    mcp.badcs != MC_INVALNUM) {
		/*
		 * Iterate over all cs# of this memory controller to find
		 * the bad one - the bad cs# need not be on the same dimm
		 * as the spare.
		 */
		for (cs = mcamd_cs_next(hdl, mc, NULL); cs != NULL;
		    cs = mcamd_cs_next(hdl, mc, cs)) {
			mcamd_prop_t csnum;

			if (!mcamd_get_numprop(hdl, cs, MCAMD_PROP_NUM,
			    &csnum)) {
				mcamd_dprintf(hdl, MCAMD_DBG_ERR,
				    "mcamd_offset_to_pa: csnum lookup failed "
				    "while looking for bad cs#");
				return (mcamd_set_errno(hdl,
				    EMCAMD_TREEINVALID));
			}
			if (csnum == mcp.badcs)
				break;
		}

		if (cs == NULL) {
			mcamd_dprintf(hdl, MCAMD_DBG_ERR, "mcamd_offset_to_pa: "
			    "failed to find cs for bad cs#%d\n", mcp.badcs);
				return (mcamd_set_errno(hdl,
				    EMCAMD_TREEINVALID));
		}

		/* found bad cs - reread properties from it instead of spare */
		if (getcsprops(hdl, cs, "mc_offset_to_pa", &csp) < 0)
			return (-1);	/* errno already set */
	}

	csmode = MC_CS_MODE(mcp.csbnkmap_reg, csp.num);

	if (gettbls(hdl, csmode, &mcp, &bamp, &rcbmp,
	    mcp.bnkswzl ? &swzlp : NULL, &csi,
	    "mc_offset_to_pa") < 0)
		return (-1);	/* errno already set */

	/*
	 * If there are umaskable DRAM InputAddr bits the add those bits
	 * to iaddr from the cs base address.
	 */
	if (MC_CSMASK_UNMASKABLE(mcp.rev) != 0) {
		iaddr |= iaddr_add(hdl, iaddr,
		    BITS(csp.base, maskhi_hi + MC_CSMASK_UNMASKABLE(mcp.rev),
		    maskhi_hi + 1), "unmaskable cs basehi bits");
	}

	/*
	 * basehi bits not meing masked pass straight through to the
	 * iaddr.
	 */
	iaddr |= iaddr_add(hdl, iaddr,
	    BITS(csp.base, maskhi_hi, maskhi_lo) &
	    ~BITS(csp.mask, maskhi_hi, maskhi_lo),
	    "cs basehi bits not being masked");

	/*
	 * if cs interleaving is active then baselo address bit are being
	 * masked - pass the rest through.
	 */
	if (mcp.csintlvfctr > 1) {
		iaddr |= iaddr_add(hdl, iaddr,
		    BITS(csp.base, masklo_hi, masklo_lo) &
		    ~BITS(csp.mask, masklo_hi, masklo_lo),
		    "cs baselo bits not being masked");
	}

	/*
	 * Reconstruct iaddr bits from known row address
	 */
	iaddr |= iaddr_add(hdl, iaddr,
	    row_to_iaddr(hdl, bamp, rcbmp, &csi, rowaddr),
	    "add iaddr bits from row");

	/*
	 * Reconstruct iaddr bits from known column address
	 */
	iaddr |= iaddr_add(hdl, iaddr,
	    col_to_iaddr(hdl, bamp, rcbmp, coladdr),
	    "add iaddr bits from col");

	/*
	 * Reconstruct iaddr bits from known internal banksel address
	 */
	iaddr |= iaddr_add(hdl, iaddr,
	    bank_to_iaddr(hdl, rcbmp, swzlp, iaddr, bankaddr),
	    "add iaddr bits from bank");

	/*
	 * Move iaddr up into the range for this MC and insert any
	 * node interleave selection bits.
	 */
	if (iaddr_unnormalize(hdl, &mcp, iaddr, pap) < 0)
		return (-1);	/* errno already set */

	return (0);
}

int
mcamd_cs_size(struct mcamd_hdl *hdl, mcamd_node_t *mc, int csnum, size_t *szp)
{
	uint_t csmode;
	struct mcprops mcp;
	const struct rct_bnkaddrmode *bamp;

	if (getmcprops(hdl, mc, "mcamd_cs_size", &mcp) < 0)
		return (-1);	/* errno already set */

	csmode = MC_CS_MODE(mcp.csbnkmap_reg, csnum);

	if (gettbls(hdl, csmode, &mcp, &bamp, NULL, NULL, NULL,
	    "mcamd_cs_size") < 0)
		return (-1);	/* errno already set */

	*szp = MC_CS_SIZE(bamp, mcp.width);

	return (0);
}