Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 79bb5c8c authored by Mitchel Humpherys's avatar Mitchel Humpherys
Browse files

iommu: Add debugging infrastructure



Currently, debugging IOMMU issues is done via manual instrumentation of
the code or via low-level techniques like using a JTAG debugger.
Introduce a set of library functions and debugfs files to facilitate
interactive debugging and testing.

This patch introduces the basic infrastructure as well an initial
debugfs for:

  - viewing IOMMU attachments (domain->dev mappings created by
    iommu_attach_device) and resulting attributes (like the base address
    of the page tables)

  - basic performance profiling

Example usage:

    # cd /sys/kernel/debug/iommu/attachments
    # cat b40000.qcom,kgsl-iommu:iommu_kgsl_cb2
    Domain: 0xffffffc0cb983f00
    PT_BASE_ADDR: virt=0xffffffc057eca000 phys=0x00000000d7eca000

    # cd /sys/kernel/debug/iommu/tests
    # cat soc:qcom,cam_smmu:msm_cam_smmu_cb1/profiling
        size       iommu_map  iommu_unmap
          4K           47 us       909 us
         64K           97 us       594 us
          2M         1536 us       605 us
         12M         8737 us      1193 us
         20M        26517 us      1121 us

        size    iommu_map_sg  iommu_unmap
         64K           31 us       656 us
          2M          885 us       600 us
         12M         2674 us       687 us
         20M         4352 us      1096 us

Change-Id: I1c301eec6e64688831cad80ffd0380743f7f0df6
Signed-off-by: default avatarMitchel Humpherys <mitchelh@codeaurora.org>
parent 226df3e6
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -406,4 +406,34 @@ config ARM_SMMU
	  Say Y here if your SoC includes an IOMMU device implementing
	  the ARM SMMU architecture.

menuconfig IOMMU_DEBUG
	bool "IOMMU Profiling and Debugging"
	help
	  Makes available some additional IOMMU profiling and debugging
	  options.

if IOMMU_DEBUG

config IOMMU_DEBUG_TRACKING
	bool "Track key IOMMU events"
	select IOMMU_API
	help
	  Enables additional debug tracking in the IOMMU framework code.
	  Tracking information and tests can be accessed through various
	  debugfs files.

	  Say Y here if you need to debug IOMMU issues and are okay with
	  the performance penalty of the tracking.

config IOMMU_TESTS
	bool "Interactive IOMMU performance/functional tests"
	select IOMMU_API
	help
	  Enables a suite of IOMMU unit tests.  The tests are runnable
	  through debugfs.  Unlike the IOMMU_DEBUG_TRACKING option, the
	  impact of enabling this option to overal system performance
	  should be minimal.

endif # IOMMU_DEBUG

endif # IOMMU_SUPPORT
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ obj-$(CONFIG_IOMMU_API) += msm_dma_iommu_mapping.o
obj-$(CONFIG_IOMMU_IO_PGTABLE) += io-pgtable.o
obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o
obj-$(CONFIG_OF_IOMMU)	+= of_iommu.o
obj-$(CONFIG_IOMMU_DEBUG) += iommu-debug.o
obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_domains.o msm_iommu_mapping.o
obj-$(CONFIG_MSM_IOMMU_V1) += msm_iommu-v1.o msm_iommu_dev-v1.o msm_iommu_sec.o
obj-$(CONFIG_MSM_IOMMU_PMON) += msm_iommu_perfmon.o msm_iommu_perfmon-v1.o
+413 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#define pr_fmt(fmt) "iommu-debug: %s: " fmt, __func__

#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/iommu.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

static struct dentry *debugfs_top_dir;

#ifdef CONFIG_IOMMU_DEBUG_TRACKING

static DEFINE_MUTEX(iommu_debug_attachments_lock);
static LIST_HEAD(iommu_debug_attachments);
static struct dentry *debugfs_attachments_dir;

struct iommu_debug_attachment {
	struct iommu_domain *domain;
	struct device *dev;
	struct dentry *dentry;
	struct list_head list;
};

static int iommu_debug_attachment_show(struct seq_file *s, void *ignored)
{
	struct iommu_debug_attachment *attach = s->private;
	phys_addr_t pt_phys;

	seq_printf(s, "Domain: 0x%p\n", attach->domain);
	if (iommu_domain_get_attr(attach->domain, DOMAIN_ATTR_PT_BASE_ADDR,
				  &pt_phys)) {
		seq_puts(s, "PT_BASE_ADDR: (Unknown)\n");
	} else {
		void *pt_virt = phys_to_virt(pt_phys);

		seq_printf(s, "PT_BASE_ADDR: virt=0x%p phys=%pa\n",
			   pt_virt, &pt_phys);
	}

	return 0;
}

