root/usr/src/common/mc/mc-amd/mcamd_rowcol.c
/*
 * 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.
 */

/*
 * Copyright (c) 2018, Joyent, Inc.
 */

#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);
}