#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pcibios.c,v 1.40 2019/11/10 21:16:28 chs Exp $");
#include "opt_pcibios.h"
#include "opt_pcifixup.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>
#if defined(PCIBIOS_INTR_FIXUP) || defined(PCIBIOS_ADDR_FIXUP) || \
defined(PCIBIOS_BUS_FIXUP)
#error The options PCIBIOS_INTR_FIXUP, PCIBIOS_ADDR_FIXUP, and PCIBIOS_BUS_FIXUP have been obsoleted by PCI_INTR_FIXUP, PCI_ADDR_FIXUP, and PCI_BUS_FIXUP. Please adjust your kernel configuration file.
#endif
#ifdef PCI_INTR_FIXUP
#include <i386/pci/pci_intr_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(void);
int pcibios_get_status(uint32_t *, uint32_t *, uint32_t *,
uint32_t *, uint32_t *, uint32_t *, uint32_t *);
int pcibios_get_intr_routing(struct pcibios_intr_routing *,
int *, uint16_t *);
int pcibios_return_code(uint16_t, const char *);
void pcibios_print_exclirq(void);
#ifdef PCIBIOS_LIBRETTO_FIXUP
static void pcibios_fixup_pir_table(void);
static void pcibios_fixup_pir_table_mask(struct pcibios_linkmap *);
struct pcibios_linkmap pir_mask[] = {
{ 2, 0x0040 },
{ 7, 0x0080 },
{ 8, 0x0020 },
{ 0, 0x0000 }
};
#endif
#ifdef PCIBIOS_SHARP_MM20_FIXUP
static void pcibios_mm20_fixup(void);
#endif
#ifdef PCIINTR_DEBUG
void pcibios_print_pir_table(void);
#endif
#define PCI_IRQ_TABLE_START 0xf0000
#define PCI_IRQ_TABLE_END 0xfffff
void
pcibios_init(void)
{
struct bios32_entry_info ei;
uint32_t rev_maj, rev_min, mech1, mech2, scmech1, scmech2;
if (bios32_service(BIOS32_MAKESIG('$', 'P', 'C', 'I'),
&pcibios_entry, &ei) == 0) {
return;
}
if (pcibios_get_status(&rev_maj, &rev_min, &mech1, &mech2,
&scmech1, &scmech2, &pcibios_max_bus) != PCIBIOS_SUCCESS) {
return;
}
aprint_normal("PCI BIOS rev. %d.%d found at %#" PRIxPADDR "\n",
rev_maj, rev_min >> 4, ei.bei_entry);
aprint_verbose("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);
pci_mode_set(mech1 ? 1 : 2);
pcibios_present = 1;
pcibios_pir_init();
#ifdef PCI_INTR_FIXUP
if (pcibios_pir_table != NULL) {
int rv;
uint16_t pciirq;
rv = pci_intr_fixup(NULL, x86_bus_space_io, &pciirq);
switch (rv) {
case -1:
aprint_error("Warning: unable to fix up PCI interrupt "
"routing\n");
break;
case 1:
panic("pcibios_init: interrupt fixup failed");
break;
}
}
#endif
}
void
pcibios_pir_init(void)
{
char *devinfo;
paddr_t pa;
char *p;
unsigned char cksum;
uint16_t tablesize;
uint8_t rev_maj, rev_min;
int i;
for (pa = PCI_IRQ_TABLE_START; pa < PCI_IRQ_TABLE_END; pa += 16) {
p = (void *)ISA_HOLE_VADDR(pa);
if (*(int *)p != BIOS32_MAKESIG('$', 'P', 'I', 'R')) {
if (*(int *)p != BIOS32_MAKESIG('_', 'P', 'I', 'R'))
continue;
}
rev_min = *(p + 4);
rev_maj = *(p + 5);
tablesize = *(uint16_t *)(p + 6);
cksum = 0;
for (i = 0; i < tablesize; i++)
cksum += *(unsigned char *)(p + i);
aprint_normal(
"PCI IRQ Routing Table rev. %d.%d found at %#" PRIxPADDR
", size %d bytes (%d entries)\n", rev_maj, rev_min, pa,
tablesize, (tablesize - 32) / 16);
if (cksum != 0) {
aprint_error("pcibios_pir_init: bad IRQ table checksum\n");
continue;
}
if (tablesize < 32 || (tablesize % 16) != 0) {
aprint_error("pcibios_pir_init: bad IRQ table size\n");
continue;
}
if (rev_maj != 1 || rev_min != 0) {
aprint_error("pcibios_pir_init: unsupported IRQ table "
"version\n");
continue;
}
memcpy(&pcibios_pir_header, p, 32);
pcibios_pir_table = malloc(tablesize - 32, M_DEVBUF,
M_WAITOK);
memcpy(pcibios_pir_table, p + 32, tablesize - 32);
pcibios_pir_table_nentries = (tablesize - 32) / 16;
aprint_verbose("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) {
devinfo = malloc(256, M_DEVBUF, M_WAITOK);
pci_devinfo(pcibios_pir_header.compat_router,
0, 0, devinfo, 256);
aprint_verbose(" (%s compatible)", devinfo);
free(devinfo, M_DEVBUF);
}
aprint_verbose("\n");
pcibios_print_exclirq();
#ifdef PCIBIOS_LIBRETTO_FIXUP
pcibios_fixup_pir_table();
#endif
#ifdef PCIBIOS_SHARP_MM20_FIXUP
pcibios_mm20_fixup();
#endif
#ifdef PCIINTR_DEBUG
pcibios_print_pir_table();
#endif
return;
}
pcibios_pir_table_nentries = 32;
pcibios_pir_table = malloc(pcibios_pir_table_nentries *
sizeof(*pcibios_pir_table), M_DEVBUF, M_WAITOK);
if (pcibios_get_intr_routing(pcibios_pir_table,
&pcibios_pir_table_nentries,
&pcibios_pir_header.exclusive_irq) != PCIBIOS_SUCCESS) {
aprint_normal("No PCI IRQ Routing information available.\n");
free(pcibios_pir_table, M_DEVBUF);
pcibios_pir_table = NULL;
pcibios_pir_table_nentries = 0;
return;
}
aprint_verbose("PCI BIOS has %d Interrupt Routing table entries\n",
pcibios_pir_table_nentries);
pcibios_print_exclirq();
#ifdef PCIBIOS_LIBRETTO_FIXUP
pcibios_fixup_pir_table();
#endif
#ifdef PCIBIOS_SHARP_MM20_FIXUP
pcibios_mm20_fixup();
#endif
#ifdef PCIINTR_DEBUG
pcibios_print_pir_table();
#endif
}
int
pcibios_get_status(uint32_t *rev_maj, uint32_t *rev_min,
uint32_t *mech1, uint32_t *mech2, uint32_t *scmech1, uint32_t *scmech2,
uint32_t *maxbus)
{
uint16_t ax, bx, cx;
uint32_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);
*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_intr_routing *table,
int *nentries, uint16_t *exclirq)
{
uint16_t ax, bx;
int rv;
struct {
uint16_t size;
void *offset;
uint16_t segment;
} __packed args;
args.size = *nentries * sizeof(*table);
args.offset = (void *)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(uint16_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:
aprint_error("%s: unknown return code 0x%x\n", func, rv);
return (rv);
}
aprint_error("%s: %s\n", func, errstr);
return (rv);
}
void
pcibios_print_exclirq(void)
{
int i;
if (pcibios_pir_header.exclusive_irq) {
aprint_verbose("PCI Exclusive IRQs:");
for (i = 0; i < 16; i++) {
if (pcibios_pir_header.exclusive_irq & (1 << i))
aprint_verbose(" %d", i);
}
aprint_verbose("\n");
}
}
#ifdef PCIBIOS_LIBRETTO_FIXUP
static void
pcibios_fixup_pir_table(void)
{
struct pcibios_linkmap *m;
for (m = pir_mask; m->link != 0; m++)
pcibios_fixup_pir_table_mask(m);
}
void
pcibios_fixup_pir_table_mask(struct pcibios_linkmap *mask)
{
int i, j;
for (i = 0; i < pcibios_pir_table_nentries; i++) {
for (j = 0; j < 4; j++) {
if (pcibios_pir_table[i].linkmap[j].link == mask->link) {
pcibios_pir_table[i].linkmap[j].bitmap
&= mask->bitmap;
}
}
}
}
#endif
#ifdef PCIINTR_DEBUG
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);
}
}
}
#endif
#ifdef PCIBIOS_SHARP_MM20_FIXUP
#define BIOS32_START 0xe0000
#define BIOS32_SIZE 0x20000
static char pcibios_shadow[BIOS32_SIZE];
static struct bios32_entry pcibios_entry_shadow;
static void
pcibios_copy_bios(void)
{
uint8_t *bad_instr;
memcpy(pcibios_shadow, ISA_HOLE_VADDR(BIOS32_START), BIOS32_SIZE);
pcibios_entry_shadow = pcibios_entry;
pcibios_entry_shadow.offset =
(void*)((u_long)pcibios_shadow +
(u_long)pcibios_entry.offset -
(u_long)ISA_HOLE_VADDR(BIOS32_START));
bad_instr = (uint8_t *)pcibios_entry_shadow.offset + 0x499;
if (*bad_instr != 0x2e)
panic("bad bios");
bad_instr[0] = 0xb4; bad_instr[1] = 0xff;
bad_instr[2] = 0x90;
bad_instr[3] = 0x90;
bad_instr[4] = 0x90;
bad_instr[5] = 0x90;
bad_instr[6] = 0x90;
}
static int
pcibios_biosroute(int bus, int device, int func, int pin, int irq)
{
uint16_t ax, bx, cx;
int rv;
aprint_debug("pcibios_biosroute: b,d,f=%d,%d,%d pin=%x irq=%d\n",
bus, device, func, pin+0xa, irq);
bx = (bus << 8) | (device << 3) | func;
cx = (irq << 8) | (0xa + pin);
__asm volatile("lcall *(%%esi) ; \
jc 1f ; \
xor %%ah, %%ah ; \
1: movw %w1, %%ds ; \
movw %w1, %%es"
: "=a" (ax)
: "r" GSEL(GDATA_SEL, SEL_KPL), "0" (0xb10f),
"b" (bx), "c" (cx),
"S" (&pcibios_entry_shadow));
rv = pcibios_return_code(ax, "pcibios_biosroute");
return rv;
}
#define MM20_PCI_BUS 0
#define MM20_PCI_EHCI_DEV 15
#define MM20_PCI_EHCI_FUNC 3
#define MM20_PCI_EHCI_PIN 3
#define MM20_PCI_EHCI_INTR 11
#define MM20_PCI_ISA_DEV 3
#define MM20_PCI_ISA_FUNC 0
static void
pcibios_mm20_fixup(void)
{
pci_chipset_tag_t pc;
pcitag_t tag;
pcibios_copy_bios();
(void)pcibios_biosroute(MM20_PCI_BUS,
MM20_PCI_EHCI_DEV,
MM20_PCI_EHCI_FUNC,
MM20_PCI_EHCI_PIN,
MM20_PCI_EHCI_INTR);
pc = NULL;
tag = pci_make_tag(pc, MM20_PCI_BUS, MM20_PCI_EHCI_DEV,
MM20_PCI_EHCI_FUNC);
pci_conf_write(pc, tag, PCI_INTERRUPT_REG,
0x50000400 + MM20_PCI_EHCI_INTR);
tag = pci_make_tag(pc, MM20_PCI_BUS, MM20_PCI_ISA_DEV,
MM20_PCI_ISA_FUNC);
pci_conf_write(pc, tag, 0x58, 0xd87f5300);
pci_conf_write(pc, tag, 0x74, 0x00000009);
}
#endif