OpenSolaris_b135/uts/sun4u/io/mc-us3i.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/obpdefs.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/open.h>
#include <sys/thread.h>
#include <sys/cpuvar.h>
#include <sys/x_call.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/ivintr.h>
#include <sys/intr.h>
#include <sys/intreg.h>
#include <sys/autoconf.h>
#include <sys/modctl.h>
#include <sys/spl.h>
#include <sys/async.h>
#include <sys/mc.h>
#include <sys/mc-us3i.h>
#include <sys/note.h>
#include <sys/cpu_module.h>

/*
 * pm-hardware-state value
 */
#define	NO_SUSPEND_RESUME	"no-suspend-resume"

/*
 * Function prototypes
 */

static int mc_open(dev_t *, int, int, cred_t *);
static int mc_close(dev_t, int, int, cred_t *);
static int mc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int mc_attach(dev_info_t *, ddi_attach_cmd_t);
static int mc_detach(dev_info_t *, ddi_detach_cmd_t);

/*
 * Configuration data structures
 */
static struct cb_ops mc_cb_ops = {
	mc_open,			/* open */
	mc_close,			/* close */
	nulldev,			/* strategy */
	nulldev,			/* print */
	nodev,				/* dump */
	nulldev,			/* read */
	nulldev,			/* write */
	mc_ioctl,			/* ioctl */
	nodev,				/* devmap */
	nodev,				/* mmap */
	nodev,				/* segmap */
	nochpoll,			/* poll */
	ddi_prop_op,			/* cb_prop_op */
	0,				/* streamtab */
	D_MP | D_NEW | D_HOTPLUG,	/* Driver compatibility flag */
	CB_REV,				/* rev */
	nodev,				/* cb_aread */
	nodev				/* cb_awrite */
};

static struct dev_ops mc_ops = {
	DEVO_REV,			/* rev */
	0,				/* refcnt  */
	ddi_no_info,			/* getinfo */
	nulldev,			/* identify */
	nulldev,			/* probe */
	mc_attach,			/* attach */
	mc_detach,			/* detach */
	nulldev,			/* reset */
	&mc_cb_ops,			/* cb_ops */
	(struct bus_ops *)0,		/* bus_ops */
	nulldev,			/* power */
	ddi_quiesce_not_needed,			/* quiesce */
};

/*
 * Driver globals
 */
static void *mcp;
static int nmcs = 0;
static int seg_id;
static int nsegments;
static uint64_t	memsize;

static uint_t	mc_debug = 0;

static int getreg;
static int nregs;
struct memory_reg_info *reg_info;

static mc_dlist_t *seg_head, *seg_tail, *bank_head, *bank_tail;
static mc_dlist_t *mctrl_head, *mctrl_tail, *dgrp_head, *dgrp_tail;
static mc_dlist_t *device_head, *device_tail;

static kmutex_t	mcmutex;
static kmutex_t	mcdatamutex;

extern struct mod_ops mod_driverops;

static struct modldrv modldrv = {
	&mod_driverops,			/* module type, this one is a driver */
	"Memory-controller",		/* module name */
	&mc_ops,			/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1,		/* rev */
	(void *)&modldrv,
	NULL
};

static int mc_get_memory_reg_info(struct mc_soft_state *softsp);
static void mc_construct(struct mc_soft_state *softsp);
static void mc_delete(int mc_id);
static void mc_node_add(mc_dlist_t *node, mc_dlist_t **head, mc_dlist_t **tail);
static void mc_node_del(mc_dlist_t *node, mc_dlist_t **head, mc_dlist_t **tail);
static void *mc_node_get(int id, mc_dlist_t *head);
static void mc_add_mem_unum_label(char *unum, int mcid, int bank, int dimm);
static int mc_get_mem_unum(int synd_code, uint64_t paddr, char *buf,
    int buflen, int *lenp);
static int mc_get_mem_info(int synd_code, uint64_t paddr,
    uint64_t *mem_sizep, uint64_t *seg_sizep, uint64_t *bank_sizep,
    int *segsp, int *banksp, int *mcidp);

#pragma weak p2get_mem_unum
#pragma weak p2get_mem_info
#pragma weak plat_add_mem_unum_label

/* For testing only */
struct test_unum {
	int		synd_code;
	uint64_t	paddr;
	char 		unum[UNUM_NAMLEN];
	int		len;
};

/*
 * These are the module initialization routines.
 */

int
_init(void)
{
	int error;

	if ((error = ddi_soft_state_init(&mcp,
	    sizeof (struct mc_soft_state), 1)) != 0)
		return (error);

	error =  mod_install(&modlinkage);
	if (error == 0) {
		mutex_init(&mcmutex, NULL, MUTEX_DRIVER, NULL);
		mutex_init(&mcdatamutex, NULL, MUTEX_DRIVER, NULL);
	}

	return (error);
}

