447 lines
12 KiB
C
447 lines
12 KiB
C
/* $NetBSD: pci_machdep.c,v 1.6 2000/05/17 09:25:58 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.
|
|
*/
|
|
|
|
#undef DEBUG
|
|
#define DEBUG
|
|
|
|
#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>
|
|
|
|
#include <vm/vm.h>
|
|
#include <vm/vm_kern.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 <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,
|
|
};
|
|
|
|
/* commonly used */
|
|
#define TAG2BUS(tag) ((tag) >> 16) & 0xff;
|
|
#define TAG2DEV(tag) ((tag) >> 11) & 0x1f;
|
|
#define TAG2FN(tag) ((tag) >> 8) & 0x7;
|
|
|
|
/*
|
|
* 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;
|
|
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));
|
|
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;
|
|
DPRINTF(SPDB_INTFIX, ("... BINGO! ..."));
|
|
|
|
/*
|
|
* OK! we found match. pull out the old interrupt
|
|
* register, patch in the new value, and put it back.
|
|
*/
|
|
intr = pci_conf_read(pc, tag, PCI_INTERRUPT_REG);
|
|
DPRINTF(SPDB_INTFIX, ("\n\t ; read %x from intreg", intr));
|
|
|
|
intr = (intr & ~PCI_INTERRUPT_LINE_MASK) |
|
|
(pp->pp_intmap[i].child_intr & PCI_INTERRUPT_LINE_MASK);
|
|
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\n\t ; gonna write %x to intreg", intr));
|
|
|
|
pci_conf_write(pc, tag, PCI_INTERRUPT_REG, intr);
|
|
DPRINTF((SPDB_INTFIX|SPDB_INTMAP), ("\n\t ; reread %x from intreg", intr));
|
|
break;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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_A ||
|
|
sc->sc_mode == PSYCHO_MODE_PSYCHO_B) {
|
|
/*
|
|
* make sure we are reading our own bus
|
|
*/
|
|
/* XXX??? */
|
|
panic("confaddr_ok: can't do SUNW,psycho yet");
|
|
}
|
|
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],
|
|
sc->sc_configaddr + tag + reg, (int)tag + reg));
|
|
|
|
if (confaddr_ok(sc, tag) == 0) {
|
|
val = (pcireg_t)~0;
|
|
} else {
|
|
#if 0
|
|
u_int32_t data;
|
|
|
|
data = probeget(sc->sc_configaddr + tag + reg,
|
|
bus_type_asi[sc->sc_configtag->type], 4);
|
|
if (data == -1)
|
|
val = (pcireg_t)~0;
|
|
else
|
|
val = (pcireg_t)data;
|
|
#else
|
|
membar_sync();
|
|
val = bus_space_read_4(sc->sc_configtag, sc->sc_configaddr,
|
|
tag + reg);
|
|
membar_sync();
|
|
#endif
|
|
}
|
|
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 %ld; reg %d; data %d; ", (long)tag, reg, (int)data));
|
|
DPRINTF(SPDB_CONF, ("asi = %x; readaddr = %qx (offset = %x)\n",
|
|
bus_type_asi[sc->sc_configtag->type],
|
|
sc->sc_configaddr + tag + reg, (int)tag + reg));
|
|
|
|
if (confaddr_ok(sc, tag) == 0)
|
|
panic("pci_conf_write: bad addr");
|
|
|
|
#if 0
|
|
probeset(sc->sc_configaddr + tag + reg,
|
|
bus_type_asi[sc->sc_configtag->type],
|
|
4, data);
|
|
#else
|
|
membar_sync();
|
|
bus_space_write_4(sc->sc_configtag, sc->sc_configaddr, tag + reg, data);
|
|
membar_sync();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* interrupt mapping foo.
|
|
*/
|
|
int
|
|
pci_intr_map(pc, tag, pin, line, ihp)
|
|
pci_chipset_tag_t pc;
|
|
pcitag_t tag;
|
|
int pin;
|
|
int line;
|
|
pci_intr_handle_t *ihp;
|
|
{
|
|
int rv;
|
|
|
|
/*
|
|
* XXX
|
|
* UltraSPARC IIi 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; pin %d; line %d", (long)tag, pin, line));
|
|
#if 1
|
|
if (line == 255) {
|
|
*ihp = -1;
|
|
rv = 1;
|
|
goto out;
|
|
}
|
|
#endif
|
|
if (pin > 4)
|
|
panic("pci_intr_map: pin > 4");
|
|
|
|
rv = psycho_intr_map(tag, pin, line, ihp);
|
|
|
|
out:
|
|
DPRINTF(SPDB_INTR, ("; handle = %d; returning %d\n", (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 > 0x32) {
|
|
printf("\n"); /* i'm *so* beautiful */
|
|
panic("pci_intr_string: bogus handle\n");
|
|
}
|
|
sprintf(str, "vector %u", ih);
|
|
DPRINTF(SPDB_INTR, ("; returning %s\n", str));
|
|
|
|
return (str);
|
|
}
|
|
|
|
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, 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");
|
|
}
|