sysiosbus.c revision ffadc26e3de00e4c5377fd016a1ffc5dfbb4ff9c
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/ddi_impldefs.h>
#include <sys/ddi_implfuncs.h>
#include <sys/sysmacros.h>
#include <sys/autoconf.h>
#include <sys/sysiosbus.h>
#include <sys/sysioerr.h>
#include <sys/machsystm.h>
#include <sys/ddi_subrdefs.h>
#ifdef _STARFIRE
#include <sys/starfire.h>
#endif /* _STARFIRE */
/* Useful debugging Stuff */
#include <sys/nexusdebug.h>
/* Bitfield debugging definitions for this file */
#define SBUS_ATTACH_DEBUG 0x1
#define SBUS_SBUSMEM_DEBUG 0x2
#define SBUS_INTERRUPT_DEBUG 0x4
#define SBUS_REGISTERS_DEBUG 0x8
/*
* Interrupt registers table.
* This table is necessary due to inconsistencies in the sysio register
* layout. If this gets fixed in the chip, we can get rid of this stupid
* table.
*/
/* Construct the interrupt number array */
struct sbus_slot_entry *ino_table[] = {
};
/*
* This table represents the Fusion interrupt priorities. They range
* from 1 - 15, so we'll pattern the priorities after the 4M. We map Fusion
* interrupt number to system priority. The mondo number is used as an
* index into this table.
*/
int interrupt_priorities[] = {
-1, 2, 3, 5, 7, 9, 11, 13, /* Slot 0 sbus level 1 - 7 */
-1, 2, 3, 5, 7, 9, 11, 13, /* Slot 1 sbus level 1 - 7 */
-1, 2, 3, 5, 7, 9, 11, 13, /* Slot 2 sbus level 1 - 7 */
-1, 2, 3, 5, 7, 9, 11, 13, /* Slot 3 sbus level 1 - 7 */
4, /* Onboard SCSI */
6, /* Onboard Ethernet */
3, /* Onboard Parallel port */
-1, /* Not in use */
9, /* Onboard Audio */
-1, -1, -1, /* Not in use */
11, /* Onboard Floppy */
9, /* Thermal interrupt */
-1, -1, -1, /* Not is use */
10, /* Timer 0 (tick timer) */
14, /* Timer 1 (not used) */
15, /* Sysio UE ECC error */
10, /* Sysio CE ECC error */
10, /* Sysio Sbus error */
10, /* PM Wakeup */
};
static int intr_cntr_on;
/*
* Function prototypes.
*/
static int
static int
static void
static int
static int
static int
static int
static int
static void
static void
sbus_add_kstats(struct sbus_soft_state *);
static int
sbus_counters_kstat_update(kstat_t *, int);
extern int
extern int
extern int
static int
static int
static int
static int
static int
static int
void *result);
static int
static int
static void
int flag);
static void sbus_intrdist(void *);
static uint_t sbus_intr_reset(void *);
static int
#ifdef _STARFIRE
void
pc_ittrans_init(int, caddr_t *);
void
int
void
#endif /* _STARFIRE */
/*
* Configuration data structures
*/
static struct bus_ops sbus_bus_ops = {
0,
0,
0,
0, /* (*bus_get_eventcookie)(); */
0, /* (*bus_add_eventcall)(); */
0, /* (*bus_remove_eventcall)(); */
0, /* (*bus_post_event)(); */
0, /* (*bus_intr_control)(); */
0, /* (*bus_config)(); */
0, /* (*bus_unconfig)(); */
0, /* (*bus_fm_init)(); */
0, /* (*bus_fm_fini)(); */
0, /* (*bus_fm_access_enter)(); */
0, /* (*bus_fm_access_exit)(); */
0, /* (*bus_power)(); */
sbus_intr_ops /* (*bus_intr_op)(); */
};
static struct cb_ops sbus_cb_ops = {
nodev, /* open */
nodev, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
nodev, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* prop_op */
NULL,
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
DEVO_REV, /* devo_rev, */
0, /* refcnt */
ddi_no_info, /* info */
nulldev, /* identify */
nulldev, /* probe */
sbus_attach, /* attach */
sbus_detach, /* detach */
nodev, /* reset */
&sbus_cb_ops, /* driver operations */
&sbus_bus_ops, /* bus operations */
nulldev /* power */
};
/* global data */
void *sbusp; /* sbus soft state hook */
static int sbus_attachcnt = 0; /* number of instances attached */
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type of module. This one is a driver */
"SBus (sysio) nexus driver %I%", /* Name of module. */
&sbus_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
};
/*
* These are the module initialization routines.
*/
int
_init(void)
{
int error;
sizeof (struct sbus_soft_state), 1)) != 0)
return (error);
/*
* Initialize cpr soft state structure
*/
sizeof (uint64_t) * MAX_INO_TABLE_SIZE, 0)) != 0)
return (error);
/* Initialize global mutex */
return (mod_install(&modlinkage));
}
int
_fini(void)
{
int error;
return (error);
return (0);
}
int
{
}
/*ARGSUSED*/
static int
{
struct sbus_soft_state *softsp;
#ifdef DEBUG
debug_info = 1;
debug_print_level = 0;
#endif
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (error);
return (error);
return (error);
/*
* Restore Interrupt Mapping registers
*/
if (cpr_softsp != NULL) {
softsp->intr_mapping_reg, 0);
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
return (DDI_FAILURE);
/* Set the dip in the soft state */
"property.");
error = DDI_FAILURE;
goto bad;
}
/*
* The firmware maps in all 3 pages of the sysio chips device
* device registers and exports the mapping in the int-sized
* property "address". Read in this address and pass it to
* the subsidiary *_init functions, so we don't create extra
* mappings to the same physical pages and we don't have to
* retrieve the more than once.
*/
/*
* Implement new policy to start ignoring the "address" property
* due to new requirements from DR. The problem is that the contents
* of the "address" property contain vm mappings from OBP which needs
* to be recaptured into kernel vm. Instead of relying on a blanket
* recapture during boot time, we map psycho registers each time during
* attach and unmap the during detach. In some future point of time
* OBP will drop creating "address" property but this driver will
* will already not rely on this property any more.
*/
return (0);
}
return (DDI_FAILURE);
}
#ifdef notdef
/*
* This bit of code, plus the firmware, will tell us if
* the #size-cells infrastructure code works, to some degree.
* You should be able to use the firmware to determine if
* the address returned by ddi_map_regs maps the correct phys. pages.
*/
{
int rv;
rv);
} else {
" virtual address 0x%x\n", addr);
}
}
#endif /* notdef */
goto bad;
goto bad;
goto bad;
goto bad;
/* Init the pokefault mutex for sbus devices */
return (DDI_SUCCESS);
bad:
return (error);
}
/* ARGSUSED */
static int
{
int instance;
struct sbus_soft_state *softsp;
switch (cmd) {
case DDI_SUSPEND:
/*
* Allocate the cpr soft data structure to save the current
* state of the interrupt mapping registers.
* This structure will be deallocated after the system
* is resumed.
*/
!= DDI_SUCCESS)
return (DDI_FAILURE);
return (DDI_SUCCESS);
case DDI_DETACH:
return (sbus_do_detach(devi));
default:
return (DDI_FAILURE);
}
}
static int
{
struct sbus_soft_state *softsp;
/* disable the streamming cache */
goto err;
}
/* remove the interrupt handlers from the system */
goto err;
}
/* disable the IOMMU */
if (iommu_uninit(softsp)) {
goto err;
}
/* unmap register space if we have a handle */
}
/*
* remove counter kstats for this device
*/
/*
* if we are the last instance to detach we need to
* remove the picN kstats. We use sbus_attachcnt as a
* count of how many instances are still attached. This
* is protected by a mutex.
*/
sbus_attachcnt --;
if (sbus_attachcnt == 0) {
}
}
}
#ifdef _STARFIRE
/* free starfire specific soft intr mapping structure */
#endif /* _STARFIRE */
/* free the soft state structure */
return (DDI_SUCCESS);
err:
return (DDI_FAILURE);
}
static int
{
int i;
extern void set_intr_mapping_reg(int, uint64_t *, int);
int numproxy;
/*
* Simply add each registers offset to the base address
* to calculate the already mapped virtual address of
* the device register...
*
* define a macro for the pointer arithmetic; all registers
* are 64 bits wide and are defined as uint64_t's.
*/
softsp->sbus_ctrl_reg));
#ifdef _STARFIRE
/* Setup interrupt target translation for starfire */
#endif /* _STARFIRE */
/* Diag reg 2 is the next 64 bit word after diag reg 1 */
(void) sbus_resume_init(softsp, 0);
/*
* Set the initial burstsizes for each slot to all 1's. This will
* get changed at initchild time.
*/
for (i = 0; i < MAX_SBUS_SLOTS; i++)
/*
* Since SYSIO is used as an interrupt mastering device for slave
* only UPA devices, we call a dedicated kernel function to register
* The address of the interrupt mapping register for the slave device.
*
* devices then register 2nd mapping register with system.
*
* #upa-interrupt-proxies property defines how many UPA interrupt
* slaves a bridge is wired to support. Older systems that lack
* this property will default to 1.
*/
if (numproxy > 0)
FFB_MAPPING_REG), 1);
if (numproxy > 1)
EXP_MAPPING_REG), 2);
/* support for a 3 interrupt proxy would go here */
/* Turn on spurious interrupt counter if we're not a DEBUG kernel. */
#ifndef DEBUG
intr_cntr_on = 1;
#else
intr_cntr_on = 0;
#endif
return (DDI_SUCCESS);
}
/*
* This procedure is part of sbus initialization. It is called by
* sbus_init() and is invoked when the system is being resumed.
*/
static int
{
int i;
/*
* This shouldn't be needed when we have a real OBP PROM.
* (RAZ) Get rid of this later!!!
*/
#ifdef _STARFIRE
/*
* For Starfire, we need to program a
* constant odd value.
* Zero out the MID field before ORing
* We leave the LSB of the MID field intact since
* we cannot have a zero(even) MID value
*/
/*
* Program in the interrupt group number
* Here we have to convert the starfire
* 7 bit upaid into a 5bit value.
*/
*softsp->sysio_ctrl_reg |=
<< SYSIO_IGN;
#else
/* for the rest of sun4u's */
*softsp->sysio_ctrl_reg |=
/* Program in the interrupt group number */
*softsp->sysio_ctrl_reg |=
#endif /* _STARFIRE */
/*
* Set appropriate fields of sbus control register.
* Set DVMA arbitration enable for all devices.
*/
/* Calculate our burstsizes now so we don't have to do it later */
if (!resume) {
/* Set burstsizes to smallest value */
for (i = 0; i < MAX_SBUS_SLOTS; i++) {
/* Write out the burst size */
/* Flush any write buffers */
"configuration reg: 0x%p", (i > 3) ? i + 9 : i,
config));
}
} else {
/* Program the slot configuration registers */
for (i = 0; i < MAX_SBUS_SLOTS; i++) {
#ifndef lint
#endif /* !lint */
slave_burstsizes = 0;
if (softsp->sbus_slave_burstsizes[i] &
/* get the 64 bit burstsizes */
softsp->sbus_slave_burstsizes[i] >>
/* Turn on 64 bit PIO's on the sbus */
} else {
softsp->sbus_slave_burstsizes[i] &
}
/* Get burstsizes into sysio register format */
/* Program the burstsizes */
/* Flush any write buffers */
#ifndef lint
#endif /* !lint */
}
}
}
return (DDI_SUCCESS);
}
struct prop_ispec {
};
/*
* Create a sysio_parent_private_data structure from the ddi properties of
* the dev_info node.
*
* The "reg" and either an "intr" or "interrupts" properties are required
* if the driver wishes to create mappings or field interrupts on behalf
* of the device.
*
* The "reg" property is assumed to be a list of at least one triple
*
* <bustype, address, size>*1
*
* On pre-fusion machines, the "intr" property was the IPL for the system.
* Most new sbus devices post an "interrupts" property that corresponds to
* a particular bus level. All devices on fusion using an "intr" property
* will have it's contents translated into a bus level. Hence, "intr" and
* "interrupts on the fusion platform can be treated the same.
*
* The "interrupts" property is assumed to be a list of at least one
* n-tuples that describes the interrupt capabilities of the bus the device
* is connected to. For SBus, this looks like
*
* <SBus-level>*1
*
* (This property obsoletes the 'intr' property).
*
* The OBP_RANGES property is optional.
*/
static void
{
struct sysio_parent_private_data *pdptr;
int n;
/*
* Make the function idempotent, because name_child could
* be called multiple times on a node.
*/
return;
/*
* Handle the 'reg'/'registers' properties.
* "registers" overrides "reg", but requires that "reg" be exported,
* so we can handle wildcard specifiers. "registers" implies an
* sbus style device. "registers" implies that we insert the
* correct value in the regspec_bustype field of each spec for a real
* (non-pseudo) device node. "registers" is a s/w only property, so
* we inhibit the prom search for this property.
*/
reg_len = 0;
/*
* Save the underlying slot number and slot offset.
* Among other things, we use these to name the child node.
*/
if (reg_len != 0) {
}
rgstr_len = 0;
&rgstr_prop, &rgstr_len);
if (rgstr_len != 0) {
/*
* Convert wildcard "registers" for a real node...
* (Else, this is the wildcard prototype node)
*/
int i;
for (i = 0; i < n; ++i, ++rp)
}
if (reg_len != 0)
}
if (reg_len != 0) {
}
/*
* See if I have ranges.
*/
DDI_SUCCESS) {
}
}
/*
* Special handling for "sbusmem" pseudo device nodes.
* The special handling automatically creates the "reg"
* property in the sbusmem nodes, based on the parent's
* property so that each slot will automtically have a
* correctly sized "reg" property, once created,
* sbus_initchild does the rest of the work to init
* the child node.
*/
static int
{
int i, n;
char ident[10];
if (slot == -1) {
return (DDI_FAILURE);
}
/*
* Find the parent range corresponding to this "slot",
* so we can set the size of the child's "reg" property.
*/
for (i = 0, n = sparc_pd_getnrng(dip); i < n; i++) {
struct regspec r;
/* create reg property */
r.regspec_addr = 0;
child, "reg", (int *)&r,
sizeof (struct regspec) / sizeof (int));
/* create size property for slot */
(void) ddi_prop_update_int(DDI_DEV_T_NONE,
(void) ddi_prop_update_string(DDI_DEV_T_NONE,
child, "ident", ident);
return (DDI_SUCCESS);
}
}
return (DDI_FAILURE);
}
/*
* Nexus routine to name a child.
* It takes a dev_info node and a buffer, returns the name
* in the buffer.
*/
static int
{
/*
* Fill in parent-private data
*/
/*
* Name the device node using the underlying (prom) values
* of the first entry in the "reg" property. For SBus devices,
* the textual form of the name is <name>@<slot#>,<offset>.
* This must match the prom's pathname or mountroot, etc, won't
*/
name[0] = '\0';
}
return (DDI_SUCCESS);
}
/*
* Called from the bus_ctl op of sysio sbus nexus driver
* to implement the DDI_CTLOPS_INITCHILD operation. That is, it names
* the children of sysio sbusses based on the reg spec.
*
* Handles the following properties:
*
* Property value
* Name type
*
* reg register spec
* registers wildcard s/w sbus register spec (.conf file property)
* intr old-form interrupt spec
* interrupts new (bus-oriented) interrupt spec
* ranges range spec
*/
static int
{
char name[MAXNAMELEN];
int slot;
#ifndef lint
#endif /* !lint */
return (DDI_FAILURE);
}
/*
* If this is a s/w node defined with the "registers" property,
* this means that this is a wildcard specifier, whose properties
* get applied to all previously defined h/w nodes with the same
* name and same parent.
*/
if (ndi_dev_is_persistent_node(child) == 0) {
int len = 0;
return (DDI_FAILURE);
}
}
/* name the child */
/*
* If a pseudo node, attempt to merge it into a hw node.
* If merge is successful, we uinitialize the node and
* return failure, to allow caller to remove the node.
* The merge fails, this is a real pseudo node. Allow
* initchild to continue.
*/
if ((ndi_dev_is_persistent_node(child) == 0) &&
(void) sbus_uninitchild(child);
return (DDI_FAILURE);
}
/* Figure out the child devices slot number */
/* If we don't have a reg property, bypass slot specific programming */
#ifdef DEBUG
#endif /* DEBUG */
goto done;
}
/* Modify the onboard slot numbers if applicable. */
/* Get the slot configuration register for the child device. */
/*
* Program the devices slot configuration register for the
* appropriate slave burstsizes.
* The upper 16 bits of the slave-burst-sizes are for 64 bit sbus
* and the lower 16 bits are the burst sizes for 32 bit sbus. If
* we see that a device supports both 64 bit and 32 bit slave accesses,
* we default to 64 bit and turn it on in the slot config reg.
*
* For older devices, make sure we check the "burst-sizes" property
* too.
*/
DDI_PROP_DONTPASS, "slave-burst-sizes", 0)) != 0 ||
DDI_PROP_DONTPASS, "burst-sizes", 0)) != 0) {
uint_t burstsizes = 0;
/*
* If we only have 32 bit burst sizes from a previous device,
* mask out any burstsizes for 64 bit mode.
*/
0xffff0000u) == 0) &&
slave_burstsizes &= 0xffff;
}
/*
* If "slave-burst-sizes was defined but we have 0 at this
* point, we must have had 64 bit burstsizes, however a prior
* device can only burst in 32 bit mode. Therefore, we leave
* the burstsizes in the 32 bit mode and disregard the 64 bit.
*/
if (slave_burstsizes == 0)
goto done;
/*
* We and in the new burst sizes with that of prior devices.
* This ensures that we always take the least common
* denominator of the burst sizes.
*/
/* Get the 64 bit burstsizes. */
/* get the 64 bit burstsizes */
/* Turn on 64 bit PIO's on the sbus */
} else {
/* Turn off 64 bit PIO's on the sbus */
/* Get the 32 bit burstsizes if we don't have 64 bit. */
}
}
/* Get the burstsizes into sysio register format */
/* Reset reg in case we're scaling back */
/* Program the burstsizes */
#ifndef lint
#endif /* !lint */
}
done:
return (DDI_SUCCESS);
}
static int
{
struct sysio_parent_private_data *pdptr;
size_t n;
sizeof (struct rangespec));
}
/*
* Strip the node to properly convert it back to prototype form
*/
return (DDI_SUCCESS);
}
#ifdef DEBUG
int sbus_peekfault_cnt = 0;
int sbus_pokefault_cnt = 0;
#endif /* DEBUG */
static int
{
int err = DDI_SUCCESS;
/* Cautious access not supported. */
return (DDI_FAILURE);
/* Set up protected environment. */
} else
err = DDI_FAILURE;
/* Flush any sbus store buffers. */
/*
* Read the sbus error reg and see if a fault occured. If
* one has, give the SYSIO time to packetize the interrupt
* for the fault and send it out. The sbus error handler will
* 0 these fields when it's called to service the fault.
*/
/* Take down protected environment. */
no_trap();
#ifdef DEBUG
if (err == DDI_FAILURE)
#endif
return (err);
}
/*ARGSUSED*/
static int
void *result)
{
int err = DDI_SUCCESS;
/* No safe access except for peek is supported. */
return (DDI_FAILURE);
} else
err = DDI_FAILURE;
#ifdef DEBUG
if (err == DDI_FAILURE)
#endif
no_trap();
return (err);
}
static int
{
switch (op) {
case DDI_CTLOPS_INITCHILD:
case DDI_CTLOPS_UNINITCHILD:
return (sbus_uninitchild(arg));
case DDI_CTLOPS_IOMIN: {
/*
* The 'arg' value of nonzero indicates 'streaming' mode.
* If in streaming mode, pick the largest of our burstsizes
* available and say that that is our minimum value (modulo
* what mincycle is).
*/
else
}
case DDI_CTLOPS_REPORTDEV: {
char *msgbuf;
/*
* So we can do one atomic cmn_err call, we allocate a 4k
* buffer, and format the reportdev message into that buffer,
* send it to cmn_err, and then free the allocated buffer.
* If message is longer than 1k, the message is truncated and
* an error message is emitted (debug kernel only).
*/
#define REPORTDEV_BUFSIZE 1024
return (DDI_FAILURE);
"%s%d at %s%d: SBus%d ",
for (i = 0, n = sysio_pd_getnreg(rdip); i < n; i++) {
if (i != 0) {
}
"slot 0x%x offset 0x%x",
}
for (i = 0, n = i_ddi_get_nintrs(rdip); i < n; i++) {
if (i != 0) {
}
if (sbuslevel > MAX_SBUS_LEVEL)
" Onboard device ");
else
"sparc9 ipl %d", pri);
}
#ifdef DEBUG
"printed length 1024, real length %d", f_len);
}
#endif /* DEBUG */
return (DDI_SUCCESS);
}
case DDI_CTLOPS_SLAVEONLY:
return (DDI_FAILURE);
case DDI_CTLOPS_AFFINITY: {
return (DDI_FAILURE);
return (DDI_FAILURE);
}
case DDI_CTLOPS_DMAPMAPC:
return (DDI_FAILURE);
case DDI_CTLOPS_POKE:
case DDI_CTLOPS_PEEK:
result));
case DDI_CTLOPS_DVMAPAGESIZE:
return (DDI_SUCCESS);
default:
}
}
static int
{
int slot = -1;
/*
* look for the node that's a direct child of this Sbus node.
*/
}
/*
* If there is one, get the slot number of *my* child
*/
return (slot);
}
/*
* This is the sbus interrupt routine wrapper function. This function
* installs itself as a child devices interrupt handler. It's function is
* to dispatch a child devices interrupt handler, and then
* reset the interrupt clear register for the child device.
*
* Warning: This routine may need to be implemented as an assembly level
* routine to improve performance.
*/
#define MAX_INTR_CNT 10
static uint_t
{
struct sbus_wrapper_arg *intr_info;
struct sbus_intr_handler *intr_handler;
while (intr_handler) {
int r;
continue;
}
intr_return |= r;
}
/* Set the interrupt state machine to idle */
if (intr_return == DDI_INTR_UNCLAIMED) {
(*spurious_cntr)++;
if (*spurious_cntr < MAX_INTR_CNT) {
if (intr_cntr_on)
return (DDI_INTR_CLAIMED);
}
#ifdef DEBUG
"interrupt level %d", MAX_INTR_CNT,
}
#endif
/*
* Reset spurious counter once we acknowledge
* it to the system level.
*/
*spurious_cntr = (uchar_t)0;
} else {
*spurious_cntr = (uchar_t)0;
}
return (intr_return);
}
/*
* add_intrspec - Add an interrupt specification.
*/
static int
{
volatile uint64_t *mondo_vec_reg;
volatile uint64_t tmp_mondo_vec;
volatile uint64_t *intr_state_reg;
int ino;
struct sbus_wrapper_arg *sbus_arg;
struct sbus_intr_handler *intr_handler;
/* Interrupt state machine reset flag */
int reset_ism_register = 1;
int ret = DDI_SUCCESS;
/* Check if we have a valid sbus slot address */
slot);
return (DDI_FAILURE);
}
ddi_get_instance(rdip)));
/* Xlate the interrupt */
return (DDI_FAILURE);
}
/* get the ino number */
/*
* This is an intermediate step in identifying
* the exact bits which represent the device in the interrupt
* state diagnostic register.
*/
if (ino > MAX_MONDO_EXTERNAL) {
} else {
}
/* Allocate a nexus interrupt data structure */
/*
* Grab this lock here. So it will protect the poll list.
*/
/* Check if we have a poll list to deal with */
if (sbus_arg) {
#ifdef lint
#endif
"0x%p\n", sbus_arg));
/*
* Two bits per ino in the diagnostic register
* indicate the status of its interrupt.
* 0 - idle, 1 - transmit, 3 - pending.
*/
while (((*intr_state_reg >>
/* empty */;
reset_ism_register = 0;
} else {
KM_SLEEP);
/*
* No handler added yet in the interrupt vector
* table for this ino.
* Install the nexus interrupt wrapper in the
* system. The wrapper will call the device
* interrupt handler.
*/
/*
* Restore original interrupt handler
* and arguments in interrupt handle.
*/
if (ret != DDI_SUCCESS) {
goto done;
}
if ((slot >= EXT_SBUS_SLOTS) ||
cpu_id = intr_dist_cpuid();
#ifdef _STARFIRE
#else
cpu_id << IMR_TID_SHIFT;
"mapping reg 0x%lx\n", tmp_mondo_vec));
#endif /* _STARFIRE */
} else {
/*
* There is already a different
* ino programmed at this IMR.
* Just read the IMR out to get the
* correct MID target.
*/
"mapping reg 0x%lx\n", tmp_mondo_vec));
}
"0x%p\n", sbus_arg));
}
/*
* Program the ino vector accordingly. This MUST be the
* last thing we do. Once we program the ino, the device
* may begin to interrupt. Add this hardware interrupt to
* the interrupt lists, and get the CPU to target it at.
*/
/* Force the interrupt state machine to idle. */
if (reset_ism_register) {
}
/* Store it in the hardware reg. */
/* Flush store buffers */
done:
return (ret);
}
static void
struct sbus_wrapper_arg *sbus_arg)
{
if (sbus_arg) {
while (listp) {
if (prevp)
else {
}
sizeof (struct sbus_intr_handler));
break;
}
}
}
}
/*
* remove_intrspec - Remove an interrupt specification.
*/
/*ARGSUSED*/
static void
{
volatile uint64_t *mondo_vec_reg;
volatile uint64_t *intr_state_reg;
#ifndef lint
#endif /* !lint */
struct sbus_wrapper_arg *sbus_arg;
/* Grab the mutex protecting the poll list */
/* Xlate the interrupt */
goto done;
}
/* Turn off the valid bit in the mapping register. */
*mondo_vec_reg &= ~INTERRUPT_VALID;
#ifndef lint
#endif /* !lint */
/* Get our bit position for checking intr pending */
if (ino > MAX_MONDO_EXTERNAL) {
} else {
}
!panicstr)
/* empty */;
/* Return if the slot is invalid */
goto done;
}
/* Decrement the intr handler count on this slot */
"ino 0x%x, sbus_arg 0x%p intr cntr %d\n", softsp,
/* If we still have a list, we're done. */
/*
* If other devices are still installed for this slot, we need to
* turn the valid bit back on.
*/
#ifndef lint
#endif /* !lint */
}
#ifdef _STARFIRE
/* Do cleanup for interrupt target translation */
#endif /* _STARFIRE */
}
/* Free up the memory used for the sbus interrupt handler */
"0x%p\n", sbus_arg));
}
done:
}
/*
* We're prepared to claim that the interrupt string is in
* the form of a list of <SBusintr> specifications, or we're dealing
* with on-board devices and we have an interrupt_number property which
* gives us our mondo number.
* Translate the sbus levels or mondos into sysiointrspecs.
*/
static int
{
int ret = DDI_SUCCESS;
/*
* Create the sysio ino number. onboard devices will have
* an "interrupts" property, that is equal to the ino number.
* If the devices are from the
* expansion slots, we construct the ino number by putting
* the slot number in the upper three bits, and the sbus
* interrupt level in the lower three bits.
*/
if (level > MAX_SBUS_LEVEL) {
} else {
/* Construct ino from slot and interrupts */
ret = DDI_FAILURE;
goto done;
}
if (slot >= MAX_SBUS_SLOT_ADDR) {
ret = DDI_FAILURE;
goto done;
}
}
/* Sanity check the inos range */
if (ino >= MAX_INO_TABLE_SIZE) {
ret = DDI_FAILURE;
goto done;
}
/* Sanity check the inos value */
ret = DDI_FAILURE;
goto done;
}
if (*pil == 0) {
#define SOC_PRIORITY 5
/* The sunfire i/o board has a soc in the printer slot */
*pil = SOC_PRIORITY;
} else {
/* Figure out the pil associated with this interrupt */
}
}
/* Or in the upa_id into the interrupt group number field */
"device %s Mondo: 0x%x, ino: 0x%x, Pil: 0x%x, sbus level: 0x%x\n",
done:
return (ret);
}
/* new intr_ops structure */
int
{
int ret = DDI_SUCCESS;
switch (intr_op) {
case DDI_INTROP_GETCAP:
*(int *)result = DDI_INTR_FLAG_LEVEL;
break;
case DDI_INTROP_ALLOC:
break;
case DDI_INTROP_FREE:
break;
case DDI_INTROP_GETPRI:
/* Xlate the interrupt */
}
break;
case DDI_INTROP_SETPRI:
break;
case DDI_INTROP_ADDISR:
break;
case DDI_INTROP_REMISR:
break;
case DDI_INTROP_ENABLE:
break;
case DDI_INTROP_DISABLE:
break;
case DDI_INTROP_NINTRS:
case DDI_INTROP_NAVAIL:
break;
case DDI_INTROP_SETCAP:
case DDI_INTROP_SETMASK:
case DDI_INTROP_CLRMASK:
case DDI_INTROP_GETPENDING:
ret = DDI_ENOTSUP;
break;
/* Sbus nexus driver supports only fixed interrupts */
DDI_INTR_TYPE_FIXED : 0;
break;
default:
break;
}
return (ret);
}
/*
* of the interrupt mapping registers.
*/
static void
int save)
{
int i;
volatile uint64_t *mondo_vec_reg;
for (i = 0; i < MAX_INO_TABLE_SIZE; i++) {
if (save) {
if (*mondo_vec_reg & INTERRUPT_VALID) {
cpr_softsp[i] = *mondo_vec_reg;
}
} else {
if (cpr_softsp[i]) {
*mondo_vec_reg = cpr_softsp[i];
}
}
}
}
}
/*
* sbus_intrdist
*
* This function retargets active interrupts by reprogramming the mondo
* vec register. If the CPU ID of the target has not changed, then
* the mondo is not reprogrammed. The routine must hold the mondo
* lock for this instance of the sbus.
*/
static void
sbus_intrdist(void *arg)
{
struct sbus_soft_state *softsp;
volatile uint64_t *mondo_vec_reg;
volatile uint64_t *intr_state_reg;
/* extract the soft state pointer */
continue;
/* Don't reprogram the same register twice */
if (mondo_vec_reg == last_mondo_vec_reg)
continue;
if ((*mondo_vec_reg & INTERRUPT_VALID) == 0)
continue;
cpu_id = intr_dist_cpuid();
#ifdef _STARFIRE
/*
* For Starfire it is a pain to check the current target for
* the mondo since we have to read the PC asics ITTR slot
* assigned to this mondo. It will be much easier to assume
* the current target is always different and do the target
* reprogram all the time.
*/
#else
/* It is the same, don't reprogram */
return;
}
#endif /* _STARFIRE */
/* So it's OK to reprogram the CPU target */
/* turn off valid bit and wait for the state machine to idle */
*mondo_vec_reg &= ~INTERRUPT_VALID;
#ifdef lint
#endif /* lint */
if (mondo > MAX_MONDO_EXTERNAL) {
/*
* Loop waiting for state machine to idle. Do not keep
* looping on a panic so that the system does not hang.
*/
INT_PENDING) && !panicstr)
/* empty */;
} else {
int int_pending = 0; /* interrupts pending */
/*
* Shift over to first bit for this Sbus slot, 16
* bits per slot, bits 0-1 of each slot are reserved.
*/
/*
* Make sure interrupts for levels 1-7 of this slot
* are not pending.
*/
do {
int level; /* Sbus interrupt level */
int shift; /* # of bits to shift */
int_pending = 0;
0x3) == INT_PENDING) {
int_pending = 1;
break;
}
}
} while (int_pending && !panicstr);
}
/* re-target the mondo and turn it on */
#ifdef _STARFIRE
cpu_id, mondo_vec_reg) <<
#else
#endif /* _STARFIRE */
/* write it back to the hardware. */
/* flush the hardware buffers. */
tmpreg = *mondo_vec_reg;
#ifdef lint
#endif /* lint */
}
}
/*
* Reset interrupts to IDLE. This function is called during
* panic handling after redistributing interrupts; it's needed to
* support dumping to network devices after 'sync' from OBP.
*
* N.B. This routine runs in a context where all other threads
* are permanently suspended.
*/
static uint_t
sbus_intr_reset(void *arg)
{
struct sbus_soft_state *softsp;
volatile uint64_t *mondo_clear_reg;
continue;
}
}
return (BF_NONE);
}
/*
* called from sbus_add_kstats() to create a kstat for each %pic
* that the SBUS supports. These (read-only) kstats export the
* event names that each %pic supports.
*
* if we fail to create any of these kstats we must remove any
* that we have already created and return;
*
* NOTE: because all sbus devices use the same events we only
* need to create the picN kstats once. All instances can
* use the same picN kstats.
*
* The flexibility exists to allow each device specify it's
* own events by creating picN kstats with the instance number
* set to ddi_get_instance(softsp->dip).
*
* When searching for a picN kstat for a device you should
* first search for a picN kstat using the instance number
* of the device you are interested in. If that fails you
* should use the first picN kstat found for that device.
*/
static void
{
/*
* SBUS Performance Events.
*
* We declare an array of event-names and event-masks.
* The num of events in this array is AC_NUM_EVENTS.
*/
{"dvma_stream_rd", 0x0}, {"dvma_stream_wr", 0x1},
{"dvma_const_rd", 0x2}, {"dvma_const_wr", 0x3},
{"dvma_tlb_misses", 0x4}, {"dvma_stream_buf_mis", 0x5},
{"dvma_cycles", 0x6}, {"dvma_bytes_xfr", 0x7},
{"interrupts", 0x8}, {"upa_inter_nack", 0x9},
{"pio_reads", 0xA}, {"pio_writes", 0xB},
{"sbus_reruns", 0xC}, {"pio_cycles", 0xD}
};
/*
* We declare an array of clear masks for each pic.
* These masks are used to clear the %pcr bits for
* each pic.
*/
/* pic0 */
/* pic1 */
};
struct kstat_named *sbus_pic_named_data;
char pic_name[30];
int pic_shift = 0;
/*
* create the picN kstat. The size of this kstat is
* SBUS_NUM_EVENTS + 1 for the clear_event_mask
*/
pic_name);
/* remove pic0 kstat if pic1 create fails */
if (pic == 1) {
sbus_picN_ksp[0] = NULL;
}
return;
}
/*
* when we are writing pcr_masks to the kstat we need to
* shift bits left by 8 for pic1 events.
*/
if (pic == 1)
pic_shift = 8;
/*
* for each picN event we need to write a kstat record
* (name = EVENT, value.ui64 = PCR_MASK)
*/
/* pcr_mask */
/* event-name */
}
/*
* we add the clear_pic event and mask as the last
* record in the kstat
*/
/* pcr mask */
/* event-name */
}
}
static void
{
struct kstat *sbus_counters_ksp;
struct kstat_named *sbus_counters_named_data;
/*
* Create the picN kstats if we are the first instance
* to attach. We use sbus_attachcnt as a count of how
* many instances have attached. This is protected by
* a mutex.
*/
if (sbus_attachcnt == 0)
sbus_attachcnt ++;
/*
* A "counter" kstat is created for each sbus
* instance that provides access to the %pcr and %pic
* registers for that instance.
*
* The size of this kstat is SBUS_NUM_PICS + 1 for %pcr
*/
KSTAT_FLAG_WRITABLE)) == NULL) {
return;
}
/* initialize the named kstats */
"pcr", KSTAT_DATA_UINT64);
"pic0", KSTAT_DATA_UINT64);
"pic1", KSTAT_DATA_UINT64);
/* update the sofstate */
}
static int
{
struct kstat_named *sbus_counters_data;
struct sbus_soft_state *softsp;
if (rw == KSTAT_WRITE) {
/*
* Write the pcr value to the softsp->sbus_pcr.
* The pic register is read-only so we don't
* attempt to write to it.
*/
} else {
/*
* Read %pcr and %pic register values and write them
* into counters kstat.
*
* Due to a hardware bug we need to right shift the %pcr
* by 4 bits. This is only done when reading the %pcr.
*
*/
/* pcr */
/*
* sbus pic register:
* (63:32) = pic0
* (31:00) = pic1
*/
/* pic0 */
/* pic1 */
}
return (0);
}
static int
{
int ino;
struct sbus_wrapper_arg *sbus_arg;
struct sbus_intr_handler *intr_handler;
/* Xlate the interrupt */
return (DDI_FAILURE);
}
while (intr_handler) {
return (DDI_SUCCESS);
}
}
return (DDI_FAILURE);
}