Merge branch 'dma-debug/2.6.31' of git://git.kernel.org/pub/scm/linux/kernel/git...
[cascardo/linux.git] / lib / dma-debug.c
index f49ab22..77053d9 100644 (file)
@@ -242,15 +242,50 @@ static void put_hash_bucket(struct hash_bucket *bucket,
 static struct dma_debug_entry *hash_bucket_find(struct hash_bucket *bucket,
                                                struct dma_debug_entry *ref)
 {
-       struct dma_debug_entry *entry;
+       struct dma_debug_entry *entry, *ret = NULL;
+       int matches = 0, match_lvl, last_lvl = 0;
 
        list_for_each_entry(entry, &bucket->list, list) {
-               if ((entry->dev_addr == ref->dev_addr) &&
-                   (entry->dev == ref->dev))
+               if ((entry->dev_addr != ref->dev_addr) ||
+                   (entry->dev != ref->dev))
+                       continue;
+
+               /*
+                * Some drivers map the same physical address multiple
+                * times. Without a hardware IOMMU this results in the
+                * same device addresses being put into the dma-debug
+                * hash multiple times too. This can result in false
+                * positives being reported. Therfore we implement a
+                * best-fit algorithm here which returns the entry from
+                * the hash which fits best to the reference value
+                * instead of the first-fit.
+                */
+               matches += 1;
+               match_lvl = 0;
+               entry->size      == ref->size      ? ++match_lvl : match_lvl;
+               entry->type      == ref->type      ? ++match_lvl : match_lvl;
+               entry->direction == ref->direction ? ++match_lvl : match_lvl;
+
+               if (match_lvl == 3) {
+                       /* perfect-fit - return the result */
                        return entry;
+               } else if (match_lvl > last_lvl) {
+                       /*
+                        * We found an entry that fits better then the
+                        * previous one
+                        */
+                       last_lvl = match_lvl;
+                       ret      = entry;
+               }
        }
 
-       return NULL;
+       /*
+        * If we have multiple matches but no perfect-fit, just return
+        * NULL.
+        */
+       ret = (matches == 1) ? ret : NULL;
+
+       return ret;
 }
 
 /*