diff options
Diffstat (limited to 'arch')
-rw-r--r-- | arch/x86_64/kernel/pci-calgary.c | 196 |
1 files changed, 156 insertions, 40 deletions
diff --git a/arch/x86_64/kernel/pci-calgary.c b/arch/x86_64/kernel/pci-calgary.c index a2828e156c3d..558e68aa6b05 100644 --- a/arch/x86_64/kernel/pci-calgary.c +++ b/arch/x86_64/kernel/pci-calgary.c | |||
@@ -50,8 +50,7 @@ int use_calgary __read_mostly = 0; | |||
50 | #endif /* CONFIG_CALGARY_DEFAULT_ENABLED */ | 50 | #endif /* CONFIG_CALGARY_DEFAULT_ENABLED */ |
51 | 51 | ||
52 | #define PCI_DEVICE_ID_IBM_CALGARY 0x02a1 | 52 | #define PCI_DEVICE_ID_IBM_CALGARY 0x02a1 |
53 | #define PCI_VENDOR_DEVICE_ID_CALGARY \ | 53 | #define PCI_DEVICE_ID_IBM_CALIOC2 0x0308 |
54 | (PCI_VENDOR_ID_IBM | PCI_DEVICE_ID_IBM_CALGARY << 16) | ||
55 | 54 | ||
56 | /* we need these for register space address calculation */ | 55 | /* we need these for register space address calculation */ |
57 | #define START_ADDRESS 0xfe000000 | 56 | #define START_ADDRESS 0xfe000000 |
@@ -193,6 +192,7 @@ static inline unsigned long verify_bit_range(unsigned long* bitmap, | |||
193 | { | 192 | { |
194 | return ~0UL; | 193 | return ~0UL; |
195 | } | 194 | } |
195 | |||
196 | #endif /* CONFIG_IOMMU_DEBUG */ | 196 | #endif /* CONFIG_IOMMU_DEBUG */ |
197 | 197 | ||
198 | static inline unsigned int num_dma_pages(unsigned long dma, unsigned int dmalen) | 198 | static inline unsigned int num_dma_pages(unsigned long dma, unsigned int dmalen) |
@@ -346,9 +346,20 @@ static void iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr, | |||
346 | 346 | ||
347 | static inline struct iommu_table *find_iommu_table(struct device *dev) | 347 | static inline struct iommu_table *find_iommu_table(struct device *dev) |
348 | { | 348 | { |
349 | struct pci_dev *pdev; | ||
350 | struct pci_bus *pbus; | ||
349 | struct iommu_table *tbl; | 351 | struct iommu_table *tbl; |
350 | 352 | ||
351 | tbl = to_pci_dev(dev)->bus->self->sysdata; | 353 | pdev = to_pci_dev(dev); |
354 | |||
355 | /* is the device behind a bridge? */ | ||
356 | if (unlikely(pdev->bus->parent)) | ||
357 | pbus = pdev->bus->parent; | ||
358 | else | ||
359 | pbus = pdev->bus; | ||
360 | |||
361 | tbl = pbus->self->sysdata; | ||
362 | BUG_ON(pdev->bus->parent && (tbl->it_busno != pdev->bus->parent->number)); | ||
352 | 363 | ||
353 | return tbl; | 364 | return tbl; |
354 | } | 365 | } |
@@ -565,6 +576,21 @@ static inline void __iomem* calgary_reg(void __iomem *bar, unsigned long offset) | |||
565 | return (void __iomem*)target; | 576 | return (void __iomem*)target; |
566 | } | 577 | } |
567 | 578 | ||
579 | static inline int is_calioc2(unsigned short device) | ||
580 | { | ||
581 | return (device == PCI_DEVICE_ID_IBM_CALIOC2); | ||
582 | } | ||
583 | |||
584 | static inline int is_calgary(unsigned short device) | ||
585 | { | ||
586 | return (device == PCI_DEVICE_ID_IBM_CALGARY); | ||
587 | } | ||
588 | |||
589 | static inline int is_cal_pci_dev(unsigned short device) | ||
590 | { | ||
591 | return (is_calgary(device) || is_calioc2(device)); | ||
592 | } | ||
593 | |||
568 | static void calgary_tce_cache_blast(struct iommu_table *tbl) | 594 | static void calgary_tce_cache_blast(struct iommu_table *tbl) |
569 | { | 595 | { |
570 | u64 val; | 596 | u64 val; |
@@ -685,8 +711,14 @@ static void __init calgary_reserve_regions(struct pci_dev *dev) | |||
685 | iommu_range_reserve(tbl, bad_dma_address, EMERGENCY_PAGES); | 711 | iommu_range_reserve(tbl, bad_dma_address, EMERGENCY_PAGES); |
686 | 712 | ||
687 | /* avoid the BIOS/VGA first 640KB-1MB region */ | 713 | /* avoid the BIOS/VGA first 640KB-1MB region */ |
688 | start = (640 * 1024); | 714 | /* for CalIOC2 - avoid the entire first 2MB */ |
689 | npages = ((1024 - 640) * 1024) >> PAGE_SHIFT; | 715 | if (is_calgary(dev->device)) { |
716 | start = (640 * 1024); | ||
717 | npages = ((1024 - 640) * 1024) >> PAGE_SHIFT; | ||
718 | } else { /* calioc2 */ | ||
719 | start = 0; | ||
720 | npages = (2 * 1024 * 1024) >> PAGE_SHIFT; | ||
721 | } | ||
690 | iommu_range_reserve(tbl, start, npages); | 722 | iommu_range_reserve(tbl, start, npages); |
691 | 723 | ||
692 | /* reserve the two PCI peripheral memory regions in IO space */ | 724 | /* reserve the two PCI peripheral memory regions in IO space */ |
@@ -721,15 +753,15 @@ static int __init calgary_setup_tar(struct pci_dev *dev, void __iomem *bbar) | |||
721 | 753 | ||
722 | /* zero out all TAR bits under sw control */ | 754 | /* zero out all TAR bits under sw control */ |
723 | val64 &= ~TAR_SW_BITS; | 755 | val64 &= ~TAR_SW_BITS; |
724 | |||
725 | tbl = dev->sysdata; | ||
726 | table_phys = (u64)__pa(tbl->it_base); | 756 | table_phys = (u64)__pa(tbl->it_base); |
757 | |||
727 | val64 |= table_phys; | 758 | val64 |= table_phys; |
728 | 759 | ||
729 | BUG_ON(specified_table_size > TCE_TABLE_SIZE_8M); | 760 | BUG_ON(specified_table_size > TCE_TABLE_SIZE_8M); |
730 | val64 |= (u64) specified_table_size; | 761 | val64 |= (u64) specified_table_size; |
731 | 762 | ||
732 | tbl->tar_val = cpu_to_be64(val64); | 763 | tbl->tar_val = cpu_to_be64(val64); |
764 | |||
733 | writeq(tbl->tar_val, target); | 765 | writeq(tbl->tar_val, target); |
734 | readq(target); /* flush */ | 766 | readq(target); /* flush */ |
735 | 767 | ||
@@ -760,6 +792,43 @@ static void __init calgary_free_bus(struct pci_dev *dev) | |||
760 | bus_info[dev->bus->number].tce_space = NULL; | 792 | bus_info[dev->bus->number].tce_space = NULL; |
761 | } | 793 | } |
762 | 794 | ||
795 | static void calgary_dump_error_regs(struct iommu_table *tbl) | ||
796 | { | ||
797 | void __iomem *bbar = tbl->bbar; | ||
798 | u32 csr, csmr, plssr, mck; | ||
799 | void __iomem *target; | ||
800 | unsigned long phboff = phb_offset(tbl->it_busno); | ||
801 | unsigned long erroff; | ||
802 | u32 errregs[7]; | ||
803 | int i; | ||
804 | |||
805 | /* dump CSR */ | ||
806 | target = calgary_reg(bbar, phboff | PHB_CSR_OFFSET); | ||
807 | csr = be32_to_cpu(readl(target)); | ||
808 | /* dump PLSSR */ | ||
809 | target = calgary_reg(bbar, phboff | PHB_PLSSR_OFFSET); | ||
810 | plssr = be32_to_cpu(readl(target)); | ||
811 | /* dump CSMR */ | ||
812 | target = calgary_reg(bbar, phboff | 0x290); | ||
813 | csmr = be32_to_cpu(readl(target)); | ||
814 | /* dump mck */ | ||
815 | target = calgary_reg(bbar, phboff | 0x800); | ||
816 | mck = be32_to_cpu(readl(target)); | ||
817 | |||
818 | printk(KERN_EMERG "Calgary: 0x%08x@CSR 0x%08x@PLSSR 0x%08x@CSMR " | ||
819 | "0x%08x@MCK\n", csr, plssr, csmr, mck); | ||
820 | |||
821 | /* dump rest of error regs */ | ||
822 | printk(KERN_EMERG "Calgary: "); | ||
823 | for (i = 0; i < ARRAY_SIZE(errregs); i++) { | ||
824 | erroff = (0x810 + (i * 0x10)); /* err regs are at 0x810 - 0x870 */ | ||
825 | target = calgary_reg(bbar, phboff | erroff); | ||
826 | errregs[i] = be32_to_cpu(readl(target)); | ||
827 | printk("0x%08x@0x%lx ", errregs[i], erroff); | ||
828 | } | ||
829 | printk("\n"); | ||
830 | } | ||
831 | |||
763 | static void calgary_watchdog(unsigned long data) | 832 | static void calgary_watchdog(unsigned long data) |
764 | { | 833 | { |
765 | struct pci_dev *dev = (struct pci_dev *)data; | 834 | struct pci_dev *dev = (struct pci_dev *)data; |
@@ -773,13 +842,16 @@ static void calgary_watchdog(unsigned long data) | |||
773 | 842 | ||
774 | /* If no error, the agent ID in the CSR is not valid */ | 843 | /* If no error, the agent ID in the CSR is not valid */ |
775 | if (val32 & CSR_AGENT_MASK) { | 844 | if (val32 & CSR_AGENT_MASK) { |
776 | printk(KERN_EMERG "calgary_watchdog: DMA error on PHB %#x, " | 845 | printk(KERN_EMERG "Calgary: DMA error on PHB %#x\n", |
777 | "CSR = %#x\n", dev->bus->number, val32); | 846 | dev->bus->number); |
847 | calgary_dump_error_regs(tbl); | ||
848 | |||
849 | /* reset error */ | ||
778 | writel(0, target); | 850 | writel(0, target); |
779 | 851 | ||
780 | /* Disable bus that caused the error */ | 852 | /* Disable bus that caused the error */ |
781 | target = calgary_reg(bbar, phb_offset(tbl->it_busno) | | 853 | target = calgary_reg(bbar, phb_offset(tbl->it_busno) | |
782 | PHB_CONFIG_RW_OFFSET); | 854 | PHB_CONFIG_RW_OFFSET); |
783 | val32 = be32_to_cpu(readl(target)); | 855 | val32 = be32_to_cpu(readl(target)); |
784 | val32 |= PHB_SLOT_DISABLE; | 856 | val32 |= PHB_SLOT_DISABLE; |
785 | writel(cpu_to_be32(val32), target); | 857 | writel(cpu_to_be32(val32), target); |
@@ -853,7 +925,9 @@ static void __init calgary_enable_translation(struct pci_dev *dev) | |||
853 | val32 = be32_to_cpu(readl(target)); | 925 | val32 = be32_to_cpu(readl(target)); |
854 | val32 |= PHB_TCE_ENABLE | PHB_DAC_DISABLE | PHB_MCSR_ENABLE; | 926 | val32 |= PHB_TCE_ENABLE | PHB_DAC_DISABLE | PHB_MCSR_ENABLE; |
855 | 927 | ||
856 | printk(KERN_INFO "Calgary: enabling translation on PHB %#x\n", busnum); | 928 | printk(KERN_INFO "Calgary: enabling translation on %s PHB %#x\n", |
929 | (dev->device == PCI_DEVICE_ID_IBM_CALGARY) ? | ||
930 | "Calgary" : "CalIOC2", busnum); | ||
857 | printk(KERN_INFO "Calgary: errant DMAs will now be prevented on this " | 931 | printk(KERN_INFO "Calgary: errant DMAs will now be prevented on this " |
858 | "bus.\n"); | 932 | "bus.\n"); |
859 | 933 | ||
@@ -894,7 +968,12 @@ static void __init calgary_init_one_nontraslated(struct pci_dev *dev) | |||
894 | { | 968 | { |
895 | pci_dev_get(dev); | 969 | pci_dev_get(dev); |
896 | dev->sysdata = NULL; | 970 | dev->sysdata = NULL; |
897 | dev->bus->self = dev; | 971 | |
972 | /* is the device behind a bridge? */ | ||
973 | if (dev->bus->parent) | ||
974 | dev->bus->parent->self = dev; | ||
975 | else | ||
976 | dev->bus->self = dev; | ||
898 | } | 977 | } |
899 | 978 | ||
900 | static int __init calgary_init_one(struct pci_dev *dev) | 979 | static int __init calgary_init_one(struct pci_dev *dev) |
@@ -911,7 +990,14 @@ static int __init calgary_init_one(struct pci_dev *dev) | |||
911 | goto done; | 990 | goto done; |
912 | 991 | ||
913 | pci_dev_get(dev); | 992 | pci_dev_get(dev); |
914 | dev->bus->self = dev; | 993 | |
994 | if (dev->bus->parent) { | ||
995 | if (dev->bus->parent->self) | ||
996 | printk(KERN_WARNING "Calgary: IEEEE, dev %p has " | ||
997 | "bus->parent->self!\n", dev); | ||
998 | dev->bus->parent->self = dev; | ||
999 | } else | ||
1000 | dev->bus->self = dev; | ||
915 | 1001 | ||
916 | tbl = dev->sysdata; | 1002 | tbl = dev->sysdata; |
917 | tbl->chip_ops->handle_quirks(tbl, dev); | 1003 | tbl->chip_ops->handle_quirks(tbl, dev); |
@@ -951,11 +1037,18 @@ static int __init calgary_locate_bbars(void) | |||
951 | target = calgary_reg(bbar, offset); | 1037 | target = calgary_reg(bbar, offset); |
952 | 1038 | ||
953 | val = be32_to_cpu(readl(target)); | 1039 | val = be32_to_cpu(readl(target)); |
1040 | |||
954 | start_bus = (u8)((val & 0x00FF0000) >> 16); | 1041 | start_bus = (u8)((val & 0x00FF0000) >> 16); |
955 | end_bus = (u8)((val & 0x0000FF00) >> 8); | 1042 | end_bus = (u8)((val & 0x0000FF00) >> 8); |
956 | for (bus = start_bus; bus <= end_bus; bus++) { | 1043 | |
957 | bus_info[bus].bbar = bbar; | 1044 | if (end_bus) { |
958 | bus_info[bus].phbid = phb; | 1045 | for (bus = start_bus; bus <= end_bus; bus++) { |
1046 | bus_info[bus].bbar = bbar; | ||
1047 | bus_info[bus].phbid = phb; | ||
1048 | } | ||
1049 | } else { | ||
1050 | bus_info[start_bus].bbar = bbar; | ||
1051 | bus_info[start_bus].phbid = phb; | ||
959 | } | 1052 | } |
960 | } | 1053 | } |
961 | } | 1054 | } |
@@ -975,24 +1068,27 @@ static int __init calgary_init(void) | |||
975 | { | 1068 | { |
976 | int ret; | 1069 | int ret; |
977 | struct pci_dev *dev = NULL; | 1070 | struct pci_dev *dev = NULL; |
1071 | void* tce_space; | ||
978 | 1072 | ||
979 | ret = calgary_locate_bbars(); | 1073 | ret = calgary_locate_bbars(); |
980 | if (ret) | 1074 | if (ret) |
981 | return ret; | 1075 | return ret; |
982 | 1076 | ||
983 | do { | 1077 | do { |
984 | dev = pci_get_device(PCI_VENDOR_ID_IBM, | 1078 | dev = pci_get_device(PCI_VENDOR_ID_IBM, PCI_ANY_ID, dev); |
985 | PCI_DEVICE_ID_IBM_CALGARY, | ||
986 | dev); | ||
987 | if (!dev) | 1079 | if (!dev) |
988 | break; | 1080 | break; |
1081 | if (!is_cal_pci_dev(dev->device)) | ||
1082 | continue; | ||
989 | if (!translate_phb(dev)) { | 1083 | if (!translate_phb(dev)) { |
990 | calgary_init_one_nontraslated(dev); | 1084 | calgary_init_one_nontraslated(dev); |
991 | continue; | 1085 | continue; |
992 | } | 1086 | } |
993 | if (!bus_info[dev->bus->number].tce_space && !translate_empty_slots) | 1087 | tce_space = bus_info[dev->bus->number].tce_space; |
1088 | if (!tce_space && !translate_empty_slots) { | ||
1089 | printk("Calg: %p failed tce_space check\n", dev); | ||
994 | continue; | 1090 | continue; |
995 | 1091 | } | |
996 | ret = calgary_init_one(dev); | 1092 | ret = calgary_init_one(dev); |
997 | if (ret) | 1093 | if (ret) |
998 | goto error; | 1094 | goto error; |
@@ -1003,10 +1099,11 @@ static int __init calgary_init(void) | |||
1003 | error: | 1099 | error: |
1004 | do { | 1100 | do { |
1005 | dev = pci_get_device_reverse(PCI_VENDOR_ID_IBM, | 1101 | dev = pci_get_device_reverse(PCI_VENDOR_ID_IBM, |
1006 | PCI_DEVICE_ID_IBM_CALGARY, | 1102 | PCI_ANY_ID, dev); |
1007 | dev); | ||
1008 | if (!dev) | 1103 | if (!dev) |
1009 | break; | 1104 | break; |
1105 | if (!is_cal_pci_dev(dev->device)) | ||
1106 | continue; | ||
1010 | if (!translate_phb(dev)) { | 1107 | if (!translate_phb(dev)) { |
1011 | pci_dev_put(dev); | 1108 | pci_dev_put(dev); |
1012 | continue; | 1109 | continue; |
@@ -1084,9 +1181,29 @@ static int __init build_detail_arrays(void) | |||
1084 | return 0; | 1181 | return 0; |
1085 | } | 1182 | } |
1086 | 1183 | ||
1087 | void __init detect_calgary(void) | 1184 | static int __init calgary_bus_has_devices(int bus, unsigned short pci_dev) |
1088 | { | 1185 | { |
1186 | int dev; | ||
1089 | u32 val; | 1187 | u32 val; |
1188 | |||
1189 | if (pci_dev == PCI_DEVICE_ID_IBM_CALIOC2) { | ||
1190 | /* | ||
1191 | * FIXME: properly scan for devices accross the | ||
1192 | * PCI-to-PCI bridge on every CalIOC2 port. | ||
1193 | */ | ||
1194 | return 1; | ||
1195 | } | ||
1196 | |||
1197 | for (dev = 1; dev < 8; dev++) { | ||
1198 | val = read_pci_config(bus, dev, 0, 0); | ||
1199 | if (val != 0xffffffff) | ||
1200 | break; | ||
1201 | } | ||
1202 | return (val != 0xffffffff); | ||
1203 | } | ||
1204 | |||
1205 | void __init detect_calgary(void) | ||
1206 | { | ||
1090 | int bus; | 1207 | int bus; |
1091 | void *tbl; | 1208 | void *tbl; |
1092 | int calgary_found = 0; | 1209 | int calgary_found = 0; |
@@ -1143,29 +1260,28 @@ void __init detect_calgary(void) | |||
1143 | specified_table_size = determine_tce_table_size(end_pfn * PAGE_SIZE); | 1260 | specified_table_size = determine_tce_table_size(end_pfn * PAGE_SIZE); |
1144 | 1261 | ||
1145 | for (bus = 0; bus < MAX_PHB_BUS_NUM; bus++) { | 1262 | for (bus = 0; bus < MAX_PHB_BUS_NUM; bus++) { |
1146 | int dev; | ||
1147 | struct calgary_bus_info *info = &bus_info[bus]; | 1263 | struct calgary_bus_info *info = &bus_info[bus]; |
1264 | unsigned short pci_device; | ||
1265 | u32 val; | ||
1266 | |||
1267 | val = read_pci_config(bus, 0, 0, 0); | ||
1268 | pci_device = (val & 0xFFFF0000) >> 16; | ||
1148 | 1269 | ||
1149 | if (read_pci_config(bus, 0, 0, 0) != PCI_VENDOR_DEVICE_ID_CALGARY) | 1270 | if (!is_cal_pci_dev(pci_device)) |
1150 | continue; | 1271 | continue; |
1151 | 1272 | ||
1152 | if (info->translation_disabled) | 1273 | if (info->translation_disabled) |
1153 | continue; | 1274 | continue; |
1154 | 1275 | ||
1155 | /* | 1276 | if (calgary_bus_has_devices(bus, pci_device) || |
1156 | * Scan the slots of the PCI bus to see if there is a device present. | 1277 | translate_empty_slots) { |
1157 | * The parent bus will be the zero-ith device, so start at 1. | 1278 | tbl = alloc_tce_table(); |
1158 | */ | 1279 | if (!tbl) |
1159 | for (dev = 1; dev < 8; dev++) { | 1280 | goto cleanup; |
1160 | val = read_pci_config(bus, dev, 0, 0); | 1281 | info->tce_space = tbl; |
1161 | if (val != 0xffffffff || translate_empty_slots) { | 1282 | calgary_found = 1; |
1162 | tbl = alloc_tce_table(); | 1283 | printk("Calg: allocated tce_table %p for bus 0x%x\n", |
1163 | if (!tbl) | 1284 | info->tce_space, bus); |
1164 | goto cleanup; | ||
1165 | info->tce_space = tbl; | ||
1166 | calgary_found = 1; | ||
1167 | break; | ||
1168 | } | ||
1169 | } | 1285 | } |
1170 | } | 1286 | } |
1171 | 1287 | ||