Merge branch 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6
[pandora-kernel.git] / arch / x86_64 / kernel / pci-calgary.c
index afc0a53..04480c3 100644 (file)
 #include <asm/dma.h>
 #include <asm/rio.h>
 
+#ifdef CONFIG_CALGARY_IOMMU_ENABLED_BY_DEFAULT
+int use_calgary __read_mostly = 1;
+#else
+int use_calgary __read_mostly = 0;
+#endif /* CONFIG_CALGARY_DEFAULT_ENABLED */
+
 #define PCI_DEVICE_ID_IBM_CALGARY 0x02a1
 #define PCI_VENDOR_DEVICE_ID_CALGARY \
        (PCI_VENDOR_ID_IBM | PCI_DEVICE_ID_IBM_CALGARY << 16)
@@ -132,13 +138,15 @@ static const unsigned long phb_debug_offsets[] = {
 
 #define PHB_DEBUG_STUFF_OFFSET 0x0020
 
+#define EMERGENCY_PAGES 32 /* = 128KB */
+
 unsigned int specified_table_size = TCE_TABLE_SIZE_UNSPECIFIED;
 static int translate_empty_slots __read_mostly = 0;
 static int calgary_detected __read_mostly = 0;
 
 static struct rio_table_hdr    *rio_table_hdr __initdata;
 static struct scal_detail      *scal_devs[MAX_NUMNODES] __initdata;
-static struct rio_detail       *rio_devs[MAX_NUMNODES*4] __initdata;
+static struct rio_detail       *rio_devs[MAX_NUMNODES * 4] __initdata;
 
 struct calgary_bus_info {
        void *tce_space;
@@ -290,6 +298,16 @@ static void __iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr,
 {
        unsigned long entry;
        unsigned long badbit;
+       unsigned long badend;
+
+       /* were we called with bad_dma_address? */
+       badend = bad_dma_address + (EMERGENCY_PAGES * PAGE_SIZE);
+       if (unlikely((dma_addr >= bad_dma_address) && (dma_addr < badend))) {
+               printk(KERN_ERR "Calgary: driver tried unmapping bad DMA "
+                      "address 0x%Lx\n", dma_addr);
+               WARN_ON(1);
+               return;
+       }
 
        entry = dma_addr >> PAGE_SHIFT;
 
@@ -647,16 +665,11 @@ static void __init calgary_reserve_peripheral_mem_2(struct pci_dev *dev)
 static void __init calgary_reserve_regions(struct pci_dev *dev)
 {
        unsigned int npages;
-       void __iomem *bbar;
-       unsigned char busnum;
        u64 start;
        struct iommu_table *tbl = dev->sysdata;
 
-       bbar = tbl->bbar;
-       busnum = dev->bus->number;
-
-       /* reserve bad_dma_address in case it's a legal address */
-       iommu_range_reserve(tbl, bad_dma_address, 1);
+       /* reserve EMERGENCY_PAGES from bad_dma_address and up */
+       iommu_range_reserve(tbl, bad_dma_address, EMERGENCY_PAGES);
 
        /* avoid the BIOS/VGA first 640KB-1MB region */
        start = (640 * 1024);
@@ -855,11 +868,6 @@ static void __init calgary_disable_translation(struct pci_dev *dev)
        del_timer_sync(&tbl->watchdog_timer);
 }
 
-static inline void __iomem * __init locate_register_space(struct pci_dev *dev)
-{
-       return busno_to_bbar(dev->bus->number);
-}
-
 static void __init calgary_init_one_nontraslated(struct pci_dev *dev)
 {
        pci_dev_get(dev);
@@ -874,15 +882,10 @@ static int __init calgary_init_one(struct pci_dev *dev)
 
        BUG_ON(dev->bus->number >= MAX_PHB_BUS_NUM);
 
-       bbar = locate_register_space(dev);
-       if (!bbar) {
-               ret = -ENODATA;
-               goto done;
-       }
-
+       bbar = busno_to_bbar(dev->bus->number);
        ret = calgary_setup_tar(dev, bbar);
        if (ret)
-               goto iounmap;
+               goto done;
 
        pci_dev_get(dev);
        dev->bus->self = dev;
@@ -890,38 +893,39 @@ static int __init calgary_init_one(struct pci_dev *dev)
 
        return 0;
 
-iounmap:
-       iounmap(bbar);
 done:
        return ret;
 }
 
-static int __init calgary_init(void)
+static int __init calgary_locate_bbars(void)
 {
-       int ret = -ENODEV;
-       struct pci_dev *dev = NULL;
-       int rio, phb, bus;
+       int ret;
+       int rioidx, phb, bus;
        void __iomem *bbar;
        void __iomem *target;
+       unsigned long offset;
        u8 start_bus, end_bus;
        u32 val;
 
-       for (rio = 0; rio < rio_table_hdr->num_rio_dev; rio++) {
+       ret = -ENODATA;
+       for (rioidx = 0; rioidx < rio_table_hdr->num_rio_dev; rioidx++) {
+               struct rio_detail *rio = rio_devs[rioidx];
 
-               if ( (rio_devs[rio]->type != COMPAT_CALGARY) &&
-                    (rio_devs[rio]->type != ALT_CALGARY) )
+               if ((rio->type != COMPAT_CALGARY) && (rio->type != ALT_CALGARY))
                        continue;
 
                /* map entire 1MB of Calgary config space */
-               bbar = ioremap_nocache(rio_devs[rio]->BBAR, 1024 * 1024);
+               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);
 
-                       target = calgary_reg(bbar, phb_debug_offsets[phb] |
-                                                  PHB_DEBUG_STUFF_OFFSET);
                        val = be32_to_cpu(readl(target));
                        start_bus = (u8)((val & 0x00FF0000) >> 16);
-                       end_bus =   (u8)((val & 0x0000FF00) >> 8);
+                       end_bus = (u8)((val & 0x0000FF00) >> 8);
                        for (bus = start_bus; bus <= end_bus; bus++) {
                                bus_info[bus].bbar = bbar;
                                bus_info[bus].phbid = phb;
@@ -929,6 +933,25 @@ static int __init calgary_init(void)
                }
        }
 
+       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;
+
+       ret = calgary_locate_bbars();
+       if (ret)
+               return ret;
 
        do {
                dev = pci_get_device(PCI_VENDOR_ID_IBM,
@@ -1000,18 +1023,13 @@ static int __init build_detail_arrays(void)
 
        if (rio_table_hdr->num_scal_dev > MAX_NUMNODES){
                printk(KERN_WARNING
-                       "Calgary: MAX_NUMNODES too low!  Defined as %d, "
+                       "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){
-       default:
-               printk(KERN_WARNING
-                      "Calgary: Invalid Rio Grande Table Version: %d\n",
-                      rio_table_hdr->version);
-               return -ENODEV;
        case 2:
                scal_detail_size = 11;
                rio_detail_size = 13;
@@ -1020,6 +1038,11 @@ static int __init build_detail_arrays(void)
                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;
@@ -1041,7 +1064,8 @@ void __init detect_calgary(void)
        void *tbl;
        int calgary_found = 0;
        unsigned long ptr;
-       int offset;
+       unsigned int offset, prev_offset;
+       int ret;
 
        /*
         * if the user specified iommu=off or iommu=soft or we found
@@ -1050,38 +1074,50 @@ void __init detect_calgary(void)
        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;
-       while (offset) {
+       /*
+        * 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);
+                       rio_table_hdr = (struct rio_table_hdr *)(ptr + offset + 4);
                        break;
                }
-               /* The next offset is stored in the 1st word. 0 means no more */
+               prev_offset = offset;
                offset = *((unsigned short *)(ptr + offset));
        }
-       if (!rio_table_hdr){
-               printk(KERN_ERR "Calgary: Unable to locate "
-                               "Rio Grande Table in EBDA - bailing!\n");
+       if (!rio_table_hdr) {
+               printk(KERN_DEBUG "Calgary: Unable to locate Rio Grande table "
+                      "in EBDA - bailing!\n");
                return;
        }
 
-       if (build_detail_arrays())
+       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++) {
                int dev;
                struct calgary_bus_info *info = &bus_info[bus];
-               info->phbid = -1;
 
                if (read_pci_config(bus, 0, 0, 0) != PCI_VENDOR_DEVICE_ID_CALGARY)
                        continue;
@@ -1106,6 +1142,9 @@ void __init detect_calgary(void)
                }
        }
 
+       printk(KERN_DEBUG "Calgary: finished detection, Calgary %s\n",
+              calgary_found ? "found" : "not found");
+
        if (calgary_found) {
                iommu_detected = 1;
                calgary_detected = 1;
@@ -1149,6 +1188,7 @@ int __init calgary_iommu_init(void)
        }
 
        force_iommu = 1;
+       bad_dma_address = 0x0;
        dma_ops = &calgary_dma_ops;
 
        return 0;