]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
Merge tag 'iommu-updates-v3.12' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 12 Sep 2013 18:29:26 +0000 (11:29 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 12 Sep 2013 18:29:26 +0000 (11:29 -0700)
Pull IOMMU Updates from Joerg Roedel:
 "This round the updates contain:

   - A new driver for the Freescale PAMU IOMMU from Varun Sethi.

     This driver has cooked for a while and required changes to the
     IOMMU-API and infrastructure that were already merged before.

   - Updates for the ARM-SMMU driver from Will Deacon

   - Various fixes, the most important one is probably a fix from Alex
     Williamson for a memory leak in the VT-d page-table freeing code

  In summary not all that much.  The biggest part in the diffstat is the
  new PAMU driver"

* tag 'iommu-updates-v3.12' of git://git.kernel.org/pub/scm/linux/kernel/git/joro/iommu:
  intel-iommu: Fix leaks in pagetable freeing
  iommu/amd: Fix resource leak in iommu_init_device()
  iommu/amd: Clean up unnecessary MSI/MSI-X capability find
  iommu/arm-smmu: Simplify VMID and ASID allocation
  iommu/arm-smmu: Don't use VMIDs for stage-1 translations
  iommu/arm-smmu: Tighten up global fault reporting
  iommu/arm-smmu: Remove broken big-endian check
  iommu/fsl: Remove unnecessary 'fsl-pamu' prefixes
  iommu/fsl: Fix whitespace problems noticed by git-am
  iommu/fsl: Freescale PAMU driver and iommu implementation.
  iommu/fsl: Add additional iommu attributes required by the PAMU driver.
  powerpc: Add iommu domain pointer to device archdata
  iommu/exynos: Remove dead code (set_prefbuf)

15 files changed:
arch/powerpc/include/asm/device.h
arch/powerpc/include/asm/fsl_pamu_stash.h [new file with mode: 0644]
arch/powerpc/sysdev/fsl_pci.h
drivers/iommu/Kconfig
drivers/iommu/Makefile
drivers/iommu/amd_iommu.c
drivers/iommu/amd_iommu_init.c
drivers/iommu/arm-smmu.c
drivers/iommu/exynos-iommu.c
drivers/iommu/fsl_pamu.c [new file with mode: 0644]
drivers/iommu/fsl_pamu.h [new file with mode: 0644]
drivers/iommu/fsl_pamu_domain.c [new file with mode: 0644]
drivers/iommu/fsl_pamu_domain.h [new file with mode: 0644]
drivers/iommu/intel-iommu.c
include/linux/iommu.h

index 77e97dd0c15d8b495ee245a3fbcc14aa38a2f785..38faeded7d595d506de55188ca8180c6e31ba67f 100644 (file)
@@ -28,6 +28,9 @@ struct dev_archdata {
                void            *iommu_table_base;
        } dma_data;
 
+#ifdef CONFIG_IOMMU_API
+       void                    *iommu_domain;
+#endif
 #ifdef CONFIG_SWIOTLB
        dma_addr_t              max_direct_dma_addr;
 #endif
diff --git a/arch/powerpc/include/asm/fsl_pamu_stash.h b/arch/powerpc/include/asm/fsl_pamu_stash.h
new file mode 100644 (file)
index 0000000..caa1b21
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ *
+ */
+
+#ifndef __FSL_PAMU_STASH_H
+#define __FSL_PAMU_STASH_H
+
+/* cache stash targets */
+enum pamu_stash_target {
+       PAMU_ATTR_CACHE_L1 = 1,
+       PAMU_ATTR_CACHE_L2,
+       PAMU_ATTR_CACHE_L3,
+};
+
+/*
+ * This attribute allows configuring stashig specific parameters
+ * in the PAMU hardware.
+ */
+
+struct pamu_stash_attribute {
+       u32     cpu;    /* cpu number */
+       u32     cache;  /* cache to stash to: L1,L2,L3 */
+};
+
+#endif  /* __FSL_PAMU_STASH_H */
index defc422a375f1083703c47962c4a245b48284061..8d455df584711c84c004468be1656fd4b55e36eb 100644 (file)
 
 struct platform_device;
 
+
+/* FSL PCI controller BRR1 register */
+#define PCI_FSL_BRR1      0xbf8
+#define PCI_FSL_BRR1_VER 0xffff
+
 #define PCIE_LTSSM     0x0404          /* PCIE Link Training and Status */
 #define PCIE_LTSSM_L0  0x16            /* L0 state */
 #define PCIE_IP_REV_2_2                0x02080202 /* PCIE IP block version Rev2.2 */
index 820d85c4a4a0f8c2b8fc813adfa30a07c529a953..fe302e33f72e7b2da024180e697422461b017bc7 100644 (file)
@@ -17,6 +17,16 @@ config OF_IOMMU
        def_bool y
        depends on OF
 
+config FSL_PAMU
+       bool "Freescale IOMMU support"
+       depends on PPC_E500MC
+       select IOMMU_API
+       select GENERIC_ALLOCATOR
+       help
+         Freescale PAMU support. PAMU is the IOMMU present on Freescale QorIQ platforms.
+         PAMU can authorize memory access, remap the memory address, and remap I/O
+         transaction types.
+
 # MSM IOMMU support
 config MSM_IOMMU
        bool "MSM IOMMU Support"
index bbe7041212dd64e3ff934cc23c45fdee3adcdda4..14c1f474cf1188008316e7e480c7e3c6d22f04f5 100644 (file)
@@ -16,3 +16,4 @@ obj-$(CONFIG_TEGRA_IOMMU_SMMU) += tegra-smmu.o
 obj-$(CONFIG_EXYNOS_IOMMU) += exynos-iommu.o
 obj-$(CONFIG_SHMOBILE_IOMMU) += shmobile-iommu.o
 obj-$(CONFIG_SHMOBILE_IPMMU) += shmobile-ipmmu.o
+obj-$(CONFIG_FSL_PAMU) += fsl_pamu.o fsl_pamu_domain.o
index 6dc659426a51f5cca55fdbaf2348b3c28fa03dde..72531f008a5e34ea871e7d3d3569a724af695d86 100644 (file)
@@ -456,8 +456,10 @@ static int iommu_init_device(struct device *dev)
        }
 
        ret = init_iommu_group(dev);
-       if (ret)
+       if (ret) {
+               free_dev_data(dev_data);
                return ret;
+       }
 
        if (pci_iommuv2_capable(pdev)) {
                struct amd_iommu *iommu;
index 7acbf351e9af2d52b80909177ce89dae6a8fc558..8f798be6e398d98ccacc5ba85f3f5b76c4096683 100644 (file)
@@ -1384,7 +1384,7 @@ static int iommu_init_msi(struct amd_iommu *iommu)
        if (iommu->int_enabled)
                goto enable_faults;
 
-       if (pci_find_capability(iommu->dev, PCI_CAP_ID_MSI))
+       if (iommu->dev->msi_cap)
                ret = iommu_setup_msi(iommu);
        else
                ret = -ENODEV;
index ebd0a4cff049a7479a32884d6ed86199208b7b94..f417e89e1e7e47e812fef6e16faf9a19432ce0b4 100644 (file)
@@ -56,9 +56,6 @@
 /* Maximum number of mapping groups per SMMU */
 #define ARM_SMMU_MAX_SMRS              128
 
-/* Number of VMIDs per SMMU */
-#define ARM_SMMU_NUM_VMIDS             256
-
 /* SMMU global address space */
 #define ARM_SMMU_GR0(smmu)             ((smmu)->base)
 #define ARM_SMMU_GR1(smmu)             ((smmu)->base + (smmu)->pagesize)
@@ -87,6 +84,7 @@
 #define ARM_SMMU_PTE_AP_UNPRIV         (((pteval_t)1) << 6)
 #define ARM_SMMU_PTE_AP_RDONLY         (((pteval_t)2) << 6)
 #define ARM_SMMU_PTE_ATTRINDX_SHIFT    2
+#define ARM_SMMU_PTE_nG                        (((pteval_t)1) << 11)
 
 /* Stage-2 PTE */
 #define ARM_SMMU_PTE_HAP_FAULT         (((pteval_t)0) << 6)
 #define ARM_SMMU_CB_FAR_LO             0x60
 #define ARM_SMMU_CB_FAR_HI             0x64
 #define ARM_SMMU_CB_FSYNR0             0x68
+#define ARM_SMMU_CB_S1_TLBIASID                0x610
 
 #define SCTLR_S1_ASIDPNE               (1 << 12)
 #define SCTLR_CFCFG                    (1 << 7)
 #define TTBCR2_ADDR_44                 4
 #define TTBCR2_ADDR_48                 5
 
+#define TTBRn_HI_ASID_SHIFT            16
+
 #define MAIR_ATTR_SHIFT(n)             ((n) << 3)
 #define MAIR_ATTR_MASK                 0xff
 #define MAIR_ATTR_DEVICE               0x04
 #define FSR_IGN                                (FSR_AFF | FSR_ASF | FSR_TLBMCF |       \
                                         FSR_TLBLKF)
 #define FSR_FAULT                      (FSR_MULTI | FSR_SS | FSR_UUT |         \
-                                        FSR_EF | FSR_PF | FSR_TF)
+                                        FSR_EF | FSR_PF | FSR_TF | FSR_IGN)
 
 #define FSYNR0_WNR                     (1 << 4)
 
@@ -365,21 +366,21 @@ struct arm_smmu_device {
        u32                             num_context_irqs;
        unsigned int                    *irqs;
 
-       DECLARE_BITMAP(vmid_map, ARM_SMMU_NUM_VMIDS);
-
        struct list_head                list;
        struct rb_root                  masters;
 };
 
 struct arm_smmu_cfg {
        struct arm_smmu_device          *smmu;
-       u8                              vmid;
        u8                              cbndx;
        u8                              irptndx;
        u32                             cbar;
        pgd_t                           *pgd;
 };
 
+#define ARM_SMMU_CB_ASID(cfg)          ((cfg)->cbndx)
+#define ARM_SMMU_CB_VMID(cfg)          ((cfg)->cbndx + 1)
+
 struct arm_smmu_domain {
        /*
         * A domain can span across multiple, chained SMMUs and requires
@@ -533,6 +534,25 @@ static void arm_smmu_tlb_sync(struct arm_smmu_device *smmu)
        }
 }
 
+static void arm_smmu_tlb_inv_context(struct arm_smmu_cfg *cfg)
+{
+       struct arm_smmu_device *smmu = cfg->smmu;
+       void __iomem *base = ARM_SMMU_GR0(smmu);
+       bool stage1 = cfg->cbar != CBAR_TYPE_S2_TRANS;
+
+       if (stage1) {
+               base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx);
+               writel_relaxed(ARM_SMMU_CB_ASID(cfg),
+                              base + ARM_SMMU_CB_S1_TLBIASID);
+       } else {
+               base = ARM_SMMU_GR0(smmu);
+               writel_relaxed(ARM_SMMU_CB_VMID(cfg),
+                              base + ARM_SMMU_GR0_TLBIVMID);
+       }
+
+       arm_smmu_tlb_sync(smmu);
+}
+
 static irqreturn_t arm_smmu_context_fault(int irq, void *dev)
 {
        int flags, ret;
@@ -590,6 +610,9 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev)
        void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
 
        gfsr = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSR);
+       if (!gfsr)
+               return IRQ_NONE;
+
        gfsynr0 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR0);
        gfsynr1 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR1);
        gfsynr2 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sGFSYNR2);
@@ -601,7 +624,7 @@ static irqreturn_t arm_smmu_global_fault(int irq, void *dev)
                gfsr, gfsynr0, gfsynr1, gfsynr2);
 
        writel(gfsr, gr0_base + ARM_SMMU_GR0_sGFSR);
-       return IRQ_NONE;
+       return IRQ_HANDLED;
 }
 
 static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain)
@@ -618,14 +641,15 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain)
        cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, root_cfg->cbndx);
 
        /* CBAR */
-       reg = root_cfg->cbar |
-             (root_cfg->vmid << CBAR_VMID_SHIFT);
+       reg = root_cfg->cbar;
        if (smmu->version == 1)
              reg |= root_cfg->irptndx << CBAR_IRPTNDX_SHIFT;
 
        /* Use the weakest memory type, so it is overridden by the pte */
        if (stage1)
                reg |= (CBAR_S1_MEMATTR_WB << CBAR_S1_MEMATTR_SHIFT);
+       else
+               reg |= ARM_SMMU_CB_VMID(root_cfg) << CBAR_VMID_SHIFT;
        writel_relaxed(reg, gr1_base + ARM_SMMU_GR1_CBAR(root_cfg->cbndx));
 
        if (smmu->version > 1) {
@@ -687,15 +711,11 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain)
 
        /* TTBR0 */
        reg = __pa(root_cfg->pgd);
-#ifndef __BIG_ENDIAN
        writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_LO);
        reg = (phys_addr_t)__pa(root_cfg->pgd) >> 32;
+       if (stage1)
+               reg |= ARM_SMMU_CB_ASID(root_cfg) << TTBRn_HI_ASID_SHIFT;
        writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_HI);
-#else
-       writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_HI);
-       reg = (phys_addr_t)__pa(root_cfg->pgd) >> 32;
-       writel_relaxed(reg, cb_base + ARM_SMMU_CB_TTBR0_LO);
-#endif
 
        /*
         * TTBCR
@@ -750,10 +770,6 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain)
                writel_relaxed(reg, cb_base + ARM_SMMU_CB_S1_MAIR0);
        }
 
-       /* Nuke the TLB */
-       writel_relaxed(root_cfg->vmid, gr0_base + ARM_SMMU_GR0_TLBIVMID);
-       arm_smmu_tlb_sync(smmu);
-
        /* SCTLR */
        reg = SCTLR_CFCFG | SCTLR_CFIE | SCTLR_CFRE | SCTLR_M | SCTLR_EAE_SBOP;
        if (stage1)
@@ -790,11 +806,6 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
                return -ENODEV;
        }
 
-       ret = __arm_smmu_alloc_bitmap(smmu->vmid_map, 0, ARM_SMMU_NUM_VMIDS);
-       if (IS_ERR_VALUE(ret))
-               return ret;
-
-       root_cfg->vmid = ret;
        if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) {
                /*
                 * We will likely want to change this if/when KVM gets
@@ -813,10 +824,9 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
        ret = __arm_smmu_alloc_bitmap(smmu->context_map, start,
                                      smmu->num_context_banks);
        if (IS_ERR_VALUE(ret))
-               goto out_free_vmid;
+               return ret;
 
        root_cfg->cbndx = ret;
-
        if (smmu->version == 1) {
                root_cfg->irptndx = atomic_inc_return(&smmu->irptndx);
                root_cfg->irptndx %= smmu->num_context_irqs;
@@ -840,8 +850,6 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
 
 out_free_context:
        __arm_smmu_free_bitmap(smmu->context_map, root_cfg->cbndx);
-out_free_vmid:
-       __arm_smmu_free_bitmap(smmu->vmid_map, root_cfg->vmid);
        return ret;
 }
 
@@ -850,17 +858,22 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain)
        struct arm_smmu_domain *smmu_domain = domain->priv;
        struct arm_smmu_cfg *root_cfg = &smmu_domain->root_cfg;
        struct arm_smmu_device *smmu = root_cfg->smmu;
+       void __iomem *cb_base;
        int irq;
 
        if (!smmu)
                return;
 
+       /* Disable the context bank and nuke the TLB before freeing it. */
+       cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, root_cfg->cbndx);
+       writel_relaxed(0, cb_base + ARM_SMMU_CB_SCTLR);
+       arm_smmu_tlb_inv_context(root_cfg);
+
        if (root_cfg->irptndx != -1) {
                irq = smmu->irqs[smmu->num_global_irqs + root_cfg->irptndx];
                free_irq(irq, domain);
        }
 
-       __arm_smmu_free_bitmap(smmu->vmid_map, root_cfg->vmid);
        __arm_smmu_free_bitmap(smmu->context_map, root_cfg->cbndx);
 }
 
