Update to 3.4-final.
[linux-flexiantxendom0-3.2.10.git] / lib / dma-debug.c
index d9b08e0..13ef233 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/spinlock.h>
 #include <linux/debugfs.h>
 #include <linux/uaccess.h>
+#include <linux/export.h>
 #include <linux/device.h>
 #include <linux/types.h>
 #include <linux/sched.h>
@@ -62,6 +63,8 @@ struct dma_debug_entry {
 #endif
 };
 
+typedef bool (*match_fn)(struct dma_debug_entry *, struct dma_debug_entry *);
+
 struct hash_bucket {
        struct list_head list;
        spinlock_t lock;
@@ -167,7 +170,7 @@ static bool driver_filter(struct device *dev)
                return false;
 
        /* driver filter on but not yet initialized */
-       drv = get_driver(dev->driver);
+       drv = dev->driver;
        if (!drv)
                return false;
 
@@ -182,7 +185,6 @@ static bool driver_filter(struct device *dev)
        }
 
        read_unlock_irqrestore(&driver_name_lock, flags);
-       put_driver(drv);
 
        return ret;
 }
@@ -240,18 +242,37 @@ static void put_hash_bucket(struct hash_bucket *bucket,
        spin_unlock_irqrestore(&bucket->lock, __flags);
 }
 
+static bool exact_match(struct dma_debug_entry *a, struct dma_debug_entry *b)
+{
+       return ((a->dev_addr == b->dev_addr) &&
+               (a->dev == b->dev)) ? true : false;
+}
+
+static bool containing_match(struct dma_debug_entry *a,
+                            struct dma_debug_entry *b)
+{
+       if (a->dev != b->dev)
+               return false;
+
+       if ((b->dev_addr <= a->dev_addr) &&
+           ((b->dev_addr + b->size) >= (a->dev_addr + a->size)))
+               return true;
+
+       return false;
+}
+
 /*
  * Search a given entry in the hash bucket list
  */
-static struct dma_debug_entry *hash_bucket_find(struct hash_bucket *bucket,
-                                               struct dma_debug_entry *ref)
+static struct dma_debug_entry *__hash_bucket_find(struct hash_bucket *bucket,
+                                                 struct dma_debug_entry *ref,
+                                                 match_fn match)
 {
        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 (!match(ref, entry))
                        continue;
 
                /*
@@ -293,6 +314,39 @@ static struct dma_debug_entry *hash_bucket_find(struct hash_bucket *bucket,
        return ret;
 }
 
+static struct dma_debug_entry *bucket_find_exact(struct hash_bucket *bucket,
+                                                struct dma_debug_entry *ref)
+{
+       return __hash_bucket_find(bucket, ref, exact_match);
+}
+
+static struct dma_debug_entry *bucket_find_contain(struct hash_bucket **bucket,
+                                                  struct dma_debug_entry *ref,
+                                                  unsigned long *flags)
+{
+
+       unsigned int max_range = dma_get_max_seg_size(ref->dev);
+       struct dma_debug_entry *entry, index = *ref;
+       unsigned int range = 0;
+
+       while (range <= max_range) {
+               entry = __hash_bucket_find(*bucket, &index, containing_match);
+
+               if (entry)
+                       return entry;
+
+               /*
+                * Nothing found, go back a hash bucket
+                */
+               put_hash_bucket(*bucket, flags);
+               range          += (1 << HASH_FN_SHIFT);
+               index.dev_addr -= (1 << HASH_FN_SHIFT);
+               *bucket = get_hash_bucket(&index, flags);
+       }
+
+       return NULL;
+}
+
 /*
  * Add an entry to a hash bucket
  */
