This patch is for ARM Short Descriptor Format.

Signed-off-by: Yong Wu <yong...@mediatek.com>
---
 drivers/iommu/Kconfig                |  18 +
 drivers/iommu/Makefile               |   1 +
 drivers/iommu/io-pgtable-arm-short.c | 813 +++++++++++++++++++++++++++++++++++
 drivers/iommu/io-pgtable-arm.c       |   3 -
 drivers/iommu/io-pgtable.c           |   4 +
 drivers/iommu/io-pgtable.h           |  14 +
 6 files changed, 850 insertions(+), 3 deletions(-)
 create mode 100644 drivers/iommu/io-pgtable-arm-short.c

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index f1fb1d3..3abd066 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -39,6 +39,24 @@ config IOMMU_IO_PGTABLE_LPAE_SELFTEST
 
          If unsure, say N here.
 
+config IOMMU_IO_PGTABLE_SHORT
+       bool "ARMv7/v8 Short Descriptor Format"
+       select IOMMU_IO_PGTABLE
+       depends on ARM || ARM64 || COMPILE_TEST
+       help
+         Enable support for the ARM Short-descriptor pagetable format.
+         This allocator supports 2 levels translation tables which supports
+         a memory map based on memory sections or pages.
+
+config IOMMU_IO_PGTABLE_SHORT_SELFTEST
+       bool "Short Descriptor selftests"
+       depends on IOMMU_IO_PGTABLE_SHORT
+       help
+         Enable self-tests for Short-descriptor page table allocator.
+         This performs a series of page-table consistency checks during boot.
+
+         If unsure, say N here.
+
 endmenu
 
 config IOMMU_IOVA
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index c6dcc51..06df3e6 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_IOMMU_API) += iommu-traces.o
 obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o
 obj-$(CONFIG_IOMMU_IO_PGTABLE) += io-pgtable.o
 obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o
+obj-$(CONFIG_IOMMU_IO_PGTABLE_SHORT) += io-pgtable-arm-short.o
 obj-$(CONFIG_IOMMU_IOVA) += iova.o
 obj-$(CONFIG_OF_IOMMU) += of_iommu.o
 obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