int
_fini(void)
{
	int error;

	if ((error = mod_remove(&modlinkage)) != 0)
		return (error);

	ddi_soft_state_fini(&mcp);
	mutex_destroy(&mcmutex);
	mutex_destroy(&mcdatamutex);
	return (0);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

static int
mc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	struct mc_soft_state *softsp;
	struct dimm_info *dimminfop;
	int instance, len, err;
	int mcreg1_len;

	switch (cmd) {
	case DDI_ATTACH:
		break;

	case DDI_RESUME:
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	instance = ddi_get_instance(devi);

	if (ddi_soft_state_zalloc(mcp, instance) != DDI_SUCCESS)
		return (DDI_FAILURE);

	softsp = ddi_get_soft_state(mcp, instance);

	/* Set the dip in the soft state */
	softsp->dip = devi;

	if ((softsp->portid = (int)ddi_getprop(DDI_DEV_T_ANY, softsp->dip,
	    DDI_PROP_DONTPASS, "portid", -1)) == -1) {
		DPRINTF(MC_ATTACH_DEBUG, ("mc%d: unable to get %s property\n",
		    instance, "portid"));
		goto bad;
	}

	DPRINTF(MC_ATTACH_DEBUG, ("mc_attach: mc %d portid %d, cpuid %d\n",
	    instance, softsp->portid, CPU->cpu_id));

	/* Get the content of Memory Control Register I from obp */
	mcreg1_len = sizeof (uint64_t);
	if ((ddi_getlongprop_buf(DDI_DEV_T_ANY, softsp->dip, DDI_PROP_DONTPASS,
	    "memory-control-register-1", (caddr_t)&(softsp->mcreg1),
	    &mcreg1_len) == DDI_PROP_SUCCESS) &&
	    (mcreg1_len == sizeof (uint64_t))) {
		softsp->mcr_read_ok = 1;
		DPRINTF(MC_ATTACH_DEBUG, ("mc%d from obp: Reg1: 0x%lx\n",
		    instance, softsp->mcreg1));
	}

	/* attach fails if mcreg1 cannot be accessed */
	if (!softsp->mcr_read_ok) {
		DPRINTF(MC_ATTACH_DEBUG, ("mc%d: unable to get mcreg1\n",
		    instance));
		goto bad;
	}

	/* nothing to suspend/resume here */
	(void) ddi_prop_create(DDI_DEV_T_NONE, devi, DDI_PROP_CANSLEEP,
	    "pm-hardware-state", NO_SUSPEND_RESUME,
	    sizeof (NO_SUSPEND_RESUME));

	/*
	 * Get the label of dimms and pin routing information from the
	 * memory-layout property of the memory controller.
	 */
	err = ddi_getlongprop(DDI_DEV_T_ANY, softsp->dip, DDI_PROP_DONTPASS,
	    "memory-layout", (caddr_t)&dimminfop, &len);
	if (err == DDI_PROP_SUCCESS && dimminfop->table_width == 1) {
		/* Set the pointer and size of property in the soft state */
		softsp->memlayoutp = dimminfop;
		softsp->memlayoutlen = len;
	} else {
		/*
		 * memory-layout property was not found or some other
		 * error occured, plat_get_mem_unum() will not work
		 * for this mc.
		 */
		softsp->memlayoutp = NULL;
		softsp->memlayoutlen = 0;
		DPRINTF(MC_ATTACH_DEBUG,
		    ("mc %d: missing or unsupported memory-layout property\n",
		    instance));
	}

	mutex_enter(&mcmutex);

	/* Get the physical segments from memory/reg, just once for all MC */
	if (!getreg) {
		if (mc_get_memory_reg_info(softsp) != 0) {
			goto bad1;
		}
		getreg = 1;
	}

	/* Construct the physical and logical layout of the MC */
	mc_construct(softsp);

	if (nmcs == 1) {
		if (&p2get_mem_unum)
			p2get_mem_unum = mc_get_mem_unum;
		if (&p2get_mem_info)
			p2get_mem_info = mc_get_mem_info;
	}

	if (ddi_create_minor_node(devi, "mc-us3i", S_IFCHR, instance,
	    "ddi_mem_ctrl", 0) != DDI_SUCCESS) {
		DPRINTF(MC_ATTACH_DEBUG, ("mc_attach: create_minor_node"
		    " failed \n"));
		goto bad1;
	}
	mutex_exit(&mcmutex);

	ddi_report_dev(devi);
	return (DDI_SUCCESS);

bad1:
	/* release all allocated data struture for this MC */
	mc_delete(softsp->portid);
	mutex_exit(&mcmutex);
	if (softsp->memlayoutp != NULL)
		kmem_free(softsp->memlayoutp, softsp->memlayoutlen);

bad:
	cmn_err(CE_WARN, "mc-us3i: attach failed for instance %d\n", instance);
	ddi_soft_state_free(mcp, instance);
	return (DDI_FAILURE);
}

/* ARGSUSED */
static int
mc_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	int instance;
	struct mc_soft_state *softsp;

	/* get the instance of this devi */
	instance = ddi_get_instance(devi);

	/* get the soft state pointer for this device node */
	softsp = ddi_get_soft_state(mcp, instance);

	switch (cmd) {
	case DDI_SUSPEND:
		return (DDI_SUCCESS);

	case DDI_DETACH:
		break;

	default:
		return (DDI_FAILURE);
	}

	DPRINTF(MC_DETACH_DEBUG, ("mc %d DETACH: portid %d\n", instance,
	    softsp->portid));

	mutex_enter(&mcmutex);

	/* release all allocated data struture for this MC */
	mc_delete(softsp->portid);

	if (softsp->memlayoutp != NULL)
		kmem_free(softsp->memlayoutp, softsp->memlayoutlen);

	if (nmcs == 0) {
		if (&p2get_mem_unum)
			p2get_mem_unum = NULL;
		if (&p2get_mem_info)
			p2get_mem_info = NULL;
	}

	mutex_exit(&mcmutex);

	ddi_remove_minor_node(devi, NULL);
	/* free up the soft state */
	ddi_soft_state_free(mcp, instance);

	return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
mc_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
	int status = 0;

	/* verify that otyp is appropriate */
	if (otyp != OTYP_CHR) {
		return (EINVAL);
	}

	mutex_enter(&mcmutex);
	/* At least one attached? */
	if (nmcs == 0) {
		status = ENXIO;
	}
	mutex_exit(&mcmutex);

	return (status);
}

/* ARGSUSED */
static int
mc_close(dev_t devp, int flag, int otyp, cred_t *credp)
{
	return (0);
}

/*
 * cmd includes MCIOC_MEMCONF, MCIOC_MEM, MCIOC_SEG, MCIOC_BANK, MCIOC_DEVGRP,
 * MCIOC_CTRLCONF, MCIOC_CONTROL.
 *
 * MCIOC_MEM, MCIOC_SEG, MCIOC_CTRLCONF, and MCIOC_CONTROL are
 * associated with various length struct. If given number is less than the
 * number in kernel, update the number and return EINVAL so that user could
 * allocate enough space for it.
 *
 */

/* ARGSUSED */
static int
mc_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred_p,
	int *rval_p)
{
	size_t	size;
	struct mc_memconf mcmconf;
	struct mc_memory *mcmem, mcmem_in;
	struct mc_segment *mcseg, mcseg_in;
	struct mc_bank mcbank;
	struct mc_devgrp mcdevgrp;
	struct mc_ctrlconf *mcctrlconf, mcctrlconf_in;
	struct mc_control *mccontrol, mccontrol_in;
	struct seg_info *seg = NULL;
	struct bank_info *bank = NULL;
	struct dgrp_info *dgrp = NULL;
	struct mctrl_info *mcport;
	mc_dlist_t *mctrl;
	int i, status = 0;
	cpu_t *cpu;

	switch (cmd) {
	case MCIOC_MEMCONF:
		mutex_enter(&mcdatamutex);

		mcmconf.nmcs = nmcs;
		mcmconf.nsegments = nsegments;
		mcmconf.nbanks = NLOGBANKS_PER_SEG;
		mcmconf.ndevgrps = NDGRPS_PER_MC;
		mcmconf.ndevs = NDIMMS_PER_DGRP;
		mcmconf.len_dev = MAX_DEVLEN;
		mcmconf.xfer_size = TRANSFER_SIZE;

		mutex_exit(&mcdatamutex);

		if (copyout(&mcmconf, (void *)arg, sizeof (mcmconf)))
			return (EFAULT);
		return (0);

	/*
	 * input: nsegments and allocate space for various length of segmentids
	 *
	 * return    0: size, number of segments, and all segment ids,
	 *		where glocal and local ids are identical.
	 *	EINVAL: if the given nsegments is less than that in kernel and
	 *		nsegments of struct will be updated.
	 *	EFAULT: if other errors in kernel.
	 */
	case MCIOC_MEM:
		if (copyin((void *)arg, &mcmem_in, sizeof (mcmem_in)) != 0)
			return (EFAULT);

		mutex_enter(&mcdatamutex);
		if (mcmem_in.nsegments < nsegments) {
			mcmem_in.nsegments = nsegments;
			mutex_exit(&mcdatamutex);
			if (copyout(&mcmem_in, (void *)arg, sizeof (mcmem_in)))
				status = EFAULT;
			else
				status = EINVAL;

			return (status);
		}

		size = sizeof (*mcmem) + (nsegments - 1) *
		    sizeof (mcmem->segmentids[0]);
		mcmem = kmem_zalloc(size, KM_SLEEP);

		mcmem->size = memsize;
		mcmem->nsegments = nsegments;
		seg = (struct seg_info *)seg_head;
		for (i = 0; i < nsegments; i++) {
			ASSERT(seg != NULL);
			mcmem->segmentids[i].globalid = seg->seg_node.id;
			mcmem->segmentids[i].localid = seg->seg_node.id;
			seg = (struct seg_info *)seg->seg_node.next;
		}
		mutex_exit(&mcdatamutex);

		if (copyout(mcmem, (void *)arg, size))
			status = EFAULT;

		kmem_free(mcmem, size);
		return (status);

	/*
	 * input: id, nbanks and allocate space for various length of bankids
	 *
	 * return    0: base, size, number of banks, and all bank ids,
	 *		where global id is unique of all banks and local id
	 *		is only unique for mc.
	 *	EINVAL: either id isn't found or if given nbanks is less than
	 *		that in kernel and nbanks of struct will be updated.
	 *	EFAULT: if other errors in kernel.
	 */
	case MCIOC_SEG:

		if (copyin((void *)arg, &mcseg_in, sizeof (mcseg_in)) != 0)
			return (EFAULT);

		mutex_enter(&mcdatamutex);
		if ((seg = mc_node_get(mcseg_in.id, seg_head)) == NULL) {
			DPRINTF(MC_CMD_DEBUG, ("MCIOC_SEG: seg not match, "
			    "id %d\n", mcseg_in.id));
			mutex_exit(&mcdatamutex);
			return (EFAULT);
		}

		if (mcseg_in.nbanks < seg->nbanks) {
			mcseg_in.nbanks = seg->nbanks;
			mutex_exit(&mcdatamutex);
			if (copyout(&mcseg_in, (void *)arg, sizeof (mcseg_in)))
				status = EFAULT;
			else
				status = EINVAL;

			return (status);
		}

		size = sizeof (*mcseg) + (seg->nbanks - 1) *
		    sizeof (mcseg->bankids[0]);
		mcseg = kmem_zalloc(size, KM_SLEEP);

		mcseg->id = seg->seg_node.id;
		mcseg->ifactor = seg->ifactor;
		mcseg->base = seg->base;
		mcseg->size = seg->size;
		mcseg->nbanks = seg->nbanks;

		bank = seg->head;

		DPRINTF(MC_CMD_DEBUG, ("MCIOC_SEG:nbanks %d seg %p bank %p\n",
		    seg->nbanks, (void *) seg, (void *) bank));

		i = 0;
		while (bank != NULL) {
			DPRINTF(MC_CMD_DEBUG, ("MCIOC_SEG:idx %d bank_id %d\n",
			    i, bank->bank_node.id));
			mcseg->bankids[i].globalid = bank->bank_node.id;
			mcseg->bankids[i++].localid = bank->local_id;
			bank = bank->next;
		}
		ASSERT(i == seg->nbanks);
		mutex_exit(&mcdatamutex);

		if (copyout(mcseg, (void *)arg, size))
			status = EFAULT;

		kmem_free(mcseg, size);
		return (status);

	/*
	 * input: id
	 *
	 * return    0: mask, match, size, and devgrpid,
	 *		where global id is unique of all devgrps and local id
	 *		is only unique for mc.
	 *	EINVAL: if id isn't found
	 *	EFAULT: if other errors in kernel.
	 */
	case MCIOC_BANK:
		if (copyin((void *)arg, &mcbank, sizeof (mcbank)) != 0)
			return (EFAULT);

		DPRINTF(MC_CMD_DEBUG, ("MCIOC_BANK: bank id %d\n", mcbank.id));

		mutex_enter(&mcdatamutex);

		if ((bank = mc_node_get(mcbank.id, bank_head)) == NULL) {
			mutex_exit(&mcdatamutex);
			return (EINVAL);
		}

		mcbank.mask = bank->mask;
		mcbank.match = bank->match;
		mcbank.size = bank->size;
		mcbank.devgrpid.globalid = bank->devgrp_id;
		mcbank.devgrpid.localid =
		    bank->bank_node.id % NLOGBANKS_PER_SEG;

		mutex_exit(&mcdatamutex);

		if (copyout(&mcbank, (void *)arg, sizeof (mcbank)))
			return (EFAULT);
		return (0);

	/*
	 * input:id and allocate space for various length of deviceids
	 *
	 * return    0: size and number of devices.
	 *	EINVAL: id isn't found
	 *	EFAULT: if other errors in kernel.
	 */
	case MCIOC_DEVGRP:

		if (copyin((void *)arg, &mcdevgrp, sizeof (mcdevgrp)) != 0)
			return (EFAULT);

		mutex_enter(&mcdatamutex);
		if ((dgrp = mc_node_get(mcdevgrp.id, dgrp_head)) == NULL) {
			DPRINTF(MC_CMD_DEBUG, ("MCIOC_DEVGRP: not match, id "
			    "%d\n", mcdevgrp.id));
			mutex_exit(&mcdatamutex);
			return (EINVAL);
		}

		mcdevgrp.ndevices = dgrp->ndevices;
		mcdevgrp.size = dgrp->size;

		mutex_exit(&mcdatamutex);

		if (copyout(&mcdevgrp, (void *)arg, sizeof (mcdevgrp)))
			status = EFAULT;

		return (status);

	/*
	 * input: nmcs and allocate space for various length of mcids
	 *
	 * return    0: number of mc, and all mcids,
	 *		where glocal and local ids are identical.
	 *	EINVAL: if the given nmcs is less than that in kernel and
	 *		nmcs of struct will be updated.
	 *	EFAULT: if other errors in kernel.
	 */
	case MCIOC_CTRLCONF:
		if (copyin((void *)arg, &mcctrlconf_in,
		    sizeof (mcctrlconf_in)) != 0)
			return (EFAULT);

		mutex_enter(&mcdatamutex);
		if (mcctrlconf_in.nmcs < nmcs) {
			mcctrlconf_in.nmcs = nmcs;
			mutex_exit(&mcdatamutex);
			if (copyout(&mcctrlconf_in, (void *)arg,
			    sizeof (mcctrlconf_in)))
				status = EFAULT;
			else
				status = EINVAL;

			return (status);
		}

		/*
		 * Cannot just use the size of the struct because of the various
		 * length struct
		 */
		size = sizeof (*mcctrlconf) + ((nmcs - 1) *
		    sizeof (mcctrlconf->mcids[0]));
		mcctrlconf = kmem_zalloc(size, KM_SLEEP);

		mcctrlconf->nmcs = nmcs;

		/* Get all MC ids and add to mcctrlconf */
		mctrl = mctrl_head;
		i = 0;
		while (mctrl != NULL) {
			mcctrlconf->mcids[i].globalid = mctrl->id;
			mcctrlconf->mcids[i].localid = mctrl->id;
			i++;
			mctrl = mctrl->next;
		}
		ASSERT(i == nmcs);

		mutex_exit(&mcdatamutex);

		if (copyout(mcctrlconf, (void *)arg, size))
			status = EFAULT;

		kmem_free(mcctrlconf, size);
		return (status);

	/*
	 * input:id, ndevgrps and allocate space for various length of devgrpids
	 *
	 * return    0: number of devgrp, and all devgrpids,
	 *		is unique of all devgrps and local id is only unique
	 *		for mc.
	 *	EINVAL: either if id isn't found or if the given ndevgrps is
	 *		less than that in kernel and ndevgrps of struct will
	 *		be updated.
	 *	EFAULT: if other errors in kernel.
	 */
	case MCIOC_CONTROL:
		if (copyin((void *)arg, &mccontrol_in,
		    sizeof (mccontrol_in)) != 0)
			return (EFAULT);

		mutex_enter(&mcdatamutex);
		if ((mcport = mc_node_get(mccontrol_in.id,
		    mctrl_head)) == NULL) {
			mutex_exit(&mcdatamutex);
			return (EINVAL);
		}

		/*
		 * mcport->ndevgrps zero means Memory Controller is disable.
		 */
		if ((mccontrol_in.ndevgrps < mcport->ndevgrps) ||
		    (mcport->ndevgrps == 0)) {
			mccontrol_in.ndevgrps = mcport->ndevgrps;
			mutex_exit(&mcdatamutex);
			if (copyout(&mccontrol_in, (void *)arg,
			    sizeof (mccontrol_in)))
				status = EFAULT;
			else if (mcport->ndevgrps != 0)
				status = EINVAL;

			return (status);
		}

		size = sizeof (*mccontrol) + (mcport->ndevgrps - 1) *
		    sizeof (mccontrol->devgrpids[0]);
		mccontrol = kmem_zalloc(size, KM_SLEEP);

		mccontrol->id = mcport->mctrl_node.id;
		mccontrol->ndevgrps = mcport->ndevgrps;
		for (i = 0; i < mcport->ndevgrps; i++) {
			mccontrol->devgrpids[i].globalid = mcport->devgrpids[i];
			mccontrol->devgrpids[i].localid =
			    mcport->devgrpids[i] % NDGRPS_PER_MC;
			DPRINTF(MC_CMD_DEBUG, ("MCIOC_CONTROL: devgrp id %d\n",
			    i));
		}
		mutex_exit(&mcdatamutex);

		if (copyout(mccontrol, (void *)arg, size))
			status = EFAULT;

		kmem_free(mccontrol, size);
		return (status);

	/*
	 * input:id
	 *
	 * return    0: CPU flushed successfully.
	 *	EINVAL: the id wasn't found
	 */
	case MCIOC_ECFLUSH:
		mutex_enter(&cpu_lock);
		cpu = cpu_get((processorid_t)arg);
		mutex_exit(&cpu_lock);
		if (cpu == NULL)
			return (EINVAL);

		xc_one(arg, (xcfunc_t *)cpu_flush_ecache, 0, 0);

		return (0);

	default:
		DPRINTF(MC_CMD_DEBUG, ("DEFAULT: cmd is wrong\n"));
		return (EFAULT);
	}
}

