diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2009-11-19 20:46:17 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2009-11-19 20:46:17 +0000 |
commit | 14fba7e59e92dafb518d15789c8084f184dd468e (patch) | |
tree | d8fc816a9ce1c51bbb8da1b2d5e73612296684f9 | |
parent | a9941f9c8cae53be46577cc64378d1fcc30d12b5 (diff) |
Add code to set up windows on PCI-PCI bridges left unconfigured by the
firmware. Fixes some (laptop) machines with ethernet and/or wiireless
behind a bridge. Some help from miod@. Tested by many.
-rw-r--r-- | sys/dev/pci/ppb.c | 152 |
1 files changed, 151 insertions, 1 deletions
diff --git a/sys/dev/pci/ppb.c b/sys/dev/pci/ppb.c index 1ba62828c96..7149d34637b 100644 --- a/sys/dev/pci/ppb.c +++ b/sys/dev/pci/ppb.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ppb.c,v 1.35 2009/09/24 21:26:25 miod Exp $ */ +/* $OpenBSD: ppb.c,v 1.36 2009/11/19 20:46:16 kettenis Exp $ */ /* $NetBSD: ppb.c,v 1.16 1997/06/06 23:48:05 thorpej Exp $ */ /* @@ -73,6 +73,7 @@ struct cfdriver ppb_cd = { NULL, "ppb", DV_DULL }; +void ppb_alloc_resources(struct ppb_softc *, struct pci_attach_args *); int ppb_intr(void *); void ppb_hotplug_insert(void *, void *); void ppb_hotplug_insert_finish(void *); @@ -163,6 +164,8 @@ ppbattach(struct device *parent, struct device *self, void *aux) printf("\n"); + ppb_alloc_resources(sc, pa); + for (pin = PCI_INTERRUPT_PIN_A; pin <= PCI_INTERRUPT_PIN_D; pin++) { pa->pa_intrpin = pa->pa_rawintrpin = pin; pa->pa_intrline = 0; @@ -308,6 +311,153 @@ ppbdetach(struct device *self, int flags) return (rv); } +void +ppb_alloc_resources(struct ppb_softc *sc, struct pci_attach_args *pa) +{ + pci_chipset_tag_t pc = sc->sc_pc; + pcireg_t id, busdata, blr, bhlcr, type, csr; + pcireg_t addr, mask; + pcitag_t tag; + int bus, dev; + int reg, reg_start, reg_end, reg_rom; + int io_count = 0; + int mem_count = 0; + u_long base, size; + + if (pa->pa_memex == NULL) + return; + + busdata = pci_conf_read(pc, sc->sc_tag, PPB_REG_BUSINFO); + bus = PPB_BUSINFO_SECONDARY(busdata); + if (bus == 0) + return; + + /* + * Count number of devices. If there are no devices behind + * this bridge, there's no point in allocating any address + * space. + */ + for (dev = 0; dev < pci_bus_maxdevs(pc, bus); dev++) { + tag = pci_make_tag(pc, bus, dev, 0); + id = pci_conf_read(pc, tag, PCI_ID_REG); + + if (PCI_VENDOR(id) == PCI_VENDOR_INVALID || + PCI_VENDOR(id) == 0) + continue; + + bhlcr = pci_conf_read(pc, tag, PCI_BHLC_REG); + switch (PCI_HDRTYPE_TYPE(bhlcr)) { + case 0: + reg_start = PCI_MAPREG_START; + reg_end = PCI_MAPREG_END; + reg_rom = PCI_ROM_REG; + break; + case 1: /* PCI-PCI bridge */ + reg_start = PCI_MAPREG_START; + reg_end = PCI_MAPREG_PPB_END; + reg_rom = 0; /* 0x38 */ + break; + case 2: /* PCI-Cardbus bridge */ + reg_start = PCI_MAPREG_START; + reg_end = PCI_MAPREG_PCB_END; + reg_rom = 0; + break; + default: + return; + } + + for (reg = reg_start; reg < reg_end; reg += 4) { + if (pci_mapreg_probe(pc, tag, reg, &type) == 0) + continue; + + if (type == PCI_MAPREG_TYPE_IO) + io_count++; + else + mem_count++; + } + + if (reg_rom != 0) { + addr = pci_conf_read(pc, tag, reg_rom); + pci_conf_write(pc, tag, reg_rom, ~PCI_ROM_ENABLE); + mask = pci_conf_read(pc, tag, reg_rom); + pci_conf_write(pc, tag, reg_rom, addr); + if (PCI_ROM_SIZE(mask)) + mem_count++; + } + } + + csr = pci_conf_read(pc, sc->sc_tag, PCI_COMMAND_STATUS_REG); + + /* + * Get the bridge in a consistent state. If memory mapped I/O + * is disabled, disabled the associated windows as well. + */ + if ((csr & PCI_COMMAND_MEM_ENABLE) == 0) { + pci_conf_write(pc, sc->sc_tag, PPB_REG_MEM, 0x0000ffff); + pci_conf_write(pc, sc->sc_tag, PPB_REG_PREFMEM, 0x0000ffff); + pci_conf_write(pc, sc->sc_tag, PPB_REG_PREFBASE_HI32, 0); + pci_conf_write(pc, sc->sc_tag, PPB_REG_PREFLIM_HI32, 0); + } + + /* Allocate I/O address space if necessary. */ + if (io_count > 0 && pa->pa_ioex) { + blr = pci_conf_read(pc, sc->sc_tag, PPB_REG_IOSTATUS); + sc->sc_iobase = (blr << PPB_IO_SHIFT) & PPB_IO_MASK; + sc->sc_iolimit = (blr & PPB_IO_MASK) | 0x00000fff; + blr = pci_conf_read(pc, sc->sc_tag, PPB_REG_IO_HI); + sc->sc_iobase |= (blr & 0x0000ffff) << 16; + sc->sc_iolimit |= (blr & 0xffff0000); + if (sc->sc_iolimit < sc->sc_iobase || sc->sc_iobase == 0) { + for (size = 0x2000; size >= PPB_IO_MIN; size >>= 1) + if (extent_alloc(pa->pa_ioex, size, size, + 0, 0, 0, &base) == 0) + break; + if (size >= PPB_IO_MIN) { + sc->sc_iobase = base; + sc->sc_iolimit = base + size - 1; + blr = pci_conf_read(pc, sc->sc_tag, + PPB_REG_IOSTATUS); + blr &= 0xffff0000; + blr |= sc->sc_iolimit & PPB_IO_MASK; + blr |= (sc->sc_iobase >> PPB_IO_SHIFT); + pci_conf_write(pc, sc->sc_tag, + PPB_REG_IOSTATUS, blr); + blr = (sc->sc_iobase & 0xffff0000) >> 16; + blr |= sc->sc_iolimit & 0xffff0000; + pci_conf_write(pc, sc->sc_tag, + PPB_REG_IO_HI, blr); + + csr |= PCI_COMMAND_IO_ENABLE; + } + } + } + + /* Allocate memory mapped I/O address space if necessary. */ + if (mem_count > 0 && pa->pa_memex) { + blr = pci_conf_read(pc, sc->sc_tag, PPB_REG_MEM); + sc->sc_membase = (blr << PPB_MEM_SHIFT) & PPB_MEM_MASK; + sc->sc_memlimit = (blr & PPB_MEM_MASK) | 0x000fffff; + if (sc->sc_memlimit < sc->sc_membase || sc->sc_membase == 0) { + for (size = 0x2000000; size >= PPB_MEM_MIN; size >>= 1) + if (extent_alloc(pa->pa_memex, size, size, + 0, 0, 0, &base) == 0) + break; + if (size >= PPB_MEM_MIN) { + sc->sc_membase = base; + sc->sc_memlimit = base + size - 1; + blr = sc->sc_memlimit & PPB_MEM_MASK; + blr |= (sc->sc_membase >> PPB_MEM_SHIFT); + pci_conf_write(pc, sc->sc_tag, + PPB_REG_MEM, blr); + + csr |= PCI_COMMAND_MEM_ENABLE; + } + } + } + + pci_conf_write(pc, sc->sc_tag, PCI_COMMAND_STATUS_REG, csr); +} + int ppb_intr(void *arg) { |