47ef6a5146
bus_space_handle_t now holds an address and two ASIs, one for normal accesses and one for streaming accesses. This allows to map individual handles different ways, so some can use MMU bypass accesses and others use virtual addresses. bus_space_map() will now create handles that use bypass accesses unles BUS_SPACE_MAP_LINEAR is passed in. So only pass in BUS_SPACE_MAP_LINEAR if you absolutely *need* to use bus_space_vaddr(). This removes at least one extra level of indirection and should reduce TLB misses. 32-bit kernels have problems accessing 64-bit addresses, so they always use virtual addresses.
496 lines
12 KiB
C
496 lines
12 KiB
C
/* $NetBSD: pci_machdep.c,v 1.26 2002/03/20 18:54:47 eeh 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
|
|
#define SPDB_PROBE 0x20
|
|
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 <machine/openfirm.h>
|
|
|
|
#include <dev/pci/pcivar.h>
|
|
#include <dev/pci/pcireg.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;
|
|
{
|
|
/* Don't do nothing */
|
|
}
|
|
|
|
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 reg;
|
|
int node, len, device, i = 0;
|
|
u_int32_t done = 0;
|
|
#ifdef DEBUG
|
|
char name[80];
|
|
#endif
|
|
|
|
node = pc->curnode;
|
|
#ifdef DEBUG
|
|
if (sparc_pci_debug & SPDB_PROBE) {
|
|
OF_getprop(node, "name", &name, sizeof(name));
|
|
printf("pci_bus_devorder: curnode %x %s\n", node, name);
|
|
}
|
|
#endif
|
|
/*
|
|
* Initially, curnode is the root of the pci tree. As we
|
|
* attach bridges, curnode should be set to that of the bridge.
|
|
*/
|
|
for (node = OF_child(node); node; node = OF_peer(node)) {
|
|
len = OF_getproplen(node, "reg");
|
|
if (len < sizeof(reg))
|
|
continue;
|
|
if (OF_getprop(node, "reg", (void *)®, sizeof(reg)) != len)
|
|
panic("pci_probe_bus: OF_getprop len botch");
|
|
|
|
device = OFW_PCI_PHYS_HI_DEVICE(reg.phys_hi);
|
|
|
|
if (done & (1 << device))
|
|
continue;
|
|
|
|
devs[i++] = device;
|
|
done |= 1 << device;
|
|
#ifdef DEBUG
|
|
if (sparc_pci_debug & SPDB_PROBE) {
|
|
OF_getprop(node, "name", &name, sizeof(name));
|
|
printf("pci_bus_devorder: adding %x %s\n", node, name);
|
|
}
|
|
#endif
|
|
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 reg;
|
|
int node, len, i = 0;
|
|
#ifdef DEBUG
|
|
char name[80];
|
|
#endif
|
|
|
|
node = pc->curnode;
|
|
#ifdef DEBUG
|
|
if (sparc_pci_debug & SPDB_PROBE) {
|
|
OF_getprop(node, "name", &name, sizeof(name));
|
|
printf("pci_bus_funcorder: curnode %x %s\n", node, name);
|
|
}
|
|
#endif
|
|
/*
|
|
* Initially, curnode is the root of the pci tree. As we
|
|
* attach bridges, curnode should be set to that of the bridge.
|
|
*
|
|
* Note this search is almost exactly the same as pci_bus_devorder()'s,
|
|
* except that we limit the search to only those with a matching
|
|
* "device" number.
|
|
*/
|
|
for (node = OF_child(node); node; node = OF_peer(node)) {
|
|
len = OF_getproplen(node, "reg");
|
|
if (len < sizeof(reg))
|
|
continue;
|
|
if (OF_getprop(node, "reg", (void *)®, sizeof(reg)) != len)
|
|
panic("pci_probe_bus: OF_getprop len botch");
|
|
|
|
if (device != OFW_PCI_PHYS_HI_DEVICE(reg.phys_hi))
|
|
continue;
|
|
|
|
funcs[i++] = OFW_PCI_PHYS_HI_FUNCTION(reg.phys_hi);
|
|
#ifdef DEBUG
|
|
if (sparc_pci_debug & SPDB_PROBE) {
|
|
OF_getprop(node, "name", &name, sizeof(name));
|
|
printf("pci_bus_funcorder: adding %x %s\n", node, name);
|
|
}
|
|
#endif
|
|
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;
|
|
{
|
|
struct ofw_pci_register reg;
|
|
pcitag_t tag;
|
|
int busrange[2];
|
|
int node, len;
|
|
#ifdef DEBUG
|
|
char name[80];
|
|
bzero(name, sizeof(name));
|
|
#endif
|
|
|
|
/*
|
|
* Hunt for the node that corresponds to this device
|
|
*
|
|
* We could cache this info in an array in the parent
|
|
* device... except then we have problems with devices
|
|
* attached below pci-pci bridges, and we would need to
|
|
* add special code to the pci-pci bridge to cache this
|
|
* info.
|
|
*/
|
|
|
|
tag = PCITAG_CREATE(-1, b, d, f);
|
|
node = pc->rootnode;
|
|
/*
|
|
* First make sure we're on the right bus. If our parent
|
|
* has a bus-range property and we're not in the range,
|
|
* then we're obviously on the wrong bus. So go up one
|
|
* level.
|
|
*/
|
|
#ifdef DEBUG
|
|
if (sparc_pci_debug & SPDB_PROBE) {
|
|
OF_getprop(node, "name", &name, sizeof(name));
|
|
printf("curnode %x %s\n", node, name);
|
|
}
|
|
#endif
|
|
#if 0
|
|
while ((OF_getprop(OF_parent(node), "bus-range", (void *)&busrange,
|
|
sizeof(busrange)) == sizeof(busrange)) &&
|
|
(b < busrange[0] || b > busrange[1])) {
|
|
/* Out of range, go up one */
|
|
node = OF_parent(node);
|
|
#ifdef DEBUG
|
|
if (sparc_pci_debug & SPDB_PROBE) {
|
|
OF_getprop(node, "name", &name, sizeof(name));
|
|
printf("going up to node %x %s\n", node, name);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
/*
|
|
* Now traverse all peers until we find the node or we find
|
|
* the right bridge.
|
|
*
|
|
* XXX We go up one and down one to make sure nobody's missed.
|
|
* but this should not be necessary.
|
|
*/
|
|
for (node = ((node)); node; node = OF_peer(node)) {
|
|
|
|
#ifdef DEBUG
|
|
if (sparc_pci_debug & SPDB_PROBE) {
|
|
OF_getprop(node, "name", &name, sizeof(name));
|
|
printf("checking node %x %s\n", node, name);
|
|
}
|
|
#endif
|
|
|
|
#if 1
|
|
/*
|
|
* Check for PCI-PCI bridges. If the device we want is
|
|
* in the bus-range for that bridge, work our way down.
|
|
*/
|
|
while ((OF_getprop(node, "bus-range", (void *)&busrange,
|
|
sizeof(busrange)) == sizeof(busrange)) &&
|
|
(b >= busrange[0] && b <= busrange[1])) {
|
|
/* Go down 1 level */
|
|
node = OF_child(node);
|
|
#ifdef DEBUG
|
|
if (sparc_pci_debug & SPDB_PROBE) {
|
|
OF_getprop(node, "name", &name, sizeof(name));
|
|
printf("going down to node %x %s\n",
|
|
node, name);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
/*
|
|
* We only really need the first `reg' property.
|
|
*
|
|
* For simplicity, we'll query the `reg' when we
|
|
* need it. Otherwise we could malloc() it, but
|
|
* that gets more complicated.
|
|
*/
|
|
len = OF_getproplen(node, "reg");
|
|
if (len < sizeof(reg))
|
|
continue;
|
|
if (OF_getprop(node, "reg", (void *)®, sizeof(reg)) != len)
|
|
panic("pci_probe_bus: OF_getprop len botch");
|
|
|
|
if (b != OFW_PCI_PHYS_HI_BUS(reg.phys_hi))
|
|
continue;
|
|
if (d != OFW_PCI_PHYS_HI_DEVICE(reg.phys_hi))
|
|
continue;
|
|
if (f != OFW_PCI_PHYS_HI_FUNCTION(reg.phys_hi))
|
|
continue;
|
|
|
|
/* Got a match */
|
|
tag = PCITAG_CREATE(node, b, d, f);
|
|
|
|
/*
|
|
* Record the node. This has two effects:
|
|
*
|
|
* 1) We don't have to search as far.
|
|
* 2) pci_bus_devorder will scan the right bus.
|
|
*/
|
|
pc->curnode = node;
|
|
|
|
/* Enable all the different spaces for this device */
|
|
pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG,
|
|
PCI_COMMAND_MEM_ENABLE|PCI_COMMAND_MASTER_ENABLE|
|
|
PCI_COMMAND_IO_ENABLE);
|
|
DPRINTF(SPDB_PROBE, ("found node %x %s\n", node, name));
|
|
return (tag);
|
|
}
|
|
/* No device found -- return a dead tag */
|
|
return (tag);
|
|
}
|
|
|
|
/* 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 = (pcireg_t)~0;
|
|
|
|
DPRINTF(SPDB_CONF, ("pci_conf_read: tag %lx reg %x ",
|
|
(long)tag, reg));
|
|
if (PCITAG_NODE(tag) != -1) {
|
|
DPRINTF(SPDB_CONF, ("asi=%x addr=%qx (offset=%x) ...",
|
|
sc->sc_configaddr._asi,
|
|
(long long)(sc->sc_configaddr._ptr +
|
|
PCITAG_OFFSET(tag) + reg),
|
|
(int)PCITAG_OFFSET(tag) + reg));
|
|
|
|
val = bus_space_read_4(sc->sc_configtag, sc->sc_configaddr,
|
|
PCITAG_OFFSET(tag) + reg);
|
|
}
|
|
#ifdef DEBUG
|
|
else DPRINTF(SPDB_CONF, ("pci_conf_read: bogus pcitag %x\n",
|
|
(int)PCITAG_OFFSET(tag)));
|
|
#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 %lx; reg %x; data %x; ",
|
|
(long)PCITAG_OFFSET(tag), reg, (int)data));
|
|
DPRINTF(SPDB_CONF, ("asi = %x; readaddr = %qx (offset = %x)\n",
|
|
sc->sc_configaddr._asi,
|
|
(long long)(sc->sc_configaddr._ptr + PCITAG_OFFSET(tag) + reg),
|
|
(int)PCITAG_OFFSET(tag) + reg));
|
|
|
|
/* If we don't know it, just punt it. */
|
|
if (PCITAG_NODE(tag) == -1) {
|
|
DPRINTF(SPDB_CONF, ("pci_conf_write: bad addr"));
|
|
return;
|
|
}
|
|
|
|
bus_space_write_4(sc->sc_configtag, sc->sc_configaddr,
|
|
PCITAG_OFFSET(tag) + reg, data);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
{
|
|
pcitag_t tag = pa->pa_tag;
|
|
int interrupts;
|
|
int len, node = PCITAG_NODE(tag);
|
|
char devtype[30];
|
|
|
|
len = OF_getproplen(node, "interrupts");
|
|
if (len < sizeof(interrupts)) {
|
|
DPRINTF(SPDB_INTMAP,
|
|
("pci_intr_map: interrupts len %d too small\n", len));
|
|
return (ENODEV);
|
|
}
|
|
if (OF_getprop(node, "interrupts", (void *)&interrupts,
|
|
sizeof(interrupts)) != len) {
|
|
DPRINTF(SPDB_INTMAP,
|
|
("pci_intr_map: could not read interrupts\n"));
|
|
return (ENODEV);
|
|
}
|
|
|
|
if (OF_mapintr(node, &interrupts, sizeof(interrupts),
|
|
sizeof(interrupts)) < 0) {
|
|
printf("OF_mapintr failed\n");
|
|
}
|
|
/* Try to find an IPL for this type of device. */
|
|
if (OF_getprop(node, "device_type", &devtype, sizeof(devtype)) > 0) {
|
|
for (len = 0; intrmap[len].in_class; len++)
|
|
if (strcmp(intrmap[len].in_class, devtype) == 0) {
|
|
interrupts |= INTLEVENCODE(intrmap[len].in_lev);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* XXXX -- we use the ino. What if there is a valid IGN? */
|
|
*ihp = interrupts;
|
|
return (0);
|
|
}
|
|
|
|
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));
|
|
sprintf(str, "ivec %x", 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");
|
|
}
|