#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/cmn_err.h>
#include <sys/stropts.h>
#include <sys/kmem.h>
#include <sys/sunndi.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/promif.h>
#include <sys/stat.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>
#ifdef DEBUG
uint_t sgsbbc_debug = 0;
#endif
static int sbbc_attach(dev_info_t *, ddi_attach_cmd_t);
static int sbbc_detach(dev_info_t *, ddi_detach_cmd_t);
static void *sbbcp;
struct chosen_iosram *master_iosram = NULL;
struct sbbc_softstate *sgsbbc_instances = NULL;
static int master_chosen = FALSE;
kmutex_t chosen_lock;
static uint32_t intr_in_enabled;
static void softsp_init(sbbc_softstate_t *, dev_info_t *);
static void sbbc_chosen_init(sbbc_softstate_t *);
static void sbbc_add_instance(sbbc_softstate_t *);
static void sbbc_remove_instance(sbbc_softstate_t *);
static int sbbc_find_dip(dev_info_t *, void *);
static void sbbc_unmap_regs(sbbc_softstate_t *);
static struct cb_ops sbbc_cb_ops = {
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
NULL,
D_NEW | D_MP
};
struct dev_ops sbbc_ops = {
DEVO_REV,
0,
ddi_getinfo_1to1,
nulldev,
nulldev,
sbbc_attach,
sbbc_detach,
nodev,
&sbbc_cb_ops,
(struct bus_ops *)NULL,
nulldev,
ddi_quiesce_not_supported,
};
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops,
"PCI SBBC",
&sbbc_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modldrv,
NULL
};
int
_init(void)
{
int error;
if ((error = ddi_soft_state_init(&sbbcp,
sizeof (sbbc_softstate_t), 1)) != 0)
return (error);
if ((error = mod_install(&modlinkage)) != 0) {
ddi_soft_state_fini(&sbbcp);
return (error);
}
mutex_init(&chosen_lock, NULL, MUTEX_DEFAULT, NULL);
iosram_init();
sbbc_mbox_init();
return (error);
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&modlinkage)) == 0)
ddi_soft_state_fini(&sbbcp);
master_chosen = FALSE;
mutex_destroy(&chosen_lock);
sbbc_mbox_fini();
iosram_fini();
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
sbbc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
int instance;
sbbc_softstate_t *softsp;
uint32_t *pci_intr_enable_reg;
int len;
#ifdef DEBUG
char name[8];
#endif
instance = ddi_get_instance(devi);
switch (cmd) {
case DDI_ATTACH:
if (ddi_soft_state_zalloc(sbbcp, instance) != 0)
return (DDI_FAILURE);
softsp = ddi_get_soft_state(sbbcp, instance);
softsp->sbbc_instance = instance;
softsp_init(softsp, devi);
if (ddi_getproplen(DDI_DEV_T_ANY, softsp->dip,
DDI_PROP_DONTPASS, "interrupts",
&len) != DDI_PROP_SUCCESS) {
SBBC_ERR1(CE_WARN, "No 'interrupts' property for the "
"SBBC instance %d\n", instance);
return (DDI_FAILURE);
}
mutex_enter(&chosen_lock);
softsp->sbbc_state = SBBC_STATE_INIT;
sbbc_add_instance(softsp);
if (sgsbbc_iosram_is_chosen(softsp)) {
ASSERT(master_iosram);
softsp->iosram = master_iosram;
master_iosram->sgsbbc = softsp;
sbbc_chosen_init(softsp);
}
mutex_exit(&chosen_lock);
#ifdef DEBUG
(void) sprintf(name, "sbbc%d", instance);
if (ddi_create_minor_node(devi, name, S_IFCHR, instance,
NULL, NULL) == DDI_FAILURE) {
mutex_destroy(&softsp->sbbc_lock);
ddi_remove_minor_node(devi, NULL);
ddi_soft_state_free(sbbcp, instance);
return (DDI_FAILURE);
}
#endif
ddi_report_dev(devi);
return (DDI_SUCCESS);
case DDI_RESUME:
if (!(softsp = ddi_get_soft_state(sbbcp, instance)))
return (DDI_FAILURE);
mutex_enter(&softsp->sbbc_lock);
if ((softsp->suspended == TRUE) && (softsp->chosen == TRUE)) {
pci_intr_enable_reg = (uint32_t *)
((char *)softsp->sbbc_regs +
SBBC_PCI_INT_ENABLE);
ddi_put32(softsp->sbbc_reg_handle1,
pci_intr_enable_reg,
(uint32_t)SBBC_PCI_ENABLE_INT_A);
if (iosram_write(SBBC_SC_INTR_ENABLED_KEY,
0, (caddr_t)&intr_in_enabled,
sizeof (intr_in_enabled))) {
mutex_exit(&softsp->sbbc_lock);
return (DDI_FAILURE);
}
}
softsp->suspended = FALSE;
mutex_exit(&softsp->sbbc_lock);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
sbbc_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
sbbc_softstate_t *softsp;
int instance;
uint32_t *pci_intr_enable_reg;
int rc = DDI_SUCCESS;
instance = ddi_get_instance(devi);
if (!(softsp = ddi_get_soft_state(sbbcp, instance)))
return (DDI_FAILURE);
switch (cmd) {
case DDI_DETACH:
mutex_enter(&chosen_lock);
softsp->sbbc_state |= SBBC_STATE_DETACH;
mutex_exit(&chosen_lock);
if (softsp->chosen == TRUE) {
if (sgsbbc_iosram_switchfrom(softsp) == DDI_FAILURE) {
SBBC_ERR(CE_WARN, "Cannot unconfigure: "
"tunnel switch failed\n");
return (DDI_FAILURE);
}
}
mutex_enter(&chosen_lock);
sbbc_remove_instance(softsp);
mutex_exit(&chosen_lock);
sbbc_unmap_regs(softsp);
mutex_destroy(&softsp->sbbc_lock);
ddi_soft_state_free(sbbcp, instance);
return (DDI_SUCCESS);
case DDI_SUSPEND:
mutex_enter(&softsp->sbbc_lock);
if ((softsp->suspended == FALSE) && (softsp->chosen == TRUE)) {
uint32_t tmp_intr_enabled = 0;
pci_intr_enable_reg = (uint32_t *)
((char *)softsp->sbbc_regs +
SBBC_PCI_INT_ENABLE);
ddi_put32(softsp->sbbc_reg_handle1,
pci_intr_enable_reg, 0);
rc = iosram_read(SBBC_SC_INTR_ENABLED_KEY,
0, (caddr_t)&intr_in_enabled,
sizeof (intr_in_enabled));
if (rc) {
mutex_exit(&softsp->sbbc_lock);
return (DDI_FAILURE);
}
rc = iosram_write(SBBC_SC_INTR_ENABLED_KEY,
0, (caddr_t)&tmp_intr_enabled,
sizeof (tmp_intr_enabled));
if (rc) {
mutex_exit(&softsp->sbbc_lock);
return (DDI_FAILURE);
}
}
softsp->suspended = TRUE;
mutex_exit(&softsp->sbbc_lock);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static void
softsp_init(sbbc_softstate_t *softsp, dev_info_t *devi)
{
softsp->dip = devi;
(void) ddi_get_iblock_cookie(devi, 0, &softsp->iblock);
mutex_init(&softsp->sbbc_lock, NULL, MUTEX_DRIVER,
(void *)softsp->iblock);
softsp->suspended = FALSE;
softsp->chosen = FALSE;
}
static int
sbbc_find_dip(dev_info_t *dip, void *arg)
{
char *node_name;
sbbc_find_dip_t *dip_struct = (sbbc_find_dip_t *)arg;
char status[OBP_MAXPROPNAME];
node_name = ddi_node_name(dip);
if (strcmp(node_name, "bootbus-controller") == 0 && DDI_CF2(dip) &&
(prom_getprop(ddi_get_nodeid(dip),
"status", (caddr_t)status) == -1) &&
(prom_getprop(ddi_get_nodeid(ddi_get_parent(dip)),
"status", (caddr_t)status) == -1)) {
if (dip != dip_struct->cur_dip) {
dip_struct->new_dip = (void *)dip;
return (DDI_WALK_TERMINATE);
}
}
return (DDI_WALK_CONTINUE);
}
uint_t
sbbc_intr_handler(caddr_t arg)
{
sbbc_softstate_t *softsp = (sbbc_softstate_t *)arg;
uint32_t *port_int_reg;
volatile uint32_t port_int_status;
volatile uint32_t intr_reason;
uint32_t intr_enabled;
sbbc_intrs_t *intr;
int i, intr_mask;
struct tunnel_key tunnel_key;
ddi_acc_handle_t intr_in_handle;
uint32_t *intr_in_reason;
if (softsp == (sbbc_softstate_t *)NULL) {
return (DDI_INTR_UNCLAIMED);
}
mutex_enter(&softsp->sbbc_lock);
if (softsp->port_int_regs == NULL) {
mutex_exit(&softsp->sbbc_lock);
return (DDI_INTR_UNCLAIMED);
}
port_int_reg = softsp->port_int_regs;
port_int_status = ddi_get32(softsp->sbbc_reg_handle1, port_int_reg);
if (iosram_read(SBBC_SC_INTR_ENABLED_KEY, 0,
(caddr_t)&intr_enabled, sizeof (intr_enabled))) {
goto intr_handler_exit;
}
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;
intr_reason = ddi_get32(intr_in_handle, intr_in_reason);
SGSBBC_DBG_INTR(CE_CONT, "intr_reason = %x\n", intr_reason);
intr_reason &= intr_enabled;
for (i = 0; i < SBBC_MAX_INTRS; i++) {
intr_mask = (1 << i);
if (intr_reason & intr_mask) {
intr = &softsp->intr_hdlrs[i];
if ((intr != NULL) &&
(intr->sbbc_intr_id != 0)) {
mutex_enter(intr->sbbc_intr_lock);
if (*(intr->sbbc_intr_state) ==
SBBC_INTR_IDLE) {
mutex_exit(intr->sbbc_intr_lock);
ddi_trigger_softintr(
intr->sbbc_intr_id);
} else {
mutex_exit(intr->sbbc_intr_lock);
}
intr_reason &= ~intr_mask;
ddi_put32(intr_in_handle, intr_in_reason,
ddi_get32(intr_in_handle,
intr_in_reason) & ~intr_mask);
}
}
if (intr_reason == 0)
break;
}
ddi_put32(softsp->sbbc_reg_handle1, port_int_reg, port_int_status);
port_int_status = ddi_get32(softsp->sbbc_reg_handle1, port_int_reg);
intr_handler_exit:
mutex_exit(&softsp->sbbc_lock);
return (DDI_INTR_CLAIMED);
}
static void
sbbc_chosen_init(sbbc_softstate_t *softsp)
{
char master_sbbc[MAXNAMELEN];
char pn[MAXNAMELEN];
int nodeid, len;
pnode_t dnode;
if (master_chosen != FALSE) {
return;
}
dnode = prom_chosennode();
if (prom_getprop(dnode, IOSRAM_CHOSEN_PROP, (caddr_t)&nodeid) <= 0) {
SBBC_ERR(CE_PANIC, "No SBBC found for Console/TOD \n");
}
if (prom_getprop(dnode, IOSRAM_TOC_PROP,
(caddr_t)&softsp->sram_toc) <= 0) {
SBBC_ERR(CE_WARN, "No SBBC TOC Offset found\n");
softsp->sram_toc = 0;
}
if (prom_phandle_to_path((phandle_t)nodeid, master_sbbc,
sizeof (master_sbbc)) < 0) {
SBBC_ERR1(CE_PANIC, "prom_phandle_to_path(%d) failed\n",
nodeid);
}
SGSBBC_DBG_ALL("chosen pathname : %s\n", master_sbbc);
SGSBBC_DBG_ALL("device pathname : %s\n", ddi_pathname(softsp->dip, pn));
if (strcmp(master_sbbc, ddi_pathname(softsp->dip, pn)) == 0) {
if (sbbc_map_regs(softsp) != DDI_SUCCESS) {
SBBC_ERR(CE_PANIC, "Can't map the SBBC regs \n");
}
if (iosram_tunnel_init(softsp) == DDI_FAILURE) {
SBBC_ERR(CE_PANIC, "Can't create the SRAM <-> SC "
"comm. tunnel \n");
}
master_chosen = TRUE;
if (ddi_getproplen(DDI_DEV_T_ANY, softsp->dip,
DDI_PROP_DONTPASS, "interrupts",
&len) != DDI_PROP_SUCCESS) {
SBBC_ERR(CE_PANIC, "No 'interrupts' property for the "
"'chosen' SBBC \n");
}
if (sbbc_add_intr(softsp) == DDI_FAILURE) {
SBBC_ERR(CE_PANIC, "Can't add interrupt handler for "
"'chosen' SBBC \n");
}
sbbc_enable_intr(softsp);
if (sbbc_mbox_create(softsp) != 0) {
cmn_err(CE_WARN, "No IOSRAM MailBox created!\n");
}
}
}
static void
sbbc_add_instance(sbbc_softstate_t *softsp)
{
#ifdef DEBUG
struct sbbc_softstate *sp;
#endif
ASSERT(mutex_owned(&chosen_lock));
#if defined(DEBUG)
for (sp = sgsbbc_instances; sp != NULL; sp = sp->next) {
ASSERT(sp != softsp);
}
#endif
if (sgsbbc_instances != NULL) {
sgsbbc_instances->prev = softsp;
}
softsp->next = sgsbbc_instances;
softsp->prev = NULL;
sgsbbc_instances = softsp;
}
static void
sbbc_remove_instance(sbbc_softstate_t *softsp)
{
struct sbbc_softstate *sp;
for (sp = sgsbbc_instances; sp != NULL; sp = sp->next) {
if (sp == softsp) {
if (sp->next != NULL) {
sp->next->prev = sp->prev;
}
if (sp->prev != NULL) {
sp->prev->next = sp->next;
}
if (sgsbbc_instances == softsp) {
sgsbbc_instances = sp->next;
}
break;
}
}
}
int
sbbc_send_intr(sbbc_softstate_t *softsp, int send_intr)
{
uchar_t *epld_int;
volatile uchar_t epld_status;
ASSERT(MUTEX_HELD(&master_iosram->iosram_lock));
if ((softsp == (sbbc_softstate_t *)NULL) ||
(softsp->epld_regs == (struct sbbc_epld_regs *)NULL))
return (ENXIO);
epld_int = &softsp->epld_regs->epld_reg[EPLD_INTERRUPT];
epld_status = ddi_get8(softsp->sbbc_reg_handle2, epld_int);
if (epld_status & INTERRUPT_ON)
return (EBUSY);
if (send_intr == TRUE)
ddi_put8(softsp->sbbc_reg_handle2, epld_int,
(epld_status | INTERRUPT_ON));
return (0);
}
int
sbbc_map_regs(sbbc_softstate_t *softsp)
{
struct ddi_device_acc_attr attr;
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (ddi_regs_map_setup(softsp->dip, RNUM_SBBC_REGS,
(caddr_t *)&softsp->sbbc_regs,
SBBC_REGS_OFFSET, SBBC_REGS_SIZE,
&attr, &softsp->sbbc_reg_handle1) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sbbc%d: unable to map interrupt "
"registers", ddi_get_instance(softsp->dip));
return (DDI_FAILURE);
}
if (ddi_regs_map_setup(softsp->dip, RNUM_SBBC_REGS,
(caddr_t *)&softsp->epld_regs,
SBBC_EPLD_OFFSET, SBBC_EPLD_SIZE,
&attr, &softsp->sbbc_reg_handle2) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sbbc%d: unable to map EPLD "
"registers", ddi_get_instance(softsp->dip));
return (DDI_FAILURE);
}
softsp->port_int_regs = (uint32_t *)((char *)softsp->sbbc_regs +
SBBC_PCI_INT_STATUS);
map_regs_exit:
return (DDI_SUCCESS);
}
static void
sbbc_unmap_regs(sbbc_softstate_t *softsp)
{
if (softsp == NULL)
return;
mutex_enter(&master_iosram->iosram_lock);
if (softsp->sbbc_regs) {
ddi_regs_map_free(&softsp->sbbc_reg_handle1);
softsp->sbbc_regs = NULL;
softsp->port_int_regs = NULL;
}
if (softsp->epld_regs) {
ddi_regs_map_free(&softsp->sbbc_reg_handle2);
softsp->epld_regs = NULL;
}
mutex_exit(&master_iosram->iosram_lock);
return;
}
sbbc_softstate_t *
sbbc_get_soft_state(int instance)
{
return (ddi_get_soft_state(sbbcp, instance));
}
int
sbbc_add_intr(sbbc_softstate_t *softsp)
{
int rc = DDI_SUCCESS;
if (ddi_add_intr(softsp->dip, 0, &softsp->iblock,
&softsp->idevice, sbbc_intr_handler,
(caddr_t)softsp) != DDI_SUCCESS) {
cmn_err(CE_WARN, "Can't register SBBC "
" interrupt handler\n");
rc = DDI_FAILURE;
}
return (rc);
}
void
sbbc_enable_intr(sbbc_softstate_t *softsp)
{
uint32_t *pci_intr_enable_reg;
pci_intr_enable_reg = (uint32_t *)((char *)softsp->sbbc_regs +
SBBC_PCI_INT_ENABLE);
ddi_put32(softsp->sbbc_reg_handle1, pci_intr_enable_reg,
(uint32_t)SBBC_PCI_ENABLE_INT_A);
}
void
sbbc_disable_intr(sbbc_softstate_t *softsp)
{
uint32_t *pci_intr_enable_reg;
pci_intr_enable_reg = (uint32_t *)((char *)softsp->sbbc_regs +
SBBC_PCI_INT_ENABLE);
ddi_put32(softsp->sbbc_reg_handle1, pci_intr_enable_reg, 0);
}