diff --git a/drivers/iommu/io-pgtable-arm-short.c 
b/drivers/iommu/io-pgtable-arm-short.c
new file mode 100644
index 0000000..56f5480
--- /dev/null
+++ b/drivers/iommu/io-pgtable-arm-short.c
@@ -0,0 +1,813 @@
+/*
+ * Copyright (c) 2014-2015 MediaTek Inc.
+ * Author: Yong Wu <yong...@mediatek.com>
+ *
+ * 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.
+ */
+#define pr_fmt(fmt)    "arm-short-desc io-pgtable: "fmt
+
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/iommu.h>
+#include <linux/errno.h>
+#include "io-pgtable.h"
+
+typedef u32 arm_short_iopte;
+
+struct arm_short_io_pgtable {
+       struct io_pgtable       iop;
+       struct kmem_cache       *pgtable_cached;
+       size_t                  pgd_size;
+       void                    *pgd;
+};
+
+#define io_pgtable_to_data(x)                  \
+       container_of((x), struct arm_short_io_pgtable, iop)
+
+#define io_pgtable_ops_to_data(x)              \
+       io_pgtable_to_data(io_pgtable_ops_to_pgtable(x))
+
+#define io_pgtable_cfg_to_pgtable(x)           \
+       container_of((x), struct io_pgtable, cfg)
+
+#define io_pgtable_cfg_to_data(x)              \
+       io_pgtable_to_data(io_pgtable_cfg_to_pgtable(x))
+
+#define ARM_SHORT_PGDIR_SHIFT                  20
+#define ARM_SHORT_PAGE_SHIFT                   12
+#define ARM_SHORT_PTRS_PER_PTE                 \
+       (1 << (ARM_SHORT_PGDIR_SHIFT - ARM_SHORT_PAGE_SHIFT))
+#define ARM_SHORT_BYTES_PER_PTE                        \
+       (ARM_SHORT_PTRS_PER_PTE * sizeof(arm_short_iopte))
+
+/* level 1 pagetable */
+#define ARM_SHORT_PGD_TYPE_PGTABLE             BIT(0)
+#define ARM_SHORT_PGD_TYPE_SECTION             BIT(1)
+#define ARM_SHORT_PGD_B                                BIT(2)
+#define ARM_SHORT_PGD_C                                BIT(3)
+#define ARM_SHORT_PGD_PGTABLE_NS               BIT(3)
+#define ARM_SHORT_PGD_SECTION_XN               BIT(4)
+#define ARM_SHORT_PGD_IMPLE                    BIT(9)
+#define ARM_SHORT_PGD_RD_WR                    (3 << 10)
+#define ARM_SHORT_PGD_RDONLY                   BIT(15)
+#define ARM_SHORT_PGD_S                                BIT(16)
+#define ARM_SHORT_PGD_nG                       BIT(17)
+#define ARM_SHORT_PGD_SUPERSECTION             BIT(18)
+#define ARM_SHORT_PGD_SECTION_NS               BIT(19)
+
+#define ARM_SHORT_PGD_TYPE_SUPERSECTION                \
+       (ARM_SHORT_PGD_TYPE_SECTION | ARM_SHORT_PGD_SUPERSECTION)
+#define ARM_SHORT_PGD_SECTION_TYPE_MSK         \
+       (ARM_SHORT_PGD_TYPE_SECTION | ARM_SHORT_PGD_SUPERSECTION)
+#define ARM_SHORT_PGD_PGTABLE_TYPE_MSK         \
+       (ARM_SHORT_PGD_TYPE_SECTION | ARM_SHORT_PGD_TYPE_PGTABLE)
+#define ARM_SHORT_PGD_TYPE_IS_PGTABLE(pgd)     \
+       (((pgd) & ARM_SHORT_PGD_PGTABLE_TYPE_MSK) == ARM_SHORT_PGD_TYPE_PGTABLE)
+#define ARM_SHORT_PGD_TYPE_IS_SECTION(pgd)     \
+       (((pgd) & ARM_SHORT_PGD_SECTION_TYPE_MSK) == ARM_SHORT_PGD_TYPE_SECTION)
+#define ARM_SHORT_PGD_TYPE_IS_SUPERSECTION(pgd)        \
+       (((pgd) & ARM_SHORT_PGD_SECTION_TYPE_MSK) == \
+       ARM_SHORT_PGD_TYPE_SUPERSECTION)
+#define ARM_SHORT_PGD_PGTABLE_MSK              0xfffffc00
+#define ARM_SHORT_PGD_SECTION_MSK              (~(SZ_1M - 1))
+#define ARM_SHORT_PGD_SUPERSECTION_MSK         (~(SZ_16M - 1))
+
+/* level 2 pagetable */
+#define ARM_SHORT_PTE_TYPE_LARGE               BIT(0)
+#define ARM_SHORT_PTE_SMALL_XN                 BIT(0)
+#define ARM_SHORT_PTE_TYPE_SMALL               BIT(1)
+#define ARM_SHORT_PTE_B                                BIT(2)
+#define ARM_SHORT_PTE_C                                BIT(3)
+#define ARM_SHORT_PTE_RD_WR                    (3 << 4)
+#define ARM_SHORT_PTE_RDONLY                   BIT(9)
+#define ARM_SHORT_PTE_S                                BIT(10)
+#define ARM_SHORT_PTE_nG                       BIT(11)
+#define ARM_SHORT_PTE_LARGE_XN                 BIT(15)
+#define ARM_SHORT_PTE_LARGE_MSK                        (~(SZ_64K - 1))
+#define ARM_SHORT_PTE_SMALL_MSK                        (~(SZ_4K - 1))
+#define ARM_SHORT_PTE_TYPE_MSK                 \
+       (ARM_SHORT_PTE_TYPE_LARGE | ARM_SHORT_PTE_TYPE_SMALL)
+#define ARM_SHORT_PTE_TYPE_IS_SMALLPAGE(pte)   \
+       (((pte) & ARM_SHORT_PTE_TYPE_SMALL) == ARM_SHORT_PTE_TYPE_SMALL)
+#define ARM_SHORT_PTE_TYPE_IS_LARGEPAGE(pte)   \
+       (((pte) & ARM_SHORT_PTE_TYPE_MSK) == ARM_SHORT_PTE_TYPE_LARGE)
+
+#define ARM_SHORT_PGD_IDX(a)                   ((a) >> ARM_SHORT_PGDIR_SHIFT)
+#define ARM_SHORT_PTE_IDX(a)                   \
+       (((a) >> ARM_SHORT_PAGE_SHIFT) & (ARM_SHORT_PTRS_PER_PTE - 1))
+
+#define ARM_SHORT_GET_PGTABLE_VA(pgd)          \
+       (phys_to_virt((unsigned long)pgd & ARM_SHORT_PGD_PGTABLE_MSK))
+
+#define ARM_SHORT_PTE_LARGE_GET_PROT(pte)      \
+       (((pte) & (~ARM_SHORT_PTE_LARGE_MSK)) & ~ARM_SHORT_PTE_TYPE_MSK)
+
+#define ARM_SHORT_PGD_GET_PROT(pgd)            \
+       (((pgd) & (~ARM_SHORT_PGD_SECTION_MSK)) & ~ARM_SHORT_PGD_SUPERSECTION)
+
+static bool selftest_running;
+
+static arm_short_iopte *
+arm_short_get_pte_in_pgd(arm_short_iopte pgd, unsigned int iova)
+{
+       arm_short_iopte *pte;
+
+       pte = ARM_SHORT_GET_PGTABLE_VA(pgd);
+       pte += ARM_SHORT_PTE_IDX(iova);
+       return pte;
+}
+
+static dma_addr_t
+__arm_short_dma_addr(struct device *dev, void *va)
+{
+       return phys_to_dma(dev, virt_to_phys(va));
+}
+
+static int
+__arm_short_set_pte(arm_short_iopte *ptep, arm_short_iopte pte,
+                   unsigned int ptenr, struct io_pgtable_cfg *cfg)
+{
+       struct device *dev = cfg->iommu_dev;
+       int i;
+
+       for (i = 0; i < ptenr; i++) {
+               if (ptep[i] && pte) {
+                       /* Someone else may have allocated for this pte */
+                       WARN_ON(!selftest_running);
+                       goto err_exist_pte;
+               }
+               ptep[i] = pte;
+       }
+
+       if (selftest_running)
+               return 0;
+
+       dma_sync_single_for_device(dev, __arm_short_dma_addr(dev, ptep),
+                                  sizeof(*ptep) * ptenr, DMA_TO_DEVICE);
+       return 0;
+
+err_exist_pte:
+       while (i--)
+               ptep[i] = 0;
+       return -EEXIST;
+}
+
+static void *
+__arm_short_alloc_pgtable(size_t size, gfp_t gfp, bool pgd,
+                         struct io_pgtable_cfg *cfg)
+{
+       struct arm_short_io_pgtable *data;
+       struct device *dev = cfg->iommu_dev;
+       dma_addr_t dma;
+       void *va;
+
+       if (pgd) {/* lvl1 pagetable */
+               va = alloc_pages_exact(size, gfp);
+       } else {  /* lvl2 pagetable */
+               data = io_pgtable_cfg_to_data(cfg);
+               va = kmem_cache_zalloc(data->pgtable_cached, gfp);
+       }
+
+       if (!va)
+               return NULL;
+
+       if (selftest_running)
+               return va;
+
+       dma = dma_map_single(dev, va, size, DMA_TO_DEVICE);
+       if (dma_mapping_error(dev, dma))
+               goto out_free;
+
+       if (dma != __arm_short_dma_addr(dev, va))
+               goto out_unmap;
+
+       if (!pgd) {
+               kmemleak_ignore(va);
+               dma_sync_single_for_device(dev, __arm_short_dma_addr(dev, va),
+                                          size, DMA_TO_DEVICE);
+       }
+
+       return va;
+
+out_unmap:
+       dev_err_ratelimited(dev, "Cannot accommodate DMA translation for IOMMU 
page tables\n");
+       dma_unmap_single(dev, dma, size, DMA_TO_DEVICE);
+out_free:
+       if (pgd)
+               free_pages_exact(va, size);
+       else
+               kmem_cache_free(data->pgtable_cached, va);
+       return NULL;
+}
+
+static void
+__arm_short_free_pgtable(void *va, size_t size, bool pgd,
+                        struct io_pgtable_cfg *cfg)
+{
+       struct arm_short_io_pgtable *data = io_pgtable_cfg_to_data(cfg);
+       struct device *dev = cfg->iommu_dev;
+
+       if (!selftest_running)
+               dma_unmap_single(dev, __arm_short_dma_addr(dev, va),
+                                size, DMA_TO_DEVICE);
+
+       if (pgd)
+               free_pages_exact(va, size);
+       else
+               kmem_cache_free(data->pgtable_cached, va);
+}
+
+static arm_short_iopte
+__arm_short_pte_prot(struct arm_short_io_pgtable *data, int prot, bool large)
+{
+       arm_short_iopte pteprot;
+       int quirk = data->iop.cfg.quirks;
+
+       pteprot = ARM_SHORT_PTE_S | ARM_SHORT_PTE_nG;
+       pteprot |= large ? ARM_SHORT_PTE_TYPE_LARGE :
+                               ARM_SHORT_PTE_TYPE_SMALL;
+       if (prot & IOMMU_CACHE)
+               pteprot |=  ARM_SHORT_PTE_B | ARM_SHORT_PTE_C;
+       if (!(quirk & IO_PGTABLE_QUIRK_SHORT_NO_XN) && (prot & IOMMU_NOEXEC)) {
+                       pteprot |= large ? ARM_SHORT_PTE_LARGE_XN :
+                               ARM_SHORT_PTE_SMALL_XN;
+       }
+       if (!(quirk & IO_PGTABLE_QUIRK_SHORT_NO_PERMS)) {
+               pteprot |= ARM_SHORT_PTE_RD_WR;
+               if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ))
+                       pteprot |= ARM_SHORT_PTE_RDONLY;
+       }
+       return pteprot;
+}
+
+static arm_short_iopte
+__arm_short_pgd_prot(struct arm_short_io_pgtable *data, int prot, bool super)
+{
+       arm_short_iopte pgdprot;
+       int quirk = data->iop.cfg.quirks;
+
+       pgdprot = ARM_SHORT_PGD_S | ARM_SHORT_PGD_nG;
+       pgdprot |= super ? ARM_SHORT_PGD_TYPE_SUPERSECTION :
+                               ARM_SHORT_PGD_TYPE_SECTION;
+       if (prot & IOMMU_CACHE)
+               pgdprot |= ARM_SHORT_PGD_C | ARM_SHORT_PGD_B;
+       if (quirk & IO_PGTABLE_QUIRK_ARM_NS)
+               pgdprot |= ARM_SHORT_PGD_SECTION_NS;
+
+       if (!(quirk & IO_PGTABLE_QUIRK_SHORT_NO_XN) && (prot & IOMMU_NOEXEC))
+                       pgdprot |= ARM_SHORT_PGD_SECTION_XN;
+
+       if (!(quirk & IO_PGTABLE_QUIRK_SHORT_NO_PERMS)) {
+               pgdprot |= ARM_SHORT_PGD_RD_WR;
+               if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ))
+                       pgdprot |= ARM_SHORT_PGD_RDONLY;
+       }
+       return pgdprot;
+}
+
+static arm_short_iopte
+__arm_short_pte_prot_split(struct arm_short_io_pgtable *data,
+                          arm_short_iopte pgdprot,
+                          arm_short_iopte pteprot_large,
+                          bool large)
+{
+       arm_short_iopte pteprot = 0;
+
+       pteprot = ARM_SHORT_PTE_S | ARM_SHORT_PTE_nG | ARM_SHORT_PTE_RD_WR;
+       pteprot |= large ? ARM_SHORT_PTE_TYPE_LARGE :
+                               ARM_SHORT_PTE_TYPE_SMALL;
+
+       /* large page to small page pte prot. Only large page may split */
+       if (!pgdprot && !large) {
+               pteprot |= pteprot_large & ~ARM_SHORT_PTE_SMALL_MSK;
+               if (pteprot_large & ARM_SHORT_PTE_LARGE_XN)
+                       pteprot |= ARM_SHORT_PTE_SMALL_XN;
+       }
+
+       /* section to pte prot */
+       if (pgdprot & ARM_SHORT_PGD_C)
+               pteprot |= ARM_SHORT_PTE_C;
+       if (pgdprot & ARM_SHORT_PGD_B)
+               pteprot |= ARM_SHORT_PTE_B;
+       if (pgdprot & ARM_SHORT_PGD_nG)
+               pteprot |= ARM_SHORT_PTE_nG;
+       if (pgdprot & ARM_SHORT_PGD_SECTION_XN)
+               pteprot |= large ? ARM_SHORT_PTE_LARGE_XN :
+                               ARM_SHORT_PTE_SMALL_XN;
+       if (pgdprot & ARM_SHORT_PGD_RD_WR)
+               pteprot |= ARM_SHORT_PTE_RD_WR;
+       if (pgdprot & ARM_SHORT_PGD_RDONLY)
+               pteprot |= ARM_SHORT_PTE_RDONLY;
+
+       return pteprot;
+}
+
+static arm_short_iopte
+__arm_short_pgtable_prot(struct arm_short_io_pgtable *data)
+{
+       arm_short_iopte pgdprot = 0;
+
+       pgdprot = ARM_SHORT_PGD_TYPE_PGTABLE;
+       if (data->iop.cfg.quirks & IO_PGTABLE_QUIRK_ARM_NS)
+               pgdprot |= ARM_SHORT_PGD_PGTABLE_NS;
+       return pgdprot;
+}
+
+static int
+_arm_short_map(struct arm_short_io_pgtable *data,
+              unsigned int iova, phys_addr_t paddr,
+              arm_short_iopte pgdprot, arm_short_iopte pteprot,
+              bool large)
+{
+       struct io_pgtable_cfg *cfg = &data->iop.cfg;
+       arm_short_iopte *pgd = data->pgd, *pte;
+       void *pte_new = NULL;
+       int ret;
+
+       pgd += ARM_SHORT_PGD_IDX(iova);
+
+       if (!pteprot) { /* section or supersection */
+               pte = pgd;
+               pteprot = pgdprot;
+       } else {        /* page or largepage */
+               if (!(*pgd)) {
+                       pte_new = __arm_short_alloc_pgtable(
+                                       ARM_SHORT_BYTES_PER_PTE,
+                                       GFP_ATOMIC, false, cfg);
+                       if (unlikely(!pte_new))
+                               return -ENOMEM;
+
+                       pgdprot |= virt_to_phys(pte_new);
+                       __arm_short_set_pte(pgd, pgdprot, 1, cfg);
+               }
+               pte = arm_short_get_pte_in_pgd(*pgd, iova);
+       }
+
+       pteprot |= (arm_short_iopte)paddr;
+       ret = __arm_short_set_pte(pte, pteprot, large ? 16 : 1, cfg);
+       if (ret && pte_new)
+               __arm_short_free_pgtable(pte_new, ARM_SHORT_BYTES_PER_PTE,
+                                        false, cfg);
+       return ret;
+}
+
+static int arm_short_map(struct io_pgtable_ops *ops, unsigned long iova,
+                        phys_addr_t paddr, size_t size, int prot)
+{
+       struct arm_short_io_pgtable *data = io_pgtable_ops_to_data(ops);
+       arm_short_iopte pgdprot = 0, pteprot = 0;
+       bool large;
+
+       /* If no access, then nothing to do */
+       if (!(prot & (IOMMU_READ | IOMMU_WRITE)))
+               return 0;
+
+       if (WARN_ON((iova | paddr) & (size - 1)))
+               return -EINVAL;
+
+       switch (size) {
+       case SZ_4K:
+       case SZ_64K:
+               large = (size == SZ_64K) ? true : false;
+               pteprot = __arm_short_pte_prot(data, prot, large);
+               pgdprot = __arm_short_pgtable_prot(data);
+               break;
+
+       case SZ_1M:
+       case SZ_16M:
+               large = (size == SZ_16M) ? true : false;
+               pgdprot = __arm_short_pgd_prot(data, prot, large);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return _arm_short_map(data, iova, paddr, pgdprot, pteprot, large);
+}
+
+static phys_addr_t arm_short_iova_to_phys(struct io_pgtable_ops *ops,
+                                         unsigned long iova)
+{
+       struct arm_short_io_pgtable *data = io_pgtable_ops_to_data(ops);
+       arm_short_iopte *pte, *pgd = data->pgd;
+       phys_addr_t pa = 0;
+
+       pgd += ARM_SHORT_PGD_IDX(iova);
+
+       if (ARM_SHORT_PGD_TYPE_IS_PGTABLE(*pgd)) {
+               pte = arm_short_get_pte_in_pgd(*pgd, iova);
+
+               if (ARM_SHORT_PTE_TYPE_IS_LARGEPAGE(*pte)) {
+                       pa = (*pte) & ARM_SHORT_PTE_LARGE_MSK;
+                       pa |= iova & ~ARM_SHORT_PTE_LARGE_MSK;
+               } else if (ARM_SHORT_PTE_TYPE_IS_SMALLPAGE(*pte)) {
+                       pa = (*pte) & ARM_SHORT_PTE_SMALL_MSK;
+                       pa |= iova & ~ARM_SHORT_PTE_SMALL_MSK;
+               }
+       } else if (ARM_SHORT_PGD_TYPE_IS_SECTION(*pgd)) {
+               pa = (*pgd) & ARM_SHORT_PGD_SECTION_MSK;
+               pa |= iova & ~ARM_SHORT_PGD_SECTION_MSK;
+       } else if (ARM_SHORT_PGD_TYPE_IS_SUPERSECTION(*pgd)) {
+               pa = (*pgd) & ARM_SHORT_PGD_SUPERSECTION_MSK;
+               pa |= iova & ~ARM_SHORT_PGD_SUPERSECTION_MSK;
+       }
+
+       return pa;
+}
+
+static bool _arm_short_whether_free_pgtable(arm_short_iopte *pgd)
+{
+       arm_short_iopte *pte;
+       int i;
+
+       pte = ARM_SHORT_GET_PGTABLE_VA(*pgd);
+       for (i = 0; i < ARM_SHORT_PTRS_PER_PTE; i++) {
+               if (pte[i] != 0)
+                       return false;
+       }
+
+       return true;
+}
+
+static int
+arm_short_split_blk_unmap(struct io_pgtable_ops *ops, unsigned int iova,
+                         phys_addr_t paddr, size_t size,
+                         arm_short_iopte pgdprotup, arm_short_iopte pteprotup,
+                         size_t blk_size)
+{
+       struct arm_short_io_pgtable *data = io_pgtable_ops_to_data(ops);
+       const struct iommu_gather_ops *tlb = data->iop.cfg.tlb;
+       struct io_pgtable_cfg *cfg = &data->iop.cfg;
+       unsigned long *pgbitmap = &cfg->pgsize_bitmap;
+       unsigned int blk_base, blk_start, blk_end, i;
+       arm_short_iopte pgdprot, pteprot;
+       phys_addr_t blk_paddr;
+       size_t mapsize = 0, nextmapsize;
+       int ret;
+
+       /* find the nearest mapsize */
+       for (i = find_first_bit(pgbitmap, BITS_PER_LONG);
+            i < BITS_PER_LONG && ((1 << i) < blk_size) &&
+            IS_ALIGNED(size, 1 << i);
+            i = find_next_bit(pgbitmap, BITS_PER_LONG, i + 1))
+               mapsize = 1 << i;
+
+       if (WARN_ON(!mapsize))
+               return 0; /* Bytes unmapped */
+       nextmapsize = 1 << i;
+
+       blk_base = iova & ~(blk_size - 1);
+       blk_start = blk_base;
+       blk_end = blk_start + blk_size;
+       blk_paddr = paddr;
+
+       for (; blk_start < blk_end;
+            blk_start += mapsize, blk_paddr += mapsize) {
+               /* Unmap! */
+               if (blk_start == iova)
+                       continue;
+
+               /* Try to upper map */
+               if (blk_base != blk_start &&
+                   IS_ALIGNED(blk_start | blk_paddr, nextmapsize) &&
+                   mapsize != nextmapsize) {
+                       mapsize = nextmapsize;
+                       i = find_next_bit(pgbitmap, BITS_PER_LONG, i + 1);
+                       if (i < BITS_PER_LONG)
+                               nextmapsize = 1 << i;
+               }
+
+               if (mapsize == SZ_1M) {
+                       pgdprot = pgdprotup;
+                       pgdprot |= __arm_short_pgd_prot(data, 0, false);
+                       pteprot = 0;
+               } else { /* small or large page */
+                       pgdprot = (blk_size == SZ_64K) ? 0 : pgdprotup;
+                       pteprot = __arm_short_pte_prot_split(
+                                       data, pgdprot, pteprotup,
+                                       mapsize == SZ_64K);
+                       pgdprot = __arm_short_pgtable_prot(data);
+               }
+
+               ret = _arm_short_map(data, blk_start, blk_paddr, pgdprot,
+                                    pteprot, mapsize == SZ_64K);
+               if (ret < 0) {
+                       /* Free the table we allocated */
+                       arm_short_iopte *pgd = data->pgd, *pte;
+
+                       pgd += ARM_SHORT_PGD_IDX(blk_base);
+                       if (*pgd) {
+                               pte = ARM_SHORT_GET_PGTABLE_VA(*pgd);
+                               __arm_short_set_pte(pgd, 0, 1, cfg);
+                               tlb->tlb_add_flush(blk_base, blk_size, true,
+                                                  data->iop.cookie);
+                               tlb->tlb_sync(data->iop.cookie);
+                               __arm_short_free_pgtable(
+                                       pte, ARM_SHORT_BYTES_PER_PTE,
+                                       false, cfg);
+                       }
+                       return 0;/* Bytes unmapped */
+               }
+       }
+
+       tlb->tlb_add_flush(blk_base, blk_size, true, data->iop.cookie);
+       tlb->tlb_sync(data->iop.cookie);
+       return size;
+}
+
+static int arm_short_unmap(struct io_pgtable_ops *ops,
+                          unsigned long iova,
+                          size_t size)
+{
+       struct arm_short_io_pgtable *data = io_pgtable_ops_to_data(ops);
+       struct io_pgtable_cfg *cfg = &data->iop.cfg;
+       arm_short_iopte *pgd, *pte = NULL;
+       arm_short_iopte curpgd, curpte = 0;
+       phys_addr_t paddr;
+       unsigned int iova_base, blk_size = 0;
+       void *cookie = data->iop.cookie;
+       bool pgtablefree = false;
+
+       pgd = (arm_short_iopte *)data->pgd + ARM_SHORT_PGD_IDX(iova);
+
+       /* Get block size */
+       if (ARM_SHORT_PGD_TYPE_IS_PGTABLE(*pgd)) {
+               pte = arm_short_get_pte_in_pgd(*pgd, iova);
+
+               if (ARM_SHORT_PTE_TYPE_IS_SMALLPAGE(*pte))
+                       blk_size = SZ_4K;
+               else if (ARM_SHORT_PTE_TYPE_IS_LARGEPAGE(*pte))
+                       blk_size = SZ_64K;
+               else
+                       WARN_ON(1);
+       } else if (ARM_SHORT_PGD_TYPE_IS_SECTION(*pgd)) {
+               blk_size = SZ_1M;
+       } else if (ARM_SHORT_PGD_TYPE_IS_SUPERSECTION(*pgd)) {
+               blk_size = SZ_16M;
+       } else {
+               WARN_ON(1);
+       }
+
+       iova_base = iova & ~(blk_size - 1);
+       pgd = (arm_short_iopte *)data->pgd + ARM_SHORT_PGD_IDX(iova_base);
+       paddr = arm_short_iova_to_phys(ops, iova_base);
+       curpgd = *pgd;
+
+       if (blk_size == SZ_4K || blk_size == SZ_64K) {
+               pte = arm_short_get_pte_in_pgd(*pgd, iova_base);
+               curpte = *pte;
+               __arm_short_set_pte(pte, 0, blk_size / SZ_4K, cfg);
+
+               pgtablefree = _arm_short_whether_free_pgtable(pgd);
+               if (pgtablefree)
+                       __arm_short_set_pte(pgd, 0, 1, cfg);
+       } else if (blk_size == SZ_1M || blk_size == SZ_16M) {
+               __arm_short_set_pte(pgd, 0, blk_size / SZ_1M, cfg);
+       }
+
+       cfg->tlb->tlb_add_flush(iova_base, blk_size, true, cookie);
+       cfg->tlb->tlb_sync(cookie);
+
+       if (pgtablefree)/* Free pgtable after tlb-flush */
+               __arm_short_free_pgtable(ARM_SHORT_GET_PGTABLE_VA(curpgd),
+                                        ARM_SHORT_BYTES_PER_PTE, false, cfg);
+
+       if (blk_size > size) { /* Split the block */
+               return arm_short_split_blk_unmap(
+                               ops, iova, paddr, size,
+                               ARM_SHORT_PGD_GET_PROT(curpgd),
+                               ARM_SHORT_PTE_LARGE_GET_PROT(curpte),
+                               blk_size);
+       } else if (blk_size < size) {
+               /* Unmap the block while remap partial again after split */
+               return blk_size +
+                       arm_short_unmap(ops, iova + blk_size, size - blk_size);
+       }
+
+       return size;
+}
+
+static struct io_pgtable *
+arm_short_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
+{
+       struct arm_short_io_pgtable *data;
+
+       if (cfg->ias > 32 || cfg->oas > 32)
+               return NULL;
+
+       cfg->pgsize_bitmap &=
+               (cfg->quirks & IO_PGTABLE_QUIRK_SHORT_SUPERSECTION) ?
+               (SZ_4K | SZ_64K | SZ_1M | SZ_16M) : (SZ_4K | SZ_64K | SZ_1M);
+
+       data = kzalloc(sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return NULL;
+
+       data->pgd_size = SZ_16K;
+       data->pgd = __arm_short_alloc_pgtable(
+                                       data->pgd_size,
+                                       GFP_KERNEL | __GFP_ZERO | __GFP_DMA,
+                                       true, cfg);
+       if (!data->pgd)
+               goto out_free_data;
+       wmb();/* Ensure the empty pgd is visible before any actual TTBR write */
+
+       data->pgtable_cached = kmem_cache_create(
+                                       "io-pgtable-arm-short",
+                                        ARM_SHORT_BYTES_PER_PTE,
+                                        ARM_SHORT_BYTES_PER_PTE,
+                                        0, NULL);
+       if (!data->pgtable_cached)
+               goto out_free_pgd;
+
+       /* TTBRs */
+       cfg->arm_short_cfg.ttbr[0] = virt_to_phys(data->pgd);
+       cfg->arm_short_cfg.ttbr[1] = 0;
+       cfg->arm_short_cfg.tcr = 0;
+       cfg->arm_short_cfg.nmrr = 0;
+       cfg->arm_short_cfg.prrr = 0;
+
+       data->iop.ops = (struct io_pgtable_ops) {
+               .map            = arm_short_map,
+               .unmap          = arm_short_unmap,
+               .iova_to_phys   = arm_short_iova_to_phys,
+       };
+
+       return &data->iop;
+
+out_free_pgd:
+       __arm_short_free_pgtable(data->pgd, data->pgd_size, true, cfg);
+out_free_data:
+       kfree(data);
+       return NULL;
+}
+
+static void arm_short_free_pgtable(struct io_pgtable *iop)
+{
+       struct arm_short_io_pgtable *data = io_pgtable_to_data(iop);
+
+       kmem_cache_destroy(data->pgtable_cached);
+       __arm_short_free_pgtable(data->pgd, data->pgd_size,
+                                true, &data->iop.cfg);
+       kfree(data);
+}
+
+struct io_pgtable_init_fns io_pgtable_arm_short_init_fns = {
+       .alloc  = arm_short_alloc_pgtable,
+       .free   = arm_short_free_pgtable,
+};
+
+#ifdef CONFIG_IOMMU_IO_PGTABLE_SHORT_SELFTEST
+
+static struct io_pgtable_cfg *cfg_cookie;
+
+static void dummy_tlb_flush_all(void *cookie)
+{
+       WARN_ON(cookie != cfg_cookie);
+}
+
+static void dummy_tlb_add_flush(unsigned long iova, size_t size, bool leaf,
+                               void *cookie)
+{
+       WARN_ON(cookie != cfg_cookie);
+       WARN_ON(!(size & cfg_cookie->pgsize_bitmap));
+}
+
+static void dummy_tlb_sync(void *cookie)
+{
+       WARN_ON(cookie != cfg_cookie);
+}
+
+static struct iommu_gather_ops dummy_tlb_ops = {
+       .tlb_flush_all  = dummy_tlb_flush_all,
+       .tlb_add_flush  = dummy_tlb_add_flush,
+       .tlb_sync       = dummy_tlb_sync,
+};
+
+#define __FAIL(ops)    ({                              \
+               WARN(1, "selftest: test failed\n");     \
+               selftest_running = false;               \
+               -EFAULT;                                \
+})
+
+static int __init arm_short_do_selftests(void)
+{
+       struct io_pgtable_ops *ops;
+       struct io_pgtable_cfg cfg = {
+               .tlb = &dummy_tlb_ops,
+               .oas = 32,
+               .ias = 32,
+               .quirks = IO_PGTABLE_QUIRK_ARM_NS |
+                       IO_PGTABLE_QUIRK_SHORT_SUPERSECTION,
+               .pgsize_bitmap = SZ_4K | SZ_64K | SZ_1M | SZ_16M,
+       };
+       unsigned int iova, size, iova_start;
+       unsigned int i, loopnr = 0;
+
+       selftest_running = true;
+
+       cfg_cookie = &cfg;
+
+       ops = alloc_io_pgtable_ops(ARM_SHORT_DESC, &cfg, &cfg);
+       if (!ops) {
+               pr_err("Failed to alloc short desc io pgtable\n");
+               return -EINVAL;
+       }
+
+       /*
+        * Initial sanity checks.
+        * Empty page tables shouldn't provide any translations.
+        */
+       if (ops->iova_to_phys(ops, 42))
+               return __FAIL(ops);
+
+       if (ops->iova_to_phys(ops, SZ_1G + 42))
+               return __FAIL(ops);
+
+       if (ops->iova_to_phys(ops, SZ_2G + 42))
+               return __FAIL(ops);
+
+       /*
+        * Distinct mappings of different granule sizes.
+        */
+       iova = 0;
+       i = find_first_bit(&cfg.pgsize_bitmap, BITS_PER_LONG);
+       while (i != BITS_PER_LONG) {
+               size = 1UL << i;
+               if (ops->map(ops, iova, iova, size, IOMMU_READ |
+                                                   IOMMU_WRITE |
+                                                   IOMMU_NOEXEC |
+                                                   IOMMU_CACHE))
+                       return __FAIL(ops);
+
+               /* Overlapping mappings */
+               if (!ops->map(ops, iova, iova + size, size,
+                             IOMMU_READ | IOMMU_NOEXEC))
+                       return __FAIL(ops);
+
+               if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+                       return __FAIL(ops);
+
+               iova += SZ_16M;
+               i++;
+               i = find_next_bit(&cfg.pgsize_bitmap, BITS_PER_LONG, i);
+               loopnr++;
+       }
+
+       /* Partial unmap */
+       i = 1;
+       size = 1UL << __ffs(cfg.pgsize_bitmap);
+       while (i < loopnr) {
+               iova_start = i * SZ_16M;
+               if (ops->unmap(ops, iova_start + size, size) != size)
+                       return __FAIL(ops);
+
+               /* Remap of partial unmap */
+               if (ops->map(ops, iova_start + size, size, size, IOMMU_READ))
+                       return __FAIL(ops);
+
+               if (ops->iova_to_phys(ops, iova_start + size + 42)
+                   != (size + 42))
+                       return __FAIL(ops);
+               i++;
+       }
+
+       /* Full unmap */
+       iova = 0;
+       i = find_first_bit(&cfg.pgsize_bitmap, BITS_PER_LONG);
+       while (i != BITS_PER_LONG) {
+               size = 1UL << i;
+
+               if (ops->unmap(ops, iova, size) != size)
+                       return __FAIL(ops);
+
+               if (ops->iova_to_phys(ops, iova + 42))
+                       return __FAIL(ops);
+
+               /* Remap full block */
+               if (ops->map(ops, iova, iova, size, IOMMU_WRITE))
+                       return __FAIL(ops);
+
+               if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
+                       return __FAIL(ops);
+
+               iova += SZ_16M;
+               i++;
+               i = find_next_bit(&cfg.pgsize_bitmap, BITS_PER_LONG, i);
+       }
+
+       free_io_pgtable_ops(ops);
+
+       selftest_running = false;
+       return 0;
+}
+
+subsys_initcall(arm_short_do_selftests);
+#endif
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index e4bc2b2..9978eca 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -38,9 +38,6 @@
 #define io_pgtable_to_data(x)                                          \
        container_of((x), struct arm_lpae_io_pgtable, iop)
 
