Commit 8a244590 authored by Muli Ben-Yehuda's avatar Muli Ben-Yehuda Committed by Linus Torvalds

x86_64: introduce CalIOC2 support

CalIOC2 is a PCI-e implementation of the Calgary logic. Most of the
programming details are the same, but some differ, e.g., TCE cache
flush. This patch introduces CalIOC2 support - detection and various
support routines. It's not expected to work yet (but will with
follow-on patches).
Signed-off-by: default avatarMuli Ben-Yehuda <muli@il.ibm.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarAndi Kleen <ak@suse.de>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 35b6dfa0
...@@ -50,8 +50,7 @@ int use_calgary __read_mostly = 0; ...@@ -50,8 +50,7 @@ int use_calgary __read_mostly = 0;
#endif /* CONFIG_CALGARY_DEFAULT_ENABLED */ #endif /* CONFIG_CALGARY_DEFAULT_ENABLED */
#define PCI_DEVICE_ID_IBM_CALGARY 0x02a1 #define PCI_DEVICE_ID_IBM_CALGARY 0x02a1
#define PCI_VENDOR_DEVICE_ID_CALGARY \ #define PCI_DEVICE_ID_IBM_CALIOC2 0x0308
(PCI_VENDOR_ID_IBM | PCI_DEVICE_ID_IBM_CALGARY << 16)
/* we need these for register space address calculation */ /* we need these for register space address calculation */
#define START_ADDRESS 0xfe000000 #define START_ADDRESS 0xfe000000
...@@ -193,6 +192,7 @@ static inline unsigned long verify_bit_range(unsigned long* bitmap, ...@@ -193,6 +192,7 @@ static inline unsigned long verify_bit_range(unsigned long* bitmap,
{ {
return ~0UL; return ~0UL;
} }
#endif /* CONFIG_IOMMU_DEBUG */ #endif /* CONFIG_IOMMU_DEBUG */
static inline unsigned int num_dma_pages(unsigned long dma, unsigned int dmalen) 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,9 +346,20 @@ static void iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr,
static inline struct iommu_table *find_iommu_table(struct device *dev) static inline struct iommu_table *find_iommu_table(struct device *dev)
{ {
struct pci_dev *pdev;
struct pci_bus *pbus;
struct iommu_table *tbl; struct iommu_table *tbl;
tbl = to_pci_dev(dev)->bus->self->sysdata; pdev = to_pci_dev(dev);
/* is the device behind a bridge? */
if (unlikely(pdev->bus->parent))
pbus = pdev->bus->parent;
else
pbus = pdev->bus;
tbl = pbus->self->sysdata;
BUG_ON(pdev->bus->parent && (tbl->it_busno != pdev->bus->parent->number));
return tbl; return tbl;
} }
...@@ -565,6 +576,21 @@ static inline void __iomem* calgary_reg(void __iomem *bar, unsigned long offset) ...@@ -565,6 +576,21 @@ static inline void __iomem* calgary_reg(void __iomem *bar, unsigned long offset)
return (void __iomem*)target; return (void __iomem*)target;
} }
static inline int is_calioc2(unsigned short device)
{
return (device == PCI_DEVICE_ID_IBM_CALIOC2);
}
static inline int is_calgary(unsigned short device)
{
return (device == PCI_DEVICE_ID_IBM_CALGARY);
}
static inline int is_cal_pci_dev(unsigned short device)
{
return (is_calgary(device) || is_calioc2(device));
}
static void calgary_tce_cache_blast(struct iommu_table *tbl) static void calgary_tce_cache_blast(struct iommu_table *tbl)
{ {
u64 val; u64 val;
...@@ -685,8 +711,14 @@ static void __init calgary_reserve_regions(struct pci_dev *dev) ...@@ -685,8 +711,14 @@ static void __init calgary_reserve_regions(struct pci_dev *dev)
iommu_range_reserve(tbl, bad_dma_address, EMERGENCY_PAGES); iommu_range_reserve(tbl, bad_dma_address, EMERGENCY_PAGES);
/* avoid the BIOS/VGA first 640KB-1MB region */ /* avoid the BIOS/VGA first 640KB-1MB region */
/* for CalIOC2 - avoid the entire first 2MB */
if (is_calgary(dev->device)) {
start = (640 * 1024); start = (640 * 1024);
npages = ((1024 - 640) * 1024) >> PAGE_SHIFT; npages = ((1024 - 640) * 1024) >> PAGE_SHIFT;
} else { /* calioc2 */
start = 0;
npages = (2 * 1024 * 1024) >> PAGE_SHIFT;
}
iommu_range_reserve(tbl, start, npages); iommu_range_reserve(tbl, start, npages);
/* reserve the two PCI peripheral memory regions in IO space */ /* 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,15 +753,15 @@ static int __init calgary_setup_tar(struct pci_dev *dev, void __iomem *bbar)
/* zero out all TAR bits under sw control */ /* zero out all TAR bits under sw control */
val64 &= ~TAR_SW_BITS; val64 &= ~TAR_SW_BITS;
tbl = dev->sysdata;
table_phys = (u64)__pa(tbl->it_base); table_phys = (u64)__pa(tbl->it_base);
val64 |= table_phys; val64 |= table_phys;
BUG_ON(specified_table_size > TCE_TABLE_SIZE_8M); BUG_ON(specified_table_size > TCE_TABLE_SIZE_8M);
val64 |= (u64) specified_table_size; val64 |= (u64) specified_table_size;
tbl->tar_val = cpu_to_be64(val64); tbl->tar_val = cpu_to_be64(val64);
writeq(tbl->tar_val, target); writeq(tbl->tar_val, target);
readq(target); /* flush */ readq(target); /* flush */
...@@ -760,6 +792,43 @@ static void __init calgary_free_bus(struct pci_dev *dev) ...@@ -760,6 +792,43 @@ static void __init calgary_free_bus(struct pci_dev *dev)
bus_info[dev->bus->number].tce_space = NULL; bus_info[dev->bus->number].tce_space = NULL;
} }
static void calgary_dump_error_regs(struct iommu_table *tbl)
{
void __iomem *bbar = tbl->bbar;
u32 csr, csmr, plssr, mck;
void __iomem *target;
unsigned long phboff = phb_offset(tbl->it_busno);
unsigned long erroff;
u32 errregs[7];
int i;
/* dump CSR */
target = calgary_reg(bbar, phboff | PHB_CSR_OFFSET);
csr = be32_to_cpu(readl(target));
/* dump PLSSR */
target = calgary_reg(bbar, phboff | PHB_PLSSR_OFFSET);
plssr = be32_to_cpu(readl(target));
/* dump CSMR */
target = calgary_reg(bbar, phboff | 0x290);
csmr = be32_to_cpu(readl(target));
/* dump mck */
target = calgary_reg(bbar, phboff | 0x800);
mck = be32_to_cpu(readl(target));
printk(KERN_EMERG "Calgary: 0x%08x@CSR 0x%08x@PLSSR 0x%08x@CSMR "
"0x%08x@MCK\n", csr, plssr, csmr, mck);
/* dump rest of error regs */
printk(KERN_EMERG "Calgary: ");
for (i = 0; i < ARRAY_SIZE(errregs); i++) {
erroff = (0x810 + (i * 0x10)); /* err regs are at 0x810 - 0x870 */
target = calgary_reg(bbar, phboff | erroff);
errregs[i] = be32_to_cpu(readl(target));
printk("0x%08x@0x%lx ", errregs[i], erroff);
}
printk("\n");
}
static void calgary_watchdog(unsigned long data) static void calgary_watchdog(unsigned long data)
{ {
struct pci_dev *dev = (struct pci_dev *)data; struct pci_dev *dev = (struct pci_dev *)data;
...@@ -773,8 +842,11 @@ static void calgary_watchdog(unsigned long data) ...@@ -773,8 +842,11 @@ static void calgary_watchdog(unsigned long data)
/* If no error, the agent ID in the CSR is not valid */ /* If no error, the agent ID in the CSR is not valid */
if (val32 & CSR_AGENT_MASK) { if (val32 & CSR_AGENT_MASK) {
printk(KERN_EMERG "calgary_watchdog: DMA error on PHB %#x, " printk(KERN_EMERG "Calgary: DMA error on PHB %#x\n",
"CSR = %#x\n", dev->bus->number, val32); dev->bus->number);
calgary_dump_error_regs(tbl);
/* reset error */
writel(0, target); writel(0, target);
/* Disable bus that caused the error */ /* Disable bus that caused the error */
...@@ -853,7 +925,9 @@ static void __init calgary_enable_translation(struct pci_dev *dev) ...@@ -853,7 +925,9 @@ static void __init calgary_enable_translation(struct pci_dev *dev)
val32 = be32_to_cpu(readl(target)); val32 = be32_to_cpu(readl(target));
val32 |= PHB_TCE_ENABLE | PHB_DAC_DISABLE | PHB_MCSR_ENABLE; val32 |= PHB_TCE_ENABLE | PHB_DAC_DISABLE | PHB_MCSR_ENABLE;
printk(KERN_INFO "Calgary: enabling translation on PHB %#x\n", busnum); printk(KERN_INFO "Calgary: enabling translation on %s PHB %#x\n",
(dev->device == PCI_DEVICE_ID_IBM_CALGARY) ?
"Calgary" : "CalIOC2", busnum);
printk(KERN_INFO "Calgary: errant DMAs will now be prevented on this " printk(KERN_INFO "Calgary: errant DMAs will now be prevented on this "
"bus.\n"); "bus.\n");
...@@ -894,6 +968,11 @@ static void __init calgary_init_one_nontraslated(struct pci_dev *dev) ...@@ -894,6 +968,11 @@ static void __init calgary_init_one_nontraslated(struct pci_dev *dev)
{ {
pci_dev_get(dev); pci_dev_get(dev);
dev->sysdata = NULL; dev->sysdata = NULL;
/* is the device behind a bridge? */
if (dev->bus->parent)
dev->bus->parent->self = dev;
else
dev->bus->self = dev; dev->bus->self = dev;
} }
...@@ -911,6 +990,13 @@ static int __init calgary_init_one(struct pci_dev *dev) ...@@ -911,6 +990,13 @@ static int __init calgary_init_one(struct pci_dev *dev)
goto done; goto done;
pci_dev_get(dev); 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; dev->bus->self = dev;
tbl = dev->sysdata; tbl = dev->sysdata;
...@@ -951,12 +1037,19 @@ static int __init calgary_locate_bbars(void) ...@@ -951,12 +1037,19 @@ static int __init calgary_locate_bbars(void)
target = calgary_reg(bbar, offset); target = calgary_reg(bbar, offset);
val = be32_to_cpu(readl(target)); val = be32_to_cpu(readl(target));
start_bus = (u8)((val & 0x00FF0000) >> 16); start_bus = (u8)((val & 0x00FF0000) >> 16);
end_bus = (u8)((val & 0x0000FF00) >> 8); end_bus = (u8)((val & 0x0000FF00) >> 8);
if (end_bus) {
for (bus = start_bus; bus <= end_bus; bus++) { for (bus = start_bus; bus <= end_bus; bus++) {
bus_info[bus].bbar = bbar; bus_info[bus].bbar = bbar;
bus_info[bus].phbid = phb; bus_info[bus].phbid = phb;
} }
} else {
bus_info[start_bus].bbar = bbar;
bus_info[start_bus].phbid = phb;
}
} }
} }
...@@ -975,24 +1068,27 @@ static int __init calgary_init(void) ...@@ -975,24 +1068,27 @@ static int __init calgary_init(void)
{ {
int ret; int ret;
struct pci_dev *dev = NULL; struct pci_dev *dev = NULL;
void* tce_space;
ret = calgary_locate_bbars(); ret = calgary_locate_bbars();
if (ret) if (ret)
return ret; return ret;
do { do {
dev = pci_get_device(PCI_VENDOR_ID_IBM, dev = pci_get_device(PCI_VENDOR_ID_IBM, PCI_ANY_ID, dev);
PCI_DEVICE_ID_IBM_CALGARY,
dev);
if (!dev) if (!dev)
break; break;
if (!is_cal_pci_dev(dev->device))
continue;
if (!translate_phb(dev)) { if (!translate_phb(dev)) {
calgary_init_one_nontraslated(dev); calgary_init_one_nontraslated(dev);
continue; continue;
} }
if (!bus_info[dev->bus->number].tce_space && !translate_empty_slots) tce_space = bus_info[dev->bus->number].tce_space;
if (!tce_space && !translate_empty_slots) {
printk("Calg: %p failed tce_space check\n", dev);
continue; continue;
}
ret = calgary_init_one(dev); ret = calgary_init_one(dev);
if (ret) if (ret)
goto error; goto error;
...@@ -1003,10 +1099,11 @@ static int __init calgary_init(void) ...@@ -1003,10 +1099,11 @@ static int __init calgary_init(void)
error: error:
do { do {
dev = pci_get_device_reverse(PCI_VENDOR_ID_IBM, dev = pci_get_device_reverse(PCI_VENDOR_ID_IBM,
PCI_DEVICE_ID_IBM_CALGARY, PCI_ANY_ID, dev);
dev);
if (!dev) if (!dev)
break; break;
if (!is_cal_pci_dev(dev->device))
continue;
if (!translate_phb(dev)) { if (!translate_phb(dev)) {
pci_dev_put(dev); pci_dev_put(dev);
continue; continue;
...@@ -1084,9 +1181,29 @@ static int __init build_detail_arrays(void) ...@@ -1084,9 +1181,29 @@ static int __init build_detail_arrays(void)
return 0; return 0;
} }
void __init detect_calgary(void) static int __init calgary_bus_has_devices(int bus, unsigned short pci_dev)
{ {
int dev;
u32 val; 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; int bus;
void *tbl; void *tbl;
int calgary_found = 0; int calgary_found = 0;
...@@ -1143,29 +1260,28 @@ void __init detect_calgary(void) ...@@ -1143,29 +1260,28 @@ void __init detect_calgary(void)
specified_table_size = determine_tce_table_size(end_pfn * PAGE_SIZE); specified_table_size = determine_tce_table_size(end_pfn * PAGE_SIZE);
for (bus = 0; bus < MAX_PHB_BUS_NUM; bus++) { for (bus = 0; bus < MAX_PHB_BUS_NUM; bus++) {
int dev;
struct calgary_bus_info *info = &bus_info[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 (read_pci_config(bus, 0, 0, 0) != PCI_VENDOR_DEVICE_ID_CALGARY) if (!is_cal_pci_dev(pci_device))
continue; continue;
if (info->translation_disabled) if (info->translation_disabled)
continue; continue;
/* if (calgary_bus_has_devices(bus, pci_device) ||
* Scan the slots of the PCI bus to see if there is a device present. translate_empty_slots) {
* The parent bus will be the zero-ith device, so start at 1.
*/
for (dev = 1; dev < 8; dev++) {
val = read_pci_config(bus, dev, 0, 0);
if (val != 0xffffffff || translate_empty_slots) {
tbl = alloc_tce_table(); tbl = alloc_tce_table();
if (!tbl) if (!tbl)
goto cleanup; goto cleanup;
info->tce_space = tbl; info->tce_space = tbl;
calgary_found = 1; calgary_found = 1;
break; printk("Calg: allocated tce_table %p for bus 0x%x\n",
} info->tce_space, bus);
} }
} }
......
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