static int iommu_debug_attachment_open(struct inode *inode, struct file *file)
{
	return single_open(file, iommu_debug_attachment_show, inode->i_private);
}

static const struct file_operations iommu_debug_attachment_fops = {
	.open	 = iommu_debug_attachment_open,
	.read	 = seq_read,
	.llseek	 = seq_lseek,
	.release = single_release,
};

void iommu_debug_attach_device(struct iommu_domain *domain,
			       struct device *dev)
{
	struct iommu_debug_attachment *attach;

	mutex_lock(&iommu_debug_attachments_lock);

	attach = kmalloc(sizeof(*attach), GFP_KERNEL);
	if (!attach)
		goto unlock;

	attach->domain = domain;
	attach->dev = dev;

	attach->dentry = debugfs_create_file(
		dev_name(dev), S_IRUSR, debugfs_attachments_dir, attach,
		&iommu_debug_attachment_fops);
	if (!attach->dentry) {
		pr_err("Couldn't create iommu/attachments/%s debugfs file for domain 0x%p\n",
		       dev_name(dev), domain);
		kfree(attach);
		goto unlock;
	}

	list_add(&attach->list, &iommu_debug_attachments);
unlock:
	mutex_unlock(&iommu_debug_attachments_lock);
}

void iommu_debug_detach_device(struct iommu_domain *domain,
			       struct device *dev)
{
	struct iommu_debug_attachment *it;

	mutex_lock(&iommu_debug_attachments_lock);
	list_for_each_entry(it, &iommu_debug_attachments, list)
		if (it->domain == domain && it->dev == dev)
			break;

	if (&it->list == &iommu_debug_attachments) {
		WARN(1, "Couldn't find debug attachment for domain=0x%p dev=%s",
		     domain, dev_name(dev));
	} else {
		list_del(&it->list);
		debugfs_remove_recursive(it->dentry);
		kfree(it);
	}
	mutex_unlock(&iommu_debug_attachments_lock);
}

static int iommu_debug_init_tracking(void)
{
	debugfs_attachments_dir = debugfs_create_dir("attachments",
						     debugfs_top_dir);
	if (!debugfs_attachments_dir) {
		pr_err("Couldn't create iommu/attachments debugfs directory\n");
		return -ENODEV;
	}

	return 0;
}
#else
static inline int iommu_debug_init_tracking(void) { return 0; }
#endif

#ifdef CONFIG_IOMMU_TESTS

static LIST_HEAD(iommu_debug_devices);
static struct dentry *debugfs_tests_dir;

struct iommu_debug_device {
	struct device *dev;
	struct list_head list;
};

static int iommu_debug_build_phoney_sg_table(struct device *dev,
					     struct sg_table *table,
					     unsigned long total_size,
					     unsigned long chunk_size)
{
	unsigned long nents = total_size / chunk_size;
	struct scatterlist *sg;
	int i;
	struct page *page;

	BUG_ON(!IS_ALIGNED(total_size, PAGE_SIZE));
	BUG_ON(!IS_ALIGNED(total_size, chunk_size));
	BUG_ON(sg_alloc_table(table, nents, GFP_KERNEL));
	page = alloc_pages(GFP_KERNEL, get_order(chunk_size));
	if (!page)
		goto free_table;

	/* all the same page... why not. */
	for_each_sg(table->sgl, sg, table->nents, i)
		sg_set_page(sg, page, chunk_size, 0);

	return 0;

free_table:
	sg_free_table(table);
	return -ENOMEM;
}

static void iommu_debug_destroy_phoney_sg_table(struct device *dev,
						struct sg_table *table,
						unsigned long chunk_size)
{
	__free_pages(sg_page(table->sgl), get_order(chunk_size));
	sg_free_table(table);
}

static const char * const _size_to_string(unsigned long size)
{
	switch (size) {
	case SZ_4K:
		return "4K";
	case SZ_64K:
		return "64K";
	case SZ_2M:
		return "2M";
	case SZ_1M * 12:
		return "12M";
	case SZ_1M * 20:
		return "20M";
	}
	return "unknown size, please add to _size_to_string";
}