@@ -570,7 +624,7 @@ static ssize_t filter_write(struct file *file, const char __user *userbuf,
         * Now parse out the first token and use it as the name for the
         * driver to filter for.
         */
-       for (i = 0; i < NAME_MAX_LEN; ++i) {
+       for (i = 0; i < NAME_MAX_LEN - 1; ++i) {
                current_driver_name[i] = buf[i];
                if (isspace(buf[i]) || buf[i] == ' ' || buf[i] == 0)
                        break;
@@ -587,9 +641,10 @@ out_unlock:
        return count;
 }
 
-const struct file_operations filter_fops = {
+static const struct file_operations filter_fops = {
        .read  = filter_read,
        .write = filter_write,
+       .llseek = default_llseek,
 };
 
 static int dma_debug_fs_init(void)
@@ -648,7 +703,7 @@ out_err:
        return -ENOMEM;
 }
 
-static int device_dma_allocations(struct device *dev)
+static int device_dma_allocations(struct device *dev, struct dma_debug_entry **out_entry)
 {
        struct dma_debug_entry *entry;
        unsigned long flags;
@@ -659,8 +714,10 @@ static int device_dma_allocations(struct device *dev)
        for (i = 0; i < HASH_SIZE; ++i) {
                spin_lock(&dma_entry_hash[i].lock);
                list_for_each_entry(entry, &dma_entry_hash[i].list, list) {
-                       if (entry->dev == dev)
+                       if (entry->dev == dev) {
                                count += 1;
+                               *out_entry = entry;
+                       }
                }
                spin_unlock(&dma_entry_hash[i].lock);
        }
@@ -670,21 +727,28 @@ static int device_dma_allocations(struct device *dev)
        return count;
 }
 
-static int dma_debug_device_change(struct notifier_block *nb,
-                                   unsigned long action, void *data)
+static int dma_debug_device_change(struct notifier_block *nb, unsigned long action, void *data)
 {
        struct device *dev = data;
+       struct dma_debug_entry *uninitialized_var(entry);
        int count;
 
+       if (global_disable)
+               return 0;
 
        switch (action) {
        case BUS_NOTIFY_UNBOUND_DRIVER:
-               count = device_dma_allocations(dev);
+               count = device_dma_allocations(dev, &entry);
                if (count == 0)
                        break;
-               err_printk(dev, NULL, "DMA-API: device driver has pending "
+               err_printk(dev, entry, "DMA-API: device driver has pending "
                                "DMA allocations while released from device "
-                               "[count=%d]\n", count);
+                               "[count=%d]\n"
+                               "One of leaked entries details: "
+                               "[device address=0x%016llx] [size=%llu bytes] "
+                               "[mapped with %s] [mapped as %s]\n",
+                       count, entry->dev_addr, entry->size,
+                       dir2name[entry->direction], type2name[entry->type]);
                break;
        default:
                break;
@@ -697,6 +761,9 @@ void dma_debug_add_bus(struct bus_type *bus)
 {
        struct notifier_block *nb;
 
+       if (global_disable)
+               return;
+
        nb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL);
        if (nb == NULL) {
                pr_err("dma_debug_add_bus: out of memory\n");
@@ -789,7 +856,7 @@ static void check_unmap(struct dma_debug_entry *ref)
        }
 
        bucket = get_hash_bucket(ref, &flags);
-       entry = hash_bucket_find(bucket, ref);
+       entry = bucket_find_exact(bucket, ref);
 
        if (!entry) {
                err_printk(ref->dev, NULL, "DMA-API: device driver tries "
@@ -889,7 +956,7 @@ static void check_sync(struct device *dev,
 
        bucket = get_hash_bucket(ref, &flags);
 
-       entry = hash_bucket_find(bucket, ref);
+       entry = bucket_find_contain(&bucket, ref, &flags);
 
        if (!entry) {
                err_printk(dev, NULL, "DMA-API: device driver tries "
@@ -909,6 +976,9 @@ static void check_sync(struct device *dev,
                                ref->size);
        }
 
+       if (entry->direction == DMA_BIDIRECTIONAL)
+               goto out;
+
        if (ref->direction != entry->direction) {
                err_printk(dev, entry, "DMA-API: device driver syncs "
                                "DMA memory with different direction "
@@ -919,9 +989,6 @@ static void check_sync(struct device *dev,
                                dir2name[ref->direction]);
        }
 
-       if (entry->direction == DMA_BIDIRECTIONAL)
-               goto out;
-
        if (to_cpu && !(entry->direction == DMA_FROM_DEVICE) &&
                      !(ref->direction == DMA_TO_DEVICE))
                err_printk(dev, entry, "DMA-API: device driver syncs "
@@ -944,7 +1011,6 @@ static void check_sync(struct device *dev,
 
 out:
        put_hash_bucket(bucket, &flags);
-
 }
 
 void debug_dma_map_page(struct device *dev, struct page *page, size_t offset,
@@ -1048,7 +1114,7 @@ static int get_nr_mapped_entries(struct device *dev,
        int mapped_ents;
 
        bucket       = get_hash_bucket(ref, &flags);
-       entry        = hash_bucket_find(bucket, ref);
+       entry        = bucket_find_exact(bucket, ref);
        mapped_ents  = 0;
 
        if (entry)