drmach.c revision 8682d1ef2a0960ed5a9f05b9448eaa3e68ac931f
/*
* 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.
*/
#include <sys/dditypes.h>
#include <sys/ndi_impldefs.h>
#include <sys/processor.h>
#include <sys/spitregs.h>
#include <sys/mem_config.h>
#include <sys/ddi_impldefs.h>
#include <sys/machsystm.h>
#include <sys/autoconf.h>
#include <sys/sysmacros.h>
#include <sys/prom_plat.h>
#include <vm/seg_kmem.h>
#include <sys/mem_cage.h>
#include <sys/archsystm.h>
#include <vm/hat_sfmmu.h>
#include <sys/cpu_module.h>
#include <sys/cpu_sgnblk_defs.h>
#include <sys/sysevent.h>
extern void flush_ecache_il(
extern uint64_t mc_get_mem_alignment(void);
extern int pc_madr_add(int, int, int, int);
typedef struct {
struct drmach_node *node;
void *data;
typedef struct drmach_node {
void *here;
typedef struct {
int min_index;
int max_index;
int arr_sz;
typedef struct {
void *isa;
char name[MAXNAMELEN];
typedef struct {
int bnum;
int assigned;
int powered;
int connect_cpuid;
int cond;
typedef struct {
int unum;
int busy;
int powered;
const char *type;
typedef struct {
int flags;
typedef struct {
typedef struct {
typedef struct {
typedef enum {
typedef struct {
int ndevs;
void *a;
static caddr_t drmach_shutdown_va;
static int drmach_initialized;
static drmach_array_t *drmach_boards;
static int drmach_cpu_delay = 100;
static int drmach_cpu_ntries = 50000;
volatile uchar_t *drmach_xt_mb;
/*
* Do not change the drmach_shutdown_mbox structure without
* considering the drmach_shutdown_asm assembly language code.
*/
struct drmach_shutdown_mbox {
int size;
int linesize;
};
static int drmach_fini(void);
drmach_board_t *, drmach_device_t **);
#ifdef DEBUG
int drmach_debug = 0; /* set to non-zero to enable debug messages */
#else
#endif /* DEBUG */
#define DRMACH_IS_BOARD_ID(id) \
((id != 0) && \
#define DRMACH_IS_CPU_ID(id) \
((id != 0) && \
#define DRMACH_IS_MEM_ID(id) \
((id != 0) && \
#define DRMACH_IS_IO_ID(id) \
((id != 0) && \
#define DRMACH_IS_DEVICE_ID(id) \
((id != 0) && \
#define DRMACH_IS_ID(id) \
((id != 0) && \
#define DRMACH_CPUID2BNUM(cpuid) \
((cpuid) / MAX_CPU_UNITS_PER_BOARD)
#define DRMACH_INTERNAL_ERROR() \
static char *drmach_ie_fmt = "drmach.c %d";
static struct {
const char *name;
const char *type;
} name2type[] = {
};
/* node types to cleanup when a board is unconfigured */
#define MISC_COUNTER_TIMER_DEVNAME "counter-timer"
#define MISC_PERF_COUNTER_DEVNAME "perf-counter"
/* utility */
#define MBYTE (1048576ull)
/*
* This is necessary because the CPU support needs
* to call cvc_assign_iocpu.
*/
#ifndef lint
char _depends_on[] = "drv/cvc";
#endif /* lint */
/*
* drmach autoconfiguration data structures and interfaces
*/
extern struct mod_ops mod_miscops;
"Sun Enterprise 10000 DR"
};
static struct modlinkage modlinkage = {
(void *)&modlmisc,
};
static kmutex_t drmach_i_lock;
int
_init(void)
{
int err;
/* check that we have the correct version of obp */
if (prom_test("SUNW,UE10000,add-brd") != 0) {
"DR Functionality");
return (-1);
}
drmach_shutdown_asm_mbox = (struct drmach_shutdown_mbox *)
VM_SLEEP);
sizeof (struct drmach_shutdown_mbox));
}
return (err);
}
int
_fini(void)
{
if (drmach_fini())
return (DDI_FAILURE);
else
return (mod_remove(&modlinkage));
}
int
{
}
static pnode_t
{
}
static int
{
int rv;
/* initialized args structure for callback */
/* save our new position with in the tree */
rv = 0;
while (nodeid != OBP_NONODE) {
if (rv)
break;
/* save our new position with in the tree */
}
return (rv);
}
static drmach_node_t *
drmach_node_new(void)
{
return (np);
}
static void
{
}
static dev_info_t *
{
if (nodeid == OBP_NONODE)
return (NULL);
else {
/* The root node doesn't have to be held */
if (dip) {
/*
* Branch rooted at dip is already held, so release
* hold acquired in e_ddi_nodeid_to_dip()
*/
}
return (dip);
}
/*NOTREACHED*/
}
static pnode_t
{
}
static int
{
}
static int
{
int rv;
if (nodeid == OBP_NONODE)
rv = -1;
rv = -1;
else {
rv = 0;
}
return (rv);
}
static int
{
int rv;
if (nodeid == OBP_NONODE)
rv = -1;
else {
}
return (rv);
}
static drmachid_t
{
dup = drmach_node_new();
return (dup);
}
/*
* drmach_array provides convenient array construction, access,
* bounds checking and array destruction logic.
*/
static drmach_array_t *
{
return (arr);
} else {
return (0);
}
}
static int
{
return (-1);
else {
return (0);
}
/*NOTREACHED*/
}
static int
{
return (-1);
else {
return (0);
}
/*NOTREACHED*/
}
static int
{
int rv;
*idx += 1;
return (rv);
}
static int
{
int rv;
*idx += 1;
*idx += 1;
return (rv);
}
static void
{
int idx;
int rv;
while (rv == 0) {
}
}
/*ARGSUSED*/
static int
{
int rprop[64];
return (DDI_FAILURE);
if (saved == OBP_NONODE) {
err = DRMACH_INTERNAL_ERROR();
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static void
{
}
{
devi_branch_t b = {0};
if (!DRMACH_IS_DEVICE_ID(id))
b.type = DEVI_BRANCH_PROM;
DEVI_BRANCH_CHILD | DEVI_BRANCH_CONFIGURE) != 0) {
/*
* If non-NULL, fdip is returned held and must be released.
*/
/* safe to call ddi_pathname as dip already held */
} else {
}
}
}
static sbd_error_t *
{
int i;
int rv;
char name[OBP_MAXDRVNAME];
if (rv) {
/* every node is expected to have a name */
"PROM Node 0x%x: property %s",
return (err);
}
/*
* The node currently being examined is not listed in the name2type[]
* array. In this case, the node is no interest to drmach. Both
* dp and err are initialized here to yield nothing (no device or
* error structure) for this case.
*/
break;
if (err) {
}
return (err);
}
/*
* The node currently being examined is not listed in the name2type[]
* array. In this case, the node is no interest to drmach. Both
* dp and err are initialized here to yield nothing (no device or
* error structure) for this case.
*/
return (NULL);
}
static void
{
}
static sbd_error_t *
{
int rv;
if (rv) {
"%s::%s: property %s",
}
return (err);
}
static sbd_error_t *
{
int rv;
if (rv) {
"%s::%s: property %s",
}
return (err);
}
static drmach_board_t *
drmach_board_new(int bnum)
{
return (bp);
}
static void
{
}
static sbd_error_t *
{
if (!DRMACH_IS_BOARD_ID(id))
int rv;
int d_idx;
while (rv == 0) {
if (err)
break;
}
}
return (err);
}
/* a simple routine to reduce redundancy of this common logic */
static pda_handle_t
drmach_pda_open(void)
{
/* catch in debug kernels */
ASSERT(0);
}
return (ph);
}
#ifdef DEBUG
int drmach_init_break = 0;
#endif
static int
{
int i;
/*
* For Starfire, we must be children of the root devinfo node
*/
break;
/* Not of interest to us */
return (DDI_WALK_PRUNECHILD);
}
if (*holdp) {
} else {
}
return (DDI_WALK_PRUNECHILD);
}
static int
drmach_init(void)
{
#ifdef DEBUG
if (drmach_init_break)
debug_enter("drmach_init: drmach_init_break set\n");
#endif
if (drmach_initialized) {
return (0);
}
do {
int bnum;
bnum = -1;
if (bnum == -1)
continue;
" invalid property value, %s=%d",
/* clean up */
return (-1);
(void) drmach_board_new(bnum);
/*
* Walk immediate children of devinfo root node and hold
* all devinfo branches of interest.
*/
hold = 1;
rdip = ddi_root_node();
drmach_initialized = 1;
return (0);
}
static int
drmach_fini(void)
{
if (drmach_initialized) {
int busy = 0;
int rv;
int idx;
while (rv == 0) {
if (err) {
/* catch in debug kernels */
ASSERT(0);
sbd_err_clear(&err);
busy = 1;
} else
}
if (busy)
return (-1);
/*
* Walk immediate children of the root devinfo node
* releasing holds acquired on branches in drmach_init()
*/
hold = 0;
rdip = ddi_root_node();
drmach_initialized = 0;
}
if (drmach_xt_mb != NULL) {
}
if (drmach_shutdown_asm_mbox != NULL) {
sizeof (struct drmach_shutdown_mbox));
}
return (0);
}
static sbd_error_t *
{
if (!DRMACH_IS_MEM_ID(id))
return (DRMACH_INTERNAL_ERROR());
return (DRMACH_INTERNAL_ERROR());
return (NULL);
}
static sbd_error_t *
{
if (!DRMACH_IS_MEM_ID(id))
return (DRMACH_INTERNAL_ERROR());
return (DRMACH_INTERNAL_ERROR());
return (NULL);
}
static sbd_error_t *
{
if (!DRMACH_IS_MEM_ID(id))
err = DRMACH_INTERNAL_ERROR();
err = DRMACH_INTERNAL_ERROR();
else
return (err);
}
static sbd_error_t *
{
if (!DRMACH_IS_MEM_ID(id))
err = DRMACH_INTERNAL_ERROR();
err = DRMACH_INTERNAL_ERROR();
else
return (err);
}
static sbd_error_t *
{
int i, b, m;
#ifdef DEBUG
/*
* This function has been coded with that fact in mind.
*/
/*
* calculate the maximum space that could be consumed,
* then verify the available buffer space is adequate.
*/
b *= MAX_BOARDS;
b += sizeof (drmach_rename_script_t *) * 3;
b += sizeof (drmach_rename_script_t *) * 1;
#endif
/*
* construct an array of MC idle register addresses of
* both MCs. The array is zero terminated -- as expected
* by drmach_copy_rename_prog__relocatable().
*/
/* source mc */
if (err)
return (err);
isp += 1;
/* target mc */
if (err)
return (err);
isp += 1;
/* terminator */
isp += 1;
/* fetch source mc asr register value */
if (err)
return (err);
else if (s_masr & STARFIRE_MC_INTERLEAVE_MASK) {
}
/* fetch target mc asr register value */
if (err)
return (err);
else if (t_masr & STARFIRE_MC_INTERLEAVE_MASK) {
}
/* get new source base pa from target's masr */
/*
* remove any existing slice offset to realign
* memory with board's slice boundary
*/
/* get new target base pa from source's masr */
/* remove any existing slice offset, then apply new offset */
/* encode new base pa into s_masr. turn off mem present bit */
/* encode new base pa into t_masr. turn on mem present bit */
/*
* Step 0: Mark source memory as not present.
*/
m = 0;
if (err)
return (err);
m++;
/*
* Step 1: Write source base address to target MC
* with present bit off.
*/
if (err)
return (err);
m++;
/*
* Step 2: Now rewrite target reg with present bit on.
*/
m++;
DRMACH_PR("preparing script for CPU and IO units:\n");
if (rv) {
/* catch this in debug kernels */
ASSERT(0);
return (DRMACH_INTERNAL_ERROR());
}
do {
int d_idx;
/*
* Step 3: Update PC MADR tables for CPUs.
*/
/* devices not initialized */
continue;
}
if (rv) {
/* must mean no devices on this board */
break;
}
do {
if (!DRMACH_IS_CPU_ID(d_id))
continue;
/*
* Disabled detaching mem node.
*/
m++;
/*
* Always write masr with present bit
* off and then again with it on.
*/
m++;
m++;
/*
* Step 4: Update PC MADR tables for IOs.
*/
/* this worked for previous loop, must work here too */
do {
if (!DRMACH_IS_IO_ID(d_id))
continue;
/*
* Disabled detaching mem node.
*/
m++;
/*
* Always write masr with present bit
* off and then again with it on.
*/
m++;
m++;
/*
* Zero masr_addr value indicates the END.
*/
DRMACH_PR("number of steps in rename script = %d\n", m);
m++;
/* paranoia */
#ifdef DEBUG
{
int j;
DRMACH_PR("mc idle register address list:");
DRMACH_PR("source mc idle addr 0x%lx, mem id %p",
DRMACH_PR("target mc idle addr 0x%lx, mem id %p",
DRMACH_PR("copy-rename script:");
for (j = 0; j < m; j++) {
DRMACH_PR("0x%lx = 0x%08x",
}
DELAY(1000000);
}
#endif
/* return number of bytes consumed */
DRMACH_PR("total number of bytes consumed is %d\n", b);
#ifdef lint
#endif
return (NULL);
}
/*
* The routine performs the necessary memory COPY and MC adr SWITCH.
* Both operations MUST be at the same "level" so that the stack is
* maintained correctly between the copy and switch. The switch
* portion implements a caching mechanism to guarantee the code text
* is cached prior to execution. This is to guard against possible
* memory access while the MC adr's are being modified.
*
* IMPORTANT: The _drmach_copy_rename_end() function must immediately
* follow drmach_copy_rename_prog__relocatable() so that the correct
* "length" of the drmach_copy_rename_prog__relocatable can be
* calculated. This routine MUST be a LEAF function, i.e. it can
* make NO function calls, primarily for two reasons:
*
* 1. We must keep the stack consistent across the "switch".
* 2. Function calls are compiled to relative offsets, and
* we execute this function we'll be executing it from
* a copied version in a different area of memory, thus
* the relative offsets will be bogus.
*
* Moreover, it must have the "__relocatable" suffix to inform DTrace
* providers (and anything else, for that matter) that this
* function's text is manually relocated elsewhere before it is
* executed. That is, it cannot be safely instrumented with any
* methodology that is PC-relative.
*/
static void
{
int csize;
int lnsize;
/*
* 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.
*/
/*
* Wait for MCs to go idle.
*/
do {
register int t = 10;
register uint_t v;
/* loop t cycles waiting for each mc to indicate it's idle */
do {
} while (v != STARFIRE_MC_IDLE_MASK && t-- > 0);
/* bailout if timedout */
if (t <= 0) {
return;
}
isp += 1;
/* stop if terminating zero has been reached */
/* advance passed terminating zero */
isp += 1;
/*
* 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.
*/
}
static void
drmach_copy_rename_end(void)
{
/*
* IMPORTANT: This function's location MUST be located immediately
* following drmach_copy_rename_prog__relocatable to
* accurately estimate its size. Note that this assumes
* the compiler keeps these functions in the order in
* which they appear :-o
*/
}
{
int len;
if (!DRMACH_IS_MEM_ID(s_id))
if (!DRMACH_IS_MEM_ID(t_id))
/* get starting physical address of target memory */
if (err)
return (err);
/* calculate slice offset mask from slice size */
/* calculate source and target base pa */
/* paranoia */
/* adjust copy memlist addresses to be relative to copy base pa */
}
#ifdef DEBUG
{
DRMACH_PR("source copy span: base pa 0x%lx, end pa 0x%lx\n",
DRMACH_PR("target copy span: base pa 0x%lx, end pa 0x%lx\n",
DRMACH_PR("copy memlist (relative to copy base pa):\n");
DRMACH_PR("current source base pa 0x%lx, size 0x%lx\n",
DRMACH_PR("current target base pa 0x%lx, size 0x%lx\n",
}
#endif
ph = drmach_pda_open();
return (DRMACH_INTERNAL_ERROR());
/*
* bp will be page aligned, since we're calling
* kmem_zalloc() with an exact multiple of PAGESIZE.
*/
/* allocate space for copy rename struct */
len = sizeof (drmach_copy_rename_program_t);
/*
* 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.
*/
/*
* Prepare data page that will contain script of
* operations to perform during copy-rename.
* Allocate temporary buffer to hold script.
*/
if (err) {
(void) drmach_copy_rename_fini(prog);
return (err);
}
return (NULL);
}
{
if (prog->restless_mc != 0) {
err = DRMACH_INTERNAL_ERROR();
}
return (err);
}
static sbd_error_t *
{
int portid;
}
return (err);
}
static void
{
register int b;
for (b = 0; b < MAX_BOARDS; b++) {
int p;
if (pda_board_present(ph, b) == 0)
continue;
/*
* Update PCs for IOCs.
*/
for (p = 0; p < MAX_IOCS; p++) {
continue;
switch (op) {
case DO_PAUSE:
value = STARFIRE_BB_PC_PAUSE(p);
break;
case DO_IDLE:
value = STARFIRE_BB_PC_IDLE(p);
break;
case DO_UNPAUSE:
value &= ~STARFIRE_BB_PC_PAUSE(p);
break;
case DO_UNIDLE:
value &= ~STARFIRE_BB_PC_IDLE(p);
break;
default:
"drmach_iopc_op: unknown op (%d)",
(int)op);
/*NOTREACHED*/
}
}
}
}
void
{
/*
* UPA IDLE
* Protocol = PAUSE -> IDLE -> UNPAUSE
* In reality since we only "idle" the IOPCs it's sufficient
* to just issue the IDLE operation since (in theory) all IOPCs
* in the field are PC6. However, we'll be robust and do the
* proper workaround protocol so that we never have to worry!
*/
DELAY(100);
DELAY(100);
/* disable CE reporting */
neer = get_error_enable();
/* enable CE reporting */
/*
* UPA UNIDLE
* Protocol = UNIDLE
*/
DELAY(100);
}
/*
* The counter-timer and perf-counter nodes are not being cleaned
* up after a board that was present at start of day is detached.
* If the board has become unconfigured with this operation, walk
* the prom tree and find all counter-timer and perf-counter nodes
* that have the same board number as the board that was just
* unconfigured and remove them.
*/
static sbd_error_t *
{
int num;
char name[OBP_MAXDRVNAME];
if (!DRMACH_IS_BOARD_ID(id)) {
}
return (err);
}
/*
* Only clean up the counter-timer and perf-counter
* nodes when the entire board is unconfigured.
*/
if (stat.configured) {
return (NULL);
}
continue;
}
continue;
}
continue;
}
continue;
}
/* Root node doesn't have to be held */
/*
* If the node is only in the OBP tree, then
* we don't have to remove it.
*/
if (dip) {
/*
* If non-NULL, fdip is held and must be
* released.
*/
} else {
}
break;
}
}
}
return (err);
}
/*ARGSUSED*/
{
/* allow status and ncm operations to always succeed */
return (NULL);
}
/* check all other commands for the required option string */
return (NULL);
}
}
}
/*ARGSUSED*/
{
switch (cmd) {
case SBD_CMD_UNCONFIGURE:
break;
case SBD_CMD_CONFIGURE:
case SBD_CMD_DISCONNECT:
case SBD_CMD_CONNECT:
case SBD_CMD_GETNCM:
case SBD_CMD_STATUS:
break;
default:
break;
}
return (err);
}
{
err = DRMACH_INTERNAL_ERROR();
} else {
}
return (err);
}
static int
drmach_attach_board(void *arg)
{
int retval;
/*
* OBP disables traps during the board probe.
* So, in order to prevent cross-call/cross-trap timeouts,
* and thus panics, we effectively block anybody from
* In the previous version of Starfire DR (2.6), a timeout
* suspension mechanism was implemented in the send-mondo
* assembly. That mechanism is unnecessary with the
* existence of xc_attention/xc_dismissed.
*/
return (retval);
}
{
int retval;
if (!DRMACH_IS_BOARD_ID(id))
int cpuid;
} else
} else {
/* cpuid was not specified */
}
return (err);
}
if (retval == 0)
else {
}
return (err);
}
/*ARGSUSED*/
{
int rv;
int d_idx; /* device index */
if (!DRMACH_IS_BOARD_ID(id))
/*
* We need to make sure all of the board's device nodes
* have been removed from the Solaris device tree before
* continuing with the disconnect. Otherwise, we could
* disconnect the board and remove the OBP device tree
* nodes with Solaris device tree nodes remaining.
*
* On Starfire, Solaris device tree nodes are deleted
* during unconfigure by drmach_unconfigure(). It's
* necessary to do this here because drmach_unconfigure()
* failures are not handled during unconfigure.
*/
while (rv == 0) {
if (err)
return (err);
}
}
/*
* Starfire board Solaris device tree counter nodes,
* which are only present on start-of-day boards, are
* removed in the dr_post_op() code flow after the
* board is unconfigured. We call the counter node
* removal function here because unconfigure errors
* can cause the dr_post_op() function to be skipped
* after an unconfigure operation even though all of
* the board's devices have been transitioned to the
* unconfigured state.
*/
if (err)
return (err);
return (NULL);
}
static int
{
int rv;
int bnum;
if (rv) {
/*
* if the node does not have a board# property, then
* by that information alone it is known that drmach
* is not interested in it.
*/
return (0);
return (0);
/*
* Create a device data structure from this node data.
* The call may yield nothing if the node is not of interest
* to drmach.
*/
return (-1);
/*
* drmach_device_new examined the node we passed in
* and determined that it was one not of interest to
* drmach. So, it is skipped.
*/
return (0);
}
if (rv) {
return (-1);
}
}
{
extern int plat_max_cpu_units_per_board();
extern int plat_max_mem_units_per_board();
extern int plat_max_io_units_per_board();
int max_devices;
int rv;
data.a = a;
if (rv == 0)
else {
else
err = DRMACH_INTERNAL_ERROR();
}
return (err);
}
int
{
int rv = 0;
*id = 0;
rv = -1;
*id = 0;
rv = -1;
}
return (rv);
}
{
return (NULL);
}
{
if (!DRMACH_IS_BOARD_ID(id))
if (err)
return (err);
else {
/* board power off is essentially a noop for Starfire */
return (NULL);
}
/*NOTREACHED*/
}
{
if (!DRMACH_IS_BOARD_ID(id))
/* board power on is essentially a noop for Starfire */
return (NULL);
}
static sbd_error_t *
{
if (!DRMACH_IS_BOARD_ID(id))
return (NULL);
}
/*ARGSUSED*/
{
return (NULL);
}
{
if (!DRMACH_IS_BOARD_ID(id))
if (err)
return (err);
return (DRMACH_INTERNAL_ERROR());
else {
return (NULL);
}
/*NOTREACHED*/
}
static sbd_error_t *
{
int portid;
return (err);
}
/*
* drmach_cpu_obp_detach()
* This requires two steps, first, we must put the cpuid into the OBP
* idle loop (Idle in Program) state. Then we call OBP to place the CPU
* into the "Detached" state, which does any special processing to
* actually detach the cpu, such as flushing ecache, and also ensures
* that a subsequent breakpoint won't restart the cpu (if it was just in
* Idle in Program state).
*/
static void
{
/*
* Cpu may not be under OBP's control. Eg, if cpu exited to download
* helper on a prior attach.
*/
if (CPU_SGN_EXISTS(cpuid) &&
!SGN_CPU_IS_OS(cpuid) &&
!SGN_CPU_IS_OBP(cpuid)) {
"unexpected signature (0x%x) for cpu %d",
}
/*
* Now we place the CPU into the "Detached" idle loop in OBP.
* This is so that the CPU won't be restarted if we break into
* OBP with a breakpoint or BREAK key from the console, and also
* if we need to do any special processing, such as flushing the
* cpu's ecache, disabling interrupts (by turning of the ET bit in
*/
}
/*
* drmach_cpu_obp_is_detached() returns TRUE if the cpu sigblock signature state
* is SIGBST_DETACHED; otherwise it returns FALSE. This routine should only
* be called after we have asked OBP to detach the CPU. It should NOT be
* called as a check during any other flow.
*/
static int
{
if (!CPU_SGN_EXISTS(cpuid) ||
return (1);
else
return (0);
}
static int
{
int ntries = drmach_cpu_ntries;
extern void restart_other_cpu(int);
/*
* NOTE: restart_other_cpu pauses cpus during the
* slave cpu start. This helps to quiesce the
* bus traffic a bit which makes the tick sync
* routine in the prom more robust.
*/
/*
* Wait for the cpu to reach its idle thread before
* we zap him with a request to blow away the mappings
* he (might) have for the drmach_shutdown_asm code
* he may have executed on unconfigure.
*/
ntries--;
}
DRMACH_PR("waited %d out of %d loops for cpu %d\n",
return (0);
}
/*
* A detaching CPU is xcalled with an xtrap to drmach_cpu_stop_self() after
* it has been offlined. The function of this routine is to get the cpu
* spinning in a safe place. The requirement is that the system will not
* reference anything on the detaching board (memory and i/o is detached
* elsewhere) and that the CPU not reference anything on any other board
* in the system. This isolation is required during and after the writes
* to the domain masks to remove the board from the domain.
*
* To accomplish this isolation the following is done:
* 1) Create a locked mapping to a location in BBSRAM where
* the cpu will execute.
* 2) Copy the target function (drmach_shutdown_asm) in which
* the cpu will execute into BBSRAM.
* 3) Jump into function with BBSRAM.
* Function will:
* 3.1) Flush its Ecache (displacement).
* 3.2) Flush its Dcache with HW mechanism.
* 3.3) Flush its Icache with HW mechanism.
* 3.4) Flush all valid and _unlocked_ D-TLB entries.
* 3.5) Flush all valid and _unlocked_ I-TLB entries.
* 3.6) Clear xt_mb to signal completion. Note: cache line is
* recovered by drmach_cpu_poweroff().
* 4) Jump into a tight loop.
*/
#define DRMACH_BBSRAM_OFFSET 0x1000
static void
drmach_cpu_stop_self(void)
{
void (*bbsram_func)(uint64_t);
extern void drmach_shutdown_asm(uint64_t);
extern void drmach_shutdown_asm_end(void);
/*
* We'll start from the 0th's base.
*/
bbsram_func = (void (*)())bbsram_addr;
/*
* Signal to drmach_cpu_poweroff() is via drmach_xt_mb cleared
* by asm code
*/
}
static void
drmach_cpu_shutdown_self(void)
{
extern void flush_windows(void);
(void) spl8();
}
/* a helper routine to keep the math in one place */
static processorid_t
{
}
/*
* Move bootproc (SIGBCPU) to another cpu. If dst_cpu is NULL, a
* destination cpu is chosen from the set of cpus not located on the
* same board as the current bootproc cpu.
*/
static sbd_error_t *
{
int rv;
/* dst_cpu is NULL when target cpu is unspecified. So, pick one. */
break;
}
return (err);
}
/* else, cp points to the selected target cpu */
} else {
return (err);
}
"SIGBCPU(%d) same as new selection(%d)",
/* technically not an error, but a no-op */
return (NULL);
}
}
/*
* Tell OBP to initialize cvc-offset field of new CPU0
* so that it's in sync with OBP and cvc_server
*/
/*
* Assign cvc to new cpu0's bbsram for I/O. This has to be
* done BEFORE cpu0 is moved via obp, since this logic
* will cause obp_helper to switch to a different bbsram for
* cvc I/O. We don't want cvc writing to a buffer from which
* nobody will pick up the data!
*/
if (rv == 0) {
return (NULL);
} else {
DRMACH_PR("prom error: prom_starfire_move_cpu0(%d) "
/*
* The move failed, hopefully obp_helper is still back
* at the old bootproc. Move cvc back there.
*/
return (err);
}
/*NOTREACHED*/
}
static sbd_error_t *
{
if (!DRMACH_IS_CPU_ID(id))
err = DRMACH_INTERNAL_ERROR();
else
return (err);
}
static sbd_error_t *
{
return (NULL);
}
{
int cpuid;
int ntries;
int p;
if (!DRMACH_IS_CPU_ID(id))
/* this cpu is SIGBCPU, can't disconnect */
}
/*
* Make sure SIGBST_DETACHED is set before
* mapping out the sig block.
*/
ntries--;
}
if (!drmach_cpu_obp_is_detached(cpuid)) {
cpuid);
}
/* map out signature block */
if (CPU_SGN_EXISTS(cpuid)) {
}
/*
* We now PC IDLE the processor to guarantee we
* stop any transactions from coming from it.
*/
DRMACH_PR("PC idle cpu %d (addr = 0x%llx, port = %d, p = %d)",
rvalue |= STARFIRE_BB_PC_IDLE(p);
DELAY(50000);
return (NULL);
}
{
if (!DRMACH_IS_CPU_ID(id))
return (NULL);
}
{
int impl;
if (!DRMACH_IS_CPU_ID(id))
return (DRMACH_INTERNAL_ERROR());
}
return (NULL);
}
void
{
/*
* Now let's flush our ecache thereby removing all references
* to the target (detaching) memory from all ecache's in
* system.
*/
/*
* Delay 100 usec out of paranoia to insure everything
* (hardware queues) has drained before we start reprogramming
* the hardware.
*/
DELAY(100);
}
{
if (!DRMACH_IS_DEVICE_ID(id))
return (NULL);
}
{
int state;
if (!DRMACH_IS_IO_ID(id))
*yes = 0;
return (NULL);
}
return (NULL);
}
{
if (!DRMACH_IS_IO_ID(id))
return (NULL);
}
static sbd_error_t *
{
if (!DRMACH_IS_IO_ID(id))
return (NULL);
}
{
if (!DRMACH_IS_IO_ID(id))
return (NULL);
}
/*ARGSUSED*/
{
return (NULL);
}
/*ARGSUSED*/
{
return (NULL);
}
static sbd_error_t *
{
int configured;
if (err)
return (err);
return (NULL);
}
static sbd_error_t *
{
return (NULL);
}
{
int rv;
if (!DRMACH_IS_MEM_ID(id))
} else if (rv != 0) {
/* catch this in debug kernels */
ASSERT(0);
" return value %d", rv);
}
/*
* Update the PDA (post2obp) structure with the
* range of the newly added memory.
*/
ph = drmach_pda_open();
}
return (NULL);
}
{
int rv;
if (err)
return (err);
else if (mcreg & STARFIRE_MC_INTERLEAVE_MASK) {
}
if (size > 0) {
if (rv != 0) {
"unexpected kcage_range_delete_post_mem_del"
" return value %d", rv);
return (DRMACH_INTERNAL_ERROR());
}
}
/*
* Update the PDA (post2obp) structure with the
* range of removed memory.
*/
ph = drmach_pda_open();
if (size > 0)
/* update PDA to board's new mc register settings */
}
return (NULL);
}
/* support routine for enable and disable */
static sbd_error_t *
{
int b;
if (!DRMACH_IS_MEM_ID(id))
ph = drmach_pda_open();
return (DRMACH_INTERNAL_ERROR());
for (b = 0; b < MAX_BOARDS; b++) {
int p;
int rv;
if (pda_board_present(ph, b) == 0)
continue;
/*
* Update PCs for CPUs.
*/
/* make sure definition in platmod is in sync with pda */
for (p = 0; p < MAX_PROCMODS; p++) {
continue;
if (rv) {
return (DRMACH_INTERNAL_ERROR());
}
}
/*
* Update PCs for IOCs.
*/
/* make sure definition in platmod is in sync with pda */
for (p = 0; p < MAX_IOCS; p++) {
continue;
if (rv) {
return (DRMACH_INTERNAL_ERROR());
}
}
}
return (NULL);
}
{
/* Turn off presence bit. */
}
return (err);
}
{
}
return (err);
}
{
if (!DRMACH_IS_MEM_ID(id))
err = DRMACH_INTERNAL_ERROR();
else {
err = DRMACH_INTERNAL_ERROR();
else {
}
}
return (err);
}
{
return (err);
}
/*
* because the OBP MEMAVAIL property will not correctly reflect the
* programming of the MCs.
*/
{
struct sf_memunit_regspec *rlist;
if (!DRMACH_IS_MEM_ID(id))
if (err)
return (err);
if (err) {
return (err);
}
for (i = 0; i < rblks; i++) {
}
/*
* Make sure the incoming memlist doesn't already
* intersect with what's present in the system (phys_install).
*/
if (rv) {
#ifdef DEBUG
DRMACH_PR("OBP derived memlist intersects"
" with phys_install\n");
DRMACH_PR("phys_install memlist:\n");
#endif
return (DRMACH_INTERNAL_ERROR());
}
#ifdef DEBUG
DRMACH_PR("OBP derived memlist:");
#endif
return (NULL);
}
{
if (!DRMACH_IS_MEM_ID(id))
ph = drmach_pda_open();
return (DRMACH_INTERNAL_ERROR());
return (NULL);
}
{
if (!DRMACH_IS_MEM_ID(id))
*bytes = mc_get_mem_alignment();
return (NULL);
}
/* field debugging tool */
{
if (!DRMACH_IS_MEM_ID(id))
return (CPU_CURRENT);
if (drmach_mem_cpu_affinity_nail) {
return (CPU_CURRENT);
cpuid = CPU_CURRENT;
return (cpuid);
}
/* try to choose a proc on the target board */
int rv;
int d_idx;
while (rv == 0) {
if (DRMACH_IS_CPU_ID(d_id)) {
DRMACH_PR("drmach_mem_cpu_affinity: "
"selected cpuid=%d\n", cpuid);
return (cpuid);
} else {
}
}
}
}
/* otherwise, this proc, wherever it is */
DRMACH_PR("drmach_mem_cpu_affinity: using default CPU_CURRENT\n");
return (CPU_CURRENT);
}
static sbd_error_t *
{
if (!DRMACH_IS_MEM_ID(id))
return (NULL);
}
static sbd_error_t *
{
/* get starting physical address of target memory */
if (err)
return (err);
/* round down to slice boundary */
/* stop at first span that is in slice */
break;
return (NULL);
}
static int
drmach_detach_board(void *arg)
{
int retval;
return (retval);
}
{
int retval;
if (!DRMACH_IS_BOARD_ID(id))
if (retval == 0)
return (NULL);
else {
}
}
/*ARGSUSED*/
static sbd_error_t *
{
if (!DRMACH_IS_CPU_ID(id))
return (err);
}
/*ARGSUSED*/
static sbd_error_t *
{
int board;
int i;
if (!DRMACH_IS_BOARD_ID(id))
ph = drmach_pda_open();
return (DRMACH_INTERNAL_ERROR());
return (DRMACH_INTERNAL_ERROR());
}
"no board descriptor found for board %d\n",
board);
return (DRMACH_INTERNAL_ERROR());
}
/* make sure definition in platmod is in sync with pda */
for (i = 0; i < MAX_PROCMODS; i++) {
"proc %d.%d PRESENT\n", board, i);
else
"proc %d.%d MISSING\n", board, i);
}
for (i = 0; i < MAX_MGROUPS; i++) {
"mgroup %d.%d PRESENT\n", board, i);
else
"mgroup %d.%d MISSING\n", board, i);
}
/* make sure definition in platmod is in sync with pda */
for (i = 0; i < MAX_IOCS; i++) {
int s;
"ioc %d.%d PRESENT\n", board, i);
for (s = 0; s < MAX_SLOTS_PER_IOC; s++) {
continue;
"..scard %d.%d.%d PRESENT\n",
board, i, s);
}
} else {
"ioc %d.%d MISSING\n",
board, i);
}
}
"board %d memsize = %d pages\n",
return (NULL);
}
/*ARGSUSED*/
{
/* copy 32 bytes at arc_pa to dst_pa */
/* increment by 32 bytes */
/* decrement by 32 bytes */
}
}
return (NULL);
}
static struct {
const char *name;
} drmach_pt_arr[] = {
{ "juggle", drmach_pt_juggle_bootproc },
{ "pda", drmach_pt_dump_pdainfo },
{ "readmem", drmach_pt_readmem },
/* the following line must always be last */
};
/*ARGSUSED*/
{
int i;
i = 0;
break;
i += 1;
}
else
return (err);
}
{
if (!DRMACH_IS_DEVICE_ID(id))
}
{
if (!DRMACH_IS_ID(id))
}
{
if (!DRMACH_IS_DEVICE_ID(id))
if (nodeid == OBP_NONODE)
return (DRMACH_INTERNAL_ERROR());
return (NULL);
/*
* Branch already held, so hold acquired in
* e_ddi_nodeid_to_dip() can be released
*/
if (flags & DEVI_BRANCH_DESTROY)
/*
* for details.
*/
/*
* If non-NULL, fdip is returned held and must be released.
*/
} else {
}
return (err);
}
return (NULL);
}
/*
* drmach interfaces to legacy Starfire platmod logic
* linkage via runtime symbol look up, called from plat_cpu_power*
*/
/*
* Start up a cpu. It is possible that we're attempting to restart
* the cpu after an UNCONFIGURE in which case the cpu will be
* spinning in its cache. So, all we have to do is wakeup him up.
* Under normal circumstances the cpu will be coming from a previous
* CONNECT and thus will be spinning in OBP. In both cases, the
* startup sequence is the same.
*/
int
{
if (drmach_cpu_start(cp) != 0)
return (EBUSY);
else
return (0);
}
int
{
void drmach_cpu_shutdown_self(void);
/*
* Capture all CPUs (except for detaching proc) to prevent
* crosscalls to the detaching proc until it has cleared its
* bit in cpu_ready_set.
*
* The CPU's remain paused and the prom_mutex is known to be free.
* This prevents the x-trap victim from blocking when doing prom
* IEEE-1275 calls at a high PIL level.
*/
/*
* Quiesce interrupts on the target CPU. We do this by setting
* the CPU 'not ready'- (i.e. removing the CPU from cpu_ready_set) to
* prevent it from receiving cross calls and cross traps.
* This prevents the processor from receiving any new soft interrupts.
*/
/* setup xt_mb, will be cleared by drmach_shutdown_asm when ready */
cnt = 0;
ntries--;
cnt++;
}
start_cpus();
DRMACH_PR("waited %d out of %d tries for "
"drmach_cpu_shutdown_self on cpu%d",
return (0);
}
/*ARGSUSED*/
int
{
return (0);
}
void
drmach_suspend_last(void)
{
}
void
drmach_resume_first(void)
{
}
/*
* Log a DR sysevent.
* Return value: 0 success, non-zero failure.
*/
int
{
sysevent_t *ev;
char attach_pnt[MAXNAMELEN];
attach_pnt[0] = '\0';
rv = -1;
goto logexit;
}
if (verbose)
DRMACH_PR("drmach_log_sysevent: %s %s, flag: %d, verbose: %d\n",
rv = -2;
goto logexit;
}
goto logexit;
goto logexit;
}
/*
* Log the event but do not sleep waiting for its
* delivery. This provides insulation from syseventd.
*/
if (ev)
"drmach_log_sysevent failed (rv %d) for %s %s\n",
return (rv);
}
/*ARGSUSED*/
int
{
return (1); /* TRUE */
}