/*
* 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
* 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/ddi_impldefs.h>
#include <sys/sysmacros.h>
#include <sys/autoconf.h>
#include <sys/cpu_module.h>
/*
* pm-hardware-state value
*/
/*
* Function prototypes
*/
/*
* Configuration data structures
*/
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 */
CB_REV, /* rev */
nodev, /* cb_aread */
nodev /* cb_awrite */
};
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 int getreg;
static int nregs;
extern struct mod_ops mod_driverops;
&mod_driverops, /* module type, this one is a driver */
"Memory-controller", /* module name */
&mc_ops, /* driver ops */
};
MODREV_1, /* rev */
(void *)&modldrv,
};
/* For testing only */
struct test_unum {
int synd_code;
int len;
};
/*
* These are the module initialization routines.
*/
int
_init(void)
{
int error;
sizeof (struct mc_soft_state), 1)) != 0)
return (error);
if (error == 0) {
}
return (error);
}
int
_fini(void)
{
int error;
return (error);
return (0);
}
int
{
}
static int
{
int mcreg1_len;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
return (DDI_FAILURE);
/* Set the dip in the soft state */
instance, "portid"));
goto bad;
}
/* Get the content of Memory Control Register I from obp */
mcreg1_len = sizeof (uint64_t);
&mcreg1_len) == DDI_PROP_SUCCESS) &&
(mcreg1_len == sizeof (uint64_t))) {
}
/* attach fails if mcreg1 cannot be accessed */
if (!softsp->mcr_read_ok) {
instance));
goto bad;
}
"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.
*/
/* Set the pointer and size of property in the soft state */
} else {
/*
* memory-layout property was not found or some other
* error occured, plat_get_mem_unum() will not work
* for this mc.
*/
softsp->memlayoutlen = 0;
("mc %d: missing or unsupported memory-layout property\n",
instance));
}
if (!getreg) {
if (mc_get_memory_reg_info(softsp) != 0) {
goto bad1;
}
getreg = 1;
}
/* Construct the physical and logical layout of the MC */
if (nmcs == 1) {
if (&p2get_mem_unum)
if (&p2get_mem_info)
}
"ddi_mem_ctrl", 0) != DDI_SUCCESS) {
" failed \n"));
goto bad1;
}
return (DDI_SUCCESS);
bad1:
/* release all allocated data struture for this MC */
bad:
return (DDI_FAILURE);
}
/* ARGSUSED */
static int
{
int instance;
/* get the instance of this devi */
/* get the soft state pointer for this device node */
switch (cmd) {
case DDI_SUSPEND:
return (DDI_SUCCESS);
case DDI_DETACH:
break;
default:
return (DDI_FAILURE);
}
/* release all allocated data struture for this MC */
if (nmcs == 0) {
if (&p2get_mem_unum)
if (&p2get_mem_info)
}
/* free up the soft state */
return (DDI_SUCCESS);
}
/* ARGSUSED */
static int
{
int status = 0;
/* verify that otyp is appropriate */
return (EINVAL);
}
/* At least one attached? */
if (nmcs == 0) {
}
return (status);
}
/* ARGSUSED */
static int
{
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
int *rval_p)
{
int i, status = 0;
switch (cmd) {
case MCIOC_MEMCONF:
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:
return (EFAULT);
else
return (status);
}
sizeof (mcmem->segmentids[0]);
for (i = 0; i < nsegments; i++) {
}
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:
return (EFAULT);
return (EFAULT);
}
else
return (status);
}
i = 0;
}
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:
return (EFAULT);
return (EINVAL);
}
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:
return (EFAULT);
return (EINVAL);
}
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:
sizeof (mcctrlconf_in)) != 0)
return (EFAULT);
sizeof (mcctrlconf_in)))
else
return (status);
}
/*
* Cannot just use the size of the struct because of the various
* length struct
*/
sizeof (mcctrlconf->mcids[0]));
/* Get all MC ids and add to mcctrlconf */
mctrl = mctrl_head;
i = 0;
i++;
}
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:
sizeof (mccontrol_in)) != 0)
return (EFAULT);
mctrl_head)) == NULL) {
return (EINVAL);
}
/*
* mcport->ndevgrps zero means Memory Controller is disable.
*/
sizeof (mccontrol_in)))
return (status);
}
i));
}
return (status);
/*
* input:id
*
* return 0: CPU flushed successfully.
* EINVAL: the id wasn't found
*/
case MCIOC_ECFLUSH:
return (EINVAL);
return (0);
default:
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
{
int len;
int i;
("mc-us3i: cannot find memory node under root\n"));
return (-1);
}
("mc-us3i: reg undefined under memory\n"));
return (-1);
}
/* debug printfs */
for (i = 0; i < nregs; i++) {
mregi++;
}
return (0);
}
/*
* Initialize a logical bank
*/
static struct bank_info *
int dgrpid)
{
bankid));
return (banki);
}
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
{
/* does this bank start a new segment? */
/* this should happen for the first segment only */
goto new_seg;
}
/* discontiguous banks go into a new segment, increment the seg_id */
seg_id++;
goto new_seg;
}
/* weave the bank into the segment */
/* contiguous or interleaved? */
return;
nsegments++;
}
/*
* 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
{
int shift;
switch (dgrp->base_device) {
case BASE_DEVICE_128Mb:
case BASE_DEVICE_256Mb:
/* 128Mb and 256Mb devices have same bank select mask */
break;
case BASE_DEVICE_512Mb:
case BASE_DEVICE_1Gb:
/* 512 and 1Gb devices have same bank select mask */
break;
}
shift += 1;
return (shift);
}
static void
{
switch (interleave) {
case INTERLEAVE_DISABLE:
/* Fall Through */
case INTERLEAVE_INTERNAL:
/* Bit 32 selects the logical bank */
}
break;
/* Row[2] selects the logical bank */
}
break;
/* Row[2] selects the logical bank */
} else {
}
break;
}
}
static void
{
int bankid;
/* xor mode - assume 2 identical dimm-pairs */
return;
}
/* xor enable means, bit 21 is used for dimm-pair select */
/* bit 20 is used for logbank select */
}
/* 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++) {
nbits++;
}
}
/* number or banks can be 4 or 16 */
/* 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++) {
}
/* xor ds bits to get the dimm-pair */
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
{
int nbanks = 0;
return (0);
}
match_save = match;
/* for bankid 0, 2, 4 .. */
" mask 0x%lx bs_shift %d match 0x%lx\n",
nbanks++;
/*
* Set match value to original before adding second
* logical bank interleaving information.
*/
match = match_save;
bankid++;
" mask 0x%lx shift %d match 0x%lx\n",
nbanks++;
}
return (nbanks);
}
/*
* Construct the logical layout
*/
static void
{
int i;
return;
/* Two dimm pairs and xor bit set */
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.
*/
}
}
/*
* Get the dimm-pair's size from the reg_info
*/
static uint64_t
{
int i;
/* dgrp end address */
size = 0;
for (i = 0; i < nregs; i++) {
/* completely outside */
regi++;
continue;
}
/* completely inside */
return (DGRP_SIZE_MAX);
}
/* start is inside, but not the end, get the remainder */
regi++;
continue;
}
/* add up size for all within range */
regi++;
}
return (size);
}
/*
* Determine the dimm-pair's dimm-densities and part-type using the MCR-I.
*/
static void
{
/* add the entry on dgrp_info list */
dgrpid));
return;
}
/* a devgrp has identical (type & size) pair */
if ((dgrpid & 1) == 0) {
/* dimm-pair 0, 2, 4, 6 */
else
} else {
/* dimm-pair 1, 3, 5, 7 */
else
}
for (i = 0; i < NDIMMS_PER_DGRP; i++) {
"exists\n", devid));
continue;
}
if (dimmp) {
i + NDIMMS_PER_DGRP * dgrpoffset],
}
}
}
/*
* Construct the physical and logical layout
*/
static void
{
/*
* Construct the Physical & Logical Layout
*/
/* allocate for mctrl_info */
mcid));
return;
}
i = 0;
i++;
}
dgrpid++;
}
nmcs++;
}
/*
* 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
{
/* delete mctrl_info */
nmcs--;
} else
/* delete device groups and devices of the detached MC */
for (i = 0; i < NDGRPS_PER_MC; i++) {
continue;
}
for (j = 0; j < NDIMMS_PER_DGRP; j++) {
&device_head, &device_tail);
} else
("mc_delete: no dev %d\n", devid));
}
}
/* delete all banks and associated segments */
for (i = 0; i < NLOGBANKS_PER_MC; i++) {
continue;
}
/* bank and segments go together */
nsegments--;
}
}
}
/*
* 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
{
} else {
}
}
/*
* 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
{
/* deleted node is at the tail of list */
} else {
}
/* deleted node is at the head of list */
} else {
}
}
/*
* 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 *
{
break;
}
return (node);
}
/*
* Memory subsystem provides 144 bits (128 Data bits, 9 ECC bits and 7
* to a specific DIMM pin is described by the memory-layout property
* via two tables: dimm table and pin table.
*
*
* 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
* endian order, i.e. dimm_table[0] represents information for
* logical bit# 143 to 136.
*
* 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.
*/
{
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
{
int i;
/*
* Enforce old Openboot requirement for synd code, either a single-bit
* code from 0..QWORD_SIZE-1 or -1 (multi-bit error).
*/
return (EINVAL);
unum[0] = '\0';
/*
* 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.
*/
/*
* Physical Address is in a bank if (Addr & Mask) == Match
*/
continue;
}
return (ENXIO);
}
/*
* single-bit error handling, we can identify specific
* DIMM.
*/
"%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]);
*/
("mc_get_mem_unum:unum %s\n", unum));
/*
* platform hook for adding label information to unum.
*/
} else {
char *p = unum;
/*
* multi-bit error handling, we can only identify
* bank of DIMMs.
*/
for (i = 0; (i < NDIMMS_PER_DGRP) && (res > 0); i++) {
i == 0 ? "" : " ",
p += strlen(p);
}
/*
* platform hook for adding label information
* to unum.
*/
}
return (ENAMETOOLONG);
} else {
return (0);
}
} /* end of while loop for logic bank list */
return (ENXIO);
}
static int
{
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.
*/
int mcid;
/*
* Physical Address is in a bank if (Addr & Mask) == Match
*/
continue;
}
/*
* Get the corresponding segment.
*/
return (EFAULT);
}
return (0);
} /* end of while loop for logic bank list */
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
{
if (&plat_add_mem_unum_label)
}