/*
 * Gets the reg property from the memory node. This provides the various
 * memory segments, at bank-boundries, dimm-pair boundries, in the form
 * of [base, size] pairs. Continuous segments, spanning boundries are
 * merged into one.
 * Returns 0 for success and -1 for failure.
 */
static int
mc_get_memory_reg_info(struct mc_soft_state *softsp)
{
	dev_info_t *devi;
	int len;
	int i;
	struct memory_reg_info *mregi;

	_NOTE(ARGUNUSED(softsp))

	if ((devi = ddi_find_devinfo("memory", -1, 0)) == NULL) {
		DPRINTF(MC_REG_DEBUG,
		    ("mc-us3i: cannot find memory node under root\n"));
		return (-1);
	}

	if (ddi_getlongprop(DDI_DEV_T_ANY, devi, DDI_PROP_DONTPASS,
	    "reg", (caddr_t)&reg_info, &len) != DDI_PROP_SUCCESS) {
		DPRINTF(MC_REG_DEBUG,
		    ("mc-us3i: reg undefined under memory\n"));
		return (-1);
	}

	nregs = len/sizeof (*mregi);

	DPRINTF(MC_REG_DEBUG, ("mc_get_memory_reg_info: nregs %d"
	    "reg_info %p\n", nregs, (void *) reg_info));

	mregi = reg_info;

	/* debug printfs  */
	for (i = 0; i < nregs; i++) {
		DPRINTF(MC_REG_DEBUG, (" [0x%lx, 0x%lx] ",
		    mregi->base, mregi->size));
		mregi++;
	}

	return (0);
}

/*
 * Initialize a logical bank
 */
static struct bank_info *
mc_add_bank(int bankid, uint64_t mask, uint64_t match, uint64_t size,
    int dgrpid)
{
	struct bank_info *banki;

	if ((banki = mc_node_get(bankid, bank_head)) != NULL) {
		DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_bank: bank %d exists\n",
		    bankid));
		return (banki);
	}

	banki = kmem_zalloc(sizeof (*banki), KM_SLEEP);

	banki->bank_node.id = bankid;
	banki->devgrp_id = dgrpid;
	banki->mask = mask;
	banki->match = match;
	banki->base = match;
	banki->size = size;

	mc_node_add((mc_dlist_t *)banki, &bank_head, &bank_tail);

	DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_bank: id %d mask 0x%lx match 0x%lx"
	    " base 0x%lx size 0x%lx\n", bankid, mask, match,
	    banki->base, banki->size));

	return (banki);
}

/*
 * Use the bank's base address to find out whether to initialize a new segment,
 * or weave the bank into an existing segment. If the tail bank of a previous
 * segment is not continuous with the new bank, the new bank goes into a new
 * segment.
 */
static void
mc_add_segment(struct bank_info *banki)
{
	struct seg_info *segi;
	struct bank_info *tb;

	/* does this bank start a new segment? */
	if ((segi = mc_node_get(seg_id, seg_head)) == NULL) {
		/* this should happen for the first segment only */
		goto new_seg;
	}

	tb = segi->tail;
	/* discontiguous banks go into a new segment, increment the seg_id */
	if (banki->base > (tb->base + tb->size)) {
		seg_id++;
		goto new_seg;
	}

	/* weave the bank into the segment */
	segi->nbanks++;
	tb->next = banki;

	banki->seg_id = segi->seg_node.id;
	banki->local_id = tb->local_id + 1;

	/* contiguous or interleaved? */
	if (banki->base != (tb->base + tb->size))
		segi->ifactor++;

	segi->size += banki->size;
	segi->tail = banki;

	memsize += banki->size;

	DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_segment: id %d add bank: id %d"
	    "size 0x%lx\n", segi->seg_node.id, banki->bank_node.id,
	    banki->size));

	return;

new_seg:
	segi = kmem_zalloc(sizeof (*segi), KM_SLEEP);

	segi->seg_node.id = seg_id;
	segi->nbanks = 1;
	segi->ifactor = 1;
	segi->base = banki->base;
	segi->size = banki->size;
	segi->head = banki;
	segi->tail = banki;

	banki->seg_id = segi->seg_node.id;
	banki->local_id = 0;

	mc_node_add((mc_dlist_t *)segi, &seg_head, &seg_tail);
	nsegments++;

	memsize += banki->size;

	DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_segment: id %d new bank: id %d"
	    "size 0x%lx\n", segi->seg_node.id, banki->bank_node.id,
	    banki->size));
}

/*
 * Returns the address bit number (row index) that controls the logical/external
 * bank assignment in interleave of kind internal-external same dimm-pair,
 * internal-external both dimm-pair. This is done by using the dimm-densities
 * and part-type.
 */
static int
get_row_shift(int row_index, struct dgrp_info *dgrp)
{
	int shift;

	switch (dgrp->base_device) {
	case BASE_DEVICE_128Mb:
	case BASE_DEVICE_256Mb:
		/* 128Mb and 256Mb devices have same bank select mask */
		shift = ADDR_GEN_128Mb_X8_ROW_0;
		break;
	case BASE_DEVICE_512Mb:
	case BASE_DEVICE_1Gb:
		/* 512 and 1Gb devices have same bank select mask */
		shift = ADDR_GEN_512Mb_X8_ROW_0;
		break;
	}

	if (dgrp->part_type == PART_TYPE_X4)
		shift += 1;

	shift += row_index;

	return (shift);
}


static void
get_device_select(int interleave, struct dgrp_info *dgrp,
    int *ds_shift, int *bs_shift)
{

	switch (interleave) {
	case INTERLEAVE_DISABLE:
	/* Fall Through */
	case INTERLEAVE_INTERNAL:
		/* Bit 33 selects the dimm group/pair */
		*ds_shift = DIMM_PAIR_SELECT_SHIFT;
		if (dgrp->nlogbanks == 2) {
			/* Bit 32 selects the logical bank */
			*bs_shift = LOG_BANK_SELECT_SHIFT;
		}
		break;
	case INTERLEAVE_INTEXT_SAME_DIMM_PAIR:
		/* Bit 33 selects the dimm group/pair */
		*ds_shift =  DIMM_PAIR_SELECT_SHIFT;
		if (dgrp->nlogbanks == 2) {
			/* Row[2] selects the logical bank */
			*bs_shift = get_row_shift(2, dgrp);
		}
		break;
	case INTERLEAVE_INTEXT_BOTH_DIMM_PAIR:
		if (dgrp->nlogbanks == 2) {
			/* Row[3] selects the dimm group/pair */
			*ds_shift = get_row_shift(3, dgrp);

			/* Row[2] selects the logical bank */
			*bs_shift = get_row_shift(2, dgrp);
		} else {
			/* Row[2] selects the dimm group/pair */
			*ds_shift = get_row_shift(2, dgrp);
		}
		break;
	}
}

static void
mc_add_xor_banks(struct mctrl_info *mctrl,
    uint64_t mask, uint64_t match, int interleave)
{
	int i, j, nbits, nbanks;
	int bankid;
	int dselect[4];
	int ds_shift = -1, bs_shift = -1;
	uint64_t id, size, xmatch;
	struct bank_info *banki;
	struct dgrp_info *dgrp;

	/* xor mode - assume 2 identical dimm-pairs */
	if ((dgrp = mc_node_get(mctrl->devgrpids[0], dgrp_head)) == NULL) {
		return;
	}

	get_device_select(interleave, dgrp, &ds_shift, &bs_shift);

	mask |= (ds_shift == -1 ? 0 : (1ULL << ds_shift));
	mask |= (bs_shift == -1 ? 0 : (1ULL << bs_shift));

	/* xor enable means, bit 21 is used for dimm-pair select */
	mask |= XOR_DEVICE_SELECT_MASK;
	if (dgrp->nlogbanks == NLOGBANKS_PER_DGRP) {
		/* bit 20 is used for logbank select */
		mask |= XOR_BANK_SELECT_MASK;
	}

	/* find out the bits set to 1 in mask, nbits can be 2 or 4 */
	nbits = 0;
	for (i = 0; i <= DIMM_PAIR_SELECT_SHIFT; i++) {
		if ((((mask >> i) & 1) == 1) && (nbits < 4)) {
			dselect[nbits] = i;
			nbits++;
		}
	}

	/* number or banks can be 4 or 16 */
	nbanks = 1 << nbits;

	size = (dgrp->size * 2)/nbanks;

	bankid = mctrl->mctrl_node.id * NLOGBANKS_PER_MC;

	/* each bit position of the mask decides the match & base for bank */
	for (i = 0; i < nbanks; i++) {
		xmatch = 0;
		for (j = 0; j < nbits; j++) {
			xmatch |= (i & (1ULL << j)) << (dselect[j] - j);
		}
		/* xor ds bits to get the dimm-pair */
		id = ((xmatch & (1ULL << ds_shift)) >> ds_shift) ^
		    ((xmatch & (1ULL << XOR_DEVICE_SELECT_SHIFT)) >>
		    XOR_DEVICE_SELECT_SHIFT);
		banki = mc_add_bank(bankid, mask, match | xmatch, size,
		    mctrl->devgrpids[id]);
		mc_add_segment(banki);
		bankid++;
	}
}

/*
 * Based on interleave, dimm-densities, part-type determine the mask
 * and match per bank, construct the logical layout by adding segments
 * and banks
 */
static int
mc_add_dgrp_banks(uint64_t bankid, uint64_t dgrpid,
    uint64_t mask, uint64_t match, int interleave)
{
	int nbanks = 0;
	struct bank_info *banki;
	struct dgrp_info *dgrp;
	int ds_shift = -1, bs_shift = -1;
	uint64_t size;
	uint64_t match_save;

	if ((dgrp = mc_node_get(dgrpid, dgrp_head)) == NULL) {
		return (0);
	}

	get_device_select(interleave, dgrp, &ds_shift, &bs_shift);

	mask |= (ds_shift == -1 ? 0 : (1ULL << ds_shift));
	mask |= (bs_shift == -1 ? 0 : (1ULL << bs_shift));
	match |= (ds_shift == -1 ? 0 : ((dgrpid & 1) << ds_shift));
	match_save = match;
	size = dgrp->size/dgrp->nlogbanks;

	/* for bankid 0, 2, 4 .. */
	match |= (bs_shift == -1 ? 0 : ((bankid & 1) << bs_shift));
	DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_segments: interleave %d"
	    " mask 0x%lx bs_shift %d match 0x%lx\n",
	    interleave, mask, bs_shift, match));
	banki = mc_add_bank(bankid, mask, match, size, dgrpid);
	nbanks++;
	mc_add_segment(banki);

	if (dgrp->nlogbanks == 2) {
		/*
		 * Set match value to original before adding second
		 * logical bank interleaving information.
		 */
		match = match_save;
		bankid++;
		match |= (bs_shift == -1 ? 0 : ((bankid & 1) << bs_shift));
		DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_segments: interleave %d"
		    " mask 0x%lx shift %d match 0x%lx\n",
		    interleave, mask, bs_shift, match));
		banki = mc_add_bank(bankid, mask, match, size, dgrpid);
		nbanks++;
		mc_add_segment(banki);
	}

	return (nbanks);
}

