diff options
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) |