diff options
author | David S. Miller <davem@sunset.davemloft.net> | 2006-02-17 02:01:10 -0500 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2006-03-20 04:13:20 -0500 |
commit | 6154f94f0e1b3984ad2d0bcda586bc8946398b8a (patch) | |
tree | 8abc4d83ed8ddef986de2c0593f55d3e879c7c78 /arch/sparc64/kernel/pci_common.c | |
parent | 14f6689cbb3ec2c194bd770fbe0d6e2d90eb6760 (diff) |
[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: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'arch/sparc64/kernel/pci_common.c')
-rw-r--r-- | arch/sparc64/kernel/pci_common.c | 269 |
1 files changed, 155 insertions, 114 deletions
diff --git a/arch/sparc64/kernel/pci_common.c b/arch/sparc64/kernel/pci_common.c index ab6a2e1b76fb..f9101966a746 100644 --- a/arch/sparc64/kernel/pci_common.c +++ b/arch/sparc64/kernel/pci_common.c | |||
@@ -541,142 +541,183 @@ void __init pci_assign_unassigned(struct pci_pbm_info *pbm, | |||
541 | pci_assign_unassigned(pbm, bus); | 541 | pci_assign_unassigned(pbm, bus); |
542 | } | 542 | } |
543 | 543 | ||
544 | static int __init pci_intmap_match(struct pci_dev *pdev, unsigned int *interrupt) | 544 | static inline unsigned int pci_slot_swivel(struct pci_pbm_info *pbm, |
545 | struct pci_dev *toplevel_pdev, | ||
546 | struct pci_dev *pdev, | ||
547 | unsigned int interrupt) | ||
545 | { | 548 | { |
546 | struct linux_prom_pci_intmap bridge_local_intmap[PROM_PCIIMAP_MAX], *intmap; | 549 | unsigned int ret; |
547 | struct linux_prom_pci_intmask bridge_local_intmask, *intmask; | ||
548 | struct pcidev_cookie *dev_pcp = pdev->sysdata; | ||
549 | struct pci_pbm_info *pbm = dev_pcp->pbm; | ||
550 | struct linux_prom_pci_registers *pregs = dev_pcp->prom_regs; | ||
551 | unsigned int hi, mid, lo, irq; | ||
552 | int i, num_intmap, map_slot; | ||
553 | 550 | ||
554 | intmap = &pbm->pbm_intmap[0]; | 551 | if (unlikely(interrupt < 1 || interrupt > 4)) { |
555 | intmask = &pbm->pbm_intmask; | 552 | printk("%s: Device %s interrupt value of %u is strange.\n", |
556 | num_intmap = pbm->num_pbm_intmap; | 553 | pbm->name, pci_name(pdev), interrupt); |
557 | map_slot = 0; | 554 | return interrupt; |
555 | } | ||
558 | 556 | ||
559 | /* If we are underneath a PCI bridge, use PROM register | 557 | ret = ((interrupt - 1 + (PCI_SLOT(pdev->devfn) & 3)) & 3) + 1; |
560 | * property of the parent bridge which is closest to | 558 | |
561 | * the PBM. | 559 | printk("%s: %s IRQ Swivel %s [%x:%x] -> [%x]\n", |
562 | * | 560 | pbm->name, pci_name(toplevel_pdev), pci_name(pdev), |
563 | * However if that parent bridge has interrupt map/mask | 561 | interrupt, PCI_SLOT(pdev->devfn), ret); |
564 | * properties of its own we use the PROM register property | ||
565 | * of the next child device on the path to PDEV. | ||
566 | * | ||
567 | * In detail the two cases are (note that the 'X' below is the | ||
568 | * 'next child on the path to PDEV' mentioned above): | ||
569 | * | ||
570 | * 1) PBM --> PCI bus lacking int{map,mask} --> X ... PDEV | ||
571 | * | ||
572 | * Here we use regs of 'PCI bus' device. | ||
573 | * | ||
574 | * 2) PBM --> PCI bus with int{map,mask} --> X ... PDEV | ||
575 | * | ||
576 | * Here we use regs of 'X'. Note that X can be PDEV. | ||
577 | */ | ||
578 | if (pdev->bus->number != pbm->pci_first_busno) { | ||
579 | struct pcidev_cookie *bus_pcp, *regs_pcp; | ||
580 | struct pci_dev *bus_dev, *regs_dev; | ||
581 | int plen; | ||
582 | 562 | ||
583 | bus_dev = pdev->bus->self; | 563 | return ret; |
584 | bus_pcp = bus_dev->sysdata; | 564 | } |
585 | regs_dev = pdev; | ||
586 | regs_pcp = regs_dev->sysdata; | ||
587 | 565 | ||
588 | while (bus_dev->bus && | 566 | static inline unsigned int pci_apply_intmap(struct pci_pbm_info *pbm, |
589 | bus_dev->bus->number != pbm->pci_first_busno && | 567 | struct pci_dev *toplevel_pdev, |
590 | prom_getproplen(bus_pcp->prom_node, | 568 | struct pci_dev *pbus, |
591 | "interrupt-map") <= 0) { | 569 | struct pci_dev *pdev, |
592 | regs_dev = bus_dev; | 570 | unsigned int interrupt, |
593 | regs_pcp = regs_dev->sysdata; | 571 | unsigned int *cnode) |
572 | { | ||
573 | struct linux_prom_pci_intmap imap[PROM_PCIIMAP_MAX]; | ||
574 | struct linux_prom_pci_intmask imask; | ||
575 | struct pcidev_cookie *pbus_pcp = pbus->sysdata; | ||
576 | struct pcidev_cookie *pdev_pcp = pdev->sysdata; | ||
577 | struct linux_prom_pci_registers *pregs = pdev_pcp->prom_regs; | ||
578 | int plen, num_imap, i; | ||
579 | unsigned int hi, mid, lo, irq, orig_interrupt; | ||
580 | |||
581 | *cnode = pbus_pcp->prom_node; | ||
582 | |||
583 | plen = prom_getproperty(pbus_pcp->prom_node, "interrupt-map", | ||
584 | (char *) &imap[0], sizeof(imap)); | ||
585 | if (plen <= 0 || | ||
586 | (plen % sizeof(struct linux_prom_pci_intmap)) != 0) { | ||
587 | printk("%s: Device %s interrupt-map has bad len %d\n", | ||
588 | pbm->name, pci_name(pbus), plen); | ||
589 | goto no_intmap; | ||
590 | } | ||
591 | num_imap = plen / sizeof(struct linux_prom_pci_intmap); | ||
592 | |||
593 | plen = prom_getproperty(pbus_pcp->prom_node, "interrupt-map-mask", | ||
594 | (char *) &imask, sizeof(imask)); | ||
595 | if (plen <= 0 || | ||
596 | (plen % sizeof(struct linux_prom_pci_intmask)) != 0) { | ||
597 | printk("%s: Device %s interrupt-map-mask has bad len %d\n", | ||
598 | pbm->name, pci_name(pbus), plen); | ||
599 | goto no_intmap; | ||
600 | } | ||
594 | 601 | ||
595 | bus_dev = bus_dev->bus->self; | 602 | orig_interrupt = interrupt; |
596 | bus_pcp = bus_dev->sysdata; | 603 | |
604 | hi = pregs->phys_hi & imask.phys_hi; | ||
605 | mid = pregs->phys_mid & imask.phys_mid; | ||
606 | lo = pregs->phys_lo & imask.phys_lo; | ||
607 | irq = interrupt & imask.interrupt; | ||
608 | |||
609 | for (i = 0; i < num_imap; i++) { | ||
610 | if (imap[i].phys_hi == hi && | ||
611 | imap[i].phys_mid == mid && | ||
612 | imap[i].phys_lo == lo && | ||
613 | imap[i].interrupt == irq) { | ||
614 | *cnode = imap[i].cnode; | ||
615 | interrupt = imap[i].cinterrupt; | ||
597 | } | 616 | } |
617 | } | ||
598 | 618 | ||
599 | pregs = regs_pcp->prom_regs; | 619 | printk("%s: %s MAP BUS %s DEV %s [%x] -> [%x]\n", |
620 | pbm->name, pci_name(toplevel_pdev), | ||
621 | pci_name(pbus), pci_name(pdev), | ||
622 | orig_interrupt, interrupt); | ||
600 | 623 | ||
624 | no_intmap: | ||
625 | return interrupt; | ||
626 | } | ||
601 | 627 | ||
602 | /* But if the PCI bridge has it's own interrupt map | 628 | /* For each PCI bus on the way to the root: |
603 | * and mask properties, use that and the regs of the | 629 | * 1) If it has an interrupt-map property, apply it. |
604 | * PCI entity at the next level down on the path to the | 630 | * 2) Else, swivel the interrupt number based upon the PCI device number. |
605 | * device. | 631 | * |
606 | */ | 632 | * Return the "IRQ controller" node. If this is the PBM's device node, |
607 | plen = prom_getproperty(bus_pcp->prom_node, "interrupt-map", | 633 | * all interrupt translations are complete, else we should use that node's |
608 | (char *) &bridge_local_intmap[0], | 634 | * "reg" property to apply the PBM's "interrupt-{map,mask}" to the interrupt. |
609 | sizeof(bridge_local_intmap)); | 635 | */ |
610 | if (plen != -1) { | 636 | static unsigned int __init pci_intmap_match_to_root(struct pci_pbm_info *pbm, |
611 | intmap = &bridge_local_intmap[0]; | 637 | struct pci_dev *pdev, |
612 | num_intmap = plen / sizeof(struct linux_prom_pci_intmap); | 638 | unsigned int *interrupt) |
613 | plen = prom_getproperty(bus_pcp->prom_node, | 639 | { |
614 | "interrupt-map-mask", | 640 | struct pci_dev *toplevel_pdev = pdev; |
615 | (char *) &bridge_local_intmask, | 641 | struct pcidev_cookie *toplevel_pcp = toplevel_pdev->sysdata; |
616 | sizeof(bridge_local_intmask)); | 642 | unsigned int cnode = toplevel_pcp->prom_node; |
617 | if (plen == -1) { | ||
618 | printk("pci_intmap_match: Warning! Bridge has intmap " | ||
619 | "but no intmask.\n"); | ||
620 | printk("pci_intmap_match: Trying to recover.\n"); | ||
621 | return 0; | ||
622 | } | ||
623 | 643 | ||
624 | intmask = &bridge_local_intmask; | 644 | while (pdev->bus->number != pbm->pci_first_busno) { |
645 | struct pci_dev *pbus = pdev->bus->self; | ||
646 | struct pcidev_cookie *pcp = pbus->sysdata; | ||
647 | int plen; | ||
625 | 648 | ||
626 | if (pdev->bus->self != bus_dev) | 649 | plen = prom_getproplen(pcp->prom_node, "interrupt-map"); |
627 | map_slot = 1; | 650 | if (plen <= 0) { |
651 | *interrupt = pci_slot_swivel(pbm, toplevel_pdev, | ||
652 | pdev, *interrupt); | ||
653 | cnode = pcp->prom_node; | ||
628 | } else { | 654 | } else { |
629 | pregs = bus_pcp->prom_regs; | 655 | *interrupt = pci_apply_intmap(pbm, toplevel_pdev, |
630 | map_slot = 1; | 656 | pbus, pdev, |
657 | *interrupt, &cnode); | ||
658 | |||
659 | while (pcp->prom_node != cnode && | ||
660 | pbus->bus->number != pbm->pci_first_busno) { | ||
661 | pbus = pbus->bus->self; | ||
662 | pcp = pbus->sysdata; | ||
663 | } | ||
631 | } | 664 | } |
632 | } | 665 | pdev = pbus; |
633 | 666 | ||
634 | if (map_slot) { | 667 | if (cnode == pbm->prom_node) |
635 | *interrupt = ((*interrupt | 668 | break; |
636 | - 1 | ||
637 | + PCI_SLOT(pdev->devfn)) & 0x3) + 1; | ||
638 | } | 669 | } |
639 | 670 | ||
640 | hi = pregs->phys_hi & intmask->phys_hi; | 671 | return cnode; |
641 | mid = pregs->phys_mid & intmask->phys_mid; | 672 | } |
642 | lo = pregs->phys_lo & intmask->phys_lo; | 673 | |
643 | irq = *interrupt & intmask->interrupt; | 674 | static int __init pci_intmap_match(struct pci_dev *pdev, unsigned int *interrupt) |
644 | 675 | { | |
645 | for (i = 0; i < num_intmap; i++) { | 676 | struct pcidev_cookie *dev_pcp = pdev->sysdata; |
646 | if (intmap[i].phys_hi == hi && | 677 | struct pci_pbm_info *pbm = dev_pcp->pbm; |
647 | intmap[i].phys_mid == mid && | 678 | struct linux_prom_pci_registers reg; |
648 | intmap[i].phys_lo == lo && | 679 | unsigned int hi, mid, lo, irq; |
649 | intmap[i].interrupt == irq) { | 680 | int i, cnode, plen; |
650 | *interrupt = intmap[i].cinterrupt; | 681 | |
651 | printk("PCI-IRQ: Routing bus[%2x] slot[%2x] map[%d] to INO[%02x]\n", | 682 | cnode = pci_intmap_match_to_root(pbm, pdev, interrupt); |
652 | pdev->bus->number, PCI_SLOT(pdev->devfn), | 683 | if (cnode == pbm->prom_node) |
653 | map_slot, *interrupt); | 684 | goto success; |
654 | return 1; | 685 | |
655 | } | 686 | plen = prom_getproperty(cnode, "reg", (char *) ®, sizeof(reg)); |
687 | if (plen <= 0 || | ||
688 | (plen % sizeof(struct linux_prom_pci_registers)) != 0) { | ||
689 | printk("%s: OBP node %x reg property has bad len %d\n", | ||
690 | pbm->name, cnode, plen); | ||
691 | goto fail; | ||
656 | } | 692 | } |
657 | 693 | ||
658 | /* We will run this code even if pbm->num_pbm_intmap is zero, just so | 694 | hi = reg.phys_hi & pbm->pbm_intmask.phys_hi; |
659 | * we can apply the slot mapping to the PROM interrupt property value. | 695 | mid = reg.phys_mid & pbm->pbm_intmask.phys_mid; |
660 | * So do not spit out these warnings in that case. | 696 | lo = reg.phys_lo & pbm->pbm_intmask.phys_lo; |
661 | */ | 697 | irq = *interrupt & pbm->pbm_intmask.interrupt; |
662 | if (num_intmap != 0) { | 698 | |
663 | /* Print it both to OBP console and kernel one so that if bootup | 699 | for (i = 0; i < pbm->num_pbm_intmap; i++) { |
664 | * hangs here the user has the information to report. | 700 | struct linux_prom_pci_intmap *intmap; |
665 | */ | 701 | |
666 | prom_printf("pci_intmap_match: bus %02x, devfn %02x: ", | 702 | intmap = &pbm->pbm_intmap[i]; |
667 | pdev->bus->number, pdev->devfn); | 703 | |
668 | prom_printf("IRQ [%08x.%08x.%08x.%08x] not found in interrupt-map\n", | 704 | if (intmap->phys_hi == hi && |
669 | pregs->phys_hi, pregs->phys_mid, pregs->phys_lo, *interrupt); | 705 | intmap->phys_mid == mid && |
670 | prom_printf("Please email this information to davem@redhat.com\n"); | 706 | intmap->phys_lo == lo && |
671 | 707 | intmap->interrupt == irq) { | |
672 | printk("pci_intmap_match: bus %02x, devfn %02x: ", | 708 | *interrupt = intmap->cinterrupt; |
673 | pdev->bus->number, pdev->devfn); | 709 | goto success; |
674 | printk("IRQ [%08x.%08x.%08x.%08x] not found in interrupt-map\n", | 710 | } |
675 | pregs->phys_hi, pregs->phys_mid, pregs->phys_lo, *interrupt); | ||
676 | printk("Please email this information to davem@redhat.com\n"); | ||
677 | } | 711 | } |
678 | 712 | ||
713 | fail: | ||
679 | return 0; | 714 | return 0; |
715 | |||
716 | success: | ||
717 | printk("PCI-IRQ: Routing bus[%2x] slot[%2x] to INO[%02x]\n", | ||
718 | pdev->bus->number, PCI_SLOT(pdev->devfn), | ||
719 | *interrupt); | ||
720 | return 1; | ||
680 | } | 721 | } |
681 | 722 | ||
682 | static void __init pdev_fixup_irq(struct pci_dev *pdev) | 723 | static void __init pdev_fixup_irq(struct pci_dev *pdev) |