/*
 * Construct the logical layout
 */
static void
mc_logical_layout(struct mctrl_info *mctrl, struct mc_soft_state *softsp)
{
	int i;
	uint64_t mcid, bankid, interleave, mask, match;

	if (mctrl->ndevgrps == 0)
		return;

	mcid = mctrl->mctrl_node.id;
	mask = MC_SELECT_MASK;
	match = mcid << MC_SELECT_SHIFT;

	interleave = (softsp->mcreg1 & MCREG1_INTERLEAVE_MASK) >>
	    MCREG1_INTERLEAVE_SHIFT;

	/* Two dimm pairs and xor bit set */
	if (mctrl->ndevgrps == NDGRPS_PER_MC &&
	    (softsp->mcreg1 & MCREG1_XOR_ENABLE)) {
		mc_add_xor_banks(mctrl, mask, match, interleave);
		return;
	}

	/*
	 * For xor bit unset or only one dimm pair.
	 * In one dimm pair case, even if xor bit is set, xor
	 * interleaving is only taking place in dimm's internal
	 * banks. Dimm and external bank select bits are the
	 * same as those without xor bit set.
	 */
	bankid = mcid * NLOGBANKS_PER_MC;
	for (i = 0; i < mctrl->ndevgrps; i++) {
		bankid += mc_add_dgrp_banks(bankid, mctrl->devgrpids[i],
		    mask, match, interleave);
	}
}

/*
 * Get the dimm-pair's size from the reg_info
 */
static uint64_t
get_devgrp_size(uint64_t start)
{
	int i;
	uint64_t size;
	uint64_t end, reg_start, reg_end;
	struct memory_reg_info *regi;

	/* dgrp end address */
	end = start + DGRP_SIZE_MAX - 1;

	regi = reg_info;
	size = 0;
	for (i = 0; i < nregs; i++) {
		reg_start = regi->base;
		reg_end = regi->base + regi->size - 1;

		/* completely outside */
		if ((reg_end < start) || (reg_start > end)) {
			regi++;
			continue;
		}

		/* completely inside */
		if ((reg_start <= start) && (reg_end >= end)) {
			return (DGRP_SIZE_MAX);
		}

		/* start is inside, but not the end, get the remainder */
		if (reg_start < start) {
			size = regi->size - (start - reg_start);
			regi++;
			continue;
		}

		/* add up size for all within range */
		size += regi->size;
		regi++;
	}

	return (size);
}

/*
 * Each device group is a pair (dimm-pair) of identical single/dual dimms.
 * Determine the dimm-pair's dimm-densities and part-type using the MCR-I.
 */
static void
mc_add_devgrp(int dgrpid, struct mc_soft_state *softsp)
{
	int i, mcid, devid, dgrpoffset;
	struct dgrp_info *dgrp;
	struct device_info *dev;
	struct dimm_info *dimmp = (struct dimm_info *)softsp->memlayoutp;

	mcid = softsp->portid;

	/* add the entry on dgrp_info list */
	if ((dgrp = mc_node_get(dgrpid, dgrp_head)) != NULL) {
		DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_devgrp: devgrp %d exists\n",
		    dgrpid));
		return;
	}

	dgrp = kmem_zalloc(sizeof (*dgrp), KM_SLEEP);

	dgrp->dgrp_node.id = dgrpid;

	/* a devgrp has identical (type & size) pair */
	if ((dgrpid & 1) == 0) {
		/* dimm-pair 0, 2, 4, 6 */
		if (softsp->mcreg1 & MCREG1_DIMM1_BANK1)
			dgrp->nlogbanks = 2;
		else
			dgrp->nlogbanks = 1;
		dgrp->base_device = (softsp->mcreg1 & MCREG1_ADDRGEN1_MASK) >>
		    MCREG1_ADDRGEN1_SHIFT;
		dgrp->part_type = (softsp->mcreg1 & MCREG1_X4DIMM1_MASK) >>
		    MCREG1_X4DIMM1_SHIFT;
	} else {
		/* dimm-pair 1, 3, 5, 7 */
		if (softsp->mcreg1 & MCREG1_DIMM2_BANK3)
			dgrp->nlogbanks = 2;
		else
			dgrp->nlogbanks = 1;
		dgrp->base_device = (softsp->mcreg1 & MCREG1_ADDRGEN2_MASK) >>
		    MCREG1_ADDRGEN2_SHIFT;
		dgrp->part_type = (softsp->mcreg1 & MCREG1_X4DIMM2_MASK) >>
		    MCREG1_X4DIMM2_SHIFT;
	}

	dgrp->base = MC_BASE(mcid) + DGRP_BASE(dgrpid);
	dgrp->size = get_devgrp_size(dgrp->base);

	DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_devgrp: id %d size %ld logbanks %d"
	    " base_device %d part_type %d\n", dgrpid, dgrp->size,
	    dgrp->nlogbanks, dgrp->base_device, dgrp->part_type));

	dgrpoffset = dgrpid % NDGRPS_PER_MC;
	dgrp->ndevices = NDIMMS_PER_DGRP;
	/* add the entry for the (identical) pair of dimms/device */
	for (i = 0; i < NDIMMS_PER_DGRP; i++) {
		devid = dgrpid * NDIMMS_PER_DGRP + i;
		dgrp->deviceids[i] = devid;

		if ((dev = mc_node_get(devid, device_head)) != NULL) {
			DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_devgrp: device %d "
			    "exists\n", devid));
			continue;
		}

		dev = kmem_zalloc(sizeof (*dev), KM_SLEEP);

		dev->dev_node.id = devid;

		dev->size = dgrp->size/2;

		if (dimmp) {
			(void) strncpy(dev->label, (char *)dimmp->label[
			    i + NDIMMS_PER_DGRP * dgrpoffset],
			    MAX_DEVLEN);

			DPRINTF(MC_CNSTRC_DEBUG, ("mc_add_devgrp: dimm %d %s\n",
			    dev->dev_node.id, dev->label));
		}

		mc_node_add((mc_dlist_t *)dev, &device_head, &device_tail);
	}

	mc_node_add((mc_dlist_t *)dgrp, &dgrp_head, &dgrp_tail);
}

/*
 * Construct the physical and logical layout
 */
