#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>
#define NO_SUSPEND_RESUME "no-suspend-resume"
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);
static struct cb_ops mc_cb_ops = {
mc_open,
mc_close,
nulldev,
nulldev,
nodev,
nulldev,
nulldev,
mc_ioctl,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
0,
D_MP | D_NEW | D_HOTPLUG,
CB_REV,
nodev,
nodev
};
static struct dev_ops mc_ops = {
DEVO_REV,
0,
ddi_no_info,
nulldev,
nulldev,
mc_attach,
mc_detach,
nulldev,
&mc_cb_ops,
(struct bus_ops *)0,
nulldev,
ddi_quiesce_not_needed,
};
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,
"Memory-controller",
&mc_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
(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
struct test_unum {
int synd_code;
uint64_t paddr;
char unum[UNUM_NAMLEN];
int len;
};
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);
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));
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));
}
if (!softsp->mcr_read_ok) {
DPRINTF(MC_ATTACH_DEBUG, ("mc%d: unable to get mcreg1\n",
instance));
goto bad;
}
(void) ddi_prop_create(DDI_DEV_T_NONE, devi, DDI_PROP_CANSLEEP,
"pm-hardware-state", NO_SUSPEND_RESUME,
sizeof (NO_SUSPEND_RESUME));
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) {
softsp->memlayoutp = dimminfop;
softsp->memlayoutlen = len;
} else {
softsp->memlayoutp = NULL;
softsp->memlayoutlen = 0;
DPRINTF(MC_ATTACH_DEBUG,
("mc %d: missing or unsupported memory-layout property\n",
instance));
}
mutex_enter(&mcmutex);
if (!getreg) {
if (mc_get_memory_reg_info(softsp) != 0) {
goto bad1;
}
getreg = 1;
}
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:
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);
}
static int
mc_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
int instance;
struct mc_soft_state *softsp;
instance = ddi_get_instance(devi);
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);
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);
ddi_soft_state_free(mcp, instance);
return (DDI_SUCCESS);
}
static int
mc_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
int status = 0;
if (otyp != OTYP_CHR) {
return (EINVAL);
}
mutex_enter(&mcmutex);
if (nmcs == 0) {
status = ENXIO;
}
mutex_exit(&mcmutex);
return (status);
}
static int
mc_close(dev_t devp, int flag, int otyp, cred_t *credp)
{
return (0);
}
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);
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);
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);
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);
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);
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);
}
size = sizeof (*mcctrlconf) + ((nmcs - 1) *
sizeof (mcctrlconf->mcids[0]));
mcctrlconf = kmem_zalloc(size, KM_SLEEP);
mcctrlconf->nmcs = nmcs;
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);
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);
}
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);
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);
}
}
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;
for (i = 0; i < nregs; i++) {
DPRINTF(MC_REG_DEBUG, (" [0x%lx, 0x%lx] ",
mregi->base, mregi->size));
mregi++;
}
return (0);
}
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);
}
static void
mc_add_segment(struct bank_info *banki)
{
struct seg_info *segi;
struct bank_info *tb;
if ((segi = mc_node_get(seg_id, seg_head)) == NULL) {
goto new_seg;
}
tb = segi->tail;
if (banki->base > (tb->base + tb->size)) {
seg_id++;
goto new_seg;
}
segi->nbanks++;
tb->next = banki;
banki->seg_id = segi->seg_node.id;
banki->local_id = tb->local_id + 1;
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));
}
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:
shift = ADDR_GEN_128Mb_X8_ROW_0;
break;
case BASE_DEVICE_512Mb:
case BASE_DEVICE_1Gb:
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:
case INTERLEAVE_INTERNAL:
*ds_shift = DIMM_PAIR_SELECT_SHIFT;
if (dgrp->nlogbanks == 2) {
*bs_shift = LOG_BANK_SELECT_SHIFT;
}
break;
case INTERLEAVE_INTEXT_SAME_DIMM_PAIR:
*ds_shift = DIMM_PAIR_SELECT_SHIFT;
if (dgrp->nlogbanks == 2) {
*bs_shift = get_row_shift(2, dgrp);
}
break;
case INTERLEAVE_INTEXT_BOTH_DIMM_PAIR:
if (dgrp->nlogbanks == 2) {
*ds_shift = get_row_shift(3, dgrp);
*bs_shift = get_row_shift(2, dgrp);
} else {
*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;
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));
mask |= XOR_DEVICE_SELECT_MASK;
if (dgrp->nlogbanks == NLOGBANKS_PER_DGRP) {
mask |= XOR_BANK_SELECT_MASK;
}
nbits = 0;
for (i = 0; i <= DIMM_PAIR_SELECT_SHIFT; i++) {
if ((((mask >> i) & 1) == 1) && (nbits < 4)) {
dselect[nbits] = i;
nbits++;
}
}
nbanks = 1 << nbits;
size = (dgrp->size * 2)/nbanks;
bankid = mctrl->mctrl_node.id * NLOGBANKS_PER_MC;
for (i = 0; i < nbanks; i++) {
xmatch = 0;
for (j = 0; j < nbits; j++) {
xmatch |= (i & (1ULL << j)) << (dselect[j] - j);
}
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++;
}
}
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;
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) {
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);
}
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;
if (mctrl->ndevgrps == NDGRPS_PER_MC &&
(softsp->mcreg1 & MCREG1_XOR_ENABLE)) {
mc_add_xor_banks(mctrl, mask, match, interleave);
return;
}
bankid = mcid * NLOGBANKS_PER_MC;
for (i = 0; i < mctrl->ndevgrps; i++) {
bankid += mc_add_dgrp_banks(bankid, mctrl->devgrpids[i],
mask, match, interleave);
}
}
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;
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;
if ((reg_end < start) || (reg_start > end)) {
regi++;
continue;
}
if ((reg_start <= start) && (reg_end >= end)) {
return (DGRP_SIZE_MAX);
}
if (reg_start < start) {
size = regi->size - (start - reg_start);
regi++;
continue;
}
size += regi->size;
regi++;
}
return (size);
}
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;
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;
if ((dgrpid & 1) == 0) {
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 {
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;
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);
}
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));
mutex_enter(&mcdatamutex);
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));
}
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);
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"));
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));
}
for (i = 0; i < NLOGBANKS_PER_MC; i++) {
bankid = mc_id * NLOGBANKS_PER_MC + i;
if (!(banki = mc_node_get(bankid, bank_head))) {
continue;
}
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);
}
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;
}
}
static void
mc_node_del(mc_dlist_t *node, mc_dlist_t **head, mc_dlist_t **tail)
{
if (node->next == NULL) {
*tail = node->prev;
} else {
node->next->prev = node->prev;
}
if (node->prev == NULL) {
*head = node->next;
} else {
node->prev->next = node->next;
}
}
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);
}
#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
};
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;
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));
mutex_enter(&mcdatamutex);
bank = (struct bank_info *)bank_head;
while (bank != NULL) {
int mcid, mcdgrpid, dimmoffset;
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)) {
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));
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));
mc_add_mem_unum_label(unum, mcid, mcdgrpid, idx4dimm);
} else {
char *p = unum;
size_t res = UNUM_NAMLEN;
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);
}
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);
}
}
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);
mutex_enter(&mcdatamutex);
bankp = (struct bank_info *)bank_head;
while (bankp != NULL) {
struct seg_info *segp;
int mcid;
if ((paddr & bankp->mask) != bankp->match) {
bankp = (struct bank_info *)bankp->bank_node.next;
continue;
}
mcid = bankp->bank_node.id / NLOGBANKS_PER_MC;
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);
}
mutex_exit(&mcdatamutex);
return (ENXIO);
}
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);
}