/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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)®_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); }