/*
* 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);
}