X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=arch%2Fsparc64%2Fkernel%2Fpci_common.c;fp=arch%2Fsparc64%2Fkernel%2Fpci_common.c;h=58310aacea28433adaa20d5598406600de5c9eea;hb=64ba3f394c830ec48a1c31b53dcae312c56f1604;hp=827ae30aa4971e5306e1f8c0ed6db494e65b71ec;hpb=be1e6109ac94a859551f8e1774eb9a8469fe055c;p=linux-2.6.git diff --git a/arch/sparc64/kernel/pci_common.c b/arch/sparc64/kernel/pci_common.c index 827ae30aa..58310aace 100644 --- a/arch/sparc64/kernel/pci_common.c +++ b/arch/sparc64/kernel/pci_common.c @@ -9,10 +9,6 @@ #include #include -#include -#include - -#include "pci_impl.h" /* Fix self device of BUS and hook it into BUS->self. * The pci_scan_bus does not do this for the host bridge. @@ -32,16 +28,16 @@ void __init pci_fixup_host_bridge_self(struct pci_bus *pbus) prom_halt(); } -/* Find the OBP PROM device tree node for a PCI device. */ -static struct device_node * __init -find_device_prom_node(struct pci_pbm_info *pbm, struct pci_dev *pdev, - struct device_node *bus_node, - struct linux_prom_pci_registers **pregs, - int *nregs) +/* Find the OBP PROM device tree node for a PCI device. + * Return zero if not found. + */ +static int __init find_device_prom_node(struct pci_pbm_info *pbm, + struct pci_dev *pdev, + int bus_prom_node, + struct linux_prom_pci_registers *pregs, + int *nregs) { - struct device_node *dp; - - *nregs = 0; + int node; /* * Return the PBM's PROM node in case we are it's PCI device, @@ -55,31 +51,27 @@ find_device_prom_node(struct pci_pbm_info *pbm, struct pci_dev *pdev, pdev->device == PCI_DEVICE_ID_SUN_SCHIZO || pdev->device == PCI_DEVICE_ID_SUN_TOMATILLO || pdev->device == PCI_DEVICE_ID_SUN_SABRE || - pdev->device == PCI_DEVICE_ID_SUN_HUMMINGBIRD)) - return bus_node; - - dp = bus_node->child; - while (dp) { - struct linux_prom_pci_registers *regs; - struct property *prop; - int len; + pdev->device == PCI_DEVICE_ID_SUN_HUMMINGBIRD)) { + *nregs = 0; + return bus_prom_node; + } - prop = of_find_property(dp, "reg", &len); - if (!prop) + node = prom_getchild(bus_prom_node); + while (node != 0) { + int err = prom_getproperty(node, "reg", + (char *)pregs, + sizeof(*pregs) * PROMREG_MAX); + if (err == 0 || err == -1) goto do_next_sibling; - - regs = prop->value; - if (((regs[0].phys_hi >> 8) & 0xff) == pdev->devfn) { - *pregs = regs; - *nregs = len / sizeof(struct linux_prom_pci_registers); - return dp; + if (((pregs[0].phys_hi >> 8) & 0xff) == pdev->devfn) { + *nregs = err / sizeof(*pregs); + return node; } do_next_sibling: - dp = dp->sibling; + node = prom_getsibling(node); } - - return NULL; + return 0; } /* Older versions of OBP on PCI systems encode 64-bit MEM @@ -136,17 +128,15 @@ static void __init fixup_obp_assignments(struct pci_dev *pdev, */ static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm, struct pci_dev *pdev, - struct device_node *bus_node) + int bus_prom_node) { - struct linux_prom_pci_registers *pregs = NULL; + struct linux_prom_pci_registers pregs[PROMREG_MAX]; struct pcidev_cookie *pcp; - struct device_node *dp; - struct property *prop; - int nregs, len; + int device_prom_node, nregs, err; - dp = find_device_prom_node(pbm, pdev, bus_node, - &pregs, &nregs); - if (!dp) { + device_prom_node = find_device_prom_node(pbm, pdev, bus_prom_node, + pregs, &nregs); + if (device_prom_node == 0) { /* If it is not in the OBP device tree then * there must be a damn good reason for it. * @@ -160,44 +150,45 @@ static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm, return; } - pcp = kzalloc(sizeof(*pcp), GFP_ATOMIC); + pcp = kmalloc(sizeof(*pcp), GFP_ATOMIC); if (pcp == NULL) { prom_printf("PCI_COOKIE: Fatal malloc error, aborting...\n"); prom_halt(); } pcp->pbm = pbm; - pcp->prom_node = dp; - pcp->op = of_find_device_by_node(dp); - memcpy(pcp->prom_regs, pregs, - nregs * sizeof(struct linux_prom_pci_registers)); + pcp->prom_node = device_prom_node; + memcpy(pcp->prom_regs, pregs, sizeof(pcp->prom_regs)); pcp->num_prom_regs = nregs; - - /* We can't have the pcidev_cookie assignments be just - * direct pointers into the property value, since they - * are potentially modified by the probing process. - */ - prop = of_find_property(dp, "assigned-addresses", &len); - if (!prop) { + err = prom_getproperty(device_prom_node, "name", + pcp->prom_name, sizeof(pcp->prom_name)); + if (err > 0) + pcp->prom_name[err] = 0; + else + pcp->prom_name[0] = 0; + + err = prom_getproperty(device_prom_node, + "assigned-addresses", + (char *)pcp->prom_assignments, + sizeof(pcp->prom_assignments)); + if (err == 0 || err == -1) pcp->num_prom_assignments = 0; - } else { - memcpy(pcp->prom_assignments, prop->value, len); + else pcp->num_prom_assignments = - (len / sizeof(pcp->prom_assignments[0])); - } + (err / sizeof(pcp->prom_assignments[0])); - if (strcmp(dp->name, "ebus") == 0) { - struct linux_prom_ebus_ranges *erng; + if (strcmp(pcp->prom_name, "ebus") == 0) { + struct linux_prom_ebus_ranges erng[PROM_PCIRNG_MAX]; int iter; /* EBUS is special... */ - prop = of_find_property(dp, "ranges", &len); - if (!prop) { + err = prom_getproperty(device_prom_node, "ranges", + (char *)&erng[0], sizeof(erng)); + if (err == 0 || err == -1) { prom_printf("EBUS: Fatal error, no range property\n"); prom_halt(); } - erng = prop->value; - len = (len / sizeof(erng[0])); - for (iter = 0; iter < len; iter++) { + err = (err / sizeof(erng[0])); + for(iter = 0; iter < err; iter++) { struct linux_prom_ebus_ranges *ep = &erng[iter]; struct linux_prom_pci_registers *ap; @@ -209,7 +200,7 @@ static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm, ap->size_hi = 0; ap->size_lo = ep->size; } - pcp->num_prom_assignments = len; + pcp->num_prom_assignments = err; } fixup_obp_assignments(pdev, pcp); @@ -219,7 +210,7 @@ static void __init pdev_cookie_fillin(struct pci_pbm_info *pbm, void __init pci_fill_in_pbm_cookies(struct pci_bus *pbus, struct pci_pbm_info *pbm, - struct device_node *dp) + int prom_node) { struct pci_dev *pdev, *pdev_next; struct pci_bus *this_pbus, *pbus_next; @@ -227,7 +218,7 @@ void __init pci_fill_in_pbm_cookies(struct pci_bus *pbus, /* This must be _safe because the cookie fillin routine can delete devices from the tree. */ list_for_each_entry_safe(pdev, pdev_next, &pbus->devices, bus_list) - pdev_cookie_fillin(pbm, pdev, dp); + pdev_cookie_fillin(pbm, pdev, prom_node); list_for_each_entry_safe(this_pbus, pbus_next, &pbus->children, node) { struct pcidev_cookie *pcp = this_pbus->self->sysdata; @@ -250,6 +241,7 @@ static void __init bad_assignment(struct pci_dev *pdev, if (res) prom_printf("PCI: RES[%016lx-->%016lx:(%lx)]\n", res->start, res->end, res->flags); + prom_printf("Please email this information to davem@redhat.com\n"); if (do_prom_halt) prom_halt(); } @@ -281,7 +273,8 @@ __init get_root_resource(struct linux_prom_pci_registers *ap, return &pbm->mem_space; default: - printk("PCI: What is resource space %x?\n", space); + printk("PCI: What is resource space %x? " + "Tell davem@redhat.com about it!\n", space); return NULL; }; } @@ -330,6 +323,19 @@ __init get_device_resource(struct linux_prom_pci_registers *ap, return res; } +static int __init pdev_resource_collisions_expected(struct pci_dev *pdev) +{ + if (pdev->vendor != PCI_VENDOR_ID_SUN) + return 0; + + if (pdev->device == PCI_DEVICE_ID_SUN_RIO_EBUS || + pdev->device == PCI_DEVICE_ID_SUN_RIO_1394 || + pdev->device == PCI_DEVICE_ID_SUN_RIO_USB) + return 1; + + return 0; +} + static void __init pdev_record_assignments(struct pci_pbm_info *pbm, struct pci_dev *pdev) { @@ -387,23 +393,19 @@ static void __init pdev_record_assignments(struct pci_pbm_info *pbm, pbm->parent->resource_adjust(pdev, res, root); if (request_resource(root, res) < 0) { - int rnum; - /* OK, there is some conflict. But this is fine * since we'll reassign it in the fixup pass. * - * Do not print the warning for ROM resources - * as such a conflict is quite common and - * harmless as the ROM bar is disabled. + * We notify the user that OBP made an error if it + * is a case we don't expect. */ - rnum = (res - &pdev->resource[0]); - if (rnum != PCI_ROM_RESOURCE) - printk(KERN_ERR "PCI: Resource collision, " - "region %d " + if (!pdev_resource_collisions_expected(pdev)) { + printk(KERN_ERR "PCI: Address space collision on region %ld " "[%016lx:%016lx] of device %s\n", - rnum, + (res - &pdev->resource[0]), res->start, res->end, pci_name(pdev)); + } } } } @@ -539,18 +541,237 @@ void __init pci_assign_unassigned(struct pci_pbm_info *pbm, pci_assign_unassigned(pbm, bus); } +static int __init pci_intmap_match(struct pci_dev *pdev, unsigned int *interrupt) +{ + struct linux_prom_pci_intmap bridge_local_intmap[PROM_PCIIMAP_MAX], *intmap; + struct linux_prom_pci_intmask bridge_local_intmask, *intmask; + struct pcidev_cookie *dev_pcp = pdev->sysdata; + struct pci_pbm_info *pbm = dev_pcp->pbm; + struct linux_prom_pci_registers *pregs = dev_pcp->prom_regs; + unsigned int hi, mid, lo, irq; + int i, num_intmap, map_slot; + + intmap = &pbm->pbm_intmap[0]; + intmask = &pbm->pbm_intmask; + num_intmap = pbm->num_pbm_intmap; + map_slot = 0; + + /* If we are underneath a PCI bridge, use PROM register + * property of the parent bridge which is closest to + * the PBM. + * + * However if that parent bridge has interrupt map/mask + * properties of its own we use the PROM register property + * of the next child device on the path to PDEV. + * + * In detail the two cases are (note that the 'X' below is the + * 'next child on the path to PDEV' mentioned above): + * + * 1) PBM --> PCI bus lacking int{map,mask} --> X ... PDEV + * + * Here we use regs of 'PCI bus' device. + * + * 2) PBM --> PCI bus with int{map,mask} --> X ... PDEV + * + * Here we use regs of 'X'. Note that X can be PDEV. + */ + if (pdev->bus->number != pbm->pci_first_busno) { + struct pcidev_cookie *bus_pcp, *regs_pcp; + struct pci_dev *bus_dev, *regs_dev; + int plen; + + bus_dev = pdev->bus->self; + regs_dev = pdev; + + while (bus_dev->bus && + bus_dev->bus->number != pbm->pci_first_busno) { + regs_dev = bus_dev; + bus_dev = bus_dev->bus->self; + } + + regs_pcp = regs_dev->sysdata; + pregs = regs_pcp->prom_regs; + + bus_pcp = bus_dev->sysdata; + + /* But if the PCI bridge has it's own interrupt map + * and mask properties, use that and the regs of the + * PCI entity at the next level down on the path to the + * device. + */ + plen = prom_getproperty(bus_pcp->prom_node, "interrupt-map", + (char *) &bridge_local_intmap[0], + sizeof(bridge_local_intmap)); + if (plen != -1) { + intmap = &bridge_local_intmap[0]; + num_intmap = plen / sizeof(struct linux_prom_pci_intmap); + plen = prom_getproperty(bus_pcp->prom_node, + "interrupt-map-mask", + (char *) &bridge_local_intmask, + sizeof(bridge_local_intmask)); + if (plen == -1) { + printk("pci_intmap_match: Warning! Bridge has intmap " + "but no intmask.\n"); + printk("pci_intmap_match: Trying to recover.\n"); + return 0; + } + + if (pdev->bus->self != bus_dev) + map_slot = 1; + } else { + pregs = bus_pcp->prom_regs; + map_slot = 1; + } + } + + if (map_slot) { + *interrupt = ((*interrupt + - 1 + + PCI_SLOT(pdev->devfn)) & 0x3) + 1; + } + + hi = pregs->phys_hi & intmask->phys_hi; + mid = pregs->phys_mid & intmask->phys_mid; + lo = pregs->phys_lo & intmask->phys_lo; + irq = *interrupt & intmask->interrupt; + + for (i = 0; i < num_intmap; i++) { + if (intmap[i].phys_hi == hi && + intmap[i].phys_mid == mid && + intmap[i].phys_lo == lo && + intmap[i].interrupt == irq) { + *interrupt = intmap[i].cinterrupt; + printk("PCI-IRQ: Routing bus[%2x] slot[%2x] map[%d] to INO[%02x]\n", + pdev->bus->number, PCI_SLOT(pdev->devfn), + map_slot, *interrupt); + return 1; + } + } + + /* We will run this code even if pbm->num_pbm_intmap is zero, just so + * we can apply the slot mapping to the PROM interrupt property value. + * So do not spit out these warnings in that case. + */ + if (num_intmap != 0) { + /* Print it both to OBP console and kernel one so that if bootup + * hangs here the user has the information to report. + */ + prom_printf("pci_intmap_match: bus %02x, devfn %02x: ", + pdev->bus->number, pdev->devfn); + prom_printf("IRQ [%08x.%08x.%08x.%08x] not found in interrupt-map\n", + pregs->phys_hi, pregs->phys_mid, pregs->phys_lo, *interrupt); + prom_printf("Please email this information to davem@redhat.com\n"); + + printk("pci_intmap_match: bus %02x, devfn %02x: ", + pdev->bus->number, pdev->devfn); + printk("IRQ [%08x.%08x.%08x.%08x] not found in interrupt-map\n", + pregs->phys_hi, pregs->phys_mid, pregs->phys_lo, *interrupt); + printk("Please email this information to davem@redhat.com\n"); + } + + return 0; +} + static void __init pdev_fixup_irq(struct pci_dev *pdev) { struct pcidev_cookie *pcp = pdev->sysdata; - struct of_device *op = pcp->op; + struct pci_pbm_info *pbm = pcp->pbm; + struct pci_controller_info *p = pbm->parent; + unsigned int portid = pbm->portid; + unsigned int prom_irq; + int prom_node = pcp->prom_node; + int err; + + /* If this is an empty EBUS device, sometimes OBP fails to + * give it a valid fully specified interrupts property. + * The EBUS hooked up to SunHME on PCI I/O boards of + * Ex000 systems is one such case. + * + * The interrupt is not important so just ignore it. + */ + if (pdev->vendor == PCI_VENDOR_ID_SUN && + pdev->device == PCI_DEVICE_ID_SUN_EBUS && + !prom_getchild(prom_node)) { + pdev->irq = 0; + return; + } - if (op->irqs[0] == 0xffffffff) { - pdev->irq = PCI_IRQ_NONE; + err = prom_getproperty(prom_node, "interrupts", + (char *)&prom_irq, sizeof(prom_irq)); + if (err == 0 || err == -1) { + pdev->irq = 0; return; } - pdev->irq = op->irqs[0]; + /* Fully specified already? */ + if (((prom_irq & PCI_IRQ_IGN) >> 6) == portid) { + pdev->irq = p->irq_build(pbm, pdev, prom_irq); + goto have_irq; + } + + /* An onboard device? (bit 5 set) */ + if ((prom_irq & PCI_IRQ_INO) & 0x20) { + pdev->irq = p->irq_build(pbm, pdev, (portid << 6 | prom_irq)); + goto have_irq; + } + + /* Can we find a matching entry in the interrupt-map? */ + if (pci_intmap_match(pdev, &prom_irq)) { + pdev->irq = p->irq_build(pbm, pdev, (portid << 6) | prom_irq); + goto have_irq; + } + + /* Ok, we have to do it the hard way. */ + { + unsigned int bus, slot, line; + + bus = (pbm == &pbm->parent->pbm_B) ? (1 << 4) : 0; + + /* If we have a legal interrupt property, use it as + * the IRQ line. + */ + if (prom_irq > 0 && prom_irq < 5) { + line = ((prom_irq - 1) & 3); + } else { + u8 pci_irq_line; + + /* Else just directly consult PCI config space. */ + pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pci_irq_line); + line = ((pci_irq_line - 1) & 3); + } + + /* Now figure out the slot. + * + * Basically, device number zero on the top-level bus is + * always the PCI host controller. Slot 0 is then device 1. + * PBM A supports two external slots (0 and 1), and PBM B + * supports 4 external slots (0, 1, 2, and 3). On-board PCI + * devices are wired to device numbers outside of these + * ranges. -DaveM + */ + if (pdev->bus->number == pbm->pci_first_busno) { + slot = PCI_SLOT(pdev->devfn) - pbm->pci_first_slot; + } else { + struct pci_dev *bus_dev; + + /* Underneath a bridge, use slot number of parent + * bridge which is closest to the PBM. + */ + bus_dev = pdev->bus->self; + while (bus_dev->bus && + bus_dev->bus->number != pbm->pci_first_busno) + bus_dev = bus_dev->bus->self; + + slot = PCI_SLOT(bus_dev->devfn) - pbm->pci_first_slot; + } + slot = slot << 2; + + pdev->irq = p->irq_build(pbm, pdev, + ((portid << 6) & PCI_IRQ_IGN) | + (bus | slot | line)); + } +have_irq: pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, pdev->irq & PCI_IRQ_INO); } @@ -706,30 +927,33 @@ void pci_register_legacy_regions(struct resource *io_res, struct resource *p; /* VGA Video RAM. */ - p = kzalloc(sizeof(*p), GFP_KERNEL); + p = kmalloc(sizeof(*p), GFP_KERNEL); if (!p) return; + memset(p, 0, sizeof(*p)); p->name = "Video RAM area"; p->start = mem_res->start + 0xa0000UL; p->end = p->start + 0x1ffffUL; p->flags = IORESOURCE_BUSY; request_resource(mem_res, p); - p = kzalloc(sizeof(*p), GFP_KERNEL); + p = kmalloc(sizeof(*p), GFP_KERNEL); if (!p) return; + memset(p, 0, sizeof(*p)); p->name = "System ROM"; p->start = mem_res->start + 0xf0000UL; p->end = p->start + 0xffffUL; p->flags = IORESOURCE_BUSY; request_resource(mem_res, p); - p = kzalloc(sizeof(*p), GFP_KERNEL); + p = kmalloc(sizeof(*p), GFP_KERNEL); if (!p) return; + memset(p, 0, sizeof(*p)); p->name = "Video ROM"; p->start = mem_res->start + 0xc0000UL; p->end = p->start + 0x7fffUL;