powerpc/iommu: Fix multiple issues with IOMMU pools code
authorAnton Blanchard <anton@samba.org>
Wed, 3 Oct 2012 18:57:10 +0000 (18:57 +0000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Thu, 4 Oct 2012 08:03:20 +0000 (18:03 +1000)
There are a number of issues in the recent IOMMU pools code:

- On a preempt kernel we might switch CPUs in the middle of building
  a scatter gather list. When this happens the handle hint passed in
  no longer falls within the local CPU's pool. Check for this and
  fall back to the pool hint.

- We were missing a spin_unlock/spin_lock in one spot where we
  switch pools.

- We need to provide locking around dart_tlb_invalidate_all and
  dart_tlb_invalidate_one now that the global lock is gone.

Reported-by: Alexander Graf <agraf@suse.de>
Signed-off-by: Anton Blanchard <anton@samba.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: <stable@kernel.org> [v3.6]
arch/powerpc/kernel/iommu.c
arch/powerpc/sysdev/dart_iommu.c

index ff5a6ce..8226c6c 100644 (file)
@@ -215,7 +215,8 @@ static unsigned long iommu_range_alloc(struct device *dev,
        spin_lock_irqsave(&(pool->lock), flags);
 
 again:
-       if ((pass == 0) && handle && *handle)
+       if ((pass == 0) && handle && *handle &&
+           (*handle >= pool->start) && (*handle < pool->end))
                start = *handle;
        else
                start = pool->hint;
@@ -236,7 +237,9 @@ again:
                 * but on second pass, start at 0 in pool 0.
                 */
                if ((start & mask) >= limit || pass > 0) {
+                       spin_unlock(&(pool->lock));
                        pool = &(tbl->pools[0]);
+                       spin_lock(&(pool->lock));
                        start = pool->start;
                } else {
                        start &= mask;
index 8ef63a0..bd968a4 100644 (file)
@@ -73,11 +73,16 @@ static int dart_is_u4;
 
 #define DBG(...)
 
+static DEFINE_SPINLOCK(invalidate_lock);
+
 static inline void dart_tlb_invalidate_all(void)
 {
        unsigned long l = 0;
        unsigned int reg, inv_bit;
        unsigned long limit;
+       unsigned long flags;
+
+       spin_lock_irqsave(&invalidate_lock, flags);
 
        DBG("dart: flush\n");
 
@@ -110,12 +115,17 @@ retry:
                        panic("DART: TLB did not flush after waiting a long "
                              "time. Buggy U3 ?");
        }
+
+       spin_unlock_irqrestore(&invalidate_lock, flags);
 }
 
 static inline void dart_tlb_invalidate_one(unsigned long bus_rpn)
 {
        unsigned int reg;
        unsigned int l, limit;
+       unsigned long flags;
+
+       spin_lock_irqsave(&invalidate_lock, flags);
 
        reg = DART_CNTL_U4_ENABLE | DART_CNTL_U4_IONE |
                (bus_rpn & DART_CNTL_U4_IONE_MASK);
@@ -137,6 +147,8 @@ wait_more:
                        panic("DART: TLB did not flush after waiting a long "
                              "time. Buggy U4 ?");
        }
+
+       spin_unlock_irqrestore(&invalidate_lock, flags);
 }
 
 static void dart_flush(struct iommu_table *tbl)