]> Pileus Git - ~andy/linux/blobdiff - drivers/iommu/tegra-smmu.c
Merge branches 'dma-debug', 'iommu/fixes', 'arm/tegra', 'arm/exynos', 'x86/amd',...
[~andy/linux] / drivers / iommu / tegra-smmu.c
index 2a4bb36bc6888a9c91aee304df7c19bad1e42f83..0b4d62e0c64573cea96dc38f237436a036aa74cf 100644 (file)
 #include <linux/io.h>
 #include <linux/of.h>
 #include <linux/of_iommu.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
 
 #include <asm/page.h>
 #include <asm/cacheflush.h>
 
 #include <mach/iomap.h>
-#include <mach/smmu.h>
 #include <mach/tegra-ahb.h>
 
+enum smmu_hwgrp {
+       HWGRP_AFI,
+       HWGRP_AVPC,
+       HWGRP_DC,
+       HWGRP_DCB,
+       HWGRP_EPP,
+       HWGRP_G2,
+       HWGRP_HC,
+       HWGRP_HDA,
+       HWGRP_ISP,
+       HWGRP_MPE,
+       HWGRP_NV,
+       HWGRP_NV2,
+       HWGRP_PPCS,
+       HWGRP_SATA,
+       HWGRP_VDE,
+       HWGRP_VI,
+
+       HWGRP_COUNT,
+
+       HWGRP_END = ~0,
+};
+
+#define HWG_AFI                (1 << HWGRP_AFI)
+#define HWG_AVPC       (1 << HWGRP_AVPC)
+#define HWG_DC         (1 << HWGRP_DC)
+#define HWG_DCB                (1 << HWGRP_DCB)
+#define HWG_EPP                (1 << HWGRP_EPP)
+#define HWG_G2         (1 << HWGRP_G2)
+#define HWG_HC         (1 << HWGRP_HC)
+#define HWG_HDA                (1 << HWGRP_HDA)
+#define HWG_ISP                (1 << HWGRP_ISP)
+#define HWG_MPE                (1 << HWGRP_MPE)
+#define HWG_NV         (1 << HWGRP_NV)
+#define HWG_NV2                (1 << HWGRP_NV2)
+#define HWG_PPCS       (1 << HWGRP_PPCS)
+#define HWG_SATA       (1 << HWGRP_SATA)
+#define HWG_VDE                (1 << HWGRP_VDE)
+#define HWG_VI         (1 << HWGRP_VI)
+
 /* bitmap of the page sizes currently supported */
 #define SMMU_IOMMU_PGSIZES     (SZ_4K)
 
 #define SMMU_CONFIG_DISABLE                    0
 #define SMMU_CONFIG_ENABLE                     1
 
-#define SMMU_TLB_CONFIG                                0x14
-#define SMMU_TLB_CONFIG_STATS__MASK            (1 << 31)
-#define SMMU_TLB_CONFIG_STATS__ENABLE          (1 << 31)
+/* REVISIT: To support multiple MCs */
+enum {
+       _MC = 0,
+};
+
+enum {
+       _TLB = 0,
+       _PTC,
+};
+
+#define SMMU_CACHE_CONFIG_BASE                 0x14
+#define __SMMU_CACHE_CONFIG(mc, cache)         (SMMU_CACHE_CONFIG_BASE + 4 * cache)
+#define SMMU_CACHE_CONFIG(cache)               __SMMU_CACHE_CONFIG(_MC, cache)
+
+#define SMMU_CACHE_CONFIG_STATS_SHIFT          31
+#define SMMU_CACHE_CONFIG_STATS_ENABLE         (1 << SMMU_CACHE_CONFIG_STATS_SHIFT)
+#define SMMU_CACHE_CONFIG_STATS_TEST_SHIFT     30
+#define SMMU_CACHE_CONFIG_STATS_TEST           (1 << SMMU_CACHE_CONFIG_STATS_TEST_SHIFT)
+
 #define SMMU_TLB_CONFIG_HIT_UNDER_MISS__ENABLE (1 << 29)
 #define SMMU_TLB_CONFIG_ACTIVE_LINES__VALUE    0x10
 #define SMMU_TLB_CONFIG_RESET_VAL              0x20000010
 
-#define SMMU_PTC_CONFIG                                0x18
-#define SMMU_PTC_CONFIG_STATS__MASK            (1 << 31)
-#define SMMU_PTC_CONFIG_STATS__ENABLE          (1 << 31)
 #define SMMU_PTC_CONFIG_CACHE__ENABLE          (1 << 29)
 #define SMMU_PTC_CONFIG_INDEX_MAP__PATTERN     0x3f
 #define SMMU_PTC_CONFIG_RESET_VAL              0x2000003f
 
 #define SMMU_ASID_SECURITY                     0x38
 
