OpenSolaris_b135/uts/intel/io/intel_nhm/mem_addr.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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/fm/protocol.h>
#include <sys/cpu_module_impl.h>
#include <sys/mc_intel.h>
#include "intel_nhm.h"
#include "nhm_log.h"
#include "mem_addr.h"

char closed_page;
char ecc_enabled;
char divby3_enabled;
char lockstep[2];
char mirror_mode[2];
char spare_channel[2];
sad_t sad[MAX_SAD_DRAM_RULE];
tad_t tad[MAX_CPU_NODES][MAX_TAD_DRAM_RULE];
sag_ch_t sag_ch[MAX_CPU_NODES][CHANNELS_PER_MEMORY_CONTROLLER]
	[MAX_TAD_DRAM_RULE];
rir_t rir[MAX_CPU_NODES][CHANNELS_PER_MEMORY_CONTROLLER]
	[MAX_TAD_DRAM_RULE];
dod_t dod_reg[MAX_CPU_NODES][CHANNELS_PER_MEMORY_CONTROLLER]
	[MAX_DIMMS_PER_CHANNEL];

static int
channel_in_interleave(int node, int channel, int rule, int *way_p,
    int *no_interleave_p)
{
	int way;
	int c;
	int i;
	uint32_t mc_channel_mapper;
	int lc;
	int rt = 0;
	int start = 0;

	if (lockstep[node] || mirror_mode[node]) {
		*no_interleave_p = 0;
		if (channel > 1)
			return (0);
		else
			return (1);
	}
	mc_channel_mapper = MC_CHANNEL_MAPPER_RD(node);
	lc = -1;
	c = 1 << channel;
	for (i = 0; i < CHANNELS_PER_MEMORY_CONTROLLER; i++) {
		if ((CHANNEL_MAP(mc_channel_mapper, i, 0) & c) != 0) {
			lc = i;
			break;
		}
	}
	if (lc == -1) {
		for (i = 0; i < CHANNELS_PER_MEMORY_CONTROLLER; i++) {
			if ((CHANNEL_MAP(mc_channel_mapper, i, 1) & c) != 0) {
				lc = i;
				break;
			}
		}
	}
	if (lc == -1) {
		return (0);
	}
	*way_p = 0;
	*no_interleave_p = 0;
	if (node && tad[node][rule].mode == 2)
		start = 4;
	for (way = start; way < INTERLEAVE_NWAY; way++) {
		if (lc == TAD_INTERLEAVE(tad[node][rule].pkg_list, way)) {
			*way_p = way;
			if (way == 0) {
				for (i = way + 1; i < INTERLEAVE_NWAY; i++) {
					c = TAD_INTERLEAVE(
					    tad[node][rule].pkg_list, i);
					if (lc != c) {
						break;
					}
				}
				if (i == INTERLEAVE_NWAY)
					*no_interleave_p = 1;
			}
			rt = 1;
			break;
		}
	}
	return (rt);
}

int
address_to_node(uint64_t addr, int *interleave_p)
{
	int i;
	int node = -1;
	uint64_t base;
	int way;
	uchar_t package;

	base = 0;
	for (i = 0; i < MAX_SAD_DRAM_RULE; i++) {
		if (sad[i].enable && addr >= base && addr < sad[i].limit) {
			switch (sad[i].mode) {
			case 0:
				way = (addr >> 6) & 7;
				break;
			case 1:
				way = ((addr >> 6) & 7) ^ ((addr >> 16) & 7);
				break;
			case 2:
				way = ((addr >> 4) & 4) |
				    (((addr >> 6) & 0x3ffffffff) % 3);
				break;
			default:
				return (-1);
			}
			package = SAD_INTERLEAVE(sad[i].node_list, way);
			if (interleave_p)
				*interleave_p = sad[i].interleave;
			if (package == 1)
				node = 0;
			else if (package == 2)
				node = 1;
			else
				node = -1;
			break;
		}
		base = sad[i].limit;
	}
	return (node);
}