static void
mc_construct(struct mc_soft_state *softsp)
{
	int i, mcid, dgrpid;
	struct mctrl_info *mctrl;

	mcid = softsp->portid;

	DPRINTF(MC_CNSTRC_DEBUG, ("mc_construct: mcid %d, mcreg1 0x%lx\n",
	    mcid, softsp->mcreg1));

	/*
	 * Construct the Physical & Logical Layout
	 */
	mutex_enter(&mcdatamutex);

	/* allocate for mctrl_info */
	if ((mctrl = mc_node_get(mcid, mctrl_head)) != NULL) {
		DPRINTF(MC_CNSTRC_DEBUG, ("mc_construct: mctrl %d exists\n",
		    mcid));
		mutex_exit(&mcdatamutex);
		return;
	}

	mctrl = kmem_zalloc(sizeof (*mctrl), KM_SLEEP);

	mctrl->mctrl_node.id = mcid;

	i = 0;
	dgrpid = mcid * NDGRPS_PER_MC;
	if (softsp->mcreg1 & MCREG1_DIMM1_BANK0) {
		mc_add_devgrp(dgrpid, softsp);
		mctrl->devgrpids[i] = dgrpid;
		mctrl->ndevgrps++;
		i++;
	}

	if (softsp->mcreg1 & MCREG1_DIMM2_BANK2) {
		dgrpid++;
		mc_add_devgrp(dgrpid, softsp);
		mctrl->devgrpids[i] = dgrpid;
		mctrl->ndevgrps++;
	}

	mc_logical_layout(mctrl, softsp);

	mctrl->dimminfop = (struct dimm_info *)softsp->memlayoutp;

	nmcs++;
	mc_node_add((mc_dlist_t *)mctrl, &mctrl_head, &mctrl_tail);

	mutex_exit(&mcdatamutex);

	DPRINTF(MC_CNSTRC_DEBUG, ("mc_construct: nmcs %d memsize %ld"
	    "nsegments %d\n", nmcs, memsize, nsegments));
}

/*
 * Delete nodes related to the given MC on mc, device group, device,
 * and bank lists. Moreover, delete corresponding segment if its connected
 * banks are all removed.
 */
static void
mc_delete(int mc_id)
{
	int i, j, dgrpid, devid, bankid;
	struct mctrl_info *mctrl;
	struct dgrp_info *dgrp;
	struct device_info *devp;
	struct seg_info *segi;
	struct bank_info *banki;

	mutex_enter(&mcdatamutex);

	/* delete mctrl_info */
	if ((mctrl = mc_node_get(mc_id, mctrl_head)) != NULL) {
		mc_node_del((mc_dlist_t *)mctrl, &mctrl_head, &mctrl_tail);
		kmem_free(mctrl, sizeof (*mctrl));
		nmcs--;
	} else
		DPRINTF(MC_DESTRC_DEBUG, ("mc_delete: mctrl is not found\n"));

	/* delete device groups and devices of the detached MC */
	for (i = 0; i < NDGRPS_PER_MC; i++) {
		dgrpid = mc_id * NDGRPS_PER_MC + i;
		if (!(dgrp = mc_node_get(dgrpid, dgrp_head))) {
			continue;
		}

		for (j = 0; j < NDIMMS_PER_DGRP; j++) {
			devid = dgrpid * NDIMMS_PER_DGRP + j;
			if (devp = mc_node_get(devid, device_head)) {
				mc_node_del((mc_dlist_t *)devp,
				    &device_head, &device_tail);
				kmem_free(devp, sizeof (*devp));
			} else
				DPRINTF(MC_DESTRC_DEBUG,
				    ("mc_delete: no dev %d\n", devid));
		}

		mc_node_del((mc_dlist_t *)dgrp, &dgrp_head, &dgrp_tail);
		kmem_free(dgrp, sizeof (*dgrp));
	}

	/* delete all banks and associated segments */
	for (i = 0; i < NLOGBANKS_PER_MC; i++) {
		bankid = mc_id * NLOGBANKS_PER_MC + i;
		if (!(banki = mc_node_get(bankid, bank_head))) {
			continue;
		}

		/* bank and segments go together */
		if ((segi = mc_node_get(banki->seg_id, seg_head)) != NULL) {
			mc_node_del((mc_dlist_t *)segi, &seg_head, &seg_tail);
			kmem_free(segi, sizeof (*segi));
			nsegments--;
		}

		mc_node_del((mc_dlist_t *)banki, &bank_head, &bank_tail);
		kmem_free(banki, sizeof (*banki));
	}

	mutex_exit(&mcdatamutex);
}

/*
 * mc_dlist is a double linking list, including unique id, and pointers to
 * next, and previous nodes. seg_info, bank_info, dgrp_info, device_info,
 * and mctrl_info has it at the top to share the operations, add, del, and get.
 *
 * The new node is added at the tail and is not sorted.
 *
 * Input: The pointer of node to be added, head and tail of the list
 */

static void
mc_node_add(mc_dlist_t *node, mc_dlist_t **head, mc_dlist_t **tail)
{
	DPRINTF(MC_LIST_DEBUG, ("mc_node_add: node->id %d head %p tail %p\n",
	    node->id, (void *) *head, (void *) *tail));

	if (*head != NULL) {
		node->prev = *tail;
		node->next = (*tail)->next;
		(*tail)->next = node;
		*tail = node;
	} else {
		node->next = node->prev = NULL;
		*head = *tail = node;
	}
}

/*
 * Input: The pointer of node to be deleted, head and tail of the list
 *
 * Deleted node will be at the following positions
 * 1. At the tail of the list
 * 2. At the head of the list
 * 3. At the head and tail of the list, i.e. only one left.
 * 4. At the middle of the list
 */

static void
mc_node_del(mc_dlist_t *node, mc_dlist_t **head, mc_dlist_t **tail)
{
	if (node->next == NULL) {
		/* deleted node is at the tail of list */
		*tail = node->prev;
	} else {
		node->next->prev = node->prev;
	}

	if (node->prev == NULL) {
		/* deleted node is at the head of list */
		*head = node->next;
	} else {
		node->prev->next = node->next;
	}
}

/*
 * Search the list from the head of the list to match the given id
 * Input: id and the head of the list
 * Return: pointer of found node
 */
static void *
mc_node_get(int id, mc_dlist_t *head)
{
	mc_dlist_t *node;

	node = head;
	while (node != NULL) {
		DPRINTF(MC_LIST_DEBUG, ("mc_node_get: id %d, given id %d\n",
		    node->id, id));
		if (node->id == id)
			break;
		node = node->next;
	}
	return (node);
}

/*
 * Memory subsystem provides 144 bits (128 Data bits, 9 ECC bits and 7
 * unused bits) interface via a pair of DIMMs. Mapping of Data/ECC bits
 * to a specific DIMM pin is described by the memory-layout property
 * via two tables: dimm table and pin table.
 *
 * Memory-layout property arranges data/ecc bits in the following order:
 *
 *   Bit#  143                          16 15       7 6           0
 *        |      Data[127:0]              | ECC[8:0] | Unused[6:0] |
 *
 * dimm table: 1 bit is used to store DIMM number (2 possible DIMMs) for
 *	each Data/ECC bit. Thus, it needs 18 bytes (144/8) to represent
 *	all Data/ECC bits in this table. Information is stored in big
 *	endian order, i.e. dimm_table[0] represents information for
 *	logical bit# 143 to 136.
 *
 * pin table: 1 byte is used to store pin position for each Data/ECC bit.
 *	Thus, this table is 144 bytes long. Information is stored in little
 *	endian order, i.e, pin_table[0] represents pin number of logical
 *	bit 0 and pin_table[143] contains pin number for logical bit 143
 *	(i.e. data bit# 127).
 *
 * qwordmap table below is used to map mc_get_mem_unum "synd_code" value into
 * logical bit position assigned above by the memory-layout property.
 */