-#define io_pgtable_ops_to_pgtable(x)                                   \
-       container_of((x), struct io_pgtable, ops)
-
 #define io_pgtable_ops_to_data(x)                                      \
        io_pgtable_to_data(io_pgtable_ops_to_pgtable(x))
 
diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c
index 6436fe2..14a9b3a 100644
--- a/drivers/iommu/io-pgtable.c
+++ b/drivers/iommu/io-pgtable.c
@@ -28,6 +28,7 @@ extern struct io_pgtable_init_fns 
io_pgtable_arm_32_lpae_s1_init_fns;
 extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns;
 extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns;
 extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns;
+extern struct io_pgtable_init_fns io_pgtable_arm_short_init_fns;
 
 static const struct io_pgtable_init_fns *
 io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] =
@@ -38,6 +39,9 @@ io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] =
        [ARM_64_LPAE_S1] = &io_pgtable_arm_64_lpae_s1_init_fns,
        [ARM_64_LPAE_S2] = &io_pgtable_arm_64_lpae_s2_init_fns,
 #endif
+#ifdef CONFIG_IOMMU_IO_PGTABLE_SHORT
+       [ARM_SHORT_DESC] = &io_pgtable_arm_short_init_fns,
+#endif
 };
 
 struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt,
