mc-us3.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/ddi_impldefs.h>
#include <sys/sysmacros.h>
#include <sys/autoconf.h>
#include <sys/cpu_module.h>
/*
* 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_getinfo_1to1, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
mc_attach, /* attach */
mc_detach, /* detach */
nulldev, /* reset */
&mc_cb_ops, /* cb_ops */
(struct bus_ops *)0, /* bus_ops */
nulldev /* power */
};
/*
* Driver globals
*/
static void *mcp;
static int nmcs = 0;
static int seg_id = 0;
static int nsegments = 0;
static int maxbanks = 0;
static kmutex_t mcdatamutex;
static int mc_is_open = 0;
extern struct mod_ops mod_driverops;
&mod_driverops, /* module type, this one is a driver */
"Memory-controller: %I%", /* module name */
&mc_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1, /* rev */
(void *)&modldrv,
};
static int mc_get_mcregs(struct mc_soft_state *);
static void mlayout_del(int mc_id);
#pragma weak p2get_mem_unum
#pragma weak p2get_mem_info
#pragma weak plat_add_mem_unum_label
/*
* 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
{
struct mc_soft_state *softsp;
/* get the instance of this devi */
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
/* get the soft state pointer for this device node */
instance));
/*
* During resume, the source and target board's bank_infos
* need to be updated with the new mc MADR values. This is
* implemented with existing functionality by first removing
* the props and allocated data structs, and then adding them
* back in.
*/
MEM_CFG_PROP_NAME) == 1) {
}
instance);
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
return (DDI_FAILURE);
/* Set the dip in the soft state */
instance, "portid"));
goto bad;
}
/* map in the registers for this device. */
instance));
goto bad;
}
/*
* Get the label of dimms and pin routing information at memory-layout
* property if the memory controller is enabled.
*
* Basically every memory-controller node on every machine should
* have one of these properties unless the memory controller is
* physically not capable of having memory attached to it, e.g.
* Excalibur's slave processor.
*/
if (err == DDI_PROP_SUCCESS) {
/*
* Set the pointer and size of property in the soft state
*/
} else if (err == DDI_PROP_NOT_FOUND) {
/*
* This is a disable MC. Clear out the pointer and size
* of property in the soft state
*/
} else {
goto bad2;
}
/* Get MC registers and construct all needed data structure */
goto bad1;
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 */
/* remove the libdevinfo property */
MEM_CFG_PROP_NAME) == 1) {
}
bad2:
/* unmap the registers for this device. */
bad:
return (DDI_FAILURE);
}
/* ARGSUSED */
static int
{
int instance;
struct mc_soft_state *softsp;
/* 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);
}
/* remove the libdevinfo property */
MEM_CFG_PROP_NAME) == 1) {
}
/* release all allocated data struture for this MC */
/* unmap the registers */
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);
}
if (mc_is_open) {
goto bad;
}
mc_is_open = 1;
bad:
return (status);
}
/* ARGSUSED */
static int
{
mc_is_open = 0;
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)
{
struct mc_memconf mcmconf;
struct mctrl_info *mcport;
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:
sizeof (struct mc_memory)) != 0)
return (EFAULT);
sizeof (struct mc_memory)))
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:
sizeof (struct mc_segment)) != 0)
return (EFAULT);
return (EFAULT);
}
sizeof (struct mc_segment)))
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);
}
/*
* If (Physic Address & MASK) == MATCH, Physic Address is
* located at this bank. The lower physical address bits
* are at [9-6].
*/
MADR_LK_SHIFT))) << MADR_LPA_SHIFT;
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:
sizeof (struct mc_devgrp)) != 0)
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 (struct mc_ctrlconf)) != 0)
return (EFAULT);
sizeof (struct mc_ctrlconf)))
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 (struct mc_control)) != 0)
return (EFAULT);
mctrl_head)) == NULL) {
return (EINVAL);
}
/*
* mcport->ndevgrps zero means Memory Controller is disable.
*/
sizeof (struct mc_control)))
return (status);
}
}
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);
}
}
/*
* Get Memory Address Decoding Registers and construct list.
* flag is to workaround Cheetah's restriction where register cannot be mapped
* if port id(MC registers on it) == cpu id(process is running on it).
*/
static int
{
int i;
int err = 0;
/* Construct lists for MC, mctrl_info, dgrp_info, and device_info */
/*
* If memlayoutp is NULL, the Memory Controller is disable, and
* doesn't need to create any bank and segment.
*/
goto exit;
/*
* Get the content of 4 Memory Address Decoding Registers, and
* construct lists of logical banks and segments.
*/
for (i = 0; i < NBANKS; i++) {
else
(i * REGOFFSET)));
ma_reg_array[i] = madreg;
break;
}
/*
* Create the logical bank property for this mc node. This
* property is an encoded array of the madr for each logical
* bank (there are NBANKS of these).
*/
MEM_CFG_PROP_NAME) != 1) {
}
exit:
if (!err) {
nmcs++;
}
return (err);
}
/*
* A cache line is composed of four quadwords with the associated ECC, the
* MTag along with its associated ECC. This is depicted below:
*
* | Data | ECC | Mtag |MTag ECC|
* 127 0 8 0 2 0 3 0
*
* synd_code will be mapped as the following order to mc_get_mem_unum.
* 143 16 7 4 0
*
* | Quadword 0 | Quadword 1 | Quadword 2 | Quadword 3 |
* 575 432 431 288 287 144 143 0
*
* dimm table: each bit at a cache line needs two bits to present one of
* four dimms. So it needs 144 bytes(576 * 2 / 8). The content is in
* big edian order, i.e. dimm_table[0] presents for bit 572 to 575.
*
* pin table: each bit at a cache line needs one byte to present pin position,
* where max. is 230. So it needs 576 bytes. The order of table index is
* the same as bit position at a cache line, i.e. pin_table[0] presents
* for bit 0, Mtag ECC 0 of Quadword 3.
*
* This is a mapping from syndrome code to QuadWord Logical layout at Safari.
* Referring to Figure 3-4, Excalibur Architecture Manual.
* This table could be moved to cheetah.c if other platform teams agree with
* the bit layout at QuadWord.
*/
{
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,
};
#define QWORD_SIZE 144
/* ARGSUSED */
static int
{
char unum[UNUM_NAMLEN];
/*
* 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.
*/
/*
* The Address Decoding logic decodes the different fields
* in the Memory Address Drcoding register to determine
* whether a particular logic bank should respond to a
* physical address.
*/
continue;
}
/*
* single-bit error handling, we can identify specific
* DIMM.
*/
pinp++;
/* or quadword = (paddr >> 4) % 4; */
/*
* 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]);
*/
/*
* platform hook for adding label information to unum.
*/
idx4dimm);
} else {
char *p = unum;
/*
* multi-bit error handling, we can only identify
* bank of DIMMs.
*/
i == 0 ? "" : " ",
p += strlen(p);
}
/*
* platform hook for adding label information
* to unum.
*/
}
return (ENOSPC);
} 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.
*/
/*
* The Address Decoding logic decodes the different fields
* in the Memory Address Decoding register to determine
* whether a particular logic bank should respond to a
* physical address.
*/
continue;
}
/*
* Get the corresponding segment.
*/
return (EFAULT);
}
return (0);
} /* end of while loop for logic bank list */
return (ENXIO);
}
/*
* Construct lists for an enabled MC where size of memory is 0.
* The lists are connected as follows:
* Attached MC -> device group list -> device list(per devgrp).
*/
static void
{
struct mctrl_info *mctrl;
struct device_info *dev;
/* allocate for mctrl_info and bank_info */
mctrl_head)) != NULL) {
return;
}
/*
* If dimminfop is NULL, the Memory Controller is disable, and
* the number of device group will be zero.
*/
return;
}
/* add the entry on dgrp_info list */
for (i = 0; i < NDGRPS; i++) {
!= NULL) {
idx);
continue;
}
/* add the entry on device_info list */
for (j = 0; j < NDIMMS; j++) {
if ((dev = (struct device_info *)
"exists\n", dmidx);
continue;
}
KM_SLEEP);
&device_tail);
} /* for loop for constructing device_info */
} /* end of for loop for constructing dgrp_info list */
}
/*
* Construct lists for Memory Configuration at logical viewpoint.
*
* Retrieve information from Memory Address Decoding Register and set up
* bank and segment lists. Link bank to its corresponding device group, and
* update size of device group and devices. Also connect bank to the segment.
*
* Memory Address Decoding Register
* -------------------------------------------------------------------------
* |63|62 53|52 41|40 37|36 20|19 18|17 14|13 12|11 8|7 0|
* |-----------|----------|------|---------|-----|------|-----|-----|-------|
* |V | - | UK | - | UM | - | LK | - | LM | - |
* -------------------------------------------------------------------------
*
*/
static int
{
int status = 0;
struct device_info *dev;
union {
struct {
} _s;
} mcreg;
/* add the entry on bank_info list */
!= NULL) {
goto exit;
}
goto exit;
}
/*
* size of a logical bank = size of segment / interleave factor
* This fomula is not only working for regular configuration,
* i.e. number of banks at a segment equals to the max
* interleave factor, but also for special case, say 3 bank
* interleave. One bank is 2 way interleave and other two are
* 4 way. So the sizes of banks are size of segment/2 and /4
* respectively.
*/
base <<= MADR_UPA_SHIFT;
"lk 0x%x uk 0x%x um 0x%x ifactor 0x%x size 0x%llx base 0x%llx\n",
/* connect the entry and update the size on dgrp_info list */
/* all avaiable dgrp should be linked at mc_construct */
status = -1;
goto exit;
}
/* Update the size of entry on device_info list */
for (i = 0; i < NDIMMS; i++) {
/* avaiable device should be linked at mc_construct */
device_head)) == NULL) {
dmidx);
status = -1;
goto exit;
}
}
/*
* Get the segment by matching the base address, link this bank
* to the segment. If not matched, allocate a new segment and
* add it at segment list.
*/
} else {
nsegments++;
}
/* Get the local id of bank which is only unique per segment. */
/* add bank at the end of the list; not sorted by bankid */
} else {
}
exit:
return (status);
}
/*
* 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
mlayout_del(int mc_id)
{
struct mctrl_info *mctrl;
/* delete mctrl_info */
NULL) {
nmcs--;
/*
* There is no other list left for disabled MC.
*/
if (ndevgrps == 0) {
return;
}
} else
/* Delete device groups and devices of the detached MC */
for (i = 0; i < NDGRPS; i++) {
continue;
}
for (j = 0; j < NDIMMS; j++) {
&device_tail);
} else {
devid);
}
}
}
/* Delete banks and segments if it has no bank */
for (i = 0; i < NBANKS; i++) {
bank_head))) {
continue;
}
base <<= MADR_UPA_SHIFT;
/* Delete bank at segment and segment if no bank left */
&bank_tail);
continue;
}
/* update the bank list at the segment */
/* node is at the tail of list */
} else {
}
/* node is at the head of list */
} else {
}
&seg_tail);
nsegments--;
}
}
} /* end of for loop for four banks */
}
/*
* Search the segment in the list starting at seg_head by base address
* input: base address
* return: pointer of found segment or null if not found.
*/
static struct seg_info *
{
break;
}
return (seg_ptr);
}
/*
* 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 mc_dlist_t *
{
break;
}
return (node);
}
/*
* mc-us3 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)
}