/*- * Copyright (c) 1999, 2000 Matthew R. Green * Copyright (c) 2001 - 2003 by Thomas Moestl * Copyright (c) 2009 by Marius Strobl * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: NetBSD: psycho.c,v 1.39 2001/10/07 20:30:41 eeh Exp * from: FreeBSD: psycho.c 183152 2008-09-18 19:45:22Z marius */ #include __FBSDID("$FreeBSD$"); /* * Driver for `Fire' JBus to PCI Express and `Oberon' Uranus to PCI Express * bridges */ #include "opt_fire.h" #include "opt_ofw_pci.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" struct fire_msiqarg; static const struct fire_desc *fire_get_desc(device_t dev); static void fire_dmamap_sync(bus_dma_tag_t dt __unused, bus_dmamap_t map, bus_dmasync_op_t op); static int fire_get_intrmap(struct fire_softc *sc, u_int ino, bus_addr_t *intrmapptr, bus_addr_t *intrclrptr); static void fire_intr_assign(void *arg); static void fire_intr_clear(void *arg); static void fire_intr_disable(void *arg); static void fire_intr_enable(void *arg); static int fire_intr_register(struct fire_softc *sc, u_int ino); static inline void fire_msiq_common(struct intr_vector *iv, struct fire_msiqarg *fmqa); static void fire_msiq_filter(void *cookie); static void fire_msiq_handler(void *cookie); static void fire_set_intr(struct fire_softc *sc, u_int index, u_int ino, driver_filter_t handler, void *arg); static timecounter_get_t fire_get_timecount; /* Interrupt handlers */ static driver_filter_t fire_dmc_pec; static driver_filter_t fire_pcie; static driver_filter_t fire_xcb; /* * Methods */ static bus_activate_resource_t fire_activate_resource; static bus_adjust_resource_t fire_adjust_resource; static pcib_alloc_msi_t fire_alloc_msi; static pcib_alloc_msix_t fire_alloc_msix; static bus_alloc_resource_t fire_alloc_resource; static device_attach_t fire_attach; static bus_get_dma_tag_t fire_get_dma_tag; static ofw_bus_get_node_t fire_get_node; static pcib_map_msi_t fire_map_msi; static pcib_maxslots_t fire_maxslots; static device_probe_t fire_probe; static pcib_read_config_t fire_read_config; static bus_read_ivar_t fire_read_ivar; static pcib_release_msi_t fire_release_msi; static pcib_release_msix_t fire_release_msix; static pcib_route_interrupt_t fire_route_interrupt; static bus_setup_intr_t fire_setup_intr; static bus_teardown_intr_t fire_teardown_intr; static pcib_write_config_t fire_write_config; static device_method_t fire_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fire_probe), DEVMETHOD(device_attach, fire_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, fire_read_ivar), DEVMETHOD(bus_setup_intr, fire_setup_intr), DEVMETHOD(bus_teardown_intr, fire_teardown_intr), DEVMETHOD(bus_alloc_resource, fire_alloc_resource), DEVMETHOD(bus_activate_resource, fire_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, fire_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_get_dma_tag, fire_get_dma_tag), /* pcib interface */ DEVMETHOD(pcib_maxslots, fire_maxslots), DEVMETHOD(pcib_read_config, fire_read_config), DEVMETHOD(pcib_write_config, fire_write_config), DEVMETHOD(pcib_route_interrupt, fire_route_interrupt), DEVMETHOD(pcib_alloc_msi, fire_alloc_msi), DEVMETHOD(pcib_release_msi, fire_release_msi), DEVMETHOD(pcib_alloc_msix, fire_alloc_msix), DEVMETHOD(pcib_release_msix, fire_release_msix), DEVMETHOD(pcib_map_msi, fire_map_msi), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, fire_get_node), DEVMETHOD_END }; static devclass_t fire_devclass; DEFINE_CLASS_0(pcib, fire_driver, fire_methods, sizeof(struct fire_softc)); EARLY_DRIVER_MODULE(fire, nexus, fire_driver, fire_devclass, 0, 0, BUS_PASS_BUS); MODULE_DEPEND(fire, nexus, 1, 1, 1); static const struct intr_controller fire_ic = { fire_intr_enable, fire_intr_disable, fire_intr_assign, fire_intr_clear }; struct fire_icarg { struct fire_softc *fica_sc; bus_addr_t fica_map; bus_addr_t fica_clr; }; static const struct intr_controller fire_msiqc_filter = { fire_intr_enable, fire_intr_disable, fire_intr_assign, NULL }; struct fire_msiqarg { struct fire_icarg fmqa_fica; struct mtx fmqa_mtx; struct fo_msiq_record *fmqa_base; uint64_t fmqa_head; uint64_t fmqa_tail; uint32_t fmqa_msiq; uint32_t fmqa_msi; }; #define FIRE_PERF_CNT_QLTY 100 #define FIRE_SPC_BARRIER(spc, sc, offs, len, flags) \ bus_barrier((sc)->sc_mem_res[(spc)], (offs), (len), (flags)) #define FIRE_SPC_READ_8(spc, sc, offs) \ bus_read_8((sc)->sc_mem_res[(spc)], (offs)) #define FIRE_SPC_WRITE_8(spc, sc, offs, v) \ bus_write_8((sc)->sc_mem_res[(spc)], (offs), (v)) #ifndef FIRE_DEBUG #define FIRE_SPC_SET(spc, sc, offs, reg, v) \ FIRE_SPC_WRITE_8((spc), (sc), (offs), (v)) #else #define FIRE_SPC_SET(spc, sc, offs, reg, v) do { \ device_printf((sc)->sc_dev, reg " 0x%016llx -> 0x%016llx\n", \ (unsigned long long)FIRE_SPC_READ_8((spc), (sc), (offs)), \ (unsigned long long)(v)); \ FIRE_SPC_WRITE_8((spc), (sc), (offs), (v)); \ } while (0) #endif #define FIRE_PCI_BARRIER(sc, offs, len, flags) \ FIRE_SPC_BARRIER(FIRE_PCI, (sc), (offs), len, flags) #define FIRE_PCI_READ_8(sc, offs) \ FIRE_SPC_READ_8(FIRE_PCI, (sc), (offs)) #define FIRE_PCI_WRITE_8(sc, offs, v) \ FIRE_SPC_WRITE_8(FIRE_PCI, (sc), (offs), (v)) #define FIRE_CTRL_BARRIER(sc, offs, len, flags) \ FIRE_SPC_BARRIER(FIRE_CTRL, (sc), (offs), len, flags) #define FIRE_CTRL_READ_8(sc, offs) \ FIRE_SPC_READ_8(FIRE_CTRL, (sc), (offs)) #define FIRE_CTRL_WRITE_8(sc, offs, v) \ FIRE_SPC_WRITE_8(FIRE_CTRL, (sc), (offs), (v)) #define FIRE_PCI_SET(sc, offs, v) \ FIRE_SPC_SET(FIRE_PCI, (sc), (offs), # offs, (v)) #define FIRE_CTRL_SET(sc, offs, v) \ FIRE_SPC_SET(FIRE_CTRL, (sc), (offs), # offs, (v)) struct fire_desc { const char *fd_string; int fd_mode; const char *fd_name; }; static const struct fire_desc const fire_compats[] = { { "pciex108e,80f0", FIRE_MODE_FIRE, "Fire" }, #if 0 { "pciex108e,80f8", FIRE_MODE_OBERON, "Oberon" }, #endif { NULL, 0, NULL } }; static const struct fire_desc * fire_get_desc(device_t dev) { const struct fire_desc *desc; const char *compat; compat = ofw_bus_get_compat(dev); if (compat == NULL) return (NULL); for (desc = fire_compats; desc->fd_string != NULL; desc++) if (strcmp(desc->fd_string, compat) == 0) return (desc); return (NULL); } static int fire_probe(device_t dev) { const char *dtype; dtype = ofw_bus_get_type(dev); if (dtype != NULL && strcmp(dtype, OFW_TYPE_PCIE) == 0 && fire_get_desc(dev) != NULL) { device_set_desc(dev, "Sun Host-PCIe bridge"); return (BUS_PROBE_GENERIC); } return (ENXIO); } static int fire_attach(device_t dev) { struct fire_softc *sc; const struct fire_desc *desc; struct ofw_pci_msi_ranges msi_ranges; struct ofw_pci_msi_addr_ranges msi_addr_ranges; struct ofw_pci_msi_eq_to_devino msi_eq_to_devino; struct fire_msiqarg *fmqa; struct timecounter *tc; struct ofw_pci_ranges *range; uint64_t ino_bitmap, val; phandle_t node; uint32_t prop, prop_array[2]; int i, j, mode; u_int lw; uint16_t mps; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); desc = fire_get_desc(dev); mode = desc->fd_mode; sc->sc_dev = dev; sc->sc_node = node; sc->sc_mode = mode; sc->sc_flags = 0; mtx_init(&sc->sc_msi_mtx, "msi_mtx", NULL, MTX_DEF); mtx_init(&sc->sc_pcib_mtx, "pcib_mtx", NULL, MTX_SPIN); /* * Fire and Oberon have two register banks: * (0) per-PBM PCI Express configuration and status registers * (1) (shared) Fire/Oberon controller configuration and status * registers */ for (i = 0; i < FIRE_NREG; i++) { j = i; sc->sc_mem_res[i] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &j, RF_ACTIVE); if (sc->sc_mem_res[i] == NULL) panic("%s: could not allocate register bank %d", __func__, i); } if (OF_getprop(node, "portid", &sc->sc_ign, sizeof(sc->sc_ign)) == -1) panic("%s: could not determine IGN", __func__); if (OF_getprop(node, "module-revision#", &prop, sizeof(prop)) == -1) panic("%s: could not determine module-revision", __func__); device_printf(dev, "%s, module-revision %d, IGN %#x\n", desc->fd_name, prop, sc->sc_ign); /* * Hunt through all the interrupt mapping regs and register * the interrupt controller for our interrupt vectors. We do * this early in order to be able to catch stray interrupts. */ i = OF_getprop(node, "ino-bitmap", (void *)prop_array, sizeof(prop_array)); if (i == -1) panic("%s: could not get ino-bitmap", __func__); ino_bitmap = ((uint64_t)prop_array[1] << 32) | prop_array[0]; for (i = 0; i <= FO_MAX_INO; i++) { if ((ino_bitmap & (1ULL << i)) == 0) continue; j = fire_intr_register(sc, i); if (j != 0) device_printf(dev, "could not register interrupt " "controller for INO %d (%d)\n", i, j); } /* JBC/UBC module initialization */ FIRE_CTRL_SET(sc, FO_XBC_ERR_LOG_EN, ~0ULL); FIRE_CTRL_SET(sc, FO_XBC_ERR_STAT_CLR, ~0ULL); /* not enabled by OpenSolaris */ FIRE_CTRL_SET(sc, FO_XBC_INT_EN, ~0ULL); if (sc->sc_mode == FIRE_MODE_FIRE) { FIRE_CTRL_SET(sc, FIRE_JBUS_PAR_CTRL, FIRE_JBUS_PAR_CTRL_P_EN); FIRE_CTRL_SET(sc, FIRE_JBC_FATAL_RST_EN, ((1ULL << FIRE_JBC_FATAL_RST_EN_SPARE_P_INT_SHFT) & FIRE_JBC_FATAL_RST_EN_SPARE_P_INT_MASK) | FIRE_JBC_FATAL_RST_EN_MB_PEA_P_INT | FIRE_JBC_FATAL_RST_EN_CPE_P_INT | FIRE_JBC_FATAL_RST_EN_APE_P_INT | FIRE_JBC_FATAL_RST_EN_PIO_CPE_INT | FIRE_JBC_FATAL_RST_EN_JTCEEW_P_INT | FIRE_JBC_FATAL_RST_EN_JTCEEI_P_INT | FIRE_JBC_FATAL_RST_EN_JTCEER_P_INT); FIRE_CTRL_SET(sc, FIRE_JBC_CORE_BLOCK_INT_EN, ~0ULL); } /* TLU initialization */ FIRE_PCI_SET(sc, FO_PCI_TLU_OEVENT_STAT_CLR, FO_PCI_TLU_OEVENT_S_MASK | FO_PCI_TLU_OEVENT_P_MASK); /* not enabled by OpenSolaris */ FIRE_PCI_SET(sc, FO_PCI_TLU_OEVENT_INT_EN, FO_PCI_TLU_OEVENT_S_MASK | FO_PCI_TLU_OEVENT_P_MASK); FIRE_PCI_SET(sc, FO_PCI_TLU_UERR_STAT_CLR, FO_PCI_TLU_UERR_INT_S_MASK | FO_PCI_TLU_UERR_INT_P_MASK); /* not enabled by OpenSolaris */ FIRE_PCI_SET(sc, FO_PCI_TLU_UERR_INT_EN, FO_PCI_TLU_UERR_INT_S_MASK | FO_PCI_TLU_UERR_INT_P_MASK); FIRE_PCI_SET(sc, FO_PCI_TLU_CERR_STAT_CLR, FO_PCI_TLU_CERR_INT_S_MASK | FO_PCI_TLU_CERR_INT_P_MASK); /* not enabled by OpenSolaris */ FIRE_PCI_SET(sc, FO_PCI_TLU_CERR_INT_EN, FO_PCI_TLU_CERR_INT_S_MASK | FO_PCI_TLU_CERR_INT_P_MASK); val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_CTRL) | ((FO_PCI_TLU_CTRL_L0S_TIM_DFLT << FO_PCI_TLU_CTRL_L0S_TIM_SHFT) & FO_PCI_TLU_CTRL_L0S_TIM_MASK) | ((FO_PCI_TLU_CTRL_CFG_DFLT << FO_PCI_TLU_CTRL_CFG_SHFT) & FO_PCI_TLU_CTRL_CFG_MASK); if (sc->sc_mode == FIRE_MODE_OBERON) val &= ~FO_PCI_TLU_CTRL_NWPR_EN; val |= FO_PCI_TLU_CTRL_CFG_REMAIN_DETECT_QUIET; FIRE_PCI_SET(sc, FO_PCI_TLU_CTRL, val); FIRE_PCI_SET(sc, FO_PCI_TLU_DEV_CTRL, 0); FIRE_PCI_SET(sc, FO_PCI_TLU_LNK_CTRL, FO_PCI_TLU_LNK_CTRL_CLK); /* DLU/LPU initialization */ if (sc->sc_mode == FIRE_MODE_OBERON) FIRE_PCI_SET(sc, FO_PCI_LPU_INT_MASK, 0); else FIRE_PCI_SET(sc, FO_PCI_LPU_RST, 0); FIRE_PCI_SET(sc, FO_PCI_LPU_LNK_LYR_CFG, FO_PCI_LPU_LNK_LYR_CFG_VC0_EN); FIRE_PCI_SET(sc, FO_PCI_LPU_FLW_CTRL_UPDT_CTRL, FO_PCI_LPU_FLW_CTRL_UPDT_CTRL_FC0_NP_EN | FO_PCI_LPU_FLW_CTRL_UPDT_CTRL_FC0_P_EN); if (sc->sc_mode == FIRE_MODE_OBERON) FIRE_PCI_SET(sc, FO_PCI_LPU_TXLNK_RPLY_TMR_THRS, (OBERON_PCI_LPU_TXLNK_RPLY_TMR_THRS_DFLT << FO_PCI_LPU_TXLNK_RPLY_TMR_THRS_SHFT) & FO_PCI_LPU_TXLNK_RPLY_TMR_THRS_MASK); else { switch ((FIRE_PCI_READ_8(sc, FO_PCI_TLU_LNK_STAT) & FO_PCI_TLU_LNK_STAT_WDTH_MASK) >> FO_PCI_TLU_LNK_STAT_WDTH_SHFT) { case 1: lw = 0; break; case 4: lw = 1; break; case 8: lw = 2; break; case 16: lw = 3; break; default: lw = 0; } mps = (FIRE_PCI_READ_8(sc, FO_PCI_TLU_CTRL) & FO_PCI_TLU_CTRL_CFG_MPS_MASK) >> FO_PCI_TLU_CTRL_CFG_MPS_SHFT; i = sizeof(fire_freq_nak_tmr_thrs) / sizeof(*fire_freq_nak_tmr_thrs); if (mps >= i) mps = i - 1; FIRE_PCI_SET(sc, FO_PCI_LPU_TXLNK_FREQ_LAT_TMR_THRS, (fire_freq_nak_tmr_thrs[mps][lw] << FO_PCI_LPU_TXLNK_FREQ_LAT_TMR_THRS_SHFT) & FO_PCI_LPU_TXLNK_FREQ_LAT_TMR_THRS_MASK); FIRE_PCI_SET(sc, FO_PCI_LPU_TXLNK_RPLY_TMR_THRS, (fire_rply_tmr_thrs[mps][lw] << FO_PCI_LPU_TXLNK_RPLY_TMR_THRS_SHFT) & FO_PCI_LPU_TXLNK_RPLY_TMR_THRS_MASK); FIRE_PCI_SET(sc, FO_PCI_LPU_TXLNK_RTR_FIFO_PTR, ((FO_PCI_LPU_TXLNK_RTR_FIFO_PTR_TL_DFLT << FO_PCI_LPU_TXLNK_RTR_FIFO_PTR_TL_SHFT) & FO_PCI_LPU_TXLNK_RTR_FIFO_PTR_TL_MASK) | ((FO_PCI_LPU_TXLNK_RTR_FIFO_PTR_HD_DFLT << FO_PCI_LPU_TXLNK_RTR_FIFO_PTR_HD_SHFT) & FO_PCI_LPU_TXLNK_RTR_FIFO_PTR_HD_MASK)); FIRE_PCI_SET(sc, FO_PCI_LPU_LTSSM_CFG2, (FO_PCI_LPU_LTSSM_CFG2_12_TO_DFLT << FO_PCI_LPU_LTSSM_CFG2_12_TO_SHFT) & FO_PCI_LPU_LTSSM_CFG2_12_TO_MASK); FIRE_PCI_SET(sc, FO_PCI_LPU_LTSSM_CFG3, (FO_PCI_LPU_LTSSM_CFG3_2_TO_DFLT << FO_PCI_LPU_LTSSM_CFG3_2_TO_SHFT) & FO_PCI_LPU_LTSSM_CFG3_2_TO_MASK); FIRE_PCI_SET(sc, FO_PCI_LPU_LTSSM_CFG4, ((FO_PCI_LPU_LTSSM_CFG4_DATA_RATE_DFLT << FO_PCI_LPU_LTSSM_CFG4_DATA_RATE_SHFT) & FO_PCI_LPU_LTSSM_CFG4_DATA_RATE_MASK) | ((FO_PCI_LPU_LTSSM_CFG4_N_FTS_DFLT << FO_PCI_LPU_LTSSM_CFG4_N_FTS_SHFT) & FO_PCI_LPU_LTSSM_CFG4_N_FTS_MASK)); FIRE_PCI_SET(sc, FO_PCI_LPU_LTSSM_CFG5, 0); } /* ILU initialization */ FIRE_PCI_SET(sc, FO_PCI_ILU_ERR_STAT_CLR, ~0ULL); /* not enabled by OpenSolaris */ FIRE_PCI_SET(sc, FO_PCI_ILU_INT_EN, ~0ULL); /* IMU initialization */ FIRE_PCI_SET(sc, FO_PCI_IMU_ERR_STAT_CLR, ~0ULL); FIRE_PCI_SET(sc, FO_PCI_IMU_INT_EN, FIRE_PCI_READ_8(sc, FO_PCI_IMU_INT_EN) & ~(FO_PCI_IMU_ERR_INT_FATAL_MES_NOT_EN_S | FO_PCI_IMU_ERR_INT_NFATAL_MES_NOT_EN_S | FO_PCI_IMU_ERR_INT_COR_MES_NOT_EN_S | FO_PCI_IMU_ERR_INT_FATAL_MES_NOT_EN_P | FO_PCI_IMU_ERR_INT_NFATAL_MES_NOT_EN_P | FO_PCI_IMU_ERR_INT_COR_MES_NOT_EN_P)); /* MMU initialization */ FIRE_PCI_SET(sc, FO_PCI_MMU_ERR_STAT_CLR, FO_PCI_MMU_ERR_INT_S_MASK | FO_PCI_MMU_ERR_INT_P_MASK); /* not enabled by OpenSolaris */ FIRE_PCI_SET(sc, FO_PCI_MMU_INT_EN, FO_PCI_MMU_ERR_INT_S_MASK | FO_PCI_MMU_ERR_INT_P_MASK); /* DMC initialization */ FIRE_PCI_SET(sc, FO_PCI_DMC_CORE_BLOCK_INT_EN, ~0ULL); FIRE_PCI_SET(sc, FO_PCI_DMC_DBG_SEL_PORTA, 0); FIRE_PCI_SET(sc, FO_PCI_DMC_DBG_SEL_PORTB, 0); /* PEC initialization */ FIRE_PCI_SET(sc, FO_PCI_PEC_CORE_BLOCK_INT_EN, ~0ULL); /* Establish handlers for interesting interrupts. */ if ((ino_bitmap & (1ULL << FO_DMC_PEC_INO)) != 0) fire_set_intr(sc, 1, FO_DMC_PEC_INO, fire_dmc_pec, sc); if ((ino_bitmap & (1ULL << FO_XCB_INO)) != 0) fire_set_intr(sc, 0, FO_XCB_INO, fire_xcb, sc); /* MSI/MSI-X support */ if (OF_getprop(node, "#msi", &sc->sc_msi_count, sizeof(sc->sc_msi_count)) == -1) panic("%s: could not determine MSI count", __func__); if (OF_getprop(node, "msi-ranges", &msi_ranges, sizeof(msi_ranges)) == -1) sc->sc_msi_first = 0; else sc->sc_msi_first = msi_ranges.first; if (OF_getprop(node, "msi-data-mask", &sc->sc_msi_data_mask, sizeof(sc->sc_msi_data_mask)) == -1) panic("%s: could not determine MSI data mask", __func__); if (OF_getprop(node, "msix-data-width", &sc->sc_msix_data_width, sizeof(sc->sc_msix_data_width)) > 0) sc->sc_flags |= FIRE_MSIX; if (OF_getprop(node, "msi-address-ranges", &msi_addr_ranges, sizeof(msi_addr_ranges)) == -1) panic("%s: could not determine MSI address ranges", __func__); sc->sc_msi_addr32 = OFW_PCI_MSI_ADDR_RANGE_32(&msi_addr_ranges); sc->sc_msi_addr64 = OFW_PCI_MSI_ADDR_RANGE_64(&msi_addr_ranges); if (OF_getprop(node, "#msi-eqs", &sc->sc_msiq_count, sizeof(sc->sc_msiq_count)) == -1) panic("%s: could not determine MSI event queue count", __func__); if (OF_getprop(node, "msi-eq-size", &sc->sc_msiq_size, sizeof(sc->sc_msiq_size)) == -1) panic("%s: could not determine MSI event queue size", __func__); if (OF_getprop(node, "msi-eq-to-devino", &msi_eq_to_devino, sizeof(msi_eq_to_devino)) == -1 && OF_getprop(node, "msi-eq-devino", &msi_eq_to_devino, sizeof(msi_eq_to_devino)) == -1) { sc->sc_msiq_first = 0; sc->sc_msiq_ino_first = FO_EQ_FIRST_INO; } else { sc->sc_msiq_first = msi_eq_to_devino.eq_first; sc->sc_msiq_ino_first = msi_eq_to_devino.devino_first; } if (sc->sc_msiq_ino_first < FO_EQ_FIRST_INO || sc->sc_msiq_ino_first + sc->sc_msiq_count - 1 > FO_EQ_LAST_INO) panic("%s: event queues exceed INO range", __func__); sc->sc_msi_bitmap = malloc(roundup2(sc->sc_msi_count, NBBY) / NBBY, M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->sc_msi_bitmap == NULL) panic("%s: could not malloc MSI bitmap", __func__); sc->sc_msi_msiq_table = malloc(sc->sc_msi_count * sizeof(*sc->sc_msi_msiq_table), M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->sc_msi_msiq_table == NULL) panic("%s: could not malloc MSI-MSI event queue table", __func__); sc->sc_msiq_bitmap = malloc(roundup2(sc->sc_msiq_count, NBBY) / NBBY, M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->sc_msiq_bitmap == NULL) panic("%s: could not malloc MSI event queue bitmap", __func__); j = FO_EQ_RECORD_SIZE * FO_EQ_NRECORDS * sc->sc_msiq_count; sc->sc_msiq = contigmalloc(j, M_DEVBUF, M_NOWAIT, 0, ~0UL, FO_EQ_ALIGNMENT, 0); if (sc->sc_msiq == NULL) panic("%s: could not contigmalloc MSI event queue", __func__); memset(sc->sc_msiq, 0, j); FIRE_PCI_SET(sc, FO_PCI_EQ_BASE_ADDR, FO_PCI_EQ_BASE_ADDR_BYPASS | (pmap_kextract((vm_offset_t)sc->sc_msiq) & FO_PCI_EQ_BASE_ADDR_MASK)); for (i = 0; i < sc->sc_msi_count; i++) { j = (i + sc->sc_msi_first) << 3; FIRE_PCI_WRITE_8(sc, FO_PCI_MSI_MAP_BASE + j, FIRE_PCI_READ_8(sc, FO_PCI_MSI_MAP_BASE + j) & ~FO_PCI_MSI_MAP_V); } for (i = 0; i < sc->sc_msiq_count; i++) { j = i + sc->sc_msiq_ino_first; if ((ino_bitmap & (1ULL << j)) == 0) { mtx_lock(&sc->sc_msi_mtx); setbit(sc->sc_msiq_bitmap, i); mtx_unlock(&sc->sc_msi_mtx); } fmqa = intr_vectors[INTMAP_VEC(sc->sc_ign, j)].iv_icarg; mtx_init(&fmqa->fmqa_mtx, "msiq_mtx", NULL, MTX_SPIN); fmqa->fmqa_base = (struct fo_msiq_record *)((caddr_t)sc->sc_msiq + (FO_EQ_RECORD_SIZE * FO_EQ_NRECORDS * i)); j = i + sc->sc_msiq_first; fmqa->fmqa_msiq = j; j <<= 3; fmqa->fmqa_head = FO_PCI_EQ_HD_BASE + j; fmqa->fmqa_tail = FO_PCI_EQ_TL_BASE + j; FIRE_PCI_WRITE_8(sc, FO_PCI_EQ_CTRL_CLR_BASE + j, FO_PCI_EQ_CTRL_CLR_COVERR | FO_PCI_EQ_CTRL_CLR_E2I | FO_PCI_EQ_CTRL_CLR_DIS); FIRE_PCI_WRITE_8(sc, fmqa->fmqa_tail, (0 << FO_PCI_EQ_TL_SHFT) & FO_PCI_EQ_TL_MASK); FIRE_PCI_WRITE_8(sc, fmqa->fmqa_head, (0 << FO_PCI_EQ_HD_SHFT) & FO_PCI_EQ_HD_MASK); } FIRE_PCI_SET(sc, FO_PCI_MSI_32_BIT_ADDR, sc->sc_msi_addr32 & FO_PCI_MSI_32_BIT_ADDR_MASK); FIRE_PCI_SET(sc, FO_PCI_MSI_64_BIT_ADDR, sc->sc_msi_addr64 & FO_PCI_MSI_64_BIT_ADDR_MASK); /* * Establish a handler for interesting PCIe messages and disable * unintersting ones. */ mtx_lock(&sc->sc_msi_mtx); for (i = 0; i < sc->sc_msiq_count; i++) { if (isclr(sc->sc_msiq_bitmap, i) != 0) { j = i; break; } } if (i == sc->sc_msiq_count) { mtx_unlock(&sc->sc_msi_mtx); panic("%s: no spare event queue for PCIe messages", __func__); } setbit(sc->sc_msiq_bitmap, j); mtx_unlock(&sc->sc_msi_mtx); i = INTMAP_VEC(sc->sc_ign, j + sc->sc_msiq_ino_first); if (bus_set_resource(dev, SYS_RES_IRQ, 2, i, 1) != 0) panic("%s: failed to add interrupt for PCIe messages", __func__); fire_set_intr(sc, 2, INTINO(i), fire_pcie, intr_vectors[i].iv_icarg); j += sc->sc_msiq_first; /* * "Please note that setting the EQNUM field to a value larger than * 35 will yield unpredictable results." */ if (j > 35) panic("%s: invalid queue for PCIe messages (%d)", __func__, j); FIRE_PCI_SET(sc, FO_PCI_ERR_COR, FO_PCI_ERR_PME_V | ((j << FO_PCI_ERR_PME_EQNUM_SHFT) & FO_PCI_ERR_PME_EQNUM_MASK)); FIRE_PCI_SET(sc, FO_PCI_ERR_NONFATAL, FO_PCI_ERR_PME_V | ((j << FO_PCI_ERR_PME_EQNUM_SHFT) & FO_PCI_ERR_PME_EQNUM_MASK)); FIRE_PCI_SET(sc, FO_PCI_ERR_FATAL, FO_PCI_ERR_PME_V | ((j << FO_PCI_ERR_PME_EQNUM_SHFT) & FO_PCI_ERR_PME_EQNUM_MASK)); FIRE_PCI_SET(sc, FO_PCI_PM_PME, 0); FIRE_PCI_SET(sc, FO_PCI_PME_TO_ACK, 0); FIRE_PCI_WRITE_8(sc, FO_PCI_EQ_CTRL_SET_BASE + (j << 3), FO_PCI_EQ_CTRL_SET_EN); #define TC_COUNTER_MAX_MASK 0xffffffff /* * Setup JBC/UBC performance counter 0 in bus cycle counting * mode as timecounter. */ if (device_get_unit(dev) == 0) { FIRE_CTRL_SET(sc, FO_XBC_PRF_CNT0, 0); FIRE_CTRL_SET(sc, FO_XBC_PRF_CNT1, 0); FIRE_CTRL_SET(sc, FO_XBC_PRF_CNT_SEL, (FO_XBC_PRF_CNT_NONE << FO_XBC_PRF_CNT_CNT1_SHFT) | (FO_XBC_PRF_CNT_XB_CLK << FO_XBC_PRF_CNT_CNT0_SHFT)); tc = malloc(sizeof(*tc), M_DEVBUF, M_NOWAIT | M_ZERO); if (tc == NULL) panic("%s: could not malloc timecounter", __func__); tc->tc_get_timecount = fire_get_timecount; tc->tc_counter_mask = TC_COUNTER_MAX_MASK; if (OF_getprop(OF_peer(0), "clock-frequency", &prop, sizeof(prop)) == -1) panic("%s: could not determine clock frequency", __func__); tc->tc_frequency = prop; tc->tc_name = strdup(device_get_nameunit(dev), M_DEVBUF); tc->tc_priv = sc; /* * Due to initial problems with the JBus-driven performance * counters not advancing which might be firmware dependent * ensure that it actually works. */ if (fire_get_timecount(tc) - fire_get_timecount(tc) != 0) tc->tc_quality = FIRE_PERF_CNT_QLTY; else tc->tc_quality = -FIRE_PERF_CNT_QLTY; tc_init(tc); } /* * Set up the IOMMU. Both Fire and Oberon have one per PBM, but * neither has a streaming buffer. */ memcpy(&sc->sc_dma_methods, &iommu_dma_methods, sizeof(sc->sc_dma_methods)); sc->sc_is.is_flags = IOMMU_FIRE | IOMMU_PRESERVE_PROM; if (sc->sc_mode == FIRE_MODE_OBERON) { sc->sc_is.is_flags |= IOMMU_FLUSH_CACHE; sc->sc_is.is_pmaxaddr = IOMMU_MAXADDR(OBERON_IOMMU_BITS); } else { sc->sc_dma_methods.dm_dmamap_sync = fire_dmamap_sync; sc->sc_is.is_pmaxaddr = IOMMU_MAXADDR(FIRE_IOMMU_BITS); } sc->sc_is.is_sb[0] = sc->sc_is.is_sb[1] = 0; /* Punch in our copies. */ sc->sc_is.is_bustag = rman_get_bustag(sc->sc_mem_res[FIRE_PCI]); sc->sc_is.is_bushandle = rman_get_bushandle(sc->sc_mem_res[FIRE_PCI]); sc->sc_is.is_iommu = FO_PCI_MMU; val = FIRE_PCI_READ_8(sc, FO_PCI_MMU + IMR_CTL); iommu_init(device_get_nameunit(sc->sc_dev), &sc->sc_is, 7, -1, 0); #ifdef FIRE_DEBUG device_printf(dev, "FO_PCI_MMU + IMR_CTL 0x%016llx -> 0x%016llx\n", (long long unsigned)val, (long long unsigned)sc->sc_is.is_cr); #endif /* Initialize memory and I/O rmans. */ sc->sc_pci_io_rman.rm_type = RMAN_ARRAY; sc->sc_pci_io_rman.rm_descr = "Fire PCI I/O Ports"; if (rman_init(&sc->sc_pci_io_rman) != 0 || rman_manage_region(&sc->sc_pci_io_rman, 0, FO_IO_SIZE) != 0) panic("%s: failed to set up I/O rman", __func__); sc->sc_pci_mem_rman.rm_type = RMAN_ARRAY; sc->sc_pci_mem_rman.rm_descr = "Fire PCI Memory"; if (rman_init(&sc->sc_pci_mem_rman) != 0 || rman_manage_region(&sc->sc_pci_mem_rman, 0, FO_MEM_SIZE) != 0) panic("%s: failed to set up memory rman", __func__); i = OF_getprop_alloc(node, "ranges", sizeof(*range), (void **)&range); /* * Make sure that the expected ranges are present. The * OFW_PCI_CS_MEM64 one is not currently used though. */ if (i != FIRE_NRANGE) panic("%s: unsupported number of ranges", __func__); /* * Find the addresses of the various bus spaces. * There should not be multiple ones of one kind. * The physical start addresses of the ranges are the configuration, * memory and I/O handles. */ for (i = 0; i < FIRE_NRANGE; i++) { j = OFW_PCI_RANGE_CS(&range[i]); if (sc->sc_pci_bh[j] != 0) panic("%s: duplicate range for space %d", __func__, j); sc->sc_pci_bh[j] = OFW_PCI_RANGE_PHYS(&range[i]); } free(range, M_OFWPROP); /* Allocate our tags. */ sc->sc_pci_iot = sparc64_alloc_bus_tag(NULL, rman_get_bustag( sc->sc_mem_res[FIRE_PCI]), PCI_IO_BUS_SPACE, NULL); if (sc->sc_pci_iot == NULL) panic("%s: could not allocate PCI I/O tag", __func__); sc->sc_pci_cfgt = sparc64_alloc_bus_tag(NULL, rman_get_bustag( sc->sc_mem_res[FIRE_PCI]), PCI_CONFIG_BUS_SPACE, NULL); if (sc->sc_pci_cfgt == NULL) panic("%s: could not allocate PCI configuration space tag", __func__); if (bus_dma_tag_create(bus_get_dma_tag(dev), 8, 0x100000000, sc->sc_is.is_pmaxaddr, ~0, NULL, NULL, sc->sc_is.is_pmaxaddr, 0xff, 0xffffffff, 0, NULL, NULL, &sc->sc_pci_dmat) != 0) panic("%s: could not create PCI DMA tag", __func__); /* Customize the tag. */ sc->sc_pci_dmat->dt_cookie = &sc->sc_is; sc->sc_pci_dmat->dt_mt = &sc->sc_dma_methods; /* * Get the bus range from the firmware. * NB: Neither Fire nor Oberon support PCI bus reenumeration. */ i = OF_getprop(node, "bus-range", (void *)prop_array, sizeof(prop_array)); if (i == -1) panic("%s: could not get bus-range", __func__); if (i != sizeof(prop_array)) panic("%s: broken bus-range (%d)", __func__, i); sc->sc_pci_secbus = prop_array[0]; sc->sc_pci_subbus = prop_array[1]; if (bootverbose != 0) device_printf(dev, "bus range %u to %u; PCI bus %d\n", sc->sc_pci_secbus, sc->sc_pci_subbus, sc->sc_pci_secbus); ofw_bus_setup_iinfo(node, &sc->sc_pci_iinfo, sizeof(ofw_pci_intr_t)); #define FIRE_SYSCTL_ADD_UINT(name, arg, desc) \ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), \ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, \ (name), CTLFLAG_RD, (arg), 0, (desc)) FIRE_SYSCTL_ADD_UINT("ilu_err", &sc->sc_stats_ilu_err, "ILU unknown errors"); FIRE_SYSCTL_ADD_UINT("jbc_ce_async", &sc->sc_stats_jbc_ce_async, "JBC correctable errors"); FIRE_SYSCTL_ADD_UINT("jbc_unsol_int", &sc->sc_stats_jbc_unsol_int, "JBC unsolicited interrupt ACK/NACK errors"); FIRE_SYSCTL_ADD_UINT("jbc_unsol_rd", &sc->sc_stats_jbc_unsol_rd, "JBC unsolicited read response errors"); FIRE_SYSCTL_ADD_UINT("mmu_err", &sc->sc_stats_mmu_err, "MMU errors"); FIRE_SYSCTL_ADD_UINT("tlu_ce", &sc->sc_stats_tlu_ce, "DLU/TLU correctable errors"); FIRE_SYSCTL_ADD_UINT("tlu_oe_non_fatal", &sc->sc_stats_tlu_oe_non_fatal, "DLU/TLU other event non-fatal errors summary"), FIRE_SYSCTL_ADD_UINT("tlu_oe_rx_err", &sc->sc_stats_tlu_oe_rx_err, "DLU/TLU receive other event errors"), FIRE_SYSCTL_ADD_UINT("tlu_oe_tx_err", &sc->sc_stats_tlu_oe_tx_err, "DLU/TLU transmit other event errors"), FIRE_SYSCTL_ADD_UINT("ubc_dmardue", &sc->sc_stats_ubc_dmardue, "UBC DMARDUE erros"); #undef FIRE_SYSCTL_ADD_UINT device_add_child(dev, "pci", -1); return (bus_generic_attach(dev)); } static void fire_set_intr(struct fire_softc *sc, u_int index, u_int ino, driver_filter_t handler, void *arg) { u_long vec; int rid; rid = index; sc->sc_irq_res[index] = bus_alloc_resource_any(sc->sc_dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->sc_irq_res[index] == NULL || INTINO(vec = rman_get_start(sc->sc_irq_res[index])) != ino || INTIGN(vec) != sc->sc_ign || intr_vectors[vec].iv_ic != &fire_ic || bus_setup_intr(sc->sc_dev, sc->sc_irq_res[index], INTR_TYPE_MISC | INTR_BRIDGE, handler, NULL, arg, &sc->sc_ihand[index]) != 0) panic("%s: failed to set up interrupt %d", __func__, index); } static int fire_intr_register(struct fire_softc *sc, u_int ino) { struct fire_icarg *fica; bus_addr_t intrclr, intrmap; int error; if (fire_get_intrmap(sc, ino, &intrmap, &intrclr) == 0) return (ENXIO); fica = malloc((ino >= FO_EQ_FIRST_INO && ino <= FO_EQ_LAST_INO) ? sizeof(struct fire_msiqarg) : sizeof(struct fire_icarg), M_DEVBUF, M_NOWAIT | M_ZERO); if (fica == NULL) return (ENOMEM); fica->fica_sc = sc; fica->fica_map = intrmap; fica->fica_clr = intrclr; error = (intr_controller_register(INTMAP_VEC(sc->sc_ign, ino), &fire_ic, fica)); if (error != 0) free(fica, M_DEVBUF); return (error); } static int fire_get_intrmap(struct fire_softc *sc, u_int ino, bus_addr_t *intrmapptr, bus_addr_t *intrclrptr) { if (ino > FO_MAX_INO) { device_printf(sc->sc_dev, "out of range INO %d requested\n", ino); return (0); } ino <<= 3; if (intrmapptr != NULL) *intrmapptr = FO_PCI_INT_MAP_BASE + ino; if (intrclrptr != NULL) *intrclrptr = FO_PCI_INT_CLR_BASE + ino; return (1); } /* * Interrupt handlers */ static int fire_dmc_pec(void *arg) { struct fire_softc *sc; device_t dev; uint64_t cestat, dmcstat, ilustat, imustat, mcstat, mmustat, mmutfar; uint64_t mmutfsr, oestat, pecstat, uestat, val; u_int fatal, oenfatal; fatal = 0; sc = arg; dev = sc->sc_dev; mtx_lock_spin(&sc->sc_pcib_mtx); mcstat = FIRE_PCI_READ_8(sc, FO_PCI_MULTI_CORE_ERR_STAT); if ((mcstat & FO_PCI_MULTI_CORE_ERR_STAT_DMC) != 0) { dmcstat = FIRE_PCI_READ_8(sc, FO_PCI_DMC_CORE_BLOCK_ERR_STAT); if ((dmcstat & FO_PCI_DMC_CORE_BLOCK_INT_EN_IMU) != 0) { imustat = FIRE_PCI_READ_8(sc, FO_PCI_IMU_INT_STAT); device_printf(dev, "IMU error %#llx\n", (unsigned long long)imustat); if ((imustat & FO_PCI_IMU_ERR_INT_EQ_NOT_EN_P) != 0) { fatal = 1; val = FIRE_PCI_READ_8(sc, FO_PCI_IMU_SCS_ERR_LOG); device_printf(dev, "SCS error log %#llx\n", (unsigned long long)val); } if ((imustat & FO_PCI_IMU_ERR_INT_EQ_OVER_P) != 0) { fatal = 1; val = FIRE_PCI_READ_8(sc, FO_PCI_IMU_EQS_ERR_LOG); device_printf(dev, "EQS error log %#llx\n", (unsigned long long)val); } if ((imustat & (FO_PCI_IMU_ERR_INT_MSI_MAL_ERR_P | FO_PCI_IMU_ERR_INT_MSI_PAR_ERR_P | FO_PCI_IMU_ERR_INT_PMEACK_MES_NOT_EN_P | FO_PCI_IMU_ERR_INT_PMPME_MES_NOT_EN_P | FO_PCI_IMU_ERR_INT_FATAL_MES_NOT_EN_P | FO_PCI_IMU_ERR_INT_NFATAL_MES_NOT_EN_P | FO_PCI_IMU_ERR_INT_COR_MES_NOT_EN_P | FO_PCI_IMU_ERR_INT_MSI_NOT_EN_P)) != 0) { fatal = 1; val = FIRE_PCI_READ_8(sc, FO_PCI_IMU_RDS_ERR_LOG); device_printf(dev, "RDS error log %#llx\n", (unsigned long long)val); } } if ((dmcstat & FO_PCI_DMC_CORE_BLOCK_INT_EN_MMU) != 0) { fatal = 1; mmustat = FIRE_PCI_READ_8(sc, FO_PCI_MMU_INT_STAT); mmutfar = FIRE_PCI_READ_8(sc, FO_PCI_MMU_TRANS_FAULT_ADDR); mmutfsr = FIRE_PCI_READ_8(sc, FO_PCI_MMU_TRANS_FAULT_STAT); if ((mmustat & (FO_PCI_MMU_ERR_INT_TBW_DPE_P | FO_PCI_MMU_ERR_INT_TBW_ERR_P | FO_PCI_MMU_ERR_INT_TBW_UDE_P | FO_PCI_MMU_ERR_INT_TBW_DME_P | FO_PCI_MMU_ERR_INT_TTC_CAE_P | FIRE_PCI_MMU_ERR_INT_TTC_DPE_P | OBERON_PCI_MMU_ERR_INT_TTC_DUE_P | FO_PCI_MMU_ERR_INT_TRN_ERR_P)) != 0) fatal = 1; else { sc->sc_stats_mmu_err++; FIRE_PCI_WRITE_8(sc, FO_PCI_MMU_ERR_STAT_CLR, mmustat); } device_printf(dev, "MMU error %#llx: TFAR %#llx TFSR %#llx\n", (unsigned long long)mmustat, (unsigned long long)mmutfar, (unsigned long long)mmutfsr); } } if ((mcstat & FO_PCI_MULTI_CORE_ERR_STAT_PEC) != 0) { pecstat = FIRE_PCI_READ_8(sc, FO_PCI_PEC_CORE_BLOCK_INT_STAT); if ((pecstat & FO_PCI_PEC_CORE_BLOCK_INT_STAT_UERR) != 0) { fatal = 1; uestat = FIRE_PCI_READ_8(sc, FO_PCI_TLU_UERR_INT_STAT); device_printf(dev, "DLU/TLU uncorrectable error %#llx\n", (unsigned long long)uestat); if ((uestat & (FO_PCI_TLU_UERR_INT_UR_P | OBERON_PCI_TLU_UERR_INT_POIS_P | FO_PCI_TLU_UERR_INT_MFP_P | FO_PCI_TLU_UERR_INT_ROF_P | FO_PCI_TLU_UERR_INT_UC_P | FIRE_PCI_TLU_UERR_INT_PP_P | OBERON_PCI_TLU_UERR_INT_POIS_P)) != 0) { val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_RX_UERR_HDR1_LOG); device_printf(dev, "receive header log %#llx\n", (unsigned long long)val); val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_RX_UERR_HDR2_LOG); device_printf(dev, "receive header log 2 %#llx\n", (unsigned long long)val); } if ((uestat & FO_PCI_TLU_UERR_INT_CTO_P) != 0) { val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_TX_UERR_HDR1_LOG); device_printf(dev, "transmit header log %#llx\n", (unsigned long long)val); val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_TX_UERR_HDR2_LOG); device_printf(dev, "transmit header log 2 %#llx\n", (unsigned long long)val); } if ((uestat & FO_PCI_TLU_UERR_INT_DLP_P) != 0) { val = FIRE_PCI_READ_8(sc, FO_PCI_LPU_LNK_LYR_INT_STAT); device_printf(dev, "link layer interrupt and status %#llx\n", (unsigned long long)val); } if ((uestat & FO_PCI_TLU_UERR_INT_TE_P) != 0) { val = FIRE_PCI_READ_8(sc, FO_PCI_LPU_PHY_LYR_INT_STAT); device_printf(dev, "phy layer interrupt and status %#llx\n", (unsigned long long)val); } } if ((pecstat & FO_PCI_PEC_CORE_BLOCK_INT_STAT_CERR) != 0) { sc->sc_stats_tlu_ce++; cestat = FIRE_PCI_READ_8(sc, FO_PCI_TLU_CERR_INT_STAT); device_printf(dev, "DLU/TLU correctable error %#llx\n", (unsigned long long)cestat); val = FIRE_PCI_READ_8(sc, FO_PCI_LPU_LNK_LYR_INT_STAT); device_printf(dev, "link layer interrupt and status %#llx\n", (unsigned long long)val); if ((cestat & FO_PCI_TLU_CERR_INT_RE_P) != 0) { FIRE_PCI_WRITE_8(sc, FO_PCI_LPU_LNK_LYR_INT_STAT, val); val = FIRE_PCI_READ_8(sc, FO_PCI_LPU_PHY_LYR_INT_STAT); device_printf(dev, "phy layer interrupt and status %#llx\n", (unsigned long long)val); } FIRE_PCI_WRITE_8(sc, FO_PCI_TLU_CERR_STAT_CLR, cestat); } if ((pecstat & FO_PCI_PEC_CORE_BLOCK_INT_STAT_OEVENT) != 0) { oenfatal = 0; oestat = FIRE_PCI_READ_8(sc, FO_PCI_TLU_OEVENT_INT_STAT); device_printf(dev, "DLU/TLU other event %#llx\n", (unsigned long long)oestat); if ((oestat & (FO_PCI_TLU_OEVENT_MFC_P | FO_PCI_TLU_OEVENT_MRC_P | FO_PCI_TLU_OEVENT_WUC_P | FO_PCI_TLU_OEVENT_RUC_P | FO_PCI_TLU_OEVENT_CRS_P)) != 0) { val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_RX_OEVENT_HDR1_LOG); device_printf(dev, "receive header log %#llx\n", (unsigned long long)val); val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_RX_OEVENT_HDR2_LOG); device_printf(dev, "receive header log 2 %#llx\n", (unsigned long long)val); if ((oestat & (FO_PCI_TLU_OEVENT_MFC_P | FO_PCI_TLU_OEVENT_MRC_P | FO_PCI_TLU_OEVENT_WUC_P | FO_PCI_TLU_OEVENT_RUC_P)) != 0) fatal = 1; else { sc->sc_stats_tlu_oe_rx_err++; oenfatal = 1; } } if ((oestat & (FO_PCI_TLU_OEVENT_MFC_P | FO_PCI_TLU_OEVENT_CTO_P | FO_PCI_TLU_OEVENT_WUC_P | FO_PCI_TLU_OEVENT_RUC_P)) != 0) { val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_TX_OEVENT_HDR1_LOG); device_printf(dev, "transmit header log %#llx\n", (unsigned long long)val); val = FIRE_PCI_READ_8(sc, FO_PCI_TLU_TX_OEVENT_HDR2_LOG); device_printf(dev, "transmit header log 2 %#llx\n", (unsigned long long)val); if ((oestat & (FO_PCI_TLU_OEVENT_MFC_P | FO_PCI_TLU_OEVENT_CTO_P | FO_PCI_TLU_OEVENT_WUC_P | FO_PCI_TLU_OEVENT_RUC_P)) != 0) fatal = 1; else { sc->sc_stats_tlu_oe_tx_err++; oenfatal = 1; } } if ((oestat & (FO_PCI_TLU_OEVENT_ERO_P | FO_PCI_TLU_OEVENT_EMP_P | FO_PCI_TLU_OEVENT_EPE_P | FIRE_PCI_TLU_OEVENT_ERP_P | OBERON_PCI_TLU_OEVENT_ERBU_P | FIRE_PCI_TLU_OEVENT_EIP_P | OBERON_PCI_TLU_OEVENT_EIUE_P)) != 0) { fatal = 1; val = FIRE_PCI_READ_8(sc, FO_PCI_LPU_LNK_LYR_INT_STAT); device_printf(dev, "link layer interrupt and status %#llx\n", (unsigned long long)val); } if ((oestat & (FO_PCI_TLU_OEVENT_IIP_P | FO_PCI_TLU_OEVENT_EDP_P | FIRE_PCI_TLU_OEVENT_EHP_P | OBERON_PCI_TLU_OEVENT_TLUEITMO_S | FO_PCI_TLU_OEVENT_ERU_P)) != 0) fatal = 1; if ((oestat & (FO_PCI_TLU_OEVENT_NFP_P | FO_PCI_TLU_OEVENT_LWC_P | FO_PCI_TLU_OEVENT_LIN_P | FO_PCI_TLU_OEVENT_LRS_P | FO_PCI_TLU_OEVENT_LDN_P | FO_PCI_TLU_OEVENT_LUP_P)) != 0) oenfatal = 1; if (oenfatal != 0) { sc->sc_stats_tlu_oe_non_fatal++; FIRE_PCI_WRITE_8(sc, FO_PCI_TLU_OEVENT_STAT_CLR, oestat); if ((oestat & FO_PCI_TLU_OEVENT_LIN_P) != 0) FIRE_PCI_WRITE_8(sc, FO_PCI_LPU_LNK_LYR_INT_STAT, FIRE_PCI_READ_8(sc, FO_PCI_LPU_LNK_LYR_INT_STAT)); } } if ((pecstat & FO_PCI_PEC_CORE_BLOCK_INT_STAT_ILU) != 0) { ilustat = FIRE_PCI_READ_8(sc, FO_PCI_ILU_INT_STAT); device_printf(dev, "ILU error %#llx\n", (unsigned long long)ilustat); if ((ilustat & (FIRE_PCI_ILU_ERR_INT_IHB_PE_P | FIRE_PCI_ILU_ERR_INT_IHB_PE_P)) != 0) fatal = 1; else { sc->sc_stats_ilu_err++; FIRE_PCI_WRITE_8(sc, FO_PCI_ILU_INT_STAT, ilustat); } } } mtx_unlock_spin(&sc->sc_pcib_mtx); if (fatal != 0) panic("%s: fatal DMC/PEC error", device_get_nameunit(sc->sc_dev)); return (FILTER_HANDLED); } static int fire_xcb(void *arg) { struct fire_softc *sc; device_t dev; uint64_t errstat, intstat, val; u_int fatal; fatal = 0; sc = arg; dev = sc->sc_dev; mtx_lock_spin(&sc->sc_pcib_mtx); if (sc->sc_mode == FIRE_MODE_OBERON) { intstat = FIRE_CTRL_READ_8(sc, FO_XBC_INT_STAT); device_printf(dev, "UBC error: interrupt status %#llx\n", (unsigned long long)intstat); if ((intstat & ~(OBERON_UBC_ERR_INT_DMARDUEB_P | OBERON_UBC_ERR_INT_DMARDUEA_P)) != 0) fatal = 1; else sc->sc_stats_ubc_dmardue++; if (fatal != 0) { mtx_unlock_spin(&sc->sc_pcib_mtx); panic("%s: fatal UBC core block error", device_get_nameunit(sc->sc_dev)); } else { FIRE_CTRL_SET(sc, FO_XBC_ERR_STAT_CLR, ~0ULL); mtx_unlock_spin(&sc->sc_pcib_mtx); } } else { errstat = FIRE_CTRL_READ_8(sc, FIRE_JBC_CORE_BLOCK_ERR_STAT); if ((errstat & (FIRE_JBC_CORE_BLOCK_ERR_STAT_MERGE | FIRE_JBC_CORE_BLOCK_ERR_STAT_JBCINT | FIRE_JBC_CORE_BLOCK_ERR_STAT_DMCINT)) != 0) { intstat = FIRE_CTRL_READ_8(sc, FO_XBC_INT_STAT); device_printf(dev, "JBC interrupt status %#llx\n", (unsigned long long)intstat); if ((intstat & FIRE_JBC_ERR_INT_EBUS_TO_P) != 0) { val = FIRE_CTRL_READ_8(sc, FIRE_JBC_CSR_ERR_LOG); device_printf(dev, "CSR error log %#llx\n", (unsigned long long)val); } if ((intstat & (FIRE_JBC_ERR_INT_UNSOL_RD_P | FIRE_JBC_ERR_INT_UNSOL_INT_P)) != 0) { if ((intstat & FIRE_JBC_ERR_INT_UNSOL_RD_P) != 0) sc->sc_stats_jbc_unsol_rd++; if ((intstat & FIRE_JBC_ERR_INT_UNSOL_INT_P) != 0) sc->sc_stats_jbc_unsol_int++; val = FIRE_CTRL_READ_8(sc, FIRE_DMCINT_IDC_ERR_LOG); device_printf(dev, "DMCINT IDC error log %#llx\n", (unsigned long long)val); } if ((intstat & (FIRE_JBC_ERR_INT_MB_PER_P | FIRE_JBC_ERR_INT_MB_PEW_P)) != 0) { fatal = 1; val = FIRE_CTRL_READ_8(sc, FIRE_MERGE_TRANS_ERR_LOG); device_printf(dev, "merge transaction error log %#llx\n", (unsigned long long)val); } if ((intstat & FIRE_JBC_ERR_INT_IJP_P) != 0) { fatal = 1; val = FIRE_CTRL_READ_8(sc, FIRE_JBCINT_OTRANS_ERR_LOG); device_printf(dev, "JBCINT out transaction error log " "%#llx\n", (unsigned long long)val); val = FIRE_CTRL_READ_8(sc, FIRE_JBCINT_OTRANS_ERR_LOG2); device_printf(dev, "JBCINT out transaction error log 2 " "%#llx\n", (unsigned long long)val); } if ((intstat & (FIRE_JBC_ERR_INT_UE_ASYN_P | FIRE_JBC_ERR_INT_CE_ASYN_P | FIRE_JBC_ERR_INT_JTE_P | FIRE_JBC_ERR_INT_JBE_P | FIRE_JBC_ERR_INT_JUE_P | FIRE_JBC_ERR_INT_ICISE_P | FIRE_JBC_ERR_INT_WR_DPE_P | FIRE_JBC_ERR_INT_RD_DPE_P | FIRE_JBC_ERR_INT_ILL_BMW_P | FIRE_JBC_ERR_INT_ILL_BMR_P | FIRE_JBC_ERR_INT_BJC_P)) != 0) { if ((intstat & (FIRE_JBC_ERR_INT_UE_ASYN_P | FIRE_JBC_ERR_INT_JTE_P | FIRE_JBC_ERR_INT_JBE_P | FIRE_JBC_ERR_INT_JUE_P | FIRE_JBC_ERR_INT_ICISE_P | FIRE_JBC_ERR_INT_WR_DPE_P | FIRE_JBC_ERR_INT_RD_DPE_P | FIRE_JBC_ERR_INT_ILL_BMW_P | FIRE_JBC_ERR_INT_ILL_BMR_P | FIRE_JBC_ERR_INT_BJC_P)) != 0) fatal = 1; else sc->sc_stats_jbc_ce_async++; val = FIRE_CTRL_READ_8(sc, FIRE_JBCINT_ITRANS_ERR_LOG); device_printf(dev, "JBCINT in transaction error log %#llx\n", (unsigned long long)val); val = FIRE_CTRL_READ_8(sc, FIRE_JBCINT_ITRANS_ERR_LOG2); device_printf(dev, "JBCINT in transaction error log 2 " "%#llx\n", (unsigned long long)val); } if ((intstat & (FIRE_JBC_ERR_INT_PIO_UNMAP_RD_P | FIRE_JBC_ERR_INT_ILL_ACC_RD_P | FIRE_JBC_ERR_INT_PIO_UNMAP_P | FIRE_JBC_ERR_INT_PIO_DPE_P | FIRE_JBC_ERR_INT_PIO_CPE_P | FIRE_JBC_ERR_INT_ILL_ACC_P)) != 0) { fatal = 1; val = FIRE_CTRL_READ_8(sc, FIRE_JBC_CSR_ERR_LOG); device_printf(dev, "DMCINT ODCD error log %#llx\n", (unsigned long long)val); } if ((intstat & (FIRE_JBC_ERR_INT_MB_PEA_P | FIRE_JBC_ERR_INT_CPE_P | FIRE_JBC_ERR_INT_APE_P | FIRE_JBC_ERR_INT_PIO_CPE_P | FIRE_JBC_ERR_INT_JTCEEW_P | FIRE_JBC_ERR_INT_JTCEEI_P | FIRE_JBC_ERR_INT_JTCEER_P)) != 0) { fatal = 1; val = FIRE_CTRL_READ_8(sc, FIRE_FATAL_ERR_LOG); device_printf(dev, "fatal error log %#llx\n", (unsigned long long)val); val = FIRE_CTRL_READ_8(sc, FIRE_FATAL_ERR_LOG2); device_printf(dev, "fatal error log 2 " "%#llx\n", (unsigned long long)val); } if (fatal != 0) { mtx_unlock_spin(&sc->sc_pcib_mtx); panic("%s: fatal JBC core block error", device_get_nameunit(sc->sc_dev)); } else { FIRE_CTRL_SET(sc, FO_XBC_ERR_STAT_CLR, ~0ULL); mtx_unlock_spin(&sc->sc_pcib_mtx); } } else { mtx_unlock_spin(&sc->sc_pcib_mtx); panic("%s: unknown JCB core block error status %#llx", device_get_nameunit(sc->sc_dev), (unsigned long long)errstat); } } return (FILTER_HANDLED); } static int fire_pcie(void *arg) { struct fire_msiqarg *fmqa; struct fire_softc *sc; struct fo_msiq_record *qrec; device_t dev; uint64_t word0; u_int head, msg, msiq; fmqa = arg; sc = fmqa->fmqa_fica.fica_sc; dev = sc->sc_dev; msiq = fmqa->fmqa_msiq; mtx_lock_spin(&fmqa->fmqa_mtx); head = (FIRE_PCI_READ_8(sc, fmqa->fmqa_head) & FO_PCI_EQ_HD_MASK) >> FO_PCI_EQ_HD_SHFT; qrec = &fmqa->fmqa_base[head]; word0 = qrec->fomqr_word0; for (;;) { KASSERT((word0 & FO_MQR_WORD0_FMT_TYPE_MSG) != 0, ("%s: received non-PCIe message in event queue %d " "(word0 %#llx)", device_get_nameunit(dev), msiq, (unsigned long long)word0)); msg = (word0 & FO_MQR_WORD0_DATA0_MASK) >> FO_MQR_WORD0_DATA0_SHFT; #define PCIE_MSG_CODE_ERR_COR 0x30 #define PCIE_MSG_CODE_ERR_NONFATAL 0x31 #define PCIE_MSG_CODE_ERR_FATAL 0x33 if (msg == PCIE_MSG_CODE_ERR_COR) device_printf(dev, "correctable PCIe error\n"); else if (msg == PCIE_MSG_CODE_ERR_NONFATAL || msg == PCIE_MSG_CODE_ERR_FATAL) panic("%s: %sfatal PCIe error", device_get_nameunit(dev), msg == PCIE_MSG_CODE_ERR_NONFATAL ? "non-" : ""); else panic("%s: received unknown PCIe message %#x", device_get_nameunit(dev), msg); qrec->fomqr_word0 &= ~FO_MQR_WORD0_FMT_TYPE_MASK; head = (head + 1) % sc->sc_msiq_size; qrec = &fmqa->fmqa_base[head]; word0 = qrec->fomqr_word0; if (__predict_true((word0 & FO_MQR_WORD0_FMT_TYPE_MASK) == 0)) break; } FIRE_PCI_WRITE_8(sc, fmqa->fmqa_head, (head & FO_PCI_EQ_HD_MASK) << FO_PCI_EQ_HD_SHFT); if ((FIRE_PCI_READ_8(sc, fmqa->fmqa_tail) & FO_PCI_EQ_TL_OVERR) != 0) { device_printf(dev, "event queue %d overflow\n", msiq); msiq <<= 3; FIRE_PCI_WRITE_8(sc, FO_PCI_EQ_CTRL_CLR_BASE + msiq, FIRE_PCI_READ_8(sc, FO_PCI_EQ_CTRL_CLR_BASE + msiq) | FO_PCI_EQ_CTRL_CLR_COVERR); } mtx_unlock_spin(&fmqa->fmqa_mtx); return (FILTER_HANDLED); } static int fire_maxslots(device_t dev) { return (1); } static uint32_t fire_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, int width) { struct fire_softc *sc; bus_space_handle_t bh; u_long offset = 0; uint32_t r, wrd; int i; uint16_t shrt; uint8_t byte; sc = device_get_softc(dev); if (bus < sc->sc_pci_secbus || bus > sc->sc_pci_subbus || slot > PCI_SLOTMAX || func > PCI_FUNCMAX || reg > PCIE_REGMAX) return (-1); offset = FO_CONF_OFF(bus, slot, func, reg); bh = sc->sc_pci_bh[OFW_PCI_CS_CONFIG]; switch (width) { case 1: i = bus_space_peek_1(sc->sc_pci_cfgt, bh, offset, &byte); r = byte; break; case 2: i = bus_space_peek_2(sc->sc_pci_cfgt, bh, offset, &shrt); r = shrt; break; case 4: i = bus_space_peek_4(sc->sc_pci_cfgt, bh, offset, &wrd); r = wrd; break; default: panic("%s: bad width", __func__); /* NOTREACHED */ } if (i) { #ifdef FIRE_DEBUG printf("%s: read data error reading: %d.%d.%d: 0x%x\n", __func__, bus, slot, func, reg); #endif r = -1; } return (r); } static void fire_write_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, uint32_t val, int width) { struct fire_softc *sc; bus_space_handle_t bh; u_long offset = 0; sc = device_get_softc(dev); if (bus < sc->sc_pci_secbus || bus > sc->sc_pci_subbus || slot > PCI_SLOTMAX || func > PCI_FUNCMAX || reg > PCIE_REGMAX) return; offset = FO_CONF_OFF(bus, slot, func, reg); bh = sc->sc_pci_bh[OFW_PCI_CS_CONFIG]; switch (width) { case 1: bus_space_write_1(sc->sc_pci_cfgt, bh, offset, val); break; case 2: bus_space_write_2(sc->sc_pci_cfgt, bh, offset, val); break; case 4: bus_space_write_4(sc->sc_pci_cfgt, bh, offset, val); break; default: panic("%s: bad width", __func__); /* NOTREACHED */ } } static int fire_route_interrupt(device_t bridge, device_t dev, int pin) { struct fire_softc *sc; struct ofw_pci_register reg; ofw_pci_intr_t pintr, mintr; uint8_t maskbuf[sizeof(reg) + sizeof(pintr)]; sc = device_get_softc(bridge); pintr = pin; if (ofw_bus_lookup_imap(ofw_bus_get_node(dev), &sc->sc_pci_iinfo, ®, sizeof(reg), &pintr, sizeof(pintr), &mintr, sizeof(mintr), NULL, maskbuf) != 0) return (mintr); device_printf(bridge, "could not route pin %d for device %d.%d\n", pin, pci_get_slot(dev), pci_get_function(dev)); return (PCI_INVALID_IRQ); } static int fire_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct fire_softc *sc; sc = device_get_softc(dev); switch (which) { case PCIB_IVAR_DOMAIN: *result = device_get_unit(dev); return (0); case PCIB_IVAR_BUS: *result = sc->sc_pci_secbus; return (0); } return (ENOENT); } #define VIS_BLOCKSIZE 64 static void fire_dmamap_sync(bus_dma_tag_t dt __unused, bus_dmamap_t map, bus_dmasync_op_t op) { static u_char buf[VIS_BLOCKSIZE] __aligned(VIS_BLOCKSIZE); register_t reg, s; if ((map->dm_flags & DMF_LOADED) == 0) return; if ((op & BUS_DMASYNC_POSTREAD) != 0) { s = intr_disable(); reg = rd(fprs); wr(fprs, reg | FPRS_FEF, 0); __asm __volatile("stda %%f0, [%0] %1" : : "r" (buf), "n" (ASI_BLK_COMMIT_S)); membar(Sync); wr(fprs, reg, 0); intr_restore(s); } else if ((op & BUS_DMASYNC_PREWRITE) != 0) membar(Sync); } static void fire_intr_enable(void *arg) { struct intr_vector *iv; struct fire_icarg *fica; struct fire_softc *sc; struct pcpu *pc; uint64_t mr; u_int ctrl, i; iv = arg; fica = iv->iv_icarg; sc = fica->fica_sc; mr = FO_PCI_IMAP_V; if (sc->sc_mode == FIRE_MODE_OBERON) mr |= (iv->iv_mid << OBERON_PCI_IMAP_T_DESTID_SHFT) & OBERON_PCI_IMAP_T_DESTID_MASK; else mr |= (iv->iv_mid << FIRE_PCI_IMAP_T_JPID_SHFT) & FIRE_PCI_IMAP_T_JPID_MASK; /* * Given that all mondos for the same target are required to use the * same interrupt controller we just use the CPU ID for indexing the * latter. */ ctrl = 0; for (i = 0; i < mp_ncpus; ++i) { pc = pcpu_find(i); if (pc == NULL || iv->iv_mid != pc->pc_mid) continue; ctrl = pc->pc_cpuid % 4; break; } mr |= (1ULL << ctrl) << FO_PCI_IMAP_INT_CTRL_NUM_SHFT & FO_PCI_IMAP_INT_CTRL_NUM_MASK; FIRE_PCI_WRITE_8(sc, fica->fica_map, mr); } static void fire_intr_disable(void *arg) { struct intr_vector *iv; struct fire_icarg *fica; struct fire_softc *sc; iv = arg; fica = iv->iv_icarg; sc = fica->fica_sc; FIRE_PCI_WRITE_8(sc, fica->fica_map, FIRE_PCI_READ_8(sc, fica->fica_map) & ~FO_PCI_IMAP_V); } static void fire_intr_assign(void *arg) { struct intr_vector *iv; struct fire_icarg *fica; struct fire_softc *sc; uint64_t mr; iv = arg; fica = iv->iv_icarg; sc = fica->fica_sc; mr = FIRE_PCI_READ_8(sc, fica->fica_map); if ((mr & FO_PCI_IMAP_V) != 0) { FIRE_PCI_WRITE_8(sc, fica->fica_map, mr & ~FO_PCI_IMAP_V); FIRE_PCI_BARRIER(sc, fica->fica_map, 8, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); } while (FIRE_PCI_READ_8(sc, fica->fica_clr) != INTCLR_IDLE) ; if ((mr & FO_PCI_IMAP_V) != 0) fire_intr_enable(arg); } static void fire_intr_clear(void *arg) { struct intr_vector *iv; struct fire_icarg *fica; iv = arg; fica = iv->iv_icarg; FIRE_PCI_WRITE_8(fica->fica_sc, fica->fica_clr, INTCLR_IDLE); } /* * Given that the event queue implementation matches our current MD and MI * interrupt frameworks like square pegs fit into round holes we are generous * and use one event queue per MSI for now, which limits us to 35 MSIs/MSI-Xs * per Host-PCIe-bridge (we use one event queue for the PCIe error messages). * This seems tolerable as long as most devices just use one MSI/MSI-X anyway. * Adding knowledge about MSIs/MSI-Xs to the MD interrupt code should allow us * to decouple the 1:1 mapping at the cost of no longer being able to bind * MSIs/MSI-Xs to specific CPUs as we currently have no reliable way to * quiesce a device while we move its MSIs/MSI-Xs to another event queue. */ static int fire_alloc_msi(device_t dev, device_t child, int count, int maxcount __unused, int *irqs) { struct fire_softc *sc; u_int i, j, msiqrun; if (powerof2(count) == 0 || count > 32) return (EINVAL); sc = device_get_softc(dev); mtx_lock(&sc->sc_msi_mtx); msiqrun = 0; for (i = 0; i < sc->sc_msiq_count; i++) { for (j = i; j < i + count; j++) { if (isclr(sc->sc_msiq_bitmap, j) == 0) break; } if (j == i + count) { msiqrun = i; break; } } if (i == sc->sc_msiq_count) { mtx_unlock(&sc->sc_msi_mtx); return (ENXIO); } for (i = 0; i + count < sc->sc_msi_count; i += count) { for (j = i; j < i + count; j++) if (isclr(sc->sc_msi_bitmap, j) == 0) break; if (j == i + count) { for (j = 0; j < count; j++) { setbit(sc->sc_msiq_bitmap, msiqrun + j); setbit(sc->sc_msi_bitmap, i + j); sc->sc_msi_msiq_table[i + j] = msiqrun + j; irqs[j] = sc->sc_msi_first + i + j; } mtx_unlock(&sc->sc_msi_mtx); return (0); } } mtx_unlock(&sc->sc_msi_mtx); return (ENXIO); } static int fire_release_msi(device_t dev, device_t child, int count, int *irqs) { struct fire_softc *sc; u_int i; sc = device_get_softc(dev); mtx_lock(&sc->sc_msi_mtx); for (i = 0; i < count; i++) { clrbit(sc->sc_msiq_bitmap, sc->sc_msi_msiq_table[irqs[i] - sc->sc_msi_first]); clrbit(sc->sc_msi_bitmap, irqs[i] - sc->sc_msi_first); } mtx_unlock(&sc->sc_msi_mtx); return (0); } static int fire_alloc_msix(device_t dev, device_t child, int *irq) { struct fire_softc *sc; u_int i, msiq; sc = device_get_softc(dev); if ((sc->sc_flags & FIRE_MSIX) == 0) return (ENXIO); mtx_lock(&sc->sc_msi_mtx); msiq = 0; for (i = 0; i < sc->sc_msiq_count; i++) { if (isclr(sc->sc_msiq_bitmap, i) != 0) { msiq = i; break; } } if (i == sc->sc_msiq_count) { mtx_unlock(&sc->sc_msi_mtx); return (ENXIO); } for (i = sc->sc_msi_count - 1; i >= 0; i--) { if (isclr(sc->sc_msi_bitmap, i) != 0) { setbit(sc->sc_msiq_bitmap, msiq); setbit(sc->sc_msi_bitmap, i); sc->sc_msi_msiq_table[i] = msiq; *irq = sc->sc_msi_first + i; mtx_unlock(&sc->sc_msi_mtx); return (0); } } mtx_unlock(&sc->sc_msi_mtx); return (ENXIO); } static int fire_release_msix(device_t dev, device_t child, int irq) { struct fire_softc *sc; sc = device_get_softc(dev); if ((sc->sc_flags & FIRE_MSIX) == 0) return (ENXIO); mtx_lock(&sc->sc_msi_mtx); clrbit(sc->sc_msiq_bitmap, sc->sc_msi_msiq_table[irq - sc->sc_msi_first]); clrbit(sc->sc_msi_bitmap, irq - sc->sc_msi_first); mtx_unlock(&sc->sc_msi_mtx); return (0); } static int fire_map_msi(device_t dev, device_t child, int irq, uint64_t *addr, uint32_t *data) { struct fire_softc *sc; struct pci_devinfo *dinfo; sc = device_get_softc(dev); dinfo = device_get_ivars(child); if (dinfo->cfg.msi.msi_alloc > 0) { if ((irq & ~sc->sc_msi_data_mask) != 0) { device_printf(dev, "invalid MSI 0x%x\n", irq); return (EINVAL); } } else { if ((sc->sc_flags & FIRE_MSIX) == 0) return (ENXIO); if (fls(irq) > sc->sc_msix_data_width) { device_printf(dev, "invalid MSI-X 0x%x\n", irq); return (EINVAL); } } if (dinfo->cfg.msi.msi_alloc > 0 && (dinfo->cfg.msi.msi_ctrl & PCIM_MSICTRL_64BIT) == 0) *addr = sc->sc_msi_addr32; else *addr = sc->sc_msi_addr64; *data = irq; return (0); } static void fire_msiq_handler(void *cookie) { struct intr_vector *iv; struct fire_msiqarg *fmqa; iv = cookie; fmqa = iv->iv_icarg; /* * Note that since fire_intr_clear() will clear the event queue * interrupt after the handler associated with the MSI [sic] has * been executed we have to protect the access to the event queue as * otherwise nested event queue interrupts cause corruption of the * event queue on MP machines. Obviously especially when abandoning * the 1:1 mapping it would be better to not clear the event queue * interrupt after each handler invocation but only once when the * outstanding MSIs have been processed but unfortunately that * doesn't work well and leads to interrupt storms with controllers/ * drivers which don't mask interrupts while the handler is executed. * Maybe delaying clearing the MSI until after the handler has been * executed could be used to work around this but that's not the * intended usage and might in turn cause lost MSIs. */ mtx_lock_spin(&fmqa->fmqa_mtx); fire_msiq_common(iv, fmqa); mtx_unlock_spin(&fmqa->fmqa_mtx); } static void fire_msiq_filter(void *cookie) { struct intr_vector *iv; struct fire_msiqarg *fmqa; iv = cookie; fmqa = iv->iv_icarg; /* * For filters we don't use fire_intr_clear() since it would clear * the event queue interrupt while we're still processing the event * queue as filters and associated post-filter handler are executed * directly, which in turn would lead to lost MSIs. So we clear the * event queue interrupt only once after processing the event queue. * Given that this still guarantees the filters to not be executed * concurrently and no other CPU can clear the event queue interrupt * while the event queue is still processed, we don't even need to * interlock the access to the event queue in this case. */ critical_enter(); fire_msiq_common(iv, fmqa); FIRE_PCI_WRITE_8(fmqa->fmqa_fica.fica_sc, fmqa->fmqa_fica.fica_clr, INTCLR_IDLE); critical_exit(); } static inline void fire_msiq_common(struct intr_vector *iv, struct fire_msiqarg *fmqa) { struct fire_softc *sc; struct fo_msiq_record *qrec; device_t dev; uint64_t word0; u_int head, msi, msiq; sc = fmqa->fmqa_fica.fica_sc; dev = sc->sc_dev; msiq = fmqa->fmqa_msiq; head = (FIRE_PCI_READ_8(sc, fmqa->fmqa_head) & FO_PCI_EQ_HD_MASK) >> FO_PCI_EQ_HD_SHFT; qrec = &fmqa->fmqa_base[head]; word0 = qrec->fomqr_word0; for (;;) { if (__predict_false((word0 & FO_MQR_WORD0_FMT_TYPE_MASK) == 0)) break; KASSERT((word0 & FO_MQR_WORD0_FMT_TYPE_MSI64) != 0 || (word0 & FO_MQR_WORD0_FMT_TYPE_MSI32) != 0, ("%s: received non-MSI/MSI-X message in event queue %d " "(word0 %#llx)", device_get_nameunit(dev), msiq, (unsigned long long)word0)); msi = (word0 & FO_MQR_WORD0_DATA0_MASK) >> FO_MQR_WORD0_DATA0_SHFT; /* * Sanity check the MSI/MSI-X as long as we use a 1:1 mapping. */ KASSERT(msi == fmqa->fmqa_msi, ("%s: received non-matching MSI/MSI-X in event queue %d " "(%d versus %d)", device_get_nameunit(dev), msiq, msi, fmqa->fmqa_msi)); FIRE_PCI_WRITE_8(sc, FO_PCI_MSI_CLR_BASE + (msi << 3), FO_PCI_MSI_CLR_EQWR_N); if (__predict_false(intr_event_handle(iv->iv_event, NULL) != 0)) printf("stray MSI/MSI-X in event queue %d\n", msiq); qrec->fomqr_word0 &= ~FO_MQR_WORD0_FMT_TYPE_MASK; head = (head + 1) % sc->sc_msiq_size; qrec = &fmqa->fmqa_base[head]; word0 = qrec->fomqr_word0; } FIRE_PCI_WRITE_8(sc, fmqa->fmqa_head, (head & FO_PCI_EQ_HD_MASK) << FO_PCI_EQ_HD_SHFT); if (__predict_false((FIRE_PCI_READ_8(sc, fmqa->fmqa_tail) & FO_PCI_EQ_TL_OVERR) != 0)) { device_printf(dev, "event queue %d overflow\n", msiq); msiq <<= 3; FIRE_PCI_WRITE_8(sc, FO_PCI_EQ_CTRL_CLR_BASE + msiq, FIRE_PCI_READ_8(sc, FO_PCI_EQ_CTRL_CLR_BASE + msiq) | FO_PCI_EQ_CTRL_CLR_COVERR); } } static int fire_setup_intr(device_t dev, device_t child, struct resource *ires, int flags, driver_filter_t *filt, driver_intr_t *intr, void *arg, void **cookiep) { struct fire_softc *sc; struct fire_msiqarg *fmqa; u_long vec; int error; u_int msi, msiq; sc = device_get_softc(dev); /* * XXX this assumes that a device only has one INTx, while in fact * Cassini+ and Saturn can use all four the firmware has assigned * to them, but so does pci(4). */ if (rman_get_rid(ires) != 0) { msi = rman_get_start(ires); msiq = sc->sc_msi_msiq_table[msi - sc->sc_msi_first]; vec = INTMAP_VEC(sc->sc_ign, sc->sc_msiq_ino_first + msiq); msiq += sc->sc_msiq_first; if (intr_vectors[vec].iv_ic != &fire_ic) { device_printf(dev, "invalid interrupt controller for vector 0x%lx\n", vec); return (EINVAL); } /* * The MD interrupt code needs the vector rather than the MSI. */ rman_set_start(ires, vec); rman_set_end(ires, vec); error = bus_generic_setup_intr(dev, child, ires, flags, filt, intr, arg, cookiep); rman_set_start(ires, msi); rman_set_end(ires, msi); if (error != 0) return (error); fmqa = intr_vectors[vec].iv_icarg; /* * XXX inject our event queue handler. */ if (filt != NULL) { intr_vectors[vec].iv_func = fire_msiq_filter; intr_vectors[vec].iv_ic = &fire_msiqc_filter; /* * Ensure the event queue interrupt is cleared, it * might have triggered before. Given we supply NULL * as ic_clear, inthand_add() won't do this for us. */ FIRE_PCI_WRITE_8(sc, fmqa->fmqa_fica.fica_clr, INTCLR_IDLE); } else intr_vectors[vec].iv_func = fire_msiq_handler; /* Record the MSI/MSI-X as long as we we use a 1:1 mapping. */ fmqa->fmqa_msi = msi; FIRE_PCI_WRITE_8(sc, FO_PCI_EQ_CTRL_SET_BASE + (msiq << 3), FO_PCI_EQ_CTRL_SET_EN); msi <<= 3; FIRE_PCI_WRITE_8(sc, FO_PCI_MSI_MAP_BASE + msi, (FIRE_PCI_READ_8(sc, FO_PCI_MSI_MAP_BASE + msi) & ~FO_PCI_MSI_MAP_EQNUM_MASK) | ((msiq << FO_PCI_MSI_MAP_EQNUM_SHFT) & FO_PCI_MSI_MAP_EQNUM_MASK)); FIRE_PCI_WRITE_8(sc, FO_PCI_MSI_CLR_BASE + msi, FO_PCI_MSI_CLR_EQWR_N); FIRE_PCI_WRITE_8(sc, FO_PCI_MSI_MAP_BASE + msi, FIRE_PCI_READ_8(sc, FO_PCI_MSI_MAP_BASE + msi) | FO_PCI_MSI_MAP_V); return (error); } /* * Make sure the vector is fully specified and we registered * our interrupt controller for it. */ vec = rman_get_start(ires); if (INTIGN(vec) != sc->sc_ign) { device_printf(dev, "invalid interrupt vector 0x%lx\n", vec); return (EINVAL); } if (intr_vectors[vec].iv_ic != &fire_ic) { device_printf(dev, "invalid interrupt controller for vector 0x%lx\n", vec); return (EINVAL); } return (bus_generic_setup_intr(dev, child, ires, flags, filt, intr, arg, cookiep)); } static int fire_teardown_intr(device_t dev, device_t child, struct resource *ires, void *cookie) { struct fire_softc *sc; u_long vec; int error; u_int msi, msiq; sc = device_get_softc(dev); if (rman_get_rid(ires) != 0) { msi = rman_get_start(ires); msiq = sc->sc_msi_msiq_table[msi - sc->sc_msi_first]; vec = INTMAP_VEC(sc->sc_ign, msiq + sc->sc_msiq_ino_first); msiq += sc->sc_msiq_first; msi <<= 3; FIRE_PCI_WRITE_8(sc, FO_PCI_MSI_MAP_BASE + msi, FIRE_PCI_READ_8(sc, FO_PCI_MSI_MAP_BASE + msi) & ~FO_PCI_MSI_MAP_V); msiq <<= 3; FIRE_PCI_WRITE_8(sc, FO_PCI_EQ_CTRL_CLR_BASE + msiq, FO_PCI_EQ_CTRL_CLR_COVERR | FO_PCI_EQ_CTRL_CLR_E2I | FO_PCI_EQ_CTRL_CLR_DIS); FIRE_PCI_WRITE_8(sc, FO_PCI_EQ_TL_BASE + msiq, (0 << FO_PCI_EQ_TL_SHFT) & FO_PCI_EQ_TL_MASK); FIRE_PCI_WRITE_8(sc, FO_PCI_EQ_HD_BASE + msiq, (0 << FO_PCI_EQ_HD_SHFT) & FO_PCI_EQ_HD_MASK); intr_vectors[vec].iv_ic = &fire_ic; /* * The MD interrupt code needs the vector rather than the MSI. */ rman_set_start(ires, vec); rman_set_end(ires, vec); error = bus_generic_teardown_intr(dev, child, ires, cookie); msi >>= 3; rman_set_start(ires, msi); rman_set_end(ires, msi); return (error); } return (bus_generic_teardown_intr(dev, child, ires, cookie)); } static struct resource * fire_alloc_resource(device_t bus, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct fire_softc *sc; struct resource *rv; struct rman *rm; sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: /* * XXX: Don't accept blank ranges for now, only single * interrupts. The other case should not happen with * the MI PCI code... * XXX: This may return a resource that is out of the * range that was specified. Is this correct...? */ if (start != end) panic("%s: XXX: interrupt range", __func__); if (*rid == 0) start = end = INTMAP_VEC(sc->sc_ign, end); return (bus_generic_alloc_resource(bus, child, type, rid, start, end, count, flags)); case SYS_RES_MEMORY: rm = &sc->sc_pci_mem_rman; break; case SYS_RES_IOPORT: rm = &sc->sc_pci_io_rman; break; default: return (NULL); } rv = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); if ((flags & RF_ACTIVE) != 0 && bus_activate_resource(child, type, *rid, rv) != 0) { rman_release_resource(rv); return (NULL); } return (rv); } static int fire_activate_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { struct fire_softc *sc; struct bus_space_tag *tag; sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: return (bus_generic_activate_resource(bus, child, type, rid, r)); case SYS_RES_MEMORY: tag = sparc64_alloc_bus_tag(r, rman_get_bustag( sc->sc_mem_res[FIRE_PCI]), PCI_MEMORY_BUS_SPACE, NULL); if (tag == NULL) return (ENOMEM); rman_set_bustag(r, tag); rman_set_bushandle(r, sc->sc_pci_bh[OFW_PCI_CS_MEM32] + rman_get_start(r)); break; case SYS_RES_IOPORT: rman_set_bustag(r, sc->sc_pci_iot); rman_set_bushandle(r, sc->sc_pci_bh[OFW_PCI_CS_IO] + rman_get_start(r)); break; } return (rman_activate_resource(r)); } static int fire_adjust_resource(device_t bus, device_t child, int type, struct resource *r, u_long start, u_long end) { struct fire_softc *sc; struct rman *rm; sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: return (bus_generic_adjust_resource(bus, child, type, r, start, end)); case SYS_RES_MEMORY: rm = &sc->sc_pci_mem_rman; break; case SYS_RES_IOPORT: rm = &sc->sc_pci_io_rman; break; default: return (EINVAL); } if (rman_is_region_manager(r, rm) == 0) return (EINVAL); return (rman_adjust_resource(r, start, end)); } static bus_dma_tag_t fire_get_dma_tag(device_t bus, device_t child __unused) { struct fire_softc *sc; sc = device_get_softc(bus); return (sc->sc_pci_dmat); } static phandle_t fire_get_node(device_t bus, device_t child __unused) { struct fire_softc *sc; sc = device_get_softc(bus); /* We only have one child, the PCI bus, which needs our own node. */ return (sc->sc_node); } static u_int fire_get_timecount(struct timecounter *tc) { struct fire_softc *sc; sc = tc->tc_priv; return (FIRE_CTRL_READ_8(sc, FO_XBC_PRF_CNT0) & TC_COUNTER_MAX_MASK); }