#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <dev/isa/isareg.h>
#include <i386/isa/isa_machdep.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#include <i386/pci/pcibiosvar.h>
#include <machine/biosvar.h>
int pcibios_flags;
struct pcibios_pir_header pcibios_pir_header;
struct pcibios_intr_routing *pcibios_pir_table;
int pcibios_pir_table_nentries;
int pcibios_flags = 0;
struct bios32_entry pcibios_entry;
struct bios32_entry_info pcibios_entry_info;
struct pcibios_intr_routing *pcibios_pir_init(struct pcibios_softc *);
int pcibios_get_status(struct pcibios_softc *,
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(struct pcibios_softc *,
struct pcibios_intr_routing *, int *, u_int16_t *);
int pcibios_return_code(struct pcibios_softc *, u_int16_t, const char *);
void pcibios_print_exclirq(struct pcibios_softc *);
void pcibios_print_pir_table(void);
#define PCI_IRQ_TABLE_START 0xf0000
#define PCI_IRQ_TABLE_END 0xfffff
struct cfdriver pcibios_cd = {
NULL, "pcibios", DV_DULL
};
int pcibiosprobe(struct device *, void *, void *);
void pcibiosattach(struct device *, struct device *, void *);
const struct cfattach pcibios_ca = {
sizeof(struct pcibios_softc), pcibiosprobe, pcibiosattach
};
int
pcibiosprobe(struct device *parent, void *match, void *aux)
{
struct bios_attach_args *ba = aux;
u_int32_t rev_maj, rev_min, mech1, mech2, scmech1, scmech2, maxbus;
int rv;
if (strcmp(ba->ba_name, "pcibios"))
return 0;
rv = bios32_service(PCIBIOS_SIGNATURE, &pcibios_entry,
&pcibios_entry_info);
PCIBIOS_PRINTV(("pcibiosprobe: 0x%hx:0x%x at 0x%x[0x%x]\n",
pcibios_entry.segment, pcibios_entry.offset,
pcibios_entry_info.bei_base, pcibios_entry_info.bei_size));
return rv &&
pcibios_get_status(NULL, &rev_maj, &rev_min, &mech1, &mech2,
&scmech1, &scmech2, &maxbus) == PCIBIOS_SUCCESS;
}
void
pcibiosattach(struct device *parent, struct device *self, void *aux)
{
struct pcibios_softc *sc = (struct pcibios_softc *)self;
u_int32_t rev_maj, rev_min, mech1, mech2, scmech1, scmech2;
pcibios_flags = sc->sc_dev.dv_cfdata->cf_flags;
pcibios_get_status((struct pcibios_softc *)self, &rev_maj,
&rev_min, &mech1, &mech2,
&scmech1, &scmech2, &sc->max_bus);
printf(": rev %d.%d @ 0x%x/0x%x\n",
rev_maj, rev_min >> 4, pcibios_entry_info.bei_base,
pcibios_entry_info.bei_size);
PCIBIOS_PRINTV(("%s: config mechanism %s%s, special cycles %s%s, "
"last bus %d\n", sc->sc_dev.dv_xname,
mech1 ? "[1]" : "[x]", mech2 ? "[2]" : "[x]",
scmech1 ? "[1]" : "[x]", scmech2 ? "[2]" : "[x]", sc->max_bus));
pci_mode = mech1 ? 1 : 2;
if (!(pcibios_flags & PCIBIOS_INTR_FIXUP) &&
pcibios_pir_init((struct pcibios_softc *)self) != NULL) {
int rv;
rv = pci_intr_fixup(sc, NULL, I386_BUS_SPACE_IO);
switch (rv) {
case -1:
printf("%s: Warning, unable to fix up PCI interrupt "
"routing\n", sc->sc_dev.dv_xname);
break;
case 1:
printf("%s: interrupt fixup failed\n", sc->sc_dev.dv_xname);
return;
}
}
if (!(pcibios_flags & PCIBIOS_BUS_FIXUP)) {
sc->max_bus = pci_bus_fixup(NULL, 0);
printf("%s: PCI bus #%d is the last bus\n",
sc->sc_dev.dv_xname, sc->max_bus);
}
if (!(pcibios_flags & PCIBIOS_ADDR_FIXUP))
pci_addr_fixup(sc, NULL, sc->max_bus);
bios32_cleanup();
}
struct pcibios_intr_routing *
pcibios_pir_init(struct pcibios_softc *sc)
{
paddr_t pa;
pcibios_pir_table = NULL;
for (pa = PCI_IRQ_TABLE_START; pa < PCI_IRQ_TABLE_END; pa += 16) {
u_int8_t *p, cksum;
struct pcibios_pir_header *pirh;
int i;
p = ISA_HOLE_VADDR(pa);
pirh = (struct pcibios_pir_header *)p;
if (pirh->signature != BIOS32_MAKESIG('$', 'P', 'I', 'R') &&
pirh->signature != BIOS32_MAKESIG('_', 'P', 'I', 'R'))
continue;
if (pirh->tablesize < sizeof(*pirh))
continue;
cksum = 0;
for (i = 0; i < pirh->tablesize; i++)
cksum += p[i];
printf("%s: PCI IRQ Routing Table rev %d.%d @ 0x%lx/%d "
"(%zd entries)\n", sc->sc_dev.dv_xname,
pirh->version >> 8, pirh->version & 0xff, pa,
pirh->tablesize, (pirh->tablesize - sizeof(*pirh)) / 16);
if (cksum != 0) {
printf("%s: bad IRQ table checksum\n",
sc->sc_dev.dv_xname);
continue;
}
if (pirh->tablesize % 16 != 0) {
printf("%s: bad IRQ table size\n", sc->sc_dev.dv_xname);
continue;
}
if (pirh->version != 0x0100) {
printf("%s: unsupported IRQ table version\n",
sc->sc_dev.dv_xname);
continue;
}
pcibios_pir_header = *pirh;
pcibios_pir_table =
malloc(pirh->tablesize - sizeof(*pirh), M_DEVBUF, M_NOWAIT);
if (pcibios_pir_table == NULL) {
printf("%s: no memory for $PIR\n", sc->sc_dev.dv_xname);
return NULL;
}
bcopy(p + sizeof(*pirh), pcibios_pir_table,
pirh->tablesize - sizeof(*pirh));
pcibios_pir_table_nentries =
(pirh->tablesize - sizeof(*pirh)) / 16;
}
if (pcibios_pir_table == NULL) {
pcibios_pir_table_nentries = 32;
pcibios_pir_table = mallocarray(pcibios_pir_table_nentries,
sizeof(*pcibios_pir_table), M_DEVBUF, M_NOWAIT);
if (pcibios_pir_table == NULL) {
printf("%s: no memory for $PIR\n", sc->sc_dev.dv_xname);
return NULL;
}
if (pcibios_get_intr_routing(sc, pcibios_pir_table,
&pcibios_pir_table_nentries,
&pcibios_pir_header.exclusive_irq) != PCIBIOS_SUCCESS) {
printf("%s: PCI IRQ Routing information unavailable.\n",
sc->sc_dev.dv_xname);
free(pcibios_pir_table, M_DEVBUF,
pcibios_pir_table_nentries *
sizeof(*pcibios_pir_table));
pcibios_pir_table = NULL;
pcibios_pir_table_nentries = 0;
return NULL;
}
printf("%s: PCI BIOS has %d Interrupt Routing table entries\n",
sc->sc_dev.dv_xname, pcibios_pir_table_nentries);
}
pcibios_print_exclirq(sc);
if (pcibios_flags & PCIBIOS_INTRDEBUG)
pcibios_print_pir_table();
return pcibios_pir_table;
}
int
pcibios_get_status(struct pcibios_softc *sc, u_int32_t *rev_maj,
u_int32_t *rev_min, u_int32_t *mech1, u_int32_t *mech2, u_int32_t *scmech1,
u_int32_t *scmech2, u_int32_t *maxbus)
{
u_int32_t ax, bx, cx, edx;
int rv;
__asm volatile("pushl %%es\n\t"
"pushl %%ds\n\t"
"movw 4(%%edi), %%cx\n\t"
"movl %%ecx, %%ds\n\t"
"lcall *%%cs:(%%edi)\n\t"
"pop %%ds\n\t"
"pop %%es\n\t"
"jc 1f\n\t"
"xor %%ah, %%ah\n"
"1:"
: "=a" (ax), "=b" (bx), "=c" (cx), "=d" (edx)
: "0" (0xb101), "D" (&pcibios_entry)
: "cc", "memory");
rv = pcibios_return_code(sc, ax, "pcibios_get_status");
if (rv != PCIBIOS_SUCCESS)
return (rv);
if (edx != BIOS32_MAKESIG('P', 'C', 'I', ' '))
return (PCIBIOS_SERVICE_NOT_PRESENT);
*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(struct pcibios_softc *sc,
struct pcibios_intr_routing *table, int *nentries, u_int16_t *exclirq)
{
u_int32_t ax, bx;
int rv;
struct {
u_int16_t size;
u_int32_t offset;
u_int16_t segment;
} __packed args;
args.size = *nentries * sizeof(*table);
args.offset = (u_int32_t)table;
args.segment = GSEL(GDATA_SEL, SEL_KPL);
memset(table, 0, args.size);
__asm volatile("pushl %%es\n\t"
"pushl %%ds\n\t"
"movw 4(%%esi), %%cx\n\t"
"movl %%ecx, %%ds\n\t"
"lcall *%%cs:(%%esi)\n\t"
"popl %%ds\n\t"
"popl %%es\n\t"
"jc 1f\n\t"
"xor %%ah, %%ah\n"
"1:\n"
: "=a" (ax), "=b" (bx)
: "0" (0xb10e), "1" (0), "D" (&args), "S" (&pcibios_entry)
: "%ecx", "%edx", "cc", "memory");
rv = pcibios_return_code(sc, 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(struct pcibios_softc *sc, u_int16_t ax, const char *func)
{
const char *errstr;
int rv = ax >> 8;
char *nam;
if (sc)
nam = sc->sc_dev.dv_xname;
else
nam = "pcibios0";
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: %s - unknown return code 0x%x\n",
nam, func, rv);
return (rv);
}
printf("%s: %s - %s\n", nam, func, errstr);
return (rv);
}
void
pcibios_print_exclirq(struct pcibios_softc *sc)
{
int i;
if (pcibios_pir_header.exclusive_irq) {
printf("%s: PCI Exclusive IRQs:", sc->sc_dev.dv_xname);
for (i = 0; i < 16; i++) {
if (pcibios_pir_header.exclusive_irq & (1 << i))
printf(" %d", i);
}
printf("\n");
}
}
void
pcibios_print_pir_table(void)
{
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);
}
}
}
void
pci_device_foreach(struct pcibios_softc *sc, pci_chipset_tag_t pc, int maxbus,
void (*func)(struct pcibios_softc *, 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);
if (PCI_VENDOR(id) == PCI_VENDOR_INVALID)
continue;
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);
if (PCI_VENDOR(id) == PCI_VENDOR_INVALID)
continue;
if (PCI_VENDOR(id) == 0)
continue;
(*func)(sc, pc, tag);
}
}
}
}