static uint64_t
channel_address(int node, int channel, int rule, uint64_t addr)
{
	uint64_t caddr;

	if (lockstep[node] || mirror_mode[node])
		channel = 0;
	caddr = (((addr >> 16) +
	    (int64_t)sag_ch[node][channel][rule].soffset) << 16) |
	    (addr & 0xffc0);
	if (sag_ch[node][channel][rule].remove8) {
		caddr = ((caddr >> 1) & ~0xff) | (caddr & 0xff);
	}
	if (sag_ch[node][channel][rule].remove7) {
		caddr = ((caddr >> 1) & ~0x7f) | (caddr & 0x7f);
	}
	if (sag_ch[node][channel][rule].remove6) {
		caddr = ((caddr >> 1) & ~0x3f) | (caddr & 0x3f);
	}
	caddr = caddr & 0x1fffffffff;
	if (sag_ch[node][channel][rule].divby3) {
		caddr = ((((caddr >> 6) / 3) << 6) & 0x1fffffffc0) |
		    (caddr & 0x3f);
	}
	return (caddr);
}

int
address_to_channel(int node, uint64_t addr, int write,
    int *log_chan, uint64_t *channel_addrp, int *interleave_p)
{
	int i;
	int channel = -1;
	uint64_t base;
	uint32_t mapper;
	uint32_t lc;
	int way;

	base = 0;
	for (i = 0; i < MAX_TAD_DRAM_RULE; i++) {
		if (tad[node][i].enable && addr >= base &&
		    addr < tad[node][i].limit) {
			switch (tad[node][i].mode) {
			case 0:
				way = (addr >> 6) & 7;
				break;
			case 1:
				way = ((addr >> 6) & 7) ^ ((addr >> 16) & 7);
				break;
			case 2:
				way = ((addr >> 4) & 4) |
				    (((addr >> 6) & 0x3ffffffff) % 3);
				break;
			default:
				return (-1);
			}
			/* get logical channel number */
			channel = TAD_INTERLEAVE(tad[node][i].pkg_list, way);
			if (log_chan)
				*log_chan = channel;

			if (channel_addrp) {
				*channel_addrp = channel_address(node,
				    channel, i, addr);
			}
			if (interleave_p)
				*interleave_p = tad[node][i].interleave;
			break;
		}
		base = tad[node][i].limit;
	}
	if (!lockstep[node] && channel != -1) {
		mapper = MC_CHANNEL_MAPPER_RD(node);
		lc = CHANNEL_MAP(mapper, channel, write);
		switch (lc) {
		case 1:
			channel = 0;
			break;
		case 2:
			channel = 1;
			break;
		case 4:
			channel = 2;
			break;
		case 3:		/* mirror PCH0 and PCH1 */
			if (!write) {
				if (((addr >> 24) & 1) ^ ((addr >> 12) & 1) ^
				    ((addr >> 6) & 1))
					channel = 1;
				else
					channel = 0;
			}
			break;
		case 5:		/* sparing PCH0 to PCH2 */
			channel = 0;
			break;
		case 6:		/* sparing PCH1 to PCH2 */
			channel = 1;
			break;
		}
	}
	return (channel);
}

int
channels_interleave(uint64_t addr)
{
	int node;
	int sinterleave;
	int channels, channels1;

	node = address_to_node(addr, &sinterleave);
	if (sinterleave == 1) {
		channels = 0;
		(void) address_to_channel(node, addr, 0, 0, 0, &channels);
	} else {
		channels = 0;
		channels1 = 0;
		(void) address_to_channel(0, addr, 0, 0, 0, &channels);
		(void) address_to_channel(1, addr, 0, 0, 0, &channels1);
		channels += channels1;
	}
	return (channels);
}

int
channel_addr_to_dimm(int node, int channel, uint64_t caddr, int *rank_p,
    uint64_t *rank_addr_p)
{
	int i;
	uint64_t base;
	uint64_t rank_addr;
	int rank;
	int dimm;
	int way;

	dimm = -1;
	rank = -1;
	base = 0;
	rank_addr = -1ULL;
	for (i = 0; i < MAX_TAD_DRAM_RULE; i++) {
		if (caddr >= base && caddr < rir[node][channel][i].limit) {
			if (closed_page) {
				way = (caddr >> 6) & 3;
				rank_addr = (((caddr + (int64_t)
				    rir[node][channel][i].way[way].offset *
				    VRANK_SZ) /
				    rir[node][channel][i].interleave) &
				    ~0x3f) + (caddr & 0x3f);
			} else {
				way = (caddr >> 12) & 3;
				rank_addr = (((caddr + (int64_t)
				    rir[node][channel][i].way[way].offset *
				    VRANK_SZ) /
				    rir[node][channel][i].interleave) &
				    ~0xfff) + (caddr & 0xfff);
			}
			rank = rir[node][channel][i].way[way].rank;
			dimm = rank >> 2;
			break;
		}
		base = rir[node][channel][i].limit;
	}
	*rank_p = rank;
	*rank_addr_p = rank_addr;
	return (dimm);
}

static int
socket_interleave(uint64_t addr, int node, int channel, int rule,
    int *way_p)
{
	int i, j;
	uint64_t base;
	uchar_t package;
	uchar_t xp;
	uchar_t xc;
	int ot = 0;
	int mode;
	int start;
	int rt = 1;
	int found = 0;

	if (mirror_mode[node] || lockstep[node])
		channel = 0;
	package = node + 1;
	mode = tad[node][rule].mode;
	base = 0;
	for (i = 0; i < MAX_SAD_DRAM_RULE; i++) {
		if (sad[i].enable && addr >= base && addr < sad[i].limit) {
			if (mode == 2) {
				for (j = 0; j < INTERLEAVE_NWAY; j++) {
					xp = SAD_INTERLEAVE(sad[i].node_list,
					    j);
					if (package != xp) {
						ot++;
						if (found) {
							rt = 2;
							break;
						}
					} else {
						found = 1;
						if (ot) {
							rt = 2;
							break;
						}
					}
				}
			} else {
				if (mode == 2)
					start = *way_p;
				else
					start = 0;
				for (j = start; j < INTERLEAVE_NWAY; j++) {
					xp = SAD_INTERLEAVE(sad[i].node_list,
					    j);
					if (package != xp) {
						ot++;
						if (found) {
							rt = 2;
							break;
						}
					} else if (!found) {
						xc = TAD_INTERLEAVE(
						    tad[node][rule].pkg_list,
						    j);
						if (channel == xc) {
							*way_p = j;
							if (ot) {
								rt = 2;
								break;
							}
							found = 1;
						}
					}
				}
			}
			break;
		}
		base = sad[i].limit;
	}
	return (rt);
}

