sgsbbc_iosram.c revision 03831d35f7499c87d51205817c93e9a8d42c4bae
/*
* 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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Driver for handling Serengeti I/O SRAM
* for Solaris <-> SC comm.
*/
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/cpuvar.h>
#include <sys/dditypes.h>
#include <sys/sunndi.h>
#include <sys/param.h>
#include <sys/mutex.h>
#include <sys/sysmacros.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/kmem.h>
#include <sys/promif.h>
#include <sys/prom_plat.h>
#include <sys/sunddi.h>
#include <sys/ddi.h>
#include <sys/serengeti.h>
#include <sys/sgsbbc_priv.h>
#include <sys/sgsbbc_iosram_priv.h>
#include <sys/sgsbbc_mailbox_priv.h>
/*
* Local stuff
*/
static int iosram_rw(int, uint32_t, caddr_t, uint32_t, int);
static int iosram_convert_key(char *);
static int iosram_switch_intr(void);
static int tunnel_init(sbbc_softstate_t *, tunnel_t *);
static void tunnel_fini(tunnel_t *);
static void tunnel_commit(sbbc_softstate_t *, tunnel_t *);
static void clear_break();
#define IOSRAM_GETB(tunnel, buf, sram, count) \
ddi_rep_get8(tunnel->reg_handle, buf, sram, count, DDI_DEV_AUTOINCR)
#define IOSRAM_PUTB(tunnel, buf, sram, count) \
ddi_rep_put8(tunnel->reg_handle, buf, sram, count, DDI_DEV_AUTOINCR)
#define IOSRAM_PUT(tunnel, sram, buf, size) \
/* CSTYLED */ \
ddi_put##size(tunnel->reg_handle, (uint##size##_t *)sram, \
/* CSTYLED */ \
*((uint##size##_t *)buf))
#define IOSRAM_GET(tunnel, sram, buf, size) \
/* CSTYLED */ \
*(uint##size##_t *)buf = ddi_get##size(tunnel->reg_handle, \
/* CSTYLED */ \
(uint##size##_t *)sram)
/*
* sgsbbc_iosram_is_chosen(struct sbbc_softstate *softsp)
*
* Looks up "chosen" node property to
* determine if it is the chosen IOSRAM.
*/
int
sgsbbc_iosram_is_chosen(sbbc_softstate_t *softsp)
{
char pn[MAXNAMELEN];
char chosen_iosram[MAXNAMELEN];
int nodeid;
int chosen;
uint_t tunnel;
extern pnode_t chosen_nodeid;
ASSERT(chosen_nodeid);
nodeid = chosen_nodeid;
(void) prom_getprop(nodeid, "iosram", (caddr_t)&tunnel);
/*
* get the full OBP pathname of this node
*/
if (prom_phandle_to_path((phandle_t)tunnel, chosen_iosram,
sizeof (chosen_iosram)) < 0) {
cmn_err(CE_NOTE, "prom_phandle_to_path(%x) failed\n", tunnel);
return (0);
}
SGSBBC_DBG_ALL("sgsbbc_iosram(%d): prom_phandle_to_path(%x) is '%s'\n",
softsp->sbbc_instance, nodeid, chosen_iosram);
(void) ddi_pathname(softsp->dip, pn);
SGSBBC_DBG_ALL("sgsbbc_iosram(%d): ddi_pathname(%p) is '%s'\n",
softsp->sbbc_instance, softsp->dip, pn);
chosen = (strcmp(chosen_iosram, pn) == 0) ? 1 : 0;
SGSBBC_DBG_ALL("sgsbbc_iosram(%d): ... %s\n", softsp->sbbc_instance,
chosen? "MASTER" : "SLAVE");
SGSBBC_DBG_ALL("sgsbbc_iosram(%d): ... %s\n", softsp->sbbc_instance,
(chosen ? "MASTER" : "SLAVE"));
return (chosen);
}
void
iosram_init()
{
int i;
if ((master_iosram = kmem_zalloc(sizeof (struct chosen_iosram),
KM_NOSLEEP)) == NULL) {
prom_printf("Can't allocate space for Chosen IOSRAM\n");
panic("Can't allocate space for Chosen IOSRAM");
}
if ((master_iosram->tunnel = kmem_zalloc(sizeof (tunnel_t),
KM_NOSLEEP)) == NULL) {
prom_printf("Can't allocate space for tunnel\n");
panic("Can't allocate space for tunnel");
}
master_iosram->iosram_sbbc = NULL;
for (i = 0; i < SBBC_MAX_KEYS; i++) {
master_iosram->tunnel->tunnel_keys[i].key = 0;
master_iosram->tunnel->tunnel_keys[i].base = NULL;
master_iosram->tunnel->tunnel_keys[i].size = 0;
}
for (i = 0; i < SBBC_MAX_INTRS; i++)
master_iosram->intrs[i].sbbc_handler = NULL;
mutex_init(&master_iosram->iosram_lock, NULL, MUTEX_DEFAULT, NULL);
rw_init(&master_iosram->tunnel_lock, NULL, RW_DEFAULT, NULL);
}
void
iosram_fini()
{
struct tunnel_key *tunnel;
int i;
rw_destroy(&master_iosram->tunnel_lock);
mutex_destroy(&master_iosram->iosram_lock);
/*
* destroy any tunnel maps
*/
for (i = 0; i < SBBC_MAX_KEYS; i++) {
tunnel = &master_iosram->tunnel->tunnel_keys[i];
if (tunnel->base != NULL) {
ddi_regs_map_free(&tunnel->reg_handle);
tunnel->base = NULL;
}
}
kmem_free(master_iosram->tunnel, sizeof (tunnel_t));
kmem_free(master_iosram, sizeof (struct chosen_iosram));
master_iosram = NULL;
}
static void
check_iosram_ver(uint16_t version)
{
uint8_t max_ver = MAX_IOSRAM_TOC_VER;
uint8_t major_ver =
(version >> IOSRAM_TOC_VER_SHIFT) & IOSRAM_TOC_VER_MASK;
SGSBBC_DBG_ALL("IOSRAM TOC version: %d.%d\n", major_ver,
version & IOSRAM_TOC_VER_MASK);
SGSBBC_DBG_ALL("Max supported IOSRAM TOC version: %d\n", max_ver);
if (major_ver > max_ver) {
panic("Up-rev System Controller version.\n"
"You must restore an earlier revision of System "
"Controller firmware, or upgrade Solaris.\n"
"Please consult the System Controller release notice "
"for additional details.");
}
}
static void
tunnel_commit(sbbc_softstate_t *softsp, tunnel_t *new_tunnel)
{
ASSERT(MUTEX_HELD(&master_iosram->iosram_lock));
master_iosram->iosram_sbbc = softsp;
master_iosram->tunnel = new_tunnel;
softsp->chosen = TRUE;
/*
* SBBC has pointer to interrupt handlers for simplicity
*/
softsp->intr_hdlrs = master_iosram->intrs;
}
static int
tunnel_init(sbbc_softstate_t *softsp, tunnel_t *new_tunnel)
{
struct iosram_toc *toc = NULL;
int i, key;
struct tunnel_key *tunnel;
ddi_acc_handle_t toc_handle;
struct ddi_device_acc_attr attr;
ASSERT(MUTEX_HELD(&master_iosram->iosram_lock));
if ((softsp == (sbbc_softstate_t *)NULL) ||
(new_tunnel == (tunnel_t *)NULL)) {
return (DDI_FAILURE);
}
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
SGSBBC_DBG_ALL("map in the IOSRAM TOC at offset %x\n",
softsp->sram_toc);
/*
* First map in the TOC, then set up the tunnel
*/
if (ddi_regs_map_setup(softsp->dip, RNUM_SBBC_REGS,
(caddr_t *)&toc,
SBBC_SRAM_OFFSET + softsp->sram_toc,
sizeof (struct iosram_toc),
&attr, &toc_handle) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sbbc%d: unable to map SRAM "
"registers", ddi_get_instance(softsp->dip));
return (DDI_FAILURE);
}
SGSBBC_DBG_ALL("dip=%p mapped TOC %p\n", softsp->dip, toc);
check_iosram_ver(toc->iosram_version);
for (i = 0; i < toc->iosram_tagno; i++) {
key = iosram_convert_key(toc->iosram_keys[i].key);
if ((key > 0) && (key < SBBC_MAX_KEYS)) {
tunnel = &new_tunnel->tunnel_keys[key];
tunnel->key = key;
tunnel->size = toc->iosram_keys[i].size;
/*
* map in the SRAM area using the offset
* from the base of SRAM + SRAM offset into
* the register property for the SBBC base
* address
*/
if (ddi_regs_map_setup(softsp->dip, RNUM_SBBC_REGS,
(caddr_t *)&tunnel->base,
SBBC_SRAM_OFFSET + toc->iosram_keys[i].offset,
toc->iosram_keys[i].size, &attr,
&tunnel->reg_handle) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sbbc%d: unable to map SRAM "
"registers", ddi_get_instance(softsp->dip));
return (DDI_FAILURE);
}
SGSBBC_DBG_ALL("%d: key %s size %d offset %x addr %p\n",
i, toc->iosram_keys[i].key,
toc->iosram_keys[i].size,
toc->iosram_keys[i].offset,
tunnel->base);
}
}
if (toc != NULL) {
ddi_regs_map_free(&toc_handle);
}
/*
* Set up the 'interrupt reason' SRAM pointers
* for the SBBC interrupt handler
*/
if (INVALID_KEY(new_tunnel, SBBC_SC_INTR_KEY)) {
/*
* Can't really do much if these are not here
*/
prom_printf("No Interrupt Reason Fields set by SC\n");
cmn_err(CE_WARN, "No Interrupt Reason Fields set by SC");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Unmap a tunnel
*/
static void
tunnel_fini(tunnel_t *tunnel)
{
int i;
struct tunnel_key *tunnel_key;
/*
* Unmap the tunnel
*/
for (i = 0; i < SBBC_MAX_KEYS; i++) {
tunnel_key = &tunnel->tunnel_keys[i];
if (tunnel_key->base != NULL) {
ddi_regs_map_free(&tunnel_key->reg_handle);
tunnel_key->base = NULL;
}
}
}
static void
clear_break()
{
struct tunnel_key tunnel_key;
uint32_t *intr_in_reason;
ddi_acc_handle_t intr_in_handle;
ASSERT(MUTEX_HELD(&master_iosram->iosram_lock));
tunnel_key = master_iosram->tunnel->tunnel_keys[SBBC_SC_INTR_KEY];
intr_in_reason = (uint32_t *)tunnel_key.base;
intr_in_handle = tunnel_key.reg_handle;
ddi_put32(intr_in_handle, intr_in_reason,
ddi_get32(intr_in_handle, intr_in_reason) & ~SBBC_CONSOLE_BRK);
}
int
iosram_tunnel_init(sbbc_softstate_t *softsp)
{
int rc;
ASSERT(master_iosram);
mutex_enter(&master_iosram->iosram_lock);
if ((rc = tunnel_init(softsp, master_iosram->tunnel)) == DDI_SUCCESS) {
tunnel_commit(softsp, master_iosram->tunnel);
clear_break();
}
mutex_exit(&master_iosram->iosram_lock);
return (rc);
}
int
iosram_read(int key, uint32_t offset, caddr_t buf, uint32_t size)
{
return (iosram_rw(key, offset, buf, size, FREAD));
}
int
iosram_write(int key, uint32_t offset, caddr_t buf, uint32_t size)
{
return (iosram_rw(key, offset, buf, size, FWRITE));
}
static int
iosram_rw(int key, uint32_t offset, caddr_t buf, uint32_t size, int flag)
{
struct tunnel_key *tunnel;
caddr_t sram_src;
/*
* Return right away if there is nothing to read/write.
*/
if (size == 0)
return (0);
rw_enter(&master_iosram->tunnel_lock, RW_READER);
/*
* Key not matched ?
*/
if (INVALID_KEY(master_iosram->tunnel, key)) {
rw_exit(&master_iosram->tunnel_lock);
return (ENXIO);
}
tunnel = &master_iosram->tunnel->tunnel_keys[key];
if ((offset + size) > tunnel->size) {
rw_exit(&master_iosram->tunnel_lock);
return (EFBIG);
}
sram_src = tunnel->base + offset;
/*
* Atomic reads/writes might be necessary for some clients.
* We assume that such clients could guarantee their buffers
* are aligned at the boundary of the request sizes. We also
* assume that the source/destination of such requests are
* aligned at the right boundaries in IOSRAM. If either
* condition fails, byte access is performed.
*/
if (flag == FREAD) {
switch (size) {
case sizeof (uint16_t):
case sizeof (uint32_t):
case sizeof (uint64_t):
if (IS_P2ALIGNED(sram_src, size) &&
IS_P2ALIGNED(buf, size)) {
if (size == sizeof (uint16_t))
IOSRAM_GET(tunnel, sram_src, buf, 16);
else if (size == sizeof (uint32_t))
IOSRAM_GET(tunnel, sram_src, buf, 32);
else
IOSRAM_GET(tunnel, sram_src, buf, 64);
break;
}
/* FALLTHRU */
default:
IOSRAM_GETB(tunnel, (uint8_t *)buf,
(uint8_t *)sram_src, (size_t)size);
break;
}
} else {
switch (size) {
case sizeof (uint16_t):
case sizeof (uint32_t):
case sizeof (uint64_t):
if (IS_P2ALIGNED(sram_src, size) &&
IS_P2ALIGNED(buf, size)) {
if (size == sizeof (uint16_t))
IOSRAM_PUT(tunnel, sram_src, buf, 16);
else if (size == sizeof (uint32_t))
IOSRAM_PUT(tunnel, sram_src, buf, 32);
else
IOSRAM_PUT(tunnel, sram_src, buf, 64);
break;
}
/* FALLTHRU */
default:
IOSRAM_PUTB(tunnel, (uint8_t *)buf,
(uint8_t *)sram_src, (size_t)size);
break;
}
}
rw_exit(&master_iosram->tunnel_lock);
return (0);
}
int
iosram_size(int key)
{
int size = -1;
rw_enter(&master_iosram->tunnel_lock, RW_READER);
/*
* Key not matched ?
*/
if (!INVALID_KEY(master_iosram->tunnel, key))
size = master_iosram->tunnel->tunnel_keys[key].size;
rw_exit(&master_iosram->tunnel_lock);
return (size);
}
/*
* Generate an interrupt to the SC using the SBBC EPLD
*
* Note: intr_num can be multiple interrupts OR'ed together
*/
int
iosram_send_intr(uint32_t intr_num)
{
int rc = 0;
uint32_t intr_reason;
uint32_t intr_enabled;
/*
* Verify that we have already set up the master sbbc
*/
if (master_iosram == NULL)
return (ENXIO);
/*
* Grab the lock to prevent tunnel switch in the middle
* of sending an interrupt.
*/
mutex_enter(&master_iosram->iosram_lock);
if (master_iosram->iosram_sbbc == NULL) {
rc = ENXIO;
goto send_intr_exit;
}
if ((rc = sbbc_send_intr(master_iosram->iosram_sbbc, FALSE)) != 0) {
/*
* previous interrupts have not been cleared yet by the SC
*/
goto send_intr_exit;
}
/*
* Set a bit in the interrupt reason field
* call back into the sbbc handler to hit the EPLD
*
* First check the interrupts enabled by the SC
*/
if ((rc = iosram_read(SBBC_INTR_SC_ENABLED_KEY, 0,
(caddr_t)&intr_enabled, sizeof (intr_enabled))) != 0) {
goto send_intr_exit;
}
if ((intr_enabled & intr_num) != intr_num) {
/*
* at least one of the interrupts is
* not enabled by the SC
*/
rc = ENOTSUP;
goto send_intr_exit;
}
if ((rc = iosram_read(SBBC_INTR_SC_KEY, 0,
(caddr_t)&intr_reason, sizeof (intr_reason))) != 0) {
goto send_intr_exit;
}
if ((intr_reason & intr_num) == intr_num) {
/*
* All interrupts specified are already pending
*/
rc = EBUSY;
goto send_intr_exit;
}
intr_reason |= intr_num;
if ((rc = iosram_write(SBBC_INTR_SC_KEY, 0,
(caddr_t)&intr_reason, sizeof (intr_reason))) != 0) {
goto send_intr_exit;
}
/*
* Hit the EPLD interrupt bit
*/
rc = sbbc_send_intr(master_iosram->iosram_sbbc, TRUE);
send_intr_exit:
mutex_exit(&master_iosram->iosram_lock);
return (rc);
}
/*
* Register an interrupt handler
*/
int
iosram_reg_intr(uint32_t intr_num, sbbc_intrfunc_t intr_handler,
caddr_t arg, uint_t *state, kmutex_t *lock)
{
sbbc_softstate_t *softsp;
int rc = 0;
sbbc_intrs_t *intr;
int intr_no;
uint32_t intr_enabled;
/*
* Verify that we have already set up the master sbbc
*/
if (master_iosram == NULL)
return (ENXIO);
/*
* determine which bit is this intr_num for ?
*/
for (intr_no = 0; intr_no < SBBC_MAX_INTRS; intr_no++) {
if (intr_num == (1 << intr_no))
break;
}
/*
* Check the parameters
*/
if ((intr_no < 0) || (intr_no >= SBBC_MAX_INTRS) ||
(intr_handler == NULL) || (state == NULL) ||
(lock == NULL))
return (EINVAL);
mutex_enter(&master_iosram->iosram_lock);
if ((softsp = master_iosram->iosram_sbbc) == NULL) {
mutex_exit(&master_iosram->iosram_lock);
return (ENXIO);
}
mutex_enter(&softsp->sbbc_lock);
intr = &master_iosram->intrs[intr_no];
if (intr->sbbc_handler != (sbbc_intrfunc_t)NULL) {
rc = EBUSY;
goto reg_intr_exit;
}
intr->sbbc_handler = intr_handler;
intr->sbbc_arg = (void *)arg;
intr->sbbc_intr_state = state;
intr->sbbc_intr_lock = lock;
intr->sbbc_intr_next = (sbbc_intrs_t *)NULL;
/*
* we need to make sure that the mutex is for
* an ADAPTIVE lock, so call mutex_init() again with
* the sbbc iblock cookie
*/
mutex_init(lock, NULL, MUTEX_DRIVER,
(void *)softsp->iblock);
if (ddi_add_softintr(softsp->dip, DDI_SOFTINT_HIGH,
&intr->sbbc_intr_id, NULL, NULL,
intr_handler, (caddr_t)arg) != DDI_SUCCESS) {
cmn_err(CE_WARN, "Can't add SBBC softint");
rc = EAGAIN;
goto reg_intr_exit;
}
/*
* Set the bit in the Interrupts Enabled Field for this
* interrupt
*/
if ((rc = iosram_read(SBBC_SC_INTR_ENABLED_KEY, 0,
(caddr_t)&intr_enabled, sizeof (intr_enabled))) != 0) {
goto reg_intr_exit;
}
intr_enabled |= intr_num;
if ((rc = iosram_write(SBBC_SC_INTR_ENABLED_KEY, 0,
(caddr_t)&intr_enabled, sizeof (intr_enabled))) != 0) {
goto reg_intr_exit;
}
reg_intr_exit:
mutex_exit(&softsp->sbbc_lock);
mutex_exit(&master_iosram->iosram_lock);
return (rc);
}
/*
* Remove an interrupt handler
*/
int
iosram_unreg_intr(uint32_t intr_num)
{
sbbc_softstate_t *softsp;
int rc = 0;
sbbc_intrs_t *intr;
int intr_no;
uint32_t intr_enabled;
/*
* Verify that we have already set up the master sbbc
*/
if (master_iosram == NULL)
return (ENXIO);
/*
* determine which bit is this intr_num for ?
*/
for (intr_no = 0; intr_no < SBBC_MAX_INTRS; intr_no++) {
if (intr_num == (1 << intr_no))
break;
}
if ((intr_no < 0) || (intr_no >= SBBC_MAX_INTRS))
return (EINVAL);
mutex_enter(&master_iosram->iosram_lock);
if ((softsp = master_iosram->iosram_sbbc) == NULL) {
mutex_exit(&master_iosram->iosram_lock);
return (ENXIO);
}
mutex_enter(&softsp->sbbc_lock);
intr = &master_iosram->intrs[intr_no];
/*
* No handler installed
*/
if (intr->sbbc_handler == (sbbc_intrfunc_t)NULL) {
rc = EINVAL;
goto unreg_intr_exit;
}
/*
* Unset the bit in the Interrupts Enabled Field for this
* interrupt
*/
if ((rc = iosram_read(SBBC_SC_INTR_ENABLED_KEY, 0,
(caddr_t)&intr_enabled, sizeof (intr_enabled))) != 0) {
goto unreg_intr_exit;
}
intr_enabled &= ~intr_num;
if ((rc = iosram_write(SBBC_SC_INTR_ENABLED_KEY, 0,
(caddr_t)&intr_enabled, sizeof (intr_enabled))) != 0) {
goto unreg_intr_exit;
}
/*
* If handler is running, wait until it's done.
* It won't get triggered again because we disabled it above.
* When we wait, drop sbbc_lock so other interrupt handlers
* can still run.
*/
for (; ; ) {
mutex_enter(intr->sbbc_intr_lock);
if (*(intr->sbbc_intr_state) != SBBC_INTR_IDLE) {
mutex_exit(intr->sbbc_intr_lock);
mutex_exit(&softsp->sbbc_lock);
delay(drv_usectohz(10000));
mutex_enter(&softsp->sbbc_lock);
mutex_enter(intr->sbbc_intr_lock);
} else {
break;
}
mutex_exit(intr->sbbc_intr_lock);
}
if (intr->sbbc_intr_id)
ddi_remove_softintr(intr->sbbc_intr_id);
intr->sbbc_handler = (sbbc_intrfunc_t)NULL;
intr->sbbc_arg = (void *)NULL;
intr->sbbc_intr_id = 0;
intr->sbbc_intr_state = NULL;
intr->sbbc_intr_lock = (kmutex_t *)NULL;
intr->sbbc_intr_next = (sbbc_intrs_t *)NULL;
unreg_intr_exit:
mutex_exit(&softsp->sbbc_lock);
mutex_exit(&master_iosram->iosram_lock);
return (rc);
}
/*
* sgsbbc_iosram_switchfrom(softsp)
* Switch master tunnel away from the specified instance.
*/
int
sgsbbc_iosram_switchfrom(struct sbbc_softstate *softsp)
{
struct sbbc_softstate *sp;
int rv = DDI_FAILURE;
int new_instance;
/*
* Find the candidate target of tunnel from the linked list.
*/
mutex_enter(&chosen_lock);
ASSERT(sgsbbc_instances);
for (sp = sgsbbc_instances; sp != NULL; sp = sp->next) {
if (softsp == sp)
continue;
if (sp->sbbc_state & SBBC_STATE_DETACH)
continue;
break;
}
if (sp == NULL) {
/* at least one IOSRAM should be attached */
rv = DDI_FAILURE;
} else {
/* Do the tunnel switch */
new_instance = ddi_get_instance(sp->dip);
rv = iosram_switch_tunnel(new_instance);
if (rv == DDI_SUCCESS) {
/* reset the chosen_iosram back ref */
sp->iosram = master_iosram;
}
}
mutex_exit(&chosen_lock);
return (rv);
}
/*
* Switch the tunnel to a different I/O board.
* At the moment, we will say that this is
* called with the instance of the SBBC to switch
* to. This will probably change, but as long as we
* can get a devinfo/softstate for the target SBBC it
* doesn't matter what the parameter is.
*/
int
iosram_switch_tunnel(int instance)
{
sbbc_softstate_t *to_softsp, *from_softsp;
dev_info_t *pdip; /* parent dip */
tunnel_t *new_tunnel; /* new tunnel */
int portid;
uint_t node; /* node id to pass to OBP */
uint_t board; /* board number to pass to OBP */
int rc = DDI_SUCCESS;
static fn_t f = "iosram_switch_tunnel";
/* Check the firmware for tunnel switch support */
if (prom_test("SUNW,switch-tunnel") != 0) {
cmn_err(CE_WARN, "Firmware does not support tunnel switch");
return (DDI_FAILURE);
}
if ((master_iosram == NULL) || (master_mbox == NULL))
return (DDI_FAILURE);
if (!(to_softsp = sbbc_get_soft_state(instance)))
return (DDI_FAILURE);
/*
* create the new tunnel
*/
if ((new_tunnel = kmem_zalloc(sizeof (tunnel_t), KM_NOSLEEP)) == NULL) {
cmn_err(CE_WARN, "Can't allocate space for new tunnel");
return (DDI_FAILURE);
}
pdip = ddi_get_parent(to_softsp->dip);
if ((portid = ddi_getprop(DDI_DEV_T_ANY, pdip, DDI_PROP_DONTPASS,
"portid", -1)) < 0) {
SGSBBC_DBG_ALL("%s: couldn't get portid\n", f);
return (DDI_FAILURE);
}
/*
* Compute node id and board number from port id
*/
node = SG_PORTID_TO_NODEID(portid);
board = SG_IO_BD_PORTID_TO_BD_NUM(portid);
/*
* lock the chosen IOSRAM
*/
mutex_enter(&master_iosram->iosram_lock);
if (master_iosram->iosram_sbbc == NULL) {
mutex_exit(&master_iosram->iosram_lock);
return (DDI_FAILURE);
}
/*
* If the target SBBC has not mapped in its
* register address space, do it now
*/
mutex_enter(&to_softsp->sbbc_lock);
if (to_softsp->sbbc_regs == NULL) {
if (sbbc_map_regs(to_softsp) != DDI_SUCCESS) {
mutex_exit(&to_softsp->sbbc_lock);
mutex_exit(&master_iosram->iosram_lock);
return (DDI_FAILURE);
}
}
/*
* Get a pointer to the current sbbc
*/
from_softsp = master_iosram->iosram_sbbc;
mutex_enter(&from_softsp->sbbc_lock);
/*
* Disable interrupts from the SC now
*/
sbbc_disable_intr(from_softsp);
/*
* move SC interrupts to the new tunnel
*/
if ((rc = sbbc_add_intr(to_softsp)) == DDI_FAILURE) {
cmn_err(CE_WARN, "Failed to add new interrupt handler");
} else if ((rc = tunnel_init(to_softsp, new_tunnel)) == DDI_FAILURE) {
cmn_err(CE_WARN, "Failed to initialize new tunnel");
ddi_remove_intr(to_softsp->dip, 0, to_softsp->iblock);
} else {
rw_enter(&master_iosram->tunnel_lock, RW_WRITER);
/*
* If OBP switch is unsuccessful, abort the switch.
*/
if ((rc = prom_serengeti_tunnel_switch(node, board))
!= DDI_SUCCESS) {
/*
* Restart other CPUs.
*/
rw_exit(&master_iosram->tunnel_lock);
cmn_err(CE_WARN, "OBP failed to switch tunnel");
/*
* Remove interrupt
*/
ddi_remove_intr(to_softsp->dip, 0, to_softsp->iblock);
/*
* Unmap new tunnel
*/
tunnel_fini(new_tunnel);
} else {
tunnel_t *orig_tunnel;
orig_tunnel = master_iosram->tunnel;
tunnel_commit(to_softsp, new_tunnel);
rw_exit(&master_iosram->tunnel_lock);
/*
* Remove interrupt from original softsp
*/
ddi_remove_intr(from_softsp->dip, 0,
from_softsp->iblock);
/*
* Unmap original tunnel
*/
tunnel_fini(orig_tunnel);
kmem_free(orig_tunnel, sizeof (tunnel_t));
/*
* Move the softintrs to the new dip.
*/
(void) iosram_switch_intr();
(void) sbbc_mbox_switch(to_softsp);
from_softsp->chosen = FALSE;
}
}
/*
* Enable interrupt.
*/
sbbc_enable_intr(master_iosram->iosram_sbbc);
/*
* Unlock and get out
*/
mutex_exit(&from_softsp->sbbc_lock);
mutex_exit(&to_softsp->sbbc_lock);
mutex_exit(&master_iosram->iosram_lock);
/*
* Call the interrupt handler directly in case
* we have missed an interrupt
*/
(void) sbbc_intr_handler(master_iosram->iosram_sbbc);
if (rc != DDI_SUCCESS) {
/*
* Free up the new_tunnel
*/
kmem_free(new_tunnel, sizeof (tunnel_t));
cmn_err(CE_WARN, "Tunnel switch failed");
}
return (rc);
}
/*
* convert an alphanumeric OBP key to
* our defined numeric keys
*/
static int
iosram_convert_key(char *toc_key)
{
if (strcmp(toc_key, TOCKEY_DOMSTAT) == 0)
return (SBBC_DOMAIN_KEY);
if (strcmp(toc_key, TOCKEY_KEYSWPO) == 0)
return (SBBC_KEYSWITCH_KEY);
if (strcmp(toc_key, TOCKEY_TODDATA) == 0)
return (SBBC_TOD_KEY);
if (strcmp(toc_key, TOCKEY_SOLCONS) == 0)
return (SBBC_CONSOLE_KEY);
if (strcmp(toc_key, TOCKEY_SOLMBOX) == 0)
return (SBBC_MAILBOX_KEY);
if (strcmp(toc_key, TOCKEY_SOLSCIR) == 0)
return (SBBC_INTR_SC_KEY);
if (strcmp(toc_key, TOCKEY_SCSOLIR) == 0)
return (SBBC_SC_INTR_KEY);
if (strcmp(toc_key, TOCKEY_ENVINFO) == 0)
return (SBBC_ENVCTRL_KEY);
if (strcmp(toc_key, TOCKEY_SOLSCIE) == 0)
return (SBBC_INTR_SC_ENABLED_KEY);
if (strcmp(toc_key, TOCKEY_SCSOLIE) == 0)
return (SBBC_SC_INTR_ENABLED_KEY);
if (strcmp(toc_key, TOCKEY_SIGBLCK) == 0)
return (SBBC_SIGBLCK_KEY);
/* Unknown key */
return (-1);
}
/*
* Move the software interrupts from the old dip to the new dip
* when doing tunnel switch.
*/
static int
iosram_switch_intr()
{
sbbc_intrs_t *intr;
int intr_no;
int rc = 0;
ASSERT(MUTEX_HELD(&master_iosram->iosram_lock));
for (intr_no = 0; intr_no < SBBC_MAX_INTRS; intr_no++) {
intr = &master_iosram->intrs[intr_no];
if (intr->sbbc_intr_id) {
ddi_remove_softintr(intr->sbbc_intr_id);
if (ddi_add_softintr(master_iosram->iosram_sbbc->dip,
DDI_SOFTINT_HIGH,
&intr->sbbc_intr_id, NULL, NULL,
intr->sbbc_handler, intr->sbbc_arg)
!= DDI_SUCCESS) {
cmn_err(CE_WARN, "Can't add SBBC softint for "
"interrupt %x", intr_no << 1);
rc = EAGAIN;
}
}
}
return (rc);
}