@@ -959,6 +972,11 @@ static void arm_smmu_free_pgtables(struct arm_smmu_domain *smmu_domain)
 static void arm_smmu_domain_destroy(struct iommu_domain *domain)
 {
        struct arm_smmu_domain *smmu_domain = domain->priv;
+
+       /*
+        * Free the domain resources. We assume that all devices have
+        * already been detached.
+        */
        arm_smmu_destroy_domain_context(domain);
        arm_smmu_free_pgtables(smmu_domain);
        kfree(smmu_domain);
@@ -1199,7 +1217,7 @@ static int arm_smmu_alloc_init_pte(struct arm_smmu_device *smmu, pmd_t *pmd,
        }
 
        if (stage == 1) {
-               pteval |= ARM_SMMU_PTE_AP_UNPRIV;
+               pteval |= ARM_SMMU_PTE_AP_UNPRIV | ARM_SMMU_PTE_nG;
                if (!(flags & IOMMU_WRITE) && (flags & IOMMU_READ))
                        pteval |= ARM_SMMU_PTE_AP_RDONLY;
 
@@ -1415,13 +1433,9 @@ static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova,
 {
        int ret;
        struct arm_smmu_domain *smmu_domain = domain->priv;
-       struct arm_smmu_cfg *root_cfg = &smmu_domain->root_cfg;
-       struct arm_smmu_device *smmu = root_cfg->smmu;
-       void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
 
        ret = arm_smmu_handle_mapping(smmu_domain, iova, 0, size, 0);
-       writel_relaxed(root_cfg->vmid, gr0_base + ARM_SMMU_GR0_TLBIVMID);
-       arm_smmu_tlb_sync(smmu);
+       arm_smmu_tlb_inv_context(&smmu_domain->root_cfg);
        return ret ? ret : size;
 }
 
@@ -1544,6 +1558,7 @@ static struct iommu_ops arm_smmu_ops = {
 static void arm_smmu_device_reset(struct arm_smmu_device *smmu)
 {
        void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
+       void __iomem *sctlr_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB_SCTLR;
        int i = 0;
        u32 scr0 = readl_relaxed(gr0_base + ARM_SMMU_GR0_sCR0);
 
@@ -1553,6 +1568,10 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu)
                writel_relaxed(S2CR_TYPE_BYPASS, gr0_base + ARM_SMMU_GR0_S2CR(i));
        }
 
+       /* Make sure all context banks are disabled */
+       for (i = 0; i < smmu->num_context_banks; ++i)
+               writel_relaxed(0, sctlr_base + ARM_SMMU_CB(smmu, i));
+
        /* Invalidate the TLB, just in case */
        writel_relaxed(0, gr0_base + ARM_SMMU_GR0_STLBIALL);
        writel_relaxed(0, gr0_base + ARM_SMMU_GR0_TLBIALLH);
@@ -1906,7 +1925,7 @@ static int arm_smmu_device_remove(struct platform_device *pdev)
                of_node_put(master->of_node);
        }
 
-       if (!bitmap_empty(smmu->vmid_map, ARM_SMMU_NUM_VMIDS))
+       if (!bitmap_empty(smmu->context_map, ARM_SMMU_MAX_CBS))
                dev_err(dev, "removing device with active domains!\n");
 
        for (i = 0; i < smmu->num_global_irqs; ++i)
index 3f32d64ab87a4f98f910212b1f1a8a8d66d99b5f..074018979cdfb047f96619bf050b0f26ba5a623f 100644 (file)
@@ -247,50 +247,6 @@ static void __sysmmu_set_prefbuf(void __iomem *sfrbase, unsigned long base,
        __raw_writel(size - 1 + base,  sfrbase + REG_PB0_EADDR + idx * 8);
 }
 
-void exynos_sysmmu_set_prefbuf(struct device *dev,
-                               unsigned long base0, unsigned long size0,
-                               unsigned long base1, unsigned long size1)
-{
-       struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
-       unsigned long flags;
-       int i;
-
-       BUG_ON((base0 + size0) <= base0);
-       BUG_ON((size1 > 0) && ((base1 + size1) <= base1));
-
-       read_lock_irqsave(&data->lock, flags);
-       if (!is_sysmmu_active(data))
-               goto finish;
-
-       for (i = 0; i < data->nsfrs; i++) {
-               if ((readl(data->sfrbases[i] + REG_MMU_VERSION) >> 28) == 3) {
-                       if (!sysmmu_block(data->sfrbases[i]))
-                               continue;
-
-                       if (size1 == 0) {
-                               if (size0 <= SZ_128K) {
-                                       base1 = base0;
-                                       size1 = size0;
-                               } else {
-                                       size1 = size0 -
-                                               ALIGN(size0 / 2, SZ_64K);
-                                       size0 = size0 - size1;
-                                       base1 = base0 + size0;
-                               }
-                       }
-
-                       __sysmmu_set_prefbuf(
-                                       data->sfrbases[i], base0, size0, 0);
-                       __sysmmu_set_prefbuf(
-                                       data->sfrbases[i], base1, size1, 1);
-
-                       sysmmu_unblock(data->sfrbases[i]);
-               }
-       }
-finish:
-       read_unlock_irqrestore(&data->lock, flags);
-}
-
 static void __set_fault_handler(struct sysmmu_drvdata *data,
                                        sysmmu_fault_handler_t handler)
 {
diff --git a/drivers/iommu/fsl_pamu.c b/drivers/iommu/fsl_pamu.c
new file mode 100644 (file)
index 0000000..cba0498
--- /dev/null
@@ -0,0 +1,1309 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ *
+ */
+
+#define pr_fmt(fmt)    "fsl-pamu: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/iommu.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/of_platform.h>
+#include <linux/bootmem.h>
+#include <linux/genalloc.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+#include <asm/fsl_guts.h>
+
+#include "fsl_pamu.h"
+
+/* define indexes for each operation mapping scenario */
+#define OMI_QMAN        0x00
+#define OMI_FMAN        0x01
+#define OMI_QMAN_PRIV   0x02
+#define OMI_CAAM        0x03
+
+#define make64(high, low) (((u64)(high) << 32) | (low))
+
+struct pamu_isr_data {
+       void __iomem *pamu_reg_base;    /* Base address of PAMU regs*/
+       unsigned int count;             /* The number of PAMUs */
+};
+
+static struct paace *ppaact;
+static struct paace *spaact;
+static struct ome *omt;
+
+/*
+ * Table for matching compatible strings, for device tree
+ * guts node, for QorIQ SOCs.
+ * "fsl,qoriq-device-config-2.0" corresponds to T4 & B4
+ * SOCs. For the older SOCs "fsl,qoriq-device-config-1.0"
+ * string would be used.
+*/
+static const struct of_device_id guts_device_ids[] = {
+       { .compatible = "fsl,qoriq-device-config-1.0", },
+       { .compatible = "fsl,qoriq-device-config-2.0", },
+       {}
+};
+
+
+/*
+ * Table for matching compatible strings, for device tree
+ * L3 cache controller node.
+ * "fsl,t4240-l3-cache-controller" corresponds to T4,
+ * "fsl,b4860-l3-cache-controller" corresponds to B4 &
+ * "fsl,p4080-l3-cache-controller" corresponds to other,
+ * SOCs.
+*/
+static const struct of_device_id l3_device_ids[] = {
+       { .compatible = "fsl,t4240-l3-cache-controller", },
+       { .compatible = "fsl,b4860-l3-cache-controller", },
+       { .compatible = "fsl,p4080-l3-cache-controller", },
+       {}
+};
+
+/* maximum subwindows permitted per liodn */
+static u32 max_subwindow_count;
+
+/* Pool for fspi allocation */
+struct gen_pool *spaace_pool;
+
+/**
+ * pamu_get_max_subwin_cnt() - Return the maximum supported
+ * subwindow count per liodn.
+ *
+ */
+u32 pamu_get_max_subwin_cnt()
+{
+       return max_subwindow_count;
+}
+
+/**
+ * pamu_get_ppaace() - Return the primary PACCE
+ * @liodn: liodn PAACT index for desired PAACE
+ *
+ * Returns the ppace pointer upon success else return
+ * null.
+ */
+static struct paace *pamu_get_ppaace(int liodn)
+{
+       if (!ppaact || liodn >= PAACE_NUMBER_ENTRIES) {
+               pr_debug("PPAACT doesn't exist\n");
+               return NULL;
+       }
+
+       return &ppaact[liodn];
+}
+
+/**
+ * pamu_enable_liodn() - Set valid bit of PACCE
+ * @liodn: liodn PAACT index for desired PAACE
+ *
+ * Returns 0 upon success else error code < 0 returned
+ */
+int pamu_enable_liodn(int liodn)
+{
+       struct paace *ppaace;
+
+       ppaace = pamu_get_ppaace(liodn);
+       if (!ppaace) {
+               pr_debug("Invalid primary paace entry\n");
+               return -ENOENT;
+       }
+
+       if (!get_bf(ppaace->addr_bitfields, PPAACE_AF_WSE)) {
+               pr_debug("liodn %d not configured\n", liodn);
+               return -EINVAL;
+       }
+
+       /* Ensure that all other stores to the ppaace complete first */
+       mb();
+
+       set_bf(ppaace->addr_bitfields, PAACE_AF_V, PAACE_V_VALID);
+       mb();
+
+       return 0;
+}
+
+/**
+ * pamu_disable_liodn() - Clears valid bit of PACCE
+ * @liodn: liodn PAACT index for desired PAACE
+ *
+ * Returns 0 upon success else error code < 0 returned
+ */
+int pamu_disable_liodn(int liodn)
+{
+       struct paace *ppaace;
+
+       ppaace = pamu_get_ppaace(liodn);
+       if (!ppaace) {
+               pr_debug("Invalid primary paace entry\n");
+               return -ENOENT;
+       }
+
+       set_bf(ppaace->addr_bitfields, PAACE_AF_V, PAACE_V_INVALID);
+       mb();
+
+       return 0;
+}
+
+/* Derive the window size encoding for a particular PAACE entry */
+static unsigned int map_addrspace_size_to_wse(phys_addr_t addrspace_size)
+{
+       /* Bug if not a power of 2 */
+       BUG_ON(!is_power_of_2(addrspace_size));
+
+       /* window size is 2^(WSE+1) bytes */
+       return __ffs(addrspace_size) - 1;
+}
+
+/* Derive the PAACE window count encoding for the subwindow count */
+static unsigned int map_subwindow_cnt_to_wce(u32 subwindow_cnt)
+{
+       /* window count is 2^(WCE+1) bytes */
+       return __ffs(subwindow_cnt) - 1;
+}
+
+/*
+ * Set the PAACE type as primary and set the coherency required domain
+ * attribute
+ */
+static void pamu_init_ppaace(struct paace *ppaace)
+{
+       set_bf(ppaace->addr_bitfields, PAACE_AF_PT, PAACE_PT_PRIMARY);
+
+       set_bf(ppaace->domain_attr.to_host.coherency_required, PAACE_DA_HOST_CR,
+              PAACE_M_COHERENCE_REQ);
+}
+
+/*
+ * Set the PAACE type as secondary and set the coherency required domain
+ * attribute.
+ */
+static void pamu_init_spaace(struct paace *spaace)
+{
+       set_bf(spaace->addr_bitfields, PAACE_AF_PT, PAACE_PT_SECONDARY);
+       set_bf(spaace->domain_attr.to_host.coherency_required, PAACE_DA_HOST_CR,
+              PAACE_M_COHERENCE_REQ);
+}
+
+/*
+ * Return the spaace (corresponding to the secondary window index)
+ * for a particular ppaace.
+ */
+static struct paace *pamu_get_spaace(struct paace *paace, u32 wnum)
+{
+       u32 subwin_cnt;
+       struct paace *spaace = NULL;
+
+       subwin_cnt = 1UL << (get_bf(paace->impl_attr, PAACE_IA_WCE) + 1);
+
+       if (wnum < subwin_cnt)
+               spaace = &spaact[paace->fspi + wnum];
+       else
+               pr_debug("secondary paace out of bounds\n");
+
+       return spaace;
+}
+
+/**
+ * pamu_get_fspi_and_allocate() - Allocates fspi index and reserves subwindows
+ *                                required for primary PAACE in the secondary
+ *                                PAACE table.
+ * @subwin_cnt: Number of subwindows to be reserved.
+ *
+ * A PPAACE entry may have a number of associated subwindows. A subwindow
+ * corresponds to a SPAACE entry in the SPAACT table. Each PAACE entry stores
+ * the index (fspi) of the first SPAACE entry in the SPAACT table. This
+ * function returns the index of the first SPAACE entry. The remaining
+ * SPAACE entries are reserved contiguously from that index.
+ *
+ * Returns a valid fspi index in the range of 0 - SPAACE_NUMBER_ENTRIES on success.
+ * If no SPAACE entry is available or the allocator can not reserve the required
+ * number of contiguous entries function returns ULONG_MAX indicating a failure.
+ *
+*/
+static unsigned long pamu_get_fspi_and_allocate(u32 subwin_cnt)
+{
+       unsigned long spaace_addr;
+
+       spaace_addr = gen_pool_alloc(spaace_pool, subwin_cnt * sizeof(struct paace));
+       if (!spaace_addr)
+               return ULONG_MAX;
+
+       return (spaace_addr - (unsigned long)spaact) / (sizeof(struct paace));
+}
+
+/* Release the subwindows reserved for a particular LIODN */
+void pamu_free_subwins(int liodn)
+{
+       struct paace *ppaace;
+       u32 subwin_cnt, size;
+
+       ppaace = pamu_get_ppaace(liodn);
+       if (!ppaace) {
+               pr_debug("Invalid liodn entry\n");
+               return;
+       }
+
+       if (get_bf(ppaace->addr_bitfields, PPAACE_AF_MW)) {
+               subwin_cnt = 1UL << (get_bf(ppaace->impl_attr, PAACE_IA_WCE) + 1);
+               size = (subwin_cnt - 1) * sizeof(struct paace);
+               gen_pool_free(spaace_pool, (unsigned long)&spaact[ppaace->fspi], size);
+               set_bf(ppaace->addr_bitfields, PPAACE_AF_MW, 0);
+       }
+}
+
+/*
+ * Function used for updating stash destination for the coressponding
+ * LIODN.
+ */
+int  pamu_update_paace_stash(int liodn, u32 subwin, u32 value)
+{
+       struct paace *paace;
+
+       paace = pamu_get_ppaace(liodn);
+       if (!paace) {
+               pr_debug("Invalid liodn entry\n");
+               return -ENOENT;
+       }
+       if (subwin) {
+               paace = pamu_get_spaace(paace, subwin - 1);
+               if (!paace) {
+                       return -ENOENT;
+               }
+       }
+       set_bf(paace->impl_attr, PAACE_IA_CID, value);
+
+       mb();
+
+       return 0;
+}
+
+/* Disable a subwindow corresponding to the LIODN */
+int pamu_disable_spaace(int liodn, u32 subwin)
+{
+       struct paace *paace;
+
+       paace = pamu_get_ppaace(liodn);
+       if (!paace) {
+               pr_debug("Invalid liodn entry\n");
+               return -ENOENT;
+       }
+       if (subwin) {
+               paace = pamu_get_spaace(paace, subwin - 1);
+               if (!paace) {
+                       return -ENOENT;
+               }
+               set_bf(paace->addr_bitfields, PAACE_AF_V,
+                        PAACE_V_INVALID);
+       } else {
+               set_bf(paace->addr_bitfields, PAACE_AF_AP,
+                        PAACE_AP_PERMS_DENIED);
+       }
+
+       mb();
+
+       return 0;
+}
+
+
+/**
+ * pamu_config_paace() - Sets up PPAACE entry for specified liodn
+ *
+ * @liodn: Logical IO device number
+ * @win_addr: starting address of DSA window
+ * @win-size: size of DSA window
+ * @omi: Operation mapping index -- if ~omi == 0 then omi not defined
+ * @rpn: real (true physical) page number
+ * @stashid: cache stash id for associated cpu -- if ~stashid == 0 then
+ *          stashid not defined
+ * @snoopid: snoop id for hardware coherency -- if ~snoopid == 0 then
+ *          snoopid not defined
+ * @subwin_cnt: number of sub-windows
+ * @prot: window permissions
+ *
+ * Returns 0 upon success else error code < 0 returned
+ */
+int pamu_config_ppaace(int liodn, phys_addr_t win_addr, phys_addr_t win_size,
+                      u32 omi, unsigned long rpn, u32 snoopid, u32 stashid,
+                      u32 subwin_cnt, int prot)
+{
+       struct paace *ppaace;
+       unsigned long fspi;
+
+       if (!is_power_of_2(win_size) || win_size < PAMU_PAGE_SIZE) {
+               pr_debug("window size too small or not a power of two %llx\n", win_size);
+               return -EINVAL;
+       }
+
+       if (win_addr & (win_size - 1)) {
+               pr_debug("window address is not aligned with window size\n");
+               return -EINVAL;
+       }
+
+       ppaace = pamu_get_ppaace(liodn);
+       if (!ppaace) {
+               return -ENOENT;
+       }
+
+       /* window size is 2^(WSE+1) bytes */
+       set_bf(ppaace->addr_bitfields, PPAACE_AF_WSE,
+               map_addrspace_size_to_wse(win_size));
+
+       pamu_init_ppaace(ppaace);
+
+       ppaace->wbah = win_addr >> (PAMU_PAGE_SHIFT + 20);
+       set_bf(ppaace->addr_bitfields, PPAACE_AF_WBAL,
+              (win_addr >> PAMU_PAGE_SHIFT));
+
+       /* set up operation mapping if it's configured */
+       if (omi < OME_NUMBER_ENTRIES) {
+               set_bf(ppaace->impl_attr, PAACE_IA_OTM, PAACE_OTM_INDEXED);
+               ppaace->op_encode.index_ot.omi = omi;
+       } else if (~omi != 0) {
+               pr_debug("bad operation mapping index: %d\n", omi);
+               return -EINVAL;
+       }
+
+       /* configure stash id */
+       if (~stashid != 0)
+               set_bf(ppaace->impl_attr, PAACE_IA_CID, stashid);
+
+       /* configure snoop id */
+       if (~snoopid != 0)
+               ppaace->domain_attr.to_host.snpid = snoopid;
+
+       if (subwin_cnt) {
+               /* The first entry is in the primary PAACE instead */
+               fspi = pamu_get_fspi_and_allocate(subwin_cnt - 1);
+               if (fspi == ULONG_MAX) {
+                       pr_debug("spaace indexes exhausted\n");
+                       return -EINVAL;
+               }
+
+               /* window count is 2^(WCE+1) bytes */
+               set_bf(ppaace->impl_attr, PAACE_IA_WCE,
+                      map_subwindow_cnt_to_wce(subwin_cnt));
+               set_bf(ppaace->addr_bitfields, PPAACE_AF_MW, 0x1);
+               ppaace->fspi = fspi;
+       } else {
+               set_bf(ppaace->impl_attr, PAACE_IA_ATM, PAACE_ATM_WINDOW_XLATE);
+               ppaace->twbah = rpn >> 20;
+               set_bf(ppaace->win_bitfields, PAACE_WIN_TWBAL, rpn);
+               set_bf(ppaace->addr_bitfields, PAACE_AF_AP, prot);
+               set_bf(ppaace->impl_attr, PAACE_IA_WCE, 0);
+               set_bf(ppaace->addr_bitfields, PPAACE_AF_MW, 0);
+       }
+       mb();
+
+       return 0;
+}
+
+/**
+ * pamu_config_spaace() - Sets up SPAACE entry for specified subwindow
+ *
+ * @liodn:  Logical IO device number
+ * @subwin_cnt:  number of sub-windows associated with dma-window
+ * @subwin: subwindow index
+ * @subwin_size: size of subwindow
+ * @omi: Operation mapping index
+ * @rpn: real (true physical) page number
+ * @snoopid: snoop id for hardware coherency -- if ~snoopid == 0 then
+ *                       snoopid not defined
+ * @stashid: cache stash id for associated cpu
+ * @enable: enable/disable subwindow after reconfiguration
+ * @prot: sub window permissions
+ *
+ * Returns 0 upon success else error code < 0 returned
+ */
+int pamu_config_spaace(int liodn, u32 subwin_cnt, u32 subwin,
+                      phys_addr_t subwin_size, u32 omi, unsigned long rpn,
+                      u32 snoopid, u32 stashid, int enable, int prot)
+{
+       struct paace *paace;
+
+
+       /* setup sub-windows */
+       if (!subwin_cnt) {
+               pr_debug("Invalid subwindow count\n");
+               return -EINVAL;
+       }
+
+       paace = pamu_get_ppaace(liodn);
+       if (subwin > 0 && subwin < subwin_cnt && paace) {
+               paace = pamu_get_spaace(paace, subwin - 1);
+
+               if (paace && !(paace->addr_bitfields & PAACE_V_VALID)) {
+                       pamu_init_spaace(paace);
+                       set_bf(paace->addr_bitfields, SPAACE_AF_LIODN, liodn);
+               }
+       }
+
+       if (!paace) {
+               pr_debug("Invalid liodn entry\n");
+               return -ENOENT;
+       }
+
+       if (!is_power_of_2(subwin_size) || subwin_size < PAMU_PAGE_SIZE) {
+               pr_debug("subwindow size out of range, or not a power of 2\n");
+               return -EINVAL;
+       }
+
+       if (rpn == ULONG_MAX) {
+               pr_debug("real page number out of range\n");
+               return -EINVAL;
+       }
+
+       /* window size is 2^(WSE+1) bytes */
+       set_bf(paace->win_bitfields, PAACE_WIN_SWSE,
+              map_addrspace_size_to_wse(subwin_size));
+
+       set_bf(paace->impl_attr, PAACE_IA_ATM, PAACE_ATM_WINDOW_XLATE);
+       paace->twbah = rpn >> 20;
+       set_bf(paace->win_bitfields, PAACE_WIN_TWBAL, rpn);
+       set_bf(paace->addr_bitfields, PAACE_AF_AP, prot);
+
+       /* configure snoop id */
+       if (~snoopid != 0)
+               paace->domain_attr.to_host.snpid = snoopid;
+
+       /* set up operation mapping if it's configured */
+       if (omi < OME_NUMBER_ENTRIES) {
+               set_bf(paace->impl_attr, PAACE_IA_OTM, PAACE_OTM_INDEXED);
+               paace->op_encode.index_ot.omi = omi;
+       } else if (~omi != 0) {
+               pr_debug("bad operation mapping index: %d\n", omi);
+               return -EINVAL;
+       }
+
+       if (~stashid != 0)
+               set_bf(paace->impl_attr, PAACE_IA_CID, stashid);
+
+       smp_wmb();
+
+       if (enable)
+               set_bf(paace->addr_bitfields, PAACE_AF_V, PAACE_V_VALID);
+
+       mb();
+
+       return 0;
+}
+
+/**
+* get_ome_index() - Returns the index in the operation mapping table
+*                   for device.
+* @*omi_index: pointer for storing the index value
+*
+*/
+void get_ome_index(u32 *omi_index, struct device *dev)
+{
+       if (of_device_is_compatible(dev->of_node, "fsl,qman-portal"))
+               *omi_index = OMI_QMAN;
+       if (of_device_is_compatible(dev->of_node, "fsl,qman"))
+               *omi_index = OMI_QMAN_PRIV;
+}
+
+/**
+ * get_stash_id - Returns stash destination id corresponding to a
+ *                cache type and vcpu.
+ * @stash_dest_hint: L1, L2 or L3
+ * @vcpu: vpcu target for a particular cache type.
+ *
+ * Returs stash on success or ~(u32)0 on failure.
+ *
+ */
+u32 get_stash_id(u32 stash_dest_hint, u32 vcpu)
+{
+       const u32 *prop;
+       struct device_node *node;
+       u32 cache_level;
+       int len, found = 0;
+       int i;
+
+       /* Fastpath, exit early if L3/CPC cache is target for stashing */
+       if (stash_dest_hint == PAMU_ATTR_CACHE_L3) {
+               node = of_find_matching_node(NULL, l3_device_ids);
+               if (node) {
+                       prop = of_get_property(node, "cache-stash-id", 0);
+                       if (!prop) {
+                               pr_debug("missing cache-stash-id at %s\n", node->full_name);
+                               of_node_put(node);
+                               return ~(u32)0;
+                       }
+                       of_node_put(node);
+                       return be32_to_cpup(prop);
+               }
+               return ~(u32)0;
+       }
+
+       for_each_node_by_type(node, "cpu") {
+               prop = of_get_property(node, "reg", &len);
+               for (i = 0; i < len / sizeof(u32); i++) {
+                       if (be32_to_cpup(&prop[i]) == vcpu) {
+                               found = 1;
+                               goto found_cpu_node;
+                       }
+               }
+       }
+found_cpu_node:
+
+       /* find the hwnode that represents the cache */
+       for (cache_level = PAMU_ATTR_CACHE_L1; (cache_level < PAMU_ATTR_CACHE_L3) && found; cache_level++) {
+               if (stash_dest_hint == cache_level) {
+                       prop = of_get_property(node, "cache-stash-id", 0);
+                       if (!prop) {
+                               pr_debug("missing cache-stash-id at %s\n", node->full_name);
+                               of_node_put(node);
+                               return ~(u32)0;
+                       }
+                       of_node_put(node);
+                       return be32_to_cpup(prop);
+               }
+
+               prop = of_get_property(node, "next-level-cache", 0);
+               if (!prop) {
+                       pr_debug("can't find next-level-cache at %s\n",
+                               node->full_name);
+                       of_node_put(node);
+                       return ~(u32)0;  /* can't traverse any further */
+               }
+               of_node_put(node);
+
+               /* advance to next node in cache hierarchy */
+               node = of_find_node_by_phandle(*prop);
+               if (!node) {
+                       pr_debug("Invalid node for cache hierarchy %s\n",
+                               node->full_name);
+                       return ~(u32)0;
+               }
+       }
+
+       pr_debug("stash dest not found for %d on vcpu %d\n",
+                 stash_dest_hint, vcpu);
+       return ~(u32)0;
+}
+
+/* Identify if the PAACT table entry belongs to QMAN, BMAN or QMAN Portal */
+#define QMAN_PAACE 1
+#define QMAN_PORTAL_PAACE 2
+#define BMAN_PAACE 3
+
+/**
+ * Setup operation mapping and stash destinations for QMAN and QMAN portal.
+ * Memory accesses to QMAN and BMAN private memory need not be coherent, so
+ * clear the PAACE entry coherency attribute for them.
+ */
+static void setup_qbman_paace(struct paace *ppaace, int  paace_type)
+{
+       switch (paace_type) {
+       case QMAN_PAACE:
+               set_bf(ppaace->impl_attr, PAACE_IA_OTM, PAACE_OTM_INDEXED);
+               ppaace->op_encode.index_ot.omi = OMI_QMAN_PRIV;
+               /* setup QMAN Private data stashing for the L3 cache */
+               set_bf(ppaace->impl_attr, PAACE_IA_CID, get_stash_id(PAMU_ATTR_CACHE_L3, 0));
+               set_bf(ppaace->domain_attr.to_host.coherency_required, PAACE_DA_HOST_CR,
+                      0);
+               break;
+       case QMAN_PORTAL_PAACE:
+               set_bf(ppaace->impl_attr, PAACE_IA_OTM, PAACE_OTM_INDEXED);
+               ppaace->op_encode.index_ot.omi = OMI_QMAN;
+               /*Set DQRR and Frame stashing for the L3 cache */
+               set_bf(ppaace->impl_attr, PAACE_IA_CID, get_stash_id(PAMU_ATTR_CACHE_L3, 0));
+               break;
+       case BMAN_PAACE:
+               set_bf(ppaace->domain_attr.to_host.coherency_required, PAACE_DA_HOST_CR,
+                      0);
+               break;
+       }
+}
+
+/**
+ * Setup the operation mapping table for various devices. This is a static
+ * table where each table index corresponds to a particular device. PAMU uses
+ * this table to translate device transaction to appropriate corenet
+ * transaction.
+ */
+static void __init setup_omt(struct ome *omt)
+{
+       struct ome *ome;
+
+       /* Configure OMI_QMAN */
+       ome = &omt[OMI_QMAN];
+
+       ome->moe[IOE_READ_IDX] = EOE_VALID | EOE_READ;
+       ome->moe[IOE_EREAD0_IDX] = EOE_VALID | EOE_RSA;
+       ome->moe[IOE_WRITE_IDX] = EOE_VALID | EOE_WRITE;
+       ome->moe[IOE_EWRITE0_IDX] = EOE_VALID | EOE_WWSAO;
+
+       ome->moe[IOE_DIRECT0_IDX] = EOE_VALID | EOE_LDEC;
+       ome->moe[IOE_DIRECT1_IDX] = EOE_VALID | EOE_LDECPE;
+
+       /* Configure OMI_FMAN */
+       ome = &omt[OMI_FMAN];
+       ome->moe[IOE_READ_IDX]  = EOE_VALID | EOE_READI;
+       ome->moe[IOE_WRITE_IDX] = EOE_VALID | EOE_WRITE;
+
+       /* Configure OMI_QMAN private */
+       ome = &omt[OMI_QMAN_PRIV];
+       ome->moe[IOE_READ_IDX]  = EOE_VALID | EOE_READ;
+       ome->moe[IOE_WRITE_IDX] = EOE_VALID | EOE_WRITE;
+       ome->moe[IOE_EREAD0_IDX] = EOE_VALID | EOE_RSA;
+       ome->moe[IOE_EWRITE0_IDX] = EOE_VALID | EOE_WWSA;
+
+       /* Configure OMI_CAAM */
+       ome = &omt[OMI_CAAM];
+       ome->moe[IOE_READ_IDX]  = EOE_VALID | EOE_READI;
+       ome->moe[IOE_WRITE_IDX] = EOE_VALID | EOE_WRITE;
+}
+
+/*
+ * Get the maximum number of PAACT table entries
+ * and subwindows supported by PAMU
+ */
+static void get_pamu_cap_values(unsigned long pamu_reg_base)
+{
+       u32 pc_val;
+
+       pc_val = in_be32((u32 *)(pamu_reg_base + PAMU_PC3));
+       /* Maximum number of subwindows per liodn */
+       max_subwindow_count = 1 << (1 + PAMU_PC3_MWCE(pc_val));
+}
+
+/* Setup PAMU registers pointing to PAACT, SPAACT and OMT */
+int setup_one_pamu(unsigned long pamu_reg_base, unsigned long pamu_reg_size,
+                  phys_addr_t ppaact_phys, phys_addr_t spaact_phys,
+                  phys_addr_t omt_phys)
+{
+       u32 *pc;
+       struct pamu_mmap_regs *pamu_regs;
+
+       pc = (u32 *) (pamu_reg_base + PAMU_PC);
+       pamu_regs = (struct pamu_mmap_regs *)
+               (pamu_reg_base + PAMU_MMAP_REGS_BASE);
+
+       /* set up pointers to corenet control blocks */
+
+       out_be32(&pamu_regs->ppbah, upper_32_bits(ppaact_phys));
+       out_be32(&pamu_regs->ppbal, lower_32_bits(ppaact_phys));
+       ppaact_phys = ppaact_phys + PAACT_SIZE;
+       out_be32(&pamu_regs->pplah, upper_32_bits(ppaact_phys));
+       out_be32(&pamu_regs->pplal, lower_32_bits(ppaact_phys));
+
+       out_be32(&pamu_regs->spbah, upper_32_bits(spaact_phys));
+       out_be32(&pamu_regs->spbal, lower_32_bits(spaact_phys));
+       spaact_phys = spaact_phys + SPAACT_SIZE;
+       out_be32(&pamu_regs->splah, upper_32_bits(spaact_phys));
+       out_be32(&pamu_regs->splal, lower_32_bits(spaact_phys));
+
+       out_be32(&pamu_regs->obah, upper_32_bits(omt_phys));
+       out_be32(&pamu_regs->obal, lower_32_bits(omt_phys));
+       omt_phys = omt_phys + OMT_SIZE;
+       out_be32(&pamu_regs->olah, upper_32_bits(omt_phys));
+       out_be32(&pamu_regs->olal, lower_32_bits(omt_phys));
+
+       /*
+        * set PAMU enable bit,
+        * allow ppaact & omt to be cached
+        * & enable PAMU access violation interrupts.
+        */
+
+       out_be32((u32 *)(pamu_reg_base + PAMU_PICS),
+                       PAMU_ACCESS_VIOLATION_ENABLE);
+       out_be32(pc, PAMU_PC_PE | PAMU_PC_OCE | PAMU_PC_SPCC | PAMU_PC_PPCC);
+       return 0;
+}
+
+/* Enable all device LIODNS */
+static void __init setup_liodns(void)
+{
+       int i, len;
+       struct paace *ppaace;
+       struct device_node *node = NULL;
+       const u32 *prop;
+
+       for_each_node_with_property(node, "fsl,liodn") {
+               prop = of_get_property(node, "fsl,liodn", &len);
+               for (i = 0; i < len / sizeof(u32); i++) {
+                       int liodn;
+
+                       liodn = be32_to_cpup(&prop[i]);
+                       if (liodn >= PAACE_NUMBER_ENTRIES) {
+                               pr_debug("Invalid LIODN value %d\n", liodn);
+                               continue;
+                       }
+                       ppaace = pamu_get_ppaace(liodn);
+                       pamu_init_ppaace(ppaace);
+                       /* window size is 2^(WSE+1) bytes */
+                       set_bf(ppaace->addr_bitfields, PPAACE_AF_WSE, 35);
+                       ppaace->wbah = 0;
+                       set_bf(ppaace->addr_bitfields, PPAACE_AF_WBAL, 0);
+                       set_bf(ppaace->impl_attr, PAACE_IA_ATM,
+                               PAACE_ATM_NO_XLATE);
+                       set_bf(ppaace->addr_bitfields, PAACE_AF_AP,
+                               PAACE_AP_PERMS_ALL);
+                       if (of_device_is_compatible(node, "fsl,qman-portal"))
+                               setup_qbman_paace(ppaace, QMAN_PORTAL_PAACE);
+                       if (of_device_is_compatible(node, "fsl,qman"))
+                               setup_qbman_paace(ppaace, QMAN_PAACE);
+                       if (of_device_is_compatible(node, "fsl,bman"))
+                               setup_qbman_paace(ppaace, BMAN_PAACE);
+                       mb();
+                       pamu_enable_liodn(liodn);
+               }
+       }
+}
+
+irqreturn_t pamu_av_isr(int irq, void *arg)
+{
+       struct pamu_isr_data *data = arg;
+       phys_addr_t phys;
+       unsigned int i, j, ret;
+
+       pr_emerg("access violation interrupt\n");
+
+       for (i = 0; i < data->count; i++) {
+               void __iomem *p = data->pamu_reg_base + i * PAMU_OFFSET;
+               u32 pics = in_be32(p + PAMU_PICS);
+
+               if (pics & PAMU_ACCESS_VIOLATION_STAT) {
+                       u32 avs1 = in_be32(p + PAMU_AVS1);
+                       struct paace *paace;
+
+                       pr_emerg("POES1=%08x\n", in_be32(p + PAMU_POES1));
+                       pr_emerg("POES2=%08x\n", in_be32(p + PAMU_POES2));
+                       pr_emerg("AVS1=%08x\n", avs1);
+                       pr_emerg("AVS2=%08x\n", in_be32(p + PAMU_AVS2));
+                       pr_emerg("AVA=%016llx\n", make64(in_be32(p + PAMU_AVAH),
+                               in_be32(p + PAMU_AVAL)));
+                       pr_emerg("UDAD=%08x\n", in_be32(p + PAMU_UDAD));
+                       pr_emerg("POEA=%016llx\n", make64(in_be32(p + PAMU_POEAH),
+                               in_be32(p + PAMU_POEAL)));
+
+                       phys = make64(in_be32(p + PAMU_POEAH),
+                               in_be32(p + PAMU_POEAL));
+
+                       /* Assume that POEA points to a PAACE */
+                       if (phys) {
+                               u32 *paace = phys_to_virt(phys);
+
+                               /* Only the first four words are relevant */
+                               for (j = 0; j < 4; j++)
+                                       pr_emerg("PAACE[%u]=%08x\n", j, in_be32(paace + j));
+                       }
+
+                       /* clear access violation condition */
+                       out_be32((p + PAMU_AVS1), avs1 & PAMU_AV_MASK);
+                       paace = pamu_get_ppaace(avs1 >> PAMU_AVS1_LIODN_SHIFT);
+                       BUG_ON(!paace);
+                       /* check if we got a violation for a disabled LIODN */
+                       if (!get_bf(paace->addr_bitfields, PAACE_AF_V)) {
+                               /*
+                                * As per hardware erratum A-003638, access
+                                * violation can be reported for a disabled
+                                * LIODN. If we hit that condition, disable
+                                * access violation reporting.
+                                */
+                               pics &= ~PAMU_ACCESS_VIOLATION_ENABLE;
+                       } else {
+                               /* Disable the LIODN */
+                               ret = pamu_disable_liodn(avs1 >> PAMU_AVS1_LIODN_SHIFT);
+                               BUG_ON(ret);
+                               pr_emerg("Disabling liodn %x\n", avs1 >> PAMU_AVS1_LIODN_SHIFT);
+                       }
+                       out_be32((p + PAMU_PICS), pics);
+               }
+       }
+
+
+       return IRQ_HANDLED;
+}
+
+#define LAWAR_EN               0x80000000
+#define LAWAR_TARGET_MASK      0x0FF00000
+#define LAWAR_TARGET_SHIFT     20
+#define LAWAR_SIZE_MASK                0x0000003F
+#define LAWAR_CSDID_MASK       0x000FF000
+#define LAWAR_CSDID_SHIFT      12
+
+#define LAW_SIZE_4K            0xb
+
+struct ccsr_law {
+       u32     lawbarh;        /* LAWn base address high */
+       u32     lawbarl;        /* LAWn base address low */
+       u32     lawar;          /* LAWn attributes */
+       u32     reserved;
+};
+
+/*
+ * Create a coherence subdomain for a given memory block.
+ */
+static int __init create_csd(phys_addr_t phys, size_t size, u32 csd_port_id)
+{
+       struct device_node *np;
+       const __be32 *iprop;
+       void __iomem *lac = NULL;       /* Local Access Control registers */
+       struct ccsr_law __iomem *law;
+       void __iomem *ccm = NULL;
+       u32 __iomem *csdids;
+       unsigned int i, num_laws, num_csds;
+       u32 law_target = 0;
+       u32 csd_id = 0;
+       int ret = 0;
+
+       np = of_find_compatible_node(NULL, NULL, "fsl,corenet-law");
+       if (!np)
+               return -ENODEV;
+
+       iprop = of_get_property(np, "fsl,num-laws", NULL);
+       if (!iprop) {
+               ret = -ENODEV;
+               goto error;
+       }
+
+       num_laws = be32_to_cpup(iprop);
+       if (!num_laws) {
+               ret = -ENODEV;
+               goto error;
+       }
+
+       lac = of_iomap(np, 0);
+       if (!lac) {
+               ret = -ENODEV;
+               goto error;
+       }
+
+       /* LAW registers are at offset 0xC00 */
+       law = lac + 0xC00;
+
+       of_node_put(np);
+
+       np = of_find_compatible_node(NULL, NULL, "fsl,corenet-cf");
+       if (!np) {
+               ret = -ENODEV;
+               goto error;
+       }
+
+       iprop = of_get_property(np, "fsl,ccf-num-csdids", NULL);
+       if (!iprop) {
+               ret = -ENODEV;
+               goto error;
+       }
+
+       num_csds = be32_to_cpup(iprop);
+       if (!num_csds) {
+               ret = -ENODEV;
+               goto error;
+       }
+
+       ccm = of_iomap(np, 0);
+       if (!ccm) {
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       /* The undocumented CSDID registers are at offset 0x600 */
+       csdids = ccm + 0x600;
+
+       of_node_put(np);
+       np = NULL;
+
+       /* Find an unused coherence subdomain ID */
+       for (csd_id = 0; csd_id < num_csds; csd_id++) {
+               if (!csdids[csd_id])
+                       break;
+       }
+
+       /* Store the Port ID in the (undocumented) proper CIDMRxx register */
+       csdids[csd_id] = csd_port_id;
+
+       /* Find the DDR LAW that maps to our buffer. */
+       for (i = 0; i < num_laws; i++) {
+               if (law[i].lawar & LAWAR_EN) {
+                       phys_addr_t law_start, law_end;
+
+                       law_start = make64(law[i].lawbarh, law[i].lawbarl);
+                       law_end = law_start +
+                               (2ULL << (law[i].lawar & LAWAR_SIZE_MASK));
+
+                       if (law_start <= phys && phys < law_end) {
+                               law_target = law[i].lawar & LAWAR_TARGET_MASK;
+                               break;
+                       }
+               }
+       }
+
+       if (i == 0 || i == num_laws) {
+               /* This should never happen*/
+               ret = -ENOENT;
+               goto error;
+       }
+
+       /* Find a free LAW entry */
+       while (law[--i].lawar & LAWAR_EN) {
+               if (i == 0) {
+                       /* No higher priority LAW slots available */
+                       ret = -ENOENT;
+                       goto error;
+               }
+       }
+
+       law[i].lawbarh = upper_32_bits(phys);
+       law[i].lawbarl = lower_32_bits(phys);
+       wmb();
+       law[i].lawar = LAWAR_EN | law_target | (csd_id << LAWAR_CSDID_SHIFT) |
+               (LAW_SIZE_4K + get_order(size));
+       wmb();
+
+error:
+       if (ccm)
+               iounmap(ccm);
+
+       if (lac)
+               iounmap(lac);
+
+       if (np)
+               of_node_put(np);
+
+       return ret;
+}
+
+/*
+ * Table of SVRs and the corresponding PORT_ID values. Port ID corresponds to a
+ * bit map of snoopers for a given range of memory mapped by a LAW.
+ *
+ * All future CoreNet-enabled SOCs will have this erratum(A-004510) fixed, so this
+ * table should never need to be updated.  SVRs are guaranteed to be unique, so
+ * there is no worry that a future SOC will inadvertently have one of these
+ * values.
+ */
+static const struct {
+       u32 svr;
+       u32 port_id;
+} port_id_map[] = {
+       {0x82100010, 0xFF000000},       /* P2040 1.0 */
+       {0x82100011, 0xFF000000},       /* P2040 1.1 */
+       {0x82100110, 0xFF000000},       /* P2041 1.0 */
+       {0x82100111, 0xFF000000},       /* P2041 1.1 */
+       {0x82110310, 0xFF000000},       /* P3041 1.0 */
+       {0x82110311, 0xFF000000},       /* P3041 1.1 */
+       {0x82010020, 0xFFF80000},       /* P4040 2.0 */
+       {0x82000020, 0xFFF80000},       /* P4080 2.0 */
+       {0x82210010, 0xFC000000},       /* P5010 1.0 */
+       {0x82210020, 0xFC000000},       /* P5010 2.0 */
+       {0x82200010, 0xFC000000},       /* P5020 1.0 */
+       {0x82050010, 0xFF800000},       /* P5021 1.0 */
+       {0x82040010, 0xFF800000},       /* P5040 1.0 */
+};
+
+#define SVR_SECURITY   0x80000 /* The Security (E) bit */
+
+static int __init fsl_pamu_probe(struct platform_device *pdev)
+{
+       void __iomem *pamu_regs = NULL;
+       struct ccsr_guts __iomem *guts_regs = NULL;
+       u32 pamubypenr, pamu_counter;
+       unsigned long pamu_reg_off;
+       unsigned long pamu_reg_base;
+       struct pamu_isr_data *data = NULL;
+       struct device_node *guts_node;
+       u64 size;
+       struct page *p;
+       int ret = 0;
+       int irq;
+       phys_addr_t ppaact_phys;
+       phys_addr_t spaact_phys;
+       phys_addr_t omt_phys;
+       size_t mem_size = 0;
+       unsigned int order = 0;
+       u32 csd_port_id = 0;
+       unsigned i;
+       /*
+        * enumerate all PAMUs and allocate and setup PAMU tables
+        * for each of them,
+        * NOTE : All PAMUs share the same LIODN tables.
+        */
+
+       pamu_regs = of_iomap(pdev->dev.of_node, 0);
+       if (!pamu_regs) {
+               dev_err(&pdev->dev, "ioremap of PAMU node failed\n");
+               return -ENOMEM;
+       }
+       of_get_address(pdev->dev.of_node, 0, &size, NULL);
+
+       irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
+       if (irq == NO_IRQ) {
+               dev_warn(&pdev->dev, "no interrupts listed in PAMU node\n");
+               goto error;
+       }
+
+       data = kzalloc(sizeof(struct pamu_isr_data), GFP_KERNEL);
+       if (!data) {
+               dev_err(&pdev->dev, "PAMU isr data memory allocation failed\n");
+               ret = -ENOMEM;
+               goto error;
+       }
+       data->pamu_reg_base = pamu_regs;
+       data->count = size / PAMU_OFFSET;
+
+       /* The ISR needs access to the regs, so we won't iounmap them */
+       ret = request_irq(irq, pamu_av_isr, 0, "pamu", data);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "error %i installing ISR for irq %i\n",
+                       ret, irq);
+               goto error;
+       }
+
+       guts_node = of_find_matching_node(NULL, guts_device_ids);
+       if (!guts_node) {
+               dev_err(&pdev->dev, "could not find GUTS node %s\n",
+                       pdev->dev.of_node->full_name);
+               ret = -ENODEV;
+               goto error;
+       }
+
+       guts_regs = of_iomap(guts_node, 0);
+       of_node_put(guts_node);
+       if (!guts_regs) {
+               dev_err(&pdev->dev, "ioremap of GUTS node failed\n");
+               ret = -ENODEV;
+               goto error;
+       }
+
+       /* read in the PAMU capability registers */
+       get_pamu_cap_values((unsigned long)pamu_regs);
+       /*
+        * To simplify the allocation of a coherency domain, we allocate the
+        * PAACT and the OMT in the same memory buffer.  Unfortunately, this
+        * wastes more memory compared to allocating the buffers separately.
+        */
+       /* Determine how much memory we need */
+       mem_size = (PAGE_SIZE << get_order(PAACT_SIZE)) +
+               (PAGE_SIZE << get_order(SPAACT_SIZE)) +
+               (PAGE_SIZE << get_order(OMT_SIZE));
+       order = get_order(mem_size);
+
+       p = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
+       if (!p) {
+               dev_err(&pdev->dev, "unable to allocate PAACT/SPAACT/OMT block\n");
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       ppaact = page_address(p);
+       ppaact_phys = page_to_phys(p);
+
+       /* Make sure the memory is naturally aligned */
+       if (ppaact_phys & ((PAGE_SIZE << order) - 1)) {
+               dev_err(&pdev->dev, "PAACT/OMT block is unaligned\n");
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       spaact = (void *)ppaact + (PAGE_SIZE << get_order(PAACT_SIZE));
+       omt = (void *)spaact + (PAGE_SIZE << get_order(SPAACT_SIZE));
+
+       dev_dbg(&pdev->dev, "ppaact virt=%p phys=0x%llx\n", ppaact,
+               (unsigned long long) ppaact_phys);
+
+       /* Check to see if we need to implement the work-around on this SOC */
+
+       /* Determine the Port ID for our coherence subdomain */
+       for (i = 0; i < ARRAY_SIZE(port_id_map); i++) {
+               if (port_id_map[i].svr == (mfspr(SPRN_SVR) & ~SVR_SECURITY)) {
+                       csd_port_id = port_id_map[i].port_id;
+                       dev_dbg(&pdev->dev, "found matching SVR %08x\n",
+                               port_id_map[i].svr);
+                       break;
+               }
+       }
+
+       if (csd_port_id) {
+               dev_dbg(&pdev->dev, "creating coherency subdomain at address "
+                       "0x%llx, size %zu, port id 0x%08x", ppaact_phys,
+                       mem_size, csd_port_id);
+
+               ret = create_csd(ppaact_phys, mem_size, csd_port_id);
+               if (ret) {
+                       dev_err(&pdev->dev, "could not create coherence "
+                               "subdomain\n");
+                       return ret;
+               }
+       }
+
+       spaact_phys = virt_to_phys(spaact);
+       omt_phys = virt_to_phys(omt);
+
+       spaace_pool = gen_pool_create(ilog2(sizeof(struct paace)), -1);
+       if (!spaace_pool) {
+               ret = -ENOMEM;
+               dev_err(&pdev->dev, "PAMU : failed to allocate spaace gen pool\n");
+               goto error;
+       }
+
+       ret = gen_pool_add(spaace_pool, (unsigned long)spaact, SPAACT_SIZE, -1);
+       if (ret)
+               goto error_genpool;
+
+       pamubypenr = in_be32(&guts_regs->pamubypenr);
+
+       for (pamu_reg_off = 0, pamu_counter = 0x80000000; pamu_reg_off < size;
+            pamu_reg_off += PAMU_OFFSET, pamu_counter >>= 1) {
+
+               pamu_reg_base = (unsigned long) pamu_regs + pamu_reg_off;
+               setup_one_pamu(pamu_reg_base, pamu_reg_off, ppaact_phys,
+                                spaact_phys, omt_phys);
+               /* Disable PAMU bypass for this PAMU */
+               pamubypenr &= ~pamu_counter;
+       }
+
+       setup_omt(omt);
+
+       /* Enable all relevant PAMU(s) */
+       out_be32(&guts_regs->pamubypenr, pamubypenr);
+
+       iounmap(guts_regs);
+
+       /* Enable DMA for the LIODNs in the device tree*/
+
+       setup_liodns();
+
+       return 0;
+
+error_genpool:
+       gen_pool_destroy(spaace_pool);
+
+error:
+       if (irq != NO_IRQ)
+               free_irq(irq, data);
+
+       if (data) {
+               memset(data, 0, sizeof(struct pamu_isr_data));
+               kfree(data);
+       }
+
+       if (pamu_regs)
+               iounmap(pamu_regs);
+
+       if (guts_regs)
+               iounmap(guts_regs);
+
+       if (ppaact)
+               free_pages((unsigned long)ppaact, order);
+
+       ppaact = NULL;
+
+       return ret;
+}
+
+static const struct of_device_id fsl_of_pamu_ids[] = {
+       {
+               .compatible = "fsl,p4080-pamu",
+       },
+       {
+               .compatible = "fsl,pamu",
+       },
+       {},
+};
+
+static struct platform_driver fsl_of_pamu_driver = {
+       .driver = {
+               .name = "fsl-of-pamu",
+               .owner = THIS_MODULE,
+       },
+       .probe = fsl_pamu_probe,
+};
+
+static __init int fsl_pamu_init(void)
+{
+       struct platform_device *pdev = NULL;
+       struct device_node *np;
+       int ret;
+
+       /*
+        * The normal OF process calls the probe function at some
+        * indeterminate later time, after most drivers have loaded.  This is
+        * too late for us, because PAMU clients (like the Qman driver)
+        * depend on PAMU being initialized early.
+        *
+        * So instead, we "manually" call our probe function by creating the
+        * platform devices ourselves.
+        */
+
+       /*
+        * We assume that there is only one PAMU node in the device tree.  A
+        * single PAMU node represents all of the PAMU devices in the SOC
+        * already.   Everything else already makes that assumption, and the
+        * binding for the PAMU nodes doesn't allow for any parent-child
+        * relationships anyway.  In other words, support for more than one
+        * PAMU node would require significant changes to a lot of code.
+        */
+
+       np = of_find_compatible_node(NULL, NULL, "fsl,pamu");
+       if (!np) {
+               pr_err("could not find a PAMU node\n");
+               return -ENODEV;
+       }
+
+       ret = platform_driver_register(&fsl_of_pamu_driver);
+       if (ret) {
+               pr_err("could not register driver (err=%i)\n", ret);
+               goto error_driver_register;
+       }
+
+       pdev = platform_device_alloc("fsl-of-pamu", 0);
+       if (!pdev) {
+               pr_err("could not allocate device %s\n",
+                      np->full_name);
+               ret = -ENOMEM;
+               goto error_device_alloc;
+       }
+       pdev->dev.of_node = of_node_get(np);
+
+       ret = pamu_domain_init();
+       if (ret)
+               goto error_device_add;
+
+       ret = platform_device_add(pdev);
+       if (ret) {
+               pr_err("could not add device %s (err=%i)\n",
+                      np->full_name, ret);
+               goto error_device_add;
+       }
+
+       return 0;
+
+error_device_add:
+       of_node_put(pdev->dev.of_node);
+       pdev->dev.of_node = NULL;
+
+       platform_device_put(pdev);
+
+error_device_alloc:
+       platform_driver_unregister(&fsl_of_pamu_driver);
+
+error_driver_register:
+       of_node_put(np);
+
+       return ret;
+}
+arch_initcall(fsl_pamu_init);
diff --git a/drivers/iommu/fsl_pamu.h b/drivers/iommu/fsl_pamu.h
new file mode 100644 (file)
index 0000000..8fc1a12
--- /dev/null
@@ -0,0 +1,410 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ *
+ */
+
+#ifndef __FSL_PAMU_H
+#define __FSL_PAMU_H
+
+#include <asm/fsl_pamu_stash.h>
+
+/* Bit Field macros
+ *     v = bit field variable; m = mask, m##_SHIFT = shift, x = value to load
+ */
+#define set_bf(v, m, x)                (v = ((v) & ~(m)) | (((x) << (m##_SHIFT)) & (m)))
+#define get_bf(v, m)           (((v) & (m)) >> (m##_SHIFT))
+
+/* PAMU CCSR space */
+#define PAMU_PGC 0x00000000     /* Allows all peripheral accesses */
+#define PAMU_PE 0x40000000      /* enable PAMU                    */
+
+/* PAMU_OFFSET to the next pamu space in ccsr */
+#define PAMU_OFFSET 0x1000
+
+#define PAMU_MMAP_REGS_BASE 0
+
+struct pamu_mmap_regs {
+       u32 ppbah;
+       u32 ppbal;
+       u32 pplah;
+       u32 pplal;
+       u32 spbah;
+       u32 spbal;
+       u32 splah;
+       u32 splal;
+       u32 obah;
+       u32 obal;
+       u32 olah;
+       u32 olal;
+};
+
+/* PAMU Error Registers */
+#define PAMU_POES1 0x0040
+#define PAMU_POES2 0x0044
+#define PAMU_POEAH 0x0048
+#define PAMU_POEAL 0x004C
+#define PAMU_AVS1  0x0050
+#define PAMU_AVS1_AV    0x1
+#define PAMU_AVS1_OTV   0x6
+#define PAMU_AVS1_APV   0x78
+#define PAMU_AVS1_WAV   0x380
+#define PAMU_AVS1_LAV   0x1c00
+#define PAMU_AVS1_GCV   0x2000
+#define PAMU_AVS1_PDV   0x4000
+#define PAMU_AV_MASK    (PAMU_AVS1_AV | PAMU_AVS1_OTV | PAMU_AVS1_APV | PAMU_AVS1_WAV \
+                       | PAMU_AVS1_LAV | PAMU_AVS1_GCV | PAMU_AVS1_PDV)
+#define PAMU_AVS1_LIODN_SHIFT 16
+#define PAMU_LAV_LIODN_NOT_IN_PPAACT 0x400
+
+#define PAMU_AVS2  0x0054
+#define PAMU_AVAH  0x0058
+#define PAMU_AVAL  0x005C
+#define PAMU_EECTL 0x0060
+#define PAMU_EEDIS 0x0064
+#define PAMU_EEINTEN 0x0068
+#define PAMU_EEDET 0x006C
+#define PAMU_EEATTR 0x0070
+#define PAMU_EEAHI 0x0074
+#define PAMU_EEALO 0x0078
+#define PAMU_EEDHI 0X007C
+#define PAMU_EEDLO 0x0080
+#define PAMU_EECC  0x0084
+#define PAMU_UDAD  0x0090
+
+/* PAMU Revision Registers */
+#define PAMU_PR1 0x0BF8
+#define PAMU_PR2 0x0BFC
+
+/* PAMU version mask */
+#define PAMU_PR1_MASK 0xffff
+
+/* PAMU Capabilities Registers */
+#define PAMU_PC1 0x0C00
+#define PAMU_PC2 0x0C04
+#define PAMU_PC3 0x0C08
+#define PAMU_PC4 0x0C0C
+
+/* PAMU Control Register */
+#define PAMU_PC 0x0C10
+
+/* PAMU control defs */
+#define PAMU_CONTROL 0x0C10
+#define PAMU_PC_PGC 0x80000000  /* PAMU gate closed bit */
+#define PAMU_PC_PE   0x40000000 /* PAMU enable bit */
+#define PAMU_PC_SPCC 0x00000010 /* sPAACE cache enable */
+#define PAMU_PC_PPCC 0x00000001 /* pPAACE cache enable */
+#define PAMU_PC_OCE  0x00001000 /* OMT cache enable */
+
+#define PAMU_PFA1 0x0C14
+#define PAMU_PFA2 0x0C18
+
+#define PAMU_PC2_MLIODN(X) ((X) >> 16)
+#define PAMU_PC3_MWCE(X) (((X) >> 21) & 0xf)
+
+/* PAMU Interrupt control and Status Register */
+#define PAMU_PICS 0x0C1C
+#define PAMU_ACCESS_VIOLATION_STAT   0x8
+#define PAMU_ACCESS_VIOLATION_ENABLE 0x4
+
+/* PAMU Debug Registers */
+#define PAMU_PD1 0x0F00
+#define PAMU_PD2 0x0F04
+#define PAMU_PD3 0x0F08
+#define PAMU_PD4 0x0F0C
+
+#define PAACE_AP_PERMS_DENIED  0x0
+#define PAACE_AP_PERMS_QUERY   0x1
+#define PAACE_AP_PERMS_UPDATE  0x2
+#define PAACE_AP_PERMS_ALL     0x3
+
+#define PAACE_DD_TO_HOST       0x0
+#define PAACE_DD_TO_IO         0x1
+#define PAACE_PT_PRIMARY       0x0
+#define PAACE_PT_SECONDARY     0x1
+#define PAACE_V_INVALID        0x0
+#define PAACE_V_VALID          0x1
+#define PAACE_MW_SUBWINDOWS    0x1
+
+#define PAACE_WSE_4K           0xB
+#define PAACE_WSE_8K           0xC
+#define PAACE_WSE_16K          0xD
+#define PAACE_WSE_32K          0xE
+#define PAACE_WSE_64K          0xF
+#define PAACE_WSE_128K         0x10
+#define PAACE_WSE_256K         0x11
+#define PAACE_WSE_512K         0x12
+#define PAACE_WSE_1M           0x13
+#define PAACE_WSE_2M           0x14
+#define PAACE_WSE_4M           0x15
+#define PAACE_WSE_8M           0x16
+#define PAACE_WSE_16M          0x17
+#define PAACE_WSE_32M          0x18
+#define PAACE_WSE_64M          0x19
+#define PAACE_WSE_128M         0x1A
+#define PAACE_WSE_256M         0x1B
+#define PAACE_WSE_512M         0x1C
+#define PAACE_WSE_1G           0x1D
+#define PAACE_WSE_2G           0x1E
+#define PAACE_WSE_4G           0x1F
+
+#define PAACE_DID_PCI_EXPRESS_1 0x00
+#define PAACE_DID_PCI_EXPRESS_2 0x01
+#define PAACE_DID_PCI_EXPRESS_3 0x02
+#define PAACE_DID_PCI_EXPRESS_4 0x03
+#define PAACE_DID_LOCAL_BUS     0x04
+#define PAACE_DID_SRIO          0x0C
+#define PAACE_DID_MEM_1         0x10
+#define PAACE_DID_MEM_2         0x11
+#define PAACE_DID_MEM_3         0x12
+#define PAACE_DID_MEM_4         0x13
+#define PAACE_DID_MEM_1_2       0x14
+#define PAACE_DID_MEM_3_4       0x15
+#define PAACE_DID_MEM_1_4       0x16
+#define PAACE_DID_BM_SW_PORTAL  0x18
+#define PAACE_DID_PAMU          0x1C
+#define PAACE_DID_CAAM          0x21
+#define PAACE_DID_QM_SW_PORTAL  0x3C
+#define PAACE_DID_CORE0_INST    0x80
+#define PAACE_DID_CORE0_DATA    0x81
+#define PAACE_DID_CORE1_INST    0x82
+#define PAACE_DID_CORE1_DATA    0x83
+#define PAACE_DID_CORE2_INST    0x84
+#define PAACE_DID_CORE2_DATA    0x85
+#define PAACE_DID_CORE3_INST    0x86
+#define PAACE_DID_CORE3_DATA    0x87
+#define PAACE_DID_CORE4_INST    0x88
+#define PAACE_DID_CORE4_DATA    0x89
+#define PAACE_DID_CORE5_INST    0x8A
+#define PAACE_DID_CORE5_DATA    0x8B
+#define PAACE_DID_CORE6_INST    0x8C
+#define PAACE_DID_CORE6_DATA    0x8D
+#define PAACE_DID_CORE7_INST    0x8E
+#define PAACE_DID_CORE7_DATA    0x8F
+#define PAACE_DID_BROADCAST     0xFF
+
+#define PAACE_ATM_NO_XLATE      0x00
+#define PAACE_ATM_WINDOW_XLATE  0x01
+#define PAACE_ATM_PAGE_XLATE    0x02
+#define PAACE_ATM_WIN_PG_XLATE  \
+                (PAACE_ATM_WINDOW_XLATE | PAACE_ATM_PAGE_XLATE)
+#define PAACE_OTM_NO_XLATE      0x00
+#define PAACE_OTM_IMMEDIATE     0x01
+#define PAACE_OTM_INDEXED       0x02
+#define PAACE_OTM_RESERVED      0x03
+
+#define PAACE_M_COHERENCE_REQ   0x01
+
+#define PAACE_PID_0             0x0
+#define PAACE_PID_1             0x1
+#define PAACE_PID_2             0x2
+#define PAACE_PID_3             0x3
+#define PAACE_PID_4             0x4
+#define PAACE_PID_5             0x5
+#define PAACE_PID_6             0x6
+#define PAACE_PID_7             0x7
+
+#define PAACE_TCEF_FORMAT0_8B   0x00
+#define PAACE_TCEF_FORMAT1_RSVD 0x01
+/*
+ * Hard coded value for the PAACT size to accomodate
+ * maximum LIODN value generated by u-boot.
+ */
+#define PAACE_NUMBER_ENTRIES    0x500
+/* Hard coded value for the SPAACT size */
+#define SPAACE_NUMBER_ENTRIES  0x800
+
+#define        OME_NUMBER_ENTRIES      16
+
+/* PAACE Bit Field Defines */
+#define PPAACE_AF_WBAL                 0xfffff000
+#define PPAACE_AF_WBAL_SHIFT           12
+#define PPAACE_AF_WSE                  0x00000fc0
+#define PPAACE_AF_WSE_SHIFT            6
+#define PPAACE_AF_MW                   0x00000020
+#define PPAACE_AF_MW_SHIFT             5
+
+#define SPAACE_AF_LIODN                        0xffff0000
+#define SPAACE_AF_LIODN_SHIFT          16
+
+#define PAACE_AF_AP                    0x00000018
+#define PAACE_AF_AP_SHIFT              3
+#define PAACE_AF_DD                    0x00000004
+#define PAACE_AF_DD_SHIFT              2
+#define PAACE_AF_PT                    0x00000002
+#define PAACE_AF_PT_SHIFT              1
+#define PAACE_AF_V                     0x00000001
+#define PAACE_AF_V_SHIFT               0
+
+#define PAACE_DA_HOST_CR               0x80
+#define PAACE_DA_HOST_CR_SHIFT         7
+
+#define PAACE_IA_CID                   0x00FF0000
+#define PAACE_IA_CID_SHIFT             16
+#define PAACE_IA_WCE                   0x000000F0
+#define PAACE_IA_WCE_SHIFT             4
+#define PAACE_IA_ATM                   0x0000000C
+#define PAACE_IA_ATM_SHIFT             2
+#define PAACE_IA_OTM                   0x00000003
+#define PAACE_IA_OTM_SHIFT             0
+
+#define PAACE_WIN_TWBAL                        0xfffff000
+#define PAACE_WIN_TWBAL_SHIFT          12
+#define PAACE_WIN_SWSE                 0x00000fc0
+#define PAACE_WIN_SWSE_SHIFT           6
+
+/* PAMU Data Structures */
+/* primary / secondary paact structure */
+struct paace {
+       /* PAACE Offset 0x00 */
+       u32 wbah;                               /* only valid for Primary PAACE */
+       u32 addr_bitfields;             /* See P/S PAACE_AF_* */
+
+       /* PAACE Offset 0x08 */
+       /* Interpretation of first 32 bits dependent on DD above */
+       union {
+               struct {
+                       /* Destination ID, see PAACE_DID_* defines */
+                       u8 did;
+                       /* Partition ID */
+                       u8 pid;
+                       /* Snoop ID */
+                       u8 snpid;
+                       /* coherency_required : 1 reserved : 7 */
+                       u8 coherency_required; /* See PAACE_DA_* */
+               } to_host;
+               struct {
+                       /* Destination ID, see PAACE_DID_* defines */
+                       u8  did;
+                       u8  reserved1;
+                       u16 reserved2;
+               } to_io;
+       } domain_attr;
+
+       /* Implementation attributes + window count + address & operation translation modes */
+       u32 impl_attr;                  /* See PAACE_IA_* */
+
+       /* PAACE Offset 0x10 */
+       /* Translated window base address */
+       u32 twbah;
+       u32 win_bitfields;                      /* See PAACE_WIN_* */
+
+       /* PAACE Offset 0x18 */
+       /* first secondary paace entry */
+       u32 fspi;                               /* only valid for Primary PAACE */
+       union {
+               struct {
+                       u8 ioea;
+                       u8 moea;
+                       u8 ioeb;
+                       u8 moeb;
+               } immed_ot;
+               struct {
+                       u16 reserved;
+                       u16 omi;
+               } index_ot;
+       } op_encode;
+
+       /* PAACE Offsets 0x20-0x38 */
+       u32 reserved[8];                        /* not currently implemented */
+};
+
+/* OME : Operation mapping entry
+ * MOE : Mapped Operation Encodings
+ * The operation mapping table is table containing operation mapping entries (OME).
+ * The index of a particular OME is programmed in the PAACE entry for translation
+ * in bound I/O operations corresponding to an LIODN. The OMT is used for translation
+ * specifically in case of the indexed translation mode. Each OME contains a 128
+ * byte mapped operation encoding (MOE), where each byte represents an MOE.
+ */
+#define NUM_MOE 128
+struct ome {
+       u8 moe[NUM_MOE];
+} __attribute__((packed));
+
+#define PAACT_SIZE              (sizeof(struct paace) * PAACE_NUMBER_ENTRIES)
+#define SPAACT_SIZE              (sizeof(struct paace) * SPAACE_NUMBER_ENTRIES)
+#define OMT_SIZE                (sizeof(struct ome) * OME_NUMBER_ENTRIES)
+
+#define PAMU_PAGE_SHIFT 12
+#define PAMU_PAGE_SIZE  4096ULL
+
+#define IOE_READ        0x00
+#define IOE_READ_IDX    0x00
+#define IOE_WRITE       0x81
+#define IOE_WRITE_IDX   0x01
+#define IOE_EREAD0      0x82    /* Enhanced read type 0 */
+#define IOE_EREAD0_IDX  0x02    /* Enhanced read type 0 */
+#define IOE_EWRITE0     0x83    /* Enhanced write type 0 */
+#define IOE_EWRITE0_IDX 0x03    /* Enhanced write type 0 */
+#define IOE_DIRECT0     0x84    /* Directive type 0 */
+#define IOE_DIRECT0_IDX 0x04    /* Directive type 0 */
+#define IOE_EREAD1      0x85    /* Enhanced read type 1 */
+#define IOE_EREAD1_IDX  0x05    /* Enhanced read type 1 */
+#define IOE_EWRITE1     0x86    /* Enhanced write type 1 */
+#define IOE_EWRITE1_IDX 0x06    /* Enhanced write type 1 */
+#define IOE_DIRECT1     0x87    /* Directive type 1 */
+#define IOE_DIRECT1_IDX 0x07    /* Directive type 1 */
+#define IOE_RAC         0x8c    /* Read with Atomic clear */
+#define IOE_RAC_IDX     0x0c    /* Read with Atomic clear */
+#define IOE_RAS         0x8d    /* Read with Atomic set */
+#define IOE_RAS_IDX     0x0d    /* Read with Atomic set */
+#define IOE_RAD         0x8e    /* Read with Atomic decrement */
+#define IOE_RAD_IDX     0x0e    /* Read with Atomic decrement */
+#define IOE_RAI         0x8f    /* Read with Atomic increment */
+#define IOE_RAI_IDX     0x0f    /* Read with Atomic increment */
+
+#define EOE_READ        0x00
+#define EOE_WRITE       0x01
+#define EOE_RAC         0x0c    /* Read with Atomic clear */
+#define EOE_RAS         0x0d    /* Read with Atomic set */
+#define EOE_RAD         0x0e    /* Read with Atomic decrement */
+#define EOE_RAI         0x0f    /* Read with Atomic increment */
+#define EOE_LDEC        0x10    /* Load external cache */
+#define EOE_LDECL       0x11    /* Load external cache with stash lock */
+#define EOE_LDECPE      0x12    /* Load external cache with preferred exclusive */
+#define EOE_LDECPEL     0x13    /* Load external cache with preferred exclusive and lock */
+#define EOE_LDECFE      0x14    /* Load external cache with forced exclusive */
+#define EOE_LDECFEL     0x15    /* Load external cache with forced exclusive and lock */
+#define EOE_RSA         0x16    /* Read with stash allocate */
+#define EOE_RSAU        0x17    /* Read with stash allocate and unlock */
+#define EOE_READI       0x18    /* Read with invalidate */
+#define EOE_RWNITC      0x19    /* Read with no intention to cache */
+#define EOE_WCI         0x1a    /* Write cache inhibited */
+#define EOE_WWSA        0x1b    /* Write with stash allocate */
+#define EOE_WWSAL       0x1c    /* Write with stash allocate and lock */
+#define EOE_WWSAO       0x1d    /* Write with stash allocate only */
+#define EOE_WWSAOL      0x1e    /* Write with stash allocate only and lock */
+#define EOE_VALID       0x80
+
+/* Function prototypes */
+int pamu_domain_init(void);
+int pamu_enable_liodn(int liodn);
+int pamu_disable_liodn(int liodn);
+void pamu_free_subwins(int liodn);
+int pamu_config_ppaace(int liodn, phys_addr_t win_addr, phys_addr_t win_size,
+                      u32 omi, unsigned long rpn, u32 snoopid, uint32_t stashid,
+                      u32 subwin_cnt, int prot);
+int pamu_config_spaace(int liodn, u32 subwin_cnt, u32 subwin_addr,
+                      phys_addr_t subwin_size, u32 omi, unsigned long rpn,
+                      uint32_t snoopid, u32 stashid, int enable, int prot);
+
+u32 get_stash_id(u32 stash_dest_hint, u32 vcpu);
+void get_ome_index(u32 *omi_index, struct device *dev);
+int  pamu_update_paace_stash(int liodn, u32 subwin, u32 value);
+int pamu_disable_spaace(int liodn, u32 subwin);
+u32 pamu_get_max_subwin_cnt(void);
+
+#endif  /* __FSL_PAMU_H */
diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c
new file mode 100644 (file)
index 0000000..c857c30
--- /dev/null
@@ -0,0 +1,1172 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ * Author: Varun Sethi <varun.sethi@freescale.com>
+ *
+ */
+
+#define pr_fmt(fmt)    "fsl-pamu-domain: %s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/iommu.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/of_platform.h>
+#include <linux/bootmem.h>
+#include <linux/err.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+
+#include <asm/pci-bridge.h>
+#include <sysdev/fsl_pci.h>
+
+#include "fsl_pamu_domain.h"
+#include "pci.h"
+
+/*
+ * Global spinlock that needs to be held while
+ * configuring PAMU.
+ */
+static DEFINE_SPINLOCK(iommu_lock);
+
+static struct kmem_cache *fsl_pamu_domain_cache;
+static struct kmem_cache *iommu_devinfo_cache;
+static DEFINE_SPINLOCK(device_domain_lock);
+
+static int __init iommu_init_mempool(void)
+{
+
+       fsl_pamu_domain_cache = kmem_cache_create("fsl_pamu_domain",
+                                        sizeof(struct fsl_dma_domain),
+                                        0,
+                                        SLAB_HWCACHE_ALIGN,
+
+                                        NULL);
+       if (!fsl_pamu_domain_cache) {
+               pr_debug("Couldn't create fsl iommu_domain cache\n");
+               return -ENOMEM;
+       }
+
+       iommu_devinfo_cache = kmem_cache_create("iommu_devinfo",
+                                        sizeof(struct device_domain_info),
+                                        0,
+                                        SLAB_HWCACHE_ALIGN,
+                                        NULL);
+       if (!iommu_devinfo_cache) {
+               pr_debug("Couldn't create devinfo cache\n");
+               kmem_cache_destroy(fsl_pamu_domain_cache);
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static phys_addr_t get_phys_addr(struct fsl_dma_domain *dma_domain, dma_addr_t iova)
+{
+       u32 win_cnt = dma_domain->win_cnt;
+       struct dma_window *win_ptr =
+                               &dma_domain->win_arr[0];
+       struct iommu_domain_geometry *geom;
+
+       geom = &dma_domain->iommu_domain->geometry;
+
+       if (!win_cnt || !dma_domain->geom_size) {
+               pr_debug("Number of windows/geometry not configured for the domain\n");
+               return 0;
+       }
+
+       if (win_cnt > 1) {
+               u64 subwin_size;
+               dma_addr_t subwin_iova;
+               u32 wnd;
+
+               subwin_size = dma_domain->geom_size >> ilog2(win_cnt);
+               subwin_iova = iova & ~(subwin_size - 1);
+               wnd = (subwin_iova - geom->aperture_start) >> ilog2(subwin_size);
+               win_ptr = &dma_domain->win_arr[wnd];
+       }
+
+       if (win_ptr->valid)
+               return (win_ptr->paddr + (iova & (win_ptr->size - 1)));
+
+       return 0;
+}
+
+static int map_subwins(int liodn, struct fsl_dma_domain *dma_domain)
+{
+       struct dma_window *sub_win_ptr =
+                               &dma_domain->win_arr[0];
+       int i, ret;
+       unsigned long rpn, flags;
+
+       for (i = 0; i < dma_domain->win_cnt; i++) {
+               if (sub_win_ptr[i].valid) {
+                       rpn = sub_win_ptr[i].paddr >>
+                                PAMU_PAGE_SHIFT;
+                       spin_lock_irqsave(&iommu_lock, flags);
+                       ret = pamu_config_spaace(liodn, dma_domain->win_cnt, i,
+                                                sub_win_ptr[i].size,
+                                                ~(u32)0,
+                                                rpn,
+                                                dma_domain->snoop_id,
+                                                dma_domain->stash_id,
+                                                (i > 0) ? 1 : 0,
+                                                sub_win_ptr[i].prot);
+                       spin_unlock_irqrestore(&iommu_lock, flags);
+                       if (ret) {
+                               pr_debug("PAMU SPAACE configuration failed for liodn %d\n",
+                                        liodn);
+                               return ret;
+                       }
+               }
+       }
+
+       return ret;
+}
+
+static int map_win(int liodn, struct fsl_dma_domain *dma_domain)
+{
+       int ret;
+       struct dma_window *wnd = &dma_domain->win_arr[0];
+       phys_addr_t wnd_addr = dma_domain->iommu_domain->geometry.aperture_start;
+       unsigned long flags;
+
+       spin_lock_irqsave(&iommu_lock, flags);
+       ret = pamu_config_ppaace(liodn, wnd_addr,
+                                wnd->size,
+                                ~(u32)0,
+                                wnd->paddr >> PAMU_PAGE_SHIFT,
+                                dma_domain->snoop_id, dma_domain->stash_id,
+                                0, wnd->prot);
+       spin_unlock_irqrestore(&iommu_lock, flags);
+       if (ret)
+               pr_debug("PAMU PAACE configuration failed for liodn %d\n",
+                       liodn);
+
+       return ret;
+}
+
+/* Map the DMA window corresponding to the LIODN */
+static int map_liodn(int liodn, struct fsl_dma_domain *dma_domain)
+{
+       if (dma_domain->win_cnt > 1)
+               return map_subwins(liodn, dma_domain);
+       else
+               return map_win(liodn, dma_domain);
+
+}
+
+/* Update window/subwindow mapping for the LIODN */
+static int update_liodn(int liodn, struct fsl_dma_domain *dma_domain, u32 wnd_nr)
+{
+       int ret;
+       struct dma_window *wnd = &dma_domain->win_arr[wnd_nr];
+       unsigned long flags;
+
+       spin_lock_irqsave(&iommu_lock, flags);
+       if (dma_domain->win_cnt > 1) {
+               ret = pamu_config_spaace(liodn, dma_domain->win_cnt, wnd_nr,
+                                        wnd->size,
+                                        ~(u32)0,
+                                        wnd->paddr >> PAMU_PAGE_SHIFT,
+                                        dma_domain->snoop_id,
+                                        dma_domain->stash_id,
+                                        (wnd_nr > 0) ? 1 : 0,
+                                        wnd->prot);
+               if (ret)
+                       pr_debug("Subwindow reconfiguration failed for liodn %d\n", liodn);
+       } else {
+               phys_addr_t wnd_addr;
+
+               wnd_addr = dma_domain->iommu_domain->geometry.aperture_start;
+
+               ret = pamu_config_ppaace(liodn, wnd_addr,
+                                        wnd->size,
+                                        ~(u32)0,
+                                        wnd->paddr >> PAMU_PAGE_SHIFT,
+                                       dma_domain->snoop_id, dma_domain->stash_id,
+                                       0, wnd->prot);
+               if (ret)
+                       pr_debug("Window reconfiguration failed for liodn %d\n", liodn);
+       }
+
+       spin_unlock_irqrestore(&iommu_lock, flags);
+
+       return ret;
+}
+
+static int update_liodn_stash(int liodn, struct fsl_dma_domain *dma_domain,
+                                u32 val)
+{
+       int ret = 0, i;
+       unsigned long flags;
+
+       spin_lock_irqsave(&iommu_lock, flags);
+       if (!dma_domain->win_arr) {
+               pr_debug("Windows not configured, stash destination update failed for liodn %d\n", liodn);
+               spin_unlock_irqrestore(&iommu_lock, flags);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < dma_domain->win_cnt; i++) {
+               ret = pamu_update_paace_stash(liodn, i, val);
+               if (ret) {
+                       pr_debug("Failed to update SPAACE %d field for liodn %d\n ", i, liodn);
+                       spin_unlock_irqrestore(&iommu_lock, flags);
+                       return ret;
+               }
+       }
+
+       spin_unlock_irqrestore(&iommu_lock, flags);
+
+       return ret;
+}
+
+/* Set the geometry parameters for a LIODN */
+static int pamu_set_liodn(int liodn, struct device *dev,
+                          struct fsl_dma_domain *dma_domain,
+                          struct iommu_domain_geometry *geom_attr,
+                          u32 win_cnt)
+{
+       phys_addr_t window_addr, window_size;
+       phys_addr_t subwin_size;
+       int ret = 0, i;
+       u32 omi_index = ~(u32)0;
+       unsigned long flags;
+
+       /*
+        * Configure the omi_index at the geometry setup time.
+        * This is a static value which depends on the type of
+        * device and would not change thereafter.
+        */
+       get_ome_index(&omi_index, dev);
+
+       window_addr = geom_attr->aperture_start;
+       window_size = dma_domain->geom_size;
+
+       spin_lock_irqsave(&iommu_lock, flags);
+       ret = pamu_disable_liodn(liodn);
+       if (!ret)
+               ret = pamu_config_ppaace(liodn, window_addr, window_size, omi_index,
+                                        0, dma_domain->snoop_id,
+                                        dma_domain->stash_id, win_cnt, 0);
+       spin_unlock_irqrestore(&iommu_lock, flags);
+       if (ret) {
+               pr_debug("PAMU PAACE configuration failed for liodn %d, win_cnt =%d\n", liodn, win_cnt);
+               return ret;
+       }
+
+       if (win_cnt > 1) {
+               subwin_size = window_size >> ilog2(win_cnt);
+               for (i = 0; i < win_cnt; i++) {
+                       spin_lock_irqsave(&iommu_lock, flags);
+                       ret = pamu_disable_spaace(liodn, i);
+                       if (!ret)
+                               ret = pamu_config_spaace(liodn, win_cnt, i,
+                                                        subwin_size, omi_index,
+                                                        0, dma_domain->snoop_id,
+                                                        dma_domain->stash_id,
+                                                        0, 0);
+                       spin_unlock_irqrestore(&iommu_lock, flags);
+                       if (ret) {
+                               pr_debug("PAMU SPAACE configuration failed for liodn %d\n", liodn);
+                               return ret;
+                       }
+               }
+       }
+
+       return ret;
+}
+
+static int check_size(u64 size, dma_addr_t iova)
+{
+       /*
+        * Size must be a power of two and at least be equal
+        * to PAMU page size.
+        */
+       if (!is_power_of_2(size) || size < PAMU_PAGE_SIZE) {
+               pr_debug("%s: size too small or not a power of two\n", __func__);
+               return -EINVAL;
+       }
+
+       /* iova must be page size aligned*/
+       if (iova & (size - 1)) {
+               pr_debug("%s: address is not aligned with window size\n", __func__);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static struct fsl_dma_domain *iommu_alloc_dma_domain(void)
+{
+       struct fsl_dma_domain *domain;
+
+       domain = kmem_cache_zalloc(fsl_pamu_domain_cache, GFP_KERNEL);
+       if (!domain)
+               return NULL;
+
+       domain->stash_id = ~(u32)0;
+       domain->snoop_id = ~(u32)0;
+       domain->win_cnt = pamu_get_max_subwin_cnt();
+       domain->geom_size = 0;
+
+       INIT_LIST_HEAD(&domain->devices);
+
+       spin_lock_init(&domain->domain_lock);
+
+       return domain;
+}
+
+static inline struct device_domain_info *find_domain(struct device *dev)
+{
+       return dev->archdata.iommu_domain;
+}
+
+static void remove_device_ref(struct device_domain_info *info, u32 win_cnt)
+{
+       unsigned long flags;
+
+       list_del(&info->link);
+       spin_lock_irqsave(&iommu_lock, flags);
+       if (win_cnt > 1)
+               pamu_free_subwins(info->liodn);
+       pamu_disable_liodn(info->liodn);
+       spin_unlock_irqrestore(&iommu_lock, flags);
+       spin_lock_irqsave(&device_domain_lock, flags);
+       info->dev->archdata.iommu_domain = NULL;
+       kmem_cache_free(iommu_devinfo_cache, info);
+       spin_unlock_irqrestore(&device_domain_lock, flags);
+}
+
+static void detach_device(struct device *dev, struct fsl_dma_domain *dma_domain)
+{
+       struct device_domain_info *info, *tmp;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dma_domain->domain_lock, flags);
+       /* Remove the device from the domain device list */
+       list_for_each_entry_safe(info, tmp, &dma_domain->devices, link) {
+               if (!dev || (info->dev == dev))
+                       remove_device_ref(info, dma_domain->win_cnt);
+       }
+       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+}
+
+static void attach_device(struct fsl_dma_domain *dma_domain, int liodn, struct device *dev)
+{
+       struct device_domain_info *info, *old_domain_info;
+       unsigned long flags;
+
+       spin_lock_irqsave(&device_domain_lock, flags);
+       /*
+        * Check here if the device is already attached to domain or not.
+        * If the device is already attached to a domain detach it.
+        */
+       old_domain_info = find_domain(dev);
+       if (old_domain_info && old_domain_info->domain != dma_domain) {
+               spin_unlock_irqrestore(&device_domain_lock, flags);
+               detach_device(dev, old_domain_info->domain);
+               spin_lock_irqsave(&device_domain_lock, flags);
+       }
+
+       info = kmem_cache_zalloc(iommu_devinfo_cache, GFP_ATOMIC);
+
+       info->dev = dev;
+       info->liodn = liodn;
+       info->domain = dma_domain;
+
+       list_add(&info->link, &dma_domain->devices);
+       /*
+        * In case of devices with multiple LIODNs just store
+        * the info for the first LIODN as all
+        * LIODNs share the same domain
+        */
+       if (!old_domain_info)
+               dev->archdata.iommu_domain = info;
+       spin_unlock_irqrestore(&device_domain_lock, flags);
+
+}
+
+static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain,
+                                           dma_addr_t iova)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+
+       if ((iova < domain->geometry.aperture_start) ||
+               iova > (domain->geometry.aperture_end))
+               return 0;
+
+       return get_phys_addr(dma_domain, iova);
+}
+
+static int fsl_pamu_domain_has_cap(struct iommu_domain *domain,
+                                     unsigned long cap)
+{
+       return cap == IOMMU_CAP_CACHE_COHERENCY;
+}
+
+static void fsl_pamu_domain_destroy(struct iommu_domain *domain)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+
+       domain->priv = NULL;
+
+       /* remove all the devices from the device list */
+       detach_device(NULL, dma_domain);
+
+       dma_domain->enabled = 0;
+       dma_domain->mapped = 0;
+
+       kmem_cache_free(fsl_pamu_domain_cache, dma_domain);
+}
+
+static int fsl_pamu_domain_init(struct iommu_domain *domain)
+{
+       struct fsl_dma_domain *dma_domain;
+
+       dma_domain = iommu_alloc_dma_domain();
+       if (!dma_domain) {
+               pr_debug("dma_domain allocation failed\n");
+               return -ENOMEM;
+       }
+       domain->priv = dma_domain;
+       dma_domain->iommu_domain = domain;
+       /* defaul geometry 64 GB i.e. maximum system address */
+       domain->geometry.aperture_start = 0;
+       domain->geometry.aperture_end = (1ULL << 36) - 1;
+       domain->geometry.force_aperture = true;
+
+       return 0;
+}
+
+/* Configure geometry settings for all LIODNs associated with domain */
+static int pamu_set_domain_geometry(struct fsl_dma_domain *dma_domain,
+                                   struct iommu_domain_geometry *geom_attr,
+                                   u32 win_cnt)
+{
+       struct device_domain_info *info;
+       int ret = 0;
+
+       list_for_each_entry(info, &dma_domain->devices, link) {
+               ret = pamu_set_liodn(info->liodn, info->dev, dma_domain,
+                                     geom_attr, win_cnt);
+               if (ret)
+                       break;
+       }
+
+       return ret;
+}
+
+/* Update stash destination for all LIODNs associated with the domain */
+static int update_domain_stash(struct fsl_dma_domain *dma_domain, u32 val)
+{
+       struct device_domain_info *info;
+       int ret = 0;
+
+       list_for_each_entry(info, &dma_domain->devices, link) {
+               ret = update_liodn_stash(info->liodn, dma_domain, val);
+               if (ret)
+                       break;
+       }
+
+       return ret;
+}
+
+/* Update domain mappings for all LIODNs associated with the domain */
+static int update_domain_mapping(struct fsl_dma_domain *dma_domain, u32 wnd_nr)
+{
+       struct device_domain_info *info;
+       int ret = 0;
+
+       list_for_each_entry(info, &dma_domain->devices, link) {
+               ret = update_liodn(info->liodn, dma_domain, wnd_nr);
+               if (ret)
+                       break;
+       }
+       return ret;
+}
+
+static int disable_domain_win(struct fsl_dma_domain *dma_domain, u32 wnd_nr)
+{
+       struct device_domain_info *info;
+       int ret = 0;
+
+       list_for_each_entry(info, &dma_domain->devices, link) {
+               if (dma_domain->win_cnt == 1 && dma_domain->enabled) {
+                       ret = pamu_disable_liodn(info->liodn);
+                       if (!ret)
+                               dma_domain->enabled = 0;
+               } else {
+                       ret = pamu_disable_spaace(info->liodn, wnd_nr);
+               }
+       }
+
+       return ret;
+}
+
+static void fsl_pamu_window_disable(struct iommu_domain *domain, u32 wnd_nr)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&dma_domain->domain_lock, flags);
+       if (!dma_domain->win_arr) {
+               pr_debug("Number of windows not configured\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return;
+       }
+
+       if (wnd_nr >= dma_domain->win_cnt) {
+               pr_debug("Invalid window index\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return;
+       }
+
+       if (dma_domain->win_arr[wnd_nr].valid) {
+               ret = disable_domain_win(dma_domain, wnd_nr);
+               if (!ret) {
+                       dma_domain->win_arr[wnd_nr].valid = 0;
+                       dma_domain->mapped--;
+               }
+       }
+
+       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+
+}
+
+static int fsl_pamu_window_enable(struct iommu_domain *domain, u32 wnd_nr,
+                                 phys_addr_t paddr, u64 size, int prot)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+       struct dma_window *wnd;
+       int pamu_prot = 0;
+       int ret;
+       unsigned long flags;
+       u64 win_size;
+
+       if (prot & IOMMU_READ)
+               pamu_prot |= PAACE_AP_PERMS_QUERY;
+       if (prot & IOMMU_WRITE)
+               pamu_prot |= PAACE_AP_PERMS_UPDATE;
+
+       spin_lock_irqsave(&dma_domain->domain_lock, flags);
+       if (!dma_domain->win_arr) {
+               pr_debug("Number of windows not configured\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return -ENODEV;
+       }
+
+       if (wnd_nr >= dma_domain->win_cnt) {
+               pr_debug("Invalid window index\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return -EINVAL;
+       }
+
+       win_size = dma_domain->geom_size >> ilog2(dma_domain->win_cnt);
+       if (size > win_size) {
+               pr_debug("Invalid window size \n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return -EINVAL;
+       }
+
+       if (dma_domain->win_cnt == 1) {
+               if (dma_domain->enabled) {
+                       pr_debug("Disable the window before updating the mapping\n");
+                       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+                       return -EBUSY;
+               }
+
+               ret = check_size(size, domain->geometry.aperture_start);
+               if (ret) {
+                       pr_debug("Aperture start not aligned to the size\n");
+                       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+                       return -EINVAL;
+               }
+       }
+
+       wnd = &dma_domain->win_arr[wnd_nr];
+       if (!wnd->valid) {
+               wnd->paddr = paddr;
+               wnd->size = size;
+               wnd->prot = pamu_prot;
+
+               ret = update_domain_mapping(dma_domain, wnd_nr);
+               if (!ret) {
+                       wnd->valid = 1;
+                       dma_domain->mapped++;
+               }
+       } else {
+               pr_debug("Disable the window before updating the mapping\n");
+               ret = -EBUSY;
+       }
+
+       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+
+       return ret;
+}
+
+/*
+ * Attach the LIODN to the DMA domain and configure the geometry
+ * and window mappings.
+ */
+static int handle_attach_device(struct fsl_dma_domain *dma_domain,
+                                struct device *dev, const u32 *liodn,
+                                int num)
+{
+       unsigned long flags;
+       struct iommu_domain *domain = dma_domain->iommu_domain;
+       int ret = 0;
+       int i;
+
+       spin_lock_irqsave(&dma_domain->domain_lock, flags);
+       for (i = 0; i < num; i++) {
+
+               /* Ensure that LIODN value is valid */
+               if (liodn[i] >= PAACE_NUMBER_ENTRIES) {
+                       pr_debug("Invalid liodn %d, attach device failed for %s\n",
+                               liodn[i], dev->of_node->full_name);
+                       ret = -EINVAL;
+                       break;
+               }
+
+               attach_device(dma_domain, liodn[i], dev);
+               /*
+                * Check if geometry has already been configured
+                * for the domain. If yes, set the geometry for
+                * the LIODN.
+                */
+               if (dma_domain->win_arr) {
+                       u32 win_cnt = dma_domain->win_cnt > 1 ? dma_domain->win_cnt : 0;
+                       ret = pamu_set_liodn(liodn[i], dev, dma_domain,
+                                             &domain->geometry,
+                                             win_cnt);
+                       if (ret)
+                               break;
+                       if (dma_domain->mapped) {
+                               /*
+                                * Create window/subwindow mapping for
+                                * the LIODN.
+                                */
+                               ret = map_liodn(liodn[i], dma_domain);
+                               if (ret)
+                                       break;
+                       }
+               }
+       }
+       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+
+       return ret;
+}
+
+static int fsl_pamu_attach_device(struct iommu_domain *domain,
+                                 struct device *dev)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+       const u32 *liodn;
+       u32 liodn_cnt;
+       int len, ret = 0;
+       struct pci_dev *pdev = NULL;
+       struct pci_controller *pci_ctl;
+
+       /*
+        * Use LIODN of the PCI controller while attaching a
+        * PCI device.
+        */
+       if (dev->bus == &pci_bus_type) {
+               pdev = to_pci_dev(dev);
+               pci_ctl = pci_bus_to_host(pdev->bus);
+               /*
+                * make dev point to pci controller device
+                * so we can get the LIODN programmed by
+                * u-boot.
+                */
+               dev = pci_ctl->parent;
+       }
+
+       liodn = of_get_property(dev->of_node, "fsl,liodn", &len);
+       if (liodn) {
+               liodn_cnt = len / sizeof(u32);
+               ret = handle_attach_device(dma_domain, dev,
+                                        liodn, liodn_cnt);
+       } else {
+               pr_debug("missing fsl,liodn property at %s\n",
+                         dev->of_node->full_name);
+                       ret = -EINVAL;
+       }
+
+       return ret;
+}
+
+static void fsl_pamu_detach_device(struct iommu_domain *domain,
+                                     struct device *dev)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+       const u32 *prop;
+       int len;
+       struct pci_dev *pdev = NULL;
+       struct pci_controller *pci_ctl;
+
+       /*
+        * Use LIODN of the PCI controller while detaching a
+        * PCI device.
+        */
+       if (dev->bus == &pci_bus_type) {
+               pdev = to_pci_dev(dev);
+               pci_ctl = pci_bus_to_host(pdev->bus);
+               /*
+                * make dev point to pci controller device
+                * so we can get the LIODN programmed by
+                * u-boot.
+                */
+               dev = pci_ctl->parent;
+       }
+
+       prop = of_get_property(dev->of_node, "fsl,liodn", &len);
+       if (prop)
+               detach_device(dev, dma_domain);
+       else
+               pr_debug("missing fsl,liodn property at %s\n",
+                         dev->of_node->full_name);
+}
+
+static  int configure_domain_geometry(struct iommu_domain *domain, void *data)
+{
+       struct iommu_domain_geometry *geom_attr = data;
+       struct fsl_dma_domain *dma_domain = domain->priv;
+       dma_addr_t geom_size;
+       unsigned long flags;
+
+       geom_size = geom_attr->aperture_end - geom_attr->aperture_start + 1;
+       /*
+        * Sanity check the geometry size. Also, we do not support
+        * DMA outside of the geometry.
+        */
+       if (check_size(geom_size, geom_attr->aperture_start) ||
+               !geom_attr->force_aperture) {
+                       pr_debug("Invalid PAMU geometry attributes\n");
+                       return -EINVAL;
+               }
+
+       spin_lock_irqsave(&dma_domain->domain_lock, flags);
+       if (dma_domain->enabled) {
+               pr_debug("Can't set geometry attributes as domain is active\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return  -EBUSY;
+       }
+
+       /* Copy the domain geometry information */
+       memcpy(&domain->geometry, geom_attr,
+              sizeof(struct iommu_domain_geometry));
+       dma_domain->geom_size = geom_size;
+
+       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+
+       return 0;
+}
+
+/* Set the domain stash attribute */
+static int configure_domain_stash(struct fsl_dma_domain *dma_domain, void *data)
+{
+       struct pamu_stash_attribute *stash_attr = data;
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&dma_domain->domain_lock, flags);
+
+       memcpy(&dma_domain->dma_stash, stash_attr,
+                sizeof(struct pamu_stash_attribute));
+
+       dma_domain->stash_id = get_stash_id(stash_attr->cache,
+                                           stash_attr->cpu);
+       if (dma_domain->stash_id == ~(u32)0) {
+               pr_debug("Invalid stash attributes\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return -EINVAL;
+       }
+
+       ret = update_domain_stash(dma_domain, dma_domain->stash_id);
+
+       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+
+       return ret;
+}
+
+/* Configure domain dma state i.e. enable/disable DMA*/
+static int configure_domain_dma_state(struct fsl_dma_domain *dma_domain, bool enable)
+{
+       struct device_domain_info *info;
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&dma_domain->domain_lock, flags);
+
+       if (enable && !dma_domain->mapped) {
+               pr_debug("Can't enable DMA domain without valid mapping\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return -ENODEV;
+       }
+
+       dma_domain->enabled = enable;
+       list_for_each_entry(info, &dma_domain->devices,
+                                link) {
+               ret = (enable) ? pamu_enable_liodn(info->liodn) :
+                       pamu_disable_liodn(info->liodn);
+               if (ret)
+                       pr_debug("Unable to set dma state for liodn %d",
+                                info->liodn);
+       }
+       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+
+       return 0;
+}
+
+static int fsl_pamu_set_domain_attr(struct iommu_domain *domain,
+                                enum iommu_attr attr_type, void *data)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+       int ret = 0;
+
+
+       switch (attr_type) {
+       case DOMAIN_ATTR_GEOMETRY:
+               ret = configure_domain_geometry(domain, data);
+               break;
+       case DOMAIN_ATTR_FSL_PAMU_STASH:
+               ret = configure_domain_stash(dma_domain, data);
+               break;
+       case DOMAIN_ATTR_FSL_PAMU_ENABLE:
+               ret = configure_domain_dma_state(dma_domain, *(int *)data);
+               break;
+       default:
+               pr_debug("Unsupported attribute type\n");
+               ret = -EINVAL;
+               break;
+       };
+
+       return ret;
+}
+
+static int fsl_pamu_get_domain_attr(struct iommu_domain *domain,
+                                enum iommu_attr attr_type, void *data)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+       int ret = 0;
+
+
+       switch (attr_type) {
+       case DOMAIN_ATTR_FSL_PAMU_STASH:
+               memcpy((struct pamu_stash_attribute *) data, &dma_domain->dma_stash,
+                                sizeof(struct pamu_stash_attribute));
+               break;
+       case DOMAIN_ATTR_FSL_PAMU_ENABLE:
+               *(int *)data = dma_domain->enabled;
+               break;
+       case DOMAIN_ATTR_FSL_PAMUV1:
+               *(int *)data = DOMAIN_ATTR_FSL_PAMUV1;
+               break;
+       default:
+               pr_debug("Unsupported attribute type\n");
+               ret = -EINVAL;
+               break;
+       };
+
+       return ret;
+}
+
+#define REQ_ACS_FLAGS  (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)
+
+static struct iommu_group *get_device_iommu_group(struct device *dev)
+{
+       struct iommu_group *group;
+
+       group = iommu_group_get(dev);
+       if (!group)
+               group = iommu_group_alloc();
+
+       return group;
+}
+
+static  bool check_pci_ctl_endpt_part(struct pci_controller *pci_ctl)
+{
+       u32 version;
+
+       /* Check the PCI controller version number by readding BRR1 register */
+       version = in_be32(pci_ctl->cfg_addr + (PCI_FSL_BRR1 >> 2));
+       version &= PCI_FSL_BRR1_VER;
+       /* If PCI controller version is >= 0x204 we can partition endpoints*/
+       if (version >= 0x204)
+               return 1;
+
+       return 0;
+}
+
+/* Get iommu group information from peer devices or devices on the parent bus */
+static struct iommu_group *get_shared_pci_device_group(struct pci_dev *pdev)
+{
+       struct pci_dev *tmp;
+       struct iommu_group *group;
+       struct pci_bus *bus = pdev->bus;
+
+       /*
+        * Traverese the pci bus device list to get
+        * the shared iommu group.
+        */
+       while (bus) {
+               list_for_each_entry(tmp, &bus->devices, bus_list) {
+                       if (tmp == pdev)
+                               continue;
+                       group = iommu_group_get(&tmp->dev);
+                       if (group)
+                               return group;
+               }
+
+               bus = bus->parent;
+       }
+
+       return NULL;
+}
+
+static struct iommu_group *get_pci_device_group(struct pci_dev *pdev)
+{
+       struct pci_controller *pci_ctl;
+       bool pci_endpt_partioning;
+       struct iommu_group *group = NULL;
+       struct pci_dev *bridge, *dma_pdev = NULL;
+
+       pci_ctl = pci_bus_to_host(pdev->bus);
+       pci_endpt_partioning = check_pci_ctl_endpt_part(pci_ctl);
+       /* We can partition PCIe devices so assign device group to the device */
+       if (pci_endpt_partioning) {
+               bridge = pci_find_upstream_pcie_bridge(pdev);
+               if (bridge) {
+                       if (pci_is_pcie(bridge))
+                               dma_pdev = pci_get_domain_bus_and_slot(
+                                               pci_domain_nr(pdev->bus),
+                                               bridge->subordinate->number, 0);
+                       if (!dma_pdev)
+                               dma_pdev = pci_dev_get(bridge);
+               } else
+                       dma_pdev = pci_dev_get(pdev);
+
+               /* Account for quirked devices */
+               swap_pci_ref(&dma_pdev, pci_get_dma_source(dma_pdev));
+
+               /*
+                * If it's a multifunction device that does not support our
+                * required ACS flags, add to the same group as lowest numbered
+                * function that also does not suport the required ACS flags.
+                */
+               if (dma_pdev->multifunction &&
+                   !pci_acs_enabled(dma_pdev, REQ_ACS_FLAGS)) {
+                       u8 i, slot = PCI_SLOT(dma_pdev->devfn);
+
+                       for (i = 0; i < 8; i++) {
+                               struct pci_dev *tmp;
+
+                               tmp = pci_get_slot(dma_pdev->bus, PCI_DEVFN(slot, i));
+                               if (!tmp)
+                                       continue;
+
+                               if (!pci_acs_enabled(tmp, REQ_ACS_FLAGS)) {
+                                       swap_pci_ref(&dma_pdev, tmp);
+                                       break;
+                               }
+                               pci_dev_put(tmp);
+                       }
+               }
+
+               /*
+                * Devices on the root bus go through the iommu.  If that's not us,
+                * find the next upstream device and test ACS up to the root bus.
+                * Finding the next device may require skipping virtual buses.
+                */
+               while (!pci_is_root_bus(dma_pdev->bus)) {
+                       struct pci_bus *bus = dma_pdev->bus;
+
+                       while (!bus->self) {
+                               if (!pci_is_root_bus(bus))
+                                       bus = bus->parent;
+                               else
+                                       goto root_bus;
+                       }
+
+                       if (pci_acs_path_enabled(bus->self, NULL, REQ_ACS_FLAGS))
+                               break;
+
+                       swap_pci_ref(&dma_pdev, pci_dev_get(bus->self));
+               }
+
+root_bus:
+               group = get_device_iommu_group(&dma_pdev->dev);
+               pci_dev_put(dma_pdev);
+               /*
+                * PCIe controller is not a paritionable entity
+                * free the controller device iommu_group.
+                */
+               if (pci_ctl->parent->iommu_group)
+                       iommu_group_remove_device(pci_ctl->parent);
+       } else {
+               /*
+                * All devices connected to the controller will share the
+                * PCI controllers device group. If this is the first
+                * device to be probed for the pci controller, copy the
+                * device group information from the PCI controller device
+                * node and remove the PCI controller iommu group.
+                * For subsequent devices, the iommu group information can
+                * be obtained from sibling devices (i.e. from the bus_devices
+                * link list).
+                */
+               if (pci_ctl->parent->iommu_group) {
+                       group = get_device_iommu_group(pci_ctl->parent);
+                       iommu_group_remove_device(pci_ctl->parent);
+               } else
+                       group = get_shared_pci_device_group(pdev);
+       }
+
+       return group;
+}
+
+static int fsl_pamu_add_device(struct device *dev)
+{
+       struct iommu_group *group = NULL;
+       struct pci_dev *pdev;
+       const u32 *prop;
+       int ret, len;
+
+       /*
+        * For platform devices we allocate a separate group for
+        * each of the devices.
+        */
+       if (dev->bus == &pci_bus_type) {
+               pdev = to_pci_dev(dev);
+               /* Don't create device groups for virtual PCI bridges */
+               if (pdev->subordinate)
+                       return 0;
+
+               group = get_pci_device_group(pdev);
+
+       } else {
+               prop = of_get_property(dev->of_node, "fsl,liodn", &len);
+               if (prop)
+                       group = get_device_iommu_group(dev);
+       }
+
+       if (!group || IS_ERR(group))
+               return PTR_ERR(group);
+
+       ret = iommu_group_add_device(group, dev);
+
+       iommu_group_put(group);
+       return ret;
+}
+
+static void fsl_pamu_remove_device(struct device *dev)
+{
+       iommu_group_remove_device(dev);
+}
+
+static int fsl_pamu_set_windows(struct iommu_domain *domain, u32 w_count)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&dma_domain->domain_lock, flags);
+       /* Ensure domain is inactive i.e. DMA should be disabled for the domain */
+       if (dma_domain->enabled) {
+               pr_debug("Can't set geometry attributes as domain is active\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return  -EBUSY;
+       }
+
+       /* Ensure that the geometry has been set for the domain */
+       if (!dma_domain->geom_size) {
+               pr_debug("Please configure geometry before setting the number of windows\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return -EINVAL;
+       }
+
+       /*
+        * Ensure we have valid window count i.e. it should be less than
+        * maximum permissible limit and should be a power of two.
+        */
+       if (w_count > pamu_get_max_subwin_cnt() || !is_power_of_2(w_count)) {
+               pr_debug("Invalid window count\n");
+               spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+               return -EINVAL;
+       }
+
+       ret = pamu_set_domain_geometry(dma_domain, &domain->geometry,
+                               ((w_count > 1) ? w_count : 0));
+       if (!ret) {
+               if (dma_domain->win_arr)
+                       kfree(dma_domain->win_arr);
+               dma_domain->win_arr = kzalloc(sizeof(struct dma_window) *
+                                                         w_count, GFP_ATOMIC);
+               if (!dma_domain->win_arr) {
+                       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+                       return -ENOMEM;
+               }
+               dma_domain->win_cnt = w_count;
+       }
+       spin_unlock_irqrestore(&dma_domain->domain_lock, flags);
+
+       return ret;
+}
+
+static u32 fsl_pamu_get_windows(struct iommu_domain *domain)
+{
+       struct fsl_dma_domain *dma_domain = domain->priv;
+
+       return dma_domain->win_cnt;
+}
+
+static struct iommu_ops fsl_pamu_ops = {
+       .domain_init    = fsl_pamu_domain_init,
+       .domain_destroy = fsl_pamu_domain_destroy,
+       .attach_dev     = fsl_pamu_attach_device,
+       .detach_dev     = fsl_pamu_detach_device,
+       .domain_window_enable = fsl_pamu_window_enable,
+       .domain_window_disable = fsl_pamu_window_disable,
+       .domain_get_windows = fsl_pamu_get_windows,
+       .domain_set_windows = fsl_pamu_set_windows,
+       .iova_to_phys   = fsl_pamu_iova_to_phys,
+       .domain_has_cap = fsl_pamu_domain_has_cap,
+       .domain_set_attr = fsl_pamu_set_domain_attr,
+       .domain_get_attr = fsl_pamu_get_domain_attr,
+       .add_device     = fsl_pamu_add_device,
+       .remove_device  = fsl_pamu_remove_device,
+};
+
+int pamu_domain_init()
+{
+       int ret = 0;
+
+       ret = iommu_init_mempool();
+       if (ret)
+               return ret;
+
+       bus_set_iommu(&platform_bus_type, &fsl_pamu_ops);
+       bus_set_iommu(&pci_bus_type, &fsl_pamu_ops);
+
+       return ret;
+}
diff --git a/drivers/iommu/fsl_pamu_domain.h b/drivers/iommu/fsl_pamu_domain.h
new file mode 100644 (file)
index 0000000..c90293f
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ *
+ */
+
+#ifndef __FSL_PAMU_DOMAIN_H
+#define __FSL_PAMU_DOMAIN_H
+
+#include "fsl_pamu.h"
+
+struct dma_window {
+       phys_addr_t paddr;
+       u64 size;
+       int valid;
+       int prot;
+};
+
+struct fsl_dma_domain {
+       /*
+        * Indicates the geometry size for the domain.
+        * This would be set when the geometry is
+        * configured for the domain.
+        */
+       dma_addr_t                      geom_size;
+       /*
+        * Number of windows assocaited with this domain.
+        * During domain initialization, it is set to the
+        * the maximum number of subwindows allowed for a LIODN.
+        * Minimum value for this is 1 indicating a single PAMU
+        * window, without any sub windows. Value can be set/
+        * queried by set_attr/get_attr API for DOMAIN_ATTR_WINDOWS.
+        * Value can only be set once the geometry has been configured.
+        */
+       u32                             win_cnt;
+       /*
+        * win_arr contains information of the configured
+        * windows for a domain. This is allocated only
+        * when the number of windows for the domain are
+        * set.
+        */
+       struct dma_window               *win_arr;
+       /* list of devices associated with the domain */
+       struct list_head                devices;
+       /* dma_domain states:
+        * mapped - A particular mapping has been created
+        * within the configured geometry.
+        * enabled - DMA has been enabled for the given
+        * domain. This translates to setting of the
+        * valid bit for the primary PAACE in the PAMU
+        * PAACT table. Domain geometry should be set and
+        * it must have a valid mapping before DMA can be
+        * enabled for it.
+        *
+        */
+       int                             mapped;
+       int                             enabled;
+       /* stash_id obtained from the stash attribute details */
+       u32                             stash_id;
+       struct pamu_stash_attribute     dma_stash;
+       u32                             snoop_id;
+       struct iommu_domain             *iommu_domain;
+       spinlock_t                      domain_lock;
+};
+
+/* domain-device relationship */
+struct device_domain_info {
+       struct list_head link;  /* link to domain siblings */
+       struct device *dev;
+       u32 liodn;
+       struct fsl_dma_domain *domain; /* pointer to domain */
+};
+#endif  /* __FSL_PAMU_DOMAIN_H */
index eec0d3e04bf578ab6a6afe36e1c55ad5d4b0c969..15e9b57e9cf05ba43e19d76f37e64275d3e6db44 100644 (file)
@@ -890,56 +890,54 @@ static int dma_pte_clear_range(struct dmar_domain *domain,
        return order;
 }
 
+static void dma_pte_free_level(struct dmar_domain *domain, int level,
+                              struct dma_pte *pte, unsigned long pfn,
+                              unsigned long start_pfn, unsigned long last_pfn)
+{
+       pfn = max(start_pfn, pfn);
+       pte = &pte[pfn_level_offset(pfn, level)];
+
+       do {
+               unsigned long level_pfn;
+               struct dma_pte *level_pte;
+
+               if (!dma_pte_present(pte) || dma_pte_superpage(pte))
+                       goto next;
+
+               level_pfn = pfn & level_mask(level - 1);
+               level_pte = phys_to_virt(dma_pte_addr(pte));
+
+               if (level > 2)
+                       dma_pte_free_level(domain, level - 1, level_pte,
+                                          level_pfn, start_pfn, last_pfn);
+
+               /* If range covers entire pagetable, free it */
+               if (!(start_pfn > level_pfn ||
+                     last_pfn < level_pfn + level_size(level))) {
+                       dma_clear_pte(pte);
+                       domain_flush_cache(domain, pte, sizeof(*pte));
+                       free_pgtable_page(level_pte);
+               }
+next:
+               pfn += level_size(level);
+       } while (!first_pte_in_page(++pte) && pfn <= last_pfn);
+}
+
 /* free page table pages. last level pte should already be cleared */
 static void dma_pte_free_pagetable(struct dmar_domain *domain,
                                   unsigned long start_pfn,
                                   unsigned long last_pfn)
 {
        int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT;
-       struct dma_pte *first_pte, *pte;
-       int total = agaw_to_level(domain->agaw);
-       int level;
-       unsigned long tmp;
-       int large_page = 2;
 
        BUG_ON(addr_width < BITS_PER_LONG && start_pfn >> addr_width);
        BUG_ON(addr_width < BITS_PER_LONG && last_pfn >> addr_width);
        BUG_ON(start_pfn > last_pfn);
 
        /* We don't need lock here; nobody else touches the iova range */
-       level = 2;
-       while (level <= total) {
-               tmp = align_to_level(start_pfn, level);
-
-               /* If we can't even clear one PTE at this level, we're done */
-               if (tmp + level_size(level) - 1 > last_pfn)
-                       return;
-
-               do {
-                       large_page = level;
-                       first_pte = pte = dma_pfn_level_pte(domain, tmp, level, &large_page);
-                       if (large_page > level)
-                               level = large_page + 1;
-                       if (!pte) {
-                               tmp = align_to_level(tmp + 1, level + 1);
-                               continue;
-                       }
-                       do {
-                               if (dma_pte_present(pte)) {
-                                       free_pgtable_page(phys_to_virt(dma_pte_addr(pte)));
-                                       dma_clear_pte(pte);
-                               }
-                               pte++;
-                               tmp += level_size(level);
-                       } while (!first_pte_in_page(pte) &&
-                                tmp + level_size(level) - 1 <= last_pfn);
+       dma_pte_free_level(domain, agaw_to_level(domain->agaw),
+                          domain->pgd, 0, start_pfn, last_pfn);
 
-                       domain_flush_cache(domain, first_pte,
-                                          (void *)pte - (void *)first_pte);
-                       
-               } while (tmp && tmp + level_size(level) - 1 <= last_pfn);
-               level++;
-       }
        /* free pgd */
        if (start_pfn == 0 && last_pfn == DOMAIN_MAX_PFN(domain->gaw)) {
                free_pgtable_page(domain->pgd);
index 3aeb7305e2f59d3eae986c1c36d0af22cfea057b..7ea319e95b4771ecb9dc4be7d04ad24a8e628d86 100644 (file)
@@ -58,10 +58,26 @@ struct iommu_domain {
 #define IOMMU_CAP_CACHE_COHERENCY      0x1
 #define IOMMU_CAP_INTR_REMAP           0x2     /* isolates device intrs */
 
+/*
+ * Following constraints are specifc to FSL_PAMUV1:
+ *  -aperture must be power of 2, and naturally aligned
+ *  -number of windows must be power of 2, and address space size
+ *   of each window is determined by aperture size / # of windows
+ *  -the actual size of the mapped region of a window must be power
+ *   of 2 starting with 4KB and physical address must be naturally
+ *   aligned.
+ * DOMAIN_ATTR_FSL_PAMUV1 corresponds to the above mentioned contraints.
+ * The caller can invoke iommu_domain_get_attr to check if the underlying
+ * iommu implementation supports these constraints.
+ */
+
 enum iommu_attr {
        DOMAIN_ATTR_GEOMETRY,
        DOMAIN_ATTR_PAGING,
        DOMAIN_ATTR_WINDOWS,
+       DOMAIN_ATTR_FSL_PAMU_STASH,
+       DOMAIN_ATTR_FSL_PAMU_ENABLE,
+       DOMAIN_ATTR_FSL_PAMUV1,
        DOMAIN_ATTR_MAX,
 };