NetBSD/sys/arch/i386/pci/pcibios.c

540 lines
14 KiB
C

/* $NetBSD: pcibios.c,v 1.5 2000/08/01 05:23:59 uch Exp $ */
/*-
* Copyright (c) 1999 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
* NASA Ames Research Center.
*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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.
*/
/*
* Copyright (c) 1999, by UCHIYAMA Yasushi
* 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. The name of the developer 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
*/
/*
* Interface to the PCI BIOS and PCI Interrupt Routing table.
*/
#include "opt_pcibios.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <dev/isa/isareg.h>
#include <machine/isa_machdep.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#include <i386/pci/pcibios.h>
#ifdef PCIBIOS_INTR_FIXUP
#include <i386/pci/pci_intr_fixup.h>
#endif
#ifdef PCIBIOS_BUS_FIXUP
#include <i386/pci/pci_bus_fixup.h>
#endif
#ifdef PCIBIOS_ADDR_FIXUP
#include <i386/pci/pci_addr_fixup.h>
#endif
#include <machine/bios32.h>
#ifdef PCIBIOSVERBOSE
int pcibiosverbose = 1;
#endif
int pcibios_present;
struct pcibios_pir_header pcibios_pir_header;
struct pcibios_intr_routing *pcibios_pir_table;
int pcibios_pir_table_nentries;
int pcibios_max_bus;
struct bios32_entry pcibios_entry;
void pcibios_pir_init __P((void));
int pcibios_get_status __P((u_int32_t *, u_int32_t *, u_int32_t *,
u_int32_t *, u_int32_t *, u_int32_t *, u_int32_t *));
int pcibios_get_intr_routing __P((struct pcibios_intr_routing *,
int *, u_int16_t *));
int pcibios_return_code __P((u_int16_t, const char *));
void pcibios_print_exclirq __P((void));
#ifdef PCIINTR_DEBUG
void pcibios_print_pir_table __P((void));
#endif
#define PCI_IRQ_TABLE_START 0xf0000
#define PCI_IRQ_TABLE_END 0xfffff
void
pcibios_init()
{
struct bios32_entry_info ei;
u_int32_t rev_maj, rev_min, mech1, mech2, scmech1, scmech2;
if (bios32_service(BIOS32_MAKESIG('$', 'P', 'C', 'I'),
&pcibios_entry, &ei) == 0) {
/*
* No PCI BIOS found; will fall back on old
* mechanism.
*/
return;
}
/*
* We've located the PCI BIOS service; get some information
* about it.
*/
if (pcibios_get_status(&rev_maj, &rev_min, &mech1, &mech2,
&scmech1, &scmech2, &pcibios_max_bus) != PCIBIOS_SUCCESS) {
/*
* We can't use the PCI BIOS; will fall back on old
* mechanism.
*/
return;
}
printf("PCI BIOS rev. %d.%d found at 0x%lx\n", rev_maj, rev_min >> 4,
ei.bei_entry);
#ifdef PCIBIOSVERBOSE
printf("pcibios: config mechanism %s%s, special cycles %s%s, "
"last bus %d\n",
mech1 ? "[1]" : "[x]",
mech2 ? "[2]" : "[x]",
scmech1 ? "[1]" : "[x]",
scmech2 ? "[2]" : "[x]",
pcibios_max_bus);
#endif
/*
* The PCI BIOS tells us the config mechanism; fill it in now
* so that pci_mode_detect() doesn't have to look for it.
*/
pci_mode = mech1 ? 1 : 2;
pcibios_present = 1;
/*
* Find the PCI IRQ Routing table.
*/
pcibios_pir_init();
#ifdef PCIBIOS_INTR_FIXUP
if (pcibios_pir_table != NULL) {
int rv;
u_int16_t pciirq;
/*
* Fixup interrupt routing.
*/
rv = pci_intr_fixup(NULL, I386_BUS_SPACE_IO, &pciirq);
switch (rv) {
case -1:
/* Non-fatal error. */
printf("Warning: unable to fix up PCI interrupt "
"routing\n");
break;
case 1:
/* Fatal error. */
panic("pcibios_init: interrupt fixup failed");
break;
}
/*
* XXX Clear `pciirq' from the ISA interrupt allocation
* XXX mask.
*/
}
#endif
#ifdef PCIBIOS_BUS_FIXUP
pcibios_max_bus = pci_bus_fixup(NULL, 0);
#ifdef PCIBIOSVERBOSE
printf("PCI bus #%d is the last bus\n", pcibios_max_bus);
#endif
#endif
#ifdef PCIBIOS_ADDR_FIXUP
pci_addr_fixup(NULL, pcibios_max_bus);
#endif
}
void
pcibios_pir_init()
{
char devinfo[256];
paddr_t pa;
caddr_t p;
unsigned char cksum;
u_int16_t tablesize;
u_int8_t rev_maj, rev_min;
int i;
for (pa = PCI_IRQ_TABLE_START; pa < PCI_IRQ_TABLE_END; pa += 16) {
p = (caddr_t)ISA_HOLE_VADDR(pa);
if (*(int *)p != BIOS32_MAKESIG('$', 'P', 'I', 'R'))
continue;
rev_min = *(p + 4);
rev_maj = *(p + 5);
tablesize = *(u_int16_t *)(p + 6);
cksum = 0;
for (i = 0; i < tablesize; i++)
cksum += *(unsigned char *)(p + i);
printf("PCI IRQ Routing Table rev. %d.%d found at 0x%lx, "
"size %d bytes (%d entries)\n", rev_maj, rev_min, pa,
tablesize, (tablesize - 32) / 16);
if (cksum != 0) {
printf("pcibios_pir_init: bad IRQ table checksum\n");
continue;
}
if (tablesize < 32 || (tablesize % 16) != 0) {
printf("pcibios_pir_init: bad IRQ table size\n");
continue;
}
if (rev_maj != 1 || rev_min != 0) {
printf("pcibios_pir_init: unsupported IRQ table "
"version\n");
continue;
}
/*
* We can handle this table! Make a copy of it.
*/
memcpy(&pcibios_pir_header, p, 32);
pcibios_pir_table = malloc(tablesize - 32, M_DEVBUF,
M_NOWAIT);
if (pcibios_pir_table == NULL) {
printf("pcibios_pir_init: no memory for $PIR\n");
return;
}
memcpy(pcibios_pir_table, p + 32, tablesize - 32);
pcibios_pir_table_nentries = (tablesize - 32) / 16;
printf("PCI Interrupt Router at %03d:%02d:%01d",
pcibios_pir_header.router_bus,
PIR_DEVFUNC_DEVICE(pcibios_pir_header.router_devfunc),
PIR_DEVFUNC_FUNCTION(pcibios_pir_header.router_devfunc));
if (pcibios_pir_header.compat_router != 0) {
pci_devinfo(pcibios_pir_header.compat_router, 0, 0,
devinfo);
printf(" (%s)", devinfo);
}
printf("\n");
pcibios_print_exclirq();
#ifdef PCIINTR_DEBUG
pcibios_print_pir_table();
#endif
return;
}
/*
* If there was no PIR table found, try using the PCI BIOS
* Get Interrupt Routing call.
*
* XXX The interface to this call sucks; just allocate enough
* XXX room for 32 entries.
*/
pcibios_pir_table_nentries = 32;
pcibios_pir_table = malloc(pcibios_pir_table_nentries *
sizeof(*pcibios_pir_table), M_DEVBUF, M_NOWAIT);
if (pcibios_pir_table == NULL) {
printf("pcibios_pir_init: no memory for $PIR\n");
return;
}
if (pcibios_get_intr_routing(pcibios_pir_table,
&pcibios_pir_table_nentries,
&pcibios_pir_header.exclusive_irq) != PCIBIOS_SUCCESS) {
printf("No PCI IRQ Routing information available.\n");
free(pcibios_pir_table, M_DEVBUF);
pcibios_pir_table = NULL;
pcibios_pir_table_nentries = 0;
return;
}
printf("PCI BIOS has %d Interrupt Routing table entries\n",
pcibios_pir_table_nentries);
pcibios_print_exclirq();
#ifdef PCIINTR_DEBUG
pcibios_print_pir_table();
#endif
}
int
pcibios_get_status(rev_maj, rev_min, mech1, mech2, scmech1, scmech2, maxbus)
u_int32_t *rev_maj, *rev_min, *mech1, *mech2, *scmech1, *scmech2,
*maxbus;
{
u_int16_t ax, bx, cx;
u_int32_t edx;
int rv;
__asm __volatile("lcall (%%edi) ; \
jc 1f ; \
xor %%ah, %%ah ; \
1:"
: "=a" (ax), "=b" (bx), "=c" (cx), "=d" (edx)
: "0" (0xb101), "D" (&pcibios_entry));
rv = pcibios_return_code(ax, "pcibios_get_status");
if (rv != PCIBIOS_SUCCESS)
return (rv);
if (edx != BIOS32_MAKESIG('P', 'C', 'I', ' '))
return (PCIBIOS_SERVICE_NOT_PRESENT); /* XXX */
/*
* Fill in the various pieces if info we're looking for.
*/
*mech1 = ax & 1;
*mech2 = ax & (1 << 1);
*scmech1 = ax & (1 << 4);
*scmech2 = ax & (1 << 5);
*rev_maj = (bx >> 8) & 0xff;
*rev_min = bx & 0xff;
*maxbus = cx & 0xff;
return (PCIBIOS_SUCCESS);
}
int
pcibios_get_intr_routing(table, nentries, exclirq)
struct pcibios_intr_routing *table;
int *nentries;
u_int16_t *exclirq;
{
u_int16_t ax, bx;
int rv;
struct {
u_int16_t size;
caddr_t offset;
u_int16_t segment;
} __attribute__((__packed__)) args;
args.size = *nentries * sizeof(*table);
args.offset = (caddr_t)table;
args.segment = GSEL(GDATA_SEL, SEL_KPL);
memset(table, 0, args.size);
__asm __volatile("lcall (%%esi) ; \
jc 1f ; \
xor %%ah, %%ah ; \
1: movw %w2, %%ds ; \
movw %w2, %%es"
: "=a" (ax), "=b" (bx)
: "r" GSEL(GDATA_SEL, SEL_KPL), "0" (0xb10e), "1" (0),
"D" (&args), "S" (&pcibios_entry));
rv = pcibios_return_code(ax, "pcibios_get_intr_routing");
if (rv != PCIBIOS_SUCCESS)
return (rv);
*nentries = args.size / sizeof(*table);
*exclirq = bx;
return (PCIBIOS_SUCCESS);
}
int
pcibios_return_code(ax, func)
u_int16_t ax;
const char *func;
{
const char *errstr;
int rv = ax >> 8;
switch (rv) {
case PCIBIOS_SUCCESS:
return (PCIBIOS_SUCCESS);
case PCIBIOS_SERVICE_NOT_PRESENT:
errstr = "service not present";
break;
case PCIBIOS_FUNCTION_NOT_SUPPORTED:
errstr = "function not supported";
break;
case PCIBIOS_BAD_VENDOR_ID:
errstr = "bad vendor ID";
break;
case PCIBIOS_DEVICE_NOT_FOUND:
errstr = "device not found";
break;
case PCIBIOS_BAD_REGISTER_NUMBER:
errstr = "bad register number";
break;
case PCIBIOS_SET_FAILED:
errstr = "set failed";
break;
case PCIBIOS_BUFFER_TOO_SMALL:
errstr = "buffer too small";
break;
default:
printf("%s: unknown return code 0x%x\n", func, rv);
return (rv);
}
printf("%s: %s\n", func, errstr);
return (rv);
}
void
pcibios_print_exclirq()
{
int i;
if (pcibios_pir_header.exclusive_irq) {
printf("PCI Exclusive IRQs:");
for (i = 0; i < 16; i++) {
if (pcibios_pir_header.exclusive_irq & (1 << i))
printf(" %d", i);
}
printf("\n");
}
}
#ifdef PCIINTR_DEBUG
void
pcibios_print_pir_table()
{
int i, j;
for (i = 0; i < pcibios_pir_table_nentries; i++) {
printf("PIR Entry %d:\n", i);
printf("\tBus: %d Device: %d\n",
pcibios_pir_table[i].bus,
PIR_DEVFUNC_DEVICE(pcibios_pir_table[i].device));
for (j = 0; j < 4; j++) {
printf("\t\tINT%c: link 0x%02x bitmap 0x%04x\n",
'A' + j,
pcibios_pir_table[i].linkmap[j].link,
pcibios_pir_table[i].linkmap[j].bitmap);
}
}
}
#endif
void
pci_device_foreach(pc, maxbus, func)
pci_chipset_tag_t pc;
int maxbus;
void (*func) __P((pci_chipset_tag_t, pcitag_t));
{
const struct pci_quirkdata *qd;
int bus, device, function, maxdevs, nfuncs;
pcireg_t id, bhlcr;
pcitag_t tag;
for (bus = 0; bus <= maxbus; bus++) {
maxdevs = pci_bus_maxdevs(pc, bus);
for (device = 0; device < maxdevs; device++) {
tag = pci_make_tag(pc, bus, device, 0);
id = pci_conf_read(pc, tag, PCI_ID_REG);
/* Invalid vendor ID value? */
if (PCI_VENDOR(id) == PCI_VENDOR_INVALID)
continue;
/* XXX Not invalid, but we've done this ~forever. */
if (PCI_VENDOR(id) == 0)
continue;
qd = pci_lookup_quirkdata(PCI_VENDOR(id),
PCI_PRODUCT(id));
bhlcr = pci_conf_read(pc, tag, PCI_BHLC_REG);
if (PCI_HDRTYPE_MULTIFN(bhlcr) ||
(qd != NULL &&
(qd->quirks & PCI_QUIRK_MULTIFUNCTION) != 0))
nfuncs = 8;
else
nfuncs = 1;
for (function = 0; function < nfuncs; function++) {
tag = pci_make_tag(pc, bus, device, function);
id = pci_conf_read(pc, tag, PCI_ID_REG);
/* Invalid vendor ID value? */
if (PCI_VENDOR(id) == PCI_VENDOR_INVALID)
continue;
/*
* XXX Not invalid, but we've done this
* ~forever.
*/
if (PCI_VENDOR(id) == 0)
continue;
(*func)(pc, tag);
}
}
}
}