sbbc.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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
* 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"
/*
* Starcat PCI SBBC Device Nexus Driver that provides interfaces into
* Console Bus, I2C, Error/Intr. EPLD, IOSRAM, and JTAG.
*/
#include <sys/types.h>
#include <sys/conf.h> /* req. by dev_ops flags MTSAFE etc. */
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_subrdefs.h>
#include <sys/pci.h>
#include <sys/nexusintr_impl.h>
#include <sys/pci/pci_nexus.h>
#include <sys/autoconf.h>
#include <sys/cmn_err.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/modctl.h>
#include <sys/stat.h>
#include <sys/sbbcreg.h> /* hw description */
#include <sys/sbbcvar.h> /* driver description */
#include <sys/sbbcio.h> /* ioctl description */
#define getprop(dip, name, addr, intp) \
ddi_getlongprop(DDI_DEV_T_NONE, (dip), DDI_PROP_DONTPASS, \
(name), (caddr_t)(addr), (intp))
/* driver entry point fn definitions */
static int sbbc_open(dev_t *, int, int, cred_t *);
static int sbbc_close(dev_t, int, int, cred_t *);
static int sbbc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
/* configuration entry point fn definitions */
static int sbbc_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int sbbc_attach(dev_info_t *, ddi_attach_cmd_t);
static int sbbc_detach(dev_info_t *, ddi_detach_cmd_t);
/* 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.
*/
static int sbbc_offset_valid(uint32_t offset);
/*
* function prototypes for bus ops routines:
*/
static int sbbc_busmap(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t offset, off_t len, caddr_t *addrp);
static int sbbc_ctlops(dev_info_t *dip, dev_info_t *rdip,
ddi_ctl_enum_t op, void *arg, void *result);
static int sbbc_intr_ops(dev_info_t *dip, dev_info_t *rdip,
ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);
static int sbbc_add_intr_impl(dev_info_t *dip, dev_info_t *rdip,
ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);
static int sbbc_remove_intr_impl(dev_info_t *dip, dev_info_t *rdip,
ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);
static int sbbc_update_intr_state(dev_info_t *dip, dev_info_t *rdip,
ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);
static int sbbc_apply_range(struct sbbcsoft *sbbc_p, dev_info_t *rdip,
sbbc_child_regspec_t *child_rp, pci_regspec_t *rp);
static int sbbc_init(struct sbbcsoft *);
static uint_t sbbc_intr_wrapper(caddr_t arg);
static int sbbc_get_ranges(struct sbbcsoft *);
static int sbbc_config4pci(struct sbbcsoft *);
static int sbbc_initchild(dev_info_t *, dev_info_t *, dev_info_t *);
static int sbbc_uninitchild(dev_info_t *, dev_info_t *);
static void sbbc_remove_reg_maps(struct sbbcsoft *);
/* debugging functions */
#ifdef DEBUG
uint32_t sbbc_dbg_flags = 0x0;
static void sbbc_dbg(uint32_t flag, dev_info_t *dip, char *fmt,
uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5);
#endif
/*
* For tracing, allocate space for the trace buffer
*/
#if defined(SBBC_TRACE)
struct sbbctrace sbbctrace_buffer[NSBBCTRACE+1];
struct sbbctrace *sbbctrace_ptr;
int sbbctrace_count;
#endif
/*
* Local declarations and variables
*/
static void *sbbcsoft_statep;
int sbbc_scmode = FALSE;
/*
* ops stuff.
*/
static struct bus_ops sbbc_bus_ops = {
BUSO_REV,
sbbc_busmap,
0,
0,
0,
NULL, /* (*bus_map_fault)() */
ddi_no_dma_map,
ddi_no_dma_allochdl,
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)() */
sbbc_ctlops,
ddi_bus_prop_op,
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 */
(int)(D_NEW | D_MP) /* cb_flag */
};
/*
* Declare ops vectors for auto configuration.
*/
struct dev_ops sbbc_ops = {
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 = {
MODREV_1,
&sbbcmodldrv,
NULL
};
int
_init(void)
{
int error;
if ((error = ddi_soft_state_init(&sbbcsoft_statep,
sizeof (struct sbbcsoft), 1)) != 0)
return (error);
if ((error = mod_install(&sbbcmodlinkage)) != 0)
ddi_soft_state_fini(&sbbcsoft_statep);
return (error);
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&sbbcmodlinkage)) == 0)
ddi_soft_state_fini(&sbbcsoft_statep);
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&sbbcmodlinkage, modinfop));
}
static int
sbbc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance;
char name[32];
struct sbbcsoft *sbbcsoftp;
struct ddi_device_acc_attr attr;
uint32_t sbbc_id_reg = 0;
uint16_t sbbc_id_reg_partid;
uint16_t sbbc_id_reg_manfid;
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
/* initialize tracing */
SBBCTRACEINIT();
SBBC_DBG0(SBBC_DBG_ATTACH, dip, "Attaching\n");
instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
if (!(sbbcsoftp =
ddi_get_soft_state(sbbcsoft_statep, instance))) {
cmn_err(CE_WARN,
"sbbc_attach:resume: unable to acquire sbbcsoftp for instance %d",
instance);
return (DDI_FAILURE);
}
mutex_enter(&sbbcsoftp->umutex);
if (!sbbcsoftp->suspended) {
mutex_exit(&sbbcsoftp->umutex);
return (DDI_FAILURE);
}
sbbcsoftp->suspended = 0;
mutex_exit(&sbbcsoftp->umutex);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
if (ddi_soft_state_zalloc(sbbcsoft_statep, instance) != 0) {
cmn_err(CE_WARN,
"sbbc_attach: Unable to allocate statep for instance %d",
instance);
return (DDI_FAILURE);
}
sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance);
if (sbbcsoftp == NULL) {
cmn_err(CE_WARN,
"sbbc_attach: Unable to acquire sbbcsoftp for instance %d",
instance);
ddi_soft_state_free(sbbcsoft_statep, instance);
return (DDI_FAILURE);
}
sbbcsoftp->instance = instance;
sbbcsoftp->dip = dip;
sbbcsoftp->oflag = FALSE;
/*
* 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)) {
cmn_err(CE_WARN,
"sbbc_attach: Unable to read sbbc ranges from OBP %d", instance);
ddi_soft_state_free(sbbcsoft_statep, instance);
return (DDI_FAILURE);
}
if (sbbc_config4pci(sbbcsoftp)) {
cmn_err(CE_WARN,
"sbbc_attach: Unable to configure sbbc on PCI %d", instance);
kmem_free(sbbcsoftp->rangep, sbbcsoftp->range_len);
ddi_soft_state_free(sbbcsoft_statep, instance);
return (DDI_FAILURE);
}
/*
* Map SBBC's internal registers used by hardware access daemon.
*/
/* map the whole thing since OBP does not map individual devices */
if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sbbcsoftp->pci_sbbc_map,
0, 0, &attr, &sbbcsoftp->pci_sbbc_map_handle) != DDI_SUCCESS) {
cmn_err(CE_WARN, "(%d):sbbc_attach failed to map sbbc_reg",
instance);
goto failed;
}
sbbc_id_reg = ddi_get32(sbbcsoftp->pci_sbbc_map_handle,
(uint32_t *)
&sbbcsoftp->pci_sbbc_map->sbbc_internal_regs.device_conf);
if (sbbc_id_reg & SBBC_SC_MODE) {
SBBC_DBG5(SBBC_DBG_ATTACH, dip,
"Mapped sbbc %llx, regs %llx, eregs %llx, sram %llx, consbus %llx\n",
sbbcsoftp->pci_sbbc_map,
&sbbcsoftp->pci_sbbc_map->sbbc_internal_regs,
&sbbcsoftp->pci_sbbc_map->echip_regs,
&sbbcsoftp->pci_sbbc_map->sram[0],
&sbbcsoftp->pci_sbbc_map->consbus);
sbbc_id_reg = ddi_get32(sbbcsoftp->pci_sbbc_map_handle,
(uint32_t *)
&sbbcsoftp->pci_sbbc_map->sbbc_internal_regs.devid);
sbbc_id_reg_partid = ((sbbc_id_reg << 4) >> 16);
sbbc_id_reg_manfid = ((sbbc_id_reg << 20) >> 21);
SBBC_DBG4(SBBC_DBG_ATTACH, dip,
"FOUND SBBC(%d) Version %x, Partid %x, Manfid %x\n",
instance, (sbbc_id_reg >> 28), sbbc_id_reg_partid,
sbbc_id_reg_manfid);
sbbc_scmode = TRUE;
SBBC_DBG1(SBBC_DBG_ATTACH, dip,
"SBBC(%d) nexus running in System Controller Mode.\n",
instance);
/*
* There will be only one SBBC instance on SC and no
* chosen node stuff to deal with :-)
*/
} else {
/* The code below needs to be common with SC mode above */
SBBC_DBG4(SBBC_DBG_ATTACH, dip,
"Mapped sbbc %llx, regs %llx, eregs %llx, sram %llx\n",
sbbcsoftp->pci_sbbc_map,
&sbbcsoftp->pci_sbbc_map->sbbc_internal_regs,
&sbbcsoftp->pci_sbbc_map->echip_regs,
&sbbcsoftp->pci_sbbc_map->sram[0]);
sbbc_id_reg = ddi_get32(sbbcsoftp->pci_sbbc_map_handle,
(uint32_t *)
&sbbcsoftp->pci_sbbc_map->sbbc_internal_regs.devid);
sbbc_id_reg_partid = ((sbbc_id_reg << 4) >> 16);
sbbc_id_reg_manfid = ((sbbc_id_reg << 20) >> 21);
SBBC_DBG4(SBBC_DBG_ATTACH, dip,
"FOUND SBBC(%d) Version %x, Partid %x, Manfid %x\n",
instance, (sbbc_id_reg >> 28), sbbc_id_reg_partid,
sbbc_id_reg_manfid);
sbbc_scmode = FALSE;
SBBC_DBG1(SBBC_DBG_ATTACH, dip,
"SBBC(%d) nexus running in Domain Mode.\n",
instance);
/*
* There will be only one SBBC instance on SC and no
* chosen node stuff to deal with :-)
*/
}
mutex_init(&sbbcsoftp->umutex, NULL, MUTEX_DRIVER, (void *)NULL);
mutex_init(&sbbcsoftp->sbbc_intr_mutex, NULL,
MUTEX_DRIVER, (void *)NULL);
/* initialize sbbc */
if (!sbbc_init(sbbcsoftp)) {
goto remlock;
}
(void) sprintf(name, "sbbc%d", instance);
if (ddi_create_minor_node(dip, name, S_IFCHR, instance, NULL,
NULL) == DDI_FAILURE) {
ddi_remove_minor_node(dip, NULL);
goto remlock;
}
ddi_report_dev(dip);
SBBC_DBG0(SBBC_DBG_ATTACH, dip, "Attached successfully\n");
return (DDI_SUCCESS);
remlock:
mutex_destroy(&sbbcsoftp->sbbc_intr_mutex);
mutex_destroy(&sbbcsoftp->umutex);
failed:
sbbc_remove_reg_maps(sbbcsoftp);
kmem_free(sbbcsoftp->rangep, sbbcsoftp->range_len);
ddi_soft_state_free(sbbcsoft_statep, instance);
SBBC_DBG0(SBBC_DBG_ATTACH, dip, "Attach failed\n");
return (DDI_FAILURE);
}
static int
sbbc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance;
struct sbbcsoft *sbbcsoftp;
SBBCTRACE(sbbc_detach, 'DETA', dip);
instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
if (!(sbbcsoftp =
ddi_get_soft_state(sbbcsoft_statep, instance))) {
cmn_err(CE_WARN,
"sbbc_detach: unable to get softstate %p",
(void *)sbbcsoftp);
return (DDI_FAILURE);
}
mutex_enter(&sbbcsoftp->umutex);
if (sbbcsoftp->suspended) {
mutex_exit(&sbbcsoftp->umutex);
return (DDI_FAILURE);
}
sbbcsoftp->suspended = 1;
mutex_exit(&sbbcsoftp->umutex);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance))) {
cmn_err(CE_WARN, "sbbc_detach: unable to get softstate %p",
(void *)sbbcsoftp);
return (DDI_FAILURE);
}
ddi_remove_minor_node(dip, NULL);
mutex_destroy(&sbbcsoftp->sbbc_intr_mutex);
mutex_destroy(&sbbcsoftp->umutex);
sbbc_remove_reg_maps(sbbcsoftp);
kmem_free(sbbcsoftp->rangep, sbbcsoftp->range_len);
ddi_soft_state_free(sbbcsoft_statep, instance);
return (DDI_SUCCESS);
}
/*
* Translate child's address into parents.
*/
static int
sbbc_busmap(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t off, off_t len, caddr_t *addrp)
{
struct sbbcsoft *sbbcsoftp;
sbbc_child_regspec_t *child_rp, *child_regs;
pci_regspec_t pci_reg;
ddi_map_req_t p_map_request;
int rnumber, i, n;
int rval = DDI_SUCCESS;
int instance;
SBBC_DBG4(SBBC_DBG_BUSMAP, dip,
"mapping child %s, type %llx, off %llx, len %llx\n",
ddi_driver_name(rdip), mp->map_type, off, len);
SBBCTRACE(sbbc_busmap, 'BMAP', mp);
/*
* Handle the mapping according to its type.
*/
instance = ddi_get_instance(dip);
if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance)))
return (DDI_FAILURE);
switch (mp->map_type) {
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.
*/
child_rp = (sbbc_child_regspec_t *)mp->map_obj.rp;
break;
case DDI_MT_RNUMBER:
/*
* map_type 0
* Get the "reg" property from the device node and convert
* it to our parent's format.
*/
rnumber = mp->map_obj.rnumber;
/* get the requester's reg property */
if (ddi_getlongprop(DDI_DEV_T_NONE, rdip, DDI_PROP_DONTPASS,
"reg", (caddr_t)&child_regs, &i) != DDI_SUCCESS) {
cmn_err(CE_WARN,
"SBBC: couldn't get %s ranges property %d",
ddi_get_name(sbbcsoftp->dip), instance);
return (DDI_ME_RNUMBER_RANGE);
}
n = i / sizeof (sbbc_child_regspec_t);
if (rnumber < 0 || rnumber >= n) {
kmem_free(child_regs, i);
return (DDI_ME_RNUMBER_RANGE);
}
child_rp = &child_regs[rnumber];
break;
default:
return (DDI_ME_INVAL);
}
/* Adjust our reg property with offset and length */
child_rp->addr_low += off;
if (len)
child_rp->size = len;
/*
* Combine this reg prop. into our parents PCI address using the ranges
* property.
*/
rval = sbbc_apply_range(sbbcsoftp, rdip, child_rp, &pci_reg);
if (mp->map_type == DDI_MT_RNUMBER)
kmem_free(child_regs, i);
if (rval != DDI_SUCCESS)
return (rval);
p_map_request = *mp;
p_map_request.map_type = DDI_MT_REGSPEC;
p_map_request.map_obj.rp = (struct regspec *)&pci_reg;
/* Send it to PCI nexus to map into the PCI space */
rval = ddi_map(dip, &p_map_request, 0, 0, addrp);
return (rval);
}
/* new intr_ops structure */
static int
sbbc_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
ddi_intr_handle_impl_t *hdlp, void *result)
{
ddi_ispec_t *ip = (ddi_ispec_t *)hdlp->ih_private;
int ret = DDI_SUCCESS;
switch (intr_op) {
case DDI_INTROP_GETCAP:
*(int *)result = 0;
break;
case DDI_INTROP_ALLOC:
*(int *)result = hdlp->ih_scratch1;
break;
case DDI_INTROP_FREE:
break;
case DDI_INTROP_GETPRI:
if (ip->is_pil == 0) {
ip->is_pil = 0x1;
cmn_err(CE_WARN, "%s%d assigning default interrupt "
"level %d for device %s%d", ddi_get_name(dip),
ddi_get_instance(dip), ip->is_pil,
ddi_get_name(rdip), ddi_get_instance(rdip));
}
*(int *)result = ip->is_pil;
break;
case DDI_INTROP_ADDISR:
ret = sbbc_add_intr_impl(dip, rdip, intr_op, hdlp, result);
break;
case DDI_INTROP_REMISR:
ret = sbbc_remove_intr_impl(dip, rdip, intr_op, hdlp, result);
break;
case DDI_INTROP_ENABLE:
ret = sbbc_update_intr_state(dip, rdip, intr_op, hdlp, &result);
break;
case DDI_INTROP_DISABLE:
ret = sbbc_update_intr_state(dip, rdip, intr_op, hdlp, &result);
break;
case DDI_INTROP_NINTRS:
case DDI_INTROP_NAVAIL:
*(int *)result = i_ddi_get_nintrs(rdip);
break;
case DDI_INTROP_SUPPORTED_TYPES:
/* PCI nexus driver supports only fixed interrupts */
*(int *)result = i_ddi_get_nintrs(rdip) ?
DDI_INTR_TYPE_FIXED : 0;
break;
default:
ret = DDI_ENOTSUP;
break;
}
return (ret);
}
static int
sbbc_add_intr_impl(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
ddi_intr_handle_impl_t *hdlp, void *result)
{
sbbcsoft_t *sbbcsoftp;
sbbc_child_intr_t *childintr;
int instance, i, rval = DDI_SUCCESS;
SBBC_DBG2(SBBC_DBG_INTR, dip,
"add: rdip 0x%llx hdlp 0x%llx\n", rdip, hdlp);
/* insert the sbbc isr wrapper instead */
instance = ddi_get_instance(dip);
if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance)))
return (DDI_FAILURE);
childintr = kmem_zalloc(sizeof (struct sbbc_child_intr), KM_SLEEP);
childintr->name = ddi_get_name(rdip);
childintr->inum = hdlp->ih_inum;
childintr->intr_handler = hdlp->ih_cb_func;
childintr->arg1 = hdlp->ih_cb_arg1;
childintr->arg2 = hdlp->ih_cb_arg2;
childintr->status = SBBC_INTR_STATE_DISABLE;
for (i = 0; i < MAX_SBBC_DEVICES; i++) {
if (sbbcsoftp->child_intr[i] == 0) {
sbbcsoftp->child_intr[i] = childintr;
break;
}
}
DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp,
(ddi_intr_handler_t *)sbbc_intr_wrapper,
(caddr_t)sbbcsoftp, NULL);
if ((rval = i_ddi_intr_ops(dip, rdip, intr_op,
hdlp, result)) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sbbc%d: failed to add intr for %s",
instance, ddi_get_name(rdip));
kmem_free(childintr, sizeof (struct sbbc_child_intr));
sbbcsoftp->child_intr[i] = NULL;
}
/*
* Restore original interrupt handler
* and arguments in interrupt handle.
*/
DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, childintr->intr_handler,
childintr->arg1, childintr->arg2);
return (rval);
}
static int
sbbc_remove_intr_impl(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
ddi_intr_handle_impl_t *hdlp, void *result)
{
sbbcsoft_t *sbbcsoftp;
sbbc_child_intr_t *childintr;
int instance, i, rval = DDI_SUCCESS;
SBBC_DBG2(SBBC_DBG_INTR, dip,
"remove: rdip 0x%llx hdlp 0x%llx\n", rdip, hdlp);
instance = ddi_get_instance(dip);
if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance)))
return (DDI_FAILURE);
/* remove the sbbc isr wrapper instead */
for (i = 0; i < MAX_SBBC_DEVICES; i++) {
if (sbbcsoftp->child_intr[i]) {
childintr = sbbcsoftp->child_intr[i];
if (childintr->status == SBBC_INTR_STATE_DISABLE &&
childintr->name == ddi_get_name(rdip)) {
/* put back child's inum */
hdlp->ih_inum = childintr->inum;
break;
}
}
}
if (i >= MAX_SBBC_DEVICES) {
cmn_err(CE_WARN, "sbbc%d:obound failed to remove intr for %s",
instance, ddi_get_name(rdip));
return (DDI_FAILURE);
}
if ((rval = i_ddi_intr_ops(dip, rdip, intr_op,
hdlp, result)) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sbbc%d: failed to remove intr for %s",
instance, ddi_get_name(rdip));
return (rval);
}
kmem_free(childintr, sizeof (struct sbbc_child_intr));
sbbcsoftp->child_intr[i] = NULL;
return (rval);
}
static int
sbbc_update_intr_state(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
ddi_intr_handle_impl_t *hdlp, void *result)
{
sbbcsoft_t *sbbcsoftp;
sbbc_child_intr_t *childintr;
int instance, i;
int ret = DDI_SUCCESS;
SBBC_DBG2(SBBC_DBG_INTR, dip, "sbbc_update_intr_state: "
"rdip 0x%llx hdlp 0x%llx state 0x%x\n", rdip, hdlp);
instance = ddi_get_instance(dip);
if (!(sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, instance)))
return (DDI_FAILURE);
for (i = 0; i < MAX_SBBC_DEVICES; i++) {
if (sbbcsoftp->child_intr[i]) {
childintr = sbbcsoftp->child_intr[i];
if (childintr->name == ddi_get_name(rdip))
break;
}
}
if (i >= MAX_SBBC_DEVICES) {
cmn_err(CE_WARN, "sbbc%d: failed to update intr state for %s",
instance, ddi_get_name(rdip));
return (DDI_FAILURE);
}
if ((ret = i_ddi_intr_ops(dip, rdip, intr_op,
hdlp, result)) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sbbc%d: failed to update intr state for %s",
instance, ddi_get_name(rdip));
return (ret);
}
/* Update the interrupt state */
childintr->status = (intr_op == DDI_INTROP_ENABLE) ?
SBBC_INTR_STATE_ENABLE : SBBC_INTR_STATE_DISABLE;
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
sbbc_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op,
void *arg, void *result)
{
sbbc_child_regspec_t *child_rp;
int i, n;
SBBC_DBG3(SBBC_DBG_CTLOPS, dip,
"Initializing %s, arg %x, op %x\n",
ddi_driver_name(rdip), arg, op);
SBBCTRACE(sbbc_ctlops, 'CTLO', arg);
switch (op) {
case DDI_CTLOPS_INITCHILD: {
return (sbbc_initchild(dip, rdip, (dev_info_t *)arg));
}
case DDI_CTLOPS_UNINITCHILD: {
return (sbbc_uninitchild(rdip, (dev_info_t *)arg));
}
case DDI_CTLOPS_REPORTDEV:
cmn_err(CE_CONT, "?%s%d at %s%d: offset %s\n",
ddi_driver_name(rdip), ddi_get_instance(rdip),
ddi_driver_name(dip), ddi_get_instance(dip),
ddi_get_name_addr(rdip));
return (DDI_SUCCESS);
case DDI_CTLOPS_REGSIZE:
if (getprop(rdip, "reg", &child_rp, &i) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
n = i / sizeof (sbbc_child_regspec_t);
if (*(int *)arg < 0 || *(int *)arg >= n) {
kmem_free(child_rp, i);
return (DDI_FAILURE);
}
*((off_t *)result) = child_rp[*(int *)arg].size;
kmem_free(child_rp, i);
return (DDI_SUCCESS);
case DDI_CTLOPS_NREGS:
if (getprop(rdip, "reg", &child_rp, &i) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
*((uint_t *)result) = i / sizeof (sbbc_child_regspec_t);
kmem_free(child_rp, i);
return (DDI_SUCCESS);
}
/*
* Now pass the request up to our parent.
*/
SBBC_DBG0(SBBC_DBG_CTLOPS, dip, "Calling ddi_ctlops\n");
return (ddi_ctlops(dip, rdip, op, arg, result));
}
/*
* 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
sbbc_apply_range(struct sbbcsoft *sbbc_p, dev_info_t *rdip,
sbbc_child_regspec_t *child_rp, pci_regspec_t *rp)
{
int b;
int rval = DDI_SUCCESS;
struct sbbc_pci_rangespec *rangep = sbbc_p->rangep;
int nrange = sbbc_p->range_cnt;
SBBC_DBG4(SBBC_DBG_MAPRANGES, rdip,
"Applying ranges for %s, rangep %llx, child_rp %llx, range %x\n",
ddi_driver_name(rdip), sbbc_p->rangep, child_rp, nrange);
SBBCTRACE(sbbc_apply_range, 'APPL', sbbc_p);
for (b = 0; b < nrange; ++b, ++rangep) {
/* Make sure the correct range is being mapped */
if (child_rp->addr_hi == rangep->sbbc_phys_hi)
/* See if we fit in this range */
if ((child_rp->addr_low >=
rangep->sbbc_phys_low) &&
((child_rp->addr_low + child_rp->size - 1)
<= (rangep->sbbc_phys_low +
rangep->rng_size - 1))) {
uint_t addr_offset = child_rp->addr_low -
rangep->sbbc_phys_low;
/*
* Use the range entry to translate
* the SBBC physical address into the
* parents PCI space.
*/
rp->pci_phys_hi =
rangep->pci_phys_hi;
rp->pci_phys_mid = rangep->pci_phys_mid;
rp->pci_phys_low =
rangep->pci_phys_low + addr_offset;
rp->pci_size_hi = 0;
rp->pci_size_low =
min(child_rp->size, (rangep->rng_size -
addr_offset));
break;
}
}
if (b == nrange) {
cmn_err(CE_WARN, "out_of_range %s", ddi_get_name(rdip));
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
sbbc_get_ranges(struct sbbcsoft *sbbcsoftp)
{
struct sbbc_pci_rangespec *rangep;
int range_len, nrange;
if (ddi_getlongprop(DDI_DEV_T_NONE, sbbcsoftp->dip, DDI_PROP_DONTPASS,
"ranges", (caddr_t)&rangep, &range_len) != DDI_SUCCESS) {
cmn_err(CE_WARN, "SBBC: couldn't get %s ranges property %d",
ddi_get_name(sbbcsoftp->dip), sbbcsoftp->instance);
return (DDI_ME_REGSPEC_RANGE);
}
nrange = range_len / sizeof (struct sbbc_pci_rangespec);
if (!nrange) {
kmem_free(rangep, range_len);
return (DDI_FAILURE);
}
/* setup the soft structure with ranges info. */
sbbcsoftp->rangep = rangep;
sbbcsoftp->range_cnt = nrange;
sbbcsoftp->range_len = range_len;
return (DDI_SUCCESS);
}
/*
* Configure the SBBC for PCI
*/
static int
sbbc_config4pci(struct sbbcsoft *sbbcsoftp)
{
ddi_acc_handle_t conf_handle;
uint16_t comm, vendid, devid, stat;
uint8_t revid;
#ifdef DEBUG
if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
cmn_err(CE_CONT,
"sbbc_config4pci: sbbcsoftp %p\n", (void *)sbbcsoftp);
}
#endif
if (pci_config_setup(sbbcsoftp->dip, &conf_handle) != DDI_SUCCESS)
return (1);
vendid = pci_config_get16(conf_handle, PCI_CONF_VENID);
devid = pci_config_get16(conf_handle, PCI_CONF_DEVID);
comm = pci_config_get16(conf_handle, PCI_CONF_COMM);
stat = pci_config_get16(conf_handle, PCI_CONF_STAT);
revid = pci_config_get8(conf_handle, PCI_CONF_REVID);
#ifdef DEBUG
if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
cmn_err(CE_CONT,
"SBBC vendid %x, devid %x, comm %x, stat %x, revid %x\n",
vendid, devid, comm, stat, revid);
}
#endif
comm = (PCI_COMM_ME | PCI_COMM_MAE | PCI_COMM_SERR_ENABLE |
PCI_COMM_PARITY_DETECT);
pci_config_put16(conf_handle, PCI_CONF_COMM, comm);
comm = pci_config_get16(conf_handle, PCI_CONF_COMM);
#ifdef DEBUG
if (sbbc_dbg_flags & SBBC_DBG_PCICONF) {
cmn_err(CE_CONT, "comm %x\n", comm);
}
#endif
pci_config_teardown(&conf_handle);
return (0);
}
/* ARGSUSED0 */
int
sbbc_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
dev_t dev = (dev_t)arg;
struct sbbcsoft *sbbcsoftp;
int instance, ret;
instance = getminor(dev);
SBBCTRACE(sbbc_getinfo, 'GINF', instance);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
sbbcsoftp = (struct sbbcsoft *)
ddi_get_soft_state(sbbcsoft_statep, instance);
*result = sbbcsoftp->dip;
ret = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)instance;
ret = DDI_SUCCESS;
break;
default:
ret = DDI_FAILURE;
break;
}
return (ret);
}
/*ARGSUSED1*/
static int
sbbc_open(dev_t *dev, int flag, int otype, cred_t *credp)
{
struct sbbcsoft *sbbcsoftp;
int instance;
/* check privilege of caller process */
if (drv_priv(credp)) {
return (EPERM);
}
instance = getminor(*dev);
if (instance < 0)
return (ENXIO);
sbbcsoftp = (struct sbbcsoft *)ddi_get_soft_state(sbbcsoft_statep,
instance);
SBBCTRACE(sbbc_open, 'OPEN', sbbcsoftp);
if (sbbcsoftp == NULL)
return (ENXIO);
mutex_enter(&sbbcsoftp->umutex);
/* check for exclusive access */
if ((sbbcsoftp->oflag == TRUE)) {
mutex_exit(&sbbcsoftp->umutex);
return (EBUSY);
}
sbbcsoftp->oflag = TRUE;
mutex_exit(&sbbcsoftp->umutex);
return (0);
}
/*ARGSUSED1*/
static int
sbbc_close(dev_t dev, int flag, int otype, cred_t *credp)
{
struct sbbcsoft *sbbcsoftp;
int instance;
instance = getminor(dev);
if (instance < 0)
return (ENXIO);
sbbcsoftp = (struct sbbcsoft *)ddi_get_soft_state(sbbcsoft_statep,
instance);
/* wait till all output activity has ceased */
mutex_enter(&sbbcsoftp->umutex);
SBBCTRACE(sbbc_close, 'CLOS', sbbcsoftp);
sbbcsoftp->oflag = FALSE;
mutex_exit(&sbbcsoftp->umutex);
return (0);
}
/*ARGSUSED2*/
static int
sbbc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rvalp)
{
struct sbbcsoft *sbbcsoftp;
SBBCTRACE(sbbc_ioctl, 'IOCT', arg);
sbbcsoftp = ddi_get_soft_state(sbbcsoft_statep, getminor(dev));
if (sbbcsoftp == NULL) {
return (ENXIO);
}
switch (cmd) {
case SBBC_SBBCREG_WR:
{
struct ssc_sbbc_regio sbbcregs;
uint64_t offset;
if (arg == NULL) {
return (ENXIO);
}
if (ddi_copyin((caddr_t)arg, (caddr_t)&sbbcregs,
sizeof (struct ssc_sbbc_regio), mode)) {
cmn_err(CE_WARN, "sbbc_ioctl: copyin failed arg %p",
(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.
*/
if ((sbbcregs.len != 4) ||
!sbbc_offset_valid(sbbcregs.offset)) {
return (EINVAL);
}
offset = (uint64_t)&sbbcsoftp->pci_sbbc_map->sbbc_internal_regs;
offset += sbbcregs.offset;
ddi_put32(sbbcsoftp->pci_sbbc_map_handle, (uint32_t *)offset,
sbbcregs.value);
}
break;
case SBBC_SBBCREG_RD:
{
struct ssc_sbbc_regio sbbcregs;
uint64_t offset;
if (arg == NULL) {
return (ENXIO);
}
if (ddi_copyin((caddr_t)arg, (caddr_t)&sbbcregs,
sizeof (struct ssc_sbbc_regio), mode)) {
cmn_err(CE_WARN, "sbbc_ioctl: copyin failed arg %p",
(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.
*/
if ((sbbcregs.len != 4) ||
!sbbc_offset_valid(sbbcregs.offset)) {
return (EINVAL);
}
offset = (uint64_t)&sbbcsoftp->pci_sbbc_map->sbbc_internal_regs;
offset += sbbcregs.offset;
sbbcregs.value = ddi_get32(sbbcsoftp->pci_sbbc_map_handle,
(uint32_t *)offset);
if (ddi_copyout((caddr_t)&sbbcregs.value,
&((struct ssc_sbbc_regio *)arg)->value, sbbcregs.len, mode)) {
cmn_err(CE_WARN, "sbbc_ioctl:copyout failed arg %p",
(void *)arg);
return (EFAULT);
}
}
break;
default:
cmn_err(CE_WARN, "sbbc_ioctl:Illegal command 0x%08x", cmd);
return (ENOTTY);
}
return (DDI_SUCCESS);
}
static void
sbbc_remove_reg_maps(struct sbbcsoft *sbbcsoftp)
{
SBBCTRACE(sbbc_remove_reg_maps, 'RMAP', sbbcsoftp);
if (sbbcsoftp->pci_sbbc_map_handle)
ddi_regs_map_free(&sbbcsoftp->pci_sbbc_map_handle);
/* need to unmap other registers as well */
}
static int
sbbc_init(struct sbbcsoft *sbbcsoftp)
{
/*
* setup the regs. and mask all the interrupts
* till we are ready.
*/
ddi_put32(sbbcsoftp->pci_sbbc_map_handle,
&sbbcsoftp->pci_sbbc_map->sbbc_internal_regs.sys_intr_enable,
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
sbbc_initchild(dev_info_t *dip, dev_info_t *rdip, dev_info_t *child)
{
sbbc_child_regspec_t *child_rp;
int reglen, slot;
char name[10];
SBBC_DBG1(SBBC_DBG_INITCHILD, dip, "Initializing %s\n",
ddi_driver_name(rdip));
/*
* Initialize a child
* Set the address portion of the node name based on the
* address/offset.
*/
if (ddi_getlongprop(DDI_DEV_T_NONE, child, DDI_PROP_DONTPASS,
"reg", (caddr_t)&child_rp, &reglen) != DDI_SUCCESS) {
if (strcmp(ddi_node_name(child), "hotplug-controller") == 0) {
slot = 1;
(void) sprintf(name, "%x", slot);
ddi_set_name_addr(child, name);
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
SBBC_DBG3(SBBC_DBG_INITCHILD, dip, "hi 0x%x, low 0x%x, size 0x%x\n",
child_rp->addr_hi, child_rp->addr_low, child_rp->size);
(void) sprintf(name, "%x,%x", child_rp->addr_hi, child_rp->addr_low);
/*
* set child's addresses from the reg property into parent private
* data structure.
*/
ddi_set_name_addr(child, name);
kmem_free(child_rp, reglen);
ddi_set_parent_data(child, NULL);
return (DDI_SUCCESS);
}
/* ARGSUSED0 */
static int
sbbc_uninitchild(dev_info_t *rdip, dev_info_t *child)
{
SBBC_DBG1(SBBC_DBG_UNINITCHILD, rdip, "Uninitializing %s\n",
ddi_driver_name(rdip));
ddi_set_name_addr(child, NULL);
ddi_remove_minor_node(child, NULL);
impl_rem_dev_props(child);
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
sbbc_intr_wrapper(caddr_t arg)
{
struct sbbcsoft *sbbcsoftp = (struct sbbcsoft *)arg;
int i, rval;
SBBC_DBG1(SBBC_DBG_INTR, sbbcsoftp->dip, "Isr arg 0x%llx\n", arg);
mutex_enter(&sbbcsoftp->sbbc_intr_mutex);
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] &&
sbbcsoftp->child_intr[i]->status ==
SBBC_INTR_STATE_ENABLE) {
/*
* Dispatch the children interrupt service routines and
* look for someone to claim.
*/
rval = sbbcsoftp->child_intr[i]->intr_handler(
sbbcsoftp->child_intr[i]->arg1,
sbbcsoftp->child_intr[i]->arg2);
if (rval == DDI_INTR_CLAIMED) {
mutex_exit(&sbbcsoftp->sbbc_intr_mutex);
return (rval);
}
}
}
mutex_exit(&sbbcsoftp->sbbc_intr_mutex);
/* 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
sbbc_offset_valid(uint32_t offset) {
/*
* 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.
*/
if (((offset >= 0x001a0) && (offset <= 0x001ff)) ||
((offset >= 0x002a0) && (offset <= 0x002ff)) ||
((offset >= 0x00350) && (offset <= 0x003ff)) ||
((offset >= 0x00500) && (offset <= 0x00fff)) ||
((offset >= 0x01160) && (offset <= 0x011ff)) ||
((offset >= 0x01210) && (offset <= 0x017ff)) ||
((offset >= 0x01810) && (offset <= 0x01fff)) ||
((offset >= 0x02030) && (offset <= 0x022ff)) ||
((offset >= 0x02340) && (offset <= 0x03fff)) ||
((offset >= 0x04030) && (offset <= 0x05fff)) ||
((offset >= 0x060a0) && (offset <= 0x060ff)) ||
(offset == 0x06120) ||
((offset >= 0x06190) && (offset <= 0x061ff)) ||
((offset >= 0x06230) && (offset <= 0x062f0)) ||
(offset > 0x06320)) {
return (0);
}
return (1);
}
#ifdef DEBUG
void
sbbc_dbg(uint32_t flag, dev_info_t *dip, char *fmt,
uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5)
{
char *s = NULL;
if (sbbc_dbg_flags && ((sbbc_dbg_flags & flag) == flag)) {
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;
}
cmn_err(CE_CONT, "%s_%s(%d): ", ddi_driver_name(dip), s,
ddi_get_instance(dip));
cmn_err(CE_CONT, fmt, a1, a2, a3, a4, a5);
}
}
#endif /* DEBUG */