f42d907165
of them, and couple them by always performing all operations on all present IOMMUs. This is required because with the current API there is no way to determine on which bus a busdma operation is performed. While being there, clean up the iommu code a bit. This should be a step in the direction of allow some of larger machines to work; tests have shown that there still seem to be problems left.
1384 lines
40 KiB
C
1384 lines
40 KiB
C
/*
|
|
* Copyright (c) 1999, 2000 Matthew R. Green
|
|
* All rights reserved.
|
|
* Copyright 2001 by Thomas Moestl <tmm@FreeBSD.org>. 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
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
/*
|
|
* Support for `psycho' and `psycho+' UPA to PCI bridge and
|
|
* UltraSPARC IIi and IIe `sabre' PCI controllers.
|
|
*/
|
|
|
|
#include "opt_psycho.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <ofw/openfirm.h>
|
|
#include <ofw/ofw_pci.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/iommureg.h>
|
|
#include <machine/bus_common.h>
|
|
#include <machine/frame.h>
|
|
#include <machine/intr_machdep.h>
|
|
#include <machine/nexusvar.h>
|
|
#include <machine/ofw_upa.h>
|
|
#include <machine/resource.h>
|
|
|
|
#include <sys/rman.h>
|
|
|
|
#include <machine/iommuvar.h>
|
|
|
|
#include <pci/pcivar.h>
|
|
#include <pci/pcireg.h>
|
|
|
|
#include <sparc64/pci/ofw_pci.h>
|
|
#include <sparc64/pci/psychoreg.h>
|
|
#include <sparc64/pci/psychovar.h>
|
|
|
|
#include "pcib_if.h"
|
|
#include "sparcbus_if.h"
|
|
|
|
static void psycho_get_ranges(phandle_t, struct upa_ranges **, int *);
|
|
static void psycho_set_intr(struct psycho_softc *, int, device_t, bus_addr_t,
|
|
int, driver_intr_t);
|
|
static int psycho_find_intrmap(struct psycho_softc *, int, bus_addr_t *,
|
|
bus_addr_t *, u_long *);
|
|
static void psycho_intr_stub(void *);
|
|
#ifdef PSYCHO_STRAY
|
|
static void psycho_intr_stray(void *);
|
|
#endif
|
|
static bus_space_tag_t psycho_alloc_bus_tag(struct psycho_softc *, int);
|
|
|
|
|
|
/* Interrupt handlers */
|
|
static void psycho_ue(void *);
|
|
static void psycho_ce(void *);
|
|
static void psycho_bus_a(void *);
|
|
static void psycho_bus_b(void *);
|
|
static void psycho_powerfail(void *);
|
|
#ifdef PSYCHO_MAP_WAKEUP
|
|
static void psycho_wakeup(void *);
|
|
#endif
|
|
|
|
/* IOMMU support */
|
|
static void psycho_iommu_init(struct psycho_softc *, int);
|
|
static ofw_pci_binit_t psycho_binit;
|
|
|
|
/*
|
|
* bus space and bus dma support for UltraSPARC `psycho'. note that most
|
|
* of the bus dma support is provided by the iommu dvma controller.
|
|
*/
|
|
static int psycho_dmamap_create(bus_dma_tag_t, bus_dma_tag_t, int,
|
|
bus_dmamap_t *);
|
|
static int psycho_dmamap_destroy(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t);
|
|
static int psycho_dmamap_load(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t,
|
|
void *, bus_size_t, bus_dmamap_callback_t *, void *, int);
|
|
static void psycho_dmamap_unload(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t);
|
|
static void psycho_dmamap_sync(bus_dma_tag_t, bus_dma_tag_t, bus_dmamap_t,
|
|
bus_dmasync_op_t);
|
|
static int psycho_dmamem_alloc(bus_dma_tag_t, bus_dma_tag_t, void **, int,
|
|
bus_dmamap_t *);
|
|
static void psycho_dmamem_free(bus_dma_tag_t, bus_dma_tag_t, void *,
|
|
bus_dmamap_t);
|
|
|
|
/*
|
|
* autoconfiguration
|
|
*/
|
|
static int psycho_probe(device_t);
|
|
static int psycho_attach(device_t);
|
|
static int psycho_read_ivar(device_t, device_t, int, u_long *);
|
|
static int psycho_setup_intr(device_t, device_t, struct resource *, int,
|
|
driver_intr_t *, void *, void **);
|
|
static int psycho_teardown_intr(device_t, device_t, struct resource *, void *);
|
|
static struct resource *psycho_alloc_resource(device_t, device_t, int, int *,
|
|
u_long, u_long, u_long, u_int);
|
|
static int psycho_activate_resource(device_t, device_t, int, int,
|
|
struct resource *);
|
|
static int psycho_deactivate_resource(device_t, device_t, int, int,
|
|
struct resource *);
|
|
static int psycho_release_resource(device_t, device_t, int, int,
|
|
struct resource *);
|
|
static int psycho_maxslots(device_t);
|
|
static u_int32_t psycho_read_config(device_t, u_int, u_int, u_int, u_int, int);
|
|
static void psycho_write_config(device_t, u_int, u_int, u_int, u_int, u_int32_t,
|
|
int);
|
|
static int psycho_route_interrupt(device_t, device_t, int);
|
|
static int psycho_intr_pending(device_t, int);
|
|
static bus_space_handle_t psycho_get_bus_handle(device_t dev, enum sbbt_id id,
|
|
bus_space_handle_t childhdl, bus_space_tag_t *tag);
|
|
|
|
static device_method_t psycho_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, psycho_probe),
|
|
DEVMETHOD(device_attach, psycho_attach),
|
|
|
|
/* Bus interface */
|
|
DEVMETHOD(bus_print_child, bus_generic_print_child),
|
|
DEVMETHOD(bus_read_ivar, psycho_read_ivar),
|
|
DEVMETHOD(bus_setup_intr, psycho_setup_intr),
|
|
DEVMETHOD(bus_teardown_intr, psycho_teardown_intr),
|
|
DEVMETHOD(bus_alloc_resource, psycho_alloc_resource),
|
|
DEVMETHOD(bus_activate_resource, psycho_activate_resource),
|
|
DEVMETHOD(bus_deactivate_resource, psycho_deactivate_resource),
|
|
DEVMETHOD(bus_release_resource, psycho_release_resource),
|
|
|
|
/* pcib interface */
|
|
DEVMETHOD(pcib_maxslots, psycho_maxslots),
|
|
DEVMETHOD(pcib_read_config, psycho_read_config),
|
|
DEVMETHOD(pcib_write_config, psycho_write_config),
|
|
DEVMETHOD(pcib_route_interrupt, psycho_route_interrupt),
|
|
|
|
/* sparcbus interface */
|
|
DEVMETHOD(sparcbus_intr_pending, psycho_intr_pending),
|
|
DEVMETHOD(sparcbus_get_bus_handle, psycho_get_bus_handle),
|
|
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static driver_t psycho_driver = {
|
|
"pcib",
|
|
psycho_methods,
|
|
sizeof(struct psycho_softc),
|
|
};
|
|
|
|
static devclass_t psycho_devclass;
|
|
|
|
DRIVER_MODULE(psycho, nexus, psycho_driver, psycho_devclass, 0, 0);
|
|
|
|
static int psycho_ndevs;
|
|
static struct psycho_softc *psycho_softcs[4];
|
|
|
|
struct psycho_clr {
|
|
struct psycho_softc *pci_sc;
|
|
bus_addr_t pci_clr; /* clear register */
|
|
driver_intr_t *pci_handler; /* handler to call */
|
|
void *pci_arg; /* argument for the handler */
|
|
void *pci_cookie; /* interrupt cookie of parent bus */
|
|
};
|
|
|
|
struct psycho_strayclr {
|
|
struct psycho_softc *psc_sc;
|
|
bus_addr_t psc_clr; /* clear register */
|
|
};
|
|
|
|
#define PSYCHO_READ8(sc, off) \
|
|
bus_space_read_8((sc)->sc_bustag, (sc)->sc_bushandle, (off))
|
|
#define PSYCHO_WRITE8(sc, off, v) \
|
|
bus_space_write_8((sc)->sc_bustag, (sc)->sc_bushandle, (off), (v))
|
|
#define PCICTL_READ8(sc, off) \
|
|
PSYCHO_READ8((sc), (sc)->sc_pcictl + (off))
|
|
#define PCICTL_WRITE8(sc, off, v) \
|
|
PSYCHO_WRITE8((sc), (sc)->sc_pcictl + (off), (v))
|
|
|
|
/*
|
|
* "sabre" is the UltraSPARC IIi onboard UPA to PCI bridge. It manages a
|
|
* single PCI bus and does not have a streaming buffer. It often has an APB
|
|
* (advanced PCI bridge) connected to it, which was designed specifically for
|
|
* the IIi. The APB let's the IIi handle two independednt PCI buses, and
|
|
* appears as two "simba"'s underneath the sabre.
|
|
*
|
|
* "psycho" and "psycho+" is a dual UPA to PCI bridge. It sits on the UPA bus
|
|
* and manages two PCI buses. "psycho" has two 64-bit 33MHz buses, while
|
|
* "psycho+" controls both a 64-bit 33Mhz and a 64-bit 66Mhz PCI bus. You
|
|
* will usually find a "psycho+" since I don't think the original "psycho"
|
|
* ever shipped, and if it did it would be in the U30.
|
|
*
|
|
* Each "psycho" PCI bus appears as a separate OFW node, but since they are
|
|
* both part of the same IC, they only have a single register space. As such,
|
|
* they need to be configured together, even though the autoconfiguration will
|
|
* attach them separately.
|
|
*
|
|
* On UltraIIi machines, "sabre" itself usually takes pci0, with "simba" often
|
|
* as pci1 and pci2, although they have been implemented with other PCI bus
|
|
* numbers on some machines.
|
|
*
|
|
* On UltraII machines, there can be any number of "psycho+" ICs, each
|
|
* providing two PCI buses.
|
|
*
|
|
*
|
|
* XXXX The psycho/sabre node has an `interrupts' attribute. They contain
|
|
* the values of the following interrupts in this order:
|
|
*
|
|
* PCI Bus Error (30)
|
|
* DMA UE (2e)
|
|
* DMA CE (2f)
|
|
* Power Fail (25)
|
|
*
|
|
* We really should attach handlers for each.
|
|
*/
|
|
#define OFW_PCI_TYPE "pci"
|
|
#define OFW_SABRE_MODEL "SUNW,sabre"
|
|
#define OFW_SABRE_COMPAT "pci108e,a001"
|
|
#define OFW_SIMBA_MODEL "SUNW,simba"
|
|
#define OFW_PSYCHO_MODEL "SUNW,psycho"
|
|
|
|
static int
|
|
psycho_probe(device_t dev)
|
|
{
|
|
phandle_t node;
|
|
char *dtype, *model;
|
|
static char compat[32];
|
|
|
|
node = nexus_get_node(dev);
|
|
if (OF_getprop(node, "compatible", compat, sizeof(compat)) == -1)
|
|
compat[0] = '\0';
|
|
|
|
dtype = nexus_get_device_type(dev);
|
|
model = nexus_get_model(dev);
|
|
/* match on a type of "pci" and a sabre or a psycho */
|
|
if (nexus_get_reg(dev) != NULL && dtype != NULL &&
|
|
strcmp(dtype, OFW_PCI_TYPE) == 0 &&
|
|
((model != NULL && (strcmp(model, OFW_SABRE_MODEL) == 0 ||
|
|
strcmp(model, OFW_PSYCHO_MODEL) == 0)) ||
|
|
strcmp(compat, OFW_SABRE_COMPAT) == 0)) {
|
|
device_set_desc(dev, "U2P UPA-PCI bridge");
|
|
return (0);
|
|
}
|
|
|
|
return (ENXIO);
|
|
}
|
|
|
|
/*
|
|
* SUNW,psycho initialisation ..
|
|
* - find the per-psycho registers
|
|
* - figure out the IGN.
|
|
* - find our partner psycho
|
|
* - configure ourselves
|
|
* - bus range, bus,
|
|
* - interrupt map,
|
|
* - setup the chipsets.
|
|
* - if we're the first of the pair, initialise the IOMMU, otherwise
|
|
* just copy it's tags and addresses.
|
|
*/
|
|
static int
|
|
psycho_attach(device_t dev)
|
|
{
|
|
struct psycho_softc *sc;
|
|
struct psycho_softc *osc = NULL;
|
|
struct psycho_softc *asc;
|
|
struct upa_regs *reg;
|
|
struct ofw_pci_bdesc obd;
|
|
char compat[32];
|
|
char *model;
|
|
phandle_t node;
|
|
u_int64_t csr;
|
|
u_long pcictl_offs, mlen;
|
|
int psycho_br[2];
|
|
int n, i, nreg, rid;
|
|
#if defined(PSYCHO_DEBUG) || defined(PSYCHO_STRAY)
|
|
bus_addr_t map, clr;
|
|
u_int64_t mr;
|
|
#endif
|
|
#ifdef PSYCHO_STRAY
|
|
struct psycho_strayclr *sclr;
|
|
#endif
|
|
|
|
node = nexus_get_node(dev);
|
|
sc = device_get_softc(dev);
|
|
if (OF_getprop(node, "compatible", compat, sizeof(compat)) == -1)
|
|
compat[0] = '\0';
|
|
|
|
sc->sc_node = node;
|
|
sc->sc_dev = dev;
|
|
sc->sc_dmatag = nexus_get_dmatag(dev);
|
|
|
|
/*
|
|
* call the model-specific initialisation routine.
|
|
*/
|
|
model = nexus_get_model(dev);
|
|
if ((model != NULL &&
|
|
strcmp(model, OFW_SABRE_MODEL) == 0) ||
|
|
strcmp(compat, OFW_SABRE_COMPAT) == 0) {
|
|
sc->sc_mode = PSYCHO_MODE_SABRE;
|
|
if (model == NULL)
|
|
model = "sabre";
|
|
} else if (model != NULL &&
|
|
strcmp(model, OFW_PSYCHO_MODEL) == 0)
|
|
sc->sc_mode = PSYCHO_MODE_PSYCHO;
|
|
else
|
|
panic("psycho_attach: unknown model!");
|
|
|
|
/*
|
|
* The psycho gets three register banks:
|
|
* (0) per-PBM configuration and status registers
|
|
* (1) per-PBM PCI configuration space, containing only the
|
|
* PBM 256-byte PCI header
|
|
* (2) the shared psycho configuration registers (struct psychoreg)
|
|
*
|
|
* XXX use the prom address for the psycho registers? we do so far.
|
|
*/
|
|
reg = nexus_get_reg(dev);
|
|
nreg = nexus_get_nreg(dev);
|
|
/* Register layouts are different. stuupid. */
|
|
if (sc->sc_mode == PSYCHO_MODE_PSYCHO) {
|
|
if (nreg <= 2)
|
|
panic("psycho_attach: %d not enough registers", nreg);
|
|
sc->sc_basepaddr = (vm_offset_t)UPA_REG_PHYS(®[2]);
|
|
mlen = UPA_REG_SIZE(®[2]);
|
|
pcictl_offs = UPA_REG_PHYS(®[0]);
|
|
} else {
|
|
if (nreg <= 0)
|
|
panic("psycho_attach: %d not enough registers", nreg);
|
|
sc->sc_basepaddr = (vm_offset_t)UPA_REG_PHYS(®[0]);
|
|
mlen = UPA_REG_SIZE(reg);
|
|
pcictl_offs = sc->sc_basepaddr + PSR_PCICTL0;
|
|
}
|
|
|
|
/*
|
|
* Match other psycho's that are already configured against
|
|
* the base physical address. This will be the same for a
|
|
* pair of devices that share register space.
|
|
*/
|
|
for (n = 0; n < psycho_ndevs && n < sizeof(psycho_softcs) /
|
|
sizeof(psycho_softcs[0]); n++) {
|
|
asc = (struct psycho_softc *)psycho_softcs[n];
|
|
|
|
if (asc == NULL || asc == sc)
|
|
/* This entry is not there or it is me */
|
|
continue;
|
|
|
|
if (asc->sc_basepaddr != sc->sc_basepaddr)
|
|
/* This is an unrelated psycho */
|
|
continue;
|
|
|
|
/* Found partner */
|
|
osc = asc;
|
|
break;
|
|
}
|
|
|
|
if (osc == NULL) {
|
|
rid = 0;
|
|
sc->sc_mem_res = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid,
|
|
sc->sc_basepaddr, sc->sc_basepaddr + mlen - 1, mlen,
|
|
RF_ACTIVE);
|
|
if (sc->sc_mem_res == NULL ||
|
|
rman_get_start(sc->sc_mem_res) != sc->sc_basepaddr)
|
|
panic("psycho_attach: can't allocate device memory");
|
|
sc->sc_bustag = rman_get_bustag(sc->sc_mem_res);
|
|
sc->sc_bushandle = rman_get_bushandle(sc->sc_mem_res);
|
|
} else {
|
|
/*
|
|
* There's another psycho using the same register space. Copy the
|
|
* relevant stuff.
|
|
*/
|
|
sc->sc_mem_res = NULL;
|
|
sc->sc_bustag = osc->sc_bustag;
|
|
sc->sc_bushandle = osc->sc_bushandle;
|
|
}
|
|
if (pcictl_offs < sc->sc_basepaddr)
|
|
panic("psycho_attach: bogus pci control register location");
|
|
sc->sc_pcictl = pcictl_offs - sc->sc_basepaddr;
|
|
csr = PSYCHO_READ8(sc, PSR_CS);
|
|
sc->sc_ign = 0x7c0; /* APB IGN is always 0x7c */
|
|
if (sc->sc_mode == PSYCHO_MODE_PSYCHO)
|
|
sc->sc_ign = PSYCHO_GCSR_IGN(csr) << 6;
|
|
|
|
device_printf(dev, "%s: impl %d, version %d: ign %x ",
|
|
model, (int)PSYCHO_GCSR_IMPL(csr), (int)PSYCHO_GCSR_VERS(csr),
|
|
sc->sc_ign);
|
|
|
|
/*
|
|
* Setup the PCI control register
|
|
*/
|
|
csr = PCICTL_READ8(sc, PCR_CS);
|
|
csr |= PCICTL_MRLM | PCICTL_ARB_PARK | PCICTL_ERRINTEN | PCICTL_4ENABLE;
|
|
csr &= ~(PCICTL_SERR | PCICTL_CPU_PRIO | PCICTL_ARB_PRIO |
|
|
PCICTL_RTRYWAIT);
|
|
PCICTL_WRITE8(sc, PCR_CS, csr);
|
|
|
|
/* Grab the psycho ranges */
|
|
psycho_get_ranges(sc->sc_node, &sc->sc_range, &sc->sc_nrange);
|
|
|
|
/* Initialize memory and i/o rmans */
|
|
sc->sc_io_rman.rm_type = RMAN_ARRAY;
|
|
sc->sc_io_rman.rm_descr = "Psycho PCI I/O Ports";
|
|
if (rman_init(&sc->sc_io_rman) != 0 ||
|
|
rman_manage_region(&sc->sc_io_rman, 0, PSYCHO_IO_SIZE) != 0)
|
|
panic("psycho_probe: failed to set up i/o rman");
|
|
sc->sc_mem_rman.rm_type = RMAN_ARRAY;
|
|
sc->sc_mem_rman.rm_descr = "Psycho PCI Memory";
|
|
if (rman_init(&sc->sc_mem_rman) != 0 ||
|
|
rman_manage_region(&sc->sc_mem_rman, 0, PSYCHO_MEM_SIZE) != 0)
|
|
panic("psycho_probe: failed to set up memory rman");
|
|
/*
|
|
* 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 IO handles.
|
|
*/
|
|
for (n = 0; n < sc->sc_nrange; n++) {
|
|
i = UPA_RANGE_CS(&sc->sc_range[n]);
|
|
if (sc->sc_bh[i] != 0)
|
|
panic("psycho_attach: duplicate range for space %d", i);
|
|
sc->sc_bh[i] = UPA_RANGE_PHYS(&sc->sc_range[n]);
|
|
}
|
|
/*
|
|
* Check that all needed handles are present. The PCI_CS_MEM64 one is
|
|
* not currently used.
|
|
*/
|
|
for (n = 0; n < 3; n++) {
|
|
if (sc->sc_bh[n] == 0)
|
|
panic("psycho_attach: range %d missing", n);
|
|
}
|
|
|
|
/* allocate our tags */
|
|
sc->sc_memt = psycho_alloc_bus_tag(sc, PCI_MEMORY_BUS_SPACE);
|
|
sc->sc_iot = psycho_alloc_bus_tag(sc, PCI_IO_BUS_SPACE);
|
|
sc->sc_cfgt = psycho_alloc_bus_tag(sc, PCI_CONFIG_BUS_SPACE);
|
|
if (bus_dma_tag_create(sc->sc_dmatag, 8, 1, 0, 0x3ffffffff, NULL, NULL,
|
|
0x3ffffffff, 0xff, 0xffffffff, 0, &sc->sc_dmat) != 0)
|
|
panic("psycho_attach: bus_dma_tag_create failed");
|
|
/* Customize the tag */
|
|
sc->sc_dmat->cookie = sc;
|
|
sc->sc_dmat->dmamap_create = psycho_dmamap_create;
|
|
sc->sc_dmat->dmamap_destroy = psycho_dmamap_destroy;
|
|
sc->sc_dmat->dmamap_load = psycho_dmamap_load;
|
|
sc->sc_dmat->dmamap_unload = psycho_dmamap_unload;
|
|
sc->sc_dmat->dmamap_sync = psycho_dmamap_sync;
|
|
sc->sc_dmat->dmamem_alloc = psycho_dmamem_alloc;
|
|
sc->sc_dmat->dmamem_free = psycho_dmamem_free;
|
|
/* XXX: register as root dma tag (kluge). */
|
|
sparc64_root_dma_tag = sc->sc_dmat;
|
|
|
|
/* Register the softc, this is needed for paired psychos. */
|
|
if (psycho_ndevs < sizeof(psycho_softcs) / sizeof(psycho_softcs[0]))
|
|
psycho_softcs[psycho_ndevs] = sc;
|
|
else
|
|
device_printf(dev, "XXX: bump the number of psycho_softcs");
|
|
psycho_ndevs++;
|
|
/*
|
|
* And finally, if we're a sabre or the first of a pair of psycho's to
|
|
* arrive here, start up the IOMMU and get a config space tag.
|
|
*/
|
|
if (osc == NULL) {
|
|
/*
|
|
* Establish handlers for interesting interrupts....
|
|
*
|
|
* XXX We need to remember these and remove this to support
|
|
* hotplug on the UPA/FHC bus.
|
|
*
|
|
* XXX Not all controllers have these, but installing them
|
|
* is better than trying to sort through this mess.
|
|
*/
|
|
psycho_set_intr(sc, 0, dev, PSR_UE_INT_MAP, INTR_FAST,
|
|
psycho_ue);
|
|
psycho_set_intr(sc, 1, dev, PSR_CE_INT_MAP, 0, psycho_ce);
|
|
psycho_set_intr(sc, 2, dev, PSR_PCIAERR_INT_MAP, INTR_FAST,
|
|
psycho_bus_a);
|
|
psycho_set_intr(sc, 4, dev, PSR_POWER_INT_MAP, INTR_FAST,
|
|
psycho_powerfail);
|
|
/* Psycho-specific initialization. */
|
|
if (sc->sc_mode == PSYCHO_MODE_PSYCHO) {
|
|
/*
|
|
* Sabres do not have the following two interrupts.
|
|
*/
|
|
psycho_set_intr(sc, 3, dev, PSR_PCIBERR_INT_MAP,
|
|
INTR_FAST, psycho_bus_b);
|
|
#ifdef PSYCHO_MAP_WAKEUP
|
|
/*
|
|
* psycho_wakeup() doesn't do anything useful right
|
|
* now.
|
|
*/
|
|
psycho_set_intr(sc, 5, dev, PSR_PWRMGT_INT_MAP, 0,
|
|
psycho_wakeup);
|
|
#endif /* PSYCHO_MAP_WAKEUP */
|
|
|
|
/* Initialize the counter-timer. */
|
|
sparc64_counter_init(sc->sc_bustag, sc->sc_bushandle,
|
|
PSR_TC0);
|
|
}
|
|
|
|
/*
|
|
* Setup IOMMU and PCI configuration if we're the first
|
|
* of a pair of psycho's to arrive here.
|
|
*
|
|
* We should calculate a TSB size based on amount of RAM
|
|
* and number of bus controllers and number an type of
|
|
* child devices.
|
|
*
|
|
* For the moment, 32KB should be more than enough.
|
|
*/
|
|
sc->sc_is = malloc(sizeof(struct iommu_state), M_DEVBUF,
|
|
M_NOWAIT);
|
|
if (sc->sc_is == NULL)
|
|
panic("psycho_attach: malloc iommu_state failed");
|
|
sc->sc_is->is_sb[0] = 0;
|
|
sc->sc_is->is_sb[1] = 0;
|
|
if (OF_getproplen(sc->sc_node, "no-streaming-cache") < 0)
|
|
sc->sc_is->is_sb[0] = sc->sc_pcictl + PCR_STRBUF;
|
|
psycho_iommu_init(sc, 2);
|
|
} else {
|
|
/* Just copy IOMMU state, config tag and address */
|
|
sc->sc_is = osc->sc_is;
|
|
if (OF_getproplen(sc->sc_node, "no-streaming-cache") < 0)
|
|
sc->sc_is->is_sb[1] = sc->sc_pcictl + PCR_STRBUF;
|
|
iommu_reset(sc->sc_is);
|
|
}
|
|
|
|
/*
|
|
* Enable all interrupts, clear all interrupt states, and install an
|
|
* interrupt handler for OBIO interrupts, which can be ISA ones
|
|
* (to frob the interrupt clear registers).
|
|
* This aids the debugging of interrupt routing problems, and is needed
|
|
* for isa drivers that use isa_irq_pending (otherwise the registers
|
|
* will never be cleared).
|
|
*/
|
|
#if defined(PSYCHO_DEBUG) || defined(PSYCHO_STRAY)
|
|
for (map = PSR_PCIA0_INT_MAP, clr = PSR_PCIA0_INT_CLR, n = 0;
|
|
map <= PSR_PCIB3_INT_MAP; map += 8, clr += 32, n++) {
|
|
mr = PSYCHO_READ8(sc, map);
|
|
#ifdef PSYCHO_DEBUG
|
|
device_printf(dev, "intr map (pci) %d: %#lx\n", n, (u_long)mr);
|
|
#endif
|
|
PSYCHO_WRITE8(sc, map, mr & ~INTMAP_V);
|
|
for (i = 0; i < 4; i++)
|
|
PCICTL_WRITE8(sc, clr + i * 8, 0);
|
|
PSYCHO_WRITE8(sc, map, mr | INTMAP_V);
|
|
}
|
|
for (map = PSR_SCSI_INT_MAP, clr = PSR_SCSI_INT_CLR, n = 0;
|
|
map <= PSR_FFB1_INT_MAP; map += 8, clr += 8, n++) {
|
|
mr = PSYCHO_READ8(sc, map);
|
|
#ifdef PSYCHO_DEBUG
|
|
device_printf(dev, "intr map (obio) %d: %#lx, clr: %#lx\n", n,
|
|
(u_long)mr, (u_long)clr);
|
|
#endif
|
|
PSYCHO_WRITE8(sc, map, mr & ~INTMAP_V);
|
|
PSYCHO_WRITE8(sc, clr, 0);
|
|
#ifdef PSYCHO_STRAY
|
|
/*
|
|
* This can cause interrupt storms, and is therefore disabled
|
|
* by default.
|
|
* XXX: use intr_setup() to not confuse higher level code
|
|
*/
|
|
if (INTVEC(mr) != 0x7e6 && INTVEC(mr) != 0x7e7 &&
|
|
INTVEC(mr) != 0) {
|
|
sclr = malloc(sizeof(*sclr), M_DEVBUF, M_WAITOK);
|
|
sclr->psc_sc = sc;
|
|
sclr->psc_clr = clr;
|
|
intr_setup(PIL_LOW, intr_dequeue, INTVEC(mr),
|
|
psycho_intr_stray, sclr);
|
|
}
|
|
#endif
|
|
PSYCHO_WRITE8(sc, map, mr | INTMAP_V);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Get the bus range from the firmware; it is used solely for obtaining
|
|
* the inital bus number, and cannot be trusted on all machines.
|
|
*/
|
|
n = OF_getprop(node, "bus-range", (void *)psycho_br, sizeof(psycho_br));
|
|
if (n == -1)
|
|
panic("could not get psycho bus-range");
|
|
if (n != sizeof(psycho_br))
|
|
panic("broken psycho bus-range (%d)", n);
|
|
|
|
sc->sc_busno = ofw_pci_alloc_busno(sc->sc_node);
|
|
obd.obd_bus = psycho_br[0];
|
|
obd.obd_secbus = obd.obd_subbus = sc->sc_busno;
|
|
obd.obd_slot = PCS_DEVICE;
|
|
obd.obd_func = PCS_FUNC;
|
|
obd.obd_init = psycho_binit;
|
|
obd.obd_super = NULL;
|
|
/* Initial setup. */
|
|
psycho_binit(dev, &obd);
|
|
/* Update the bus number to what was just programmed. */
|
|
obd.obd_bus = obd.obd_secbus;
|
|
/*
|
|
* Initialize the interrupt registers of all devices hanging from
|
|
* the host bridge directly or indirectly via PCI-PCI bridges.
|
|
* The MI code (and the PCI spec) assume that this is done during
|
|
* system initialization, however the firmware does not do this
|
|
* at least on some models, and we probably shouldn't trust that
|
|
* the firmware uses the same model as this driver if it does.
|
|
* Additionally, set up the bus numbers and ranges.
|
|
*/
|
|
ofw_pci_init(dev, sc->sc_node, &obd);
|
|
|
|
device_add_child(dev, "pci", device_get_unit(dev));
|
|
return (bus_generic_attach(dev));
|
|
}
|
|
|
|
static void
|
|
psycho_set_intr(struct psycho_softc *sc, int index,
|
|
device_t dev, bus_addr_t map, int iflags, driver_intr_t handler)
|
|
{
|
|
int rid, vec;
|
|
u_int64_t mr;
|
|
|
|
mr = PSYCHO_READ8(sc, map);
|
|
vec = INTVEC(mr);
|
|
sc->sc_irq_res[index] = bus_alloc_resource(dev, SYS_RES_IRQ, &rid,
|
|
vec, vec, 1, RF_ACTIVE);
|
|
if (sc->sc_irq_res[index] == NULL)
|
|
panic("psycho_set_intr: failed to get interrupt");
|
|
bus_setup_intr(dev, sc->sc_irq_res[index], INTR_TYPE_MISC | iflags,
|
|
handler, sc, &sc->sc_ihand[index]);
|
|
PSYCHO_WRITE8(sc, map, mr | INTMAP_V);
|
|
}
|
|
|
|
static int
|
|
psycho_find_intrmap(struct psycho_softc *sc, int ino, bus_addr_t *intrmapptr,
|
|
bus_addr_t *intrclrptr, bus_addr_t *intrdiagptr)
|
|
{
|
|
bus_addr_t intrmap, intrclr;
|
|
u_int64_t im;
|
|
u_long diag;
|
|
int found;
|
|
|
|
found = 0;
|
|
/* Hunt thru obio first */
|
|
diag = PSYCHO_READ8(sc, PSR_OBIO_INT_DIAG);
|
|
for (intrmap = PSR_SCSI_INT_MAP, intrclr = PSR_SCSI_INT_CLR;
|
|
intrmap <= PSR_FFB1_INT_MAP; intrmap += 8, intrclr += 8,
|
|
diag >>= 2) {
|
|
im = PSYCHO_READ8(sc, intrmap);
|
|
if (INTINO(im) == ino) {
|
|
diag &= 2;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
diag = PSYCHO_READ8(sc, PSR_PCI_INT_DIAG);
|
|
/* Now do PCI interrupts */
|
|
for (intrmap = PSR_PCIA0_INT_MAP, intrclr = PSR_PCIA0_INT_CLR;
|
|
intrmap <= PSR_PCIB3_INT_MAP; intrmap += 8, intrclr += 32,
|
|
diag >>= 8) {
|
|
im = PSYCHO_READ8(sc, intrmap);
|
|
if (((im ^ ino) & 0x3c) == 0) {
|
|
intrclr += 8 * (ino & 3);
|
|
diag = (diag >> ((ino & 3) * 2)) & 2;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (intrmapptr != NULL)
|
|
*intrmapptr = intrmap;
|
|
if (intrclrptr != NULL)
|
|
*intrclrptr = intrclr;
|
|
if (intrdiagptr != NULL)
|
|
*intrdiagptr = diag;
|
|
return (found);
|
|
}
|
|
|
|
/* grovel the OBP for various psycho properties */
|
|
static void
|
|
psycho_get_ranges(phandle_t node, struct upa_ranges **rp, int *np)
|
|
{
|
|
|
|
*np = OF_getprop_alloc(node, "ranges", sizeof(**rp), (void **)rp);
|
|
if (*np == -1)
|
|
panic("could not get psycho ranges");
|
|
}
|
|
|
|
/*
|
|
* Interrupt handlers.
|
|
*/
|
|
static void
|
|
psycho_ue(void *arg)
|
|
{
|
|
struct psycho_softc *sc = (struct psycho_softc *)arg;
|
|
u_int64_t afar, afsr;
|
|
|
|
afar = PSYCHO_READ8(sc, PSR_UE_AFA);
|
|
afsr = PSYCHO_READ8(sc, PSR_UE_AFS);
|
|
/*
|
|
* On the UltraSPARC-IIi/IIe, IOMMU misses/protection faults cause
|
|
* the AFAR to be set to the physical address of the TTE entry that
|
|
* was invalid/write protected. Call into the iommu code to have
|
|
* them decoded to virtual IO addresses.
|
|
*/
|
|
if ((afsr & UEAFSR_P_DTE) != 0)
|
|
iommu_decode_fault(sc->sc_is, afar);
|
|
/* It's uncorrectable. Dump the regs and panic. */
|
|
panic("%s: uncorrectable DMA error AFAR %#lx AFSR %#lx\n",
|
|
device_get_name(sc->sc_dev), (u_long)afar, (u_long)afsr);
|
|
}
|
|
|
|
static void
|
|
psycho_ce(void *arg)
|
|
{
|
|
struct psycho_softc *sc = (struct psycho_softc *)arg;
|
|
u_int64_t afar, afsr;
|
|
|
|
PSYCHO_WRITE8(sc, PSR_CE_INT_CLR, 0);
|
|
afar = PSYCHO_READ8(sc, PSR_CE_AFA);
|
|
afsr = PSYCHO_READ8(sc, PSR_CE_AFS);
|
|
/* It's correctable. Dump the regs and continue. */
|
|
printf("%s: correctable DMA error AFAR %#lx AFSR %#lx\n",
|
|
device_get_name(sc->sc_dev), (u_long)afar, (u_long)afsr);
|
|
}
|
|
|
|
static void
|
|
psycho_bus_a(void *arg)
|
|
{
|
|
struct psycho_softc *sc = (struct psycho_softc *)arg;
|
|
u_int64_t afar, afsr;
|
|
|
|
afar = PSYCHO_READ8(sc, PSR_PCICTL0 + PCR_AFA);
|
|
afsr = PSYCHO_READ8(sc, PSR_PCICTL0 + PCR_AFS);
|
|
/* It's uncorrectable. Dump the regs and panic. */
|
|
panic("%s: PCI bus A error AFAR %#lx AFSR %#lx\n",
|
|
device_get_name(sc->sc_dev), (u_long)afar, (u_long)afsr);
|
|
}
|
|
|
|
static void
|
|
psycho_bus_b(void *arg)
|
|
{
|
|
struct psycho_softc *sc = (struct psycho_softc *)arg;
|
|
u_int64_t afar, afsr;
|
|
|
|
afar = PSYCHO_READ8(sc, PSR_PCICTL1 + PCR_AFA);
|
|
afsr = PSYCHO_READ8(sc, PSR_PCICTL1 + PCR_AFS);
|
|
/* It's uncorrectable. Dump the regs and panic. */
|
|
panic("%s: PCI bus B error AFAR %#lx AFSR %#lx\n",
|
|
device_get_name(sc->sc_dev), (u_long)afar, (u_long)afsr);
|
|
}
|
|
|
|
static void
|
|
psycho_powerfail(void *arg)
|
|
{
|
|
|
|
/* We lost power. Try to shut down NOW. */
|
|
#ifdef DEBUGGER_ON_POWERFAIL
|
|
struct psycho_softc *sc = (struct psycho_softc *)arg;
|
|
|
|
Debugger("powerfail");
|
|
PSYCHO_WRITE8(sc, PSR_POWER_INT_CLR, 0);
|
|
#else
|
|
printf("Power Failure Detected: Shutting down NOW.\n");
|
|
shutdown_nice(0);
|
|
#endif
|
|
}
|
|
|
|
#ifdef PSYCHO_MAP_WAKEUP
|
|
static void
|
|
psycho_wakeup(void *arg)
|
|
{
|
|
struct psycho_softc *sc = (struct psycho_softc *)arg;
|
|
|
|
PSYCHO_WRITE8(sc, PSR_PWRMGT_INT_CLR, 0);
|
|
/* Gee, we don't really have a framework to deal with this properly. */
|
|
printf("%s: power management wakeup\n", device_get_name(sc->sc_dev));
|
|
}
|
|
#endif /* PSYCHO_MAP_WAKEUP */
|
|
|
|
/* initialise the IOMMU... */
|
|
void
|
|
psycho_iommu_init(struct psycho_softc *sc, int tsbsize)
|
|
{
|
|
char *name;
|
|
struct iommu_state *is = sc->sc_is;
|
|
u_int32_t iobase = -1;
|
|
int *vdma = NULL;
|
|
int nitem;
|
|
|
|
/* punch in our copies */
|
|
is->is_bustag = sc->sc_bustag;
|
|
is->is_bushandle = sc->sc_bushandle;
|
|
is->is_iommu = PSR_IOMMU;
|
|
is->is_dtag = PSR_IOMMU_TLB_TAG_DIAG;
|
|
is->is_ddram = PSR_IOMMU_TLB_DATA_DIAG;
|
|
is->is_dqueue = PSR_IOMMU_QUEUE_DIAG;
|
|
is->is_dva = PSR_IOMMU_SVADIAG;
|
|
is->is_dtcmp = PSR_IOMMU_TLB_CMP_DIAG;
|
|
|
|
/*
|
|
* Separate the men from the boys. Get the `virtual-dma'
|
|
* property for sabre and use that to make sure the damn
|
|
* iommu works.
|
|
*
|
|
* We could query the `#virtual-dma-size-cells' and
|
|
* `#virtual-dma-addr-cells' and DTRT, but I'm lazy.
|
|
*/
|
|
nitem = OF_getprop_alloc(sc->sc_node, "virtual-dma", sizeof(vdma),
|
|
(void **)&vdma);
|
|
if (nitem > 0) {
|
|
iobase = vdma[0];
|
|
tsbsize = ffs(vdma[1]);
|
|
if (tsbsize < 25 || tsbsize > 31 ||
|
|
(vdma[1] & ~(1 << (tsbsize - 1))) != 0) {
|
|
printf("bogus tsb size %x, using 7\n", vdma[1]);
|
|
tsbsize = 31;
|
|
}
|
|
tsbsize -= 24;
|
|
free(vdma, M_OFWPROP);
|
|
}
|
|
|
|
/* give us a nice name.. */
|
|
name = (char *)malloc(32, M_DEVBUF, M_NOWAIT);
|
|
if (name == 0)
|
|
panic("couldn't malloc iommu name");
|
|
snprintf(name, 32, "%s dvma", device_get_name(sc->sc_dev));
|
|
|
|
iommu_init(name, is, tsbsize, iobase, 0);
|
|
}
|
|
|
|
static void
|
|
psycho_binit(device_t busdev, struct ofw_pci_bdesc *obd)
|
|
{
|
|
|
|
#ifdef PSYCHO_DEBUG
|
|
printf("psycho at %u/%u/%u: setting bus #s to %u/%u/%u\n",
|
|
obd->obd_bus, obd->obd_slot, obd->obd_func, obd->obd_bus,
|
|
obd->obd_secbus, obd->obd_subbus);
|
|
#endif /* PSYCHO_DEBUG */
|
|
/*
|
|
* NOTE: this must be kept in this order, since the last write will
|
|
* change the config space address of the psycho.
|
|
*/
|
|
PCIB_WRITE_CONFIG(busdev, obd->obd_bus, obd->obd_slot, obd->obd_func,
|
|
PCSR_SUBBUS, obd->obd_subbus, 1);
|
|
PCIB_WRITE_CONFIG(busdev, obd->obd_bus, obd->obd_slot, obd->obd_func,
|
|
PCSR_SECBUS, obd->obd_secbus, 1);
|
|
}
|
|
|
|
static int
|
|
psycho_maxslots(device_t dev)
|
|
{
|
|
|
|
/*
|
|
* XXX: is this correct? At any rate, a number that is too high
|
|
* shouldn't do any harm, if only because of the way things are
|
|
* handled in psycho_read_config.
|
|
*/
|
|
return (31);
|
|
}
|
|
|
|
/*
|
|
* Keep a table of quirky PCI devices that need fixups before the MI PCI code
|
|
* creates the resource lists. This needs to be moved around once other bus
|
|
* drivers are added. Moving it to the MI code should maybe be reconsidered
|
|
* if one of these devices appear in non-sparc64 boxen. It's likely that not
|
|
* all BIOSes/firmwares can deal with them.
|
|
*/
|
|
struct psycho_dquirk {
|
|
u_int32_t dq_devid;
|
|
int dq_quirk;
|
|
};
|
|
|
|
/* Quirk types. May be or'ed together. */
|
|
#define DQT_BAD_INTPIN 1 /* Intpin reg 0, but intpin used */
|
|
|
|
static struct psycho_dquirk dquirks[] = {
|
|
{ 0x1001108e, DQT_BAD_INTPIN }, /* Sun HME (PCIO func. 1) */
|
|
{ 0x1101108e, DQT_BAD_INTPIN }, /* Sun GEM (PCIO2 func. 1) */
|
|
{ 0x1102108e, DQT_BAD_INTPIN }, /* Sun FireWire ctl. (PCIO2 func. 2) */
|
|
{ 0x1103108e, DQT_BAD_INTPIN }, /* Sun USB ctl. (PCIO2 func. 3) */
|
|
};
|
|
|
|
#define NDQUIRKS (sizeof(dquirks) / sizeof(dquirks[0]))
|
|
|
|
static u_int32_t
|
|
psycho_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg,
|
|
int width)
|
|
{
|
|
struct psycho_softc *sc;
|
|
bus_space_handle_t bh;
|
|
u_long offset = 0;
|
|
u_int32_t r, devid;
|
|
int i;
|
|
|
|
/*
|
|
* The psycho bridge does not tolerate accesses to unconfigured PCI
|
|
* devices' or function's config space, so look up the device in the
|
|
* firmware device tree first, and if it is not present, return a value
|
|
* that will make the detection code think that there is no device here.
|
|
* This is ugly...
|
|
*/
|
|
if (reg == 0 && ofw_pci_find_node(bus, slot, func) == 0)
|
|
return (0xffffffff);
|
|
sc = (struct psycho_softc *)device_get_softc(dev);
|
|
offset = PSYCHO_CONF_OFF(bus, slot, func, reg);
|
|
bh = sc->sc_bh[PCI_CS_CONFIG];
|
|
switch (width) {
|
|
case 1:
|
|
r = bus_space_read_1(sc->sc_cfgt, bh, offset);
|
|
break;
|
|
case 2:
|
|
r = bus_space_read_2(sc->sc_cfgt, bh, offset);
|
|
break;
|
|
case 4:
|
|
r = bus_space_read_4(sc->sc_cfgt, bh, offset);
|
|
break;
|
|
default:
|
|
panic("psycho_read_config: bad width");
|
|
}
|
|
if (reg == PCIR_INTPIN && r == 0) {
|
|
/* Check for DQT_BAD_INTPIN quirk. */
|
|
devid = psycho_read_config(dev, bus, slot, func,
|
|
PCIR_DEVVENDOR, 4);
|
|
for (i = 0; i < NDQUIRKS; i++) {
|
|
if (dquirks[i].dq_devid == devid) {
|
|
/*
|
|
* Need to set the intpin to a value != 0 so
|
|
* that the MI code will think that this device
|
|
* has an interrupt.
|
|
* Just use 1 (intpin a) for now. This is, of
|
|
* course, bogus, but since interrupts are
|
|
* routed in advance, this does not really
|
|
* matter.
|
|
*/
|
|
if ((dquirks[i].dq_quirk & DQT_BAD_INTPIN) != 0)
|
|
r = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return (r);
|
|
}
|
|
|
|
static void
|
|
psycho_write_config(device_t dev, u_int bus, u_int slot, u_int func,
|
|
u_int reg, u_int32_t val, int width)
|
|
{
|
|
struct psycho_softc *sc;
|
|
bus_space_handle_t bh;
|
|
u_long offset = 0;
|
|
|
|
sc = (struct psycho_softc *)device_get_softc(dev);
|
|
offset = PSYCHO_CONF_OFF(bus, slot, func, reg);
|
|
bh = sc->sc_bh[PCI_CS_CONFIG];
|
|
switch (width) {
|
|
case 1:
|
|
bus_space_write_1(sc->sc_cfgt, bh, offset, val);
|
|
break;
|
|
case 2:
|
|
bus_space_write_2(sc->sc_cfgt, bh, offset, val);
|
|
break;
|
|
case 4:
|
|
bus_space_write_4(sc->sc_cfgt, bh, offset, val);
|
|
break;
|
|
default:
|
|
panic("psycho_write_config: bad width");
|
|
}
|
|
}
|
|
|
|
static int
|
|
psycho_route_interrupt(device_t bus, device_t dev, int pin)
|
|
{
|
|
|
|
/*
|
|
* XXX: ugly loathsome hack:
|
|
* We can't use ofw_pci_route_intr() here; the device passed may be
|
|
* the one of a bridge, so the original device can't be recovered.
|
|
*
|
|
* We need to use the firmware to route interrupts, however it has
|
|
* no interface which could be used to interpret intpins; instead,
|
|
* all assignments are done by device.
|
|
*
|
|
* The MI pci code will try to reroute interrupts of 0, although they
|
|
* are correct; all other interrupts are preinitialized, so if we
|
|
* get here, the intline is either 0 (so return 0), or we hit a
|
|
* device which was not preinitialized (e.g. hotplugged stuff), in
|
|
* which case we are lost.
|
|
*/
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
psycho_read_ivar(device_t dev, device_t child, int which, uintptr_t *result)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)device_get_softc(dev);
|
|
switch (which) {
|
|
case PCIB_IVAR_BUS:
|
|
*result = sc->sc_busno;
|
|
return (0);
|
|
}
|
|
return (ENOENT);
|
|
}
|
|
|
|
/* Write to the correct clr register, and call the actual handler. */
|
|
static void
|
|
psycho_intr_stub(void *arg)
|
|
{
|
|
struct psycho_clr *pc;
|
|
|
|
pc = (struct psycho_clr *)arg;
|
|
pc->pci_handler(pc->pci_arg);
|
|
PSYCHO_WRITE8(pc->pci_sc, pc->pci_clr, 0);
|
|
}
|
|
|
|
#ifdef PSYCHO_STRAY
|
|
/*
|
|
* Write to the correct clr register and return. arg is the address of the clear
|
|
* register to be used.
|
|
* XXX: print a message?
|
|
*/
|
|
static void
|
|
psycho_intr_stray(void *arg)
|
|
{
|
|
struct psycho_strayclr *sclr = arg;
|
|
|
|
PSYCHO_WRITE8(sclr->psc_sc, sclr->psc_clr, 0);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
psycho_setup_intr(device_t dev, device_t child,
|
|
struct resource *ires, int flags, driver_intr_t *intr, void *arg,
|
|
void **cookiep)
|
|
{
|
|
struct psycho_softc *sc;
|
|
struct psycho_clr *pc;
|
|
bus_addr_t intrmapptr, intrclrptr;
|
|
long vec = rman_get_start(ires);
|
|
u_int64_t mr;
|
|
int ino, error;
|
|
|
|
sc = (struct psycho_softc *)device_get_softc(dev);
|
|
pc = (struct psycho_clr *)malloc(sizeof(*pc), M_DEVBUF, M_NOWAIT);
|
|
if (pc == NULL)
|
|
return (NULL);
|
|
|
|
/*
|
|
* Hunt through all the interrupt mapping regs to look for our
|
|
* interrupt vector.
|
|
*
|
|
* XXX We only compare INOs rather than IGNs since the firmware may
|
|
* not provide the IGN and the IGN is constant for all device on that
|
|
* PCI controller. This could cause problems for the FFB/external
|
|
* interrupt which has a full vector that can be set arbitrarily.
|
|
*/
|
|
ino = INTINO(vec);
|
|
|
|
if (!psycho_find_intrmap(sc, ino, &intrmapptr, &intrclrptr, NULL)) {
|
|
printf("Cannot find interrupt vector %lx\n", vec);
|
|
free(pc, M_DEVBUF);
|
|
return (NULL);
|
|
}
|
|
|
|
#ifdef PSYCHO_DEBUG
|
|
device_printf(dev, "psycho_setup_intr: INO %d, map %#lx, clr %#lx\n",
|
|
ino, (u_long)intrmapptr, (u_long)intrclrptr);
|
|
#endif
|
|
pc->pci_sc = sc;
|
|
pc->pci_arg = arg;
|
|
pc->pci_handler = intr;
|
|
pc->pci_clr = intrclrptr;
|
|
/* Disable the interrupt while we fiddle with it */
|
|
mr = PSYCHO_READ8(sc, intrmapptr);
|
|
PSYCHO_WRITE8(sc, intrmapptr, mr & ~INTMAP_V);
|
|
error = BUS_SETUP_INTR(device_get_parent(dev), child, ires, flags,
|
|
psycho_intr_stub, pc, cookiep);
|
|
if (error != 0) {
|
|
free(pc, M_DEVBUF);
|
|
return (error);
|
|
}
|
|
pc->pci_cookie = *cookiep;
|
|
*cookiep = pc;
|
|
|
|
/*
|
|
* Clear the interrupt, it might have been triggered before it was
|
|
* set up.
|
|
*/
|
|
PSYCHO_WRITE8(sc, intrclrptr, 0);
|
|
/*
|
|
* Enable the interrupt now we have the handler installed.
|
|
* Read the current value as we can't change it besides the
|
|
* valid bit so so make sure only this bit is changed.
|
|
*/
|
|
PSYCHO_WRITE8(sc, intrmapptr, mr | INTMAP_V);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
psycho_teardown_intr(device_t dev, device_t child,
|
|
struct resource *vec, void *cookie)
|
|
{
|
|
struct psycho_clr *pc;
|
|
int error;
|
|
|
|
pc = (struct psycho_clr *)cookie;
|
|
error = BUS_TEARDOWN_INTR(device_get_parent(dev), child, vec,
|
|
pc->pci_cookie);
|
|
/*
|
|
* Don't disable the interrupt for now, so that stray interupts get
|
|
* detected...
|
|
*/
|
|
if (error != 0)
|
|
free(pc, M_DEVBUF);
|
|
return (error);
|
|
}
|
|
|
|
static struct resource *
|
|
psycho_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 psycho_softc *sc;
|
|
struct resource *rv;
|
|
struct rman *rm;
|
|
bus_space_tag_t bt;
|
|
bus_space_handle_t bh;
|
|
int needactivate = flags & RF_ACTIVE;
|
|
|
|
flags &= ~RF_ACTIVE;
|
|
|
|
sc = (struct psycho_softc *)device_get_softc(bus);
|
|
if (type == 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("psycho_alloc_resource: XXX: interrupt range");
|
|
start = end |= sc->sc_ign;
|
|
return (bus_alloc_resource(bus, type, rid, start, end,
|
|
count, flags));
|
|
}
|
|
switch (type) {
|
|
case SYS_RES_MEMORY:
|
|
rm = &sc->sc_mem_rman;
|
|
bt = sc->sc_memt;
|
|
bh = sc->sc_bh[PCI_CS_MEM32];
|
|
break;
|
|
case SYS_RES_IOPORT:
|
|
rm = &sc->sc_io_rman;
|
|
bt = sc->sc_iot;
|
|
/* XXX: probably should use ranges property here. */
|
|
bh = sc->sc_bh[PCI_CS_IO];
|
|
break;
|
|
default:
|
|
return (NULL);
|
|
}
|
|
|
|
rv = rman_reserve_resource(rm, start, end, count, flags, child);
|
|
if (rv == NULL)
|
|
return (NULL);
|
|
|
|
bh += rman_get_start(rv);
|
|
rman_set_bustag(rv, bt);
|
|
rman_set_bushandle(rv, bh);
|
|
|
|
if (needactivate) {
|
|
if (bus_activate_resource(child, type, *rid, rv)) {
|
|
rman_release_resource(rv);
|
|
return (NULL);
|
|
}
|
|
}
|
|
|
|
return (rv);
|
|
}
|
|
|
|
static int
|
|
psycho_activate_resource(device_t bus, device_t child, int type, int rid,
|
|
struct resource *r)
|
|
{
|
|
void *p;
|
|
int error;
|
|
|
|
if (type == SYS_RES_IRQ)
|
|
return (bus_activate_resource(bus, type, rid, r));
|
|
if (type == SYS_RES_MEMORY) {
|
|
/*
|
|
* Need to memory-map the device space, as some drivers depend
|
|
* on the virtual address being set and useable.
|
|
*/
|
|
error = sparc64_bus_mem_map(rman_get_bustag(r),
|
|
rman_get_bushandle(r), rman_get_size(r), 0, NULL, &p);
|
|
if (error != 0)
|
|
return (error);
|
|
rman_set_virtual(r, p);
|
|
}
|
|
return (rman_activate_resource(r));
|
|
}
|
|
|
|
static int
|
|
psycho_deactivate_resource(device_t bus, device_t child, int type, int rid,
|
|
struct resource *r)
|
|
{
|
|
|
|
if (type == SYS_RES_IRQ)
|
|
return (bus_deactivate_resource(bus, type, rid, r));
|
|
if (type == SYS_RES_MEMORY) {
|
|
sparc64_bus_mem_unmap(rman_get_virtual(r), rman_get_size(r));
|
|
rman_set_virtual(r, NULL);
|
|
}
|
|
return (rman_deactivate_resource(r));
|
|
}
|
|
|
|
static int
|
|
psycho_release_resource(device_t bus, device_t child, int type, int rid,
|
|
struct resource *r)
|
|
{
|
|
int error;
|
|
|
|
if (type == SYS_RES_IRQ)
|
|
return (bus_release_resource(bus, type, rid, r));
|
|
if (rman_get_flags(r) & RF_ACTIVE) {
|
|
error = bus_deactivate_resource(child, type, rid, r);
|
|
if (error)
|
|
return error;
|
|
}
|
|
return (rman_release_resource(r));
|
|
}
|
|
|
|
static int
|
|
psycho_intr_pending(device_t dev, int intr)
|
|
{
|
|
struct psycho_softc *sc;
|
|
u_long diag;
|
|
|
|
sc = (struct psycho_softc *)device_get_softc(dev);
|
|
if (!psycho_find_intrmap(sc, intr, NULL, NULL, &diag)) {
|
|
printf("psycho_intr_pending: mapping not found for %d\n", intr);
|
|
return (0);
|
|
}
|
|
return (diag != 0);
|
|
}
|
|
|
|
static bus_space_handle_t
|
|
psycho_get_bus_handle(device_t dev, enum sbbt_id id,
|
|
bus_space_handle_t childhdl, bus_space_tag_t *tag)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)device_get_softc(dev);
|
|
switch(id) {
|
|
case SBBT_IO:
|
|
*tag = sc->sc_iot;
|
|
return (sc->sc_bh[PCI_CS_IO] + childhdl);
|
|
case SBBT_MEM:
|
|
*tag = sc->sc_memt;
|
|
return (sc->sc_bh[PCI_CS_MEM32] + childhdl);
|
|
default:
|
|
panic("psycho_get_bus_handle: illegal space\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* below here is bus space and bus dma support
|
|
*/
|
|
static bus_space_tag_t
|
|
psycho_alloc_bus_tag(struct psycho_softc *sc, int type)
|
|
{
|
|
bus_space_tag_t bt;
|
|
|
|
bt = (bus_space_tag_t)malloc(sizeof(struct bus_space_tag), M_DEVBUF,
|
|
M_NOWAIT | M_ZERO);
|
|
if (bt == NULL)
|
|
panic("psycho_alloc_bus_tag: out of memory");
|
|
|
|
bzero(bt, sizeof *bt);
|
|
bt->cookie = sc;
|
|
bt->parent = sc->sc_bustag;
|
|
bt->type = type;
|
|
return (bt);
|
|
}
|
|
|
|
/*
|
|
* hooks into the iommu dvma calls.
|
|
*/
|
|
static int
|
|
psycho_dmamem_alloc(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, void **vaddr,
|
|
int flags, bus_dmamap_t *mapp)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)pdmat->cookie;
|
|
return (iommu_dvmamem_alloc(pdmat, ddmat, sc->sc_is, vaddr, flags,
|
|
mapp));
|
|
}
|
|
|
|
static void
|
|
psycho_dmamem_free(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, void *vaddr,
|
|
bus_dmamap_t map)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)pdmat->cookie;
|
|
iommu_dvmamem_free(pdmat, ddmat, sc->sc_is, vaddr, map);
|
|
}
|
|
|
|
static int
|
|
psycho_dmamap_create(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, int flags,
|
|
bus_dmamap_t *mapp)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)pdmat->cookie;
|
|
return (iommu_dvmamap_create(pdmat, ddmat, sc->sc_is, flags, mapp));
|
|
|
|
}
|
|
|
|
static int
|
|
psycho_dmamap_destroy(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat,
|
|
bus_dmamap_t map)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)pdmat->cookie;
|
|
return (iommu_dvmamap_destroy(pdmat, ddmat, sc->sc_is, map));
|
|
}
|
|
|
|
static int
|
|
psycho_dmamap_load(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map,
|
|
void *buf, bus_size_t buflen, bus_dmamap_callback_t *callback,
|
|
void *callback_arg, int flags)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)pdmat->cookie;
|
|
return (iommu_dvmamap_load(pdmat, ddmat, sc->sc_is, map, buf, buflen,
|
|
callback, callback_arg, flags));
|
|
}
|
|
|
|
static void
|
|
psycho_dmamap_unload(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)pdmat->cookie;
|
|
iommu_dvmamap_unload(pdmat, ddmat, sc->sc_is, map);
|
|
}
|
|
|
|
static void
|
|
psycho_dmamap_sync(bus_dma_tag_t pdmat, bus_dma_tag_t ddmat, bus_dmamap_t map,
|
|
bus_dmasync_op_t op)
|
|
{
|
|
struct psycho_softc *sc;
|
|
|
|
sc = (struct psycho_softc *)pdmat->cookie;
|
|
iommu_dvmamap_sync(pdmat, ddmat, sc->sc_is, map, op);
|
|
}
|