uint64_t
dimm_to_addr(int node, int channel, int rank, uint64_t rank_addr,
    uint64_t *rank_base_p, uint64_t *rank_sz_p, uint32_t *socket_interleave_p,
    uint32_t *channel_interleave_p, uint32_t *rank_interleave_p,
    uint32_t *socket_way_p, uint32_t *channel_way_p, uint32_t *rank_way_p)
{
	int i;
	int way, xway;
	uint64_t addr;
	uint64_t caddr;
	uint64_t cbaddr;
	uint64_t baddr;
	uint64_t rlimit;
	uint64_t rank_sz;
	uint64_t base;
	int lchannel;
	int bits;
	int no_interleave;
	int sinterleave;
	int cinterleave;
	int rinterleave;
	int found = 0;

	if (lockstep[node] || mirror_mode[node])
		lchannel = 0;
	else
		lchannel = channel;
	addr = -1;
	base = 0;
	for (i = 0; i < MAX_TAD_DRAM_RULE && found == 0; i++) {
		for (way = 0; way < MAX_RIR_WAY; way++) {
			if (rir[node][channel][i].way[way].dimm_rank == rank) {
				rlimit = rir[node][channel][i].way[way].rlimit;
				if (rlimit && rank_addr >= rlimit)
					continue;
				cbaddr = base;
				if (closed_page) {
					caddr = (rank_addr & ~0x3f) *
					    rir[node][channel][i].interleave -
					    (int64_t)rir[node][channel][i].
					    way[way].soffset * VRANK_SZ;
					caddr += way << 6;
					caddr |= rank_addr & 0x3f;
				} else {
					caddr = (rank_addr & ~0xfff) *
					    rir[node][channel][i].interleave -
					    (int64_t)rir[node][channel][i].
					    way[way].soffset * VRANK_SZ;
					caddr += way << 12;
					caddr |= rank_addr & 0xfff;
				}
				if (caddr < rir[node][channel][i].limit) {
					rinterleave =
					    rir[node][channel][i].interleave;
					rank_sz = (rir[node][channel][i].limit -
					    base) / rinterleave;
					found = 1;
					if (rank_interleave_p) {
						*rank_interleave_p =
						    rinterleave;
					}
					if (rank_way_p)
						*rank_way_p = way;
					break;
				}
			}
		}
		base = rir[node][channel][i].limit;
	}
	if (!found)
		return (-1ULL);
	base = 0;
	for (i = 0; i < MAX_TAD_DRAM_RULE; i++) {
		way = 0;
		if (tad[node][i].enable &&
		    channel_in_interleave(node, channel, i, &way,
		    &no_interleave)) {
			bits = 0;
			addr = caddr;
			baddr = cbaddr;
			if (sag_ch[node][lchannel][i].divby3) {
				addr = (((addr >> 6) * 3) << 6) +
				    (addr & 0x3f);
				baddr = (((baddr >> 6) * 3) << 6);
			}
			if (sag_ch[node][lchannel][i].remove6) {
				bits = 1;
				addr = ((addr & ~0x3f) << 1) | (addr & 0x3f);
				baddr = (baddr & ~0x3f) << 1;
			}
			if (sag_ch[node][lchannel][i].remove7) {
				bits =  bits | 2;
				addr = ((addr & ~0x7f) << 1) | (addr & 0x7f);
				baddr = ((baddr & ~0x7f) << 1) | (baddr & 0x40);
			}
			if (sag_ch[node][lchannel][i].remove8) {
				bits =  bits | 4;
				addr = ((addr & ~0xff) << 1) | (addr & 0xff);
				baddr = ((baddr & ~0xff) << 1) | (baddr & 0xc0);
			}
			addr -= (int64_t)sag_ch[node][lchannel][i].soffset <<
			    16;
			baddr -= (int64_t)
			    sag_ch[node][lchannel][i].soffset << 16;
			if (addr < tad[node][i].limit) {
				/*
				 * this is the target address descripter to use
				 */
				sinterleave = socket_interleave(addr,
				    node, channel, i, &way);
				if (socket_interleave_p) {
					*socket_interleave_p = sinterleave;
				}
				if (socket_way_p)
					*socket_way_p = way;
				if ((no_interleave && sinterleave == 1) ||
				    mirror_mode[node] || lockstep[node]) {
					cinterleave = 1;
				} else {
					cinterleave = channels_interleave(addr);
				}
				if (channel_interleave_p) {
					*channel_interleave_p = cinterleave;
				}
				if (baddr + (rank_sz * rinterleave *
				    cinterleave * sinterleave) >
				    tad[node][i].limit) {
					/*
					 * The system address mapped to this
					 * rank is not contiguous or has
					 * different socket/channel interleave
					 * adjust vitual rank to address where
					 * change or break occures
					 */
					rank_sz = (tad[node][i].limit - baddr) /
					    (cinterleave * sinterleave *
					    rinterleave);
				}
				if (rank_sz_p) {
					*rank_sz_p = rank_sz;
				}
				if (rank_base_p)
					*rank_base_p = baddr;
				if (channel_way_p)
					*channel_way_p = way;
				if (sinterleave == 1 && no_interleave) {
					break;
				}
				switch (tad[node][i].mode) {
				case 0:
					addr += way * 0x40;
					break;
				case 1:
					way = (way ^ (addr >> 16)) & bits;
					addr += way * 0x40;
					break;
				case 2:
					if (sinterleave == 1) {
						xway = ((addr >> 4) & 4) |
						    (((addr >> 6) &
						    0x3ffffffff) % 3);
						if (((way - xway) & 3) == 3)
							xway = (way - xway) & 4;
						else
							xway = way - xway;
						switch (xway) {
						case 0:
							way = 0;
							break;
						case 5:
							way = 1;
							break;
						case 2:
							way = 2;
							break;
						case 4:
							way = 3;
							break;
						case 1:
							way = 4;
							break;
						case 6:
							way = 5;
							break;
						}
					} else {
						xway = (way & 3) -
						    (((addr >> 6) &
						    0x3ffffffff) % 3);
						if (xway < 0)
							xway += 3;
						switch (xway) {
						case 0:
							way = 0;
							break;
						case 1:
							way = 1;
							break;
						case 2:
							way = 2;
							break;
						}
					}
					addr += way * 0x40;
					break;
				}
				break;
			} else if (baddr < tad[node][i].limit) {
				/*
				 * the channel address is not contiguous or
				 * socket/channel interleave changes in the
				 * middle of the rank adjust base and size for
				 * virtual rank to where the break occurs
				 */
				sinterleave = socket_interleave(baddr,
				    node, channel, i, &way);
				if ((no_interleave && sinterleave == 1) ||
				    mirror_mode[node] || lockstep[node]) {
					cinterleave = 1;
				} else {
					cinterleave =
					    channels_interleave(baddr);
				}
				rank_sz -= (tad[node][i].limit - baddr) /
				    (cinterleave * sinterleave * rinterleave);
				cbaddr += (tad[node][i].limit - baddr) /
				    (cinterleave * sinterleave);
			}
		}
		base = tad[node][i].limit;
	}
	return (addr);
}
/*ARGSUSED*/
static cmi_errno_t
nhm_patounum(void *arg, uint64_t pa, uint8_t valid_hi, uint8_t valid_lo,
    uint32_t synd, int syndtype, mc_unum_t *unump)
{
	int node;
	int channel;
	int dimm;
	int rank;
	int log_chan;
	uint64_t bank, row, column;
	uint64_t caddr, raddr;

	node = address_to_node(pa, 0);
	if (node == -1) {
		return (CMIERR_UNKNOWN);
	}
	channel = address_to_channel(node, pa, syndtype, &log_chan, &caddr, 0);
	if (channel == -1) {
		return (CMIERR_UNKNOWN);
	}
	/*
	 * If driver was built with closed tree present then we will have Intel
	 * proprietary functions caddr_to_dimm and rankaddr_to_dimm for finding
	 * dimm/bank/row/column address otherwise we just locate dimm and
	 * offset.
	 */
	if (&caddr_to_dimm)
		dimm = caddr_to_dimm(node, log_chan, caddr, &rank, &raddr);
	else
		dimm = channel_addr_to_dimm(node, log_chan, caddr, &rank,
		    &raddr);
	if (dimm == -1) {
		return (CMIERR_UNKNOWN);

	}
	unump->unum_board = 0;
	unump->unum_chip = node;
	unump->unum_mc = 0;
	unump->unum_chan = channel;
	unump->unum_cs = dimm;
	unump->unum_rank = rank;

	if (&rankaddr_to_dimm) {
		if (rankaddr_to_dimm(raddr, node, channel, dimm, 0, &bank, &row,
		    &column) != DDI_SUCCESS) {
			return (CMIERR_UNKNOWN);
		};
		unump->unum_offset = TCODE_OFFSET(rank, bank, row, column);
	} else {
		unump->unum_offset = raddr;
	}

	return (CMI_SUCCESS);
}