diff --git a/drivers/iommu/io-pgtable.h b/drivers/iommu/io-pgtable.h
index 68c63d9..0f45e60 100644
--- a/drivers/iommu/io-pgtable.h
+++ b/drivers/iommu/io-pgtable.h
@@ -9,6 +9,7 @@ enum io_pgtable_fmt {
        ARM_32_LPAE_S2,
        ARM_64_LPAE_S1,
        ARM_64_LPAE_S2,
+       ARM_SHORT_DESC,
        IO_PGTABLE_NUM_FMTS,
 };
 
@@ -45,6 +46,9 @@ struct iommu_gather_ops {
  */
 struct io_pgtable_cfg {
        #define IO_PGTABLE_QUIRK_ARM_NS (1 << 0)        /* Set NS bit in PTEs */
+       #define IO_PGTABLE_QUIRK_SHORT_SUPERSECTION     BIT(1)
+       #define IO_PGTABLE_QUIRK_SHORT_NO_XN            BIT(2) /* No XN bit */
+       #define IO_PGTABLE_QUIRK_SHORT_NO_PERMS         BIT(3) /* No AP bit */
        int                             quirks;
        unsigned long                   pgsize_bitmap;
        unsigned int                    ias;
@@ -64,6 +68,13 @@ struct io_pgtable_cfg {
                        u64     vttbr;
                        u64     vtcr;
                } arm_lpae_s2_cfg;
+
+               struct {
+                       u32     ttbr[2];
+                       u32     tcr;
+                       u32     nmrr;
+                       u32     prrr;
+               } arm_short_cfg;
        };
 };
 
@@ -130,6 +141,9 @@ struct io_pgtable {
        struct io_pgtable_ops   ops;
 };
 
+#define io_pgtable_ops_to_pgtable(x)           \
+       container_of((x), struct io_pgtable, ops)
+
 /**
  * struct io_pgtable_init_fns - Alloc/free a set of page tables for a
  *                              particular format.
-- 
1.8.1.1.dirty

_______________________________________________
iommu mailing list
iommu@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/iommu

Reply via email to