-#define SMMU_STATS_TLB_HIT_COUNT               0x1f0
-#define SMMU_STATS_TLB_MISS_COUNT              0x1f4
-#define SMMU_STATS_PTC_HIT_COUNT               0x1f8
-#define SMMU_STATS_PTC_MISS_COUNT              0x1fc
+#define SMMU_STATS_CACHE_COUNT_BASE            0x1f0
+
+#define SMMU_STATS_CACHE_COUNT(mc, cache, hitmiss)             \
+       (SMMU_STATS_CACHE_COUNT_BASE + 8 * cache + 4 * hitmiss)
 
 #define SMMU_TRANSLATION_ENABLE_0              0x228
 #define SMMU_TRANSLATION_ENABLE_1              0x22c
@@ -231,6 +285,12 @@ struct smmu_as {
        spinlock_t              client_lock; /* for client list */
 };
 
+struct smmu_debugfs_info {
+       struct smmu_device *smmu;
+       int mc;
+       int cache;
+};
+
 /*
  * Per SMMU device - IOMMU device
  */
@@ -251,6 +311,9 @@ struct smmu_device {
        unsigned long translation_enable_2;
        unsigned long asid_security;
 
+       struct dentry *debugfs_root;
+       struct smmu_debugfs_info *debugfs_info;
+
        struct device_node *ahb;
 
        int             num_as;
@@ -412,8 +475,8 @@ static int smmu_setup_regs(struct smmu_device *smmu)
        smmu_write(smmu, smmu->translation_enable_1, SMMU_TRANSLATION_ENABLE_1);
        smmu_write(smmu, smmu->translation_enable_2, SMMU_TRANSLATION_ENABLE_2);
        smmu_write(smmu, smmu->asid_security, SMMU_ASID_SECURITY);
-       smmu_write(smmu, SMMU_TLB_CONFIG_RESET_VAL, SMMU_TLB_CONFIG);
-       smmu_write(smmu, SMMU_PTC_CONFIG_RESET_VAL, SMMU_PTC_CONFIG);
+       smmu_write(smmu, SMMU_TLB_CONFIG_RESET_VAL, SMMU_CACHE_CONFIG(_TLB));
+       smmu_write(smmu, SMMU_PTC_CONFIG_RESET_VAL, SMMU_CACHE_CONFIG(_PTC));
 
        smmu_flush_regs(smmu, 1);
 
@@ -895,6 +958,175 @@ static struct iommu_ops smmu_iommu_ops = {
        .pgsize_bitmap  = SMMU_IOMMU_PGSIZES,
 };
 
+/* Should be in the order of enum */
+static const char * const smmu_debugfs_mc[] = { "mc", };
+static const char * const smmu_debugfs_cache[] = {  "tlb", "ptc", };
+
+static ssize_t smmu_debugfs_stats_write(struct file *file,
+                                       const char __user *buffer,
+                                       size_t count, loff_t *pos)
+{
+       struct smmu_debugfs_info *info;
+       struct smmu_device *smmu;
+       struct dentry *dent;
+       int i;
+       enum {
+               _OFF = 0,
+               _ON,
+               _RESET,
+       };
+       const char * const command[] = {
+               [_OFF]          = "off",
+               [_ON]           = "on",
+               [_RESET]        = "reset",
+       };
+       char str[] = "reset";
+       u32 val;
+       size_t offs;
+
+       count = min_t(size_t, count, sizeof(str));
+       if (copy_from_user(str, buffer, count))
+               return -EINVAL;
+
+       for (i = 0; i < ARRAY_SIZE(command); i++)
+               if (strncmp(str, command[i],
+                           strlen(command[i])) == 0)
+                       break;
+
+       if (i == ARRAY_SIZE(command))
+               return -EINVAL;
+
+       dent = file->f_dentry;
+       info = dent->d_inode->i_private;
+       smmu = info->smmu;
+
+       offs = SMMU_CACHE_CONFIG(info->cache);
+       val = smmu_read(smmu, offs);
+       switch (i) {
+       case _OFF:
+               val &= ~SMMU_CACHE_CONFIG_STATS_ENABLE;
+               val &= ~SMMU_CACHE_CONFIG_STATS_TEST;
+               smmu_write(smmu, val, offs);
+               break;
+       case _ON:
+               val |= SMMU_CACHE_CONFIG_STATS_ENABLE;
+               val &= ~SMMU_CACHE_CONFIG_STATS_TEST;
+               smmu_write(smmu, val, offs);
+               break;
+       case _RESET:
+               val |= SMMU_CACHE_CONFIG_STATS_TEST;
+               smmu_write(smmu, val, offs);
+               val &= ~SMMU_CACHE_CONFIG_STATS_TEST;
+               smmu_write(smmu, val, offs);
+               break;
+       default:
+               BUG();
+               break;
+       }
+
+       dev_dbg(smmu->dev, "%s() %08x, %08x @%08x\n", __func__,
+               val, smmu_read(smmu, offs), offs);
+
+       return count;
+}
+
+static int smmu_debugfs_stats_show(struct seq_file *s, void *v)
+{
+       struct smmu_debugfs_info *info;
+       struct smmu_device *smmu;
+       struct dentry *dent;
+       int i;
+       const char * const stats[] = { "hit", "miss", };
+
+       dent = d_find_alias(s->private);
+       info = dent->d_inode->i_private;
+       smmu = info->smmu;
+
+       for (i = 0; i < ARRAY_SIZE(stats); i++) {
+               u32 val;
+               size_t offs;
+
+               offs = SMMU_STATS_CACHE_COUNT(info->mc, info->cache, i);
+               val = smmu_read(smmu, offs);
+               seq_printf(s, "%s:%08x ", stats[i], val);
+
+               dev_dbg(smmu->dev, "%s() %s %08x @%08x\n", __func__,
+                       stats[i], val, offs);
+       }
+       seq_printf(s, "\n");
+
+       return 0;
+}
+
+static int smmu_debugfs_stats_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, smmu_debugfs_stats_show, inode);
+}
+
+static const struct file_operations smmu_debugfs_stats_fops = {
+       .open           = smmu_debugfs_stats_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+       .write          = smmu_debugfs_stats_write,
+};
+
+static void smmu_debugfs_delete(struct smmu_device *smmu)
+{
+       debugfs_remove_recursive(smmu->debugfs_root);
+       kfree(smmu->debugfs_info);
+}
+
+static void smmu_debugfs_create(struct smmu_device *smmu)
+{
+       int i;
+       size_t bytes;
+       struct dentry *root;
+
+       bytes = ARRAY_SIZE(smmu_debugfs_mc) * ARRAY_SIZE(smmu_debugfs_cache) *
+               sizeof(*smmu->debugfs_info);
+       smmu->debugfs_info = kmalloc(bytes, GFP_KERNEL);
+       if (!smmu->debugfs_info)
+               return;
+
+       root = debugfs_create_dir(dev_name(smmu->dev), NULL);
+       if (!root)
+               goto err_out;
+       smmu->debugfs_root = root;
+
+       for (i = 0; i < ARRAY_SIZE(smmu_debugfs_mc); i++) {
+               int j;
+               struct dentry *mc;
+
+               mc = debugfs_create_dir(smmu_debugfs_mc[i], root);
+               if (!mc)
+                       goto err_out;
+
+               for (j = 0; j < ARRAY_SIZE(smmu_debugfs_cache); j++) {
+                       struct dentry *cache;
+                       struct smmu_debugfs_info *info;
+
+                       info = smmu->debugfs_info;
+                       info += i * ARRAY_SIZE(smmu_debugfs_mc) + j;
+                       info->smmu = smmu;
+                       info->mc = i;
+                       info->cache = j;
+
+                       cache = debugfs_create_file(smmu_debugfs_cache[j],
+                                                   S_IWUGO | S_IRUGO, mc,
+                                                   (void *)info,
+                                                   &smmu_debugfs_stats_fops);
+                       if (!cache)
+                               goto err_out;
+               }
+       }
+
+       return;
+
+err_out:
+       smmu_debugfs_delete(smmu);
+}
+
 static int tegra_smmu_suspend(struct device *dev)
 {
        struct smmu_device *smmu = dev_get_drvdata(dev);
@@ -999,6 +1231,7 @@ static int tegra_smmu_probe(struct platform_device *pdev)
        if (!smmu->avp_vector_page)
                return -ENOMEM;
 
+       smmu_debugfs_create(smmu);
        smmu_handle = smmu;
        return 0;
 }
@@ -1008,6 +1241,8 @@ static int tegra_smmu_remove(struct platform_device *pdev)
        struct smmu_device *smmu = platform_get_drvdata(pdev);
        int i;
 
+       smmu_debugfs_delete(smmu);
+
        smmu_write(smmu, SMMU_CONFIG_DISABLE, SMMU_CONFIG);
        for (i = 0; i < smmu->num_as; i++)
                free_pdir(&smmu->as[i]);