/*ARGSUSED*/
static cmi_errno_t
nhm_unumtopa(void *arg, mc_unum_t *unump, nvlist_t *nvl, uint64_t *pap)
{
	uint64_t pa;
	cmi_errno_t rt;
	int node;
	int channel;
	int log_chan;
	int rank;
	int i;
	nvlist_t **hcl, *hcsp;
	uint_t npr;
	uint64_t offset;
	char *hcnm, *hcid;
	long v;
	uint64_t row, bank, col;
	int dimm;
	uint64_t rank_addr;

	if (unump == NULL) {
		if (nvlist_lookup_nvlist(nvl, FM_FMRI_HC_SPECIFIC,
		    &hcsp) != 0)
			return (CMIERR_UNKNOWN);
		if (nvlist_lookup_uint64(hcsp,
		    "asru-" FM_FMRI_HC_SPECIFIC_OFFSET, &offset) != 0 &&
		    nvlist_lookup_uint64(hcsp, FM_FMRI_HC_SPECIFIC_OFFSET,
		    &offset) != 0) {
			if (nvlist_lookup_uint64(hcsp,
			    "asru-" FM_FMRI_HC_SPECIFIC_PHYSADDR, &pa) == 0 ||
			    nvlist_lookup_uint64(hcsp,
			    FM_FMRI_HC_SPECIFIC_PHYSADDR, &pa) == 0) {
				*pap = pa;
				return (CMI_SUCCESS);
			}
			return (CMIERR_UNKNOWN);
		}
		if (nvlist_lookup_nvlist_array(nvl, FM_FMRI_HC_LIST,
		    &hcl, &npr) != 0)
			return (CMIERR_UNKNOWN);
		node = -1;
		channel = -1;
		dimm = -1;
		rank = -1;
		for (i = 0; i < npr; i++) {
			if (nvlist_lookup_string(hcl[i], FM_FMRI_HC_NAME,
			    &hcnm) != 0 ||
			    nvlist_lookup_string(hcl[i], FM_FMRI_HC_ID,
			    &hcid) != 0 ||
			    ddi_strtol(hcid, NULL, 0, &v) != 0)
				return (CMIERR_UNKNOWN);
			if (strcmp(hcnm, "chip") == 0)
				node = (int)v;
			else if (strcmp(hcnm, "dram-channel") == 0)
				channel = (int)v;
			else if (strcmp(hcnm, "dimm") == 0)
				dimm = (int)v;
			else if (strcmp(hcnm, "rank") == 0)
				rank = (int)v;
		}
		if (node == -1 || channel == -1 || dimm == -1 || rank == -1)
			return (CMIERR_UNKNOWN);
	} else {
		node = unump->unum_chip;
		channel = unump->unum_chan;
		rank = unump->unum_rank;
		dimm = unump->unum_cs;
		offset = unump->unum_offset;
	}

	/*
	 * If driver was built with closed tree present then we will have Intel
	 * proprietary functions dimm_to_rankaddr for finding
	 * physical address.
	 */
	if (&dimm_to_rankaddr && (offset & OFFSET_ROW_BANK_COL) != 0) {
		row = TCODE_OFFSET_RAS(offset);
		bank = TCODE_OFFSET_BANK(offset);
		col = TCODE_OFFSET_CAS(offset);
		rank_addr = dimm_to_rankaddr(node, channel, dimm, row,
		    bank, col, &log_chan);
		pa = rankaddr_to_phyaddr(node, log_chan, dimm, rank,
		    rank_addr);
	} else if ((offset & OFFSET_ROW_BANK_COL) == 0) {
		pa = dimm_to_addr(node, channel, rank, offset, 0, 0, 0, 0, 0,
		    0, 0, 0);
	} else {
		pa = -1LL;
	}

	if (pa == -1) {
		rt = CMIERR_UNKNOWN;
	} else {
		rt = CMI_SUCCESS;
		*pap = pa;
	}
	return (rt);
}

