summaryrefslogtreecommitdiff
path: root/drivers/iommu/iommu-debug.c
diff options
context:
space:
mode:
authorMitchel Humpherys <mitchelh@codeaurora.org>2015-07-06 15:21:24 -0700
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:13:27 -0700
commit1cbd5563954ad46b8c97522fa7b6f0ce941d6293 (patch)
tree5e8c9fbf72cb6bef82883ecf08315abb3aab03a1 /drivers/iommu/iommu-debug.c
parent682dd703a1a69de85744334a56f822bbf1655259 (diff)
iommu/iommu-debug: Add translation and mapping test files
Running ATOS commands on custom mappings is a useful tool for debugging. Add a new debugfs file for interactively attaching, detaching, mapping, unmapping, and issuing ATOS commands. Example usage: # cd /sys/kernel/debug/iommu/tests/soc:qcom,msm-audio-ion # echo 1 > attach # echo 0x1000,0x5000,0x1000,1 > map # echo 0x1008 > atos # cat atos 0x5008 # echo 0x2000 > atos # cat atos FAIL # echo 0 > attach Change-Id: I596cd3f05fcb59653e2acddc17d175855a1eb9a1 Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
Diffstat (limited to 'drivers/iommu/iommu-debug.c')
-rw-r--r--drivers/iommu/iommu-debug.c360
1 files changed, 356 insertions, 4 deletions
diff --git a/drivers/iommu/iommu-debug.c b/drivers/iommu/iommu-debug.c
index df9f003d60d0..72ebbac55b77 100644
--- a/drivers/iommu/iommu-debug.c
+++ b/drivers/iommu/iommu-debug.c
@@ -20,6 +20,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
+#include <linux/uaccess.h>
static struct dentry *debugfs_top_dir;
@@ -148,6 +149,10 @@ static struct dentry *debugfs_tests_dir;
struct iommu_debug_device {
struct device *dev;
+ struct iommu_domain *domain;
+ u64 iova;
+ u64 phys;
+ size_t len;
struct list_head list;
};
@@ -336,6 +341,325 @@ static const struct file_operations iommu_debug_profiling_fops = {
.release = single_release,
};
+static int iommu_debug_attach_do_attach(struct iommu_debug_device *ddev)
+{
+ int htw_disable = 1;
+
+ ddev->domain = iommu_domain_alloc(&platform_bus_type);
+ if (!ddev->domain) {
+ pr_err("Couldn't allocate domain\n");
+ return -ENOMEM;
+ }
+
+ if (iommu_domain_set_attr(ddev->domain,
+ DOMAIN_ATTR_COHERENT_HTW_DISABLE,
+ &htw_disable)) {
+ pr_err("Couldn't disable coherent htw\n");
+ goto out_domain_free;
+ }
+
+ if (iommu_attach_device(ddev->domain, ddev->dev)) {
+ pr_err("Couldn't attach new domain to device. Is it already attached?\n");
+ goto out_domain_free;
+ }
+
+ return 0;
+
+out_domain_free:
+ iommu_domain_free(ddev->domain);
+ ddev->domain = NULL;
+ return -EIO;
+}
+
+static ssize_t iommu_debug_attach_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *offset)
+{
+ struct iommu_debug_device *ddev = file->private_data;
+ ssize_t retval;
+ char val;
+
+ if (count > 2) {
+ pr_err("Invalid value. Expected 0 or 1.\n");
+ retval = -EINVAL;
+ goto out;
+ }
+
+ if (copy_from_user(&val, ubuf, 1)) {
+ pr_err("Couldn't copy from user\n");
+ retval = -EFAULT;
+ goto out;
+ }
+
+ if (val == '1') {
+ if (ddev->domain) {
+ pr_err("Already attached.\n");
+ retval = -EINVAL;
+ goto out;
+ }
+ if (WARN(ddev->dev->archdata.iommu,
+ "Attachment tracking out of sync with device\n")) {
+ retval = -EINVAL;
+ goto out;
+ }
+ if (iommu_debug_attach_do_attach(ddev)) {
+ retval = -EIO;
+ goto out;
+ }
+ pr_err("Attached\n");
+ } else if (val == '0') {
+ if (!ddev->domain) {
+ pr_err("No domain. Did you already attach?\n");
+ retval = -EINVAL;
+ goto out;
+ }
+ iommu_detach_device(ddev->domain, ddev->dev);
+ iommu_domain_free(ddev->domain);
+ ddev->domain = NULL;
+ pr_err("Detached\n");
+ } else {
+ pr_err("Invalid value. Expected 0 or 1\n");
+ retval = -EFAULT;
+ goto out;
+ }
+
+ retval = count;
+out:
+ return retval;
+}
+
+static ssize_t iommu_debug_attach_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *offset)
+{
+ struct iommu_debug_device *ddev = file->private_data;
+ char c[2];
+
+ if (*offset)
+ return 0;
+
+ c[0] = ddev->domain ? '1' : '0';
+ c[1] = '\n';
+ if (copy_to_user(ubuf, &c, 2)) {
+ pr_err("copy_to_user failed\n");
+ return -EFAULT;
+ }
+ *offset = 1; /* non-zero means we're done */
+
+ return 2;
+}
+
+static const struct file_operations iommu_debug_attach_fops = {
+ .open = simple_open,
+ .write = iommu_debug_attach_write,
+ .read = iommu_debug_attach_read,
+};
+
+static ssize_t iommu_debug_atos_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *offset)
+{
+ struct iommu_debug_device *ddev = file->private_data;
+ dma_addr_t iova;
+
+ if (kstrtoll_from_user(ubuf, count, 0, &iova)) {
+ pr_err("Invalid format for iova\n");
+ ddev->iova = 0;
+ return -EINVAL;
+ }
+
+ ddev->iova = iova;
+ pr_err("Saved iova=%pa for future ATOS commands\n", &iova);
+ return count;
+}
+
+static ssize_t iommu_debug_atos_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *offset)
+{
+ struct iommu_debug_device *ddev = file->private_data;
+ phys_addr_t phys;
+ char buf[100];
+ ssize_t retval;
+ size_t buflen;
+
+ if (!ddev->domain) {
+ pr_err("No domain. Did you already attach?\n");
+ return -EINVAL;
+ }
+
+ if (*offset)
+ return 0;
+
+ memset(buf, 0, 100);
+
+ phys = iommu_iova_to_phys_hard(ddev->domain, ddev->iova);
+ if (!phys)
+ strlcpy(buf, "FAIL\n", 100);
+ else
+ snprintf(buf, 100, "%pa\n", &phys);
+
+ buflen = strlen(buf);
+ if (copy_to_user(ubuf, buf, buflen)) {
+ pr_err("Couldn't copy_to_user\n");
+ retval = -EFAULT;
+ } else {
+ *offset = 1; /* non-zero means we're done */
+ retval = buflen;
+ }
+
+ return retval;
+}
+
+static const struct file_operations iommu_debug_atos_fops = {
+ .open = simple_open,
+ .write = iommu_debug_atos_write,
+ .read = iommu_debug_atos_read,
+};
+
+static ssize_t iommu_debug_map_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *offset)
+{
+ ssize_t retval;
+ int ret;
+ char *comma1, *comma2, *comma3;
+ char buf[100];
+ dma_addr_t iova;
+ phys_addr_t phys;
+ size_t size;
+ int prot;
+ struct iommu_debug_device *ddev = file->private_data;
+
+ if (count >= 100) {
+ pr_err("Value too large\n");
+ return -EINVAL;
+ }
+
+ if (!ddev->domain) {
+ pr_err("No domain. Did you already attach?\n");
+ return -EINVAL;
+ }
+
+ memset(buf, 0, 100);
+
+ if (copy_from_user(buf, ubuf, count)) {
+ pr_err("Couldn't copy from user\n");
+ retval = -EFAULT;
+ }
+
+ comma1 = strnchr(buf, count, ',');
+ if (!comma1)
+ goto invalid_format;
+
+ comma2 = strnchr(comma1 + 1, count, ',');
+ if (!comma2)
+ goto invalid_format;
+
+ comma3 = strnchr(comma2 + 1, count, ',');
+ if (!comma3)
+ goto invalid_format;
+
+ /* split up the words */
+ *comma1 = *comma2 = *comma3 = '\0';
+
+ if (kstrtou64(buf, 0, &iova))
+ goto invalid_format;
+
+ if (kstrtou64(comma1 + 1, 0, &phys))
+ goto invalid_format;
+
+ if (kstrtoul(comma2 + 1, 0, &size))
+ goto invalid_format;
+
+ if (kstrtoint(comma3 + 1, 0, &prot))
+ goto invalid_format;
+
+ ret = iommu_map(ddev->domain, iova, phys, size, prot);
+ if (ret) {
+ pr_err("iommu_map failed with %d\n", ret);
+ retval = -EIO;
+ goto out;
+ }
+
+ retval = count;
+ pr_err("Mapped %pa to %pa (len=0x%zx, prot=0x%x)\n",
+ &iova, &phys, size, prot);
+out:
+ return retval;
+
+invalid_format:
+ pr_err("Invalid format. Expected: iova,phys,len,prot where `prot' is the bitwise OR of IOMMU_READ, IOMMU_WRITE, etc.\n");
+ return retval;
+}
+
+static const struct file_operations iommu_debug_map_fops = {
+ .open = simple_open,
+ .write = iommu_debug_map_write,
+};
+
+static ssize_t iommu_debug_unmap_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *offset)
+{
+ ssize_t retval;
+ char *comma1;
+ char buf[100];
+ dma_addr_t iova;
+ size_t size;
+ size_t unmapped;
+ struct iommu_debug_device *ddev = file->private_data;
+
+ if (count >= 100) {
+ pr_err("Value too large\n");
+ return -EINVAL;
+ }
+
+ if (!ddev->domain) {
+ pr_err("No domain. Did you already attach?\n");
+ return -EINVAL;
+ }
+
+ memset(buf, 0, 100);
+
+ if (copy_from_user(buf, ubuf, count)) {
+ pr_err("Couldn't copy from user\n");
+ retval = -EFAULT;
+ goto out;
+ }
+
+ comma1 = strnchr(buf, count, ',');
+ if (!comma1)
+ goto invalid_format;
+
+ /* split up the words */
+ *comma1 = '\0';
+
+ if (kstrtou64(buf, 0, &iova))
+ goto invalid_format;
+
+ if (kstrtoul(buf, 0, &size))
+ goto invalid_format;
+
+ unmapped = iommu_unmap(ddev->domain, iova, size);
+ if (unmapped != size) {
+ pr_err("iommu_unmap failed. Expected to unmap: 0x%zx, unmapped: 0x%zx",
+ size, unmapped);
+ return -EIO;
+ }
+
+ retval = count;
+ pr_err("Unmapped %pa (len=0x%zx)\n", &iova, size);
+out:
+ return retval;
+
+invalid_format:
+ pr_err("Invalid format. Expected: iova,len\n");
+ return retval;
+}
+
+static const struct file_operations iommu_debug_unmap_fops = {
+ .open = simple_open,
+ .write = iommu_debug_unmap_write,
+};
+
/*
* The following will only work for drivers that implement the generic
* device tree bindings described in
@@ -344,7 +668,7 @@ static const struct file_operations iommu_debug_profiling_fops = {
static int snarf_iommu_devices(struct device *dev, void *ignored)
{
struct iommu_debug_device *ddev;
- struct dentry *dir, *profiling_dentry;
+ struct dentry *dir;
if (!of_find_property(dev->of_node, "iommus", NULL))
return 0;
@@ -359,14 +683,42 @@ static int snarf_iommu_devices(struct device *dev, void *ignored)
dev_name(dev));
goto err;
}
- profiling_dentry = debugfs_create_file("profiling", S_IRUSR, dir, ddev,
- &iommu_debug_profiling_fops);
- if (!profiling_dentry) {
+
+ if (!debugfs_create_file("profiling", S_IRUSR, dir, ddev,
+ &iommu_debug_profiling_fops)) {
pr_err("Couldn't create iommu/devices/%s/profiling debugfs file\n",
dev_name(dev));
goto err_rmdir;
}
+ if (!debugfs_create_file("attach", S_IRUSR, dir, ddev,
+ &iommu_debug_attach_fops)) {
+ pr_err("Couldn't create iommu/devices/%s/attach debugfs file\n",
+ dev_name(dev));
+ goto err_rmdir;
+ }
+
+ if (!debugfs_create_file("atos", S_IWUSR, dir, ddev,
+ &iommu_debug_atos_fops)) {
+ pr_err("Couldn't create iommu/devices/%s/atos debugfs file\n",
+ dev_name(dev));
+ goto err_rmdir;
+ }
+
+ if (!debugfs_create_file("map", S_IWUSR, dir, ddev,
+ &iommu_debug_map_fops)) {
+ pr_err("Couldn't create iommu/devices/%s/map debugfs file\n",
+ dev_name(dev));
+ goto err_rmdir;
+ }
+
+ if (!debugfs_create_file("unmap", S_IWUSR, dir, ddev,
+ &iommu_debug_unmap_fops)) {
+ pr_err("Couldn't create iommu/devices/%s/unmap debugfs file\n",
+ dev_name(dev));
+ goto err_rmdir;
+ }
+
list_add(&ddev->list, &iommu_debug_devices);
return 0;