#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/intreg.h>
#include <sys/intr.h>
#include <sys/machsystm.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/kmem.h>
#include <sys/async.h>
#include <sys/ivintr.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/ndifm.h>
#include <sys/ontrap.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_subrdefs.h>
#include <sys/epm.h>
#include <sys/spl.h>
#include <sys/fm/util.h>
#include <sys/fm/util.h>
#include <sys/fm/protocol.h>
#include <sys/fm/io/pci.h>
#include <sys/fm/io/sun4upci.h>
#include <sys/pcicmu/pcicmu.h>
#include <sys/cmn_err.h>
#include <sys/time.h>
#include <sys/pci.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/errno.h>
#include <sys/file.h>
uint32_t pcmu_spurintr_duration = 60000000;
ushort_t pcmu_command_default = PCI_COMM_SERR_ENABLE |
PCI_COMM_WAIT_CYC_ENAB |
PCI_COMM_PARITY_DETECT |
PCI_COMM_ME |
PCI_COMM_MAE |
PCI_COMM_IO;
#ifdef DEBUG
uint64_t pcmu_debug_flags = 0;
#endif
uint_t ecc_error_intr_enable = 1;
uint_t pcmu_ecc_afsr_retries = 100;
uint_t pcmu_intr_retry_intv = 5;
uint_t pcmu_panic_on_fatal_errors = 1;
hrtime_t pcmu_intrpend_timeout = 5ll * NANOSEC;
uint64_t pcmu_errtrig_pa = 0x0;
uint_t pcmu_unclaimed_intr_max = 20;
static int pcmu_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int pcmu_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int pcmu_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg, void **result);
static int pcmu_open(dev_t *devp, int flags, int otyp, cred_t *credp);
static int pcmu_close(dev_t dev, int flags, int otyp, cred_t *credp);
static int pcmu_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp);
static int pcmu_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int flags, char *name, caddr_t valuep, int *lengthp);
static int pcmu_ctlops_poke(pcmu_t *pcmu_p, peekpoke_ctlops_t *in_args);
static int pcmu_ctlops_peek(pcmu_t *pcmu_p, peekpoke_ctlops_t *in_args,
void *result);
static int map_pcmu_registers(pcmu_t *, dev_info_t *);
static void unmap_pcmu_registers(pcmu_t *);
static void pcmu_pbm_clear_error(pcmu_pbm_t *);
static int pcmu_ctlops(dev_info_t *, dev_info_t *, ddi_ctl_enum_t,
void *, void *);
static int pcmu_map(dev_info_t *, dev_info_t *, ddi_map_req_t *,
off_t, off_t, caddr_t *);
static int pcmu_intr_ops(dev_info_t *, dev_info_t *, ddi_intr_op_t,
ddi_intr_handle_impl_t *, void *);
static uint32_t pcmu_identity_init(pcmu_t *pcmu_p);
static int pcmu_intr_setup(pcmu_t *pcmu_p);
static void pcmu_pbm_errstate_get(pcmu_t *pcmu_p,
pcmu_pbm_errstate_t *pbm_err_p);
static int pcmu_obj_setup(pcmu_t *pcmu_p);
static void pcmu_obj_destroy(pcmu_t *pcmu_p);
static void pcmu_obj_resume(pcmu_t *pcmu_p);
static void pcmu_obj_suspend(pcmu_t *pcmu_p);
static void u2u_ittrans_init(pcmu_t *, u2u_ittrans_data_t **);
static void u2u_ittrans_resume(u2u_ittrans_data_t **);
static void u2u_ittrans_uninit(u2u_ittrans_data_t *);
static pcmu_ksinfo_t *pcmu_name_kstat;
static struct bus_ops pcmu_bus_ops = {
BUSO_REV,
pcmu_map,
0,
0,
0,
i_ddi_map_fault,
0,
0,
0,
0,
0,
0,
0,
0,
pcmu_ctlops,
ddi_bus_prop_op,
ndi_busop_get_eventcookie,
ndi_busop_add_eventcall,
ndi_busop_remove_eventcall,
ndi_post_event,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
pcmu_intr_ops
};
struct cb_ops pcmu_cb_ops = {
pcmu_open,
pcmu_close,
nodev,
nodev,
nodev,
nodev,
nodev,
pcmu_ioctl,
nodev,
nodev,
nodev,
nochpoll,
pcmu_prop_op,
NULL,
D_NEW | D_MP | D_HOTPLUG,
CB_REV,
nodev,
nodev
};
static struct dev_ops pcmu_ops = {
DEVO_REV,
0,
pcmu_info,
nulldev,
0,
pcmu_attach,
pcmu_detach,
nodev,
&pcmu_cb_ops,
&pcmu_bus_ops,
0,
ddi_quiesce_not_needed,
};
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops,
"OPL CMU-CH PCI Nexus driver",
&pcmu_ops,
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modldrv, NULL
};
void *per_pcmu_state;
kmutex_t pcmu_global_mutex;
errorq_t *pcmu_ecc_queue = NULL;
extern void pcmu_child_cfg_save(dev_info_t *dip);
extern void pcmu_child_cfg_restore(dev_info_t *dip);
int
_init(void)
{
int e;
e = ddi_soft_state_init(&per_pcmu_state, sizeof (pcmu_t), 1);
if (e != 0)
return (e);
mutex_init(&pcmu_global_mutex, NULL, MUTEX_DRIVER, NULL);
pcmu_kstat_init();
e = mod_install(&modlinkage);
if (e != 0) {
ddi_soft_state_fini(&per_pcmu_state);
mutex_destroy(&pcmu_global_mutex);
}
return (e);
}
int
_fini(void)
{
int e;
e = mod_remove(&modlinkage);
if (e != 0) {
return (e);
}
if (pcmu_ecc_queue) {
errorq_destroy(pcmu_ecc_queue);
pcmu_ecc_queue = NULL;
}
pcmu_kstat_fini();
ddi_soft_state_fini(&per_pcmu_state);
mutex_destroy(&pcmu_global_mutex);
return (e);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
pcmu_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int instance = getminor((dev_t)arg) >> 8;
pcmu_t *pcmu_p = get_pcmu_soft_state(instance);
switch (infocmd) {
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)instance;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2DEVINFO:
if (pcmu_p == NULL)
return (DDI_FAILURE);
*result = (void *)pcmu_p->pcmu_dip;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
pcmu_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
pcmu_t *pcmu_p;
int instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_ATTACH:
PCMU_DBG0(PCMU_DBG_ATTACH, dip, "DDI_ATTACH\n");
if (alloc_pcmu_soft_state(instance) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: can't allocate pci state",
ddi_driver_name(dip), instance);
goto err_bad_pcmu_softstate;
}
pcmu_p = get_pcmu_soft_state(instance);
pcmu_p->pcmu_dip = dip;
mutex_init(&pcmu_p->pcmu_mutex, NULL, MUTEX_DRIVER, NULL);
pcmu_p->pcmu_soft_state = PCMU_SOFT_STATE_CLOSED;
pcmu_p->pcmu_open_count = 0;
if (get_pcmu_properties(pcmu_p, dip) == DDI_FAILURE) {
goto err_bad_pcmu_prop;
}
if (map_pcmu_registers(pcmu_p, dip) == DDI_FAILURE) {
goto err_bad_reg_prop;
}
if (pcmu_obj_setup(pcmu_p) != DDI_SUCCESS) {
goto err_bad_objs;
}
if (ddi_create_minor_node(dip, "devctl", S_IFCHR,
(uint_t)instance<<8 | 0xff,
DDI_NT_NEXUS, 0) != DDI_SUCCESS) {
goto err_bad_devctl_node;
}
ddi_report_dev(dip);
pcmu_p->pcmu_state = PCMU_ATTACHED;
PCMU_DBG0(PCMU_DBG_ATTACH, dip, "attach success\n");
break;
err_bad_objs:
ddi_remove_minor_node(dip, "devctl");
err_bad_devctl_node:
unmap_pcmu_registers(pcmu_p);
err_bad_reg_prop:
free_pcmu_properties(pcmu_p);
err_bad_pcmu_prop:
mutex_destroy(&pcmu_p->pcmu_mutex);
free_pcmu_soft_state(instance);
err_bad_pcmu_softstate:
return (DDI_FAILURE);
case DDI_RESUME:
PCMU_DBG0(PCMU_DBG_ATTACH, dip, "DDI_RESUME\n");
pcmu_p = get_pcmu_soft_state(instance);
mutex_enter(&pcmu_p->pcmu_mutex);
if (pcmu_p->pcmu_state != PCMU_SUSPENDED) {
PCMU_DBG0(PCMU_DBG_ATTACH, dip,
"instance NOT suspended\n");
mutex_exit(&pcmu_p->pcmu_mutex);
return (DDI_FAILURE);
}
pcmu_obj_resume(pcmu_p);
pcmu_p->pcmu_state = PCMU_ATTACHED;
pcmu_child_cfg_restore(dip);
mutex_exit(&pcmu_p->pcmu_mutex);
break;
default:
PCMU_DBG0(PCMU_DBG_ATTACH, dip, "unsupported attach op\n");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
pcmu_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance = ddi_get_instance(dip);
pcmu_t *pcmu_p = get_pcmu_soft_state(instance);
int len;
if (pcmu_p->pcmu_state != PCMU_ATTACHED) {
PCMU_DBG0(PCMU_DBG_ATTACH, dip,
"failed - instance not attached\n");
return (DDI_FAILURE);
}
mutex_enter(&pcmu_p->pcmu_mutex);
switch (cmd) {
case DDI_DETACH:
PCMU_DBG0(PCMU_DBG_DETACH, dip, "DDI_DETACH\n");
pcmu_obj_destroy(pcmu_p);
free_pcmu_properties(pcmu_p);
unmap_pcmu_registers(pcmu_p);
mutex_exit(&pcmu_p->pcmu_mutex);
mutex_destroy(&pcmu_p->pcmu_mutex);
free_pcmu_soft_state(instance);
if (ddi_getproplen(DDI_DEV_T_ANY, dip,
DDI_PROP_NOTPROM | DDI_PROP_DONTPASS,
"interrupt-priorities", &len) == DDI_PROP_SUCCESS) {
(void) ddi_prop_remove(DDI_DEV_T_NONE, dip,
"interrupt-priorities");
}
return (DDI_SUCCESS);
case DDI_SUSPEND:
pcmu_child_cfg_save(dip);
pcmu_obj_suspend(pcmu_p);
pcmu_p->pcmu_state = PCMU_SUSPENDED;
mutex_exit(&pcmu_p->pcmu_mutex);
return (DDI_SUCCESS);
default:
PCMU_DBG0(PCMU_DBG_DETACH, dip, "unsupported detach op\n");
mutex_exit(&pcmu_p->pcmu_mutex);
return (DDI_FAILURE);
}
}
static int
pcmu_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
pcmu_t *pcmu_p;
if (otyp != OTYP_CHR) {
return (EINVAL);
}
pcmu_p = DEV_TO_SOFTSTATE(*devp);
if (pcmu_p == NULL) {
return (ENXIO);
}
PCMU_DBG2(PCMU_DBG_OPEN, pcmu_p->pcmu_dip,
"devp=%x: flags=%x\n", devp, flags);
mutex_enter(&pcmu_p->pcmu_mutex);
if (flags & FEXCL) {
if (pcmu_p->pcmu_soft_state != PCMU_SOFT_STATE_CLOSED) {
mutex_exit(&pcmu_p->pcmu_mutex);
PCMU_DBG0(PCMU_DBG_OPEN, pcmu_p->pcmu_dip, "busy\n");
return (EBUSY);
}
pcmu_p->pcmu_soft_state = PCMU_SOFT_STATE_OPEN_EXCL;
} else {
if (pcmu_p->pcmu_soft_state == PCMU_SOFT_STATE_OPEN_EXCL) {
mutex_exit(&pcmu_p->pcmu_mutex);
PCMU_DBG0(PCMU_DBG_OPEN, pcmu_p->pcmu_dip, "busy\n");
return (EBUSY);
}
pcmu_p->pcmu_soft_state = PCMU_SOFT_STATE_OPEN;
}
pcmu_p->pcmu_open_count++;
mutex_exit(&pcmu_p->pcmu_mutex);
return (0);
}
static int
pcmu_close(dev_t dev, int flags, int otyp, cred_t *credp)
{
pcmu_t *pcmu_p;
if (otyp != OTYP_CHR) {
return (EINVAL);
}
pcmu_p = DEV_TO_SOFTSTATE(dev);
if (pcmu_p == NULL) {
return (ENXIO);
}
PCMU_DBG2(PCMU_DBG_CLOSE, pcmu_p->pcmu_dip,
"dev=%x: flags=%x\n", dev, flags);
mutex_enter(&pcmu_p->pcmu_mutex);
pcmu_p->pcmu_soft_state = PCMU_SOFT_STATE_CLOSED;
pcmu_p->pcmu_open_count = 0;
mutex_exit(&pcmu_p->pcmu_mutex);
return (0);
}
static int
pcmu_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp)
{
pcmu_t *pcmu_p;
dev_info_t *dip;
struct devctl_iocdata *dcp;
uint_t bus_state;
int rv = 0;
pcmu_p = DEV_TO_SOFTSTATE(dev);
if (pcmu_p == NULL) {
return (ENXIO);
}
dip = pcmu_p->pcmu_dip;
PCMU_DBG2(PCMU_DBG_IOCTL, dip, "dev=%x: cmd=%x\n", dev, cmd);
switch (cmd) {
case DEVCTL_DEVICE_GETSTATE:
case DEVCTL_DEVICE_ONLINE:
case DEVCTL_DEVICE_OFFLINE:
case DEVCTL_BUS_GETSTATE:
return (ndi_devctl_ioctl(dip, cmd, arg, mode, 0));
}
if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)
return (EFAULT);
switch (cmd) {
case DEVCTL_DEVICE_RESET:
PCMU_DBG0(PCMU_DBG_IOCTL, dip, "DEVCTL_DEVICE_RESET\n");
rv = ENOTSUP;
break;
case DEVCTL_BUS_QUIESCE:
PCMU_DBG0(PCMU_DBG_IOCTL, dip, "DEVCTL_BUS_QUIESCE\n");
if (ndi_get_bus_state(dip, &bus_state) == NDI_SUCCESS) {
if (bus_state == BUS_QUIESCED) {
break;
}
}
(void) ndi_set_bus_state(dip, BUS_QUIESCED);
break;
case DEVCTL_BUS_UNQUIESCE:
PCMU_DBG0(PCMU_DBG_IOCTL, dip, "DEVCTL_BUS_UNQUIESCE\n");
if (ndi_get_bus_state(dip, &bus_state) == NDI_SUCCESS) {
if (bus_state == BUS_ACTIVE) {
break;
}
}
(void) ndi_set_bus_state(dip, BUS_ACTIVE);
break;
case DEVCTL_BUS_RESET:
PCMU_DBG0(PCMU_DBG_IOCTL, dip, "DEVCTL_BUS_RESET\n");
rv = ENOTSUP;
break;
case DEVCTL_BUS_RESETALL:
PCMU_DBG0(PCMU_DBG_IOCTL, dip, "DEVCTL_BUS_RESETALL\n");
rv = ENOTSUP;
break;
default:
rv = ENOTTY;
}
ndi_dc_freehdl(dcp);
return (rv);
}
static int pcmu_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int flags, char *name, caddr_t valuep, int *lengthp)
{
return (ddi_prop_op(dev, dip, prop_op, flags, name, valuep, lengthp));
}
static int
pcmu_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t off, off_t len, caddr_t *addrp)
{
pcmu_t *pcmu_p = get_pcmu_soft_state(ddi_get_instance(dip));
struct regspec p_regspec;
ddi_map_req_t p_mapreq;
int reglen, rval, r_no;
pci_regspec_t reloc_reg, *rp = &reloc_reg;
PCMU_DBG2(PCMU_DBG_MAP, dip, "rdip=%s%d:",
ddi_driver_name(rdip), ddi_get_instance(rdip));
if (mp->map_flags & DDI_MF_USER_MAPPING) {
return (DDI_ME_UNIMPLEMENTED);
}
switch (mp->map_type) {
case DDI_MT_REGSPEC:
reloc_reg = *(pci_regspec_t *)mp->map_obj.rp;
break;
case DDI_MT_RNUMBER:
r_no = mp->map_obj.rnumber;
PCMU_DBG1(PCMU_DBG_MAP | PCMU_DBG_CONT, dip, " r#=%x", r_no);
if (ddi_getlongprop(DDI_DEV_T_NONE, rdip, DDI_PROP_DONTPASS,
"reg", (caddr_t)&rp, ®len) != DDI_SUCCESS) {
return (DDI_ME_RNUMBER_RANGE);
}
if (r_no < 0 || r_no >= reglen / sizeof (pci_regspec_t)) {
kmem_free(rp, reglen);
return (DDI_ME_RNUMBER_RANGE);
}
rp += r_no;
break;
default:
return (DDI_ME_INVAL);
}
PCMU_DBG0(PCMU_DBG_MAP | PCMU_DBG_CONT, dip, "\n");
if (rval = pcmu_reloc_reg(dip, rdip, pcmu_p, rp)) {
goto done;
}
if (len) {
rp->pci_size_low = (uint_t)len;
}
rp->pci_phys_low += off;
if (rval = pcmu_xlate_reg(pcmu_p, rp, &p_regspec)) {
goto done;
}
p_mapreq = *mp;
p_mapreq.map_type = DDI_MT_REGSPEC;
p_mapreq.map_obj.rp = &p_regspec;
rval = ddi_map(dip, &p_mapreq, 0, 0, addrp);
done:
if (mp->map_type == DDI_MT_RNUMBER) {
kmem_free(rp - r_no, reglen);
}
return (rval);
}
#ifdef DEBUG
int pcmu_peekfault_cnt = 0;
int pcmu_pokefault_cnt = 0;
#endif
static int
pcmu_do_poke(pcmu_t *pcmu_p, peekpoke_ctlops_t *in_args)
{
pcmu_pbm_t *pcbm_p = pcmu_p->pcmu_pcbm_p;
int err = DDI_SUCCESS;
on_trap_data_t otd;
mutex_enter(&pcbm_p->pcbm_pokeflt_mutex);
pcbm_p->pcbm_ontrap_data = &otd;
if (!on_trap(&otd, OT_DATA_ACCESS)) {
uintptr_t tramp = otd.ot_trampoline;
otd.ot_trampoline = (uintptr_t)&poke_fault;
err = do_poke(in_args->size, (void *)in_args->dev_addr,
(void *)in_args->host_addr);
otd.ot_trampoline = tramp;
} else {
err = DDI_FAILURE;
}
pcmu_pbm_clear_error(pcbm_p);
if (otd.ot_trap & OT_DATA_ACCESS) {
err = DDI_FAILURE;
}
no_trap();
pcbm_p->pcbm_ontrap_data = NULL;
mutex_exit(&pcbm_p->pcbm_pokeflt_mutex);
#ifdef DEBUG
if (err == DDI_FAILURE)
pcmu_pokefault_cnt++;
#endif
return (err);
}
static int
pcmu_ctlops_poke(pcmu_t *pcmu_p, peekpoke_ctlops_t *in_args)
{
return (pcmu_do_poke(pcmu_p, in_args));
}
static int
pcmu_do_peek(pcmu_t *pcmu_p, peekpoke_ctlops_t *in_args)
{
int err = DDI_SUCCESS;
on_trap_data_t otd;
if (!on_trap(&otd, OT_DATA_ACCESS)) {
uintptr_t tramp = otd.ot_trampoline;
otd.ot_trampoline = (uintptr_t)&peek_fault;
err = do_peek(in_args->size, (void *)in_args->dev_addr,
(void *)in_args->host_addr);
otd.ot_trampoline = tramp;
} else
err = DDI_FAILURE;
no_trap();
#ifdef DEBUG
if (err == DDI_FAILURE)
pcmu_peekfault_cnt++;
#endif
return (err);
}
static int
pcmu_ctlops_peek(pcmu_t *pcmu_p, peekpoke_ctlops_t *in_args, void *result)
{
result = (void *)in_args->host_addr;
return (pcmu_do_peek(pcmu_p, in_args));
}
static int
pcmu_ctlops(dev_info_t *dip, dev_info_t *rdip,
ddi_ctl_enum_t op, void *arg, void *result)
{
pcmu_t *pcmu_p = get_pcmu_soft_state(ddi_get_instance(dip));
switch (op) {
case DDI_CTLOPS_INITCHILD:
return (pcmu_init_child(pcmu_p, (dev_info_t *)arg));
case DDI_CTLOPS_UNINITCHILD:
return (pcmu_uninit_child(pcmu_p, (dev_info_t *)arg));
case DDI_CTLOPS_REPORTDEV:
return (pcmu_report_dev(rdip));
case DDI_CTLOPS_IOMIN:
return (DDI_SUCCESS);
case DDI_CTLOPS_REGSIZE:
*((off_t *)result) = pcmu_get_reg_set_size(rdip, *((int *)arg));
return (DDI_SUCCESS);
case DDI_CTLOPS_NREGS:
*((uint_t *)result) = pcmu_get_nreg_set(rdip);
return (DDI_SUCCESS);
case DDI_CTLOPS_DVMAPAGESIZE:
*((ulong_t *)result) = 0;
return (DDI_SUCCESS);
case DDI_CTLOPS_POKE:
return (pcmu_ctlops_poke(pcmu_p, (peekpoke_ctlops_t *)arg));
case DDI_CTLOPS_PEEK:
return (pcmu_ctlops_peek(pcmu_p, (peekpoke_ctlops_t *)arg,
result));
case DDI_CTLOPS_AFFINITY:
break;
case DDI_CTLOPS_QUIESCE:
return (DDI_FAILURE);
case DDI_CTLOPS_UNQUIESCE:
return (DDI_FAILURE);
default:
break;
}
PCMU_DBG2(PCMU_DBG_CTLOPS, dip,
"passing request to parent: rdip=%s%d\n",
ddi_driver_name(rdip), ddi_get_instance(rdip));
return (ddi_ctlops(dip, rdip, op, arg, result));
}
static int
pcmu_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
ddi_intr_handle_impl_t *hdlp, void *result)
{
pcmu_t *pcmu_p = get_pcmu_soft_state(ddi_get_instance(dip));
int ret = DDI_SUCCESS;
switch (intr_op) {
case DDI_INTROP_GETCAP:
(void) pci_intx_get_cap(rdip, (int *)result);
break;
case DDI_INTROP_SETCAP:
ret = DDI_ENOTSUP;
break;
case DDI_INTROP_ALLOC:
*(int *)result = hdlp->ih_scratch1;
break;
case DDI_INTROP_FREE:
break;
case DDI_INTROP_GETPRI:
*(int *)result = hdlp->ih_pri ? hdlp->ih_pri : 0;
break;
case DDI_INTROP_SETPRI:
break;
case DDI_INTROP_ADDISR:
ret = pcmu_add_intr(dip, rdip, hdlp);
break;
case DDI_INTROP_REMISR:
ret = pcmu_remove_intr(dip, rdip, hdlp);
break;
case DDI_INTROP_ENABLE:
ret = pcmu_ib_update_intr_state(pcmu_p, rdip, hdlp,
PCMU_INTR_STATE_ENABLE);
break;
case DDI_INTROP_DISABLE:
ret = pcmu_ib_update_intr_state(pcmu_p, rdip, hdlp,
PCMU_INTR_STATE_DISABLE);
break;
case DDI_INTROP_SETMASK:
ret = pci_intx_set_mask(rdip);
break;
case DDI_INTROP_CLRMASK:
ret = pci_intx_clr_mask(rdip);
break;
case DDI_INTROP_GETPENDING:
ret = pci_intx_get_pending(rdip, (int *)result);
break;
case DDI_INTROP_NINTRS:
case DDI_INTROP_NAVAIL:
*(int *)result = i_ddi_get_intx_nintrs(rdip);
break;
case DDI_INTROP_SUPPORTED_TYPES:
*(int *)result = i_ddi_get_intx_nintrs(rdip) ?
DDI_INTR_TYPE_FIXED : 0;
break;
default:
ret = DDI_ENOTSUP;
break;
}
return (ret);
}
static int
pcmu_obj_setup(pcmu_t *pcmu_p)
{
int ret;
mutex_enter(&pcmu_global_mutex);
pcmu_p->pcmu_rev = ddi_prop_get_int(DDI_DEV_T_ANY, pcmu_p->pcmu_dip,
DDI_PROP_DONTPASS, "module-revision#", 0);
pcmu_ib_create(pcmu_p);
pcmu_cb_create(pcmu_p);
pcmu_ecc_create(pcmu_p);
pcmu_pbm_create(pcmu_p);
pcmu_err_create(pcmu_p);
if ((ret = pcmu_intr_setup(pcmu_p)) != DDI_SUCCESS)
goto done;
if ((strncmp(ddi_binding_name(pcmu_p->pcmu_dip),
PCICMU_OPL_DC_BINDING_NAME, strlen(PCICMU_OPL_DC_BINDING_NAME))
!= 0) || (pcmu_p->pcmu_rev > 4)) {
pcmu_kstat_create(pcmu_p);
}
done:
mutex_exit(&pcmu_global_mutex);
if (ret != DDI_SUCCESS) {
cmn_err(CE_NOTE, "Interrupt register failure, returning 0x%x\n",
ret);
}
return (ret);
}
static void
pcmu_obj_destroy(pcmu_t *pcmu_p)
{
mutex_enter(&pcmu_global_mutex);
pcmu_kstat_destroy(pcmu_p);
pcmu_pbm_destroy(pcmu_p);
pcmu_err_destroy(pcmu_p);
pcmu_ecc_destroy(pcmu_p);
pcmu_cb_destroy(pcmu_p);
pcmu_ib_destroy(pcmu_p);
pcmu_intr_teardown(pcmu_p);
mutex_exit(&pcmu_global_mutex);
}
static void
pcmu_obj_resume(pcmu_t *pcmu_p)
{
mutex_enter(&pcmu_global_mutex);
pcmu_ib_configure(pcmu_p->pcmu_ib_p);
pcmu_ecc_configure(pcmu_p);
pcmu_ib_resume(pcmu_p->pcmu_ib_p);
u2u_ittrans_resume((u2u_ittrans_data_t **)
&(pcmu_p->pcmu_cb_p->pcb_ittrans_cookie));
pcmu_pbm_configure(pcmu_p->pcmu_pcbm_p);
pcmu_cb_resume(pcmu_p->pcmu_cb_p);
pcmu_pbm_resume(pcmu_p->pcmu_pcbm_p);
mutex_exit(&pcmu_global_mutex);
}
static void
pcmu_obj_suspend(pcmu_t *pcmu_p)
{
mutex_enter(&pcmu_global_mutex);
pcmu_pbm_suspend(pcmu_p->pcmu_pcbm_p);
pcmu_ib_suspend(pcmu_p->pcmu_ib_p);
pcmu_cb_suspend(pcmu_p->pcmu_cb_p);
mutex_exit(&pcmu_global_mutex);
}
static int
pcmu_intr_setup(pcmu_t *pcmu_p)
{
dev_info_t *dip = pcmu_p->pcmu_dip;
pcmu_pbm_t *pcbm_p = pcmu_p->pcmu_pcbm_p;
pcmu_cb_t *pcb_p = pcmu_p->pcmu_cb_p;
int i, no_of_intrs;
if (ddi_getlongprop(DDI_DEV_T_NONE, dip, DDI_PROP_DONTPASS,
"interrupts", (caddr_t)&pcmu_p->pcmu_inos,
&pcmu_p->pcmu_inos_len) != DDI_SUCCESS) {
cmn_err(CE_PANIC, "%s%d: no interrupts property\n",
ddi_driver_name(dip), ddi_get_instance(dip));
}
i = ddi_getprop(DDI_DEV_T_ANY, dip, 0, "#interrupt-cells", 1);
i = CELLS_1275_TO_BYTES(i);
no_of_intrs = pcmu_p->pcmu_inos_len / i;
for (i = 0; i < no_of_intrs; i++) {
pcmu_p->pcmu_inos[i] =
PCMU_IB_MONDO_TO_INO(pcmu_p->pcmu_inos[i]);
}
pcb_p->pcb_no_of_inos = no_of_intrs;
if (i = pcmu_ecc_register_intr(pcmu_p)) {
goto teardown;
}
intr_dist_add(pcmu_cb_intr_dist, pcb_p);
pcmu_ecc_enable_intr(pcmu_p);
if (i = pcmu_pbm_register_intr(pcbm_p)) {
intr_dist_rem(pcmu_cb_intr_dist, pcb_p);
goto teardown;
}
intr_dist_add(pcmu_pbm_intr_dist, pcbm_p);
pcmu_ib_intr_enable(pcmu_p, pcmu_p->pcmu_inos[CBNINTR_PBM]);
intr_dist_add_weighted(pcmu_ib_intr_dist_all, pcmu_p->pcmu_ib_p);
return (DDI_SUCCESS);
teardown:
pcmu_intr_teardown(pcmu_p);
return (i);
}
void
pcmu_fix_ranges(pcmu_ranges_t *rng_p, int rng_entries)
{
int i;
for (i = 0; i < rng_entries; i++, rng_p++) {
if ((rng_p->child_high & PCI_REG_ADDR_M) == PCI_ADDR_CONFIG)
rng_p->parent_low |= rng_p->child_high;
}
}
static int
map_pcmu_registers(pcmu_t *pcmu_p, dev_info_t *dip)
{
ddi_device_acc_attr_t attr;
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
if (ddi_regs_map_setup(dip, 0, &pcmu_p->pcmu_address[0], 0, 0,
&attr, &pcmu_p->pcmu_ac[0]) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: unable to map reg entry 0\n",
ddi_driver_name(dip), ddi_get_instance(dip));
return (DDI_FAILURE);
}
if (ddi_regs_map_setup(dip, 2, &pcmu_p->pcmu_address[2], 0, 0,
&attr, &pcmu_p->pcmu_ac[2]) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: unable to map reg entry 2\n",
ddi_driver_name(dip), ddi_get_instance(dip));
ddi_regs_map_free(&pcmu_p->pcmu_ac[0]);
return (DDI_FAILURE);
}
attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
if (ddi_regs_map_setup(dip, 1, &pcmu_p->pcmu_address[1], 0,
PCI_CONF_HDR_SIZE, &attr, &pcmu_p->pcmu_ac[1]) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: unable to map reg entry 1\n",
ddi_driver_name(dip), ddi_get_instance(dip));
ddi_regs_map_free(&pcmu_p->pcmu_ac[0]);
return (DDI_FAILURE);
}
PCMU_DBG2(PCMU_DBG_ATTACH, dip, "address (%p,%p)\n",
pcmu_p->pcmu_address[0], pcmu_p->pcmu_address[1]);
return (DDI_SUCCESS);
}
static void
unmap_pcmu_registers(pcmu_t *pcmu_p)
{
ddi_regs_map_free(&pcmu_p->pcmu_ac[0]);
ddi_regs_map_free(&pcmu_p->pcmu_ac[1]);
ddi_regs_map_free(&pcmu_p->pcmu_ac[2]);
}
static uintptr_t
get_reg_base(pcmu_t *pcmu_p)
{
return ((uintptr_t)pcmu_p->pcmu_address[2]);
}
static uintptr_t
get_config_reg_base(pcmu_t *pcmu_p)
{
return ((uintptr_t)(pcmu_p->pcmu_address[1]));
}
uint64_t
ib_get_map_reg(pcmu_ib_mondo_t mondo, uint32_t cpu_id)
{
return ((mondo) | (cpu_id << PCMU_INTR_MAP_REG_TID_SHIFT) |
PCMU_INTR_MAP_REG_VALID);
}
uint32_t
ib_map_reg_get_cpu(volatile uint64_t reg)
{
return ((reg & PCMU_INTR_MAP_REG_TID) >>
PCMU_INTR_MAP_REG_TID_SHIFT);
}
uint64_t *
ib_intr_map_reg_addr(pcmu_ib_t *pib_p, pcmu_ib_ino_t ino)
{
uint64_t *addr;
ASSERT(ino & 0x20);
addr = (uint64_t *)(pib_p->pib_obio_intr_map_regs +
(((uint_t)ino & 0x1f) << 3));
return (addr);
}
uint64_t *
ib_clear_intr_reg_addr(pcmu_ib_t *pib_p, pcmu_ib_ino_t ino)
{
uint64_t *addr;
ASSERT(ino & 0x20);
addr = (uint64_t *)(pib_p->pib_obio_clear_intr_regs +
(((uint_t)ino & 0x1f) << 3));
return (addr);
}
uintptr_t
pcmu_ib_setup(pcmu_ib_t *pib_p)
{
pcmu_t *pcmu_p = pib_p->pib_pcmu_p;
uintptr_t a = get_reg_base(pcmu_p);
pib_p->pib_ign = PCMU_ID_TO_IGN(pcmu_p->pcmu_id);
pib_p->pib_max_ino = PCMU_MAX_INO;
pib_p->pib_obio_intr_map_regs = a + PCMU_IB_OBIO_INTR_MAP_REG_OFFSET;
pib_p->pib_obio_clear_intr_regs =
a + PCMU_IB_OBIO_CLEAR_INTR_REG_OFFSET;
return (a);
}
uint32_t
pcmu_intr_dist_cpuid(pcmu_ib_t *pib_p, pcmu_ib_ino_info_t *ino_p)
{
dev_info_t *rdip = ino_p->pino_ih_head->ih_dip;
dev_info_t *prdip = ddi_get_parent(rdip);
pcmu_ib_ino_info_t *sino_p;
dev_info_t *sdip;
dev_info_t *psdip;
char *buf1 = NULL, *buf2 = NULL;
char *s1, *s2, *s3;
int l2;
int cpu_id;
if (strcmp(ddi_driver_name(prdip), "pcicmu") != 0)
goto newcpu;
s1 = ddi_get_name_addr(rdip);
if (s1 == NULL) {
goto newcpu;
}
buf1 = kmem_alloc(MAXNAMELEN, KM_SLEEP);
buf2 = kmem_alloc(MAXNAMELEN, KM_SLEEP);
s1 = strcpy(buf1, s1);
s2 = strcpy(buf2, s1);
s1 = strrchr(s1, ',');
if (s1) {
*s1 = '\0';
s1 = buf1;
s2 = strrchr(s2, ',');
*(s2 + 1) = '\0';
s2 = buf2;
l2 = strlen(s2);
} else {
(void) strcat(s2, ",");
l2 = strlen(s2);
}
ASSERT(MUTEX_HELD(&pib_p->pib_ino_lst_mutex));
for (sino_p = pib_p->pib_ino_lst; sino_p; sino_p = sino_p->pino_next) {
if ((sino_p == ino_p) || (sino_p->pino_established == 0))
continue;
sdip = sino_p->pino_ih_head->ih_dip;
psdip = ddi_get_parent(sdip);
if (psdip != prdip)
continue;
if (strcmp(ddi_driver_name(psdip), "pcicmu") != 0)
continue;
s3 = ddi_get_name_addr(sdip);
if ((s1 && (strcmp(s1, s3) == 0)) ||
(strncmp(s2, s3, l2) == 0)) {
extern int intr_dist_debug;
if (intr_dist_debug) {
cmn_err(CE_CONT, "intr_dist: "
"pcicmu`pcmu_intr_dist_cpuid "
"%s#%d %s: cpu %d established "
"by %s#%d %s\n", ddi_driver_name(rdip),
ddi_get_instance(rdip),
ddi_deviname(rdip, buf1),
sino_p->pino_cpuid,
ddi_driver_name(sdip),
ddi_get_instance(sdip),
ddi_deviname(sdip, buf2));
}
break;
}
}
if (sino_p) {
cpu_id = sino_p->pino_cpuid;
goto out;
}
newcpu: cpu_id = intr_dist_cpuid();
out: if (buf1)
kmem_free(buf1, MAXNAMELEN);
if (buf2)
kmem_free(buf2, MAXNAMELEN);
return (cpu_id);
}
void
pcmu_cb_teardown(pcmu_t *pcmu_p)
{
pcmu_cb_t *pcb_p = pcmu_p->pcmu_cb_p;
u2u_ittrans_uninit((u2u_ittrans_data_t *)pcb_p->pcb_ittrans_cookie);
}
int
pcmu_ecc_add_intr(pcmu_t *pcmu_p, int inum, pcmu_ecc_intr_info_t *eii_p)
{
uint32_t mondo;
mondo = ((pcmu_p->pcmu_cb_p->pcb_ign << PCMU_INO_BITS) |
pcmu_p->pcmu_inos[inum]);
VERIFY(add_ivintr(mondo, pcmu_pil[inum], (intrfunc)pcmu_ecc_intr,
(caddr_t)eii_p, NULL, NULL) == 0);
return (PCMU_ATTACH_RETCODE(PCMU_ECC_OBJ,
PCMU_OBJ_INTR_ADD, DDI_SUCCESS));
}
void
pcmu_ecc_rem_intr(pcmu_t *pcmu_p, int inum, pcmu_ecc_intr_info_t *eii_p)
{
uint32_t mondo;
mondo = ((pcmu_p->pcmu_cb_p->pcb_ign << PCMU_INO_BITS) |
pcmu_p->pcmu_inos[inum]);
VERIFY(rem_ivintr(mondo, pcmu_pil[inum]) == 0);
}
void
pcmu_pbm_configure(pcmu_pbm_t *pcbm_p)
{
pcmu_t *pcmu_p = pcbm_p->pcbm_pcmu_p;
dev_info_t *dip = pcmu_p->pcmu_dip;
#define pbm_err ((PCMU_PCI_AFSR_E_MASK << PCMU_PCI_AFSR_PE_SHIFT) | \
(PCMU_PCI_AFSR_E_MASK << PCMU_PCI_AFSR_SE_SHIFT))
#define csr_err (PCI_STAT_PERROR | PCI_STAT_S_PERROR | \
PCI_STAT_R_MAST_AB | PCI_STAT_R_TARG_AB | \
PCI_STAT_S_TARG_AB | PCI_STAT_S_PERROR)
*pcbm_p->pcbm_async_flt_status_reg = pbm_err;
PCMU_DBG1(PCMU_DBG_ATTACH, dip,
"pcmu_pbm_configure: conf status reg=%x\n", csr_err);
pcbm_p->pcbm_config_header->ch_status_reg = csr_err;
PCMU_DBG1(PCMU_DBG_ATTACH, dip,
"pcmu_pbm_configure: conf status reg==%x\n",
pcbm_p->pcbm_config_header->ch_status_reg);
(void) ndi_prop_update_int(DDI_DEV_T_ANY, dip, "latency-timer",
(int)pcbm_p->pcbm_config_header->ch_latency_timer_reg);
#undef pbm_err
#undef csr_err
}
uint_t
pcmu_pbm_disable_errors(pcmu_pbm_t *pcbm_p)
{
pcmu_t *pcmu_p = pcbm_p->pcbm_pcmu_p;
pcmu_ib_t *pib_p = pcmu_p->pcmu_ib_p;
*pcbm_p->pcbm_ctrl_reg &= ~PCMU_PCI_CTRL_ERR_INT_EN;
pcmu_ib_intr_disable(pib_p,
pcmu_p->pcmu_inos[CBNINTR_PBM], PCMU_IB_INTR_NOWAIT);
return (BF_NONE);
}
void
pcmu_cb_setup(pcmu_t *pcmu_p)
{
uint64_t csr, csr_pa, pa;
pcmu_cb_t *pcb_p = pcmu_p->pcmu_cb_p;
pcb_p->pcb_ign = PCMU_ID_TO_IGN(pcmu_p->pcmu_id);
pa = (uint64_t)hat_getpfnum(kas.a_hat, pcmu_p->pcmu_address[0]);
pcb_p->pcb_base_pa = pa = pa >> (32 - MMU_PAGESHIFT) << 32;
pcb_p->pcb_map_pa = pa + PCMU_IB_OBIO_INTR_MAP_REG_OFFSET;
pcb_p->pcb_clr_pa = pa + PCMU_IB_OBIO_CLEAR_INTR_REG_OFFSET;
pcb_p->pcb_obsta_pa = pa + PCMU_IB_OBIO_INTR_STATE_DIAG_REG;
csr_pa = pa + PCMU_CB_CONTROL_STATUS_REG_OFFSET;
csr = lddphysio(csr_pa);
if (csr & PCMU_CB_CONTROL_STATUS_APERR) {
csr |= PCMU_CB_CONTROL_STATUS_APERR;
cmn_err(CE_WARN, "clearing UPA address parity error\n");
}
csr |= PCMU_CB_CONTROL_STATUS_APCKEN;
csr &= ~PCMU_CB_CONTROL_STATUS_IAP;
stdphysio(csr_pa, csr);
u2u_ittrans_init(pcmu_p,
(u2u_ittrans_data_t **)&pcb_p->pcb_ittrans_cookie);
}
void
pcmu_ecc_setup(pcmu_ecc_t *pecc_p)
{
pecc_p->pecc_ue.pecc_errpndg_mask = 0;
pecc_p->pecc_ue.pecc_offset_mask = PCMU_ECC_UE_AFSR_DW_OFFSET;
pecc_p->pecc_ue.pecc_offset_shift = PCMU_ECC_UE_AFSR_DW_OFFSET_SHIFT;
pecc_p->pecc_ue.pecc_size_log2 = 3;
}
static uintptr_t
get_pbm_reg_base(pcmu_t *pcmu_p)
{
return ((uintptr_t)(pcmu_p->pcmu_address[0]));
}
void
pcmu_pbm_setup(pcmu_pbm_t *pcbm_p)
{
pcmu_t *pcmu_p = pcbm_p->pcbm_pcmu_p;
uintptr_t a = get_pbm_reg_base(pcmu_p);
pcbm_p->pcbm_config_header =
(config_header_t *)get_config_reg_base(pcmu_p);
pcbm_p->pcbm_ctrl_reg = (uint64_t *)(a + PCMU_PCI_CTRL_REG_OFFSET);
pcbm_p->pcbm_diag_reg = (uint64_t *)(a + PCMU_PCI_DIAG_REG_OFFSET);
pcbm_p->pcbm_async_flt_status_reg =
(uint64_t *)(a + PCMU_PCI_ASYNC_FLT_STATUS_REG_OFFSET);
pcbm_p->pcbm_async_flt_addr_reg =
(uint64_t *)(a + PCMU_PCI_ASYNC_FLT_ADDR_REG_OFFSET);
}
void
pcmu_pbm_teardown(pcmu_pbm_t *pcbm_p)
{
}
int
pcmu_get_numproxy(dev_info_t *dip)
{
return (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"#upa-interrupt-proxies", 1));
}
int
pcmu_get_portid(dev_info_t *dip)
{
return (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"portid", -1));
}
static pcmu_kev_mask_t
pcicmu_pcmu_events[] = {
{"pio_cycles_b", 0xf}, {"interrupts", 0x11},
{"upa_inter_nack", 0x12}, {"pio_reads", 0x13},
{"pio_writes", 0x14},
{"clear_pic", 0x1f}
};
void
pcmu_kstat_init()
{
pcmu_name_kstat = (pcmu_ksinfo_t *)kmem_alloc(sizeof (pcmu_ksinfo_t),
KM_NOSLEEP);
if (pcmu_name_kstat == NULL) {
cmn_err(CE_WARN, "pcicmu : no space for kstat\n");
} else {
pcmu_name_kstat->pic_no_evs =
sizeof (pcicmu_pcmu_events) / sizeof (pcmu_kev_mask_t);
pcmu_name_kstat->pic_shift[0] = PCMU_SHIFT_PIC0;
pcmu_name_kstat->pic_shift[1] = PCMU_SHIFT_PIC1;
pcmu_create_name_kstat("pcmup",
pcmu_name_kstat, pcicmu_pcmu_events);
}
}
void
pcmu_kstat_fini()
{
if (pcmu_name_kstat != NULL) {
pcmu_delete_name_kstat(pcmu_name_kstat);
kmem_free(pcmu_name_kstat, sizeof (pcmu_ksinfo_t));
pcmu_name_kstat = NULL;
}
}
void
pcmu_add_upstream_kstat(pcmu_t *pcmu_p)
{
pcmu_cntr_pa_t *cntr_pa_p = &pcmu_p->pcmu_uks_pa;
uint64_t regbase = va_to_pa((void *)get_reg_base(pcmu_p));
cntr_pa_p->pcr_pa = regbase + PCMU_PERF_PCR_OFFSET;
cntr_pa_p->pic_pa = regbase + PCMU_PERF_PIC_OFFSET;
pcmu_p->pcmu_uksp = pcmu_create_cntr_kstat(pcmu_p, "pcmup",
NUM_OF_PICS, pcmu_cntr_kstat_pa_update, cntr_pa_p);
}
static void
u2u_ittrans_init(pcmu_t *pcmu_p, u2u_ittrans_data_t **ittrans_cookie)
{
u2u_ittrans_data_t *u2u_trans_p;
ddi_device_acc_attr_t attr;
int ret;
int board;
u2u_trans_p = (u2u_ittrans_data_t *)
kmem_zalloc(sizeof (u2u_ittrans_data_t), KM_SLEEP);
board = ddi_getprop(DDI_DEV_T_ANY, pcmu_p->pcmu_dip,
DDI_PROP_DONTPASS, "board#", -1);
u2u_trans_p->u2u_board = board;
if (board == -1) {
cmn_err(CE_PANIC, "u2u:Invalid property;board = %d", board);
}
mutex_init(&(u2u_trans_p->u2u_ittrans_lock), "u2u_ittrans_lock",
MUTEX_DEFAULT, NULL);
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
ret = ddi_regs_map_setup(pcmu_p->pcmu_dip,
REGS_INDEX_OF_U2U, (caddr_t *)(&(u2u_trans_p->u2u_regs_base)),
0, 0, &attr, &(u2u_trans_p->u2u_acc));
if (ret != DDI_SUCCESS) {
cmn_err(CE_PANIC, "u2u%d: registers map setup failed", board);
}
u2u_trans_p->u2u_port_id = *(volatile int32_t *)
(u2u_trans_p->u2u_regs_base + U2U_PID_REGISTER_OFFSET);
if (pcmu_p->pcmu_id != u2u_trans_p->u2u_port_id) {
cmn_err(CE_PANIC, "u2u%d: Invalid Port-ID", board);
}
*ittrans_cookie = u2u_trans_p;
}
static void
u2u_ittrans_resume(u2u_ittrans_data_t **ittrans_cookie)
{
u2u_ittrans_data_t *u2u_trans_p;
u2u_ittrans_id_t *ittrans_id_p;
uintptr_t data_reg_addr;
int ix;
u2u_trans_p = *ittrans_cookie;
for (ix = 0; ix < U2U_DATA_NUM; ix++) {
ittrans_id_p = &(u2u_trans_p->u2u_ittrans_id[ix]);
data_reg_addr = u2u_trans_p->u2u_regs_base +
U2U_DATA_REGISTER_OFFSET + (ix * sizeof (uint64_t));
if (ittrans_id_p->u2u_ino_map_reg == NULL) {
continue;
}
*(volatile uint32_t *) (data_reg_addr) =
(uint32_t)ittrans_id_p->u2u_tgt_cpu_id;
}
}
static void
u2u_ittrans_uninit(u2u_ittrans_data_t *ittrans_cookie)
{
if (ittrans_cookie == NULL) {
return;
}
if (ittrans_cookie == (u2u_ittrans_data_t *)(-1)) {
return;
}
ddi_regs_map_free(&(ittrans_cookie->u2u_acc));
mutex_destroy(&(ittrans_cookie->u2u_ittrans_lock));
kmem_free((void *)ittrans_cookie, sizeof (u2u_ittrans_data_t));
}
int
u2u_translate_tgtid(pcmu_t *pcmu_p, uint_t cpu_id,
volatile uint64_t *pino_map_reg)
{
int index = -1;
int ix;
int err_level;
u2u_ittrans_id_t *ittrans_id_p;
uintptr_t data_reg_addr;
u2u_ittrans_data_t *ittrans_cookie;
ittrans_cookie =
(u2u_ittrans_data_t *)(pcmu_p->pcmu_cb_p->pcb_ittrans_cookie);
if (ittrans_cookie == NULL) {
return (cpu_id);
}
if (ittrans_cookie == (u2u_ittrans_data_t *)(-1)) {
return (-1);
}
mutex_enter(&(ittrans_cookie->u2u_ittrans_lock));
for (ix = 0; ix < U2U_DATA_NUM; ix++) {
ittrans_id_p = &(ittrans_cookie->u2u_ittrans_id[ix]);
if (ittrans_id_p->u2u_ino_map_reg == pino_map_reg) {
index = ix;
break;
}
if (index == -1 &&
ittrans_id_p->u2u_ino_map_reg == NULL) {
index = ix;
}
}
if (index == -1) {
if (panicstr) {
err_level = CE_WARN;
} else {
err_level = CE_PANIC;
}
cmn_err(err_level, "u2u%d:No more U2U-Data regs!!",
ittrans_cookie->u2u_board);
return (cpu_id);
}
data_reg_addr = ittrans_cookie->u2u_regs_base
+ U2U_DATA_REGISTER_OFFSET
+ (index * sizeof (uint64_t));
*(volatile uint32_t *) (data_reg_addr) = (uint32_t)cpu_id;
if (!panicstr) {
ittrans_id_p = &(ittrans_cookie->u2u_ittrans_id[index]);
ittrans_id_p->u2u_tgt_cpu_id = cpu_id;
ittrans_id_p->u2u_ino_map_reg = pino_map_reg;
}
mutex_exit(&(ittrans_cookie->u2u_ittrans_lock));
return (index);
}
void
u2u_ittrans_cleanup(u2u_ittrans_data_t *ittrans_cookie,
volatile uint64_t *pino_map_reg)
{
int ix;
u2u_ittrans_id_t *ittrans_id_p;
if (ittrans_cookie == NULL) {
return;
}
if (ittrans_cookie == (u2u_ittrans_data_t *)(-1)) {
return;
}
mutex_enter(&(ittrans_cookie->u2u_ittrans_lock));
for (ix = 0; ix < U2U_DATA_NUM; ix++) {
ittrans_id_p = &(ittrans_cookie->u2u_ittrans_id[ix]);
if (ittrans_id_p->u2u_ino_map_reg == pino_map_reg) {
ittrans_id_p->u2u_ino_map_reg = NULL;
break;
}
}
mutex_exit(&(ittrans_cookie->u2u_ittrans_lock));
}
void
pcmu_ecc_classify(uint64_t err, pcmu_ecc_errstate_t *ecc_err_p)
{
struct async_flt *ecc = &ecc_err_p->ecc_aflt;
pcmu_t *pcmu_p = ecc_err_p->ecc_ii_p.pecc_p->pecc_pcmu_p;
ASSERT(MUTEX_HELD(&pcmu_p->pcmu_err_mutex));
ecc_err_p->ecc_bridge_type = PCI_OPLCMU;
ecc_err_p->ecc_dev_id = (ecc_err_p->ecc_afsr & PCMU_ECC_UE_AFSR_ID)
>> PCMU_ECC_UE_AFSR_ID_SHIFT;
ecc_err_p->ecc_dw_offset = (ecc_err_p->ecc_afsr &
PCMU_ECC_UE_AFSR_DW_OFFSET) >> PCMU_ECC_UE_AFSR_DW_OFFSET_SHIFT;
switch (err) {
case PCMU_ECC_UE_AFSR_E_PIO:
if (ecc_err_p->pecc_pri) {
ecc->flt_erpt_class = PCI_ECC_PIO_UE;
} else {
ecc->flt_erpt_class = PCI_ECC_SEC_PIO_UE;
}
ecc->flt_panic = 1;
break;
default:
return;
}
}
int
pcmu_pbm_classify(pcmu_pbm_errstate_t *pbm_err_p)
{
uint32_t e;
int nerr = 0;
char **tmp_class;
if (pbm_err_p->pcbm_pri) {
tmp_class = &pbm_err_p->pcbm_pci.pcmu_err_class;
e = PBM_AFSR_TO_PRIERR(pbm_err_p->pbm_afsr);
pbm_err_p->pbm_log = FM_LOG_PCI;
} else {
tmp_class = &pbm_err_p->pbm_err_class;
e = PBM_AFSR_TO_SECERR(pbm_err_p->pbm_afsr);
pbm_err_p->pbm_log = FM_LOG_PBM;
}
if (e & PCMU_PCI_AFSR_E_MA) {
*tmp_class = pbm_err_p->pcbm_pri ? PCI_MA : PCI_SEC_MA;
nerr++;
}
return (nerr);
}
static void
pcmu_clear_error(pcmu_t *pcmu_p, pcmu_pbm_errstate_t *pbm_err_p)
{
pcmu_pbm_t *pcbm_p = pcmu_p->pcmu_pcbm_p;
ASSERT(MUTEX_HELD(&pcbm_p->pcbm_pcmu_p->pcmu_err_mutex));
*pcbm_p->pcbm_ctrl_reg = pbm_err_p->pbm_ctl_stat;
*pcbm_p->pcbm_async_flt_status_reg = pbm_err_p->pbm_afsr;
pcbm_p->pcbm_config_header->ch_status_reg =
pbm_err_p->pcbm_pci.pcmu_cfg_stat;
}
int
pcmu_pbm_err_handler(dev_info_t *dip, ddi_fm_error_t *derr,
const void *impl_data, int caller)
{
int fatal = 0;
int nonfatal = 0;
int unknown = 0;
uint32_t prierr, secerr;
pcmu_pbm_errstate_t pbm_err;
pcmu_t *pcmu_p = (pcmu_t *)impl_data;
int ret = 0;
ASSERT(MUTEX_HELD(&pcmu_p->pcmu_err_mutex));
pcmu_pbm_errstate_get(pcmu_p, &pbm_err);
derr->fme_ena = derr->fme_ena ? derr->fme_ena :
fm_ena_generate(0, FM_ENA_FMT1);
prierr = PBM_AFSR_TO_PRIERR(pbm_err.pbm_afsr);
secerr = PBM_AFSR_TO_SECERR(pbm_err.pbm_afsr);
if (derr->fme_flag == DDI_FM_ERR_PEEK) {
nonfatal++;
goto done;
} else if (derr->fme_flag == DDI_FM_ERR_POKE) {
if ((prierr == PCMU_PCI_AFSR_E_MA) && !secerr &&
(pbm_err.pcbm_pci.pcmu_cfg_stat & PCI_STAT_R_MAST_AB)) {
nonfatal++;
goto done;
}
}
if (prierr || secerr) {
ret = pcmu_pbm_afsr_report(dip, derr->fme_ena, &pbm_err);
if (ret == DDI_FM_FATAL) {
fatal++;
} else {
nonfatal++;
}
}
ret = pcmu_cfg_report(dip, derr, &pbm_err.pcbm_pci, caller, prierr);
if (ret == DDI_FM_FATAL) {
fatal++;
} else if (ret == DDI_FM_NONFATAL) {
nonfatal++;
}
done:
if (ret == DDI_FM_FATAL) {
fatal++;
} else if (ret == DDI_FM_NONFATAL) {
nonfatal++;
} else if (ret == DDI_FM_UNKNOWN) {
unknown++;
}
pcmu_clear_error(pcmu_p, &pbm_err);
return (fatal ? DDI_FM_FATAL : (nonfatal ? DDI_FM_NONFATAL :
(unknown ? DDI_FM_UNKNOWN : DDI_FM_OK)));
}
int
pcmu_check_error(pcmu_t *pcmu_p)
{
pcmu_pbm_t *pcbm_p = pcmu_p->pcmu_pcbm_p;
uint16_t pcmu_cfg_stat;
uint64_t pbm_afsr;
ASSERT(MUTEX_HELD(&pcmu_p->pcmu_err_mutex));
pcmu_cfg_stat = pcbm_p->pcbm_config_header->ch_status_reg;
pbm_afsr = *pcbm_p->pcbm_async_flt_status_reg;
if ((pcmu_cfg_stat & (PCI_STAT_S_PERROR | PCI_STAT_S_TARG_AB |
PCI_STAT_R_TARG_AB | PCI_STAT_R_MAST_AB |
PCI_STAT_S_SYSERR | PCI_STAT_PERROR)) ||
(PBM_AFSR_TO_PRIERR(pbm_afsr))) {
return (1);
}
return (0);
}
static void
pcmu_pbm_errstate_get(pcmu_t *pcmu_p, pcmu_pbm_errstate_t *pbm_err_p)
{
pcmu_pbm_t *pcbm_p = pcmu_p->pcmu_pcbm_p;
ASSERT(MUTEX_HELD(&pcmu_p->pcmu_err_mutex));
bzero(pbm_err_p, sizeof (pcmu_pbm_errstate_t));
pbm_err_p->pbm_bridge_type = PCI_OPLCMU;
pbm_err_p->pcbm_pci.pcmu_cfg_stat =
pcbm_p->pcbm_config_header->ch_status_reg;
pbm_err_p->pbm_ctl_stat = *pcbm_p->pcbm_ctrl_reg;
pbm_err_p->pcbm_pci.pcmu_cfg_comm =
pcbm_p->pcbm_config_header->ch_command_reg;
pbm_err_p->pbm_afsr = *pcbm_p->pcbm_async_flt_status_reg;
pbm_err_p->pbm_afar = *pcbm_p->pcbm_async_flt_addr_reg;
pbm_err_p->pcbm_pci.pcmu_pa = *pcbm_p->pcbm_async_flt_addr_reg;
}
static void
pcmu_pbm_clear_error(pcmu_pbm_t *pcbm_p)
{
uint64_t pbm_afsr;
pbm_afsr = *pcbm_p->pcbm_async_flt_status_reg;
while (((pbm_afsr >> PCMU_PCI_AFSR_PE_SHIFT) &
(PCMU_PCI_AFSR_E_MA | PCMU_PCI_AFSR_E_TA))) {
pbm_afsr = *pcbm_p->pcbm_async_flt_status_reg;
}
}
void
pcmu_err_create(pcmu_t *pcmu_p)
{
if (pcmu_ecc_queue == NULL) {
pcmu_ecc_queue = errorq_create("pcmu_ecc_queue",
(errorq_func_t)pcmu_ecc_err_drain,
(void *)NULL,
ECC_MAX_ERRS, sizeof (pcmu_ecc_errstate_t),
PIL_2, ERRORQ_VITAL);
if (pcmu_ecc_queue == NULL)
panic("failed to create required system error queue");
}
mutex_init(&pcmu_p->pcmu_err_mutex, NULL, MUTEX_DRIVER,
(void *)pcmu_p->pcmu_fm_ibc);
}
void
pcmu_err_destroy(pcmu_t *pcmu_p)
{
mutex_destroy(&pcmu_p->pcmu_err_mutex);
}
void
pcmu_pbm_ereport_post(dev_info_t *dip, uint64_t ena,
pcmu_pbm_errstate_t *pbm_err)
{
char *aux_msg;
uint32_t prierr, secerr;
pcmu_t *pcmu_p;
int instance = ddi_get_instance(dip);
ena = ena ? ena : fm_ena_generate(0, FM_ENA_FMT1);
pcmu_p = get_pcmu_soft_state(instance);
prierr = PBM_AFSR_TO_PRIERR(pbm_err->pbm_afsr);
secerr = PBM_AFSR_TO_SECERR(pbm_err->pbm_afsr);
if (prierr)
aux_msg = "PCI primary error: Master Abort";
else if (secerr)
aux_msg = "PCI secondary error: Master Abort";
else
aux_msg = "";
cmn_err(CE_WARN, "%s %s: %s %s=0x%lx, %s=0x%lx, %s=0x%lx %s=0x%x",
(pcmu_p->pcmu_pcbm_p)->pcbm_nameinst_str,
(pcmu_p->pcmu_pcbm_p)->pcbm_nameaddr_str,
aux_msg,
PCI_PBM_AFAR, pbm_err->pbm_afar,
PCI_PBM_AFSR, pbm_err->pbm_afsr,
PCI_PBM_CSR, pbm_err->pbm_ctl_stat,
"portid", pcmu_p->pcmu_id);
}