📄 pci-calgary_64.c
字号:
writel(cpu_to_be32(val32), target); readl(target); /* flush */ init_timer(&tbl->watchdog_timer); tbl->watchdog_timer.function = &calgary_watchdog; tbl->watchdog_timer.data = (unsigned long)dev; mod_timer(&tbl->watchdog_timer, jiffies);}static void __init calgary_disable_translation(struct pci_dev *dev){ u32 val32; unsigned char busnum; void __iomem *target; void __iomem *bbar; struct iommu_table *tbl; busnum = dev->bus->number; tbl = pci_iommu(dev->bus); bbar = tbl->bbar; /* disable TCE in PHB Config Register */ target = calgary_reg(bbar, phb_offset(busnum) | PHB_CONFIG_RW_OFFSET); val32 = be32_to_cpu(readl(target)); val32 &= ~(PHB_TCE_ENABLE | PHB_DAC_DISABLE | PHB_MCSR_ENABLE); printk(KERN_INFO "Calgary: disabling translation on PHB %#x!\n", busnum); writel(cpu_to_be32(val32), target); readl(target); /* flush */ del_timer_sync(&tbl->watchdog_timer);}static void __init calgary_init_one_nontraslated(struct pci_dev *dev){ pci_dev_get(dev); set_pci_iommu(dev->bus, NULL); /* is the device behind a bridge? */ if (dev->bus->parent) dev->bus->parent->self = dev; else dev->bus->self = dev;}static int __init calgary_init_one(struct pci_dev *dev){ void __iomem *bbar; struct iommu_table *tbl; int ret; BUG_ON(dev->bus->number >= MAX_PHB_BUS_NUM); bbar = busno_to_bbar(dev->bus->number); ret = calgary_setup_tar(dev, bbar); if (ret) goto done; pci_dev_get(dev); if (dev->bus->parent) { if (dev->bus->parent->self) printk(KERN_WARNING "Calgary: IEEEE, dev %p has " "bus->parent->self!\n", dev); dev->bus->parent->self = dev; } else dev->bus->self = dev; tbl = pci_iommu(dev->bus); tbl->chip_ops->handle_quirks(tbl, dev); calgary_enable_translation(dev); return 0;done: return ret;}static int __init calgary_locate_bbars(void){ int ret; int rioidx, phb, bus; void __iomem *bbar; void __iomem *target; unsigned long offset; u8 start_bus, end_bus; u32 val; ret = -ENODATA; for (rioidx = 0; rioidx < rio_table_hdr->num_rio_dev; rioidx++) { struct rio_detail *rio = rio_devs[rioidx]; if ((rio->type != COMPAT_CALGARY) && (rio->type != ALT_CALGARY)) continue; /* map entire 1MB of Calgary config space */ bbar = ioremap_nocache(rio->BBAR, 1024 * 1024); if (!bbar) goto error; for (phb = 0; phb < PHBS_PER_CALGARY; phb++) { offset = phb_debug_offsets[phb] | PHB_DEBUG_STUFF_OFFSET; target = calgary_reg(bbar, offset); val = be32_to_cpu(readl(target)); start_bus = (u8)((val & 0x00FF0000) >> 16); end_bus = (u8)((val & 0x0000FF00) >> 8); if (end_bus) { for (bus = start_bus; bus <= end_bus; bus++) { bus_info[bus].bbar = bbar; bus_info[bus].phbid = phb; } } else { bus_info[start_bus].bbar = bbar; bus_info[start_bus].phbid = phb; } } } return 0;error: /* scan bus_info and iounmap any bbars we previously ioremap'd */ for (bus = 0; bus < ARRAY_SIZE(bus_info); bus++) if (bus_info[bus].bbar) iounmap(bus_info[bus].bbar); return ret;}static int __init calgary_init(void){ int ret; struct pci_dev *dev = NULL; struct calgary_bus_info *info; ret = calgary_locate_bbars(); if (ret) return ret; do { dev = pci_get_device(PCI_VENDOR_ID_IBM, PCI_ANY_ID, dev); if (!dev) break; if (!is_cal_pci_dev(dev->device)) continue; info = &bus_info[dev->bus->number]; if (info->translation_disabled) { calgary_init_one_nontraslated(dev); continue; } if (!info->tce_space && !translate_empty_slots) continue; ret = calgary_init_one(dev); if (ret) goto error; } while (1); return ret;error: do { dev = pci_get_device_reverse(PCI_VENDOR_ID_IBM, PCI_ANY_ID, dev); if (!dev) break; if (!is_cal_pci_dev(dev->device)) continue; info = &bus_info[dev->bus->number]; if (info->translation_disabled) { pci_dev_put(dev); continue; } if (!info->tce_space && !translate_empty_slots) continue; calgary_disable_translation(dev); calgary_free_bus(dev); pci_dev_put(dev); /* Undo calgary_init_one()'s pci_dev_get() */ } while (1); return ret;}static inline int __init determine_tce_table_size(u64 ram){ int ret; if (specified_table_size != TCE_TABLE_SIZE_UNSPECIFIED) return specified_table_size; /* * Table sizes are from 0 to 7 (TCE_TABLE_SIZE_64K to * TCE_TABLE_SIZE_8M). Table size 0 has 8K entries and each * larger table size has twice as many entries, so shift the * max ram address by 13 to divide by 8K and then look at the * order of the result to choose between 0-7. */ ret = get_order(ram >> 13); if (ret > TCE_TABLE_SIZE_8M) ret = TCE_TABLE_SIZE_8M; return ret;}static int __init build_detail_arrays(void){ unsigned long ptr; int i, scal_detail_size, rio_detail_size; if (rio_table_hdr->num_scal_dev > MAX_NUMNODES){ printk(KERN_WARNING "Calgary: MAX_NUMNODES too low! Defined as %d, " "but system has %d nodes.\n", MAX_NUMNODES, rio_table_hdr->num_scal_dev); return -ENODEV; } switch (rio_table_hdr->version){ case 2: scal_detail_size = 11; rio_detail_size = 13; break; case 3: scal_detail_size = 12; rio_detail_size = 15; break; default: printk(KERN_WARNING "Calgary: Invalid Rio Grande Table Version: %d\n", rio_table_hdr->version); return -EPROTO; } ptr = ((unsigned long)rio_table_hdr) + 3; for (i = 0; i < rio_table_hdr->num_scal_dev; i++, ptr += scal_detail_size) scal_devs[i] = (struct scal_detail *)ptr; for (i = 0; i < rio_table_hdr->num_rio_dev; i++, ptr += rio_detail_size) rio_devs[i] = (struct rio_detail *)ptr; return 0;}static int __init calgary_bus_has_devices(int bus, unsigned short pci_dev){ int dev; u32 val; if (pci_dev == PCI_DEVICE_ID_IBM_CALIOC2) { /* * FIXME: properly scan for devices accross the * PCI-to-PCI bridge on every CalIOC2 port. */ return 1; } for (dev = 1; dev < 8; dev++) { val = read_pci_config(bus, dev, 0, 0); if (val != 0xffffffff) break; } return (val != 0xffffffff);}void __init detect_calgary(void){ int bus; void *tbl; int calgary_found = 0; unsigned long ptr; unsigned int offset, prev_offset; int ret; /* * if the user specified iommu=off or iommu=soft or we found * another HW IOMMU already, bail out. */ if (swiotlb || no_iommu || iommu_detected) return; if (!use_calgary) return; if (!early_pci_allowed()) return; printk(KERN_DEBUG "Calgary: detecting Calgary via BIOS EBDA area\n"); ptr = (unsigned long)phys_to_virt(get_bios_ebda()); rio_table_hdr = NULL; prev_offset = 0; offset = 0x180; /* * The next offset is stored in the 1st word. * Only parse up until the offset increases: */ while (offset > prev_offset) { /* The block id is stored in the 2nd word */ if (*((unsigned short *)(ptr + offset + 2)) == 0x4752){ /* set the pointer past the offset & block id */ rio_table_hdr = (struct rio_table_hdr *)(ptr + offset + 4); break; } prev_offset = offset; offset = *((unsigned short *)(ptr + offset)); } if (!rio_table_hdr) { printk(KERN_DEBUG "Calgary: Unable to locate Rio Grande table " "in EBDA - bailing!\n"); return; } ret = build_detail_arrays(); if (ret) { printk(KERN_DEBUG "Calgary: build_detail_arrays ret %d\n", ret); return; } specified_table_size = determine_tce_table_size(end_pfn * PAGE_SIZE); for (bus = 0; bus < MAX_PHB_BUS_NUM; bus++) { struct calgary_bus_info *info = &bus_info[bus]; unsigned short pci_device; u32 val; val = read_pci_config(bus, 0, 0, 0); pci_device = (val & 0xFFFF0000) >> 16; if (!is_cal_pci_dev(pci_device)) continue; if (info->translation_disabled) continue; if (calgary_bus_has_devices(bus, pci_device) || translate_empty_slots) { tbl = alloc_tce_table(); if (!tbl) goto cleanup; info->tce_space = tbl; calgary_found = 1; } } printk(KERN_DEBUG "Calgary: finished detection, Calgary %s\n", calgary_found ? "found" : "not found"); if (calgary_found) { iommu_detected = 1; calgary_detected = 1; printk(KERN_INFO "PCI-DMA: Calgary IOMMU detected.\n"); printk(KERN_INFO "PCI-DMA: Calgary TCE table spec is %d, " "CONFIG_IOMMU_DEBUG is %s.\n", specified_table_size, debugging ? "enabled" : "disabled"); } return;cleanup: for (--bus; bus >= 0; --bus) { struct calgary_bus_info *info = &bus_info[bus]; if (info->tce_space) free_tce_table(info->tce_space); }}int __init calgary_iommu_init(void){ int ret; if (no_iommu || swiotlb) return -ENODEV; if (!calgary_detected) return -ENODEV; /* ok, we're trying to use Calgary - let's roll */ printk(KERN_INFO "PCI-DMA: Using Calgary IOMMU\n"); ret = calgary_init(); if (ret) { printk(KERN_ERR "PCI-DMA: Calgary init failed %d, " "falling back to no_iommu\n", ret); if (end_pfn > MAX_DMA32_PFN) printk(KERN_ERR "WARNING more than 4GB of memory, " "32bit PCI may malfunction.\n"); return ret; } force_iommu = 1; bad_dma_address = 0x0; dma_ops = &calgary_dma_ops; return 0;}static int __init calgary_parse_options(char *p){ unsigned int bridge; size_t len; char* endp; while (*p) { if (!strncmp(p, "64k", 3)) specified_table_size = TCE_TABLE_SIZE_64K; else if (!strncmp(p, "128k", 4)) specified_table_size = TCE_TABLE_SIZE_128K; else if (!strncmp(p, "256k", 4)) specified_table_size = TCE_TABLE_SIZE_256K; else if (!strncmp(p, "512k", 4)) specified_table_size = TCE_TABLE_SIZE_512K; else if (!strncmp(p, "1M", 2)) specified_table_size = TCE_TABLE_SIZE_1M; else if (!strncmp(p, "2M", 2)) specified_table_size = TCE_TABLE_SIZE_2M; else if (!strncmp(p, "4M", 2)) specified_table_size = TCE_TABLE_SIZE_4M; else if (!strncmp(p, "8M", 2)) specified_table_size = TCE_TABLE_SIZE_8M; len = strlen("translate_empty_slots"); if (!strncmp(p, "translate_empty_slots", len)) translate_empty_slots = 1; len = strlen("disable"); if (!strncmp(p, "disable", len)) { p += len; if (*p == '=') ++p; if (*p == '\0') break; bridge = simple_strtol(p, &endp, 0); if (p == endp) break; if (bridge < MAX_PHB_BUS_NUM) { printk(KERN_INFO "Calgary: disabling " "translation for PHB %#x\n", bridge); bus_info[bridge].translation_disabled = 1; } } p = strpbrk(p, ","); if (!p) break; p++; /* skip ',' */ } return 1;}__setup("calgary=", calgary_parse_options);static void __init calgary_fixup_one_tce_space(struct pci_dev *dev){ struct iommu_table *tbl; unsigned int npages; int i; tbl = pci_iommu(dev->bus); for (i = 0; i < 4; i++) { struct resource *r = &dev->resource[PCI_BRIDGE_RESOURCES + i]; /* Don't give out TCEs that map MEM resources */ if (!(r->flags & IORESOURCE_MEM)) continue; /* 0-based? we reserve the whole 1st MB anyway */ if (!r->start) continue; /* cover the whole region */ npages = (r->end - r->start) >> PAGE_SHIFT; npages++; iommu_range_reserve(tbl, r->start, npages); }}static int __init calgary_fixup_tce_spaces(void){ struct pci_dev *dev = NULL; struct calgary_bus_info *info; if (no_iommu || swiotlb || !calgary_detected) return -ENODEV; printk(KERN_DEBUG "Calgary: fixing up tce spaces\n"); do { dev = pci_get_device(PCI_VENDOR_ID_IBM, PCI_ANY_ID, dev); if (!dev) break; if (!is_cal_pci_dev(dev->device)) continue; info = &bus_info[dev->bus->number]; if (info->translation_disabled) continue; if (!info->tce_space) continue; calgary_fixup_one_tce_space(dev); } while (1); return 0;}/* * We need to be call after pcibios_assign_resources (fs_initcall level) * and before device_initcall. */rootfs_initcall(calgary_fixup_tce_spaces);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -