Commit 6154f94f authored by David S. Miller's avatar David S. Miller

[SPARC64]: Rewrite pci_intmap_match().

The whole algorithm was wrong.  What we need to do is:

1) Walk each PCI bus above this device on the path to the
   PCI controller nexus, and for each:
      a) If interrupt-map exists, apply it, record IRQ controller node
      b) Else, swivel interrupt number using PCI_SLOT(), use PCI bus
	 parent OBP node as controller node
      c) Walk up to "controller node" until we hit the first PCI bus
	 in this domain, or "controller node" is the PCI controller
	 OBP node
2) If we walked to PCI controller OBP node, we're done.
3) Else, apply PCI controller interrupt-map to interrupt.

There is some stuff that needs to be checked out for ebus and
isa, but the PCI part is good to go.
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 14f6689c
...@@ -541,142 +541,183 @@ void __init pci_assign_unassigned(struct pci_pbm_info *pbm, ...@@ -541,142 +541,183 @@ void __init pci_assign_unassigned(struct pci_pbm_info *pbm,
pci_assign_unassigned(pbm, bus); pci_assign_unassigned(pbm, bus);
} }
static int __init pci_intmap_match(struct pci_dev *pdev, unsigned int *interrupt) static inline unsigned int pci_slot_swivel(struct pci_pbm_info *pbm,
struct pci_dev *toplevel_pdev,
struct pci_dev *pdev,
unsigned int interrupt)
{ {
struct linux_prom_pci_intmap bridge_local_intmap[PROM_PCIIMAP_MAX], *intmap; unsigned int ret;
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]; if (unlikely(interrupt < 1 || interrupt > 4)) {
intmask = &pbm->pbm_intmask; printk("%s: Device %s interrupt value of %u is strange.\n",
num_intmap = pbm->num_pbm_intmap; pbm->name, pci_name(pdev), interrupt);
map_slot = 0; return interrupt;
}
/* If we are underneath a PCI bridge, use PROM register ret = ((interrupt - 1 + (PCI_SLOT(pdev->devfn) & 3)) & 3) + 1;
* property of the parent bridge which is closest to
* the PBM. printk("%s: %s IRQ Swivel %s [%x:%x] -> [%x]\n",
* pbm->name, pci_name(toplevel_pdev), pci_name(pdev),
* However if that parent bridge has interrupt map/mask interrupt, PCI_SLOT(pdev->devfn), ret);
* 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; return ret;
bus_pcp = bus_dev->sysdata; }
regs_dev = pdev;
regs_pcp = regs_dev->sysdata;
while (bus_dev->bus && static inline unsigned int pci_apply_intmap(struct pci_pbm_info *pbm,
bus_dev->bus->number != pbm->pci_first_busno && struct pci_dev *toplevel_pdev,
prom_getproplen(bus_pcp->prom_node, struct pci_dev *pbus,
"interrupt-map") <= 0) { struct pci_dev *pdev,
regs_dev = bus_dev; unsigned int interrupt,
regs_pcp = regs_dev->sysdata; unsigned int *cnode)
{
struct linux_prom_pci_intmap imap[PROM_PCIIMAP_MAX];
struct linux_prom_pci_intmask imask;
struct pcidev_cookie *pbus_pcp = pbus->sysdata;
struct pcidev_cookie *pdev_pcp = pdev->sysdata;
struct linux_prom_pci_registers *pregs = pdev_pcp->prom_regs;
int plen, num_imap, i;
unsigned int hi, mid, lo, irq, orig_interrupt;
*cnode = pbus_pcp->prom_node;
plen = prom_getproperty(pbus_pcp->prom_node, "interrupt-map",
(char *) &imap[0], sizeof(imap));
if (plen <= 0 ||
(plen % sizeof(struct linux_prom_pci_intmap)) != 0) {
printk("%s: Device %s interrupt-map has bad len %d\n",
pbm->name, pci_name(pbus), plen);
goto no_intmap;
}
num_imap = plen / sizeof(struct linux_prom_pci_intmap);
plen = prom_getproperty(pbus_pcp->prom_node, "interrupt-map-mask",
(char *) &imask, sizeof(imask));
if (plen <= 0 ||
(plen % sizeof(struct linux_prom_pci_intmask)) != 0) {
printk("%s: Device %s interrupt-map-mask has bad len %d\n",
pbm->name, pci_name(pbus), plen);
goto no_intmap;
}
bus_dev = bus_dev->bus->self; orig_interrupt = interrupt;
bus_pcp = bus_dev->sysdata;
hi = pregs->phys_hi & imask.phys_hi;
mid = pregs->phys_mid & imask.phys_mid;
lo = pregs->phys_lo & imask.phys_lo;
irq = interrupt & imask.interrupt;
for (i = 0; i < num_imap; i++) {
if (imap[i].phys_hi == hi &&
imap[i].phys_mid == mid &&
imap[i].phys_lo == lo &&
imap[i].interrupt == irq) {
*cnode = imap[i].cnode;
interrupt = imap[i].cinterrupt;
} }
}
pregs = regs_pcp->prom_regs; printk("%s: %s MAP BUS %s DEV %s [%x] -> [%x]\n",
pbm->name, pci_name(toplevel_pdev),
pci_name(pbus), pci_name(pdev),
orig_interrupt, interrupt);
no_intmap:
return interrupt;
}
/* But if the PCI bridge has it's own interrupt map /* For each PCI bus on the way to the root:
* and mask properties, use that and the regs of the * 1) If it has an interrupt-map property, apply it.
* PCI entity at the next level down on the path to the * 2) Else, swivel the interrupt number based upon the PCI device number.
* device. *
*/ * Return the "IRQ controller" node. If this is the PBM's device node,
plen = prom_getproperty(bus_pcp->prom_node, "interrupt-map", * all interrupt translations are complete, else we should use that node's
(char *) &bridge_local_intmap[0], * "reg" property to apply the PBM's "interrupt-{map,mask}" to the interrupt.
sizeof(bridge_local_intmap)); */
if (plen != -1) { static unsigned int __init pci_intmap_match_to_root(struct pci_pbm_info *pbm,
intmap = &bridge_local_intmap[0]; struct pci_dev *pdev,
num_intmap = plen / sizeof(struct linux_prom_pci_intmap); unsigned int *interrupt)
plen = prom_getproperty(bus_pcp->prom_node, {
"interrupt-map-mask", struct pci_dev *toplevel_pdev = pdev;
(char *) &bridge_local_intmask, struct pcidev_cookie *toplevel_pcp = toplevel_pdev->sysdata;
sizeof(bridge_local_intmask)); unsigned int cnode = toplevel_pcp->prom_node;
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;
}
intmask = &bridge_local_intmask; while (pdev->bus->number != pbm->pci_first_busno) {
struct pci_dev *pbus = pdev->bus->self;
struct pcidev_cookie *pcp = pbus->sysdata;
int plen;
if (pdev->bus->self != bus_dev) plen = prom_getproplen(pcp->prom_node, "interrupt-map");
map_slot = 1; if (plen <= 0) {
*interrupt = pci_slot_swivel(pbm, toplevel_pdev,
pdev, *interrupt);
cnode = pcp->prom_node;
} else { } else {
pregs = bus_pcp->prom_regs; *interrupt = pci_apply_intmap(pbm, toplevel_pdev,
map_slot = 1; pbus, pdev,
*interrupt, &cnode);
while (pcp->prom_node != cnode &&
pbus->bus->number != pbm->pci_first_busno) {
pbus = pbus->bus->self;
pcp = pbus->sysdata;
}
} }
} pdev = pbus;
if (map_slot) { if (cnode == pbm->prom_node)
*interrupt = ((*interrupt break;
- 1
+ PCI_SLOT(pdev->devfn)) & 0x3) + 1;
} }
hi = pregs->phys_hi & intmask->phys_hi; return cnode;
mid = pregs->phys_mid & intmask->phys_mid; }
lo = pregs->phys_lo & intmask->phys_lo;
irq = *interrupt & intmask->interrupt; static int __init pci_intmap_match(struct pci_dev *pdev, unsigned int *interrupt)
{
for (i = 0; i < num_intmap; i++) { struct pcidev_cookie *dev_pcp = pdev->sysdata;
if (intmap[i].phys_hi == hi && struct pci_pbm_info *pbm = dev_pcp->pbm;
intmap[i].phys_mid == mid && struct linux_prom_pci_registers reg;
intmap[i].phys_lo == lo && unsigned int hi, mid, lo, irq;
intmap[i].interrupt == irq) { int i, cnode, plen;
*interrupt = intmap[i].cinterrupt;
printk("PCI-IRQ: Routing bus[%2x] slot[%2x] map[%d] to INO[%02x]\n", cnode = pci_intmap_match_to_root(pbm, pdev, interrupt);
pdev->bus->number, PCI_SLOT(pdev->devfn), if (cnode == pbm->prom_node)
map_slot, *interrupt); goto success;
return 1;
} plen = prom_getproperty(cnode, "reg", (char *) &reg, sizeof(reg));
if (plen <= 0 ||
(plen % sizeof(struct linux_prom_pci_registers)) != 0) {
printk("%s: OBP node %x reg property has bad len %d\n",
pbm->name, cnode, plen);
goto fail;
} }
/* We will run this code even if pbm->num_pbm_intmap is zero, just so hi = reg.phys_hi & pbm->pbm_intmask.phys_hi;
* we can apply the slot mapping to the PROM interrupt property value. mid = reg.phys_mid & pbm->pbm_intmask.phys_mid;
* So do not spit out these warnings in that case. lo = reg.phys_lo & pbm->pbm_intmask.phys_lo;
*/ irq = *interrupt & pbm->pbm_intmask.interrupt;
if (num_intmap != 0) {
/* Print it both to OBP console and kernel one so that if bootup for (i = 0; i < pbm->num_pbm_intmap; i++) {
* hangs here the user has the information to report. struct linux_prom_pci_intmap *intmap;
*/
prom_printf("pci_intmap_match: bus %02x, devfn %02x: ", intmap = &pbm->pbm_intmap[i];
pdev->bus->number, pdev->devfn);
prom_printf("IRQ [%08x.%08x.%08x.%08x] not found in interrupt-map\n", if (intmap->phys_hi == hi &&
pregs->phys_hi, pregs->phys_mid, pregs->phys_lo, *interrupt); intmap->phys_mid == mid &&
prom_printf("Please email this information to davem@redhat.com\n"); intmap->phys_lo == lo &&
intmap->interrupt == irq) {
printk("pci_intmap_match: bus %02x, devfn %02x: ", *interrupt = intmap->cinterrupt;
pdev->bus->number, pdev->devfn); goto success;
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");
} }
fail:
return 0; return 0;
success:
printk("PCI-IRQ: Routing bus[%2x] slot[%2x] to INO[%02x]\n",
pdev->bus->number, PCI_SLOT(pdev->devfn),
*interrupt);
return 1;
} }
static void __init pdev_fixup_irq(struct pci_dev *pdev) static void __init pdev_fixup_irq(struct pci_dev *pdev)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment