diff options
Diffstat (limited to 'usr/src/uts/sun4u/io/mc-us3i.c')
| -rw-r--r-- | usr/src/uts/sun4u/io/mc-us3i.c | 1764 |
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)®_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); +} |
