summaryrefslogtreecommitdiff
path: root/usr/src/uts/sun4u/io/mc-us3i.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/sun4u/io/mc-us3i.c')
-rw-r--r--usr/src/uts/sun4u/io/mc-us3i.c1764
1 files changed, 1764 insertions, 0 deletions
diff --git a/usr/src/uts/sun4u/io/mc-us3i.c b/usr/src/uts/sun4u/io/mc-us3i.c
new file mode 100644
index 0000000000..b2e213387e
--- /dev/null
+++ b/usr/src/uts/sun4u/io/mc-us3i.c
@@ -0,0 +1,1764 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#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 */
+};
+
+/*
+ * 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;
+static int mc_is_open = 0;
+
+extern struct mod_ops mod_driverops;
+
+static struct modldrv modldrv = {
+ &mod_driverops, /* module type, this one is a driver */
+ "Memory-controller: %I%", /* 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;
+ goto bad;
+ }
+
+ if (mc_is_open) {
+ status = EBUSY;
+ goto bad;
+ }
+ mc_is_open = 1;
+bad:
+
+ mutex_exit(&mcmutex);
+ return (status);
+}
+
+/* ARGSUSED */
+static int
+mc_close(dev_t devp, int flag, int otyp, cred_t *credp)
+{
+ mutex_enter(&mcmutex);
+ mc_is_open = 0;
+ mutex_exit(&mcmutex);
+
+ 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))) {
+ 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 (ENOSPC);
+ } 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);
+}