sbdp_mem.c revision 56f33205c9ed776c3c909e07d52e94610a675740
/*
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* memory management for serengeti dr memory
*/
#include <sys/memlist_impl.h>
#include <sys/machsystm.h>
#include <sys/mem_cage.h>
#include <sys/sbd_ioctl.h>
#include <sys/sbdp_priv.h>
#include <sys/sbdp_mem.h>
#include <sys/cheetahregs.h>
#include <sys/cpu_module.h>
static void _sbdp_copy_rename_end(void);
static int sbdp_copy_rename__relocatable(sbdp_cr_handle_t *,
struct memlist *, sbdp_rename_script_t *);
static int sbdp_prep_rename_script(sbdp_cr_handle_t *);
void sbdp_add_bank_to_seg(sbdp_bank_t *);
void sbdp_remove_bank_from_seg(sbdp_bank_t *);
#ifdef DEBUG
void sbdp_print_seg(sbdp_seg_t *);
#endif
/*
* Head to the system segments link list
*/
{
int size;
if (size <= SG_SLICE_16G_SIZE) {
return (SG_SLICE_16G_SIZE);
} else if (size <= SG_SLICE_32G_SIZE) {
return (SG_SLICE_32G_SIZE);
} else {
return (SG_SLICE_64G_SIZE);
}
}
/* ARGSUSED */
int
{
return (0);
}
void
{
SBDP_DBG_MEM("memlist> EMPTY\n");
} else {
}
}
struct mem_arg {
int board;
int ndips;
dev_info_t **list;
};
/*
* Returns mem dip held
*/
static int
{
return (DDI_FAILURE);
return (DDI_FAILURE);
if (dip) {
}
return (DDI_SUCCESS);
}
struct memlist *
{
int i, j, skip = 0;
continue;
size = 0;
for (j = 0; j < SBDP_MAX_MCS_PER_NODE; j++) {
skip++;
continue;
}
continue;
}
/*
* Release hold acquired in sbdp_get_mem_dip()
*/
ddi_release_devi(list[i]);
}
/*
* XXX - The following two lines are from existing code.
* However, this appears to be incorrect - this check should be
* made for each dip in list i.e within the for(i) loop.
*/
if (skip == SBDP_MAX_MCS_PER_NODE)
return (mlist);
}
struct memlist *
{
return (NULL);
mp = memlist_get_one();
break;
}
else
}
return (hl);
}
int
{
return (0);
}
/*ARGSUSED*/
static void
{
}
typedef enum {
int
{
int err = 0;
int *indexp;
int (*funcp)();
int linesize;
SBDP_DBG_MEM("moving memory from memory board %d to board %d\n",
return (-1);
}
"sbdp: copy-rename funclen (%d) > PAGESIZE (%d)",
return (-1);
}
/*
* mempage will be page aligned, since we're calling
* kmem_alloc() with an exact multiple of PAGESIZE.
*/
/*
* Copy the code for the copy-rename routine into
* a page aligned piece of memory. We do this to guarantee
* that we're executing within the same page and thus reduce
* the possibility of cache collisions between different
* pages.
*/
funclen);
/*
* Prepare data page that will contain script of
* operations to perform during copy-rename.
* Allocate temporary buffer to hold script.
*/
/*
* We need to make sure we don't switch cpus since we depend on the
* correct cpu processing
*/
if (scriptlen <= 0) {
err = 1;
goto cleanup;
}
err = 1;
goto cleanup;
}
/*
* Find aligned area within data page to maintain script.
*/
err = 1;
goto cleanup;
}
SBDP_DBG_MEM("copy-rename script data area = 0x%lx\n",
indexp = (int *)index_area;
indexp[0] = 0;
indexp[1] = 0;
"sbdp: index area size (%d) > available (%d)\n",
err = 1;
goto cleanup;
}
srhp = sbdp_get_sr_handle();
copytime = ddi_get_lbolt();
SBDP_DBG_MEM("Didn't find memory list\n");
}
SBDP_DBG_MEM("src\n\tbd\t%d\n\tnode\t%d\n\tbpa 0x%lx\n\tnodes\t%p\n",
SBDP_DBG_MEM("tgt\n\tbd\t%d\n\tnode\t%d\n\tbpa 0x%lx\n\tnodes\t%p\n",
/*
* Quiesce the OS.
*/
if (sbdp_suspend(srhp)) {
err = 1;
goto cleanup;
}
/*
* =================================
* COPY-RENAME BEGIN.
* =================================
*/
SBDP_DBG_MEM("Flushing all of the cpu caches\n");
xc_all(sbdp_flush_ecache, 0, 0);
/* disable CE reporting */
neer = get_error_enable();
/* enable CE reporting */
SBDP_DBG_MEM("after execking the function\n");
/*
* =================================
* COPY-RENAME END.
* =================================
*/
/*
* Resume the OS.
*/
"sbdp: failed to resume OS for copy-rename");
err = 1;
}
SBDP_DBG_MEM("copy-rename elapsed time = %ld ticks (%ld secs)\n",
switch (cr_err) {
case SBDP_CR_OK:
break;
case SBDP_CR_MC_IDLE_ERR: {
"copy-rename aborted", path);
err = 1;
break;
}
default:
err = 1;
break;
}
if (err)
goto cleanup;
/*
* Rename memory for lgroup.
* Source and target board numbers are packaged in arg.
*/
/*
* swap list of banks
*/
/*
* Update the cached board info for both the source and the target
*/
/*
* Tell the sc that we have swapped slices.
*/
/* This is dangerous. The in use slice could be re-used! */
SBDP_DBG_MEM("swaping slices failed\n");
}
return (err ? -1 : 0);
}
static int
{
int i, m;
SBDP_DBG_MEM("sbdp_copy_regs: failed to read source Decode "
"Regs");
return (-1);
}
m = *index;
for (i = 0; i < SBDP_MAX_MCS_PER_NODE; i++) {
/*
* Skip invalid banks
*/
continue;
}
if (!inval) {
/*
* We need to calculate the offset from the base pa
* to add it appropriately to the new_base.
* The offset needs to be in UM relative to the mc
* decode register. Since we are going from physical
* address to UM, we need to shift it by PHYS2UM_SHIFT.
* To get it ready to OR it with the MC decode reg,
* we need to shift it left MC_UM_SHIFT
*/
/* Convert tmp_base into a physical address */
/* Convert tmp_base to be MC reg ready */
}
mc_decode[i] &= ~SG_DECODE_UM;
mc_decode[i] |= SG_DECODE_VALID;
/*
* Step 1: Write source base address to the MC
* with present bit off.
*/
m++;
/*
* Step 2: Now rewrite the mc reg with present bit on.
*/
m++;
}
*index = m;
return (0);
}
static int
{
int len;
if (len != sizeof (mc_regspace))
return (-1);
return (-1);
return (0);
}
static int
{
char namebuf[OBP_MAXPROPNAME];
if (cpu_node == OBP_NONODE) {
SBDP_DBG_MEM("mc_get_sibling_cpu failed: dnode=0x%x\n",
mc_node);
return (-1);
}
if (len < 0) {
SBDP_DBG_MEM("invalid prom_getproplen for name prop: "
return (-1);
}
SBDP_DBG_MEM("failed to read name property for dnode=0x%x\n",
cpu_node);
return (-1);
}
/*
* If this is a CMP node, the child has the implementation
* property.
*/
}
SBDP_DBG_MEM("failed to read implementation# property for "
"dnode=0x%x\n", cpu_node);
return (-1);
}
SBDP_DBG_MEM("mc_get_sibling_cpu_impl: found impl=0x%x, dnode=0x%x\n",
return (impl);
}
/*
* Provide EMU Activity Status register ASI and address. Only valid for
* Panther processors.
*/
static int
{
int portid;
portid == -1) {
SBDP_DBG_MEM("mc_get_idle_reg: failed to read portid prop "
"for dnode=0x%x\n", nodeid);
return (-1);
}
SBDP_DBG_MEM("mc_get_idle_reg: failed to read reg prop "
"for dnode=0x%x\n", nodeid);
return (-1);
}
/*
* Local access will be via ASI 0x4a, otherwise via Safari PIO.
* This assumes the copy-rename will later run on the same proc,
* hence there is an assumption we are already bound.
*/
*asi = ASI_SAFARI_CONFIG;
} else {
}
return (0);
}
/*
* If non-Panther board, add phys_banks entry for each physical bank.
* If Panther board, add mc_idle_regs entry for each EMU Activity Status
* register. Increment the array indices b_idx and r_idx for each entry
* populated by this routine.
*
* The caller is responsible for allocating sufficient array entries.
*/
static int
{
int i, j;
int impl = -1;
for (i = 0; i < SBDP_MAX_MEM_NODES_PER_BOARD; i++) {
if (memnodes[i] == OBP_NONODE) {
continue;
}
/* MC should not be accessed if cpu has failed */
if (sibling_cpu_cond == SBD_COND_FAILED ||
SBDP_DBG_MEM("sbdp: skipping MC with failed cpu: "
"board=%d, mem node=%d, condition=%d",
continue;
}
/*
* Initialize the board cpu type, assuming all board cpus are
* the same type. This is true of all Cheetah-based processors.
* Failure to read the cpu type is considered a fatal error.
*/
if (impl == -1) {
if (impl == -1) {
SBDP_DBG_MEM("sbdp: failed to get cpu impl "
"for MC dnode=0x%x\n", memnodes[i]);
return (-1);
}
}
switch (impl) {
case CHEETAH_IMPL:
case CHEETAH_PLUS_IMPL:
case JAGUAR_IMPL:
SBDP_DBG_MEM("sbdp: failed to read source "
return (-1);
}
for (j = 0; j < SBDP_MAX_MCS_PER_NODE; j++) {
if ((mc_decode & SG_DECODE_VALID) !=
continue;
}
(*b_idx)++;
}
break;
case PANTHER_IMPL:
return (-1);
}
(*r_idx)++;
break;
default:
impl);
ASSERT(0);
return (-1);
}
}
return (0);
}
/*
* For non-Panther MCs that do not support read-bypass-write, we do a read
* to each physical bank, relying on the reads to block until all outstanding
* write requests have completed. This mechanism is referred to as the bus
* sync list and is used for Cheetah, Cheetah+, and Jaguar processors. The
* bus sync list PAs for the source and target are kept together and comprise
* Section 1 of the rename script.
*
* For Panther processors that support the EMU Activity Status register,
* we ensure the writes have completed by polling the MCU_ACT_STATUS
* field several times to make sure the MC queues are empty. The
* EMU Activity Status register PAs for the source and target are
* kept together and comprise Section 2 of the rename script.
*/
static int
{
int i;
/* CONSTCOND */
ASSERT(sizeof (sbdp_rename_script_t) ==
sizeof (sbdp_mc_idle_script_t));
/* allocate space for both source and target */
SG_MAX_BANKS_PER_MC * 2;
mc_idle_regs, &nregs) != 0 ||
mc_idle_regs, &nregs) != 0) {
return (-1);
}
/* section 1 */
for (i = 0; i < nbanks; i++)
/* section 2 */
for (i = 0; i < nregs; i++)
return (0);
}
/*
* code assumes single mem-unit.
*/
static int
{
int m = 0, i;
/*
* Calculate the new base address for the target bd
*/
/*
* Calculate the old base address for the source bd
*/
m = 0;
/*
* Ensure the MC queues have been idled on the source and target
* following the copy.
*/
return (-1);
/*
* Script section terminator
*/
m++;
/*
* Invalidate the base in the target mc registers
*/
for (i = 0; i < t_num; i++) {
&m) < 0)
return (-1);
}
/*
* Invalidate the base in the source mc registers
*/
for (i = 0; i < s_num; i++) {
&m) < 0)
return (-1);
}
/*
* Copy the new base into the targets mc registers
*/
for (i = 0; i < t_num; i++) {
&m) < 0)
return (-1);
}
/*
* Copy the old base into the source mc registers
*/
for (i = 0; i < s_num; i++) {
&m) < 0)
return (-1);
}
/*
* Zero masr_addr value indicates the END.
*/
m++;
#ifdef DEBUG
{
int i;
SBDP_DBG_MEM("dumping copy-rename script:\n");
for (i = 0; i < m; i++) {
SBDP_DBG_MEM("0x%lx = 0x%lx, asi 0x%x\n",
}
DELAY(1000000);
}
#endif /* DEBUG */
return (m * sizeof (sbdp_rename_script_t));
}
/*
* EMU Activity Status Register needs to be read idle several times.
* See Panther PRM 12.5.
*/
#define SBDP_MCU_IDLE_RETRIES 10
#define SBDP_MCU_IDLE_READS 3
/*
* Using the "__relocatable" suffix informs DTrace providers (and anything
* else, for that matter) that this function's text may be manually relocated
* elsewhere before it is executed. That is, it cannot be safely instrumented
* with any methodology that is PC-relative.
*/
static int
register sbdp_rename_script_t *rsp)
{
int i;
size = 0;
/*
* DO COPY.
*/
/*
* This copy does NOT use an ASI
* that avoids the Ecache, therefore
* the dst_pa addresses may remain
* in our Ecache after the dst_pa
* has been removed from the system.
* A subsequent write-back to memory
* will cause an ARB-stop because the
* physical address no longer exists
* in the system. Therefore we must
* flush out local Ecache after we
* finish the copy.
*/
/* copy 32 bytes at src_pa to dst_pa */
/* increment by 32 bytes */
/* decrement by 32 bytes */
}
}
/*
* Since bcopy32_il() does NOT use an ASI to bypass
* the Ecache, we need to flush our Ecache after
* the copy is complete.
*/
/*
* Non-Panther MCs are idled by reading each physical bank.
*/
}
/*
* Panther MCs are idled by polling until the MCU idle state
* is read SBDP_MCU_IDLE_READS times in succession.
*/
for (i = 0; i < SBDP_MCU_IDLE_RETRIES; i++) {
register uint64_t v;
register int n_idle = 0;
do {
} while (v != MCU_ACT_STATUS &&
++n_idle < SBDP_MCU_IDLE_READS);
if (n_idle == SBDP_MCU_IDLE_READS)
break;
}
if (i == SBDP_MCU_IDLE_RETRIES) {
/* bailout */
return (SBDP_CR_MC_IDLE_ERR);
}
isp++;
}
/* skip terminator */
isp++;
/*
* The following inline assembly routine caches
* the rename script and then caches the code that
* will do the rename. This is necessary
* so that we don't have any memory references during
* the reprogramming. We accomplish this by first
* jumping through the code to guarantee it's cached
* before we actually execute it.
*/
return (err);
}
static void
_sbdp_copy_rename_end(void)
{
/*
* IMPORTANT: This function's location MUST be located immediately
* following sbdp_copy_rename__relocatable to accurately
* estimate its size. Note that this assumes (!)the
* compiler keeps these functions in the order in which
* they appear :-o
*/
}
int
{
#ifdef lint
/*
* Delete when implemented
*/
#endif
return (0);
}
/*
* In Serengeti this is a nop
*/
int
{
#ifdef lint
#endif
return (0);
}
/*
* In Serengeti this is a nop
*/
int
{
#ifdef lint
#endif
return (0);
}
/* ARGSUSED */
int
{
return (0);
}
/* ARGSUSED */
int
{
return (0);
}
/*
* We are assuming one memory node therefore the base address is the lowest
* segment possible
*/
#define PA_ABOVE_MAX (0x8000000000000000ull)
int
{
int rc;
return (-1);
return (-1);
return (-1);
rc = -1;
continue;
&tmp_pa) == 0) {
rc = 0;
}
/*
* Release hold acquired in sbdp_get_mem_dip()
*/
ddi_release_devi(list[i]);
}
if (rc == 0)
else {
/*
* Record the fact that an error has occurred
*/
}
return (rc);
}
static int
{
int i, valid;
int rc;
SBDP_DBG_MEM("sbdp_get_lowest_addr_in_node: failed to "
"read source Decode Regs\n");
return (-1);
}
rc = -1;
for (i = 0; i < SBDP_MAX_MCS_PER_NODE; i++) {
if (valid)
rc = 0;
}
if (rc == 0)
return (rc);
}
int
{
char type[OBP_MAXPROPNAME];
int bd;
int board;
char name[OBP_MAXDRVNAME];
int len;
/*
* Make sure that this node doesn't have its status
* as failed
*/
return (DDI_FAILURE);
}
else
type[0] = '\0';
int wnode;
return (DDI_FAILURE);
/*
* Make sure we don't overwrite the array
*/
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
}
return (DDI_FAILURE);
}
static int
{
int valid;
return (-1);
SBDP_DBG_MEM("sbdp_get_meminfo: failed to read source "
"Decode Regs");
return (-1);
}
/*
* Calculate memory size
*/
/*
* Check the valid bit to see if bank is there
*/
if (valid) {
}
return (0);
}
/*
* do is search in the same branch as the mem node for its sibling cpu or
* CMP node.
*/
{
int portid;
return (OBP_NONODE);
/*
* cpus and memory are siblings so we don't need to traverse
* the whole tree, just a branch
*/
}
/*
* Given a memory node, check it's sibling cpu or CMP to see if
* access to mem will be ok. We need to search for the node and
* if found get its condition.
*/
{
int i;
if (cond == SBD_COND_OK) {
int wnode;
int bd;
int unit;
int portid;
return (SBD_COND_UNKNOWN);
/*
* Access to the memory controller should not
* be attempted if any of the cores are marked
* as being in reset.
*/
for (i = 0; i < SBDP_MAX_CORES_PER_CMP; i++) {
break;
}
}
}
return (cond);
}
int
{
int len;
int local_mc;
int portid;
int i;
(portid == -1))
return (-1);
/*
* mc should not be accessed if their corresponding cpu
* has failed.
*/
if ((sibling_cpu_cond == SBD_COND_FAILED) ||
(sibling_cpu_cond == SBD_COND_UNUSABLE)) {
return (-1);
}
if (len != sizeof (mc_regspace))
return (-1);
return (-1);
/*
* Make sure we don't switch cpus
*/
local_mc = 1;
else
local_mc = 0;
for (i = 0; i < SG_MAX_BANKS_PER_MC; i++) {
mask = SG_REG_2_OFFSET(i);
/*
* If the memory controller is local to this CPU, we use
* the special ASI to read the decode registers.
* Otherwise, we load the values from a magic address in
* I/O space.
*/
if (local_mc) {
mask & MC_OFFSET_MASK);
} else {
}
}
return (0);
}
{
int len;
int portid;
int local_mc;
(portid == -1))
return (-1);
if (len != sizeof (mc_regspace))
return (-1);
return (-1);
/*
* Make sure we don't switch cpus
*/
local_mc = 1;
else
local_mc = 0;
if (local_mc) {
*asi = ASI_MC_DECODE;
} else {
}
return (addr);
}
/* ARGSUSED */
int
{
return (0);
}
int
{
if (size > 0) {
int rv;
if (rv != 0) {
"unexpected kcage_range_delete_post_mem_del"
" return value %d", rv);
return (-1);
}
}
return (0);
}
/*
* This routine gets the size including the
* bad banks
*/
int
{
}
/*
* This function compares the list of banks passed with the banks
* in the segment
*/
int
{
int i = 0;
continue;
i++;
}
}
}
/*
* If we find the same num of banks that are equal, then this segment
* is not interleaved across boards
*/
return (0);
return (1);
}
/*
* This routine determines if any of the memory banks on the board
* participate in across board memory interleaving
*/
int
{
int is_interleave = 0;
#ifdef DEBUG
#endif
/*
* Get the banks for this board
*/
return (-1);
/*
* Search for the first bank with valid memory
*/
break;
/*
* If there are no banks in the board, then the board is
* not interleaved across boards
*/
return (0);
}
/*
* Find the segment for the first bank
*/
/*
* Something bad has happened.
*/
return (-1);
}
/*
* Make sure that this segment is only composed of the banks
* in this board. If one is missing or we have an extra one
* the board is interleaved across boards
*/
return (is_interleave);
}
/*
* Each node has 4 logical banks. This routine adds all the banks (including
* the invalid ones to the passed list. Note that we use the bd list and not
* the seg list
*/
int
{
int i;
return (-1);
for (i = 0; i < SBDP_MAX_MCS_PER_NODE; i++) {
/*
* This creates the mem for the new member of the list
*/
/*
* Insert bank into the beginning of the list
*/
/*
* Add this bank into its corresponding
* segment
*/
}
return (0);
}
/*
* given the info, create a new bank node and set the info
* as appropriate. We allocate the memory for the bank. It is
* up to the caller to ensure the mem is freed
*/
void
{
static int id = 0;
}
/*
* Each bd has the potential of having mem banks on it. The banks
* may be empty or not. This routine gets all the mem banks
* for this bd
*/
void
{
int i, nmem;
return;
}
for (i = 0; i < nmem; i++) {
}
}
/*
* swap the list of banks for the 2 boards
*/
void
{
return;
}
/*
* free all the banks on the board. Note that a bank node belongs
* to 2 lists. The first list is the board list. The second one is
* the seg list. We only need to remove the bank from both lists but only
* free the node once.
*/
void
{
/*
* Remove the bank from the seg list first
*/
}
}
#ifdef DEBUG
void
{
int i;
SBDP_DBG_MEM("\tvalid %d\tuk 0x%x\tum 0x%x\tlk 0x%x"
}
}
void
sbdp_print_all_segs(void)
{
}
void
{
int i;
SBDP_DBG_MEM("\tvalid %d\tuk 0x%x\tum 0x%x\tlk 0x%x"
}
}
#endif
void
{
static int id = 0;
/*
* if we got an invalid bank just skip it
*/
return;
/*
* This bank is part of a new segment, so create
* a struct for it and added to the list of segments
*/
/*
* add to the seg list
*/
}
/*
* add bank into segs bank list. Note we add at the head
*/
}
/*
* Remove this segment from the seg list
*/
void
{
break;
}
}
}
}
/*
* remove this bank from its seg list
*/
void
{
/*
* if we got an invalid bank just skip it
*/
return;
/*
* If the bank doesn't belong to any seg just return
*/
return;
}
/*
* Find bank in the seg
*/
/*
* found node, remove it
*/
break;
}
}
}
/*
* No banks left on this segment, remove the segment
*/
SBDP_DBG_MEM("No banks left in this segment, removing it\n");
}
}
{
break;
}
return (cur_seg);
}
#ifdef DEBUG
int
{
/* copy 32 bytes at src_pa to dst_pa */
/* increment by 32 bytes */
/* decrement by 32 bytes */
}
}
return (0);
}
static int
{
}
int
sbdp_strtoi(char *p, char **pos)
{
int n;
int c, neg = 0;
if (!isdigit(c = *p)) {
while (isspace(c))
c = *++p;
switch (c) {
case '-':
neg++;
/* FALLTHROUGH */
case '+':
c = *++p;
}
if (!isdigit(c)) {
*pos = p;
return (0);
}
}
for (n = '0' - c; isdigit(c = *++p); ) {
n *= 10; /* two steps to avoid unnecessary overflow */
n += '0' - c; /* accum neg to avoid surprises at MAX */
}
*pos = p;
return (neg ? n : -n);
}
int
{
int board, i;
char *opts;
int t_board;
if (scriptlen <= 0) {
"sbdp failed to prep for copy-rename");
}
for (i = 0; i < (scriptlen / (sizeof (sbdp_rename_script_t))); i++) {
prom_printf("0x%lx = 0x%lx, asi 0x%x\n",
}
prom_printf("\n");
return (0);
}
#endif