summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/include/asm/dma-iommu.h1
-rw-r--r--arch/arm/mm/dma-mapping.c120
-rw-r--r--lib/dma-debug.c108
3 files changed, 229 insertions, 0 deletions
diff --git a/arch/arm/include/asm/dma-iommu.h b/arch/arm/include/asm/dma-iommu.h
index ed1e019b59e1..4d9f5742a149 100644
--- a/arch/arm/include/asm/dma-iommu.h
+++ b/arch/arm/include/asm/dma-iommu.h
@@ -21,6 +21,7 @@ struct dma_iommu_mapping {
spinlock_t lock;
struct kref kref;
+ struct list_head list;
};
struct dma_iommu_mapping *
diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index eecc6fd69ca0..938922676144 100644
--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -25,6 +25,8 @@
#include <linux/io.h>
#include <linux/vmalloc.h>
#include <linux/sizes.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
#include <asm/memory.h>
#include <asm/highmem.h>
@@ -1010,11 +1012,108 @@ int arm_dma_set_mask(struct device *dev, u64 dma_mask)
return 0;
}
+#if defined(CONFIG_ARM_DMA_USE_IOMMU)
+
+static LIST_HEAD(iommu_mapping_list);
+static DEFINE_SPINLOCK(iommu_mapping_list_lock);
+
+#if defined(CONFIG_DEBUG_FS)
+static dma_addr_t bit_to_addr(size_t pos, dma_addr_t base, size_t order)
+{
+ return base + pos * (1 << (PAGE_SHIFT + order));
+}
+
+static void seq_print_dma_areas(struct seq_file *s, void *bitmap,
+ dma_addr_t base, size_t bits, size_t order)
+{
+ /* one bit = one (page + order) sized block */
+ size_t pos = find_first_bit(bitmap, bits), end;
+
+ for (; pos < bits; pos = find_next_bit(bitmap, bits, end + 1)) {
+ end = find_next_zero_bit(bitmap, bits, pos);
+ seq_printf(s, " 0x%08x-0x%08x pages=%d\n",
+ bit_to_addr(pos, base, order),
+ bit_to_addr(end, base, order) - 1,
+ (end - pos) << order);
+ }
+}
+
+static void seq_print_mapping(struct seq_file *s,
+ struct dma_iommu_mapping *mapping)
+{
+ seq_printf(s, " memory map: base=0x%x size=%d order=%d domain=%p\n",
+ mapping->base, mapping->end - mapping->base,
+ mapping->order, mapping->domain);
+
+ seq_print_dma_areas(s, mapping->bitmap, mapping->base, mapping->bits,
+ mapping->order);
+}
+
+static void debug_dma_seq_print_mappings(struct seq_file *s)
+{
+ struct dma_iommu_mapping *mapping;
+ int i = 0;
+
+ list_for_each_entry(mapping, &iommu_mapping_list, list) {
+ seq_printf(s, "Map %d (%p):\n", i, mapping);
+ seq_print_mapping(s, mapping);
+ i++;
+ }
+}
+
+static int dump_iommu_mappings(struct seq_file *s, void *data)
+{
+ debug_dma_seq_print_mappings(s);
+ return 0;
+}
+
+static int dump_iommu_mappings_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dump_iommu_mappings, NULL);
+}
+
+static const struct file_operations dump_iommu_mappings_fops = {
+ .open = dump_iommu_mappings_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#endif /* CONFIG_DEBUG_FS */
+
+void dma_debugfs_platform_info(struct dentry *dent)
+{
+ debugfs_create_file("dump_mappings", S_IRUGO, dent, NULL,
+ &dump_iommu_mappings_fops);
+}
+
+#else /* !CONFIG_ARM_DMA_USE_IOMMU */
+static inline void dma_debugfs_platform_info(struct dentry *dent)
+{
+}
+#endif /* !CONFIG_ARM_DMA_USE_IOMMU */
+
+#if defined(CONFIG_DMA_API_DEBUG)
+static inline void dma_debug_platform(void)
+{
+}
+#else /* !CONFIG_DMA_API_DEBUG */
+static void dma_debug_platform(void)
+{
+ struct dentry *dent;
+
+ dent = debugfs_create_dir("dma-api", NULL);
+ if (dent)
+ dma_debugfs_platform_info(dent);
+}
+#endif /* !CONFIG_DMA_API_DEBUG */
+
#define PREALLOC_DMA_DEBUG_ENTRIES 4096
static int __init dma_debug_do_init(void)
{
dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES);
+ dma_debug_platform();
return 0;
}
fs_initcall(dma_debug_do_init);
@@ -1048,6 +1147,24 @@ static int __init iova_gap_pages_init(void)
}
core_initcall(iova_gap_pages_init);
+static void iommu_mapping_list_add(struct dma_iommu_mapping *mapping)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&iommu_mapping_list_lock, flags);
+ list_add_tail(&mapping->list, &iommu_mapping_list);
+ spin_unlock_irqrestore(&iommu_mapping_list_lock, flags);
+}
+
+static void iommu_mapping_list_del(struct dma_iommu_mapping *mapping)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&iommu_mapping_list_lock, flags);
+ list_del(&mapping->list);
+ spin_unlock_irqrestore(&iommu_mapping_list_lock, flags);
+}
+
static int pg_iommu_map(struct iommu_domain *domain, unsigned long iova,
phys_addr_t phys, size_t len, int prot)
{
@@ -2155,6 +2272,8 @@ arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, size_t size,
goto err3;
kref_init(&mapping->kref);
+
+ iommu_mapping_list_add(mapping);
return mapping;
err3:
kfree(mapping->bitmap);
@@ -2170,6 +2289,7 @@ static void release_iommu_mapping(struct kref *kref)
struct dma_iommu_mapping *mapping =
container_of(kref, struct dma_iommu_mapping, kref);
+ iommu_mapping_list_del(mapping);
iommu_domain_free(mapping->domain);
kfree(mapping->bitmap);
kfree(mapping);
diff --git a/lib/dma-debug.c b/lib/dma-debug.c
index cc42171ff462..554352eeae2e 100644
--- a/lib/dma-debug.c
+++ b/lib/dma-debug.c
@@ -659,6 +659,111 @@ out_unlock:
return count;
}
+char *__weak debug_dma_platformdata(struct device *dev)
+{
+ /* empty string by default */
+ static char buf[1];
+
+ return buf;
+}
+
+static inline void seq_print_ip_sym(struct seq_file *s, unsigned long ip)
+{
+ seq_printf(s, "[<%p>] %pS\n", (void *)ip, (void *)ip);
+}
+
+void seq_print_trace(struct seq_file *s, struct stack_trace *trace)
+{
+ int i;
+
+ if (WARN_ON(!trace->entries))
+ return;
+
+ for (i = trace->skip; i < trace->nr_entries; i++)
+ seq_print_ip_sym(s, trace->entries[i]);
+}
+
+/*
+ * Print all map entries just in the order they are stored. We assume that the
+ * user will be able to parse this later anyway. Detailed output includes stack
+ * traces of allocations.
+ */
+void seq_print_dma_mappings(struct seq_file *s, int detail)
+{
+ 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) {
+ seq_printf(s,
+ " %s %s idx %d P=%llx D=%llx L=%llx %s A=%s\n",
+ dev_name(entry->dev),
+ type2name[entry->type], idx,
+ (u64)entry->paddr,
+ entry->dev_addr, entry->size,
+ dir2name[entry->direction],
+ debug_dma_platformdata(entry->dev));
+
+ if (detail)
+ seq_print_trace(s, &entry->stacktrace);
+ }
+
+ spin_unlock_irqrestore(&bucket->lock, flags);
+ }
+}
+
+void __weak dma_debugfs_platform_info(struct dentry *dent)
+{
+}
+
+static int _dump_allocs(struct seq_file *s, void *data)
+{
+ int detail = (int)s->private;
+
+ seq_print_dma_mappings(s, detail);
+ return 0;
+}
+
+#define DEFINE_DEBUGFS(__name, __func, __data) \
+static int __name ## _open(struct inode *inode, struct file *file) \
+{ \
+ return single_open(file, __func, __data); \
+} \
+static const struct file_operations __name ## _fops = { \
+ .open = __name ## _open, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = single_release, \
+}
+
+DEFINE_DEBUGFS(_dump_allocs, _dump_allocs, NULL);
+DEFINE_DEBUGFS(_dump_allocs_detail, _dump_allocs, (void *)1);
+#undef DEFINE_DEBUGFS
+
+static int map_dump_debug_fs_init(void)
+{
+#define CREATE_FILE(name) \
+ debugfs_create_file(#name, S_IRUGO, \
+ dma_debug_dent, NULL, \
+ &_##name##_fops)
+
+ if (!CREATE_FILE(dump_allocs))
+ return -ENOMEM;
+
+ if (!CREATE_FILE(dump_allocs_detail))
+ return -ENOMEM;
+
+#undef CREATE_FILE
+
+ dma_debugfs_platform_info(dma_debug_dent);
+ return 0;
+}
+
static const struct file_operations filter_fops = {
.read = filter_read,
.write = filter_write,
@@ -713,6 +818,9 @@ static int dma_debug_fs_init(void)
if (!filter_dent)
goto out_err;
+ if (map_dump_debug_fs_init())
+ goto out_err;
+
return 0;
out_err: