suspend-vs-iommu: prevent suspend if we could not resume
[safe/jmp/linux-2.6] / arch / x86 / kernel / pci-gart_64.c
index 845cbec..aa8ec92 100644 (file)
@@ -25,6 +25,8 @@
 #include <linux/bitops.h>
 #include <linux/kdebug.h>
 #include <linux/scatterlist.h>
+#include <linux/iommu-helper.h>
+#include <linux/sysdev.h>
 #include <asm/atomic.h>
 #include <asm/io.h>
 #include <asm/mtrr.h>
@@ -82,17 +84,24 @@ AGPEXTERN __u32 *agp_gatt_table;
 static unsigned long next_bit;  /* protected by iommu_bitmap_lock */
 static int need_flush;         /* global flush state. set for each gart wrap */
 
-static unsigned long alloc_iommu(int size)
+static unsigned long alloc_iommu(struct device *dev, int size)
 {
        unsigned long offset, flags;
+       unsigned long boundary_size;
+       unsigned long base_index;
+
+       base_index = ALIGN(iommu_bus_base & dma_get_seg_boundary(dev),
+                          PAGE_SIZE) >> PAGE_SHIFT;
+       boundary_size = ALIGN(dma_get_seg_boundary(dev) + 1,
+                             PAGE_SIZE) >> PAGE_SHIFT;
 
        spin_lock_irqsave(&iommu_bitmap_lock, flags);
-       offset = find_next_zero_string(iommu_gart_bitmap, next_bit,
-                                       iommu_pages, size);
+       offset = iommu_area_alloc(iommu_gart_bitmap, iommu_pages, next_bit,
+                                 size, base_index, boundary_size, 0);
        if (offset == -1) {
                need_flush = 1;
-               offset = find_next_zero_string(iommu_gart_bitmap, 0,
-                                               iommu_pages, size);
+               offset = iommu_area_alloc(iommu_gart_bitmap, iommu_pages, 0,
+                                         size, base_index, boundary_size, 0);
        }
        if (offset != -1) {
                set_bit_string(iommu_gart_bitmap, offset, size);
@@ -114,7 +123,7 @@ static void free_iommu(unsigned long offset, int size)
        unsigned long flags;
 
        spin_lock_irqsave(&iommu_bitmap_lock, flags);
-       __clear_bit_string(iommu_gart_bitmap, offset, size);
+       iommu_area_free(iommu_gart_bitmap, offset, size);
        spin_unlock_irqrestore(&iommu_bitmap_lock, flags);
 }
 
@@ -235,7 +244,7 @@ static dma_addr_t dma_map_area(struct device *dev, dma_addr_t phys_mem,
                                size_t size, int dir)
 {
        unsigned long npages = to_pages(phys_mem, size);
-       unsigned long iommu_page = alloc_iommu(npages);
+       unsigned long iommu_page = alloc_iommu(dev, npages);
        int i;
 
        if (iommu_page == -1) {
@@ -256,9 +265,9 @@ static dma_addr_t dma_map_area(struct device *dev, dma_addr_t phys_mem,
 }
 
 static dma_addr_t
-gart_map_simple(struct device *dev, char *buf, size_t size, int dir)
+gart_map_simple(struct device *dev, phys_addr_t paddr, size_t size, int dir)
 {
-       dma_addr_t map = dma_map_area(dev, virt_to_bus(buf), size, dir);
+       dma_addr_t map = dma_map_area(dev, paddr, size, dir);
 
        flush_gart();
 
@@ -267,18 +276,17 @@ gart_map_simple(struct device *dev, char *buf, size_t size, int dir)
 
 /* Map a single area into the IOMMU */
 static dma_addr_t
-gart_map_single(struct device *dev, void *addr, size_t size, int dir)
+gart_map_single(struct device *dev, phys_addr_t paddr, size_t size, int dir)
 {
-       unsigned long phys_mem, bus;
+       unsigned long bus;
 
        if (!dev)
                dev = &fallback_dev;
 
-       phys_mem = virt_to_phys(addr);
-       if (!need_iommu(dev, phys_mem, size))
-               return phys_mem;
+       if (!need_iommu(dev, paddr, size))
+               return paddr;
 
-       bus = gart_map_simple(dev, addr, size, dir);
+       bus = gart_map_simple(dev, paddr, size, dir);
 
        return bus;
 }
@@ -355,10 +363,11 @@ static int dma_map_sg_nonforce(struct device *dev, struct scatterlist *sg,
 }
 
 /* Map multiple scatterlist entries continuous into the first. */
-static int __dma_map_cont(struct scatterlist *start, int nelems,
-                         struct scatterlist *sout, unsigned long pages)
+static int __dma_map_cont(struct device *dev, struct scatterlist *start,
+                         int nelems, struct scatterlist *sout,
+                         unsigned long pages)
 {
-       unsigned long iommu_start = alloc_iommu(pages);
+       unsigned long iommu_start = alloc_iommu(dev, pages);
        unsigned long iommu_page = iommu_start;
        struct scatterlist *s;
        int i;
@@ -394,8 +403,8 @@ static int __dma_map_cont(struct scatterlist *start, int nelems,
 }
 
 static inline int
-dma_map_cont(struct scatterlist *start, int nelems, struct scatterlist *sout,
-            unsigned long pages, int need)
+dma_map_cont(struct device *dev, struct scatterlist *start, int nelems,
+            struct scatterlist *sout, unsigned long pages, int need)
 {
        if (!need) {
                BUG_ON(nelems != 1);
@@ -403,7 +412,7 @@ dma_map_cont(struct scatterlist *start, int nelems, struct scatterlist *sout,
                sout->dma_length = start->length;
                return 0;
        }
-       return __dma_map_cont(start, nelems, sout, pages);
+       return __dma_map_cont(dev, start, nelems, sout, pages);
 }
 
 /*
@@ -416,6 +425,8 @@ gart_map_sg(struct device *dev, struct scatterlist *sg, int nents, int dir)
        struct scatterlist *s, *ps, *start_sg, *sgmap;
        int need = 0, nextneed, i, out, start;
        unsigned long pages = 0;
+       unsigned int seg_size;
+       unsigned int max_seg_size;
 
        if (nents == 0)
                return 0;
@@ -426,6 +437,8 @@ gart_map_sg(struct device *dev, struct scatterlist *sg, int nents, int dir)
        out = 0;
        start = 0;
        start_sg = sgmap = sg;
+       seg_size = 0;
+       max_seg_size = dma_get_max_seg_size(dev);
        ps = NULL; /* shut up gcc */
        for_each_sg(sg, s, nents, i) {
                dma_addr_t addr = sg_phys(s);
@@ -443,11 +456,13 @@ gart_map_sg(struct device *dev, struct scatterlist *sg, int nents, int dir)
                         * offset.
                         */
                        if (!iommu_merge || !nextneed || !need || s->offset ||
+                           (s->length + seg_size > max_seg_size) ||
                            (ps->offset + ps->length) % PAGE_SIZE) {
-                               if (dma_map_cont(start_sg, i - start, sgmap,
-                                                 pages, need) < 0)
+                               if (dma_map_cont(dev, start_sg, i - start,
+                                                sgmap, pages, need) < 0)
                                        goto error;
                                out++;
+                               seg_size = 0;
                                sgmap = sg_next(sgmap);
                                pages = 0;
                                start = i;
@@ -455,11 +470,12 @@ gart_map_sg(struct device *dev, struct scatterlist *sg, int nents, int dir)
                        }
                }
 
+               seg_size += s->length;
                need = nextneed;
                pages += to_pages(s->offset, s->length);
                ps = s;
        }
-       if (dma_map_cont(start_sg, i - start, sgmap, pages, need) < 0)
+       if (dma_map_cont(dev, start_sg, i - start, sgmap, pages, need) < 0)
                goto error;
        out++;
        flush_gart();
@@ -533,6 +549,28 @@ static __init unsigned read_aperture(struct pci_dev *dev, u32 *size)
        return aper_base;
 }
 
+static int gart_resume(struct sys_device *dev)
+{
+       return 0;
+}
+
+static int gart_suspend(struct sys_device *dev, pm_message_t state)
+{
+       return -EINVAL;
+}
+
+static struct sysdev_class gart_sysdev_class = {
+       .name = "gart",
+       .suspend = gart_suspend,
+       .resume = gart_resume,
+
+};
+
+static struct sys_device device_gart = {
+       .id     = 0,
+       .cls    = &gart_sysdev_class,
+};
+
 /*
  * Private Northbridge GATT initialization in case we cannot use the
  * AGP driver for some reason.
@@ -543,7 +581,7 @@ static __init int init_k8_gatt(struct agp_kern_info *info)
        unsigned aper_base, new_aper_base;
        struct pci_dev *dev;
        void *gatt;
-       int i;
+       int i, error;
 
        printk(KERN_INFO "PCI-DMA: Disabling AGP.\n");
        aper_size = aper_base = info->aper_size = 0;
@@ -591,6 +629,12 @@ static __init int init_k8_gatt(struct agp_kern_info *info)
 
                pci_write_config_dword(dev, 0x90, ctl);
        }
+
+       error = sysdev_class_register(&gart_sysdev_class);
+       if (!error)
+               error = sysdev_register(&device_gart);
+       if (error)
+               panic("Could not register gart_sysdev -- would corrupt data on next suspend");
        flush_gart();
 
        printk(KERN_INFO "PCI-DMA: aperture base @ %x size %u KB\n",
@@ -599,8 +643,8 @@ static __init int init_k8_gatt(struct agp_kern_info *info)
 
  nommu:
        /* Should not happen anymore */
-       printk(KERN_ERR "PCI-DMA: More than 4GB of RAM and no IOMMU\n"
-              KERN_ERR "PCI-DMA: 32bit PCI IO may malfunction.\n");
+       printk(KERN_WARNING "PCI-DMA: More than 4GB of RAM and no IOMMU\n"
+              KERN_WARNING "falling back to iommu=soft.\n");
        return -1;
 }
 
@@ -676,9 +720,9 @@ void __init gart_iommu_init(void)
            !gart_iommu_aperture ||
            (no_agp && init_k8_gatt(&info) < 0)) {
                if (end_pfn > MAX_DMA32_PFN) {
-                       printk(KERN_ERR "WARNING more than 4GB of memory "
-                                       "but GART IOMMU not available.\n"
-                              KERN_ERR "WARNING 32bit PCI may malfunction.\n");
+                       printk(KERN_WARNING "More than 4GB of memory "
+                                         "but GART IOMMU not available.\n"
+                              KERN_WARNING "falling back to iommu=soft.\n");
                }
                return;
        }
@@ -733,6 +777,15 @@ void __init gart_iommu_init(void)
         */
        set_memory_np((unsigned long)__va(iommu_bus_base),
                                iommu_size >> PAGE_SHIFT);
+       /*
+        * Tricky. The GART table remaps the physical memory range,
+        * so the CPU wont notice potential aliases and if the memory
+        * is remapped to UC later on, we might surprise the PCI devices
+        * with a stray writeout of a cacheline. So play it sure and
+        * do an explicit, full-scale wbinvd() _after_ having marked all
+        * the pages as Not-Present:
+        */
+       wbinvd();
 
        /*
         * Try to workaround a bug (thanks to BenH)