/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright (c) 2011 Bayard G. Bell. All rights reserved.
*/
#include <sys/ddi_impldefs.h>
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <vm/hat_sfmmu.h>
#include <sys/autoconf.h>
#include <sys/cpu_module.h>
/* Useful debugging Stuff */
#include <sys/nexusdebug.h>
/*
* Function prototypes
*/
void **result);
static void ac_add_kstats(struct ac_soft_state *);
static void ac_del_kstats(struct ac_soft_state *);
static int ac_misc_kstat_update(kstat_t *, int);
static int ac_counters_kstat_update(kstat_t *, int);
static int ac_reset_timeout(int rw);
static void ac_timeout(void *);
static int ac_enter_transition(void);
static void ac_exit_transition(void);
int ac_add_memory(ac_cfga_pkt_t *);
int ac_del_memory(ac_cfga_pkt_t *);
int ac_mem_stat(ac_cfga_pkt_t *, int);
int ac_mem_test_start(ac_cfga_pkt_t *, int);
int ac_mem_test_stop(ac_cfga_pkt_t *, int);
int ac_mem_test_read(ac_cfga_pkt_t *, int);
int ac_mem_test_write(ac_cfga_pkt_t *, int);
/*
* ac audit message events
*/
typedef enum {
/* The memory ioctl interface version of this driver. */
static int ac_mem_exercise(ac_cfga_pkt_t *, int);
/*
* Configuration data structures
*/
ac_open, /* open */
ac_close, /* close */
nulldev, /* strategy */
nulldev, /* print */
nodev, /* dump */
nulldev, /* read */
nulldev, /* write */
ac_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, /* devo_rev, */
0, /* refcnt */
ac_info, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
ac_attach, /* attach */
ac_detach, /* detach */
nulldev, /* reset */
&ac_cb_ops, /* cb_ops */
(struct bus_ops *)0, /* bus_ops */
nulldev, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
/*
* Driver globals
*/
#define AC_GETSOFTC(I) \
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type of module. This one is a driver */
"AC Leaf", /* name of module */
&ac_ops, /* driver ops */
};
(void *)&modldrv,
};
/*
* These are the module initialization routines.
*/
int
_init(void)
{
int error;
1)) != 0)
return (error);
return (error);
}
/* Initialize global mutex */
return (0);
}
int
_fini(void)
{
int error;
}
return (error);
}
int
{
}
/* ARGSUSED */
static int
{
int instance;
if (infocmd == DDI_INFO_DEVT2INSTANCE) {
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
static int
{
int instance;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
instance);
return (DDI_FAILURE);
}
/* Set the dip in the soft state */
/* Get the board number from this nodes parent */
goto bad;
}
/* map in the registers for this device. */
goto bad;
}
/* Setup the pointers to the hardware registers */
"pm-hardware-state", "no-suspend-resume");
/* setup the the AC counter registers to allow for hotplug. */
}
/* set the AC rev into the bd list structure */
/* Create the minor nodes */
(AC_PUTINSTANCE(instance) | 0),
DDI_NT_ATTACHMENT_POINT, 0) == DDI_FAILURE) {
"ddi_create_minor_node failed", instance,
}
DDI_NT_ATTACHMENT_POINT, 0) == DDI_FAILURE) {
"ddi_create_minor_node failed", instance,
}
/* purge previous fhc pa database entries */
/* Inherit Memory Bank Status */
/* Final Memory Bank Status evaluation and messaging */
}
/* create the kstats for this device. */
return (DDI_SUCCESS);
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;
else
/* FALLTHROUGH */
default:
return (DDI_FAILURE);
}
int cpui;
/*
* In the case of a DR operation this condition
* will have been assured when the board was unconfigured.
*/
return (DDI_FAILURE);
}
/*
* CPU busy test is done by the DR sequencer before
* device detach called.
*/
/*
* Flush all E-caches to remove references to this
* board's memory.
*
* Do this one CPU at a time to avoid stalls and timeouts
* due to all CPUs flushing concurrently.
* xc_one returns silently for non-existant CPUs.
*/
}
/* delete the kstat for this driver. */
/* unmap the registers */
/* Remove the minor nodes. */
/* free the soft state structure */
return (DDI_SUCCESS);
}
/* ARGSUSED */
static int
{
int instance;
int vis;
/* Is the instance attached? */
#ifdef DEBUG
#endif /* DEBUG */
return (ENXIO);
}
/*
* If the board is not configured, hide the memory APs
*/
if (!vis)
return (ENXIO);
/* verify that otyp is appropriate */
return (EINVAL);
}
return (DDI_SUCCESS);
}
/* ARGSUSED */
static int
{
int instance;
return (DDI_SUCCESS);
}
static int
{
#ifdef _MULTI_DATAMODEL
sizeof (ac_cfga_cmd32_t), flag) != 0) {
return (EFAULT);
}
} else
#endif /* _MULTI_DATAMODEL */
sizeof (ac_cfga_cmd_t), flag) != 0) {
return (EFAULT);
}
return (0);
}
static int
{
#ifdef _MULTI_DATAMODEL
}
} else
#endif
}
SYSC_OUTPUT_LEN, flag) != 0))) {
}
return (ret);
}
/* ARGSUSED */
static int
int cmd,
int flag,
int *rval_p)
{
int instance;
int retval;
#ifdef DEBUG
#endif /* DEBUG */
return (ENXIO);
}
/*
* Dispose of the easy ones first.
*/
switch (cmd) {
case AC_MEM_ADMIN_VER:
/*
* Specify the revision of this ioctl interface driver.
*/
sizeof (ac_mem_version_t), flag) != 0)
return (EFAULT);
return (DDI_SUCCESS);
case AC_MEM_CONFIGURE:
case AC_MEM_UNCONFIGURE:
case AC_MEM_STAT:
case AC_MEM_TEST_START:
case AC_MEM_TEST_STOP:
case AC_MEM_TEST_READ:
case AC_MEM_TEST_WRITE:
case AC_MEM_EXERCISE:
break;
default:
return (ENOTTY);
}
return (ENOTSUP);
}
return (retval);
switch (cmd) {
case AC_MEM_CONFIGURE:
break;
}
break;
}
if (!retval)
else
break;
case AC_MEM_UNCONFIGURE:
break;
}
break;
}
if (!retval) {
} else
break;
case AC_MEM_STAT:
/*
* Query usage of a bank of memory.
*/
break;
case AC_MEM_TEST_START:
break;
}
break;
case AC_MEM_TEST_STOP:
break;
}
break;
case AC_MEM_TEST_READ:
/*
* read a 'page' (or less) of memory safely.
*/
break;
}
break;
case AC_MEM_TEST_WRITE:
/*
* write a 'page' (or less) of memory safely.
*/
break;
}
break;
case AC_MEM_EXERCISE:
break;
default:
ASSERT(0);
break;
}
return (retval);
}
static void
{
/*
* create the unix-misc kstat for address controller
* using the board number as the instance.
*/
sizeof (struct ac_kstat) / sizeof (kstat_named_t),
KSTAT_FLAG_PERSISTENT)) == NULL) {
return;
}
/* initialize the named kstats */
/*
* Create the picN kstats if we are the first instance
* to attach. We use ac_attachcnt as a count of how
* many instances have attached. This is protected by
* a mutex.
*/
if (ac_attachcnt == 0)
ac_attachcnt ++;
/*
* Create the "counter" kstat for each AC instance.
* This provides access to the %pcr and %pic
* registers for that instance.
*
* The size of this kstat is AC_NUM_PICS + 1 for %pcr
*/
KSTAT_FLAG_WRITABLE)) == NULL) {
return;
}
/* initialize the named kstats */
"pcr", KSTAT_DATA_UINT64);
"pic0", KSTAT_DATA_UINT64);
"pic1", KSTAT_DATA_UINT64);
/* update the sofstate */
}
/*
* called from ac_add_kstats() to create a kstat for each %pic
* that the AC supports. These (read-only) kstats export the
* event names and %pcr masks that each %pic supports.
*
* if we fail to create any of these kstats we must remove any
* that we have already created and return;
*
* NOTE: because all AC's use the same events we only need to
* create the picN kstats once. All instances can use
* the same picN kstats.
*
* The flexibility exists to allow each device specify it's
* own events by creating picN kstats with the instance number
* set to ddi_get_instance(softsp->dip).
*
* When searching for a picN kstat for a device you should
* first search for a picN kstat using the instance number
* of the device you are interested in. If that fails you
* should use the first picN kstat found for that device.
*/
static void
{
typedef struct ac_event_mask {
char *event_name;
/*
* AC Performance Events.
*
* We declare an array of event-names and event-masks.
*/
{"mem_bank0_rds", 0x1}, {"mem_bank0_wrs", 0x2},
{"mem_bank0_stall", 0x3}, {"mem_bank1_rds", 0x4},
{"mem_bank1_wrs", 0x5}, {"mem_bank1_stall", 0x6},
{"clock_cycles", 0x7}, {"addr_pkts", 0x8},
{"data_pkts", 0x9}, {"flow_ctl_cyc", 0xa},
{"fast_arb_pkts", 0xb}, {"bus_cont_cyc", 0xc},
{"data_bus_can", 0xd}, {"ac_addr_pkts", 0xe},
{"ac_data_pkts", 0xf}, {"rts_pkts", 0x10},
{"rtsa_pkts", 0x11}, {"rto_pkts", 0x12},
{"rs_pkts", 0x13}, {"wb_pkts", 0x14},
{"ws_pkts", 0x15}, {"rio_pkts", 0x16},
{"rbio_pkts", 0x17}, {"wio_pkts", 0x18},
{"wbio_pkts", 0x19}, {"upa_a_rds_m", 0x1a},
{"upa_a_rdo_v", 0x1b}, {"upa_b_rds_m", 0x1c},
{"upa_b_rdo_v", 0x1d}, {"upa_a_preqs_fr", 0x20},
{"upa_a_sreqs_to", 0x21}, {"upa_a_preqs_to", 0x22},
{"upa_a_rds_fr", 0x23}, {"upa_a_rdsa_fr", 0x24},
{"upa_a_rdo_fr", 0x25}, {"upa_a_rdd_fr", 0x26},
{"upa_a_rio_rbio", 0x27}, {"upa_a_wio_wbio", 0x28},
{"upa_a_cpb_to", 0x29}, {"upa_a_inv_to", 0x2a},
{"upa_a_hits_buff", 0x2b}, {"upa_a_wb", 0x2c},
{"upa_a_wi", 0x2d}, {"upa_b_preqs_fr", 0x30},
{"upa_b_sreqs_to", 0x31}, {"upa_b_preqs_to", 0x32},
{"upa_b_rds_fr", 0x33}, {"upa_b_rdsa_fr", 0x34},
{"upa_b_rdo_fr", 0x35}, {"upa_b_rdd_fr", 0x36},
{"upa_b_rio_rbio", 0x37}, {"upa_b_wio_wbio", 0x38},
{"upa_b_cpb_to", 0x39}, {"upa_b_inv_to", 0x3a},
{"upa_b_hits_buff", 0x3b}, {"upa_b_wb", 0x3c},
{"upa_b_wi", 0x3d}
};
/*
* array of clear masks for each pic.
* These masks are used to clear the %pcr bits for
* each pic.
*/
/* pic0 */
/* pic1 */
};
int pic_shift = 0;
/*
* create the picN kstat. The size of this kstat is
* AC_NUM_EVENTS + 1 for the clear_event_mask
*/
pic_name);
/* remove pic0 kstat if pic1 create fails */
if (pic == 1) {
kstat_delete(ac_picN_ksp[0]);
ac_picN_ksp[0] = NULL;
}
return;
}
/*
* when we are storing pcr_masks we need to shift bits
* left by 8 for pic1 events.
*/
if (pic == 1)
pic_shift = 8;
/*
* for each picN event we need to write a kstat record
* (name = EVENT, value.ui64 = PCR_MASK)
*/
/* pcr_mask */
/* event-name */
}
/*
* we add the clear_pic event and mask as the last
* record in the kstat
*/
/* pcr mask */
/* event-name */
}
}
static void
{
int pic;
/* remove "misc" kstat */
}
/* remove "bus" kstat */
}
/*
* if we are the last instance to detach we need to
* remove the picN kstats. We use ac_attachcnt as a
* count of how many instances are still attached. This
* is protected by a mutex.
*/
ac_attachcnt --;
if (ac_attachcnt == 0) {
}
}
}
}
static enum ac_bank_status
{
switch (rst) {
case SYSC_CFGA_RSTATE_EMPTY:
return (StNoMem);
return (StBad);
switch (ost) {
return (StSpare);
return (StActive);
default:
return (StUnknown);
}
default:
return (StUnknown);
}
}
static enum ac_bank_condition
{
switch (cond) {
case SYSC_CFGA_COND_UNKNOWN:
return (ConUnknown);
case SYSC_CFGA_COND_OK:
return (ConOK);
case SYSC_CFGA_COND_FAILING:
return (ConFailing);
case SYSC_CFGA_COND_FAILED:
return (ConFailed);
case SYSC_CFGA_COND_UNUSABLE:
return (ConBad);
default:
return (ConUnknown);
}
}
static int
{
/* Need the NULL check in case kstat is about to be deleted. */
/* this is a read-only kstat. Bail out on a write */
if (rw == KSTAT_WRITE) {
return (EACCES);
} else {
/*
* copy the current state of the hardware into the
* kstat structure.
*/
}
return (0);
}
static int
{
/*
* return the AC counters to hot-plug mode after the
* ac_hot_plug_timeout_interval has expired. We tell
* ac_reset_timeout() whether this is a kstat_read or a
* kstat_write call. If this fails we reject the kstat
* operation.
*/
if (ac_reset_timeout(rw) != 0)
return (-1);
if (rw == KSTAT_WRITE) {
/*
* Write the %pcr value to the softsp->ac_mccr.
* This interface does not support writing to the
* %pic.
*/
} else {
/*
* Read %pcr and %pic register values and write them
* into counters kstat.
*/
/* pcr */
/*
* ac pic register:
* (63:32) = pic1
* (31:00) = pic0
*/
/* pic0 */
/* pic1 */
}
return (0);
}
/*
* Decode the memory state given to us and plug it into the soft state
*/
static void
{
char *propval;
int proplen;
if (GRP_SIZE_IS_SET(memdec)) {
/* determine the memory bank size (in MB) */
} else {
}
/*
* decode the memory bank property. set condition based
* on the values.
*/
} else {
"unknown %smemory state [%s]",
propval);
if (memdec & AC_MEM_VALID) {
} else {
}
}
} else {
/* we don't have the property, deduce the state of memory */
if (memdec & AC_MEM_VALID) {
} else {
/* could be an i/o board... */
}
}
/* we assume that all other bank statuses are NOT valid */
if ((memdec & AC_MEM_VALID) != 0) {
/* register existence in the memloc database */
}
}
}
static void
{
/*
* Downgrade the status of any bank that did not get
* programmed.
*/
(memdec & AC_MEM_VALID) == 0) {
"spare memory bank not valid - it was ",
"firmware. Disabling...");
}
/*
* Log a message about good banks.
*/
"ostate %d condition %d\n",
}
}
/*ARGSUSED*/
static void
{
}
static char *
{
char *type_str;
switch (ostate) {
switch (event) {
type_str = "unconfiguring";
break;
type_str = "unconfigured";
break;
default:
type_str = "unconfigure?";
break;
}
break;
switch (event) {
type_str = "configuring";
break;
type_str = "configured";
break;
default:
type_str = "configure?";
break;
}
break;
default:
type_str = "undefined occupant state";
break;
}
return (type_str);
}
static void
{
switch (event) {
"%s memory bank %d in slot %d",
break;
"%s memory bank %d in slot %d",
break;
"memory bank %d in slot %d is %s",
event));
break;
"memory bank %d in slot %d not %s",
event));
break;
"memory bank %d in slot %d not %s",
event));
break;
default:
"unknown audit of memory bank %d in slot %d",
break;
}
}
static int
{
} else {
base = 0;
npgs = 0;
}
case AC_MEMX_RELOCATE_ALL: {
if (npgs == 0 ||
return (EINVAL);
}
return (EBUSY);
}
}
goto retry;
}
} else {
}
}
int result;
if (result == 0) {
while (npgs-- > 0) {
}
} else {
}
}
}
return (EFAULT);
return (DDI_SUCCESS);
}
default:
return (EINVAL);
}
}
static int
{
(rw == KSTAT_READ)) {
/*
* We are in hot-plug mode. A kstat_read is not
* going to affect this. return 0 to allow the
* kstat_read to continue.
*/
return (0);
(rw == KSTAT_WRITE)) {
/*
* There are no pending timeouts and we have received a
* kstat_write request so we must be transitioning
* from "hot-plug" mode to non "hot-plug" mode.
* Try to lock all boards before allowing the kstat_write.
*/
if (ac_enter_transition() == TRUE)
else {
/* cannot lock boards so fail */
return (-1);
}
/*
* We need to display a Warning about hot-plugging any
* boards. This message is only needed when we are
* transitioning out of "hot-plug" mode.
*/
"hot-plug mode.");
"or power supplies in this system until further notice.");
/*
* There is a pending timeout so we must already be
* in non "hot-plug" mode. It doesn't matter if the
* kstat request is a read or a write.
*
* We need to cancel the existing timeout.
*/
(void) untimeout(ac_hot_plug_timeout);
}
/*
* create a new timeout.
*/
return (0);
}
static void
{
#ifdef lint
#endif /* lint */
(void) fhc_bdlist_lock(-1);
/*
* Foreach ac in the board list we need to
* re-program the pcr into "hot-plug" mode.
* We also program the pic register with the
* bus pause timing
*/
board = fhc_bd_first();
/*
* This board must not have an AC.
* Skip it and move on.
*/
continue;
}
/* program the pcr into hot-plug mode */
/* program the pic with the bus pause time value */
/* get the next board */
}
/*
* It is now safe to start hot-plugging again. We need
* to display a message.
*/
"can be resumed.");
}
/*
* This function will acquire the lock and set the in_transition
* bit for all the slots. If the slots are being used,
* we return FALSE; else set in_transition and return TRUE.
*/
static int
ac_enter_transition(void)
{
/* mutex lock the structure */
(void) fhc_bdlist_lock(-1);
list = fhc_bd_clock();
/* change the in_transition bit */
return (FALSE);
} else {
return (TRUE);
}
}
/*
* clear the in_transition bit for all the slots.
*/
static void
ac_exit_transition(void)
{
list = fhc_bd_clock();
}