PCI Express hotplug support, adapted from OpenBSD. Index: pci.c =================================================================== RCS file: /cvsroot/src/sys/dev/pci/pci.c,v retrieving revision 1.118 diff -u -r1.118 pci.c --- pci.c 12 Jun 2008 22:44:47 -0000 1.118 +++ pci.c 23 Sep 2008 02:17:53 -0000 @@ -311,6 +311,7 @@ pa.pa_tag = tag; pa.pa_id = id; pa.pa_class = class; + pa.pa_bridgetag = sc->sc_bridgetag; /* * Set up memory, I/O enable, and PCI command flags @@ -756,10 +757,8 @@ int offset; pcireg_t value; - if (!pci_get_capability(pc, tag, PCI_CAP_PWRMGMT, &offset, &value)) { - printf("pci_set_powerstate not supported\n"); + if (!pci_get_capability(pc, tag, PCI_CAP_PWRMGMT, &offset, &value)) return EOPNOTSUPP; - } return pci_set_powerstate_int(pc, tag, state, offset, value); } Index: pcivar.h =================================================================== RCS file: /cvsroot/src/sys/dev/pci/pcivar.h,v retrieving revision 1.83 diff -u -r1.83 pcivar.h --- pcivar.h 22 Jul 2008 04:52:19 -0000 1.83 +++ pcivar.h 23 Sep 2008 02:17:53 -0000 @@ -104,6 +104,8 @@ pcitag_t pa_tag; pcireg_t pa_id, pa_class; + pcitag_t *pa_bridgetag; + /* * Interrupt information. * Index: ppb.c =================================================================== RCS file: /cvsroot/src/sys/dev/pci/ppb.c,v retrieving revision 1.39 diff -u -r1.39 ppb.c --- ppb.c 3 May 2008 05:44:06 -0000 1.39 +++ ppb.c 23 Sep 2008 02:17:53 -0000 @@ -34,25 +34,52 @@ __KERNEL_RCSID(0, "$NetBSD: ppb.c,v 1.39 2008/05/03 05:44:06 cegger Exp $"); #include +#include #include #include #include +#include #include #include #include #include +#include "locators.h" + +extern int pci_enumerate_bus(struct pci_softc *, const int *, + int (*)(struct pci_attach_args *), struct pci_attach_args *); + +struct ppb_work { + struct work pw_work; + enum ppb_event { + PPB_EV_INSERT, + PPB_EV_REMOVE + } pw_event; +}; + struct ppb_softc { device_t sc_dev; /* generic device glue */ pci_chipset_tag_t sc_pc; /* our PCI chipset... */ pcitag_t sc_tag; /* ...and tag. */ + device_t sc_pcibus; + void *sc_ih; + int sc_pcie_off; + struct workqueue *sc_wq; + struct ppb_work sc_pw_insert, sc_pw_remove; + pcireg_t sc_pciconfext[48]; }; static bool ppb_resume(device_t PMF_FN_PROTO); static bool ppb_suspend(device_t PMF_FN_PROTO); +static int ppb_intr(void *); +static void ppb_event_worker(struct work *, void *); +static void ppb_rescan(struct ppb_softc *); +static int ppb_fixup(struct pci_attach_args *); +static int ppb_fixup_type0(pci_chipset_tag_t, pcitag_t, pcitag_t); +static int ppb_fixup_type1(pci_chipset_tag_t, pcitag_t, pcitag_t); static int ppbmatch(device_t parent, cfdata_t match, void *aux) @@ -72,29 +99,6 @@ } static void -ppb_fix_pcix(device_t self) -{ - struct ppb_softc *sc = device_private(self); - pcireg_t reg; - int off; - - if (!pci_get_capability(sc->sc_pc, sc->sc_tag, PCI_CAP_PCIEXPRESS, - &off, ®)) - return; /* Not a PCIe device */ - - if ((reg & 0x000f0000) != 0x00010000) { - aprint_normal_dev(self, "unsupported PCI Express version\n"); - return; - } - reg = pci_conf_read(sc->sc_pc, sc->sc_tag, off + 0x18); - if (reg & 0x003f) { - aprint_normal_dev(self, "disabling notification events\n"); - reg &= ~0x003f; - pci_conf_write(sc->sc_pc, sc->sc_tag, off + 0x18, reg); - } -} - -static void ppbattach(device_t parent, device_t self, void *aux) { struct ppb_softc *sc = device_private(self); @@ -103,6 +107,7 @@ struct pcibus_attach_args pba; pcireg_t busdata; char devinfo[256]; + pcireg_t reg; pci_devinfo(pa->pa_id, pa->pa_class, 0, devinfo, sizeof(devinfo)); aprint_normal(": %s (rev. 0x%02x)\n", devinfo, @@ -112,6 +117,9 @@ sc->sc_pc = pc; sc->sc_tag = pa->pa_tag; sc->sc_dev = self; + sc->sc_wq = NULL; + sc->sc_pw_insert.pw_event = PPB_EV_INSERT; + sc->sc_pw_remove.pw_event = PPB_EV_REMOVE; busdata = pci_conf_read(pc, pa->pa_tag, PPB_REG_BUSINFO); @@ -120,7 +128,40 @@ return; } - ppb_fix_pcix(self); + if (pci_get_capability(sc->sc_pc, sc->sc_tag, PCI_CAP_PCIEXPRESS, + &sc->sc_pcie_off, ®)) { + const char *intrstr; + pci_intr_handle_t ih; + pcireg_t csr; + int err; + + err = workqueue_create(&sc->sc_wq, "pciehp", + ppb_event_worker, sc, PRI_NONE, IPL_NONE, 0); + if (err) { + aprint_error_dev(self, "couldn't create workqueue\n"); + goto nointr; + } + + csr = pci_conf_read(sc->sc_pc, sc->sc_tag, + sc->sc_pcie_off + 0x18); + csr |= 0x28; + pci_conf_write(sc->sc_pc, sc->sc_tag, + sc->sc_pcie_off + 0x18, csr); + + if (pci_intr_map(pa, &ih)) { + aprint_error_dev(self, "couldn't map interrupt\n"); + goto nointr; + } + intrstr = pci_intr_string(pc, ih); + sc->sc_ih = pci_intr_establish(pc, ih, IPL_VM, ppb_intr, sc); + if (sc->sc_ih == NULL) { + aprint_error_dev(self, + "couldn't establish interrupt\n"); + goto nointr; + } + aprint_normal_dev(self, "interrupting at %s\n", intrstr); + } +nointr: #if 0 /* @@ -154,14 +195,23 @@ pba.pba_intrswiz = pa->pa_intrswiz; pba.pba_intrtag = pa->pa_intrtag; - config_found_ia(self, "pcibus", &pba, pcibusprint); + sc->sc_pcibus = config_found_ia(self, "pcibus", &pba, pcibusprint); } static int ppbdetach(device_t self, int flags) { + struct ppb_softc *sc = device_private(self); int rc; + if (sc->sc_ih) { + pci_intr_disestablish(sc->sc_pc, sc->sc_ih); + sc->sc_ih = NULL; + } + if (sc->sc_wq) { + workqueue_destroy(sc->sc_wq); + sc->sc_wq = NULL; + } if ((rc = config_detach_children(self, flags)) != 0) return rc; pmf_device_deregister(self); @@ -182,8 +232,6 @@ sc->sc_pciconfext[(off - 0x40)/4]); } - ppb_fix_pcix(dv); - return true; } @@ -200,6 +248,239 @@ return true; } +static int +ppb_intr(void *opaque) +{ + struct ppb_softc *sc = opaque; + pcireg_t reg; + int ret = 0; + + reg = pci_conf_read(sc->sc_pc, sc->sc_tag, sc->sc_pcie_off + 0x18); + if (reg & 0x00080000) { /* hotplug status change */ + if (reg & 0x00400000) { + aprint_debug_dev(sc->sc_dev, "device inserted\n"); + workqueue_enqueue(sc->sc_wq, + (struct work *)&sc->sc_pw_insert, NULL); + } else { + aprint_debug_dev(sc->sc_dev, "device removed\n"); + workqueue_enqueue(sc->sc_wq, + (struct work *)&sc->sc_pw_remove, NULL); + } + ++ret; + } + if (reg & 0x00040000) { /* MRL sensor changed */ + aprint_debug_dev(sc->sc_dev, "MRL sensor changed\n"); + ++ret; + } + if (reg & 0x00020000) { /* power fault */ + aprint_debug_dev(sc->sc_dev, "power fault\n"); + ++ret; + } + if (reg & 0x00010000) { /* attention button */ + aprint_debug_dev(sc->sc_dev, "attention button pressed\n"); + ++ret; + } + + if (ret) + pci_conf_write(sc->sc_pc, sc->sc_tag, + sc->sc_pcie_off + 0x18, reg); + + return ret; +} + +static void +ppb_event_worker(struct work *work, void *opaque) +{ + struct ppb_softc *sc = opaque; + struct ppb_work *pw = (struct ppb_work *)work; + + if (sc->sc_pcibus == NULL) + return; + + switch (pw->pw_event) { + case PPB_EV_INSERT: + (void)tsleep(pw, PRI_NONE, "ppbhp", hz); + ppb_rescan(sc); + break; + case PPB_EV_REMOVE: + config_detach_children(sc->sc_pcibus, DETACH_FORCE); + break; + } +} + +static void +ppb_rescan(struct ppb_softc *sc) +{ + static const int wildcard[PCICF_NLOCS] = { + PCICF_DEV_DEFAULT, PCICF_FUNCTION_DEFAULT + }; + + KASSERT(sc->sc_pcibus != NULL); + + pci_enumerate_bus(device_private(sc->sc_pcibus), + wildcard, ppb_fixup, NULL); + pcirescan(sc->sc_pcibus, "pci", wildcard); +} + +static int +ppb_fixup(struct pci_attach_args *pa) +{ + pcireg_t bhlcr; + + bhlcr = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_BHLC_REG); + switch (PCI_HDRTYPE_TYPE(bhlcr)) { + case 0: + return ppb_fixup_type0(pa->pa_pc, pa->pa_tag, + *pa->pa_bridgetag); + case 1: + return ppb_fixup_type1(pa->pa_pc, pa->pa_tag, + *pa->pa_bridgetag); + default: + return 0; + } +} + +static int +ppb_fixup_type0(pci_chipset_tag_t pc, pcitag_t tag, pcitag_t bridgetag) +{ + pcireg_t blr, type, intr, csr; + int reg, line; + bus_addr_t base, io_base, io_limit, mem_base, mem_limit; + bus_size_t size, io_size, mem_size; + + /* + * The code below assumes that the address ranges on our + * parent PCI Express bridge are really available and don't + * overlap with other devices in the system. + */ + + /* Figure out the I/O address range of the bridge. */ + blr = pci_conf_read(pc, bridgetag, PPB_REG_IOSTATUS); + io_base = (blr & 0x000000f0) << 8; + io_limit = (blr & 0x0000f000) | 0x00000fff; + if (io_limit > io_base) + io_size = (io_limit - io_base + 1); + else + io_size = 0; + + /* Figure out the memory mapped I/O address range of the bridge. */ + blr = pci_conf_read(pc, bridgetag, PPB_REG_MEM); + mem_base = (blr & 0x0000fff0) << 16; + mem_limit = (blr & 0xffff0000) | 0x000fffff; + if (mem_limit > mem_base) + mem_size = (mem_limit - mem_base + 1); + else + mem_size = 0; + + /* Assign resources to the Base Address Registers. */ + for (reg = PCI_MAPREG_START; reg < PCI_MAPREG_END; reg += 4) { + if (!pci_mapreg_probe(pc, tag, reg, &type)) + continue; + if (pci_mapreg_info(pc, tag, reg, type, &base, &size, NULL)) + continue; + if (base != 0) + continue; + switch (type) { + case PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_32BIT: + case PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_64BIT: + base = roundup(mem_base, size); + size += base - mem_base; + if (size > mem_size) + continue; + pci_conf_write(pc, tag, reg, base); + mem_base += size; + mem_size -= size; + + csr = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG); + csr |= PCI_COMMAND_MEM_ENABLE; + pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, csr); + break; + case PCI_MAPREG_TYPE_IO: + base = roundup(io_base, size); + size += base - io_base; + if (size > io_size) + continue; + pci_conf_write(pc, tag, reg, base); + io_base += size; + io_size -= size; + + csr = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG); + csr |= PCI_COMMAND_IO_ENABLE; + pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, csr); + break; + default: + break; + } + + if (type & PCI_MAPREG_MEM_TYPE_64BIT) + reg += 4; + } + + /* + * Fill in the interrupt line for platforms that need it. + * + * XXX We assume that the interrupt line matches the line used + * by the PCI Express bridge. This may not be true. + */ + intr = pci_conf_read(pc, tag, PCI_INTERRUPT_REG); + if (PCI_INTERRUPT_PIN(intr) != PCI_INTERRUPT_PIN_NONE && + PCI_INTERRUPT_LINE(intr) == 0) { + /* Get the interrupt line from our parent. */ + intr = pci_conf_read(pc, bridgetag, PCI_INTERRUPT_REG); + line = PCI_INTERRUPT_LINE(intr); + + intr = pci_conf_read(pc, tag, PCI_INTERRUPT_REG); + intr &= ~(PCI_INTERRUPT_LINE_MASK << PCI_INTERRUPT_LINE_SHIFT); + intr |= line << PCI_INTERRUPT_LINE_SHIFT; + pci_conf_write(pc, tag, PCI_INTERRUPT_REG, intr); + } + + return 0; +} + +static int +ppb_fixup_type1(pci_chipset_tag_t pc, pcitag_t tag, pcitag_t bridgetag) +{ + pcireg_t bhlcr, bir, csr, val; + int bus, dev, reg; + + bir = pci_conf_read(pc, bridgetag, PPB_REG_BUSINFO); + if (PPB_BUSINFO_SUBORDINATE(bir) <= PPB_BUSINFO_SECONDARY(bir)) + return 0; + + bus = PPB_BUSINFO_SECONDARY(bir); + bir = pci_conf_read(pc, tag, PPB_REG_BUSINFO); + bir &= (0xff << 24); + bir |= bus++; + bir |= (bus << 8); + bir |= (bus << 16); + pci_conf_write(pc, tag, PPB_REG_BUSINFO, bir); + + for (reg = PPB_REG_IOSTATUS; reg < PPB_REG_BRIDGECONTROL; reg += 4) { + val = pci_conf_read(pc, bridgetag, reg); + pci_conf_write(pc, tag, reg, val); + } + + csr = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG); + csr |= PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MEM_ENABLE; + csr |= PCI_COMMAND_MASTER_ENABLE; + csr |= PCI_COMMAND_INVALIDATE_ENABLE; + csr |= PCI_COMMAND_SERR_ENABLE; + pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, csr); + + for (dev = 0; dev < pci_bus_maxdevs(pc, bus); dev++) { + tag = pci_make_tag(pc, bus, dev, 0); + + bhlcr = pci_conf_read(pc, tag, PCI_BHLC_REG); + if (PCI_HDRTYPE_TYPE(bhlcr) != 0) + continue; + + ppb_fixup_type0(pc, tag, bridgetag); + } + + return 0; +} + static void ppbchilddet(device_t self, device_t child) {