static void iommu_debug_device_profiling(struct seq_file *s, struct device *dev)
{
	unsigned long sizes[] = { SZ_4K, SZ_64K, SZ_2M, SZ_1M * 12,
				  SZ_1M * 20, 0 };
	unsigned long *sz;
	struct iommu_domain *domain;
	unsigned long iova = 0x10000;
	phys_addr_t paddr = 0xa000;
	int htw_disable = 1;

	domain = iommu_domain_alloc(&platform_bus_type);
	if (!domain) {
		seq_puts(s, "Couldn't allocate domain\n");
		return;
	}

	if (iommu_domain_set_attr(domain, DOMAIN_ATTR_COHERENT_HTW_DISABLE,
				  &htw_disable)) {
		seq_puts(s, "Couldn't disable coherent htw\n");
		goto out_domain_free;
	}

	if (iommu_attach_device(domain, dev)) {
		seq_puts(s,
			 "Couldn't attach new domain to device. Is it already attached?\n");
		goto out_domain_free;
	}

	seq_printf(s, "%8s %15s %12s\n", "size", "iommu_map", "iommu_unmap");
	for (sz = sizes; *sz; ++sz) {
		unsigned long size = *sz;
		size_t unmapped;
		s64 map_elapsed_us, unmap_elapsed_us;
		struct timespec tbefore, tafter, diff;

		getnstimeofday(&tbefore);
		if (iommu_map(domain, iova, paddr, size,
			      IOMMU_READ | IOMMU_WRITE)) {
			seq_puts(s, "Failed to map\n");
			continue;
		}
		getnstimeofday(&tafter);
		diff = timespec_sub(tafter, tbefore);
		map_elapsed_us = div_s64(timespec_to_ns(&diff), 1000);

		getnstimeofday(&tbefore);
		unmapped = iommu_unmap(domain, iova, size);
		if (unmapped != size) {
			seq_printf(s, "Only unmapped %zx instead of %zx\n",
				unmapped, size);
			continue;
		}
		getnstimeofday(&tafter);
		diff = timespec_sub(tafter, tbefore);
		unmap_elapsed_us = div_s64(timespec_to_ns(&diff), 1000);

		seq_printf(s, "%8s %12lld us %9lld us\n", _size_to_string(size),
			map_elapsed_us, unmap_elapsed_us);
	}

	seq_putc(s, '\n');
	seq_printf(s, "%8s %15s %12s\n", "size", "iommu_map_sg", "iommu_unmap");
	for (sz = sizes; *sz; ++sz) {
		unsigned long size = *sz;
		size_t unmapped;
		s64 map_elapsed_us, unmap_elapsed_us;
		struct timespec tbefore, tafter, diff;
		struct sg_table table;
		unsigned long chunk_size = SZ_4K;

		if (iommu_debug_build_phoney_sg_table(dev, &table, size,
						      chunk_size)) {
			seq_puts(s,
				"couldn't build phoney sg table! bailing...\n");
			goto out_detach;
		}

		getnstimeofday(&tbefore);
		if (iommu_map_sg(domain, iova, table.sgl, table.nents,
				 IOMMU_READ | IOMMU_WRITE) != size) {
			seq_puts(s, "Failed to map_sg\n");
			goto next;
		}
		getnstimeofday(&tafter);
		diff = timespec_sub(tafter, tbefore);
		map_elapsed_us = div_s64(timespec_to_ns(&diff), 1000);

		getnstimeofday(&tbefore);
		unmapped = iommu_unmap(domain, iova, size);
		if (unmapped != size) {
			seq_printf(s, "Only unmapped %zx instead of %zx\n",
				unmapped, size);
			goto next;
		}
		getnstimeofday(&tafter);
		diff = timespec_sub(tafter, tbefore);
		unmap_elapsed_us = div_s64(timespec_to_ns(&diff), 1000);

		seq_printf(s, "%8s %12lld us %9lld us\n", _size_to_string(size),
			map_elapsed_us, unmap_elapsed_us);

next:
		iommu_debug_destroy_phoney_sg_table(dev, &table, chunk_size);
	}

out_detach:
	iommu_detach_device(domain, dev);
out_domain_free:
	iommu_domain_free(domain);
}

static int iommu_debug_device_show(struct seq_file *s, void *ignored)
{
	struct iommu_debug_device *ddev = s->private;

	iommu_debug_device_profiling(s, ddev->dev);

	return 0;
}

static int iommu_debug_device_open(struct inode *inode, struct file *file)
{
	return single_open(file, iommu_debug_device_show, inode->i_private);
}

