546 lines
14 KiB
C
546 lines
14 KiB
C
/* $NetBSD: pci_machdep.c,v 1.21 2001/03/21 01:33:48 mrg Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 1999, 2000 Matthew R. Green
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* functions expected by the MI PCI code.
|
|
*/
|
|
|
|
#ifdef DEBUG
|
|
#define SPDB_CONF 0x01
|
|
#define SPDB_INTR 0x04
|
|
#define SPDB_INTMAP 0x08
|
|
#define SPDB_INTFIX 0x10
|
|
int sparc_pci_debug = 0x0;
|
|
#define DPRINTF(l, s) do { if (sparc_pci_debug & l) printf s; } while (0)
|
|
#else
|
|
#define DPRINTF(l, s)
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/time.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/device.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#define _SPARC_BUS_DMA_PRIVATE
|
|
#include <machine/bus.h>
|
|
#include <machine/autoconf.h>
|
|
|
|
#include <dev/pci/pcivar.h>
|
|
#include <dev/pci/pcireg.h>
|
|
|
|
#include <dev/ofw/openfirm.h>
|
|
#include <dev/ofw/ofw_pci.h>
|
|
|
|
#include <sparc64/dev/iommureg.h>
|
|
#include <sparc64/dev/iommuvar.h>
|
|
#include <sparc64/dev/psychoreg.h>
|
|
#include <sparc64/dev/psychovar.h>
|
|
|
|
/* this is a base to be copied */
|
|
struct sparc_pci_chipset _sparc_pci_chipset = {
|
|
NULL,
|
|
};
|
|
|
|
/*
|
|
* functions provided to the MI code.
|
|
*/
|
|
|
|
void
|
|
pci_attach_hook(parent, self, pba)
|
|
struct device *parent;
|
|
struct device *self;
|
|
struct pcibus_attach_args *pba;
|
|
{
|
|
pci_chipset_tag_t pc = pba->pba_pc;
|
|
struct psycho_pbm *pp = pc->cookie;
|
|
struct psycho_registers *pr;
|
|
pcitag_t tag;
|
|
char *name, *devtype;
|
|
u_int32_t hi, mid, lo, intr, line;
|
|
u_int32_t dev, fn, bus;
|
|
int node, i, n, *ip, *ap;
|
|
|
|
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\npci_attach_hook:"));
|
|
|
|
/*
|
|
* ok, here we look in the OFW for each PCI device and fix it's
|
|
* "interrupt line" register to be useful.
|
|
*/
|
|
|
|
for (node = firstchild(pc->node); node; node = nextsibling(node)) {
|
|
pr = NULL;
|
|
ip = ap = NULL;
|
|
|
|
/*
|
|
* ok, for each child we get the "interrupts" property,
|
|
* which contains a value to match against later.
|
|
* XXX deal with multiple "interrupts" values XXX.
|
|
* then we get the "assigned-addresses" property which
|
|
* contains, in the first entry, the PCI bus, device and
|
|
* function associated with this node, which we use to
|
|
* generate a pcitag_t to use pci_conf_read() and
|
|
* pci_conf_write(). next, we get the 'reg" property
|
|
* which is structured like the following:
|
|
* u_int32_t phys_hi;
|
|
* u_int32_t phys_mid;
|
|
* u_int32_t phys_lo;
|
|
* u_int32_t size_hi;
|
|
* u_int32_t size_lo;
|
|
* we mask these values with the "interrupt-map-mask"
|
|
* property of our parent and them compare with each
|
|
* entry in the "interrupt-map" property (also of our
|
|
* parent) which is structred like the following:
|
|
* u_int32_t phys_hi;
|
|
* u_int32_t phys_mid;
|
|
* u_int32_t phys_lo;
|
|
* u_int32_t intr;
|
|
* int32_t child_node;
|
|
* u_int32_t child_intr;
|
|
* if there is an exact match with phys_hi, phys_mid,
|
|
* phys_lo and the interrupt, we have a match and we
|
|
* know that this interrupt's value is really the
|
|
* child_intr of the interrupt map entry. we put this
|
|
* into the PCI interrupt line register so that when
|
|
* the driver for this node wants to attach, we know
|
|
* it's INO already.
|
|
*/
|
|
|
|
name = getpropstring(node, "name");
|
|
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\n\tnode %x name `%s'", node, name));
|
|
devtype = getpropstring(node, "device_type");
|
|
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), (" devtype `%s':", devtype));
|
|
|
|
/* ignore PCI bridges, we'll get them later */
|
|
if (strcmp(devtype, "pci") == 0)
|
|
continue;
|
|
|
|
/* if there isn't any "interrupts" then we don't care to fix it */
|
|
ip = NULL;
|
|
if (getprop(node, "interrupts", sizeof(int), &n, (void **)&ip))
|
|
continue;
|
|
DPRINTF(SPDB_INTFIX, (" got interrupts"));
|
|
|
|
/* and if there isn't an "assigned-addresses" we can't find b/d/f */
|
|
if (getprop(node, "assigned-addresses", sizeof(int), &n,
|
|
(void **)&ap))
|
|
goto clean1;
|
|
DPRINTF(SPDB_INTFIX, (" got assigned-addresses"));
|
|
|
|
/* ok, and now the "reg" property, so we know what we're talking about. */
|
|
if (getprop(node, "reg", sizeof(*pr), &n,
|
|
(void **)&pr))
|
|
goto clean2;
|
|
DPRINTF(SPDB_INTFIX, (" got reg"));
|
|
|
|
bus = TAG2BUS(ap[0]);
|
|
dev = TAG2DEV(ap[0]);
|
|
fn = TAG2FN(ap[0]);
|
|
|
|
DPRINTF(SPDB_INTFIX, ("; bus %u dev %u fn %u", bus, dev, fn));
|
|
|
|
tag = pci_make_tag(pc, bus, dev, fn);
|
|
|
|
DPRINTF(SPDB_INTFIX, ("; tag %08x\n\t; reg: hi %x mid %x lo %x intr %x", tag, pr->phys_hi, pr->phys_mid, pr->phys_lo, *ip));
|
|
|
|
/*
|
|
* if there is no interrupt-map property in the parent, we must
|
|
* assume our "interrupts" property is valid.
|
|
*/
|
|
if (pp->pp_nintmap == 0) {
|
|
intr = *ip;
|
|
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\n\t ; no interrupt-map, using intr %x", *ip));
|
|
goto bingo;
|
|
}
|
|
|
|
DPRINTF(SPDB_INTFIX, ("\n\t; intmapmask: hi %x mid %x lo %x intr %x", pp->pp_intmapmask.phys_hi, pp->pp_intmapmask.phys_mid,
|
|
pp->pp_intmapmask.phys_lo, pp->pp_intmapmask.intr));
|
|
|
|
hi = pr->phys_hi & pp->pp_intmapmask.phys_hi;
|
|
mid = pr->phys_mid & pp->pp_intmapmask.phys_mid;
|
|
lo = pr->phys_lo & pp->pp_intmapmask.phys_lo;
|
|
intr = *ip & pp->pp_intmapmask.intr;
|
|
|
|
DPRINTF(SPDB_INTFIX, ("\n\t; after: hi %x mid %x lo %x intr %x", hi, mid, lo, intr));
|
|
|
|
for (i = 0; i < pp->pp_nintmap; i++) {
|
|
DPRINTF(SPDB_INTFIX, ("\n\t\tmatching for: hi %x mid %x lo %x intr %x", pp->pp_intmap[i].phys_hi, pp->pp_intmap[i].phys_mid,
|
|
pp->pp_intmap[i].phys_lo, pp->pp_intmap[i].intr));
|
|
|
|
if (pp->pp_intmap[i].phys_hi != hi ||
|
|
pp->pp_intmap[i].phys_mid != mid ||
|
|
pp->pp_intmap[i].phys_lo != lo ||
|
|
pp->pp_intmap[i].intr != intr)
|
|
continue;
|
|
intr = pp->pp_intmap[i].child_intr;
|
|
DPRINTF(SPDB_INTFIX, ("... BINGO! ..."));
|
|
|
|
bingo:
|
|
/*
|
|
* OK! we found match. pull out the old interrupt
|
|
* register, patch in the new value, and put it back.
|
|
*/
|
|
line = pci_conf_read(pc, tag, PCI_INTERRUPT_REG);
|
|
DPRINTF(SPDB_INTFIX, ("\n\t ; read %x from intreg", line));
|
|
|
|
line = PCI_INTERRUPT_CODE(PCI_INTERRUPT_LATENCY(line),
|
|
PCI_INTERRUPT_GRANT(line),
|
|
PCI_INTERRUPT_PIN(line),
|
|
PCI_INTERRUPT_LINE(intr));
|
|
|
|
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\n\t ; gonna write %x to intreg", line));
|
|
pci_conf_write(pc, tag, PCI_INTERRUPT_REG, intr);
|
|
break;
|
|
}
|
|
if (i == pp->pp_nintmap) {
|
|
/*
|
|
* Not matched by parent interrupt map. If the
|
|
* interrupt property has the INTMAP_OBIO bit
|
|
* set, assume the PROM has (wrongly) supplied it
|
|
* in the parent's bus format, rather than as a
|
|
* PCI interrupt line number.
|
|
*
|
|
* This seems to be an issue only with the
|
|
* psycho host-to-pci bridge.
|
|
*/
|
|
if (pp->pp_sc->sc_mode == PSYCHO_MODE_PSYCHO &&
|
|
(*ip & INTMAP_OBIO) != 0) {
|
|
DPRINTF((SPDB_INTFIX|SPDB_INTMAP),
|
|
("\n\t; PSYCHO: no match but obio interrupt in parent format"));
|
|
|
|
intr = *ip;
|
|
i = -1;
|
|
goto bingo; /* hackish */
|
|
}
|
|
}
|
|
|
|
/* enable mem & dma if not already */
|
|
pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG,
|
|
PCI_COMMAND_MEM_ENABLE|PCI_COMMAND_MASTER_ENABLE|PCI_COMMAND_IO_ENABLE);
|
|
|
|
|
|
/* clean up */
|
|
if (pr)
|
|
free(pr, M_DEVBUF);
|
|
clean2:
|
|
if (ap)
|
|
free(ap, M_DEVBUF);
|
|
clean1:
|
|
if (ip)
|
|
free(ip, M_DEVBUF);
|
|
}
|
|
DPRINTF(SPDB_INTFIX, ("\n"));
|
|
}
|
|
|
|
int
|
|
pci_bus_maxdevs(pc, busno)
|
|
pci_chipset_tag_t pc;
|
|
int busno;
|
|
{
|
|
|
|
return 32;
|
|
}
|
|
|
|
#ifdef __PCI_BUS_DEVORDER
|
|
int
|
|
pci_bus_devorder(pc, busno, devs)
|
|
pci_chipset_tag_t pc;
|
|
int busno;
|
|
char *devs;
|
|
{
|
|
struct ofw_pci_register reg0;
|
|
int node, len, device, i = 0;
|
|
u_int32_t done = 0;
|
|
|
|
for (node = OF_child(pc->node); node; node = OF_peer(node)) {
|
|
len = OF_getproplen(node, "reg");
|
|
if (len < sizeof(reg0))
|
|
continue;
|
|
if (OF_getprop(node, "reg", (void *)®0, sizeof(reg0)) != len)
|
|
panic("pci_probe_bus: OF_getprop len botch");
|
|
|
|
device = OFW_PCI_PHYS_HI_DEVICE(reg0.phys_hi);
|
|
|
|
if (done & (1 << device))
|
|
continue;
|
|
|
|
devs[i++] = device;
|
|
done |= 1 << device;
|
|
if (i == 32)
|
|
break;
|
|
}
|
|
if (i < 32)
|
|
devs[i] = -1;
|
|
|
|
return i;
|
|
}
|
|
#endif
|
|
|
|
#ifdef __PCI_DEV_FUNCORDER
|
|
int
|
|
pci_dev_funcorder(pc, busno, device, funcs)
|
|
pci_chipset_tag_t pc;
|
|
int busno;
|
|
int device;
|
|
char *funcs;
|
|
{
|
|
struct ofw_pci_register reg0;
|
|
int node, len, i = 0;
|
|
|
|
for (node = OF_child(pc->node); node; node = OF_peer(node)) {
|
|
len = OF_getproplen(node, "reg");
|
|
if (len < sizeof(reg0))
|
|
continue;
|
|
if (OF_getprop(node, "reg", (void *)®0, sizeof(reg0)) != len)
|
|
panic("pci_probe_bus: OF_getprop len botch");
|
|
|
|
if (device != OFW_PCI_PHYS_HI_DEVICE(reg0.phys_hi))
|
|
continue;
|
|
|
|
funcs[i++] = OFW_PCI_PHYS_HI_FUNCTION(reg0.phys_hi);
|
|
if (i == 8)
|
|
break;
|
|
}
|
|
if (i < 8)
|
|
funcs[i] = -1;
|
|
|
|
return i;
|
|
}
|
|
#endif
|
|
|
|
pcitag_t
|
|
pci_make_tag(pc, b, d, f)
|
|
pci_chipset_tag_t pc;
|
|
int b;
|
|
int d;
|
|
int f;
|
|
{
|
|
|
|
/* make me a useable offset */
|
|
return (b << 16) | (d << 11) | (f << 8);
|
|
}
|
|
|
|
static int confaddr_ok __P((struct psycho_softc *, pcitag_t));
|
|
|
|
/*
|
|
* this function is a large hack. ideally, we should also trap accesses
|
|
* properly, but we have to avoid letting anything read various parts
|
|
* of bus 0 dev 0 fn 0 space or the machine may hang. so, even if we
|
|
* do properly implement PCI config access trap handling, this function
|
|
* should remain in place Just In Case.
|
|
*/
|
|
static int
|
|
confaddr_ok(sc, tag)
|
|
struct psycho_softc *sc;
|
|
pcitag_t tag;
|
|
{
|
|
int bus, dev, fn;
|
|
|
|
bus = TAG2BUS(tag);
|
|
dev = TAG2DEV(tag);
|
|
fn = TAG2FN(tag);
|
|
|
|
if (sc->sc_mode == PSYCHO_MODE_SABRE) {
|
|
/*
|
|
* bus 0 is only ok for dev 0 fn 0, dev 1 fn 0 and dev fn 1.
|
|
*/
|
|
if (bus == 0 &&
|
|
((dev == 0 && fn > 0) ||
|
|
(dev == 1 && fn > 1) ||
|
|
(dev > 1))) {
|
|
DPRINTF(SPDB_CONF, (" confaddr_ok: rejecting bus %d dev %d fn %d -", bus, dev, fn));
|
|
return (0);
|
|
}
|
|
} else if (sc->sc_mode == PSYCHO_MODE_PSYCHO) {
|
|
/*
|
|
* make sure we are reading our own bus
|
|
*/
|
|
/* XXX??? */
|
|
paddr_t addr = sc->sc_configaddr + tag;
|
|
int asi = bus_type_asi[sc->sc_configtag->type];
|
|
if (probeget(addr, asi, 4) == -1) {
|
|
DPRINTF(SPDB_CONF, (" confaddr_ok: rejecting bus %d dev %d fn %d -", bus, dev, fn));
|
|
return (0);
|
|
}
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/* assume we are mapped little-endian/side-effect */
|
|
pcireg_t
|
|
pci_conf_read(pc, tag, reg)
|
|
pci_chipset_tag_t pc;
|
|
pcitag_t tag;
|
|
int reg;
|
|
{
|
|
struct psycho_pbm *pp = pc->cookie;
|
|
struct psycho_softc *sc = pp->pp_sc;
|
|
pcireg_t val;
|
|
|
|
DPRINTF(SPDB_CONF, ("pci_conf_read: tag %lx; reg %x; ", (long)tag, reg));
|
|
DPRINTF(SPDB_CONF, ("asi = %x; readaddr = %qx (offset = %x) ...",
|
|
bus_type_asi[sc->sc_configtag->type],
|
|
(long long)(sc->sc_configaddr + tag + reg), (int)tag + reg));
|
|
|
|
if (confaddr_ok(sc, tag) == 0) {
|
|
val = (pcireg_t)~0;
|
|
} else {
|
|
membar_sync();
|
|
val = bus_space_read_4(sc->sc_configtag, sc->sc_configaddr,
|
|
tag + reg);
|
|
membar_sync();
|
|
}
|
|
DPRINTF(SPDB_CONF, (" returning %08x\n", (u_int)val));
|
|
|
|
return (val);
|
|
}
|
|
|
|
void
|
|
pci_conf_write(pc, tag, reg, data)
|
|
pci_chipset_tag_t pc;
|
|
pcitag_t tag;
|
|
int reg;
|
|
pcireg_t data;
|
|
{
|
|
struct psycho_pbm *pp = pc->cookie;
|
|
struct psycho_softc *sc = pp->pp_sc;
|
|
|
|
DPRINTF(SPDB_CONF, ("pci_conf_write: tag %lx; reg %x; data %x; ", (long)tag, reg, (int)data));
|
|
DPRINTF(SPDB_CONF, ("asi = %x; readaddr = %qx (offset = %x)\n",
|
|
bus_type_asi[sc->sc_configtag->type],
|
|
(long long)(sc->sc_configaddr + tag + reg), (int)tag + reg));
|
|
|
|
if (confaddr_ok(sc, tag) == 0)
|
|
panic("pci_conf_write: bad addr");
|
|
|
|
membar_sync();
|
|
bus_space_write_4(sc->sc_configtag, sc->sc_configaddr, tag + reg, data);
|
|
membar_sync();
|
|
}
|
|
|
|
/*
|
|
* interrupt mapping foo.
|
|
* XXX: how does this deal with multiple interrupts for a device?
|
|
*/
|
|
int
|
|
pci_intr_map(pa, ihp)
|
|
struct pci_attach_args *pa;
|
|
pci_intr_handle_t *ihp;
|
|
{
|
|
int rv, pin, line;
|
|
|
|
pin = pa->pa_intrpin;
|
|
line = pa->pa_intrline;
|
|
|
|
DPRINTF(SPDB_INTMAP, ("pci_intr_map: dev %u fn %u: ", pa->pa_device,
|
|
pa->pa_function));
|
|
/*
|
|
* XXX
|
|
* UltraSPARC PCI does not use PCI_INTERRUPT_REG, but we have
|
|
* used this space for our own purposes...
|
|
*/
|
|
DPRINTF(SPDB_INTR, ("pci_intr_map: tag %lx; line %d",
|
|
(long)pa->pa_intrtag, line));
|
|
|
|
if (line >= 0x40) {
|
|
*ihp = -1;
|
|
rv = 1;
|
|
goto out;
|
|
}
|
|
if (pin > 4)
|
|
panic("pci_intr_map: pin > 4");
|
|
|
|
(*ihp) = line & 0x3f;
|
|
rv = 0;
|
|
out:
|
|
DPRINTF(SPDB_INTR, ("; handle = %x; returning %d\n", (u_int)*ihp, rv));
|
|
return (rv);
|
|
}
|
|
|
|
const char *
|
|
pci_intr_string(pc, ih)
|
|
pci_chipset_tag_t pc;
|
|
pci_intr_handle_t ih;
|
|
{
|
|
static char str[16];
|
|
|
|
DPRINTF(SPDB_INTR, ("pci_intr_string: ih %u", ih));
|
|
if (ih < 0 || ih >= 0x40) {
|
|
printf("\n"); /* i'm *so* beautiful */
|
|
panic("pci_intr_string: bogus handle\n");
|
|
}
|
|
sprintf(str, "ipl %u", ih);
|
|
DPRINTF(SPDB_INTR, ("; returning %s\n", str));
|
|
|
|
return (str);
|
|
}
|
|
|
|
const struct evcnt *
|
|
pci_intr_evcnt(pc, ih)
|
|
pci_chipset_tag_t pc;
|
|
pci_intr_handle_t ih;
|
|
{
|
|
|
|
/* XXX for now, no evcnt parent reported */
|
|
return NULL;
|
|
}
|
|
|
|
void *
|
|
pci_intr_establish(pc, ih, level, func, arg)
|
|
pci_chipset_tag_t pc;
|
|
pci_intr_handle_t ih;
|
|
int level;
|
|
int (*func) __P((void *));
|
|
void *arg;
|
|
{
|
|
void *cookie;
|
|
struct psycho_pbm *pp = (struct psycho_pbm *)pc->cookie;
|
|
|
|
DPRINTF(SPDB_INTR, ("pci_intr_establish: ih %lu; level %d", (u_long)ih, level));
|
|
cookie = bus_intr_establish(pp->pp_memt, ih, level, 0, func, arg);
|
|
|
|
DPRINTF(SPDB_INTR, ("; returning handle %p\n", cookie));
|
|
return (cookie);
|
|
}
|
|
|
|
void
|
|
pci_intr_disestablish(pc, cookie)
|
|
pci_chipset_tag_t pc;
|
|
void *cookie;
|
|
{
|
|
|
|
DPRINTF(SPDB_INTR, ("pci_intr_disestablish: cookie %p\n", cookie));
|
|
|
|
/* XXX */
|
|
panic("can't disestablish PCI interrupts yet");
|
|
}
|