dma-debug: add variables and checks for driver filter
[safe/jmp/linux-2.6] / lib / dma-debug.c
index d1c0ac1..c01f647 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <linux/scatterlist.h>
 #include <linux/dma-mapping.h>
+#include <linux/stacktrace.h>
 #include <linux/dma-debug.h>
 #include <linux/spinlock.h>
 #include <linux/debugfs.h>
@@ -28,6 +29,8 @@
 #include <linux/list.h>
 #include <linux/slab.h>
 
+#include <asm/sections.h>
+
 #define HASH_SIZE       1024ULL
 #define HASH_FN_SHIFT   13
 #define HASH_FN_MASK    (HASH_SIZE - 1)
@@ -39,6 +42,8 @@ enum {
        dma_debug_coherent,
 };
 
+#define DMA_DEBUG_STACKTRACE_ENTRIES 5
+
 struct dma_debug_entry {
        struct list_head list;
        struct device    *dev;
@@ -49,6 +54,10 @@ struct dma_debug_entry {
        int              direction;
        int              sg_call_ents;
        int              sg_mapped_ents;
+#ifdef CONFIG_STACKTRACE
+       struct           stack_trace stacktrace;
+       unsigned long    st_entries[DMA_DEBUG_STACKTRACE_ENTRIES];
+#endif
 };
 
 struct hash_bucket {
@@ -76,6 +85,7 @@ static u32 show_num_errors = 1;
 
 static u32 num_free_entries;
 static u32 min_free_entries;
+static u32 nr_total_entries;
 
 /* number of preallocated entries requested by kernel cmdline */
 static u32 req_entries;
@@ -89,6 +99,15 @@ static struct dentry *show_num_errors_dent  __read_mostly;
 static struct dentry *num_free_entries_dent __read_mostly;
 static struct dentry *min_free_entries_dent __read_mostly;
 
+/* per-driver filter related state */
+
+#define NAME_MAX_LEN   64
+
+static char                  current_driver_name[NAME_MAX_LEN] __read_mostly;
+static struct device_driver *current_driver                    __read_mostly;
+
+static DEFINE_RWLOCK(driver_name_lock);
+
 static const char *type2name[4] = { "single", "page",
                                    "scather-gather", "coherent" };
 
@@ -108,12 +127,61 @@ static const char *dir2name[4] = { "DMA_BIDIRECTIONAL", "DMA_TO_DEVICE",
  *                    system log than the user configured. This variable is
  *                    writeable via debugfs.
  */
-#define err_printk(dev, format, arg...) do {                   \
+static inline void dump_entry_trace(struct dma_debug_entry *entry)
+{
+#ifdef CONFIG_STACKTRACE
+       if (entry) {
+               printk(KERN_WARNING "Mapped at:\n");
+               print_stack_trace(&entry->stacktrace, 0);
+       }
+#endif
+}
+
+static bool driver_filter(struct device *dev)
+{
+       /* driver filter off */
+       if (likely(!current_driver_name[0]))
+               return true;
+
+       /* driver filter on and initialized */
+       if (current_driver && dev->driver == current_driver)
+               return true;
+
+       /* driver filter on but not yet initialized */
+       if (!current_driver && current_driver_name[0]) {
+               struct device_driver *drv = get_driver(dev->driver);
+               unsigned long flags;
+               bool ret = false;
+
+               if (!drv)
+                       return false;
+
+               /* lock to protect against change of current_driver_name */
+               read_lock_irqsave(&driver_name_lock, flags);
+
+               if (drv->name &&
+                   strncmp(current_driver_name, drv->name, 63) == 0) {
+                       current_driver = drv;
+                       ret = true;
+               }
+
+               read_unlock_irqrestore(&driver_name_lock, flags);
+               put_driver(drv);
+
+               return ret;
+       }
+
+       return false;
+}
+
+#define err_printk(dev, entry, format, arg...) do {            \
                error_count += 1;                               \
-               if (show_all_errors || show_num_errors > 0) {   \
+               if (driver_filter(dev) &&                       \
+                   (show_all_errors || show_num_errors > 0)) { \
                        WARN(1, "%s %s: " format,               \
                             dev_driver_string(dev),            \
                             dev_name(dev) , ## arg);           \
+                       dump_entry_trace(entry);                \
                }                                               \
                if (!show_all_errors && show_num_errors > 0)    \
                        show_num_errors -= 1;                   \
@@ -194,6 +262,36 @@ static void hash_bucket_del(struct dma_debug_entry *entry)
 }
 
 /*
+ * Dump mapping entries for debugging purposes
+ */
+void debug_dma_dump_mappings(struct device *dev)
+{
+       int idx;
+
+       for (idx = 0; idx < HASH_SIZE; idx++) {
+               struct hash_bucket *bucket = &dma_entry_hash[idx];
+               struct dma_debug_entry *entry;
+               unsigned long flags;
+
+               spin_lock_irqsave(&bucket->lock, flags);
+
+               list_for_each_entry(entry, &bucket->list, list) {
+                       if (!dev || dev == entry->dev) {
+                               dev_info(entry->dev,
+                                        "%s idx %d P=%Lx D=%Lx L=%Lx %s\n",
+                                        type2name[entry->type], idx,
+                                        (unsigned long long)entry->paddr,
+                                        entry->dev_addr, entry->size,
+                                        dir2name[entry->direction]);
+                       }
+               }
+
+               spin_unlock_irqrestore(&bucket->lock, flags);
+       }
+}
+EXPORT_SYMBOL(debug_dma_dump_mappings);
+
+/*
  * Wrapper function for adding an entry to the hash.
  * This function takes care of locking itself.
  */
@@ -207,6 +305,21 @@ static void add_dma_entry(struct dma_debug_entry *entry)
        put_hash_bucket(bucket, &flags);
 }
 
+static struct dma_debug_entry *__dma_entry_alloc(void)
+{
+       struct dma_debug_entry *entry;
+
+       entry = list_entry(free_entries.next, struct dma_debug_entry, list);
+       list_del(&entry->list);
+       memset(entry, 0, sizeof(*entry));
+
+       num_free_entries -= 1;
+       if (num_free_entries < min_free_entries)
+               min_free_entries = num_free_entries;
+
+       return entry;
+}
+
 /* struct dma_entry allocator
  *
  * The next two functions implement the allocator for
@@ -226,13 +339,14 @@ static struct dma_debug_entry *dma_entry_alloc(void)
                goto out;
        }
 
-       entry = list_entry(free_entries.next, struct dma_debug_entry, list);
-       list_del(&entry->list);
-       memset(entry, 0, sizeof(*entry));
+       entry = __dma_entry_alloc();
 
-       num_free_entries -= 1;
-       if (num_free_entries < min_free_entries)
-               min_free_entries = num_free_entries;
+#ifdef CONFIG_STACKTRACE
+       entry->stacktrace.max_entries = DMA_DEBUG_STACKTRACE_ENTRIES;
+       entry->stacktrace.entries = entry->st_entries;
+       entry->stacktrace.skip = 2;
+       save_stack_trace(&entry->stacktrace);
+#endif
 
 out:
        spin_unlock_irqrestore(&free_entries_lock, flags);
@@ -254,6 +368,53 @@ static void dma_entry_free(struct dma_debug_entry *entry)
        spin_unlock_irqrestore(&free_entries_lock, flags);
 }
 
+int dma_debug_resize_entries(u32 num_entries)
+{
+       int i, delta, ret = 0;
+       unsigned long flags;
+       struct dma_debug_entry *entry;
+       LIST_HEAD(tmp);
+
+       spin_lock_irqsave(&free_entries_lock, flags);
+
+       if (nr_total_entries < num_entries) {
+               delta = num_entries - nr_total_entries;
+
+               spin_unlock_irqrestore(&free_entries_lock, flags);
+
+               for (i = 0; i < delta; i++) {
+                       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+                       if (!entry)
+                               break;
+
+                       list_add_tail(&entry->list, &tmp);
+               }
+
+               spin_lock_irqsave(&free_entries_lock, flags);
+
+               list_splice(&tmp, &free_entries);
+               nr_total_entries += i;
+               num_free_entries += i;
+       } else {
+               delta = nr_total_entries - num_entries;
+
+               for (i = 0; i < delta && !list_empty(&free_entries); i++) {
+                       entry = __dma_entry_alloc();
+                       kfree(entry);
+               }
+
+               nr_total_entries -= i;
+       }
+
+       if (nr_total_entries != num_entries)
+               ret = 1;
+
+       spin_unlock_irqrestore(&free_entries_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL(dma_debug_resize_entries);
+
 /*
  * DMA-API debugging init code
  *
@@ -344,6 +505,10 @@ out_err:
        return -ENOMEM;
 }
 
+void dma_debug_add_bus(struct bus_type *bus)
+{
+       /* FIXME: register notifier */
+}
 
 /*
  * Let the architectures decide how many entries should be preallocated.
@@ -379,6 +544,8 @@ void dma_debug_init(u32 num_entries)
                return;
        }
 
+       nr_total_entries = num_free_entries;
+
        printk(KERN_INFO "DMA-API: debugging enabled by kernel config\n");
 }
 
@@ -420,14 +587,17 @@ static void check_unmap(struct dma_debug_entry *ref)
        struct hash_bucket *bucket;
        unsigned long flags;
 
-       if (dma_mapping_error(ref->dev, ref->dev_addr))
+       if (dma_mapping_error(ref->dev, ref->dev_addr)) {
+               err_printk(ref->dev, NULL, "DMA-API: device driver tries "
+                          "to free an invalid DMA memory address\n");
                return;
+       }
 
        bucket = get_hash_bucket(ref, &flags);
        entry = hash_bucket_find(bucket, ref);
 
        if (!entry) {
-               err_printk(ref->dev, "DMA-API: device driver tries "
+               err_printk(ref->dev, NULL, "DMA-API: device driver tries "
                           "to free DMA memory it has not allocated "
                           "[device address=0x%016llx] [size=%llu bytes]\n",
                           ref->dev_addr, ref->size);
@@ -435,7 +605,7 @@ static void check_unmap(struct dma_debug_entry *ref)
        }
 
        if (ref->size != entry->size) {
-               err_printk(ref->dev, "DMA-API: device driver frees "
+               err_printk(ref->dev, entry, "DMA-API: device driver frees "
                           "DMA memory with different size "
                           "[device address=0x%016llx] [map size=%llu bytes] "
                           "[unmap size=%llu bytes]\n",
@@ -443,7 +613,7 @@ static void check_unmap(struct dma_debug_entry *ref)
        }
 
        if (ref->type != entry->type) {
-               err_printk(ref->dev, "DMA-API: device driver frees "
+               err_printk(ref->dev, entry, "DMA-API: device driver frees "
                           "DMA memory with wrong function "
                           "[device address=0x%016llx] [size=%llu bytes] "
                           "[mapped as %s] [unmapped as %s]\n",
@@ -451,7 +621,7 @@ static void check_unmap(struct dma_debug_entry *ref)
                           type2name[entry->type], type2name[ref->type]);
        } else if ((entry->type == dma_debug_coherent) &&
                   (ref->paddr != entry->paddr)) {
-               err_printk(ref->dev, "DMA-API: device driver frees "
+               err_printk(ref->dev, entry, "DMA-API: device driver frees "
                           "DMA memory with different CPU address "
                           "[device address=0x%016llx] [size=%llu bytes] "
                           "[cpu alloc address=%p] [cpu free address=%p]",
@@ -461,7 +631,7 @@ static void check_unmap(struct dma_debug_entry *ref)
 
        if (ref->sg_call_ents && ref->type == dma_debug_sg &&
            ref->sg_call_ents != entry->sg_call_ents) {
-               err_printk(ref->dev, "DMA-API: device driver frees "
+               err_printk(ref->dev, entry, "DMA-API: device driver frees "
                           "DMA sg list with different entry count "
                           "[map count=%d] [unmap count=%d]\n",
                           entry->sg_call_ents, ref->sg_call_ents);
@@ -472,7 +642,7 @@ static void check_unmap(struct dma_debug_entry *ref)
         * DMA API don't handle this properly, so check for it here
         */
        if (ref->direction != entry->direction) {
-               err_printk(ref->dev, "DMA-API: device driver frees "
+               err_printk(ref->dev, entry, "DMA-API: device driver frees "
                           "DMA memory with different direction "
                           "[device address=0x%016llx] [size=%llu bytes] "
                           "[mapped with %s] [unmapped with %s]\n",
@@ -491,8 +661,26 @@ out:
 static void check_for_stack(struct device *dev, void *addr)
 {
        if (object_is_on_stack(addr))
-               err_printk(dev, "DMA-API: device driver maps memory from stack"
-                               " [addr=%p]\n", addr);
+               err_printk(dev, NULL, "DMA-API: device driver maps memory from"
+                               "stack [addr=%p]\n", addr);
+}
+
+static inline bool overlap(void *addr, u64 size, void *start, void *end)
+{
+       void *addr2 = (char *)addr + size;
+
+       return ((addr >= start && addr < end) ||
+               (addr2 >= start && addr2 < end) ||
+               ((addr < start) && (addr2 >= end)));
+}
+
+static void check_for_illegal_area(struct device *dev, void *addr, u64 size)
+{
+       if (overlap(addr, size, _text, _etext) ||
+           overlap(addr, size, __start_rodata, __end_rodata))
+               err_printk(dev, NULL, "DMA-API: device driver maps "
+                               "memory from kernel text or rodata "
+                               "[addr=%p] [size=%llu]\n", addr, size);
 }
 
 static void check_sync(struct device *dev, dma_addr_t addr,
@@ -513,15 +701,15 @@ static void check_sync(struct device *dev, dma_addr_t addr,
        entry = hash_bucket_find(bucket, &ref);
 
        if (!entry) {
-               err_printk(dev, "DMA-API: device driver tries "
+               err_printk(dev, NULL, "DMA-API: device driver tries "
                                "to sync DMA memory it has not allocated "
                                "[device address=0x%016llx] [size=%llu bytes]\n",
-                               addr, size);
+                               (unsigned long long)addr, size);
                goto out;
        }
 
        if ((offset + size) > entry->size) {
-               err_printk(dev, "DMA-API: device driver syncs"
+               err_printk(dev, entry, "DMA-API: device driver syncs"
                                " DMA memory outside allocated range "
                                "[device address=0x%016llx] "
                                "[allocation size=%llu bytes] [sync offset=%llu] "
@@ -530,11 +718,11 @@ static void check_sync(struct device *dev, dma_addr_t addr,
        }
 
        if (direction != entry->direction) {
-               err_printk(dev, "DMA-API: device driver syncs "
+               err_printk(dev, entry, "DMA-API: device driver syncs "
                                "DMA memory with different direction "
                                "[device address=0x%016llx] [size=%llu bytes] "
                                "[mapped with %s] [synced with %s]\n",
-                               addr, entry->size,
+                               (unsigned long long)addr, entry->size,
                                dir2name[entry->direction],
                                dir2name[direction]);
        }
@@ -544,21 +732,21 @@ static void check_sync(struct device *dev, dma_addr_t addr,
 
        if (to_cpu && !(entry->direction == DMA_FROM_DEVICE) &&
                      !(direction == DMA_TO_DEVICE))
-               err_printk(dev, "DMA-API: device driver syncs "
+               err_printk(dev, entry, "DMA-API: device driver syncs "
                                "device read-only DMA memory for cpu "
                                "[device address=0x%016llx] [size=%llu bytes] "
                                "[mapped with %s] [synced with %s]\n",
-                               addr, entry->size,
+                               (unsigned long long)addr, entry->size,
                                dir2name[entry->direction],
                                dir2name[direction]);
 
        if (!to_cpu && !(entry->direction == DMA_TO_DEVICE) &&
                       !(direction == DMA_FROM_DEVICE))
-               err_printk(dev, "DMA-API: device driver syncs "
+               err_printk(dev, entry, "DMA-API: device driver syncs "
                                "device write-only DMA memory to device "
                                "[device address=0x%016llx] [size=%llu bytes] "
                                "[mapped with %s] [synced with %s]\n",
-                               addr, entry->size,
+                               (unsigned long long)addr, entry->size,
                                dir2name[entry->direction],
                                dir2name[direction]);
 
@@ -590,9 +778,13 @@ void debug_dma_map_page(struct device *dev, struct page *page, size_t offset,
        entry->size      = size;
        entry->direction = direction;
 
-       if (map_single) {
+       if (map_single)
                entry->type = dma_debug_single;
-               check_for_stack(dev, page_address(page) + offset);
+
+       if (!PageHighMem(page)) {
+               void *addr = ((char *)page_address(page)) + offset;
+               check_for_stack(dev, addr);
+               check_for_illegal_area(dev, addr, size);
        }
 
        add_dma_entry(entry);
@@ -644,7 +836,10 @@ void debug_dma_map_sg(struct device *dev, struct scatterlist *sg,
                entry->sg_call_ents   = nents;
                entry->sg_mapped_ents = mapped_ents;
 
-               check_for_stack(dev, sg_virt(s));
+               if (!PageHighMem(sg_page(s))) {
+                       check_for_stack(dev, sg_virt(s));
+                       check_for_illegal_area(dev, sg_virt(s), s->length);
+               }
 
                add_dma_entry(entry);
        }
@@ -782,3 +977,35 @@ void debug_dma_sync_single_range_for_device(struct device *dev,
 }
 EXPORT_SYMBOL(debug_dma_sync_single_range_for_device);
 
+void debug_dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
+                              int nelems, int direction)
+{
+       struct scatterlist *s;
+       int i;
+
+       if (unlikely(global_disable))
+               return;
+
+       for_each_sg(sg, s, nelems, i) {
+               check_sync(dev, s->dma_address, s->dma_length, 0,
+                               direction, true);
+       }
+}
+EXPORT_SYMBOL(debug_dma_sync_sg_for_cpu);
+
+void debug_dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
+                                 int nelems, int direction)
+{
+       struct scatterlist *s;
+       int i;
+
+       if (unlikely(global_disable))
+               return;
+
+       for_each_sg(sg, s, nelems, i) {
+               check_sync(dev, s->dma_address, s->dma_length, 0,
+                               direction, false);
+       }
+}
+EXPORT_SYMBOL(debug_dma_sync_sg_for_device);
+