static const struct file_operations iommu_debug_device_fops = {
	.open	 = iommu_debug_device_open,
	.read	 = seq_read,
	.llseek	 = seq_lseek,
	.release = single_release,
};

/*
 * The following will only work for drivers that implement the generic
 * device tree bindings described in
 * Documentation/devicetree/bindings/iommu/iommu.txt
 */
static int snarf_iommu_devices(struct device *dev, void *ignored)
{
	struct iommu_debug_device *ddev;
	struct dentry *dir, *profiling_dentry;

	if (!of_find_property(dev->of_node, "iommus", NULL))
		return 0;

	ddev = kmalloc(sizeof(*ddev), GFP_KERNEL);
	if (!ddev)
		return -ENODEV;
	ddev->dev = dev;
	dir = debugfs_create_dir(dev_name(dev), debugfs_tests_dir);
	if (!dir) {
		pr_err("Couldn't create iommu/devices/%s debugfs dir\n",
		       dev_name(dev));
		goto err;
	}
	profiling_dentry = debugfs_create_file("profiling", S_IRUSR, dir, ddev,
					       &iommu_debug_device_fops);
	if (!profiling_dentry) {
		pr_err("Couldn't create iommu/devices/%s/profiling debugfs file\n",
		       dev_name(dev));
		goto err_rmdir;
	}

	list_add(&ddev->list, &iommu_debug_devices);
	return 0;

err_rmdir:
	debugfs_remove_recursive(dir);
err:
	kfree(ddev);
	return 0;
}

static int iommu_debug_init_tests(void)
{
	debugfs_tests_dir = debugfs_create_dir("tests",
					       debugfs_top_dir);
	if (!debugfs_tests_dir) {
		pr_err("Couldn't create iommu/tests debugfs directory\n");
		return -ENODEV;
	}

	return bus_for_each_dev(&platform_bus_type, NULL, NULL,
				snarf_iommu_devices);
}
#else
static inline int iommu_debug_init_tests(void) { return 0; }
#endif

static int iommu_debug_init(void)
{
	debugfs_top_dir = debugfs_create_dir("iommu", NULL);
	if (!debugfs_top_dir) {
		pr_err("Couldn't create iommu debugfs directory\n");
		return -ENODEV;
	}

	if (iommu_debug_init_tracking())
		goto err;

	if (iommu_debug_init_tests())
		goto err;

	return 0;
err:
	debugfs_remove_recursive(debugfs_top_dir);
	return -ENODEV;
}

static void iommu_debug_exit(void)
{
	debugfs_remove_recursive(debugfs_top_dir);
}

module_init(iommu_debug_init);
module_exit(iommu_debug_exit);
+23 −0
Original line number Diff line number Diff line
#ifndef IOMMU_DEBUG_H
#define IOMMU_DEBUG_H

#ifdef CONFIG_IOMMU_DEBUG_TRACKING

void iommu_debug_attach_device(struct iommu_domain *domain, struct device *dev);
void iommu_debug_detach_device(struct iommu_domain *domain, struct device *dev);

#else  /* !CONFIG_IOMMU_DEBUG_TRACKING */

static inline void iommu_debug_attach_device(struct iommu_domain *domain,
					     struct device *dev)
{
}

static inline void iommu_debug_detach_device(struct iommu_domain *domain,
					     struct device *dev)
{
}

#endif  /* CONFIG_IOMMU_DEBUG_TRACKING */

#endif /* IOMMU_DEBUG_H */
+6 −1
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@
#include <linux/bitops.h>
#include <trace/events/iommu.h>

#include "iommu-debug.h"

static struct kset *iommu_group_kset;
static struct ida iommu_group_ida;
static struct mutex iommu_group_mutex;
@@ -958,8 +960,10 @@ int iommu_attach_device(struct iommu_domain *domain, struct device *dev)
		return -ENODEV;

	ret = domain->ops->attach_dev(domain, dev);
	if (!ret)
	if (!ret) {
		trace_attach_device_to_domain(dev);
		iommu_debug_attach_device(domain, dev);
	}
	return ret;
}
EXPORT_SYMBOL_GPL(iommu_attach_device);
@@ -969,6 +973,7 @@ void iommu_detach_device(struct iommu_domain *domain, struct device *dev)
	if (unlikely(domain->ops->detach_dev == NULL))
		return;

	iommu_debug_detach_device(domain, dev);
	domain->ops->detach_dev(domain, dev);
	trace_detach_device_from_domain(dev);
}