#define	QWORD_SIZE	144
static uint8_t qwordmap[QWORD_SIZE] =
{
16,   17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
32,   33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
48,   49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
64,   65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
80,   81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,
96,   97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
7,    8,   9,  10,  11,  12,  13,  14,  15,   4,   5,   6,   0,   1,   2,   3
};


/* ARGSUSED */
static int
mc_get_mem_unum(int synd_code, uint64_t paddr, char *buf, int buflen, int *lenp)
{
	int i;
	int pos_cacheline, position, index, idx4dimm;
	int qwlayout = synd_code;
	short offset, data;
	char unum[UNUM_NAMLEN];
	struct dimm_info *dimmp;
	struct pin_info *pinp;
	struct bank_info *bank;
	struct mctrl_info *mctrl;

	/*
	 * Enforce old Openboot requirement for synd code, either a single-bit
	 * code from 0..QWORD_SIZE-1 or -1 (multi-bit error).
	 */
	if (qwlayout < -1 || qwlayout >= QWORD_SIZE)
		return (EINVAL);

	unum[0] = '\0';

	DPRINTF(MC_GUNUM_DEBUG, ("mc_get_mem_unum:qwlayout %d phyaddr 0x%lx\n",
	    qwlayout, paddr));

	/*
	 * Scan all logical banks to get one responding to the physical
	 * address. Then compute the index to look up dimm and pin tables
	 * to generate the unmuber.
	 */
	mutex_enter(&mcdatamutex);
	bank = (struct bank_info *)bank_head;
	while (bank != NULL) {
		int mcid, mcdgrpid, dimmoffset;

		/*
		 * Physical Address is in a bank if (Addr & Mask) == Match
		 */
		if ((paddr & bank->mask) != bank->match) {
			bank = (struct bank_info *)bank->bank_node.next;
			continue;
		}

		mcid = bank->bank_node.id / NLOGBANKS_PER_MC;
		mctrl = mc_node_get(mcid, mctrl_head);
		ASSERT(mctrl != NULL);

		DPRINTF(MC_GUNUM_DEBUG, ("mc_get_mem_unum:mc %d bank %d "
		    "dgrp %d\n", mcid, bank->bank_node.id, bank->devgrp_id));

		mcdgrpid = bank->devgrp_id % NDGRPS_PER_MC;
		dimmoffset = mcdgrpid * NDIMMS_PER_DGRP;

		dimmp = (struct dimm_info *)mctrl->dimminfop;
		if (dimmp == NULL) {
			mutex_exit(&mcdatamutex);
			return (ENXIO);
		}

		if ((qwlayout >= 0) && (qwlayout < QWORD_SIZE)) {
			/*
			 * single-bit error handling, we can identify specific
			 * DIMM.
			 */

			pinp = (struct pin_info *)&dimmp->data[0];

			pos_cacheline = qwordmap[qwlayout];
			position = 143 - pos_cacheline;
			index = position / 8;
			offset = 7 - (position % 8);

			DPRINTF(MC_GUNUM_DEBUG, ("mc_get_mem_unum:position "
			    "%d\n", position));
			/*
			 * Trade-off: We cound't add pin number to
			 * unumber string because statistic number
			 * pumps up at the corresponding dimm not pin.
			 * (void) sprintf(unum, "Pin %1u ", (uint_t)
			 * pinp->pintable[pos_cacheline]);
			 */
			DPRINTF(MC_GUNUM_DEBUG, ("mc_get_mem_unum:pin number "
			    "%1u\n", (uint_t)pinp->pintable[pos_cacheline]));
			data = pinp->dimmtable[index];
			idx4dimm = (data >> offset) & 1;

			(void) strncpy(unum,
			    (char *)dimmp->label[dimmoffset + idx4dimm],
			    UNUM_NAMLEN);

			DPRINTF(MC_GUNUM_DEBUG,
			    ("mc_get_mem_unum:unum %s\n", unum));

			/*
			 * platform hook for adding label information to unum.
			 */
			mc_add_mem_unum_label(unum, mcid, mcdgrpid, idx4dimm);
		} else {
			char *p = unum;
			size_t res = UNUM_NAMLEN;

			/*
			 * multi-bit error handling, we can only identify
			 * bank of DIMMs.
			 */

			for (i = 0; (i < NDIMMS_PER_DGRP) && (res > 0); i++) {
				(void) snprintf(p, res, "%s%s",
				    i == 0 ? "" : " ",
				    (char *)dimmp->label[dimmoffset + i]);
				res -= strlen(p);
				p += strlen(p);
			}

			/*
			 * platform hook for adding label information
			 * to unum.
			 */
			mc_add_mem_unum_label(unum, mcid, mcdgrpid, -1);
		}
		mutex_exit(&mcdatamutex);
		if ((strlen(unum) >= UNUM_NAMLEN) ||
		    (strlen(unum) >= buflen)) {
			return (ENAMETOOLONG);
		} else {
			(void) strncpy(buf, unum, UNUM_NAMLEN);
			*lenp = strlen(buf);
			return (0);
		}
	}	/* end of while loop for logic bank list */

	mutex_exit(&mcdatamutex);
	return (ENXIO);
}

static int
mc_get_mem_info(int synd_code, uint64_t paddr,
    uint64_t *mem_sizep, uint64_t *seg_sizep, uint64_t *bank_sizep,
    int *segsp, int *banksp, int *mcidp)
{
	struct bank_info *bankp;

	if (synd_code < -1 || synd_code >= QWORD_SIZE)
		return (EINVAL);

	/*
	 * Scan all logical banks to get one responding to the physical
	 * address. Then compute the index to look up dimm and pin tables
	 * to generate the unmuber.
	 */
	mutex_enter(&mcdatamutex);
	bankp = (struct bank_info *)bank_head;
	while (bankp != NULL) {
		struct seg_info *segp;
		int mcid;

		/*
		 * Physical Address is in a bank if (Addr & Mask) == Match
		 */
		if ((paddr & bankp->mask) != bankp->match) {
			bankp = (struct bank_info *)bankp->bank_node.next;
			continue;
		}

		mcid = bankp->bank_node.id / NLOGBANKS_PER_MC;

		/*
		 * Get the corresponding segment.
		 */
		if ((segp = (struct seg_info *)mc_node_get(bankp->seg_id,
		    seg_head)) == NULL) {
			mutex_exit(&mcdatamutex);
			return (EFAULT);
		}

		*mem_sizep = memsize;
		*seg_sizep = segp->size;
		*bank_sizep = bankp->size;
		*segsp = nsegments;
		*banksp = segp->nbanks;
		*mcidp = mcid;

		mutex_exit(&mcdatamutex);
		return (0);

	}	/* end of while loop for logic bank list */

	mutex_exit(&mcdatamutex);
	return (ENXIO);
}
/*
 * mc-us3i driver allows a platform to add extra label
 * information to the unum string. If a platform implements a
 * kernel function called plat_add_mem_unum_label() it will be
 * executed. This would typically be implemented in the platmod.
 */
static void
mc_add_mem_unum_label(char *unum, int mcid, int bank, int dimm)
{
	if (&plat_add_mem_unum_label)
		plat_add_mem_unum_label(unum, mcid, bank, dimm);
}