static const cmi_mc_ops_t nhm_mc_ops = {
	nhm_patounum,
	nhm_unumtopa,
	nhm_error_trap	/* cmi_mc_logout */
};

/*ARGSUSED*/
int
inhm_mc_register(cmi_hdl_t hdl, void *arg1, void *arg2, void *arg3)
{
	cmi_mc_register(hdl, &nhm_mc_ops, NULL);
	return (CMI_HDL_WALK_NEXT);
}

static int
choose_cpu(int *lastslot_p)
{
	uint32_t id;
	int first;
	int last;

	first = 0;
	last = MAX_CPU_NODES;
	id = CPU_ID_RD(0);
	if (id == NHM_EP_CPU || id == NHM_WS_CPU || id == NHM_JF_CPU ||
	    id == NHM_WM_CPU) {
		id = CPU_ID_RD(1);
		if (id != NHM_EP_CPU && id != NHM_WS_CPU && id != NHM_JF_CPU &&
		    id != NHM_WM_CPU) {
			last = 1;
		}
	} else {
		first = 1;
	}
	*lastslot_p = last;
	return (first);
}

static int
sad_interleave(uint32_t list)
{
	int rt = 1;
	int i, j;
	int p;

	for (i = 1; i < INTERLEAVE_NWAY; i++) {
		p = SAD_INTERLEAVE(list, i);
		for (j = 0; j < i; j++) {
			if (p == SAD_INTERLEAVE(list, j))
				break;
		}
		if (i == j)
			rt++;
	}
	return (rt);
}

