diff options
| author | John Baldwin <jhb@FreeBSD.org> | 2004-02-18 22:40:23 +0000 |
|---|---|---|
| committer | John Baldwin <jhb@FreeBSD.org> | 2004-02-18 22:40:23 +0000 |
| commit | 2e41ba54d62fdecc8e28b7b45a537c739110658f (patch) | |
| tree | 1d0b0f6e8f61c8700c371e9e3dbd61c52c200ba4 /sys | |
| parent | 0aef5c8497542451afaf932c704bb2a684491ddc (diff) | |
Notes
Diffstat (limited to 'sys')
| -rw-r--r-- | sys/i386/pci/pci_pir.c | 1044 |
1 files changed, 440 insertions, 604 deletions
diff --git a/sys/i386/pci/pci_pir.c b/sys/i386/pci/pci_pir.c index 0e3b8c3ff15d..ea924a87960b 100644 --- a/sys/i386/pci/pci_pir.c +++ b/sys/i386/pci/pci_pir.c @@ -2,6 +2,7 @@ * Copyright (c) 1997, Stefan Esser <se@freebsd.org> * Copyright (c) 2000, Michael Smith <msmith@freebsd.org> * Copyright (c) 2000, BSDi + * Copyright (c) 2004, John Baldwin <jhb@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,59 +30,81 @@ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); -#include <sys/param.h> /* XXX trim includes */ +#include <sys/param.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/kernel.h> -#include <sys/module.h> #include <sys/malloc.h> -#include <sys/lock.h> -#include <sys/mutex.h> #include <sys/sysctl.h> #include <vm/vm.h> #include <vm/pmap.h> #include <machine/md_var.h> #include <dev/pci/pcivar.h> #include <dev/pci/pcireg.h> -#include <isa/isavar.h> #include <machine/pci_cfgreg.h> #include <machine/segments.h> #include <machine/pc/bios.h> -#include "pcib_if.h" +#define NUM_ISA_INTERRUPTS 16 -#define PRVERB(a) do { \ - if (bootverbose) \ - printf a ; \ -} while(0) +/* + * A link device. Loosely based on the ACPI PCI link device. This doesn't + * try to support priorities for different ISA interrupts. + */ +struct pci_link { + TAILQ_ENTRY(pci_link) pl_links; + uint8_t pl_id; + uint8_t pl_irq; + uint16_t pl_irqmask; + int pl_references; + int pl_routed; +}; -static int cfgmech; -static int devmax; +struct pci_link_lookup { + struct pci_link **pci_link_ptr; + int bus; + int device; + int pin; +}; -static int pci_cfgintr_valid(struct PIR_entry *pe, int pin, int irq); -static int pci_cfgintr_unique(struct PIR_entry *pe, int pin); -static int pci_cfgintr_linked(struct PIR_entry *pe, int pin); -static int pci_cfgintr_search(struct PIR_entry *pe, int bus, int device, int matchpin, int pin); -static int pci_cfgintr_virgin(struct PIR_entry *pe, int pin); +typedef void pir_entry_handler(struct PIR_entry *entry, + struct PIR_intpin* intpin, void *arg); static void pci_print_irqmask(u_int16_t irqs); -static void pci_print_route_table(struct PIR_table *prt, int size); -static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes); -static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes); -static int pcireg_cfgopen(void); +static int pci_pir_biosroute(int bus, int device, int func, int pin, + int irq); +static int pci_pir_choose_irq(struct pci_link *pci_link, int irqmask); +static void pci_pir_create_links(struct PIR_entry *entry, + struct PIR_intpin *intpin, void *arg); +static void pci_pir_dump_links(void); +static struct pci_link *pci_pir_find_link(uint8_t link_id); +static void pci_pir_find_link_handler(struct PIR_entry *entry, + struct PIR_intpin *intpin, void *arg); +static void pci_pir_initial_irqs(struct PIR_entry *entry, + struct PIR_intpin *intpin, void *arg); +static void pci_pir_print_intpin(struct PIR_entry *entry, + struct PIR_intpin *intpin, void *arg); +static void pci_pir_print_table(void); +static uint8_t pci_pir_search_irq(int bus, int device, int pin); +static int pci_pir_valid_irq(struct pci_link *pci_link, int irq); +static void pci_pir_walk_table(pir_entry_handler *handler, void *arg); -static struct PIR_table *pci_route_table; -static int pci_route_count; +MALLOC_DEFINE(M_PIR, "$PIR", "$PIR structures"); -static struct mtx pcicfg_mtx; +static struct PIR_table *pci_route_table; +static int pci_route_count, pir_bios_irqs, pir_parsed; +static TAILQ_HEAD(, pci_link) pci_links; +static int pir_interrupt_weight[NUM_ISA_INTERRUPTS]; /* sysctl vars */ SYSCTL_DECL(_hw_pci); #ifdef PC98 +/* IRQs 3, 5, 7, 9, 10, 11, 12, 13 */ #define PCI_IRQ_OVERRIDE_MASK 0x3e68 #else -#define PCI_IRQ_OVERRIDE_MASK 0xdef4 +/* IRQs 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15 */ +#define PCI_IRQ_OVERRIDE_MASK 0xdef8 #endif static uint32_t pci_irq_override_mask = PCI_IRQ_OVERRIDE_MASK; @@ -91,409 +114,433 @@ SYSCTL_INT(_hw_pci, OID_AUTO, irq_override_mask, CTLFLAG_RDTUN, "Mask of allowed irqs to try to route when it has no good clue about\n" "which irqs it should use."); - /* - * Some BIOS writers seem to want to ignore the spec and put - * 0 in the intline rather than 255 to indicate none. Some use - * numbers in the range 128-254 to indicate something strange and - * apparently undocumented anywhere. Assume these are completely bogus - * and map them to 255, which means "none". + * Look for the interrupt routing table. + * + * We use PCI BIOS's PIR table if it's available. $PIR is the standard way + * to do this. Sadly, some machines are not standards conforming and have + * _PIR instead. We shrug and cope by looking for both. */ -static __inline__ int -pci_i386_map_intline(int line) +void +pci_pir_open(void) { - if (line == 0 || line >= 128) - return (PCI_INVALID_IRQ); - return (line); + struct PIR_table *pt; + uint32_t sigaddr; + int i; + uint8_t ck, *cv; + + /* Don't try if we've already found a table. */ + if (pci_route_table != NULL) + return; + + /* Look for $PIR and then _PIR. */ + sigaddr = bios_sigsearch(0, "$PIR", 4, 16, 0); + if (sigaddr == 0) + sigaddr = bios_sigsearch(0, "_PIR", 4, 16, 0); + if (sigaddr == 0) + return; + + /* If we found something, check the checksum and length. */ + /* XXX - Use pmap_mapdev()? */ + pt = (struct PIR_table *)(uintptr_t)BIOS_PADDRTOVADDR(sigaddr); + if (pt->pt_header.ph_length <= sizeof(struct PIR_header)) + return; + for (cv = (u_int8_t *)pt, ck = 0, i = 0; + i < (pt->pt_header.ph_length); i++) + ck += cv[i]; + if (ck != 0) + return; + + /* Ok, we've got a valid table. */ + pci_route_table = pt; + pci_route_count = (pt->pt_header.ph_length - + sizeof(struct PIR_header)) / + sizeof(struct PIR_entry); + printf("Found $PIR table, %d entries at %p\n", + pci_route_count, pci_route_table); + if (bootverbose) + pci_pir_print_table(); } -static u_int16_t -pcibios_get_version(void) +/* + * Find the pci_link structure for a given link ID. + */ +static struct pci_link * +pci_pir_find_link(uint8_t link_id) { - struct bios_regs args; + struct pci_link *pci_link; - if (PCIbios.ventry == 0) { - PRVERB(("pcibios: No call entry point\n")); - return (0); - } - args.eax = PCIBIOS_BIOS_PRESENT; - if (bios32(&args, PCIbios.ventry, GSEL(GCODE_SEL, SEL_KPL))) { - PRVERB(("pcibios: BIOS_PRESENT call failed\n")); - return (0); + TAILQ_FOREACH(pci_link, &pci_links, pl_links) { + if (pci_link->pl_id == link_id) + return (pci_link); } - if (args.edx != 0x20494350) { - PRVERB(("pcibios: BIOS_PRESENT didn't return 'PCI ' in edx\n")); - return (0); - } - return (args.ebx & 0xffff); + return (NULL); } -/* - * Initialise access to PCI configuration space +/* + * Find the link device associated with a PCI device in the table. */ -int -pci_cfgregopen(void) +static void +pci_pir_find_link_handler(struct PIR_entry *entry, struct PIR_intpin *intpin, + void *arg) { - static int opened = 0; - u_long sigaddr; - static struct PIR_table *pt; - u_int16_t v; - u_int8_t ck, *cv; - int i; + struct pci_link_lookup *lookup; - if (opened) - return(1); - - if (pcireg_cfgopen() == 0) - return(0); + lookup = (struct pci_link_lookup *)arg; + if (entry->pe_bus == lookup->bus && + entry->pe_device == lookup->device && + intpin - entry->pe_intpin == lookup->pin) + *lookup->pci_link_ptr = pci_pir_find_link(intpin->link); +} - v = pcibios_get_version(); - if (v > 0) - printf("pcibios: BIOS version %x.%02x\n", (v & 0xff00) >> 8, - v & 0xff); +/* + * Check to see if a possible IRQ setting is valid. + */ +static int +pci_pir_valid_irq(struct pci_link *pci_link, int irq) +{ - /* - * Look for the interrupt routing table. - * - * We use PCI BIOS's PIR table if it's available $PIR is the - * standard way to do this. Sadly, some machines are not - * standards conforming and have _PIR instead. We shrug and cope - * by looking for both. - */ - if (pcibios_get_version() >= 0x0210 && pt == NULL) { - sigaddr = bios_sigsearch(0, "$PIR", 4, 16, 0); - if (sigaddr == 0) - sigaddr = bios_sigsearch(0, "_PIR", 4, 16, 0); - if (sigaddr != 0) { - pt = (struct PIR_table *)(uintptr_t) - BIOS_PADDRTOVADDR(sigaddr); - for (cv = (u_int8_t *)pt, ck = 0, i = 0; - i < (pt->pt_header.ph_length); i++) { - ck += cv[i]; - } - if (ck == 0 && pt->pt_header.ph_length > - sizeof(struct PIR_header)) { - pci_route_table = pt; - pci_route_count = (pt->pt_header.ph_length - - sizeof(struct PIR_header)) / - sizeof(struct PIR_entry); - printf("Using $PIR table, %d entries at %p\n", - pci_route_count, pci_route_table); - if (bootverbose) - pci_print_route_table(pci_route_table, - pci_route_count); - } - } - } - mtx_init(&pcicfg_mtx, "pcicfg", NULL, MTX_SPIN); - opened = 1; - return(1); + if (!PCI_INTERRUPT_VALID(irq)) + return (0); + return (pci_link->pl_irqmask & (1 << irq)); } -/* - * Read configuration space register +/* + * Walk the $PIR executing the worker function for each valid intpin entry + * in the table. The handler is passed a pointer to both the entry and + * the intpin in the entry. */ -u_int32_t -pci_cfgregread(int bus, int slot, int func, int reg, int bytes) +static void +pci_pir_walk_table(pir_entry_handler *handler, void *arg) { - uint32_t line; + struct PIR_entry *entry; + struct PIR_intpin *intpin; + int i, pin; - /* - * Some BIOS writers seem to want to ignore the spec and put - * 0 in the intline rather than 255 to indicate none. The rest of - * the code uses 255 as an invalid IRQ. - */ - if (reg == PCIR_INTLINE && bytes == 1) { - line = pcireg_cfgread(bus, slot, func, PCIR_INTLINE, 1); - return (pci_i386_map_intline(line)); + entry = &pci_route_table->pt_entry[0]; + for (i = 0; i < pci_route_count; i++, entry++) { + intpin = &entry->pe_intpin[0]; + for (pin = 0; pin < 4; pin++, intpin++) + if (intpin->link != 0) + handler(entry, intpin, arg); } - return (pcireg_cfgread(bus, slot, func, reg, bytes)); } -/* - * Write configuration space register - */ -void -pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes) +static void +pci_pir_create_links(struct PIR_entry *entry, struct PIR_intpin *intpin, + void *arg) { + struct pci_link *pci_link; - pcireg_cfgwrite(bus, slot, func, reg, data, bytes); + pci_link = pci_pir_find_link(intpin->link); + if (pci_link != NULL) { + pci_link->pl_references++; + if (intpin->irqs != pci_link->pl_irqmask) { + if (bootverbose) + printf( + "$PIR: Entry %d.%d.INT%c has different mask for link %#x, merging\n", + entry->pe_bus, entry->pe_device, + (intpin - entry->pe_intpin) + 'A', + pci_link->pl_id); + pci_link->pl_irqmask &= intpin->irqs; + } + } else { + pci_link = malloc(sizeof(struct pci_link), M_PIR, M_WAITOK); + pci_link->pl_id = intpin->link; + pci_link->pl_irqmask = intpin->irqs; + pci_link->pl_irq = PCI_INVALID_IRQ; + pci_link->pl_references = 1; + pci_link->pl_routed = 0; + TAILQ_INSERT_TAIL(&pci_links, pci_link, pl_links); + } } /* - * Route a PCI interrupt + * Look to see if any of the function on the PCI device at bus/device have + * an interrupt routed to intpin 'pin' by the BIOS. */ -int -pci_cfgintr(int bus, int device, int pin, int oldirq) +static uint8_t +pci_pir_search_irq(int bus, int device, int pin) { - struct PIR_entry *pe; - int i, irq; - struct bios_regs args; - u_int16_t v; - int already = 0; - int errok = 0; - - v = pcibios_get_version(); - if (v < 0x0210) { - PRVERB(( - "pci_cfgintr: BIOS %x.%02x doesn't support interrupt routing\n", - (v & 0xff00) >> 8, v & 0xff)); + uint32_t value; + uint8_t func, maxfunc; + + /* See if we have a valid device at function 0. */ + value = pci_cfgregread(bus, device, 0, PCIR_HDRTYPE, 1); + if ((value & PCIM_HDRTYPE) > PCI_MAXHDRTYPE) return (PCI_INVALID_IRQ); - } - if ((bus < 0) || (bus > 255) || (device < 0) || (device > 255) || - (pin < 1) || (pin > 4)) - return(PCI_INVALID_IRQ); + if (value & PCIM_MFDEV) + maxfunc = PCI_FUNCMAX; + else + maxfunc = 0; - /* - * Scan the entry table for a contender - */ - for (i = 0, pe = &pci_route_table->pt_entry[0]; i < pci_route_count; - i++, pe++) { - if ((bus != pe->pe_bus) || (device != pe->pe_device)) + /* Scan all possible functions at this device. */ + for (func = 0; func <= maxfunc; func++) { + value = pci_cfgregread(bus, device, func, PCIR_DEVVENDOR, 4); + if (value == 0xffffffff) continue; - /* - * A link of 0 means that this intpin is not connected to - * any other device's interrupt pins and is not connected to - * any of the Interrupt Router's interrupt pins, so we can't - * route it. - */ - if (pe->pe_intpin[pin - 1].link == 0) - continue; - - if (pci_cfgintr_valid(pe, pin, oldirq)) { - printf("pci_cfgintr: %d:%d INT%c BIOS irq %d\n", bus, - device, 'A' + pin - 1, oldirq); - return (oldirq); - } + value = pci_cfgregread(bus, device, func, PCIR_INTPIN, 1); /* - * We try to find a linked interrupt, then we look to see - * if the interrupt is uniquely routed, then we look for - * a virgin interrupt. The virgin interrupt should return - * an interrupt we can route, but if that fails, maybe we - * should try harder to route a different interrupt. - * However, experience has shown that that's rarely the - * failure mode we see. + * See if it uses the pin in question. Note that the passed + * in pin uses 0 for A, .. 3 for D whereas the intpin + * register uses 0 for no interrupt, 1 for A, .. 4 for D. */ - irq = pci_cfgintr_linked(pe, pin); - if (irq != PCI_INVALID_IRQ) - already = 1; - if (irq == PCI_INVALID_IRQ) { - irq = pci_cfgintr_unique(pe, pin); - if (irq != PCI_INVALID_IRQ) - errok = 1; - } - if (irq == PCI_INVALID_IRQ) - irq = pci_cfgintr_virgin(pe, pin); - if (irq == PCI_INVALID_IRQ) - break; - - /* - * Ask the BIOS to route the interrupt. If we picked an - * interrupt that failed, we should really try other - * choices that the BIOS offers us. - * - * For uniquely routed interrupts, we need to try - * to route them on some machines. Yet other machines - * fail to route, so we have to pretend that in that - * case it worked. Isn't pc hardware fun? - * - * NOTE: if we want to whack hardware to do this, then - * I think the right way to do that would be to have - * bridge drivers that do this. I'm not sure that the - * $PIR table would be valid for those interrupt - * routers. - */ - args.eax = PCIBIOS_ROUTE_INTERRUPT; - args.ebx = (bus << 8) | (device << 3); - /* pin value is 0xa - 0xd */ - args.ecx = (irq << 8) | (0xa + pin - 1); - if (!already && - bios32(&args, PCIbios.ventry, GSEL(GCODE_SEL, SEL_KPL)) && - !errok) { - PRVERB(("pci_cfgintr: ROUTE_INTERRUPT failed.\n")); - return(PCI_INVALID_IRQ); - } - printf("pci_cfgintr: %d:%d INT%c routed to irq %d\n", bus, - device, 'A' + pin - 1, irq); - return(irq); + if (value != pin + 1) + continue; + value = pci_cfgregread(bus, device, func, PCIR_INTLINE, 1); + if (bootverbose) + printf( + "$PIR: Found matching pin for %d.%d.INT%c at func %d: %d\n", + bus, device, pin + 'A', func, value); + if (value != PCI_INVALID_IRQ) + return (value); } - - PRVERB(("pci_cfgintr: can't route an interrupt to %d:%d INT%c\n", bus, - device, 'A' + pin - 1)); - return(PCI_INVALID_IRQ); + return (PCI_INVALID_IRQ); } /* - * Check to see if an existing IRQ setting is valid. + * Try to initialize IRQ based on this device's IRQ. */ -static int -pci_cfgintr_valid(struct PIR_entry *pe, int pin, int irq) +static void +pci_pir_initial_irqs(struct PIR_entry *entry, struct PIR_intpin *intpin, + void *arg) { - uint32_t irqmask; + struct pci_link *pci_link; + uint8_t irq, pin; - if (!PCI_INTERRUPT_VALID(irq)) - return (0); - irqmask = pe->pe_intpin[pin - 1].irqs; - if (irqmask & (1 << irq)) { - PRVERB(("pci_cfgintr_valid: BIOS irq %d is valid\n", irq)); - return (1); - } - return (0); + pin = intpin - entry->pe_intpin; + pci_link = pci_pir_find_link(intpin->link); + irq = pci_pir_search_irq(entry->pe_bus, entry->pe_device, pin); + if (irq == PCI_INVALID_IRQ) + return; + if (pci_pir_valid_irq(pci_link, irq)) { + if (pci_link->pl_irq == PCI_INVALID_IRQ) + pci_link->pl_irq = irq; + else if (pci_link->pl_irq != irq) + printf( + "$PIR: BIOS IRQ %d for %d.%d.INT%c does not match link %#x irq %d\n", + irq, entry->pe_bus, entry->pe_device, pin + 'A', + pci_link->pl_id, pci_link->pl_irq); + } else + printf( + "$PIR: BIOS IRQ %d for %d.%d.INT%c is not valid for link %#x\n", + irq, entry->pe_bus, entry->pe_device, pin + 'A', + pci_link->pl_id); } /* - * Look to see if the routing table claims this pin is uniquely routed. + * Parse $PIR to enumerate link devices and attempt to determine their + * initial state. This could perhaps be cleaner if we had drivers for the + * various interrupt routers as they could read the initial IRQ for each + * link. */ -static int -pci_cfgintr_unique(struct PIR_entry *pe, int pin) +void +pci_pir_parse(void) { - int irq; - uint32_t irqmask; - - irqmask = pe->pe_intpin[pin - 1].irqs; - if (irqmask != 0 && powerof2(irqmask)) { - irq = ffs(irqmask) - 1; - PRVERB(("pci_cfgintr_unique: hard-routed to irq %d\n", irq)); - return(irq); + char tunable_buffer[64]; + struct pci_link *pci_link; + int i, irq; + + /* Only parse once. */ + if (pir_parsed) + return; + pir_parsed = 1; + + /* Enumerate link devices. */ + TAILQ_INIT(&pci_links); + pci_pir_walk_table(pci_pir_create_links, NULL); + if (bootverbose) { + printf("$PIR: Links after initial probe:\n"); + pci_pir_dump_links(); } - return(PCI_INVALID_IRQ); -} -/* - * Look for another device which shares the same link byte and - * already has a unique IRQ, or which has had one routed already. - */ -static int -pci_cfgintr_linked(struct PIR_entry *pe, int pin) -{ - struct PIR_entry *oe; - struct PIR_intpin *pi; - int i, j, irq; + /* Check for unique IRQ masks. */ + TAILQ_FOREACH(pci_link, &pci_links, pl_links) { + if (pci_link->pl_irqmask != 0 && powerof2(pci_link->pl_irqmask)) + pci_link->pl_irq = ffs(pci_link->pl_irqmask) - 1; + } /* - * Scan table slots. + * Check to see if the BIOS has already routed any of the links by + * checking each device connected to each link to see if it has a + * valid IRQ. */ - for (i = 0, oe = &pci_route_table->pt_entry[0]; i < pci_route_count; - i++, oe++) { - /* scan interrupt pins */ - for (j = 0, pi = &oe->pe_intpin[0]; j < 4; j++, pi++) { - - /* don't look at the entry we're trying to match */ - if ((pe == oe) && (i == (pin - 1))) - continue; - /* compare link bytes */ - if (pi->link != pe->pe_intpin[pin - 1].link) - continue; - /* link destination mapped to a unique interrupt? */ - if (pi->irqs != 0 && powerof2(pi->irqs)) { - irq = ffs(pi->irqs) - 1; - PRVERB(("pci_cfgintr_linked: linked (%x) to hard-routed irq %d\n", - pi->link, irq)); - return(irq); - } + pci_pir_walk_table(pci_pir_initial_irqs, NULL); + if (bootverbose) { + printf("$PIR: Links after initial IRQ discovery:\n"); + pci_pir_dump_links(); + } - /* - * look for the real PCI device that matches this - * table entry - */ - irq = pci_cfgintr_search(pe, oe->pe_bus, oe->pe_device, - j + 1, pin); - if (irq != PCI_INVALID_IRQ) - return(irq); + /* + * Allow the user to override the IRQ for a given link device as + * long as the override is valid or is 255 or 0 to clear a preset + * IRQ. + */ + i = 0; + TAILQ_FOREACH(pci_link, &pci_links, pl_links) { + snprintf(tunable_buffer, sizeof(tunable_buffer), + "hw.pci.link.%#x.irq", pci_link->pl_id); + if (getenv_int(tunable_buffer, &irq) == 0) + continue; + if (irq == 0) + irq = PCI_INVALID_IRQ; + if (irq == PCI_INVALID_IRQ || + pci_pir_valid_irq(pci_link, irq)) { + pci_link->pl_irq = irq; + i = 1; } } - return(PCI_INVALID_IRQ); + if (bootverbose && i) { + printf("$PIR: Links after tunable overrides:\n"); + pci_pir_dump_links(); + } + + /* + * Build initial interrupt weights as well as bitmap of "known-good" + * IRQs that the BIOS has already used for PCI link devices. + */ + TAILQ_FOREACH(pci_link, &pci_links, pl_links) { + if (!PCI_INTERRUPT_VALID(pci_link->pl_irq)) + continue; + pir_bios_irqs |= 1 << pci_link->pl_irq; + pir_interrupt_weight[pci_link->pl_irq] += + pci_link->pl_references; + } + if (bootverbose) { + printf("$PIR: IRQs used by BIOS: "); + pci_print_irqmask(pir_bios_irqs); + printf("\n"); + printf("$PIR: Interrupt Weights:\n[ "); + for (i = 0; i < NUM_ISA_INTERRUPTS; i++) + printf(" %3d", i); + printf(" ]\n[ "); + for (i = 0; i < NUM_ISA_INTERRUPTS; i++) + printf(" %3d", pir_interrupt_weight[i]); + printf(" ]\n"); + } } /* - * Scan for the real PCI device at (bus)/(device) using intpin (matchpin) and - * see if it has already been assigned an interrupt. + * Use the PCI BIOS to route an interrupt for a given device. + * + * Input: + * AX = PCIBIOS_ROUTE_INTERRUPT + * BH = bus + * BL = device [7:3] / function [2:0] + * CH = IRQ + * CL = Interrupt Pin (0x0A = A, ... 0x0D = D) */ static int -pci_cfgintr_search(struct PIR_entry *pe, int bus, int device, int matchpin, int pin) +pci_pir_biosroute(int bus, int device, int func, int pin, int irq) { - devclass_t pci_devclass; - device_t *pci_devices; - int pci_count; - device_t *pci_children; - int pci_childcount; - device_t *busp, *childp; - int i, j, irq; + struct bios_regs args; - /* - * Find all the PCI busses. - */ - pci_count = 0; - if ((pci_devclass = devclass_find("pci")) != NULL) - devclass_get_devices(pci_devclass, &pci_devices, &pci_count); + args.eax = PCIBIOS_ROUTE_INTERRUPT; + args.ebx = (bus << 8) | (device << 3) | func; + args.ecx = (irq << 8) | (0xa + pin); + return (bios32(&args, PCIbios.ventry, GSEL(GCODE_SEL, SEL_KPL))); +} + + +/* + * Route a PCI interrupt using a link device from the $PIR. + */ +int +pci_pir_route_interrupt(int bus, int device, int func, int pin) +{ + struct pci_link_lookup lookup; + struct pci_link *pci_link; + int error, irq; + + if (pci_route_table == NULL) + return (PCI_INVALID_IRQ); + + /* Lookup link device for this PCI device/pin. */ + pci_link = NULL; + lookup.bus = bus; + lookup.device = device; + lookup.pin = pin - 1; + lookup.pci_link_ptr = &pci_link; + pci_pir_walk_table(pci_pir_find_link_handler, &lookup); + if (pci_link == NULL) { + printf("$PIR: No matching entry for %d.%d.INT%c\n", bus, + device, pin - 1 + 'A'); + return (PCI_INVALID_IRQ); + } /* - * Scan all the PCI busses/devices looking for this one. + * Pick a new interrupt if we don't have one already. We look for + * an interrupt from several different sets. First, we check the + * set of PCI only interrupts from the $PIR. Second, we check the + * set of known-good interrupts that the BIOS has already used. + * Lastly, we check the "all possible valid IRQs" set. */ - irq = PCI_INVALID_IRQ; - for (i = 0, busp = pci_devices; (i < pci_count) && (irq == PCI_INVALID_IRQ); - i++, busp++) { - pci_childcount = 0; - device_get_children(*busp, &pci_children, &pci_childcount); - - for (j = 0, childp = pci_children; j < pci_childcount; j++, - childp++) { - if ((pci_get_bus(*childp) == bus) && - (pci_get_slot(*childp) == device) && - (pci_get_intpin(*childp) == matchpin)) { - irq = pci_i386_map_intline(pci_get_irq(*childp)); - if (irq != PCI_INVALID_IRQ) - PRVERB(("pci_cfgintr_search: linked (%x) to configured irq %d at %d:%d:%d\n", - pe->pe_intpin[pin - 1].link, irq, - pci_get_bus(*childp), - pci_get_slot(*childp), - pci_get_function(*childp))); - break; - } + if (!PCI_INTERRUPT_VALID(pci_link->pl_irq)) { + irq = pci_pir_choose_irq(pci_link, + pci_route_table->pt_header.ph_pci_irqs); + if (!PCI_INTERRUPT_VALID(irq)) + irq = pci_pir_choose_irq(pci_link, pir_bios_irqs); + if (!PCI_INTERRUPT_VALID(irq)) + irq = pci_pir_choose_irq(pci_link, + pci_irq_override_mask); + if (!PCI_INTERRUPT_VALID(irq)) { + if (bootverbose) + printf( + "$PIR: Failed to route interrupt for %d:%d INT%c\n", + bus, device, pin - 1 + 'A'); + return (PCI_INVALID_IRQ); + } + pci_link->pl_irq = irq; + } + + /* Ask the BIOS to route this IRQ if we haven't done so already. */ + if (!pci_link->pl_routed) { + error = pci_pir_biosroute(bus, device, func, pin - 1, + pci_link->pl_irq); + + /* Ignore errors when routing a unique interrupt. */ + if (error && !powerof2(pci_link->pl_irqmask)) { + printf("$PIR: ROUTE_INTERRUPT failed.\n"); + return (PCI_INVALID_IRQ); } - if (pci_children != NULL) - free(pci_children, M_TEMP); + pci_link->pl_routed = 1; } - if (pci_devices != NULL) - free(pci_devices, M_TEMP); - return(irq); + printf("$PIR: %d:%d INT%c routed to irq %d\n", bus, device, + pin - 1 + 'A', pci_link->pl_irq); + return (pci_link->pl_irq); } /* - * Pick a suitable IRQ from those listed as routable to this device. + * Try to pick an interrupt for the specified link from the interrupts + * set in the mask. */ static int -pci_cfgintr_virgin(struct PIR_entry *pe, int pin) +pci_pir_choose_irq(struct pci_link *pci_link, int irqmask) { - int irq, ibit; - - /* - * first scan the set of PCI-only interrupts and see if any of these - * are routable - */ - for (irq = 0; irq < 16; irq++) { - ibit = (1 << irq); + int i, irq, realmask; - /* can we use this interrupt? */ - if ((pci_route_table->pt_header.ph_pci_irqs & ibit) && - (pe->pe_intpin[pin - 1].irqs & ibit)) { - PRVERB(("pci_cfgintr_virgin: using routable PCI-only interrupt %d\n", irq)); - return(irq); - } - } - - /* life is tough, so just pick an interrupt */ - for (irq = 0; irq < 16; irq++) { - ibit = (1 << irq); - if ((ibit & pci_irq_override_mask) == 0) + /* XXX: Need to have a #define of known bad IRQs to also mask out? */ + realmask = pci_link->pl_irqmask & irqmask; + if (realmask == 0) + return (PCI_INVALID_IRQ); + + /* Find IRQ with lowest weight. */ + irq = PCI_INVALID_IRQ; + for (i = 0; i < NUM_ISA_INTERRUPTS; i++) { + if (!(realmask & 1 << i)) continue; - if (pe->pe_intpin[pin - 1].irqs & ibit) { - PRVERB(("pci_cfgintr_virgin: using routable interrupt %d\n", irq)); - return(irq); - } + if (irq == PCI_INVALID_IRQ || + pir_interrupt_weight[i] < pir_interrupt_weight[irq]) + irq = i; + } + if (bootverbose && PCI_INTERRUPT_VALID(irq)) { + printf("$PIR: Found IRQ %d for link %#x from ", irq, + pci_link->pl_id); + pci_print_irqmask(realmask); + printf("\n"); } - return(PCI_INVALID_IRQ); + return (irq); } static void @@ -517,279 +564,68 @@ pci_print_irqmask(u_int16_t irqs) } /* - * Dump the contents of a PCI BIOS Interrupt Routing Table to the console. + * Dump the contents of a single intpin entry to the console. */ static void -pci_print_route_table(struct PIR_table *prt, int size) +pci_pir_print_intpin(struct PIR_entry *entry, struct PIR_intpin *intpin, + void *arg) { - struct PIR_entry *entry; - struct PIR_intpin *intpin; - int i, pin; - printf("PCI-Only Interrupts: "); - pci_print_irqmask(prt->pt_header.ph_pci_irqs); - printf("\nLocation Bus Device Pin Link IRQs\n"); - entry = &prt->pt_entry[0]; - for (i = 0; i < size; i++, entry++) { - intpin = &entry->pe_intpin[0]; - for (pin = 0; pin < 4; pin++, intpin++) - if (intpin->link != 0) { - if (entry->pe_slot == 0) - printf("embedded "); - else - printf("slot %-3d ", entry->pe_slot); - printf(" %3d %3d %c 0x%02x ", - entry->pe_bus, entry->pe_device, - 'A' + pin, intpin->link); - pci_print_irqmask(intpin->irqs); - printf("\n"); - } - } + if (entry->pe_slot == 0) + printf("embedded "); + else + printf("slot %-3d ", entry->pe_slot); + printf(" %3d %3d %c 0x%02x ", entry->pe_bus, entry->pe_device, + intpin - entry->pe_intpin + 'A', intpin->link); + pci_print_irqmask(intpin->irqs); + printf("\n"); } /* - * See if any interrupts for a given PCI bus are routed in the PIR. Don't - * even bother looking if the BIOS doesn't support routing anyways. - */ -int -pci_probe_route_table(int bus) -{ - int i; - u_int16_t v; - - v = pcibios_get_version(); - if (v < 0x0210) - return (0); - for (i = 0; i < pci_route_count; i++) - if (pci_route_table->pt_entry[i].pe_bus == bus) - return (1); - return (0); -} - -/* - * Configuration space access using direct register operations + * Dump the contents of a PCI BIOS Interrupt Routing Table to the console. */ - -/* enable configuration space accesses and return data port address */ -static int -pci_cfgenable(unsigned bus, unsigned slot, unsigned func, int reg, int bytes) -{ - int dataport = 0; - - if (bus <= PCI_BUSMAX - && slot < devmax - && func <= PCI_FUNCMAX - && reg <= PCI_REGMAX - && bytes != 3 - && (unsigned) bytes <= 4 - && (reg & (bytes - 1)) == 0) { - switch (cfgmech) { - case 1: - outl(CONF1_ADDR_PORT, (1 << 31) - | (bus << 16) | (slot << 11) - | (func << 8) | (reg & ~0x03)); - dataport = CONF1_DATA_PORT + (reg & 0x03); - break; - case 2: - outb(CONF2_ENABLE_PORT, 0xf0 | (func << 1)); - outb(CONF2_FORWARD_PORT, bus); - dataport = 0xc000 | (slot << 8) | reg; - break; - } - } - return (dataport); -} - -/* disable configuration space accesses */ static void -pci_cfgdisable(void) -{ - switch (cfgmech) { - case 1: - outl(CONF1_ADDR_PORT, 0); - break; - case 2: - outb(CONF2_ENABLE_PORT, 0); - outb(CONF2_FORWARD_PORT, 0); - break; - } -} - -static int -pcireg_cfgread(int bus, int slot, int func, int reg, int bytes) +pci_pir_print_table(void) { - int data = -1; - int port; - mtx_lock_spin(&pcicfg_mtx); - port = pci_cfgenable(bus, slot, func, reg, bytes); - if (port != 0) { - switch (bytes) { - case 1: - data = inb(port); - break; - case 2: - data = inw(port); - break; - case 4: - data = inl(port); - break; - } - pci_cfgdisable(); - } - mtx_unlock_spin(&pcicfg_mtx); - return (data); + printf("PCI-Only Interrupts: "); + pci_print_irqmask(pci_route_table->pt_header.ph_pci_irqs); + printf("\nLocation Bus Device Pin Link IRQs\n"); + pci_pir_walk_table(pci_pir_print_intpin, NULL); } +/* + * Display link devices. + */ static void -pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes) +pci_pir_dump_links(void) { - int port; + struct pci_link *pci_link; - mtx_lock_spin(&pcicfg_mtx); - port = pci_cfgenable(bus, slot, func, reg, bytes); - if (port != 0) { - switch (bytes) { - case 1: - outb(port, data); - break; - case 2: - outw(port, data); - break; - case 4: - outl(port, data); - break; - } - pci_cfgdisable(); + printf("Link IRQ Ref IRQs\n"); + TAILQ_FOREACH(pci_link, &pci_links, pl_links) { + printf("%#4x %3d %3d ", pci_link->pl_id, pci_link->pl_irq, + pci_link->pl_references); + pci_print_irqmask(pci_link->pl_irqmask); + printf("\n"); } - mtx_unlock_spin(&pcicfg_mtx); } -/* check whether the configuration mechanism has been correctly identified */ -static int -pci_cfgcheck(int maxdev) +/* + * See if any interrupts for a given PCI bus are routed in the PIR. Don't + * even bother looking if the BIOS doesn't support routing anyways. If we + * are probing a PCI-PCI bridge, then require_parse will be true and we should + * only succeed if a host-PCI bridge has already attached and parsed the PIR. + */ +int +pci_pir_probe(int bus, int require_parse) { - uint32_t id, class; - uint8_t header; - uint8_t device; - int port; - - if (bootverbose) - printf("pci_cfgcheck:\tdevice "); - - for (device = 0; device < maxdev; device++) { - if (bootverbose) - printf("%d ", device); - - port = pci_cfgenable(0, device, 0, 0, 4); - id = inl(port); - if (id == 0 || id == 0xffffffff) - continue; - - port = pci_cfgenable(0, device, 0, 8, 4); - class = inl(port) >> 8; - if (bootverbose) - printf("[class=%06x] ", class); - if (class == 0 || (class & 0xf870ff) != 0) - continue; - - port = pci_cfgenable(0, device, 0, 14, 1); - header = inb(port); - if (bootverbose) - printf("[hdr=%02x] ", header); - if ((header & 0x7e) != 0) - continue; - - if (bootverbose) - printf("is there (id=%08x)\n", id); - - pci_cfgdisable(); - return (1); - } - if (bootverbose) - printf("-- nothing found\n"); + int i; - pci_cfgdisable(); + if (pci_route_table == NULL || (require_parse && !pir_parsed)) + return (0); + for (i = 0; i < pci_route_count; i++) + if (pci_route_table->pt_entry[i].pe_bus == bus) + return (1); return (0); } - -static int -pcireg_cfgopen(void) -{ - uint32_t mode1res, oldval1; - uint8_t mode2res, oldval2; - - oldval1 = inl(CONF1_ADDR_PORT); - - if (bootverbose) { - printf("pci_open(1):\tmode 1 addr port (0x0cf8) is 0x%08x\n", - oldval1); - } - - if ((oldval1 & CONF1_ENABLE_MSK) == 0) { - - cfgmech = 1; - devmax = 32; - - outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK); - DELAY(1); - mode1res = inl(CONF1_ADDR_PORT); - outl(CONF1_ADDR_PORT, oldval1); - - if (bootverbose) - printf("pci_open(1a):\tmode1res=0x%08x (0x%08lx)\n", - mode1res, CONF1_ENABLE_CHK); - - if (mode1res) { - if (pci_cfgcheck(32)) - return (cfgmech); - } - - outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK1); - mode1res = inl(CONF1_ADDR_PORT); - outl(CONF1_ADDR_PORT, oldval1); - - if (bootverbose) - printf("pci_open(1b):\tmode1res=0x%08x (0x%08lx)\n", - mode1res, CONF1_ENABLE_CHK1); - - if ((mode1res & CONF1_ENABLE_MSK1) == CONF1_ENABLE_RES1) { - if (pci_cfgcheck(32)) - return (cfgmech); - } - } - - oldval2 = inb(CONF2_ENABLE_PORT); - - if (bootverbose) { - printf("pci_open(2):\tmode 2 enable port (0x0cf8) is 0x%02x\n", - oldval2); - } - - if ((oldval2 & 0xf0) == 0) { - - cfgmech = 2; - devmax = 16; - - outb(CONF2_ENABLE_PORT, CONF2_ENABLE_CHK); - mode2res = inb(CONF2_ENABLE_PORT); - outb(CONF2_ENABLE_PORT, oldval2); - - if (bootverbose) - printf("pci_open(2a):\tmode2res=0x%02x (0x%02x)\n", - mode2res, CONF2_ENABLE_CHK); - - if (mode2res == CONF2_ENABLE_RES) { - if (bootverbose) - printf("pci_open(2a):\tnow trying mechanism 2\n"); - - if (pci_cfgcheck(16)) - return (cfgmech); - } - } - - cfgmech = 0; - devmax = 0; - return (cfgmech); -} - |
