sbbc.c revision 634ee33290d819010228493b9f821509cac62322
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Starcat PCI SBBC Nexus Driver.
*
* This source code's compiled binary runs on both a Starcat System
* Controller (SSC) and a Starcat Domain. One of the SBBC hardware
* registers is read during attach(9e) in order to determine which
* environment the driver is executing on.
*
* On both the SSC and the Domain, this driver provides nexus driver
* services to its Device Tree children. Note that the children in
* each environment are not necessarily the same.
*
* This driver allows one concurrent open(2) of its associated device
* ioctl(2)'s in order to read and write from the 2MB (PCI) space
* reserved for "SBBC Internal Registers". Among other things,
* devices such as Console Bus, I2C, EPLD, IOSRAM, and JTAG. The 2MB
* space is very sparse; EINVAL is returned if a reserved or unaligned
* address is specified in the ioctl(2).
*
* Note that the 2MB region reserved for SBBC Internal Registers is
* a subset of the 128MB of PCI address space addressable by the SBBC
* ASIC. Address space outside of the 2MB (such as the 64MB reserved
* for the Console Bus) is not accessible via this driver.
*
* Also, note that the SBBC Internal Registers are only read and
* written by the SSC; no process on the Domain accesses these
* registers. As a result, the registers are unmapped (when running
* on the Domain) near the end of attach(9e) processing. This conserves
* kernel virtual address space resources (as one instance of the driver
* is created for each Domain-side IO assembly). (To be complete, only
* one instance of the driver is created on the SSC).
*/
#include <sys/ddi_impldefs.h>
#include <sys/ddi_subrdefs.h>
#include <sys/autoconf.h>
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
/* driver entry point fn definitions */
/* configuration entry point fn definitions */
/* local utility routines */
/*
* NOTE - sbbc_offset_valid contains detailed address information taken from
* the Serengeti Architecture Programmer's Reference Manual. If any
* changes are made to the SBBC registers, this routine may need to be
* updated.
*/
/*
* function prototypes for bus ops routines:
*/
static int sbbc_get_ranges(struct sbbcsoft *);
static int sbbc_config4pci(struct sbbcsoft *);
static void sbbc_remove_reg_maps(struct sbbcsoft *);
/* debugging functions */
#ifdef DEBUG
#endif
/*
* For tracing, allocate space for the trace buffer
*/
#if defined(SBBC_TRACE)
struct sbbctrace *sbbctrace_ptr;
int sbbctrace_count;
#endif
/*
* Local declarations and variables
*/
static void *sbbcsoft_statep;
/* Determines whether driver is executing on System Controller or Domain */
int sbbc_scmode = FALSE;
/*
* ops stuff.
*/
static struct bus_ops sbbc_bus_ops = {
0,
0,
0,
NULL, /* (*bus_map_fault)() */
ddi_no_dma_freehdl, /* (*bus_dma_freehdl)() */
ddi_no_dma_bindhdl, /* (*bus_dma_bindhdl)() */
ddi_no_dma_unbindhdl, /* (*bus_dma_unbindhdl)() */
ddi_no_dma_flush, /* (*bus_dma_flush)() */
ddi_no_dma_win, /* (*bus_dma_win)() */
ddi_no_dma_mctl, /* (*bus_dma_ctl)() */
0, /* (*bus_get_eventcookie)(); */
0, /* (*bus_add_eventcall)(); */
0, /* (*bus_remove_eventcall)(); */
0, /* (*bus_post_event)(); */
0, /* (*bus_intr_ctl)(); */
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)(); */
sbbc_intr_ops /* (*bus_intr_op)(); */
};
/*
* cb_ops
*/
static struct cb_ops sbbc_cb_ops = {
sbbc_open, /* cb_open */
sbbc_close, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
sbbc_ioctl, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
NULL, /* cb_stream */
};
/*
* Declare ops vectors for auto configuration.
*/
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
sbbc_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
sbbc_attach, /* devo_attach */
sbbc_detach, /* devo_detach */
nodev, /* devo_reset */
&sbbc_cb_ops, /* devo_cb_ops */
&sbbc_bus_ops, /* devo_bus_ops */
nulldev /* devo_power */
};
/*
* Loadable module support.
*/
extern struct mod_ops mod_driverops;
static struct modldrv sbbcmodldrv = {
&mod_driverops, /* type of module - driver */
"PCI Sbbc Nexus Driver v%I%",
&sbbc_ops,
};
static struct modlinkage sbbcmodlinkage = {
};
int
_init(void)
{
int error;
sizeof (struct sbbcsoft), 1)) != 0)
return (error);
return (error);
}
int
_fini(void)
{
int error;
return (error);
}
int
{
}
static int
{
int instance;
char name[32];
struct ddi_device_acc_attr attr;
/* initialize tracing */
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
if (!(sbbcsoftp =
"to acquire sbbcsoftp for instance %d",
instance);
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
"for instance %d", instance);
return (DDI_FAILURE);
}
"sbbcsoftp for instance %d", instance);
return (DDI_FAILURE);
}
/*
* Read our ranges property from OBP to map children space.
* And setup the internal structure for a later use when
* a child gets initialized.
*/
if (sbbc_get_ranges(sbbcsoftp)) {
"ranges from OBP %d", instance);
return (DDI_FAILURE);
}
if (sbbc_config4pci(sbbcsoftp)) {
"sbbc on PCI %d", instance);
return (DDI_FAILURE);
}
MUTEX_DRIVER, (void *)NULL);
/* Map SBBC's Internal Registers */
sizeof (struct sbbc_regs_map), &attr,
instance);
goto failed;
}
#ifdef DEBUG
#endif
/*
* Read a hardware register to determine if we are executing on
* a Starcat System Controller or a Starcat Domain.
*/
if (sbbc_id_reg & SBBC_SC_MODE) {
sbbc_scmode = TRUE;
"in System Controller Mode.\n", instance);
/* initialize SBBC ASIC */
goto failed;
}
} else {
sbbc_scmode = FALSE;
"running in Domain Mode.\n", instance);
/* initialize SBBC ASIC before we unmap registers */
goto failed;
}
/*
* Access to SBBC registers is no longer needed. Unmap
* the registers to conserve kernel virtual address space.
*/
"SBBC registers\n", instance);
}
NULL) == DDI_FAILURE) {
goto failed;
}
return (DDI_SUCCESS);
return (DDI_FAILURE);
}
static int
{
int instance;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
if (!(sbbcsoftp =
"sbbc_detach: unable to get softstate %p",
(void *)sbbcsoftp);
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
(void *)sbbcsoftp);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Translate child's address into parents.
*/
static int
{
int rnumber, i, n;
int rval = DDI_SUCCESS;
int instance;
"mapping child %s, type %llx, off %llx, len %llx\n",
/*
* Handle the mapping according to its type.
*/
return (DDI_FAILURE);
case DDI_MT_REGSPEC:
/*
* We assume the register specification is in sbbc format.
* We must convert it into a PCI format regspec and pass
* the request to our parent.
*/
break;
case DDI_MT_RNUMBER:
/*
* map_type 0
* Get the "reg" property from the device node and convert
* it to our parent's format.
*/
/* get the requester's reg property */
"SBBC: couldn't get %s ranges property %d",
return (DDI_ME_RNUMBER_RANGE);
}
n = i / sizeof (sbbc_child_regspec_t);
kmem_free(child_regs, i);
return (DDI_ME_RNUMBER_RANGE);
}
break;
default:
return (DDI_ME_INVAL);
}
/* Adjust our reg property with offset and length */
if (len)
/*
* Combine this reg prop. into our parents PCI address using the ranges
* property.
*/
kmem_free(child_regs, i);
if (rval != DDI_SUCCESS)
return (rval);
p_map_request = *mp;
/* Send it to PCI nexus to map into the PCI space */
return (rval);
}
/* new intr_ops structure */
static 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:
}
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;
/* PCI nexus driver supports only fixed interrupts */
DDI_INTR_TYPE_FIXED : 0;
break;
default:
ret = DDI_ENOTSUP;
break;
}
return (ret);
}
static int
{
/* insert the sbbc isr wrapper instead */
return (DDI_FAILURE);
for (i = 0; i < MAX_SBBC_DEVICES; i++) {
if (sbbcsoftp->child_intr[i] == 0) {
break;
}
}
}
/*
* Restore original interrupt handler
* and arguments in interrupt handle.
*/
return (rval);
}
static int
{
return (DDI_FAILURE);
/* remove the sbbc isr wrapper instead */
for (i = 0; i < MAX_SBBC_DEVICES; i++) {
if (sbbcsoftp->child_intr[i]) {
/* put back child's inum */
break;
}
}
}
if (i >= MAX_SBBC_DEVICES) {
return (DDI_FAILURE);
}
return (rval);
}
return (rval);
}
static int
{
int instance, i;
int ret = DDI_SUCCESS;
return (DDI_FAILURE);
for (i = 0; i < MAX_SBBC_DEVICES; i++) {
if (sbbcsoftp->child_intr[i]) {
break;
}
}
if (i >= MAX_SBBC_DEVICES) {
return (DDI_FAILURE);
}
return (ret);
}
/* Update the interrupt state */
return (ret);
}
/*
* This entry point is called before a child's probe or attach is called.
* The arg pointer points to child's dev_info_t structure.
*/
static int
{
int i, n;
"Initializing %s, arg %x, op %x\n",
switch (op) {
case DDI_CTLOPS_INITCHILD: {
}
case DDI_CTLOPS_UNINITCHILD: {
}
case DDI_CTLOPS_REPORTDEV:
return (DDI_SUCCESS);
case DDI_CTLOPS_REGSIZE:
return (DDI_FAILURE);
}
n = i / sizeof (sbbc_child_regspec_t);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
case DDI_CTLOPS_NREGS:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Now pass the request up to our parent.
*/
}
/*
* The following routine uses ranges property, that was read earlier, and
* takes child's reg property, and computes the complete address and size
* for the PCI parent to map.
*/
static int
{
int b;
int rval = DDI_SUCCESS;
"Applying ranges for %s, rangep %llx, child_rp %llx, range %x\n",
/* Make sure the correct range is being mapped */
/* See if we fit in this range */
rangep->sbbc_phys_low) &&
<= (rangep->sbbc_phys_low +
/*
* Use the range entry to translate
* the SBBC physical address into the
* parents PCI space.
*/
rp->pci_phys_hi =
rp->pci_phys_low =
rp->pci_size_hi = 0;
rp->pci_size_low =
addr_offset));
break;
}
}
if (b == nrange) {
return (DDI_ME_REGSPEC_RANGE);
}
return (rval);
}
/*
* The following routine reads sbbc's ranges property from OBP and sets up
* its soft structure with it.
*/
static int
{
struct sbbc_pci_rangespec *rangep;
return (DDI_ME_REGSPEC_RANGE);
}
if (!nrange) {
return (DDI_FAILURE);
}
/* setup the soft structure with ranges info. */
return (DDI_SUCCESS);
}
/*
* Configure the SBBC for PCI
*/
static int
{
#ifdef DEBUG
if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
"sbbc_config4pci: sbbcsoftp %p\n", (void *)sbbcsoftp);
}
#endif
return (1);
#ifdef DEBUG
if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
"SBBC vendid %x, devid %x, comm %x, stat %x, revid %x\n",
}
#endif
#ifdef DEBUG
if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
}
#endif
return (0);
}
/* ARGSUSED0 */
int
{
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
ret = DDI_FAILURE;
} else {
ret = DDI_SUCCESS;
}
break;
case DDI_INFO_DEVT2INSTANCE:
ret = DDI_SUCCESS;
break;
default:
ret = DDI_FAILURE;
break;
}
return (ret);
}
/*ARGSUSED1*/
static int
{
int instance;
/* check privilege of caller process */
return (EPERM);
}
if (instance < 0)
return (ENXIO);
instance);
return (ENXIO);
/* check for exclusive access */
return (EBUSY);
}
return (0);
}
/*ARGSUSED1*/
static int
{
int instance;
if (instance < 0)
return (ENXIO);
instance);
/* wait till all output activity has ceased */
return (0);
}
/*ARGSUSED2*/
static int
int *rvalp)
{
return (ENXIO);
}
switch (cmd) {
case SBBC_SBBCREG_WR:
{
struct ssc_sbbc_regio sbbcregs;
if (sbbc_scmode == FALSE) {
/* then we're executing on Domain; Writes not allowed */
return (EINVAL);
}
return (ENXIO);
}
sizeof (struct ssc_sbbc_regio), mode)) {
(void *)arg);
return (EFAULT);
}
/*
* Bug #4287186: SBBC driver on cp1500 doesn't check length for
* reads or writes
* Note that I've also added a check to make sure the offset is
* valid, since misaligned (i.e. not on 16-byte boundary)
* accesses or accesses to "Reserved" register offsets are
* treated as unmapped by the SBBC.
*/
return (EINVAL);
}
}
break;
case SBBC_SBBCREG_RD:
{
struct ssc_sbbc_regio sbbcregs;
if (sbbc_scmode == FALSE) {
/* then we're executing on Domain; Reads not allowed */
return (EINVAL);
}
return (ENXIO);
}
sizeof (struct ssc_sbbc_regio), mode)) {
(void *)arg);
return (EFAULT);
}
/*
* Bug #4287186: SBBC driver on cp1500 doesn't check length for
* reads or writes
* Note that I've also added a check to make sure the offset is
* valid, since misaligned (i.e. not on 16-byte boundary)
* accesses or accesses to "Reserved" register offsets are
* treated as unmapped by the SBBC.
*/
return (EINVAL);
}
(void *)arg);
return (EFAULT);
}
}
break;
default:
return (ENOTTY);
}
return (DDI_SUCCESS);
}
static void
{
if (sbbcsoftp->pci_sbbc_map_handle)
}
static int
{
/* Mask all the interrupts until we are ready. */
0x00000000);
return (1);
}
/*
* The following routine is a generic routine to initialize any child of
* sbbc nexus driver information into parent private data structure.
*/
/* ARGSUSED0 */
static int
{
char name[10];
/*
* Initialize a child
* Set the address portion of the node name based on the
*/
slot = 1;
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
/*
* set child's addresses from the reg property into parent private
* data structure.
*/
return (DDI_SUCCESS);
}
/* ARGSUSED0 */
static int
{
return (DDI_SUCCESS);
}
/*
* The following routine is an interrupt service routine that is used
* as a wrapper to all the children requiring interrupt services.
*/
static uint_t
{
int i, rval;
for (i = 0; i < MAX_SBBC_DEVICES; i++) {
/*
* Check the interrupt status reg. to determine the cause.
*/
/*
* Check the error status reg. to determine the cause.
*/
if (sbbcsoftp->child_intr[i] &&
/*
* Dispatch the children interrupt service routines and
* look for someone to claim.
*/
if (rval == DDI_INTR_CLAIMED) {
return (rval);
}
}
}
/* for now do not claim since we know its not enabled */
return (DDI_INTR_UNCLAIMED);
}
/*
* This function checks an SBBC register offset to make sure that it is properly
* aligned (i.e. on a 16-byte boundary) and that it corresponds to an accessible
* register. Since the SBBC treates accesses to unaligned or reserved addresses
* as unmapped, failing to check for these would leave a loophole that could be
* used to crash the system.
*/
static int
/*
* Check for proper alignment first.
*/
if ((offset % 16) != 0) {
return (0);
}
/*
* Now start checking for the various reserved ranges.
* While sticking a bunch of constants in the code (rather than
* #define'd values) is usually best avoided, it would probably
* do more harm than good here. These values were taken from the
* Serengeti Architecture Programmer's Reference Manual dated
* August 10, 1999, pages 2-99 through 2-103. While there are
* various "clever" ways this check could be performed that would
* be slightly more efficient, arranging the code in this fashion
* should maximize maintainability.
*/
(offset == 0x06120) ||
(offset > 0x06320)) {
return (0);
}
return (1);
}
#ifdef DEBUG
void
{
char *s = NULL;
switch (flag) {
case SBBC_DBG_ATTACH:
s = "attach";
break;
case SBBC_DBG_DETACH:
s = "detach";
break;
case SBBC_DBG_CTLOPS:
s = "ctlops";
break;
case SBBC_DBG_INITCHILD:
s = "initchild";
break;
case SBBC_DBG_UNINITCHILD:
s = "uninitchild";
break;
case SBBC_DBG_BUSMAP:
s = "busmap";
break;
case SBBC_DBG_INTR:
s = "intr";
break;
case SBBC_DBG_INTROPS:
s = "intr_ops";
break;
case SBBC_DBG_PCICONF:
s = "pciconfig";
break;
case SBBC_DBG_MAPRANGES:
s = "mapranges";
break;
case SBBC_DBG_PROPERTIES:
s = "properties";
break;
case SBBC_DBG_OPEN:
s = "open";
break;
case SBBC_DBG_CLOSE:
s = "close";
break;
case SBBC_DBG_IOCTL:
s = "ioctl";
break;
default:
s = "Unknown debug flag";
break;
}
}
}
/*
* Dump the SBBC chip's Device ID Register
*/
int instance)
{
"FOUND SBBC(%d) Version %x, Partid %x, Manfid %x\n",
}
#endif /* DEBUG */