static int
tad_interleave(uint32_t list)
{
	int rt = 1;
	int i, j;
	int c;

	for (i = 1; i < INTERLEAVE_NWAY; i++) {
		c = TAD_INTERLEAVE(list, i);
		for (j = 0; j < i; j++) {
			if (c == TAD_INTERLEAVE(list, j))
				break;
		}
		if (i == j)
			rt++;
	}
	return (rt);
}

static void
set_rank(int socket, int channel, int rule, int way, int rank,
    uint64_t rank_addr)
{
	int k, l;
	if (rank_addr == 0)
		return;
	/*
	 * set limit on any rules which have virtual rank in current rank and
	 * are not already limited by earlier rule
	 */
	for (k = 0; k < rule; k++) {
		for (l = 0; l < MAX_RIR_WAY; l++) {
			if (rir[socket][channel][k].way[l].dimm_rank == rank &&
			    rir[socket][channel][k].way[l].rlimit == 0) {
				rir[socket][channel][k].way[l].rlimit =
				    rank_addr;
			}
		}
	}
	/*
	 * set limit if this rule supplies more than 1 virtual rank from current
	 * rank
	 */
	for (l = 0; l < way; l++) {
		if (rir[socket][channel][k].way[l].dimm_rank == rank &&
		    rir[socket][channel][k].way[l].rlimit == 0) {
			rir[socket][channel][k].way[l].rlimit = rank_addr;
		}
	}
}

