blob: 829268c82268c933998bf19ac8498dc17b38f08d [file] [log] [blame]
/*
* 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
* or http://www.opensolaris.org/os/licensing.
* 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.
*/
/*
* bscv.c - multi-threaded lom driver for the Stiletto platform.
*/
/*
* Included files.
*/
#include <sys/note.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/uio.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/stream.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/reboot.h>
#include <sys/modctl.h>
#include <sys/mkdev.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/consdev.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/disp.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/stream.h>
#include <sys/strlog.h>
#include <sys/log.h>
#include <sys/utsname.h>
#include <sys/callb.h>
#include <sys/sysevent.h>
#include <sys/nvpair.h>
#include <sys/sysevent/eventdefs.h>
#include <sys/sysevent/domain.h>
#include <sys/sysevent/env.h>
#include <sys/sysevent/dr.h>
#include <sys/lom_io.h>
#include <sys/bscbus.h>
#include <sys/bscv_impl.h>
/*
* Variables defined here and visible internally only
*/
static void *bscv_statep = NULL;
/*
* Forward declarations
*/
static int bscv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int bscv_attach(dev_info_t *, ddi_attach_cmd_t);
static int bscv_detach(dev_info_t *, ddi_detach_cmd_t);
static int bscv_quiesce(dev_info_t *);
static int bscv_map_regs(bscv_soft_state_t *);
static void bscv_unmap_regs(bscv_soft_state_t *);
static void bscv_map_chan_logical_physical(bscv_soft_state_t *);
static int bscv_open(dev_t *, int, int, cred_t *);
static int bscv_close(dev_t, int, int, cred_t *);
static void bscv_full_stop(bscv_soft_state_t *);
static void bscv_enter(bscv_soft_state_t *);
static int bscv_tryenter(bscv_soft_state_t *ssp);
static void bscv_exit(bscv_soft_state_t *);
#ifdef DEBUG
static int bscv_held(bscv_soft_state_t *);
#endif /* DEBUG */
static void bscv_put8(bscv_soft_state_t *, int, bscv_addr_t, uint8_t);
static void bscv_put16(bscv_soft_state_t *, int, bscv_addr_t, uint16_t);
static void bscv_put32(bscv_soft_state_t *, int, bscv_addr_t, uint32_t);
static uint8_t bscv_get8(bscv_soft_state_t *, int, bscv_addr_t);
static uint16_t bscv_get16(bscv_soft_state_t *, int, bscv_addr_t);
static uint32_t bscv_get32(bscv_soft_state_t *, int, bscv_addr_t);
static void bscv_setclear8(bscv_soft_state_t *, int,
bscv_addr_t, uint8_t, uint8_t);
static void bscv_setclear8_volatile(bscv_soft_state_t *, int,
bscv_addr_t, uint8_t, uint8_t);
static void bscv_rep_rw8(bscv_soft_state_t *, int,
uint8_t *, bscv_addr_t, size_t, uint_t, boolean_t);
static uint8_t bscv_get8_cached(bscv_soft_state_t *, bscv_addr_t);
static uint8_t bscv_get8_locked(bscv_soft_state_t *, int, bscv_addr_t, int *);
static void bscv_rep_get8_locked(bscv_soft_state_t *, int,
uint8_t *, bscv_addr_t, size_t, uint_t, int *);
static boolean_t bscv_faulty(bscv_soft_state_t *);
static void bscv_clear_fault(bscv_soft_state_t *);
static void bscv_set_fault(bscv_soft_state_t *);
static boolean_t bscv_session_error(bscv_soft_state_t *);
static int bscv_retcode(bscv_soft_state_t *);
static int bscv_should_retry(bscv_soft_state_t *);
static void bscv_locked_result(bscv_soft_state_t *, int *);
static void bscv_put8_once(bscv_soft_state_t *, int, bscv_addr_t, uint8_t);
static uint8_t bscv_get8_once(bscv_soft_state_t *, int, bscv_addr_t);
static uint32_t bscv_probe(bscv_soft_state_t *, int, uint32_t *);
static void bscv_resync_comms(bscv_soft_state_t *, int);
static boolean_t bscv_window_setup(bscv_soft_state_t *);
static int bscv_eerw(bscv_soft_state_t *, uint32_t, uint8_t *,
unsigned, boolean_t);
static int bscv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int bscv_ioc_dogstate(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_psustate(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_fanstate(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_fledstate(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_ledstate(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_info(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_mread(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_volts(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_stats(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_temp(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_cons(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_eventlog2(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_info2(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_test(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_mprog2(bscv_soft_state_t *, intptr_t, int);
static int bscv_ioc_mread2(bscv_soft_state_t *, intptr_t, int);
static void bscv_event_daemon(void *);
static void bscv_start_event_daemon(bscv_soft_state_t *);
static int bscv_stop_event_daemon(bscv_soft_state_t *);
static int bscv_pause_event_daemon(bscv_soft_state_t *);
static void bscv_resume_event_daemon(bscv_soft_state_t *);
static void bscv_event_process(bscv_soft_state_t *ssp, boolean_t);
static int bscv_event_validate(bscv_soft_state_t *, uint32_t, uint8_t);
static void bscv_event_process_one(bscv_soft_state_t *, lom_event_t *);
static void bscv_build_eventstring(bscv_soft_state_t *,
lom_event_t *, char *, char *);
static int bscv_level_of_event(lom_event_t *);
static void bscv_status(bscv_soft_state_t *, uint8_t, uint8_t);
char *bscv_get_label(char [][MAX_LOM2_NAME_STR], int, int);
static void bscv_generic_sysevent(bscv_soft_state_t *, char *, char *, char *,
char *, int32_t, char *);
static void bscv_sysevent(bscv_soft_state_t *, lom_event_t *);
static int bscv_prog(bscv_soft_state_t *, intptr_t, int);
static int bscv_prog_image(bscv_soft_state_t *, boolean_t,
uint8_t *, int, uint32_t);
static int bscv_prog_receive_image(bscv_soft_state_t *, lom_prog_t *,
uint8_t *, int);
static void bscv_leave_programming_mode(bscv_soft_state_t *, boolean_t);
static int bscv_prog_stop_lom(bscv_soft_state_t *);
static int bscv_prog_start_lom(bscv_soft_state_t *);
static int bscv_attach_common(bscv_soft_state_t *);
static int bscv_cleanup(bscv_soft_state_t *);
static void bscv_setup_capability(bscv_soft_state_t *);
static int bscv_probe_check(bscv_soft_state_t *);
static void bscv_setup_hostname(bscv_soft_state_t *);
static void bscv_read_hostname(bscv_soft_state_t *, char *);
static void bscv_write_hostname(bscv_soft_state_t *, char *, uint8_t);
static void bscv_setup_static_info(bscv_soft_state_t *);
static uint8_t bscv_read_env_name(bscv_soft_state_t *, uint8_t,
uint8_t, uint8_t, char [][MAX_LOM2_NAME_STR], int);
static void bscv_setup_events(bscv_soft_state_t *);
static void bscv_trace(bscv_soft_state_t *, char, const char *,
const char *, ...);
#ifdef __sparc
static void bscv_idi_init();
static void bscv_idi_fini();
static void bscv_idi_new_instance(dev_info_t *dip);
static void bscv_idi_clear_err();
void bscv_idi_set(struct bscv_idi_info info);
static boolean_t bscv_idi_err();
static boolean_t bscv_nodename_set(struct bscv_idi_info info);
static boolean_t bscv_sig_set(struct bscv_idi_info info);
static boolean_t bscv_wdog_pat(struct bscv_idi_info info);
static boolean_t bscv_wdog_cfg(struct bscv_idi_info info);
static void bscv_write_sig(bscv_soft_state_t *ssp, bscv_sig_t s);
#endif /* __sparc */
static void bscv_setup_watchdog(bscv_soft_state_t *ssp);
static void bscv_write_wdog_cfg(bscv_soft_state_t *,
uint_t, boolean_t, uint8_t);
#if defined(__i386) || defined(__amd64)
static void bscv_inform_bsc(bscv_soft_state_t *, uint32_t);
static void bscv_watchdog_pat_request(void *);
static void bscv_watchdog_cfg_request(bscv_soft_state_t *, uint8_t);
static uint_t bscv_set_watchdog_timer(bscv_soft_state_t *, uint_t);
static void bscv_clear_watchdog_timer(bscv_soft_state_t *);
static boolean_t bscv_panic_callback(void *, int);
static void bscv_watchdog_cyclic_add(bscv_soft_state_t *);
static void bscv_watchdog_cyclic_remove(bscv_soft_state_t *);
static uint8_t wdog_reset_on_timeout = 1;
#define WDOG_ON 1
#define WDOG_OFF 0
#define CLK_WATCHDOG_DEFAULT 10 /* 10 seconds */
#define WATCHDOG_PAT_INTERVAL 1000000000 /* 1 second */
static int bscv_watchdog_enable;
static int bscv_watchdog_available;
static int watchdog_activated;
static uint_t bscv_watchdog_timeout_seconds;
#endif /* __i386 || __amd64 */
#ifdef __sparc
struct bscv_idi_callout bscv_idi_callout_table[] = {
{BSCV_IDI_NODENAME, &bscv_nodename_set },
{BSCV_IDI_SIG, &bscv_sig_set },
{BSCV_IDI_WDOG_PAT, &bscv_wdog_pat },
{BSCV_IDI_WDOG_CFG, &bscv_wdog_cfg },
{BSCV_IDI_NULL, NULL }
};
static struct bscv_idi_callout_mgr bscv_idi_mgr;
#endif /* __sparc */
/*
* Local Definitions
*/
#define STATUS_READ_LIMIT 8 /* Read up to 8 status changes at a time */
#define MYNAME "bscv"
#define BSCV_INST_TO_MINOR(i) (i)
#define BSCV_MINOR_TO_INST(m) (m)
/*
* Strings for daemon event reporting
*/
static char *eventSubsysStrings[] =
{ "", /* 00 */
"Alarm ", /* 01 */
"temperature sensor ", /* 02 */
"overheat sensor ", /* 03 */
"Fan ", /* 04 */
"supply rail ", /* 05 */
"circuit breaker ", /* 06 */
"PSU ", /* 07 */
"user ", /* 08 */
"phonehome ", /* 09; unutilized */
"LOM ", /* 0a */
"host ", /* 0b */
"event log ", /* 0c */
"", /* 0d; EVENT_SUBSYS_EXTRA unutilized */
"LED ", /* 0e */
};
static char *eventTypeStrings[] =
{
"[null event]", /* 00 */
"ON", /* 01 */
"OFF", /* 02 */
"state change", /* 03 */
"power on", /* 04 */
"power off", /* 05 */
"powered off unexpectedly", /* 06 */
"reset unexpectedly", /* 07 */
"booted", /* 08 */
"watchdog enabled", /* 09 */
"watchdog disabled", /* 0a */
"watchdog triggered", /* 0b */
"failed", /* 0c */
"recovered", /* 0d */
"reset", /* 0e */
"XIR reset", /* 0f */
"console selected", /* 10 */
"time reference", /* 11 */
"script failure", /* 12 */
"modem access failure", /* 13 */
"modem dialing failure", /* 14 */
"bad checksum", /* 15 */
"added", /* 16 */
"removed", /* 17 */
"changed", /* 18 */
"login", /* 19 */
"password changed", /* 1a */
"login failed", /* 1b */
"logout", /* 1c */
"flash download", /* 1d */
"data lost", /* 1e */
"device busy", /* 1f */
"fault led state", /* 20 */
"overheat", /* 21 */
"severe overheat", /* 22 */
"no overheat", /* 23 */
"SCC", /* 24 */
"device inaccessible", /* 25 */
"Hostname change", /* 26 */
"CPU signature timeout", /* 27 */
"Bootmode change", /* 28 */
"Watchdog change policy", /* 29 */
"Watchdog change timeout", /* 2a */
};
/*
* These store to mapping between the logical service, e.g. chan_prog for
* programming, and the actual Xbus channel which carries that traffic.
* Any services can be shared on the same channel apart from chan_wdogpat.
*/
static int chan_general; /* General Traffic */
static int chan_wdogpat; /* Watchdog Patting */
static int chan_cpusig; /* CPU signatures */
static int chan_eeprom; /* EEPROM I/O */
static int chan_prog; /* Programming */
/*
* cb_ops structure defining the driver entry points
*/
static struct cb_ops bscv_cb_ops = {
bscv_open, /* open */
bscv_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
bscv_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* prop op */
NULL, /* ! STREAMS */
D_NEW | D_MP /* MT/MP Safe */
};
/*
* dev_ops structure defining autoconfiguration driver autoconfiguration
* routines
*/
static struct dev_ops bscv_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
bscv_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
bscv_attach, /* devo_attach */
bscv_detach, /* devo_detach */
nodev, /* devo_reset */
&bscv_cb_ops, /* devo_cb_ops */
(struct bus_ops *)0, /* devo_bus_ops */
NULL, /* devo_power */
bscv_quiesce, /* devo_quiesce */
};
/*
* module configuration section
*/
#ifdef DEBUG
#define BSCV_VERSION_STRING "bscv driver - Debug"
#else /* DEBUG */
#define BSCV_VERSION_STRING "bscv driver"
#endif /* DEBUG */
static struct modldrv modldrv = {
&mod_driverops,
BSCV_VERSION_STRING,
&bscv_dev_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL
};
#ifdef DEBUG
/* Tracing is enabled if value is non-zero. */
static int bscv_trace_flag = 1;
#define BSCV_TRACE if (bscv_trace_flag != 0) bscv_trace
#else
#define BSCV_TRACE
#endif
/*
* kernel accessible routines. These routines are necessarily global so the
* driver can be loaded, and unloaded successfully
*/
/*
* function - _init
* description - initializes the driver state structure and installs the
* driver module into the kernel
* inputs - none
* outputs - success or failure of module installation
*/
int
_init(void)
{
register int e;
if ((e = ddi_soft_state_init(&bscv_statep,
sizeof (bscv_soft_state_t), 1)) != 0) {
return (e);
}
if ((e = mod_install(&modlinkage)) != 0) {
ddi_soft_state_fini(&bscv_statep);
}
#ifdef __sparc
if (e == 0) bscv_idi_init();
#endif /* __sparc */
return (e);
}
/*
* function - _info
* description - provide information about a kernel loaded module
* inputs - module infomation
* outputs - success or failure of information request
*/
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* function - _fini
* description - removes a module from the kernel and frees the driver soft
* state memory
* inputs - none
* outputs - success or failure of module removal
*/
int
_fini(void)
{
register int e;
if ((e = mod_remove(&modlinkage)) != 0) {
return (e);
}
#ifdef __sparc
bscv_idi_fini();
#endif /* __sparc */
ddi_soft_state_fini(&bscv_statep);
return (e);
}
/*
* function - bscv_getinfo
* description - routine used to provide information on the driver
* inputs - device information structure, command, command arg, storage
* area for the result
* outputs - DDI_SUCCESS or DDI_FAILURE
*/
/*ARGSUSED*/
static int
bscv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
bscv_soft_state_t *ssp;
dev_t dev = (dev_t)arg;
int instance;
int error;
instance = DEVICETOINSTANCE(dev);
switch (cmd) {
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)instance;
error = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2DEVINFO:
ssp = ddi_get_soft_state(bscv_statep, instance);
if (ssp == NULL)
return (DDI_FAILURE);
*result = (void *) ssp->dip;
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
break;
}
return (error);
}
#ifdef __sparc
void
bscv_idi_init()
{
bscv_idi_mgr.valid_inst = (uint32_t)~0; /* No valid instances */
bscv_idi_mgr.tbl = bscv_idi_callout_table;
bscv_idi_mgr.errs = 0;
/*
* Now that all fields are initialized, set the magic flag. This is
* a kind of integrity check for the data structure.
*/
bscv_idi_mgr.magic = BSCV_IDI_CALLOUT_MAGIC;
}
static void
bscv_idi_clear_err()
{
ASSERT(bscv_idi_mgr.magic == BSCV_IDI_CALLOUT_MAGIC);
bscv_idi_mgr.errs = 0;
}
/*
* function - bscv_idi_err
* description - error messaging service which throttles the number of error
* messages to avoid overflowing storage
* inputs - none
* returns - boolean to indicate whether a message should be reported
* side-effects - updates the error number counter
*/
static boolean_t
bscv_idi_err()
{
ASSERT(bscv_idi_mgr.magic == BSCV_IDI_CALLOUT_MAGIC);
bscv_idi_mgr.errs++;
if (bscv_idi_mgr.errs++ < BSCV_IDI_ERR_MSG_THRESHOLD)
return (B_TRUE);
return (B_FALSE);
}
void
bscv_idi_new_instance(dev_info_t *dip)
{
ASSERT(bscv_idi_mgr.magic == BSCV_IDI_CALLOUT_MAGIC);
/*
* We don't care how many instances we have, or their value, so long
* as we have at least one valid value. This is so service routines
* can get any required locks via a soft state pointer.
*/
if (bscv_idi_mgr.valid_inst == (uint32_t)~0) {
bscv_idi_mgr.valid_inst = ddi_get_instance(dip);
}
}
void
bscv_idi_fini()
{
bscv_idi_mgr.valid_inst = (uint32_t)~0; /* No valid instances */
bscv_idi_mgr.tbl = NULL;
}
#endif /* __sparc */
/*
* function - bscv_attach
* description - this routine is responsible for setting aside memory for the
* driver data structures, initialising the mutexes and creating
* the device minor nodes. Additionally, this routine calls the
* the callback routine.
* inputs - device information structure, DDI_ATTACH command
* outputs - DDI_SUCCESS or DDI_FAILURE
*/
int
bscv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
bscv_soft_state_t *ssp;
int instance;
switch (cmd) {
case DDI_ATTACH:
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(bscv_statep, instance) !=
DDI_SUCCESS) {
return (DDI_FAILURE);
}
ssp = ddi_get_soft_state(bscv_statep, instance);
ssp->progress = 0;
ssp->dip = dip;
ssp->instance = instance;
ssp->event_waiting = B_FALSE;
ssp->status_change = B_FALSE;
ssp->nodename_change = B_FALSE;
ssp->cap0 = 0;
ssp->cap1 = 0;
ssp->cap2 = 0;
ssp->prog_mode_only = B_FALSE;
ssp->programming = B_FALSE;
ssp->cssp_prog = B_FALSE;
ssp->task_flags = 0;
ssp->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "debug", 0);
ssp->majornum = ddi_driver_major(dip);
ssp->minornum = BSCV_INST_TO_MINOR(instance);
#if defined(__i386) || defined(__amd64)
ssp->last_nodename[0] = '\0';
#endif /* __i386 || __amd64 */
/*
* initialise the mutexes
*/
mutex_init(&ssp->cmd_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ssp->task_mu, NULL, MUTEX_DRIVER, NULL);
cv_init(&ssp->task_cv, NULL, CV_DRIVER, NULL);
cv_init(&ssp->task_evnt_cv, NULL, CV_DRIVER, NULL);
mutex_init(&ssp->prog_mu, NULL, MUTEX_DRIVER, NULL);
ssp->progress |= BSCV_LOCKS;
BSCV_TRACE(ssp, 'A', "bscv_attach",
"bscv_attach: mutexes and condition vars initialised");
/* Map in physical communication channels */
if (bscv_map_regs(ssp) != DDI_SUCCESS) {
(void) bscv_cleanup(ssp);
return (DDI_FAILURE);
}
ssp->progress |= BSCV_MAPPED_REGS;
/* Associate logical channels to physical channels */
bscv_map_chan_logical_physical(ssp);
bscv_enter(ssp);
bscv_leave_programming_mode(ssp, B_FALSE);
if (bscv_attach_common(ssp) == DDI_FAILURE) {
bscv_exit(ssp);
(void) bscv_cleanup(ssp);
return (DDI_FAILURE);
}
#ifdef __sparc
/*
* At this point the inter-driver-interface is made available.
* The IDI uses the event thread service which
* bscv_attach_common() sets up.
*/
bscv_idi_new_instance(dip);
#endif /* __sparc */
bscv_exit(ssp);
/*
* now create the minor nodes
*/
if (ddi_create_minor_node(ssp->dip, "lom", S_IFCHR,
BSCV_INST_TO_MINOR(instance),
DDI_PSEUDO, 0) != DDI_SUCCESS) {
(void) bscv_cleanup(ssp);
return (DDI_FAILURE);
}
BSCV_TRACE(ssp, 'A', "bscv_attach",
"bscv_attach: device minor nodes created");
ssp->progress |= BSCV_NODES;
if (!ssp->prog_mode_only)
bscv_start_event_daemon(ssp);
#if defined(__i386) || defined(__amd64)
bscv_watchdog_enable = 1;
bscv_watchdog_available = 1;
watchdog_activated = 0;
bscv_watchdog_timeout_seconds = CLK_WATCHDOG_DEFAULT;
if (bscv_watchdog_enable && (boothowto & RB_DEBUG)) {
bscv_watchdog_available = 0;
cmn_err(CE_WARN, "bscv: kernel debugger "
"detected: hardware watchdog disabled");
}
/*
* Before we enable the watchdog - register the panic
* callback so that we get called to stop the watchdog
* in the case of a panic.
*/
ssp->callb_id = callb_add(bscv_panic_callback,
(void *)ssp, CB_CL_PANIC, "");
if (bscv_watchdog_available) {
(void) bscv_set_watchdog_timer(ssp,
CLK_WATCHDOG_DEFAULT);
bscv_enter(ssp);
bscv_setup_watchdog(ssp); /* starts cyclic callback */
bscv_exit(ssp);
}
#endif /* __i386 || __amd64 */
ddi_report_dev(dip);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/*
* function - bscv_detach
* description - routine that prepares a module to be unloaded. It undoes all
* the work done by the bscv_attach)() routine. This is
* facilitated by the use of the progress indicator
* inputs - device information structure, DDI_DETACH command
* outputs - DDI_SUCCESS or DDI_FAILURE
*/
/*ARGSUSED*/
static int
bscv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
return (DDI_FAILURE);
}
/*
* quiesce(9E) entry point.
*
* This function is called when the system is single-threaded at high
* PIL with preemption disabled. Therefore, this function must not be
* blocked.
*
* This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
* DDI_FAILURE indicates an error condition and should almost never happen.
*/
static int
bscv_quiesce(dev_info_t *dip)
{
bscv_soft_state_t *ssp;
int instance;
instance = ddi_get_instance(dip);
ssp = ddi_get_soft_state(bscv_statep, instance);
if (ssp == NULL) {
return (DDI_FAILURE);
}
#ifdef DEBUG
/* Disable tracing, as we are executing at High-Interrupt level */
bscv_trace_flag = 0;
#endif
/* quiesce the device */
bscv_full_stop(ssp);
return (DDI_SUCCESS);
}
/*
* cb_ops routines
*/
/*
* function - bscv_open
* description - routine to provide association between user fd and device
* minor number. This routine is necessarily simple since a
* read/write interface is not provided. Additionally, the
* driver does not enforce exclusive access (FEXCL) or
* non-blocking during an open (FNDELAY). Deferred attach is
* supported.
* inputs - device number, flag specifying open type, device type,
* permissions
* outputs - success or failure of operation
*/
/*ARGSUSED*/
static int
bscv_open(dev_t *devp, int flag, int otype, cred_t *cred)
{
bscv_soft_state_t *ssp;
int instance;
instance = DEVICETOINSTANCE(*devp);
ssp = ddi_get_soft_state(bscv_statep, instance);
if (ssp == NULL) {
return (ENXIO); /* not attached yet */
}
BSCV_TRACE(ssp, 'O', "bscv_open", "instance 0x%x", instance);
if (otype != OTYP_CHR) {
return (EINVAL);
}
return (0);
}
/*
* function - bscv_close
* description - routine to perform the final close on the device. As per the
* open routine, neither FEXCL or FNDELAY accesses are enforced
* by the driver.
* inputs - device number,flag specifying open type, device type,
* permissions
* outputs - success or failure of operation
*/
/*ARGSUSED1*/
static int
bscv_close(dev_t dev, int flag, int otype, cred_t *cred)
{
bscv_soft_state_t *ssp;
int instance;
instance = DEVICETOINSTANCE(dev);
ssp = ddi_get_soft_state(bscv_statep, instance);
if (ssp == NULL) {
return (ENXIO);
}
BSCV_TRACE(ssp, 'O', "bscv_close", "instance 0x%x", instance);
return (0);
}
static int
bscv_map_regs(bscv_soft_state_t *ssp)
{
int i;
int retval;
int *props;
unsigned int nelements;
ASSERT(ssp);
ssp->nchannels = 0;
/*
* Work out how many channels are available by looking at the number
* of elements of the regs property array.
*/
retval = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ssp->dip,
DDI_PROP_DONTPASS, "reg", &props, &nelements);
/* We don't need props anymore. Free memory if it was allocated */
if (retval == DDI_PROP_SUCCESS)
ddi_prop_free(props);
/* Check for sanity of nelements */
if (retval != DDI_PROP_SUCCESS) {
BSCV_TRACE(ssp, 'A', "bscv_map_regs", "lookup reg returned"
" 0x%x", retval);
goto cleanup_exit;
} else if (nelements % LOMBUS_REGSPEC_SIZE != 0) {
BSCV_TRACE(ssp, 'A', "bscv_map_regs", "nelements %d not"
" a multiple of %d", nelements, LOMBUS_REGSPEC_SIZE);
goto cleanup_exit;
} else if (nelements > BSCV_MAXCHANNELS * LOMBUS_REGSPEC_SIZE) {
BSCV_TRACE(ssp, 'A', "bscv_map_regs", "nelements %d too large"
", probably a misconfiguration", nelements);
goto cleanup_exit;
} else if (nelements < BSCV_MINCHANNELS * LOMBUS_REGSPEC_SIZE) {
BSCV_TRACE(ssp, 'A', "bscv_map_regs", "nelements %d too small"
", need to have at least a general and a wdog channel",
nelements);
goto cleanup_exit;
}
ssp->nchannels = nelements / LOMBUS_REGSPEC_SIZE;
ssp->attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
ssp->attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
ssp->attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
for (i = 0; i < ssp->nchannels; i++) {
retval = ddi_regs_map_setup(ssp->dip, i,
(caddr_t *)&ssp->channel[i].regs,
0, 0, &ssp->attr, &ssp->channel[i].handle);
if (retval != DDI_SUCCESS) {
BSCV_TRACE(ssp, 'A', "bscv_map_regs", "map failure"
" 0x%x on space %d", retval, i);
/* Rewind all current mappings - avoiding failed one */
i--;
for (; i >= 0; i--) {
ddi_regs_map_free(&ssp->channel[i].handle);
}
goto cleanup_exit;
}
}
return (DDI_SUCCESS);
cleanup_exit:
/*
* It is important to set nchannels to 0 even if, say, only one of
* the two required handles was mapped. If we cannot achieve our
* minimum config its not safe to do any IO; this keeps our failure
* mode handling simpler.
*/
ssp->nchannels = 0;
return (DDI_FAILURE);
}
static void
bscv_unmap_regs(bscv_soft_state_t *ssp)
{
int i;
ASSERT(ssp);
for (i = 0; i < ssp->nchannels; i++) {
ddi_regs_map_free(&ssp->channel[i].handle);
}
}
/*
* Map logical services onto physical XBus channels.
*/
static void
bscv_map_chan_logical_physical(bscv_soft_state_t *ssp)
{
ASSERT(ssp);
/*
* We can assert that there will always be at least two channels,
* to allow watchdog pats to be segregated from all other traffic.
*/
chan_general = 0;
chan_wdogpat = 1;
/*
* By default move all other services onto the generic channel unless
* the hardware supports additional channels.
*/
chan_cpusig = chan_eeprom = chan_prog = chan_general;
if (ssp->nchannels > 2)
chan_cpusig = 2;
if (ssp->nchannels > 3)
chan_eeprom = 3;
if (ssp->nchannels > 4)
chan_prog = 4;
}
/*
* function - bscv_full_stop
* description - gracefully shut the lom down during panic or reboot.
* Disables the watchdog and sets up serial event reporting.
* inputs - soft state pointer
* outputs - none
*/
void
bscv_full_stop(bscv_soft_state_t *ssp)
{
uint8_t bits2set = 0;
uint8_t bits2clear = 0;
int obtained_lock;
BSCV_TRACE(ssp, 'W', "bscv_full_stop",
"turning off watchdog");
/*
* Obtain the softstate lock only if it is not already owned,
* as this function can be called from a High-level interrupt
* context. As a result, our thread cannot sleep.
* At end of function, our thread releases the lock only if
* it acquired the lock.
*/
obtained_lock = (bscv_tryenter(ssp) != 0);
#if defined(__i386) || defined(__amd64)
if (ddi_in_panic()) {
bscv_inform_bsc(ssp, BSC_INFORM_PANIC);
} else {
bscv_inform_bsc(ssp, BSC_INFORM_OFFLINE);
}
#endif /* __i386 || __amd64 */
/* set serial event reporting */
switch (ssp->serial_reporting) {
case LOM_SER_EVENTS_ON:
case LOM_SER_EVENTS_DEF:
/* Make sure serial event reporting is on */
bits2clear = EBUS_ALARM_NOEVENTS;
break;
case LOM_SER_EVENTS_OFF:
/* Make sure serial event reporting is on */
bits2set = EBUS_ALARM_NOEVENTS;
break;
default:
break;
}
bscv_setclear8_volatile(ssp, chan_general,
EBUS_IDX_ALARM, bits2set, bits2clear);
/* Do not free the lock if our thread did not obtain it. */
if (obtained_lock != 0) {
bscv_exit(ssp);
}
}
/*
* LOM I/O routines.
*
* locking
*
* Two sets of routines are provided:
* normal - must be called after acquiring an appropriate lock.
* locked - perform all the locking required and return any error
* code in the supplied 'res' argument. If there is no
* error 'res' is not changed.
* The locked routines are designed for use in ioctl commands where
* only a single operation needs to be performed and the overhead of
* locking and result checking adds significantly to code complexity.
*
* locking primitives
*
* bscv_enter() - acquires an I/O lock for the calling thread.
* bscv_tryenter() - conditionally acquires an I/O lock for calling thread.
* bscv_exit() - releases an I/O lock acquired by bscv_enter().
* bscv_held() - used to assert ownership of an I/O lock.
*
* normal I/O routines
*
* Note bscv_{put|get}{16|32} routines are big-endian. This assumes that
* the firmware works that way too.
*
* bscv_put8(), bscv_put16, bscv_put32 - write values to the LOM
* and handle any retries if necessary.
* 16 and 32 bit values are big-endian.
* bscv_get8(), bscv_get16, bscv_get32 - read values from the LOM
* and handle any retries if necessary.
* 16 and 32 bit values are big-endian.
* bscv_setclear8() - set or clear the specified bits in the register
* at the supplied address.
* bscv_setclear8_volatile() - set or clear the specified bits in the
* register at the supplied address. If the lom reports
* that the registers has changed since the last read
* re-read and apply the set or clear to the new bits.
* bscv_get8_cached() - Return a cached register value (addr < 0x80).
* Does not access the hardware. A read of the hardware
* automatically updates this cache.
*
* locked I/O routines
*
* bscv_get8_locked(), bscv_rep_get8_locked().
*
* Call the indicated function from above, but wrapping it with
* bscv_enter()/bscv_exit().
*
*
* Fault management
*
* LOM communications fault are grouped into three categories:
* 1) Faulty - the LOM is not responding and no attempt to communicate
* with it should be made.
* 2) Transient fault - something which might recover after a retry
* but which doesn't affect our ability to perform other
* commands.
* 3) Command error - an inappropriate command was executed. A retry
* will not fix it but the command failed.
*
* The current implementation of the bscv driver is not very good at
* noticing command errors due to the structure of the original code
* that it is based on. It is possible to extend the driver to do this
* and would probably involve having a concept of a "session error"
* which is less severe than a fault but means that a sequence of
* commands had some fault which cannot be recovered.
*
*
* faults
*
* bscv_faulty() - returns B_TRUE if the LOM (communications) have been
* declared faulty.
* bscv_clear_fault() - marks the LOM as not faulty.
* bscv_set_fault() - marks the LOM as being faulty.
*
* bscv_clear_fault and bscv_set_fault should generally not be called
* directly.
*
* command errors/transient faults
*
* bscv_retcode() - returns the actual error code of the last operation.
* bscv_should_retry() - determines if last operation may suceed if
* retried.
* bscv_locked_result() - Set the result of a locked register access.
*
* low level I/O primitives
*
* These are generally not called directly. These perform a single
* access to the LOM device. They do not handle retries.
*
* bscv_put8_once()
* bscv_get8_once()
* bscv_probe() - perform a probe (NOP) operation to check out lom comms.
* bscv_resync_comms() - resynchronise communications after a transient fault.
*/
static void
bscv_enter(bscv_soft_state_t *ssp)
{
BSCV_TRACE(ssp, '@', "bscv_enter", "");
mutex_enter(&ssp->cmd_mutex);
ssp->had_session_error = B_FALSE;
}
static int
bscv_tryenter(bscv_soft_state_t *ssp)
{
int rv;
BSCV_TRACE(ssp, '@', "bscv_tryenter", "");
if ((rv = mutex_tryenter(&ssp->cmd_mutex)) != 0) {
ssp->had_session_error = B_FALSE;
}
return (rv);
}
static void
bscv_exit(bscv_soft_state_t *ssp)
{
mutex_exit(&ssp->cmd_mutex);
BSCV_TRACE(ssp, '@', "bscv_exit", "");
}
#ifdef DEBUG
static int
bscv_held(bscv_soft_state_t *ssp)
{
return (mutex_owned(&ssp->cmd_mutex));
}
#endif /* DEBUG */
static void
bscv_put8(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, uint8_t val)
{
boolean_t needretry;
int num_failures;
ASSERT(bscv_held(ssp));
if (bscv_faulty(ssp)) {
return;
}
BSCV_TRACE(ssp, '@', "bscv_put8",
"addr 0x%x.%02x <= 0x%02x", addr >> 8, addr & 0xff, val);
for (num_failures = 0;
num_failures < BSC_FAILURE_RETRY_LIMIT;
num_failures++) {
bscv_put8_once(ssp, chan, addr, val);
needretry = bscv_should_retry(ssp);
if (!needretry) {
break;
}
}
if (ssp->command_error != 0) {
ssp->had_session_error = B_TRUE;
}
if (needretry) {
/* Failure - we ran out of retries */
cmn_err(CE_WARN, "bscv_put8: addr 0x%x.%02x retried "
"write %d times, giving up",
addr >> 8, addr & 0xff, num_failures);
bscv_set_fault(ssp);
} else if (num_failures > 0) {
BSCV_TRACE(ssp, 'R', "bscv_put8",
"addr 0x%x.%02x retried write %d times, succeeded",
addr >> 8, addr & 0xff, num_failures);
}
}
static void
bscv_put16(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, uint16_t val)
{
ASSERT(bscv_held(ssp));
BSCV_TRACE(ssp, '@', "bscv_put16",
"addr 0x%x.%02x <= %04x", addr >> 8, addr & 0xff, val);
bscv_put8(ssp, chan, addr, val >> 8);
bscv_put8(ssp, chan, addr + 1, val & 0xff);
}
static void
bscv_put32(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, uint32_t val)
{
ASSERT(bscv_held(ssp));
BSCV_TRACE(ssp, '@', "bscv_put32",
"addr 0x%x.%02x <= %08x", addr >> 8, addr & 0xff, val);
bscv_put8(ssp, chan, addr, (val >> 24) & 0xff);
bscv_put8(ssp, chan, addr + 1, (val >> 16) & 0xff);
bscv_put8(ssp, chan, addr + 2, (val >> 8) & 0xff);
bscv_put8(ssp, chan, addr + 3, val & 0xff);
}
static uint8_t
bscv_get8(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr)
{
uint8_t retval;
boolean_t needretry;
int num_failures;
ASSERT(bscv_held(ssp));
if (bscv_faulty(ssp)) {
return (0);
}
for (num_failures = 0;
num_failures < BSC_FAILURE_RETRY_LIMIT;
num_failures++) {
retval = bscv_get8_once(ssp, chan, addr);
needretry = bscv_should_retry(ssp);
if (!needretry) {
break;
}
}
if (ssp->command_error != 0) {
ssp->had_session_error = B_TRUE;
}
if (needretry) {
/* Failure */
cmn_err(CE_WARN, "bscv_get8: addr 0x%x.%02x retried "
"read %d times, giving up",
addr >> 8, addr & 0xff, num_failures);
bscv_set_fault(ssp);
} else if (num_failures > 0) {
BSCV_TRACE(ssp, 'R', "bscv_get8",
"addr 0x%x.%02x retried read %d times, succeeded",
addr >> 8, addr & 0xff, num_failures);
}
BSCV_TRACE(ssp, '@', "bscv_get8",
"addr 0x%x.%02x => %02x", addr >> 8, addr & 0xff, retval);
return (retval);
}
static uint16_t
bscv_get16(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr)
{
uint16_t retval;
ASSERT(bscv_held(ssp));
retval = bscv_get8(ssp, chan, addr) << 8;
retval |= bscv_get8(ssp, chan, addr + 1);
BSCV_TRACE(ssp, '@', "bscv_get16",
"addr 0x%x.%02x => %04x", addr >> 8, addr & 0xff, retval);
return (retval);
}
static uint32_t
bscv_get32(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr)
{
uint32_t retval;
ASSERT(bscv_held(ssp));
retval = bscv_get8(ssp, chan, addr) << 24;
retval |= bscv_get8(ssp, chan, addr + 1) << 16;
retval |= bscv_get8(ssp, chan, addr + 2) << 8;
retval |= bscv_get8(ssp, chan, addr + 3);
BSCV_TRACE(ssp, '@', "bscv_get32",
"addr 0x%x.%02x => %08x", addr >> 8, addr & 0xff, retval);
return (retval);
}
static void
bscv_setclear8(bscv_soft_state_t *ssp, int chan,
bscv_addr_t addr, uint8_t set, uint8_t clear)
{
uint8_t val;
ASSERT(bscv_held(ssp));
ASSERT(addr < BSC_ADDR_CACHE_LIMIT);
val = ssp->lom_regs[addr] | set;
val &= ~clear;
BSCV_TRACE(ssp, '@', "bscv_setclear8",
"addr 0x%x.%02x, set %02x, clear %02x => %02x",
addr >> 8, addr & 0xff,
set, clear, val);
bscv_put8(ssp, chan, addr, val);
}
static void
bscv_setclear8_volatile(bscv_soft_state_t *ssp, int chan,
bscv_addr_t addr, uint8_t set, uint8_t clear)
{
uint8_t val;
boolean_t needretry;
int num_failures;
ASSERT(bscv_held(ssp));
ASSERT(addr < BSC_ADDR_CACHE_LIMIT);
if (bscv_faulty(ssp)) {
return;
}
BSCV_TRACE(ssp, '@', "bscv_setclear8_volatile",
"addr 0x%x.%02x => set %02x clear %02x",
addr >> 8, addr & 0xff, set, clear);
val = bscv_get8_cached(ssp, addr);
for (num_failures = 0;
num_failures < BSC_FAILURE_RETRY_LIMIT;
num_failures++) {
val |= set;
val &= ~clear;
bscv_put8_once(ssp, chan, addr, val);
if (ssp->command_error == EBUS_ERROR_STALEDATA) {
/* Re-read the stale register from the lom */
val = bscv_get8_once(ssp, chan, addr);
needretry = 1;
} else {
needretry = bscv_should_retry(ssp);
if (!needretry) {
break;
}
}
}
if (ssp->command_error != 0) {
ssp->had_session_error = B_TRUE;
}
if (needretry) {
/* Failure */
cmn_err(CE_WARN, "bscv_setclear8_volatile: addr 0x%x.%02x "
"retried write %d times, giving up",
addr >> 8, addr & 0xff, num_failures);
if (ssp->command_error != EBUS_ERROR_STALEDATA) {
bscv_set_fault(ssp);
}
} else if (num_failures > 0) {
BSCV_TRACE(ssp, 'R', "bscv_setclear8_volatile",
"addr 0x%x.%02x retried write %d times, succeeded",
addr >> 8, addr & 0xff, num_failures);
}
}
static void
bscv_rep_rw8(bscv_soft_state_t *ssp, int chan, uint8_t *host_addr,
bscv_addr_t dev_addr, size_t repcount, uint_t flags,
boolean_t is_write)
{
size_t inc;
ASSERT(bscv_held(ssp));
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc) {
if (flags & DDI_DEV_AUTOINCR) {
if (is_write) {
bscv_put8(ssp, chan, dev_addr, *host_addr++);
} else {
*host_addr++ = bscv_get8(ssp, chan, dev_addr);
}
} else {
if (is_write) {
bscv_put8_once(ssp, chan,
dev_addr, *host_addr++);
} else {
*host_addr++ = bscv_get8_once(ssp, chan,
dev_addr);
}
/* We need this because _once routines don't do it */
if (ssp->command_error != 0) {
ssp->had_session_error = B_TRUE;
}
}
if (bscv_faulty(ssp) || bscv_session_error(ssp)) {
/*
* No retry here. If we were AUTOINCR then get/put
* will have retried. For NO_AUTOINCR we cannot retry
* because the data would be corrupted.
*/
break;
}
}
}
static uint8_t
bscv_get8_cached(bscv_soft_state_t *ssp, bscv_addr_t addr)
{
ASSERT(addr < BSC_ADDR_CACHE_LIMIT);
/* Can be called with or without the lock held */
return (ssp->lom_regs[addr]);
}
static uint8_t
bscv_get8_locked(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, int *res)
{
uint8_t retval;
ASSERT(addr < BSC_ADDR_CACHE_LIMIT);
bscv_enter(ssp);
retval = bscv_get8(ssp, chan, addr);
bscv_locked_result(ssp, res);
bscv_exit(ssp);
BSCV_TRACE(ssp, '@', "bscv_get8_locked",
"addr 0x%x.%02x => %02x", addr >> 8, addr & 0xff, retval);
return (retval);
}
static void
bscv_rep_get8_locked(bscv_soft_state_t *ssp, int chan, uint8_t *host_addr,
bscv_addr_t dev_addr, size_t repcount, uint_t flags, int *res)
{
bscv_enter(ssp);
bscv_rep_rw8(ssp, chan, host_addr, dev_addr, repcount,
flags, B_FALSE /* read */);
bscv_locked_result(ssp, res);
bscv_exit(ssp);
}
static boolean_t
bscv_faulty(bscv_soft_state_t *ssp)
{
ASSERT(bscv_held(ssp));
return (ssp->had_fault);
}
static void
bscv_clear_fault(bscv_soft_state_t *ssp)
{
ASSERT(bscv_held(ssp));
BSCV_TRACE(ssp, 'J', "bscv_clear_fault", "clearing fault flag");
ssp->had_fault = B_FALSE;
ssp->had_session_error = B_FALSE;
}
static void
bscv_set_fault(bscv_soft_state_t *ssp)
{
ASSERT(bscv_held(ssp));
BSCV_TRACE(ssp, 'J', "bscv_set_fault", "setting fault flag");
ssp->had_fault = B_TRUE;
}
static boolean_t
bscv_session_error(bscv_soft_state_t *ssp)
{
ASSERT(bscv_held(ssp));
return (ssp->had_session_error);
}
static int
bscv_retcode(bscv_soft_state_t *ssp)
{
BSCV_TRACE(ssp, '@', "bscv_retcode",
"code 0x%x", ssp->command_error);
return (ssp->command_error);
}
static int
bscv_should_retry(bscv_soft_state_t *ssp)
{
if ((ssp->command_error == EBUS_ERROR_DEVICEFAIL) ||
(ssp->command_error >= LOMBUS_ERR_BASE)) {
/* This command is due to an I/O fault - retry might fix */
return (1);
} else {
/*
* The command itself was bad - there is no point in fixing
* Note. Whatever happens we should know that if we were
* doing EBUS_IDX_SELFTEST0..EBUS_IDX_SELFTEST7 and we
* had 0x80 set then this is a test error not a retry
* error.
*/
return (0);
}
}
static void
bscv_locked_result(bscv_soft_state_t *ssp, int *res)
{
if (bscv_faulty(ssp) || (bscv_retcode(ssp) != 0)) {
*res = EIO;
}
}
static void
bscv_put8_once(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, uint8_t val)
{
uint32_t fault;
ASSERT(bscv_held(ssp));
ssp->command_error = 0;
if (bscv_faulty(ssp)) {
/* Bail out things are not working */
return;
} else if (ssp->nchannels == 0) {
/* Didn't manage to map handles so ddi_{get,put}* broken */
BSCV_TRACE(ssp, '@', "bscv_put8_once",
"nchannels is 0x0 so cannot do IO");
return;
}
/* Clear any pending fault */
ddi_put32(ssp->channel[chan].handle,
(uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG), 0);
/* Do the access and get fault code - may take a long time */
ddi_put8(ssp->channel[chan].handle,
&ssp->channel[chan].regs[addr], val);
fault = ddi_get32(ssp->channel[chan].handle,
(uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG));
ssp->command_error = fault;
if (fault == 0) {
/* Things were ok - update cache entry */
if (addr < BSC_ADDR_CACHE_LIMIT) {
/* Store cacheable entries */
ssp->lom_regs[addr] = val;
}
} else if (fault >= LOMBUS_ERR_BASE) {
/* lombus problem - do a resync session */
cmn_err(CE_WARN, "!bscv_put8_once: Had comms fault "
"for address 0x%x.%02x - data 0x%x, fault 0x%x",
addr >> 8, addr & 0xff, val, fault);
/* Attempt to resync with the lom */
bscv_resync_comms(ssp, chan);
/*
* Note: we do not set fault status here. That
* is done if our caller decides to give up talking to
* the lom. The observant might notice that this means
* that if we mend things on the last attempt we still
* get the fault set - we just live with that!
*/
}
BSCV_TRACE(ssp, '@', "bscv_put8_once",
"addr 0x%x.%02x <= 0x%02x", addr >> 8, addr & 0xff, val);
}
static uint8_t
bscv_get8_once(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr)
{
uint8_t val;
uint32_t fault;
ASSERT(bscv_held(ssp));
ssp->command_error = 0;
if (bscv_faulty(ssp)) {
/* Bail out things are not working */
return (0xff);
} else if (ssp->nchannels == 0) {
/* Didn't manage to map handles so ddi_{get,put}* broken */
BSCV_TRACE(ssp, '@', "bscv_get8_once",
"nchannels is 0x0 so cannot do IO");
return (0xff);
}
/* Clear any pending fault */
ddi_put32(ssp->channel[chan].handle,
(uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG), 0);
/* Do the access and get fault code - may take a long time */
val = ddi_get8(ssp->channel[chan].handle,
&ssp->channel[chan].regs[addr]);
fault = ddi_get32(ssp->channel[chan].handle,
(uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG));
ssp->command_error = fault;
if (fault >= LOMBUS_ERR_BASE) {
/* lombus problem - do a resync session */
cmn_err(CE_WARN, "!bscv_get8_once: Had comms fault "
"for address 0x%x.%02x - data 0x%x, fault 0x%x",
addr >> 8, addr & 0xff, val, fault);
/* Attempt to resync with the lom */
bscv_resync_comms(ssp, chan);
/*
* Note: we do not set fault status here. That
* is done if our caller decides to give up talking to
* the lom. The observant might notice that this means
* that if we mend things on the last attempt we still
* get the fault set - we just live with that!
*/
}
/*
* FIXME - should report error if you get
* EBUS_ERROR_DEVICEFAIL reported from the BSC. That gets
* logged as a failure in bscv_should_retry and may contribute
* to a permanent failure. Reference issues seen by Mitac.
*/
if (!bscv_faulty(ssp)) {
if (addr < BSC_ADDR_CACHE_LIMIT) {
/* Store cacheable entries */
ssp->lom_regs[addr] = val;
}
}
BSCV_TRACE(ssp, '@', "bscv_get8_once",
"addr 0x%x.%02x => 0x%02x", addr >> 8, addr & 0xff, val);
return (val);
}
static uint32_t
bscv_probe(bscv_soft_state_t *ssp, int chan, uint32_t *fault)
{
uint32_t async_reg;
if (ssp->nchannels == 0) {
/*
* Failed to map handles, so cannot do any IO. Set the
* fault indicator and return a dummy value.
*/
BSCV_TRACE(ssp, '@', "bscv_probe",
"nchannels is 0x0 so cannot do any IO");
*fault = LOMBUS_ERR_REG_NUM;
return ((~(int8_t)0));
}
/* Clear faults */
ddi_put32(ssp->channel[chan].handle,
(uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG), 0);
/* Probe and Check faults */
*fault = ddi_get32(ssp->channel[chan].handle,
(uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_PROBE_REG));
/* Read status */
async_reg = ddi_get32(ssp->channel[chan].handle,
(uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_ASYNC_REG));
BSCV_TRACE(ssp, '@', "bscv_probe",
"async status 0x%x, fault 0x%x", async_reg, *fault);
return (async_reg);
}
static void
bscv_resync_comms(bscv_soft_state_t *ssp, int chan)
{
int try;
uint32_t command_error = ssp->command_error;
uint32_t fault = 0;
if (ssp->nchannels == 0) {
/*
* Didn't manage to map handles so ddi_{get,put}* broken.
* Therefore, there is no way to resync comms.
*/
BSCV_TRACE(ssp, '@', "bscv_resync_comms",
"nchannels is 0x0 so not possible to resync comms");
return;
}
if (command_error >= LOMBUS_ERR_BASE &&
command_error != LOMBUS_ERR_REG_NUM &&
command_error != LOMBUS_ERR_REG_SIZE &&
command_error != LOMBUS_ERR_TIMEOUT) {
/* Resync here to make sure that the lom is talking */
cmn_err(CE_WARN, "!bscv_resync_comms: "
"Attempting comms resync after comms fault 0x%x",
command_error);
for (try = 1; try <= 8; try++) {
/* Probe */
fault = ddi_get32(ssp->channel[chan].handle,
(uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0,
LOMBUS_PROBE_REG));
if (fault == 0) {
break;
} else {
cmn_err(CE_WARN, "!bscv_resync_comms: "
"comms resync (probing) - try 0x%x "
"had fault 0x%x", try, fault);
}
}
if (fault != 0) {
cmn_err(CE_WARN, "!bscv_resync_comms: "
"Failed to resync comms - giving up");
ssp->bad_resync++;
} else {
cmn_err(CE_WARN, "!bscv_resync_comms: "
"resync comms after 0x%x tries", try);
ssp->bad_resync = 0;
}
}
}
/*
* LOMLite configuration/event eeprom access routines
*
* bscv_window_setup() - Read/Sanity check the eeprom parameters.
* This must be called prior to calling bscv_eerw().
* bscv_eerw() - Read/write data from/to the eeprom.
*/
/*
* function - bscv_window_setup
* description - this routine reads the eeprom parameters and sanity
* checks them to ensure that the lom is talking sense.
* inputs - soft state ptr
* outputs - B_TRUE if the eeprom is ok, B_FALSE if the eeprom is not OK.
*/
static boolean_t
bscv_window_setup(bscv_soft_state_t *ssp)
{
ASSERT(bscv_held(ssp));
if (ssp->eeinfo_valid) {
/* Already have good cached values */
return (ssp->eeinfo_valid);
}
ssp->eeprom_size =
bscv_get8(ssp, chan_general, EBUS_IDX_EEPROM_SIZE_KB) * 1024;
ssp->eventlog_start = bscv_get16(ssp, chan_general,
EBUS_IDX_LOG_START_HI);
/*
* The log does not run to the end of the EEPROM because it is a
* logical partition. The last 8K partition is reserved for FRUID
* usage.
*/
ssp->eventlog_size = EBUS_LOG_END - ssp->eventlog_start;
BSCV_TRACE(ssp, 'I', "bscv_window_setup", "eeprom size 0x%x log_start"
" 0x%x log_size 0x%x", ssp->eeprom_size, ssp->eventlog_start,
ssp->eventlog_size);
if (bscv_faulty(ssp) || bscv_session_error(ssp)) {
ssp->eeinfo_valid = B_FALSE;
} else if ((ssp->eeprom_size == 0) ||
(ssp->eventlog_start >= ssp->eeprom_size)) {
/* Sanity check values */
cmn_err(CE_WARN,
"!bscv_window_setup: read invalid eeprom parameters");
ssp->eeinfo_valid = B_FALSE;
} else {
ssp->eeinfo_valid = B_TRUE;
}
BSCV_TRACE(ssp, 'I', "bscv_window_setup", "returning eeinfo_valid %s",
ssp->eeinfo_valid ? "true" : "false");
return (ssp->eeinfo_valid);
}
/*
* function - bscv_eerw
* description - this routine reads/write data from/to the eeprom.
* It takes care of setting the window on the eeprom correctly.
* inputs - soft state ptr, eeprom offset, data buffer, size, read/write
* outputs - B_TRUE if the eeprom is ok, B_FALSE if the eeprom is not OK.
*/
static int
bscv_eerw(bscv_soft_state_t *ssp, uint32_t eeoffset, uint8_t *buf,
unsigned size, boolean_t is_write)
{
uint32_t blk_addr = eeoffset;
unsigned remaining = size;
uint8_t page_idx;
uint8_t this_page;
uint8_t blk_size;
int res = 0;
while (remaining > 0) {
page_idx = blk_addr & 0xff;
if ((page_idx + remaining) > 0x100) {
blk_size = 0x100 - page_idx;
} else {
blk_size = remaining;
}
/* Select correct eeprom page */
this_page = blk_addr >> 8;
bscv_put8(ssp, chan_eeprom, EBUS_IDX_EEPROM_PAGESEL, this_page);
BSCV_TRACE(ssp, 'M', "lom_eerw",
"%s data @0x%x.%02x, size 0x%x, 0x%x bytes remaining",
is_write ? "writing" : "reading",
this_page, page_idx, blk_size, remaining - blk_size);
bscv_rep_rw8(ssp, chan_eeprom,
buf, BSCVA(EBUS_CMD_SPACE_EEPROM, page_idx),
blk_size, DDI_DEV_AUTOINCR, is_write);
if (bscv_faulty(ssp) || bscv_session_error(ssp)) {
res = EIO;
break;
}
remaining -= blk_size;
blk_addr += blk_size;
buf += blk_size;
}
return (res);
}
static boolean_t
bscv_is_null_event(bscv_soft_state_t *ssp, lom_event_t *e)
{
ASSERT(e != NULL);
if (EVENT_DECODE_SUBSYS(e->ev_subsys) == EVENT_SUBSYS_NONE &&
e->ev_event == EVENT_NONE) {
/*
* This marks a NULL event.
*/
BSCV_TRACE(ssp, 'E', "bscv_is_null_event",
"EVENT_SUBSYS_NONE/EVENT_NONE null event");
return (B_TRUE);
} else if (e->ev_subsys == 0xff && e->ev_event == 0xff) {
/*
* Under some circumstances, we've seen all 1s to represent
* a manually cleared event log at the BSC prompt. Only
* a test/diagnosis environment is likely to show this.
*/
BSCV_TRACE(ssp, 'E', "bscv_is_null_event", "0xffff null event");
return (B_TRUE);
} else {
/*
* Not a NULL event.
*/
BSCV_TRACE(ssp, 'E', "bscv_is_null_event", "returning False");
return (B_FALSE);
}
}
/*
* *********************************************************************
* IOCTL Processing
* *********************************************************************
*/
/*
* function - bscv_ioctl
* description - routine that acts as a high level manager for ioctls. It
* calls the appropriate handler for ioctls on the alarm:mon and
* alarm:ctl minor nodes respectively
*
* Unsupported ioctls (now deprecated)
* LOMIOCALCTL
* LOMIOCALSTATE
* LOMIOCCLEARLOG
* LOMIOCCTL
* LOMIOCCTL2
* LOMIOCDAEMON
* LOMIOCDMON
* LOMIOCDOGCTL, TSIOCDOGCTL
* LOMIOCDOGPAT, TSIOCDOGPAT
* LOMIOCDOGTIME, TSIOCDOGTIME
* LOMIOCEVENTLOG
* LOMIOCEVNT
* LOMIOCGETMASK
* LOMIOCMPROG
* LOMIOCNBMON, TSIOCNBMON
* LOMIOCSLEEP
* LOMIOCUNLOCK, TSIOCUNLOCK
* LOMIOCWTMON, TSIOCWTMON
*
* Supported ioctls
* LOMIOCDOGSTATE, TSIOCDOGSTATE
* LOMIOCPROG
* LOMIOCPSUSTATE
* LOMIOCFANSTATE
* LOMIOCFLEDSTATE
* LOMIOCINFO
* LOMIOCMREAD
* LOMIOCVOLTS
* LOMIOCSTATS
* LOMIOCTEMP
* LOMIOCCONS
* LOMIOCEVENTLOG2
* LOMIOCINFO2
* LOMIOCTEST
* LOMIOCMPROG2
* LOMIOCMREAD2
*
* inputs - device number, command, user space arg, filemode, user
* credentials, return value
* outputs - the return value propagated back by the lower level routines.
*/
/*ARGSUSED*/
static int
bscv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred, int *rvalp)
{
bscv_soft_state_t *ssp;
int instance;
int res = 0;
instance = DEVICETOINSTANCE(dev);
ssp = ddi_get_soft_state(bscv_statep, instance);
if (ssp == NULL) {
return (ENXIO);
}
/*
* The Combined Switch and Service Processor takes care of configuration
* and control. The CSSP tells the BSC chip about it; therefore the
* bscv driver doesn't send such configuration and control to the BSC.
* Additionally Watchdog configuration is no longer done from userland
* lom.
*/
switch (cmd) {
case LOMIOCALCTL:
case LOMIOCALSTATE:
case LOMIOCCLEARLOG:
case LOMIOCCTL:
case LOMIOCCTL2:
case LOMIOCDAEMON:
case LOMIOCDMON:
case LOMIOCDOGCTL:
case LOMIOCDOGPAT:
case LOMIOCDOGTIME:
case LOMIOCEVENTLOG:
case LOMIOCEVNT:
case LOMIOCGETMASK:
case LOMIOCMPROG:
case LOMIOCNBMON:
case LOMIOCSLEEP:
case LOMIOCUNLOCK:
case LOMIOCWTMON:
return (ENOTSUP);
}
/*
* set the default result.
*/
*rvalp = 0;
if (ssp->cssp_prog) {
return (ENXIO);
} else if ((ssp->prog_mode_only || ssp->programming) &&
cmd != LOMIOCPROG) {
return (ENXIO);
}
/*
* Check that the caller has appropriate access permissions
* (FWRITE set in mode) for those ioctls which change lom
* state
*/
if (!(mode & FWRITE)) {
switch (cmd) {
case LOMIOCMPROG2:
case LOMIOCMREAD2:
case LOMIOCPROG:
case LOMIOCTEST:
return (EACCES);
/* NOTREACHED */
default:
/* Does not require write access */
break;
}
}
switch (cmd) {
case LOMIOCDOGSTATE:
res = bscv_ioc_dogstate(ssp, arg, mode);
break;
case LOMIOCPROG:
res = bscv_prog(ssp, arg, mode);
break;
case LOMIOCPSUSTATE:
res = bscv_ioc_psustate(ssp, arg, mode);
break;
case LOMIOCFANSTATE:
res = bscv_ioc_fanstate(ssp, arg, mode);
break;
case LOMIOCFLEDSTATE:
res = bscv_ioc_fledstate(ssp, arg, mode);
break;
case LOMIOCLEDSTATE:
res = bscv_ioc_ledstate(ssp, arg, mode);
break;
case LOMIOCINFO:
res = bscv_ioc_info(ssp, arg, mode);
break;
case LOMIOCMREAD:
res = bscv_ioc_mread(ssp, arg, mode);
break;
case LOMIOCVOLTS:
res = bscv_ioc_volts(ssp, arg, mode);
break;
case LOMIOCSTATS:
res = bscv_ioc_stats(ssp, arg, mode);
break;
case LOMIOCTEMP:
res = bscv_ioc_temp(ssp, arg, mode);
break;
case LOMIOCCONS:
res = bscv_ioc_cons(ssp, arg, mode);
break;
case LOMIOCEVENTLOG2:
res = bscv_ioc_eventlog2(ssp, arg, mode);
break;
case LOMIOCINFO2:
res = bscv_ioc_info2(ssp, arg, mode);
break;
case LOMIOCTEST:
res = bscv_ioc_test(ssp, arg, mode);
break;
case LOMIOCMPROG2:
res = bscv_ioc_mprog2(ssp, arg, mode);
break;
case LOMIOCMREAD2:
res = bscv_ioc_mread2(ssp, arg, mode);
break;
default:
BSCV_TRACE(ssp, 'I', "bscv_ioctl", "Invalid IOCTL 0x%x", cmd);
res = EINVAL;
}
return (res);
}
/*
* LOMIOCDOGSTATE
* TSIOCDOGSTATE - indicate whether the alarm watchdog and reset
* circuitry is enabled or not.
*/
static int
bscv_ioc_dogstate(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_dogstate_t dogstate;
uint8_t dogval;
int res = 0;
dogval = bscv_get8_locked(ssp, chan_general, EBUS_IDX_WDOG_CTRL, &res);
dogstate.dog_enable = (dogval & EBUS_WDOG_ENABLE) ? 1 : 0;
dogstate.reset_enable = (dogval & EBUS_WDOG_RST) ? 1 : 0;
dogstate.dog_timeout = bscv_get8_locked(ssp, chan_general,
EBUS_IDX_WDOG_TIME, &res);
if ((res == 0) &&
(ddi_copyout((caddr_t)&dogstate,
(caddr_t)arg, sizeof (dogstate), mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCPSUSTATE - returns full information for 4 PSUs. All this
* information is available from two bytes of LOMlite RAM, but if
* on the first read it is noticed that two or more of the PSUs are
* not present only 1 byte will be read subsequently.
*/
static int
bscv_ioc_psustate(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_psudata_t psudata;
uint8_t psustat;
int i;
int res = 0;
for (i = 0; i < MAX_PSUS; i++) {
psustat = bscv_get8_locked(ssp, chan_general,
EBUS_IDX_PSU1_STAT + i, &res);
psudata.fitted[i] = psustat & EBUS_PSU_PRESENT;
psudata.output[i] = psustat & EBUS_PSU_OUTPUT;
psudata.supplyb[i] = psustat & EBUS_PSU_INPUTB;
psudata.supplya[i] = psustat & EBUS_PSU_INPUTA;
psudata.standby[i] = psustat & EBUS_PSU_STANDBY;
}
if (ddi_copyout((caddr_t)&psudata, (caddr_t)arg, sizeof (psudata),
mode) < 0) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCFANSTATE - returns full information including speed for 4
* fans and the minimum and maximum operating speeds for each fan as
* stored in the READ ONLY EEPROM data. As this EEPROM data is set
* at manufacture time, this data should only be read by the driver
* once and stored locally.
*/
static int
bscv_ioc_fanstate(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_fandata_t fandata;
int numfans;
int i;
int res = 0;
bzero(&fandata, sizeof (lom_fandata_t));
numfans = EBUS_CONFIG_NFAN_DEC(bscv_get8_locked(ssp,
chan_general, EBUS_IDX_CONFIG, &res));
for (i = 0; (i < numfans) && (res == 0); i++) {
if (ssp->fanspeed[i] != LOM_FAN_NOT_PRESENT) {
fandata.fitted[i] = 1;
fandata.speed[i] = ssp->fanspeed[i];
fandata.minspeed[i] = bscv_get8_cached(ssp,
EBUS_IDX_FAN1_LOW + i);
}
}
if ((res == 0) &&
(ddi_copyout((caddr_t)&fandata, (caddr_t)arg, sizeof (fandata),
mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCFLEDSTATE - returns the state of the fault LED
*/
static int
bscv_ioc_fledstate(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_fled_info_t fled_info;
uint8_t fledstate;
int res = 0;
fledstate = bscv_get8_locked(ssp, chan_general, EBUS_IDX_ALARM, &res);
/* Decode of 0x0F is off and 0x00-0x07 is on. */
if (EBUS_ALARM_LED_DEC(fledstate) == 0x0F) {
fled_info.on = 0;
} else {
/* has +1 here - not 2 as in the info ioctl */
fled_info.on = EBUS_ALARM_LED_DEC(fledstate) + 1;
}
if ((res == 0) &&
(ddi_copyout((caddr_t)&fled_info, (caddr_t)arg,
sizeof (fled_info), mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCLEDSTATE - returns the state of the requested LED
*/
static int
bscv_ioc_ledstate(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_led_state_t led_state;
int fw_led_state;
int res = 0;
/* copy in arguments supplied */
if (ddi_copyin((caddr_t)arg, (caddr_t)&led_state,
sizeof (lom_led_state_t), mode) < 0) {
return (EFAULT);
}
/*
* check if led index is -1, if so set it to max value for
* this implementation.
*/
if (led_state.index == -1) {
led_state.index = MAX_LED_ID;
}
/* is the index in a valid range */
if ((led_state.index > MAX_LED_ID) || (led_state.index < 0)) {
led_state.state = LOM_LED_OUTOFRANGE;
} else {
/* read the relevant led info */
fw_led_state = bscv_get8_locked(ssp, chan_general,
EBUS_IDX_LED1_STATUS + led_state.index, &res);
/* set the state values accordingly */
switch (fw_led_state) {
case LOM_LED_STATE_OFF:
led_state.state = LOM_LED_OFF;
led_state.colour = LOM_LED_COLOUR_ANY;
break;
case LOM_LED_STATE_ON_STEADY:
led_state.state = LOM_LED_ON;
led_state.colour = LOM_LED_COLOUR_ANY;
break;
case LOM_LED_STATE_ON_FLASHING:
case LOM_LED_STATE_ON_SLOWFLASH:
led_state.state = LOM_LED_BLINKING;
led_state.colour = LOM_LED_COLOUR_ANY;
break;
case LOM_LED_STATE_NOT_PRESENT:
led_state.state = LOM_LED_NOT_IMPLEMENTED;
led_state.colour = LOM_LED_COLOUR_NONE;
break;
case LOM_LED_STATE_INACCESSIBLE:
case LOM_LED_STATE_STANDBY:
default:
led_state.state = LOM_LED_ACCESS_ERROR;
led_state.colour = LOM_LED_COLOUR_NONE;
break;
}
/* set the label info */
(void) strcpy(led_state.label,
ssp->led_names[led_state.index]);
}
/* copy out lom_state */
if ((res == 0) &&
(ddi_copyout((caddr_t)&led_state, (caddr_t)arg,
sizeof (lom_led_state_t), mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCINFO - returns with a structure containing any information
* stored on the LOMlite which a user should not need to access but
* may be useful for diagnostic problems. The structure contains: the
* serial escape character, alarm3 mode, version and checksum read from
* RAM and the Product revision and ID read from EEPROM.
*/
static int
bscv_ioc_info(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_info_t info;
int i;
uint16_t csum;
int res = 0;
info.ser_char = bscv_get8_locked(ssp, chan_general, EBUS_IDX_ESCAPE,
&res);
info.a3mode = WATCHDOG;
info.fver = bscv_get8_locked(ssp, chan_general, EBUS_IDX_FW_REV, &res);
csum = bscv_get8_locked(ssp, chan_general, EBUS_IDX_CHECK_HI, &res)
<< 8;
csum |= bscv_get8_locked(ssp, chan_general, EBUS_IDX_CHECK_LO, &res);
info.fchksum = csum;
info.prod_rev = bscv_get8_locked(ssp, chan_general, EBUS_IDX_MODEL_REV,
&res);
for (i = 0; i < sizeof (info.prod_id); i++) {
info.prod_id[i] = bscv_get8_locked(ssp,
chan_general, EBUS_IDX_MODEL_ID1 + i, &res);
}
if (bscv_get8_locked(ssp, chan_general, EBUS_IDX_ALARM, &res) &
EBUS_ALARM_NOEVENTS) {
info.events = OFF;
} else {
info.events = ON;
}
if ((res == 0) &&
(ddi_copyout((caddr_t)&info, (caddr_t)arg, sizeof (info),
mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCMREAD - used to query the LOMlite configuration parameters
*/
static int
bscv_ioc_mread(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_mprog_t mprog;
int i;
int fanz;
int res = 0;
for (i = 0; i < sizeof (mprog.mod_id); i++) {
mprog.mod_id[i] = bscv_get8_locked(ssp, chan_general,
EBUS_IDX_MODEL_ID1 + i, &res);
}
mprog.mod_rev = bscv_get8_locked(ssp, chan_general, EBUS_IDX_MODEL_REV,
&res);
mprog.config = bscv_get8_locked(ssp, chan_general, EBUS_IDX_CONFIG,
&res);
/* Read the fan calibration values */
fanz = sizeof (mprog.fanhz) / sizeof (mprog.fanhz[0]);
for (i = 0; i < fanz; i++) {
mprog.fanhz[i] = bscv_get8_cached(ssp,
EBUS_IDX_FAN1_CAL + i);
mprog.fanmin[i] = bscv_get8_cached(ssp,
EBUS_IDX_FAN1_LOW + i);
}
if ((res == 0) &&
(ddi_copyout((caddr_t)&mprog, (caddr_t)arg, sizeof (mprog),
mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCVOLTS
*/
static int
bscv_ioc_volts(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
int i;
uint16_t supply;
int res = 0;
supply = (bscv_get8_locked(ssp, chan_general, EBUS_IDX_SUPPLY_HI, &res)
<< 8) | bscv_get8_locked(ssp, chan_general, EBUS_IDX_SUPPLY_LO,
&res);
for (i = 0; i < ssp->volts.num; i++) {
ssp->volts.status[i] = (supply >> i) & 1;
}
if ((res == 0) &&
(ddi_copyout((caddr_t)&ssp->volts, (caddr_t)arg,
sizeof (ssp->volts), mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCSTATS
*/
static int
bscv_ioc_stats(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
int i;
uint8_t status;
int res = 0;
status = bscv_get8_locked(ssp, chan_general, EBUS_IDX_CBREAK_STATUS,
&res);
for (i = 0; i < ssp->sflags.num; i++) {
ssp->sflags.status[i] = (int)((status >> i) & 1);
}
if ((res == 0) &&
(ddi_copyout((caddr_t)&ssp->sflags, (caddr_t)arg,
sizeof (ssp->sflags), mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCTEMP
*/
static int
bscv_ioc_temp(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
int i;
int idx;
uint8_t status_ov;
lom_temp_t temps;
int res = 0;
bzero(&temps, sizeof (temps));
idx = 0;
for (i = 0; i < ssp->temps.num; i++) {
if (ssp->temps.temp[i] != LOM_TEMP_STATE_NOT_PRESENT) {
temps.temp[idx] = ssp->temps.temp[i];
bcopy(ssp->temps.name[i], temps.name[idx],
sizeof (temps.name[idx]));
temps.warning[idx] = ssp->temps.warning[i];
temps.shutdown[idx] = ssp->temps.shutdown[i];
idx++;
}
}
temps.num = idx;
bcopy(ssp->temps.name_ov, temps.name_ov, sizeof (temps.name_ov));
temps.num_ov = ssp->temps.num_ov;
status_ov = bscv_get8_locked(ssp, chan_general, EBUS_IDX_OTEMP_STATUS,
&res);
for (i = 0; i < ssp->temps.num_ov; i++) {
ssp->temps.status_ov[i] = (status_ov >> i) & 1;
}
if ((res == 0) &&
(ddi_copyout((caddr_t)&temps, (caddr_t)arg, sizeof (temps),
mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCCONS
*/
static int
bscv_ioc_cons(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_cbuf_t cbuf;
int datasize;
int res = 0;
bzero(&cbuf, sizeof (cbuf));
datasize = EBUS_IDX1_CONS_BUF_END - EBUS_IDX1_CONS_BUF_START + 1;
/* Ensure that we do not overfill cbuf and that it is NUL terminated */
if (datasize > (sizeof (cbuf) - 1)) {
datasize = sizeof (cbuf) - 1;
}
bscv_rep_get8_locked(ssp, chan_general, (uint8_t *)cbuf.lrbuf,
BSCVA(EBUS_CMD_SPACE1, (EBUS_IDX1_CONS_BUF_END - datasize + 1)),
datasize, DDI_DEV_AUTOINCR, &res);
/* This is always within the array due to the checks above */
cbuf.lrbuf[datasize] = '\0';
if ((res == 0) &&
(ddi_copyout((caddr_t)&cbuf, (caddr_t)arg, sizeof (cbuf),
mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCEVENTLOG2
*/
static int
bscv_ioc_eventlog2(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom_eventlog2_t *eventlog2;
int events_recorded;
int level;
uint16_t next_offset;
lom_event_t event;
int res = 0;
eventlog2 = (lom_eventlog2_t *)kmem_zalloc(sizeof (*eventlog2),
KM_SLEEP);
/*
* First get number of events and level requested.
*/
if (ddi_copyin((caddr_t)arg, (caddr_t)eventlog2,
sizeof (lom_eventlog2_t), mode) < 0) {
kmem_free((void *)eventlog2, sizeof (*eventlog2));
return (EFAULT);
}
bscv_enter(ssp);
/*
* OK we have full private access to the LOM now so loop
* over the eventlog addr spaces until we get the required
* number of events.
*/
if (!bscv_window_setup(ssp)) {
res = EIO;
bscv_exit(ssp);
kmem_free((void *)eventlog2, sizeof (*eventlog2));
return (res);
}
/*
* Read count, next event ptr MSB,LSB. Note a read of count
* is necessary to latch values for the next event ptr
*/
(void) bscv_get8(ssp, chan_general, EBUS_IDX_UNREAD_EVENTS);
next_offset = bscv_get16(ssp, chan_general, EBUS_IDX_LOG_PTR_HI);
BSCV_TRACE(ssp, 'I', "bscv_ioc_eventlog2", "log_ptr_hi 0x%x",
next_offset);
events_recorded = 0;
while (events_recorded < eventlog2->num) {
/*
* Working backwards - read an event at a time.
* next_offset is one event on from where we want to be!
* Decrement next_offset and maybe wrap to the end of the
* buffer.
* Note the unsigned arithmetic, so check values first!
*/
if (next_offset <= ssp->eventlog_start) {
/* Wrap to the end of the buffer */
next_offset = ssp->eventlog_start + ssp->eventlog_size;
BSCV_TRACE(ssp, 'I', "bscv_ioc_eventlog2", "wrapping"
" around to end of buffer; next_offset 0x%x",
next_offset);
}
next_offset -= sizeof (event);
if (bscv_eerw(ssp, next_offset, (uint8_t *)&event,
sizeof (event), B_FALSE /* read */) != 0) {
/* Fault reading data - stop */
BSCV_TRACE(ssp, 'I', "bscv_ioc_eventlog2", "read"
" failure for offset 0x%x", next_offset);
res = EIO;
break;
}
if (bscv_is_null_event(ssp, &event)) {
/*
* No more events in this log so give up.
*/
BSCV_TRACE(ssp, 'I', "bscv_ioc_eventlog2", "no more"
" events left at offset 0x%x", next_offset);
break;
}
/*
* Are we interested in this event
*/
level = bscv_level_of_event(&event);
if (level <= eventlog2->level) {
/* Arggh why the funny byte ordering 3, 2, 0, 1 */
eventlog2->code[events_recorded] =
((unsigned)event.ev_event |
((unsigned)event.ev_subsys << 8) |
((unsigned)event.ev_resource << 16) |
((unsigned)event.ev_detail << 24));
eventlog2->time[events_recorded] =
((unsigned)event.ev_data[0] |
((unsigned)event.ev_data[1] << 8) |
((unsigned)event.ev_data[3] << 16) |
((unsigned)event.ev_data[2] << 24));
bscv_build_eventstring(ssp,
&event, eventlog2->string[events_recorded],
eventlog2->string[events_recorded] +
sizeof (eventlog2->string[events_recorded]));
events_recorded++;
}
}
eventlog2->num = events_recorded;
bscv_exit(ssp);
if ((res == 0) &&
(ddi_copyout((caddr_t)eventlog2, (caddr_t)arg,
sizeof (lom_eventlog2_t), mode) < 0)) {
res = EFAULT;
}
kmem_free((void *)eventlog2, sizeof (lom_eventlog2_t));
return (res);
}
/*
* LOMIOCINFO2
*/
static int
bscv_ioc_info2(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom2_info_t info2;
int i;
uint16_t csum;
int res = 0;
bzero(&info2, sizeof (info2));
(void) strncpy(info2.escape_chars, ssp->escape_chars,
sizeof (info2.escape_chars));
info2.serial_events = ssp->reporting_level | ssp->serial_reporting;
info2.a3mode = WATCHDOG;
info2.fver = bscv_get8_locked(ssp, chan_general, EBUS_IDX_FW_REV, &res);
csum = bscv_get8_locked(ssp, chan_general, EBUS_IDX_CHECK_HI, &res)
<< 8;
csum |= bscv_get8_locked(ssp, chan_general, EBUS_IDX_CHECK_LO, &res);
info2.fchksum = csum;
info2.prod_rev = bscv_get8_locked(ssp, chan_general,
EBUS_IDX_MODEL_REV, &res);
for (i = 0; i < sizeof (info2.prod_id); i++) {
info2.prod_id[i] = bscv_get8_locked(ssp, chan_general,
EBUS_IDX_MODEL_ID1 + i, &res);
}
info2.serial_config = bscv_get8_locked(ssp, chan_general,
EBUS_IDX_SER_TIMEOUT, &res);
if (bscv_get8_locked(ssp, chan_general, EBUS_IDX_CONFIG_MISC, &res) &
EBUS_CONFIG_MISC_SECURITY_ENABLED) {
info2.serial_config |= LOM_SER_SECURITY;
}
if (bscv_get8_locked(ssp, chan_general, EBUS_IDX_CONFIG_MISC, &res) &
EBUS_CONFIG_MISC_AUTO_CONSOLE) {
info2.serial_config |= LOM_SER_RETURN;
}
if (bscv_get8_locked(ssp, chan_general, EBUS_IDX_WDOG_CTRL, &res) &
EBUS_WDOG_BREAK_DISABLE) {
info2.serial_config |= LOM_DISABLE_WDOG_BREAK;
}
info2.baud_rate = bscv_get8_locked(ssp, chan_general,
EBUS_IDX_SER_BAUD, &res);
info2.serial_hw_config =
((int)bscv_get8_locked(ssp, chan_general,
EBUS_IDX_SER_CHARMODE, &res) |
((int)bscv_get8_locked(ssp, chan_general,
EBUS_IDX_SER_FLOWCTL, &res) << 8) |
((int)bscv_get8_locked(ssp, chan_general,
EBUS_IDX_SER_MODEMTYPE, &res) << 16));
/*
* There is no phone home support on the blade platform. We hardcode
* FALSE and NUL for config and script respectively.
*/
info2.phone_home_config = B_FALSE;
info2.phone_home_script[0] = '\0';
for (i = 0; i < ssp->num_fans; i++) {
(void) strcpy(info2.fan_names[i], ssp->fan_names[i]);
}
if ((res == 0) &&
(ddi_copyout((caddr_t)&info2, (caddr_t)arg, sizeof (info2),
mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCTEST
*/
static int
bscv_ioc_test(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
uint32_t test;
uint8_t testnum;
uint8_t testarg;
int res = 0;
if (ddi_copyin((caddr_t)arg, (caddr_t)&test, sizeof (test),
mode) < 0) {
return (EFAULT);
}
/*
* Extract num iterations.
*/
testarg = (test & 0xff00) >> 8;
testnum = test & 0xff;
BSCV_TRACE(ssp, 'F', "bscv_ioc_test",
"LOMIOCTEST data 0x%x (test 0x%x, arg 0x%x)",
test, (EBUS_IDX_SELFTEST0 + testnum), testarg);
switch (testnum + EBUS_IDX_SELFTEST0) {
default:
/* Invalid test */
res = EINVAL;
break;
case EBUS_IDX_SELFTEST0: /* power on self-test result */
case EBUS_IDX_SELFTEST1: /* not used currently */
case EBUS_IDX_SELFTEST2: /* not used currently */
case EBUS_IDX_SELFTEST3: /* not used currently */
case EBUS_IDX_SELFTEST4: /* not used currently */
case EBUS_IDX_SELFTEST5: /* not used currently */
case EBUS_IDX_SELFTEST6: /* LED self-test */
case EBUS_IDX_SELFTEST7: /* platform-specific tests */
/* Run the test */
/* Stop other things and then run the test */
bscv_enter(ssp);
/*
* Then we simply write the argument to the relevant register
* and wait for the return code.
*/
bscv_put8(ssp, chan_general,
EBUS_IDX_SELFTEST0 + testnum, testarg);
if (bscv_faulty(ssp)) {
res = EIO;
} else {
/* Get hold of the SunVTS error code */
test = bscv_retcode(ssp);
}
bscv_exit(ssp);
break;
}
BSCV_TRACE(ssp, 'F', "bscv_ioc_test",
"LOMIOCTEST status 0x%x, res 0x%x", test, res);
if ((res == 0) &&
(ddi_copyout((caddr_t)&test, (caddr_t)arg, sizeof (test),
mode) < 0)) {
res = EFAULT;
}
return (res);
}
/*
* LOMIOCMPROG2
*/
static int
bscv_ioc_mprog2(bscv_soft_state_t *ssp, intptr_t arg, int mode)
{
lom2_mprog_t mprog2;
uint32_t base_addr;
uint32_t data_size;
uint32_t eeprom_size;
int res = 0;
if (ddi_copyin((caddr_t)arg, (caddr_t)&mprog2, sizeof (mprog2),
mode) < 0) {
return (EFAULT);
}
/*
* Note that originally this was accessed as 255 byte pages
* in address spaces 240-255. We have to emulate this behaviour.
*/
if ((mprog2.addr_space < 240) || (mprog2.addr_space > 255)) {
return (EINVAL);
}
bscv_enter(ssp);
/* Calculate required data location */
data_size = 255;
base_addr = (mprog2.addr_space - 240) * data_size;
eeprom_size = bscv_get8(ssp, chan_general, EBUS_IDX_EEPROM_SIZE_KB) *
1024;
if (bscv_faulty(ssp)) {
bscv_exit(ssp);
return (EIO);
} else if ((base_addr + data_size) > eeprom_size) {
BSCV_TRACE(ssp, 'M', "bscv_ioc_mprog2",
"Request extends past end of eeprom");
bscv_exit(ssp);
return (ENXIO);
}
bscv_put8(ssp, chan_general, EBUS_IDX_CMD_RES, EBUS_CMD_UNLOCK1);
if (bscv_faulty(ssp)) {
BSCV_TRACE(ssp, 'M', "bscv_ioc_mprog2", "ML1 Write failed");
bscv_exit(ssp);
return (EIO);
}
bscv_put8(ssp, chan_general, EBUS_IDX_CMD_RES, EBUS_CMD_UNLOCK2);
if (bscv_faulty(ssp)) {
BSCV_TRACE(ssp, 'M', "bscv_ioc_mprog2", "ML2 Write failed");
bscv_exit(ssp);
return (EIO);
}
if (bscv_eerw(ssp, base_addr, &mprog2.data[0],
data_size, B_TRUE /* write */) != 0) {
res = EIO;
}
<