void
mem_reg_init()
{
	int i, j, k, l, m;
	uint32_t sad_dram_rule;
	uint32_t tad_dram_rule;
	uint32_t mc_ras_enables;
	uint32_t mc_channel_mapping;
	uint32_t sagch;
	uint32_t rir_limit;
	uint32_t rir_way;
	uint32_t mc_control;
	uint32_t id;
	int nhm_slot;
	int nhm_lastslot;
	uint8_t	rank;
	uint64_t base;
	int ras_dev = 0;
	uint32_t dod_value;

	nhm_slot = choose_cpu(&nhm_lastslot);

	for (i = 0; i < MAX_SAD_DRAM_RULE; i++) {
		sad_dram_rule = SAD_DRAM_RULE_RD(nhm_slot, i);
		sad[i].enable = SAD_DRAM_RULE_ENABLE(sad_dram_rule);
		sad[i].limit = SAD_DRAM_LIMIT(sad_dram_rule);
		sad[i].mode = SAD_DRAM_MODE(sad_dram_rule);
		sad[i].node_list = SAD_INTERLEAVE_LIST_RD(nhm_slot, i);
		sad[i].interleave = sad_interleave(sad[i].node_list);
		for (j = 0; j < INTERLEAVE_NWAY; j++) {
			sad[i].node_tgt[j] = (sad[i].node_list >>
			    (j * 4)) & 0x3;
		}
	}

	for (i = nhm_slot; i < nhm_lastslot; i++) {
		id = MC_CPU_RAS_RD(i);
		if (id == NHM_CPU_RAS || id == NHM_JF_CPU_RAS ||
		    id == NHM_WM_CPU_RAS) {
			ras_dev = 1;
			mc_ras_enables = MC_RAS_ENABLES_RD(i);
			if (RAS_LOCKSTEP_ENABLE(mc_ras_enables))
				lockstep[i] = 1;
			if (RAS_MIRROR_MEM_ENABLE(mc_ras_enables))
				mirror_mode[i] = 1;
		}
		mc_channel_mapping = MC_CHANNEL_MAPPER_RD(i);
		if (CHANNEL_MAP(mc_channel_mapping, 2, 0) == 0 &&
		    CHANNEL_MAP(mc_channel_mapping, 2, 1) == 0)
			spare_channel[i] = 1;
		for (j = 0; j < MAX_TAD_DRAM_RULE; j++) {
			tad_dram_rule = TAD_DRAM_RULE_RD(i, j);
			tad[i][j].enable = TAD_DRAM_RULE_ENABLE(tad_dram_rule);
			tad[i][j].limit = TAD_DRAM_LIMIT(tad_dram_rule);
			tad[i][j].mode = TAD_DRAM_MODE(tad_dram_rule);
			tad[i][j].pkg_list =
			    TAD_INTERLEAVE_LIST_RD(i, j);
			for (k = 0; k < INTERLEAVE_NWAY; k++) {
				tad[i][j].pkg_tgt[k] = ((tad[i][j].pkg_list >>
				    (k * 4)) & 0x3);
			}
			if (mirror_mode[i] || lockstep[i]) {
				tad[i][j].interleave = 1;
			} else {
				tad[i][j].interleave =
				    tad_interleave(tad[i][j].pkg_list);
				if (spare_channel[i] &&
				    tad[i][j].interleave ==
				    CHANNELS_PER_MEMORY_CONTROLLER)
					tad[i][j].interleave--;
			}
		}
		for (j = 0; j < CHANNELS_PER_MEMORY_CONTROLLER; j++) {
			m = 0;
			base = 0;
			for (k = 0; k < MAX_TAD_DRAM_RULE; k++) {
				sagch = MC_SAG_RD(i, j, k);
				sag_ch[i][j][k].offset =
				    CH_ADDRESS_OFFSET(sagch);
				sag_ch[i][j][k].soffset =
				    CH_ADDRESS_SOFFSET(sagch);
				sag_ch[i][j][k].divby3 = DIVBY3(sagch);
				sag_ch[i][j][k].remove6 = REMOVE_6(sagch);
				sag_ch[i][j][k].remove7 = REMOVE_7(sagch);
				sag_ch[i][j][k].remove8 = REMOVE_8(sagch);

				rir_limit = MC_RIR_LIMIT_RD(i, j, k);
				rir[i][j][k].limit = RIR_LIMIT(rir_limit);
				for (l = 0; l < MAX_RIR_WAY; l++) {
					rir_way = MC_RIR_WAY_RD(i, j, m);
					rir[i][j][k].way[l].offset =
					    RIR_OFFSET(rir_way);
					rir[i][j][k].way[l].soffset =
					    RIR_SOFFSET(rir_way);
					rir[i][j][k].way[l].rank =
					    RIR_RANK(rir_way);
					rir[i][j][k].way[l].dimm =
					    RIR_DIMM(rir_way);
					rir[i][j][k].way[l].dimm_rank =
					    RIR_DIMM_RANK(rir_way);
					rir[i][j][k].way[l].rlimit = 0;
					m++;
				}
				rank = rir[i][j][k].way[0].dimm_rank;
				if (rank == rir[i][j][k].way[1].dimm_rank &&
				    rank == rir[i][j][k].way[2].dimm_rank &&
				    rank == rir[i][j][k].way[3].dimm_rank) {
					rir[i][j][k].interleave = 1;
				} else if
				    (rank == rir[i][j][k].way[1].dimm_rank ||
				    rank == rir[i][j][k].way[2].dimm_rank ||
				    rank == rir[i][j][k].way[3].dimm_rank) {
					rir[i][j][k].interleave = 2;
				} else {
					rir[i][j][k].interleave = 4;
				}
				for (l = 0; l < MAX_RIR_WAY; l++) {
					set_rank(i, j, k, l,
					    rir[i][j][k].way[l].dimm_rank,
					    ((rir[i][j][k].way[l].soffset +
					    base) /
					    rir[i][j][k].interleave));
				}
				base = rir[i][j][k].limit;
			}
			for (k = 0; k < MAX_DIMMS_PER_CHANNEL; k++) {
				dod_value = MC_DOD_RD(i, j, k);
				dod_reg[i][j][k].NUMCol = NUMCOL(dod_value);
				dod_reg[i][j][k].NUMRow = NUMROW(dod_value);
				dod_reg[i][j][k].NUMBank = NUMBANK(dod_value);
				dod_reg[i][j][k].NUMRank = NUMRANK(dod_value);
				dod_reg[i][j][k].DIMMPresent =
				    DIMMPRESENT(dod_value);
				dod_reg[i][j][k].RankOffset =
				    RANKOFFSET(dod_value);
			}
		}
	}
	mc_control = MC_CONTROL_RD(nhm_slot);
	closed_page = MC_CONTROL_CLOSED_PAGE(mc_control);
	if (ras_dev)
		ecc_enabled = MC_CONTROL_ECCEN(mc_control);
	else if ((MC_STATUS_RD(nhm_slot) & WS_ECC_ENABLED) != 0)
		ecc_enabled = 1;
	divby3_enabled = MC_CONTROL_DIVBY3(mc_control);
}