* [PATCH v3 03/16] iommu/io-pgtable-arm: Add support for AARCH64 split pagetables
2019-05-29 20:54 [PATCH v3 00/16] drm/msm: Per-instance pagetable support Jordan Crouse
2019-05-29 20:54 ` [PATCH v3 01/16] iommu/arm-smmu: Allow client devices to select direct mapping Jordan Crouse
@ 2019-05-29 20:54 ` Jordan Crouse
2019-05-29 20:54 ` [PATCH v3 04/16] iommu/arm-smmu: Add support for DOMAIN_ATTR_SPLIT_TABLES Jordan Crouse
2019-05-29 20:54 ` [PATCH v3 06/16] iommu/arm-smmu: Add auxiliary domain support for arm-smmuv2 Jordan Crouse
3 siblings, 0 replies; 5+ messages in thread
From: Jordan Crouse @ 2019-05-29 20:54 UTC (permalink / raw)
To: freedreno
Cc: Rob Herring, jean-philippe.brucker, linux-arm-msm, Joerg Roedel,
Will Deacon, dianders, linux-kernel, iommu, hoegsberg, Zhen Lei,
Robin Murphy, linux-arm-kernel
Add a new sub-format ARM_64_LPAE_SPLIT_S1 to create and set up
split pagetables (TTBR0 and TTBR1). The initialization function
sets up the correct va_size and sign extension bits and
correctly programs the TCR registers. Split pagetable formats
use their own own map/unmap wrappers to ensure that the correct
pagetable is selected based on the incoming iova but most of the
heavy lifting is common.
v3: New patch taking most of the TTBR1 specific code out of arm-smmu
Signed-off-by: Jordan Crouse <jcrouse@codeaurora.org>
---
drivers/iommu/io-pgtable-arm.c | 261 +++++++++++++++++++++++++++++++++++++----
drivers/iommu/io-pgtable.c | 1 +
include/linux/io-pgtable.h | 2 +
3 files changed, 240 insertions(+), 24 deletions(-)
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 4e21efb..6ee333b9 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -129,7 +129,12 @@
#define ARM_LPAE_TCR_TG0_64K (1 << 14)
#define ARM_LPAE_TCR_TG0_16K (2 << 14)
+#define ARM_LPAE_TCR_TG1_4K (0 << 30)
+#define ARM_LPAE_TCR_TG1_64K (1 << 30)
+#define ARM_LPAE_TCR_TG1_16K (2 << 30)
+
#define ARM_LPAE_TCR_SH0_SHIFT 12
+#define ARM_LPAE_TCR_SH1_SHIFT 28
#define ARM_LPAE_TCR_SH0_MASK 0x3
#define ARM_LPAE_TCR_SH_NS 0
#define ARM_LPAE_TCR_SH_OS 2
@@ -137,6 +142,8 @@
#define ARM_LPAE_TCR_ORGN0_SHIFT 10
#define ARM_LPAE_TCR_IRGN0_SHIFT 8
+#define ARM_LPAE_TCR_ORGN1_SHIFT 26
+#define ARM_LPAE_TCR_IRGN1_SHIFT 24
#define ARM_LPAE_TCR_RGN_MASK 0x3
#define ARM_LPAE_TCR_RGN_NC 0
#define ARM_LPAE_TCR_RGN_WBWA 1
@@ -147,6 +154,7 @@
#define ARM_LPAE_TCR_SL0_MASK 0x3
#define ARM_LPAE_TCR_T0SZ_SHIFT 0
+#define ARM_LPAE_TCR_T1SZ_SHIFT 16
#define ARM_LPAE_TCR_SZ_MASK 0xf
#define ARM_LPAE_TCR_PS_SHIFT 16
@@ -163,6 +171,14 @@
#define ARM_LPAE_TCR_PS_48_BIT 0x5ULL
#define ARM_LPAE_TCR_PS_52_BIT 0x6ULL
+#define ARM_LPAE_TCR_SEP_SHIFT 47
+#define ARM_LPAE_TCR_SEP_31 (0x0ULL << ARM_LPAE_TCR_SEP_SHIFT)
+#define ARM_LPAE_TCR_SEP_35 (0x1ULL << ARM_LPAE_TCR_SEP_SHIFT)
+#define ARM_LPAE_TCR_SEP_39 (0x2ULL << ARM_LPAE_TCR_SEP_SHIFT)
+#define ARM_LPAE_TCR_SEP_41 (0x3ULL << ARM_LPAE_TCR_SEP_SHIFT)
+#define ARM_LPAE_TCR_SEP_43 (0x4ULL << ARM_LPAE_TCR_SEP_SHIFT)
+#define ARM_LPAE_TCR_SEP_UPSTREAM (0x7ULL << ARM_LPAE_TCR_SEP_SHIFT)
+
#define ARM_LPAE_MAIR_ATTR_SHIFT(n) ((n) << 3)
#define ARM_LPAE_MAIR_ATTR_MASK 0xff
#define ARM_LPAE_MAIR_ATTR_DEVICE 0x04
@@ -188,11 +204,12 @@ struct arm_lpae_io_pgtable {
struct io_pgtable iop;
int levels;
+ u32 sep;
size_t pgd_size;
unsigned long pg_shift;
unsigned long bits_per_level;
- void *pgd;
+ void *pgd[2];
};
typedef u64 arm_lpae_iopte;
@@ -437,7 +454,8 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data,
arm_lpae_iopte pte;
if (data->iop.fmt == ARM_64_LPAE_S1 ||
- data->iop.fmt == ARM_32_LPAE_S1) {
+ data->iop.fmt == ARM_32_LPAE_S1 ||
+ data->iop.fmt == ARM_64_LPAE_SPLIT_S1) {
pte = ARM_LPAE_PTE_nG;
if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ))
pte |= ARM_LPAE_PTE_AP_RDONLY;
@@ -478,11 +496,10 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data,
return pte;
}
-static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova,
- phys_addr_t paddr, size_t size, int iommu_prot)
+static int _arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova,
+ phys_addr_t paddr, size_t size, int iommu_prot,
+ arm_lpae_iopte *ptep)
{
- struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
- arm_lpae_iopte *ptep = data->pgd;
int ret, lvl = ARM_LPAE_START_LVL(data);
arm_lpae_iopte prot;
@@ -505,12 +522,39 @@ static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova,
return ret;
}
+static int arm_lpae_split_map(struct io_pgtable_ops *ops, unsigned long iova,
+ phys_addr_t paddr, size_t size, int iommu_prot)
+{
+ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
+ unsigned long mask = 1UL << data->sep;
+ arm_lpae_iopte *ptep;
+
+ if (iova & mask) {
+ ptep = data->pgd[1];
+ iova &= (mask - 1);
+ } else
+ ptep = data->pgd[0];
+
+ return _arm_lpae_map(data, iova, paddr, size, iommu_prot, ptep);
+}
+
+static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova,
+ phys_addr_t paddr, size_t size, int iommu_prot)
+{
+ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
+
+ return _arm_lpae_map(data, iova, paddr, size, iommu_prot, data->pgd[0]);
+}
+
static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl,
arm_lpae_iopte *ptep)
{
arm_lpae_iopte *start, *end;
unsigned long table_size;
+ if (!ptep)
+ return;
+
if (lvl == ARM_LPAE_START_LVL(data))
table_size = data->pgd_size;
else
@@ -540,7 +584,8 @@ static void arm_lpae_free_pgtable(struct io_pgtable *iop)
{
struct arm_lpae_io_pgtable *data = io_pgtable_to_data(iop);
- __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd);
+ __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd[0]);
+ __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd[1]);
kfree(data);
}
@@ -651,11 +696,28 @@ static size_t __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,
return __arm_lpae_unmap(data, iova, size, lvl + 1, ptep);
}
+static size_t arm_lpae_split_unmap(struct io_pgtable_ops *ops,
+ unsigned long iova, size_t size)
+{
+ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
+ unsigned long mask = 1UL << data->sep;
+ arm_lpae_iopte *ptep;
+ int lvl = ARM_LPAE_START_LVL(data);
+
+ if (iova & mask) {
+ ptep = data->pgd[1];
+ iova &= (mask - 1);
+ } else
+ ptep = data->pgd[0];
+
+ return __arm_lpae_unmap(data, iova, size, lvl, ptep);
+}
+
static size_t arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova,
size_t size)
{
struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
- arm_lpae_iopte *ptep = data->pgd;
+ arm_lpae_iopte *ptep = data->pgd[0];
int lvl = ARM_LPAE_START_LVL(data);
if (WARN_ON(iova >= (1ULL << data->iop.cfg.ias)))
@@ -664,11 +726,11 @@ static size_t arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova,
return __arm_lpae_unmap(data, iova, size, lvl, ptep);
}
-static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
- unsigned long iova)
+static phys_addr_t _arm_lpae_iova_to_phys(struct arm_lpae_io_pgtable *data,
+ unsigned long iova,
+ arm_lpae_iopte *ptep)
{
- struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
- arm_lpae_iopte pte, *ptep = data->pgd;
+ arm_lpae_iopte pte;
int lvl = ARM_LPAE_START_LVL(data);
do {
@@ -700,6 +762,31 @@ static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
return iopte_to_paddr(pte, data) | iova;
}
+
+static phys_addr_t arm_lpae_split_iova_to_phys(struct io_pgtable_ops *ops,
+ unsigned long iova)
+{
+ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
+ unsigned long mask = 1UL << data->sep;
+ arm_lpae_iopte *ptep;
+
+ if (iova & mask) {
+ ptep = data->pgd[1];
+ iova &= (mask - 1);
+ } else
+ ptep = data->pgd[0];
+
+ return _arm_lpae_iova_to_phys(data, iova, ptep);
+}
+
+static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
+ unsigned long iova)
+{
+ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
+
+ return _arm_lpae_iova_to_phys(data, iova, data->pgd[0]);
+}
+
static void arm_lpae_restrict_pgsizes(struct io_pgtable_cfg *cfg)
{
unsigned long granule, page_sizes;
@@ -779,6 +866,9 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
pgd_bits = va_bits - (data->bits_per_level * (data->levels - 1));
data->pgd_size = 1UL << (pgd_bits + ilog2(sizeof(arm_lpae_iopte)));
+ data->pgd[0] = NULL;
+ data->pgd[1] = NULL;
+
data->iop.ops = (struct io_pgtable_ops) {
.map = arm_lpae_map,
.unmap = arm_lpae_unmap,
@@ -788,8 +878,8 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
return data;
}
-static struct io_pgtable *
-arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie)
+static struct arm_lpae_io_pgtable *
+_arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg)
{
u64 reg;
struct arm_lpae_io_pgtable *data;
@@ -847,8 +937,6 @@ arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie)
reg |= (64ULL - cfg->ias) << ARM_LPAE_TCR_T0SZ_SHIFT;
- /* Disable speculative walks through TTBR1 */
- reg |= ARM_LPAE_TCR_EPD1;
cfg->arm_lpae_s1_cfg.tcr = reg;
/* MAIRs */
@@ -863,24 +951,143 @@ arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie)
cfg->arm_lpae_s1_cfg.mair[1] = 0;
/* Looking good; allocate a pgd */
- data->pgd = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg);
- if (!data->pgd)
+ data->pgd[0] = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg);
+ if (!data->pgd[0])
goto out_free_data;
/* Ensure the empty pgd is visible before any actual TTBR write */
wmb();
/* TTBRs */
- cfg->arm_lpae_s1_cfg.ttbr[0] = virt_to_phys(data->pgd);
+ cfg->arm_lpae_s1_cfg.ttbr[0] = virt_to_phys(data->pgd[0]);
cfg->arm_lpae_s1_cfg.ttbr[1] = 0;
+ return data;
+
+out_free_data:
+ kfree(data);
+ return NULL;
+}
+
+
+/* Allocate split pagetables */
+static struct io_pgtable *
+arm_64_lpae_alloc_pgtable_split_s1(struct io_pgtable_cfg *cfg, void *cookie)
+{
+ u64 reg;
+ struct arm_lpae_io_pgtable *data;
+ u32 sep;
+
+ /* Figure out what the sign extension bit should be */
+ switch (cfg->ias) {
+ case 32:
+ case 36:
+ case 40:
+ case 42:
+ case 44:
+ sep = cfg->ias - 1;
+ /* Adjust the address size to account for the extension bit */
+ cfg->ias--;
+ break;
+ case 48:
+ /* IAS of 48 is a special case, it has a dedicated bit */
+ sep = 48;
+ break;
+ default:
+ return NULL;
+ }
+
+ data = _arm_64_lpae_alloc_pgtable_s1(cfg);
+ if (!data)
+ return NULL;
+
+ /* Add the TTBR1 settings */
+ reg = cfg->arm_lpae_s1_cfg.tcr;
+
+ /* TCR */
+ reg |= (ARM_LPAE_TCR_SH_IS << ARM_LPAE_TCR_SH1_SHIFT) |
+ (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_IRGN1_SHIFT) |
+ (ARM_LPAE_TCR_RGN_WBWA << ARM_LPAE_TCR_ORGN1_SHIFT);
+
+ switch (ARM_LPAE_GRANULE(data)) {
+ case SZ_4K:
+ reg |= ARM_LPAE_TCR_TG1_4K;
+ break;
+ case SZ_16K:
+ reg |= ARM_LPAE_TCR_TG1_16K;
+ break;
+ case SZ_64K:
+ reg |= ARM_LPAE_TCR_TG1_64K;
+ break;
+ }
+
+ reg |= (64ULL - cfg->ias) << ARM_LPAE_TCR_T1SZ_SHIFT;
+
+ switch (sep) {
+ case 31:
+ reg |= ARM_LPAE_TCR_SEP_31;
+ break;
+ case 35:
+ reg |= ARM_LPAE_TCR_SEP_35;
+ break;
+ case 39:
+ reg |= ARM_LPAE_TCR_SEP_39;
+ break;
+ case 41:
+ reg |= ARM_LPAE_TCR_SEP_41;
+ break;
+ case 43:
+ reg |= ARM_LPAE_TCR_SEP_43;
+ break;
+ case 48:
+ reg |= ARM_LPAE_TCR_SEP_UPSTREAM;
+ break;
+ }
+
+ cfg->arm_lpae_s1_cfg.tcr = reg;
+
+ /* Allocate the TTBR1 pagetable */
+ data->pgd[1] = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg);
+ if (!data->pgd[1])
+ goto out_free_data;
+
+ /* Override the data ops with split table specific ops */
+ data->iop.ops = (struct io_pgtable_ops) {
+ .map = arm_lpae_split_map,
+ .unmap = arm_lpae_split_unmap,
+ .iova_to_phys = arm_lpae_split_iova_to_phys,
+ };
+
+ /*
+ * remember the sign extension bit, we'll need it later to figure out
+ * which pagetable to use
+ */
+ data->sep = sep;
+
+ /* Ensure the empty pgd is visible before any actual TTBR write */
+ wmb();
+
+ cfg->arm_lpae_s1_cfg.ttbr[1] = virt_to_phys(data->pgd[1]);
return &data->iop;
out_free_data:
+ __arm_lpae_free_pages(data->pgd[0], data->pgd_size, cfg);
kfree(data);
return NULL;
}
static struct io_pgtable *
+arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie)
+{
+ struct arm_lpae_io_pgtable *data;
+
+ data = _arm_64_lpae_alloc_pgtable_s1(cfg);
+ if (!data)
+ return NULL;
+
+ return &data->iop;
+}
+
+static struct io_pgtable *
arm_64_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie)
{
u64 reg, sl;
@@ -961,15 +1168,15 @@ arm_64_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie)
cfg->arm_lpae_s2_cfg.vtcr = reg;
/* Allocate pgd pages */
- data->pgd = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg);
- if (!data->pgd)
+ data->pgd[0] = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg);
+ if (!data->pgd[0])
goto out_free_data;
/* Ensure the empty pgd is visible before any actual TTBR write */
wmb();
/* VTTBR */
- cfg->arm_lpae_s2_cfg.vttbr = virt_to_phys(data->pgd);
+ cfg->arm_lpae_s2_cfg.vttbr = virt_to_phys(data->pgd[0]);
return &data->iop;
out_free_data:
@@ -1042,6 +1249,11 @@ struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns = {
.free = arm_lpae_free_pgtable,
};
+struct io_pgtable_init_fns io_pgtable_arm_64_lpae_split_s1_init_fns = {
+ .alloc = arm_64_lpae_alloc_pgtable_split_s1,
+ .free = arm_lpae_free_pgtable,
+};
+
struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns = {
.alloc = arm_64_lpae_alloc_pgtable_s2,
.free = arm_lpae_free_pgtable,
@@ -1096,9 +1308,9 @@ static void __init arm_lpae_dump_ops(struct io_pgtable_ops *ops)
pr_err("cfg: pgsize_bitmap 0x%lx, ias %u-bit\n",
cfg->pgsize_bitmap, cfg->ias);
- pr_err("data: %d levels, 0x%zx pgd_size, %lu pg_shift, %lu bits_per_level, pgd @ %p\n",
+ pr_err("data: %d levels, 0x%zx pgd_size, %lu pg_shift, %lu bits_per_level, pgd @ %p %p\n",
data->levels, data->pgd_size, data->pg_shift,
- data->bits_per_level, data->pgd);
+ data->bits_per_level, data->pgd[0], data->pgd[1]);
}
#define __FAIL(ops, i) ({ \
@@ -1113,6 +1325,7 @@ static int __init arm_lpae_run_tests(struct io_pgtable_cfg *cfg)
static const enum io_pgtable_fmt fmts[] = {
ARM_64_LPAE_S1,
ARM_64_LPAE_S2,
+ ARM_64_LPAE_SPLIT_S1,
};
int i, j;
diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c
index 5227cfd..58d0012 100644
--- a/drivers/iommu/io-pgtable.c
+++ b/drivers/iommu/io-pgtable.c
@@ -29,6 +29,7 @@ io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = {
[ARM_32_LPAE_S1] = &io_pgtable_arm_32_lpae_s1_init_fns,
[ARM_32_LPAE_S2] = &io_pgtable_arm_32_lpae_s2_init_fns,
[ARM_64_LPAE_S1] = &io_pgtable_arm_64_lpae_s1_init_fns,
+ [ARM_64_LPAE_SPLIT_S1] = &io_pgtable_arm_64_lpae_split_s1_init_fns,
[ARM_64_LPAE_S2] = &io_pgtable_arm_64_lpae_s2_init_fns,
[ARM_MALI_LPAE] = &io_pgtable_arm_mali_lpae_init_fns,
#endif
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index 76969a5..821080c 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -10,6 +10,7 @@ enum io_pgtable_fmt {
ARM_32_LPAE_S1,
ARM_32_LPAE_S2,
ARM_64_LPAE_S1,
+ ARM_64_LPAE_SPLIT_S1,
ARM_64_LPAE_S2,
ARM_V7S,
ARM_MALI_LPAE,
@@ -214,6 +215,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_64_lpae_split_s1_init_fns;
extern struct io_pgtable_init_fns io_pgtable_arm_v7s_init_fns;
extern struct io_pgtable_init_fns io_pgtable_arm_mali_lpae_init_fns;
--
2.7.4
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 06/16] iommu/arm-smmu: Add auxiliary domain support for arm-smmuv2
2019-05-29 20:54 [PATCH v3 00/16] drm/msm: Per-instance pagetable support Jordan Crouse
` (2 preceding siblings ...)
2019-05-29 20:54 ` [PATCH v3 04/16] iommu/arm-smmu: Add support for DOMAIN_ATTR_SPLIT_TABLES Jordan Crouse
@ 2019-05-29 20:54 ` Jordan Crouse
3 siblings, 0 replies; 5+ messages in thread
From: Jordan Crouse @ 2019-05-29 20:54 UTC (permalink / raw)
To: freedreno
Cc: jean-philippe.brucker, linux-arm-msm, Joerg Roedel, Will Deacon,
dianders, linux-kernel, iommu, hoegsberg, Robin Murphy,
linux-arm-kernel
Support auxiliary domains for arm-smmu-v2 to initialize and support multiple
pagetables for a single SMMU context bank. Since the smmu-v2 hardware
doesn't have any built in support for switching the pagetable base it is
left as an exercise to the caller to actually use the pagetable; aux
domains in the IOMMU driver are only preoccupied with creating and managing
the pagetable memory.
Following is a pseudo code example of how a domain can be created
/* Check to see if aux domains are supported */
if (iommu_dev_has_feature(dev, IOMMU_DEV_FEAT_AUX)) {
iommu = iommu_domain_alloc(...);
if (iommu_aux_attach_device(domain, dev))
return FAIL;
/* Save the base address of the pagetable for use by the driver
iommu_domain_get_attr(domain, DOMAIN_ATTR_PTBASE, &ptbase);
}
Then 'domain' can be used like any other iommu domain to map and
unmap iova addresses in the pagetable. The driver/hardware is used
to switch the pagetable according to its own specific implementation.
v3: Trivial update to reflect new pgtable ops situation
Signed-off-by: Jordan Crouse <jcrouse@codeaurora.org>
---
drivers/iommu/arm-smmu.c | 125 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 110 insertions(+), 15 deletions(-)
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c
index 33e6928..589da47 100644
--- a/drivers/iommu/arm-smmu.c
+++ b/drivers/iommu/arm-smmu.c
@@ -262,6 +262,8 @@ struct arm_smmu_domain {
spinlock_t cb_lock; /* Serialises ATS1* ops and TLB syncs */
u32 attributes;
struct iommu_domain domain;
+ bool is_aux;
+ u64 ttbr0;
};
struct arm_smmu_option_prop {
@@ -803,6 +805,12 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S2))
smmu_domain->stage = ARM_SMMU_DOMAIN_S1;
+ /* Aux domains can only be created for stage-1 tables */
+ if (smmu_domain->is_aux && smmu_domain->stage != ARM_SMMU_DOMAIN_S1) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
/*
* Choosing a suitable context format is even more fiddly. Until we
* grow some way for the caller to express a preference, and/or move
@@ -850,6 +858,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
ias = min(ias, 32UL);
oas = min(oas, 32UL);
}
+
smmu_domain->tlb_ops = &arm_smmu_s1_tlb_ops;
break;
case ARM_SMMU_DOMAIN_NESTED:
@@ -869,6 +878,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
ias = min(ias, 40UL);
oas = min(oas, 40UL);
}
+
if (smmu->version == ARM_SMMU_V2)
smmu_domain->tlb_ops = &arm_smmu_s2_tlb_ops_v2;
else
@@ -878,23 +888,30 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
ret = -EINVAL;
goto out_unlock;
}
- ret = __arm_smmu_alloc_bitmap(smmu->context_map, start,
- smmu->num_context_banks);
- if (ret < 0)
- goto out_unlock;
- cfg->cbndx = ret;
- if (smmu->version < ARM_SMMU_V2) {
- cfg->irptndx = atomic_inc_return(&smmu->irptndx);
- cfg->irptndx %= smmu->num_context_irqs;
- } else {
- cfg->irptndx = cfg->cbndx;
- }
+ /*
+ * Aux domains will use the same context bank assigned to the master
+ * domain for the device
+ */
+ if (!smmu_domain->is_aux) {
+ ret = __arm_smmu_alloc_bitmap(smmu->context_map, start,
+ smmu->num_context_banks);
+ if (ret < 0)
+ goto out_unlock;
- if (smmu_domain->stage == ARM_SMMU_DOMAIN_S2)
- cfg->vmid = cfg->cbndx + 1 + smmu->cavium_id_base;
- else
- cfg->asid = cfg->cbndx + smmu->cavium_id_base;
+ cfg->cbndx = ret;
+ if (smmu->version < ARM_SMMU_V2) {
+ cfg->irptndx = atomic_inc_return(&smmu->irptndx);
+ cfg->irptndx %= smmu->num_context_irqs;
+ } else {
+ cfg->irptndx = cfg->cbndx;
+ }
+
+ if (smmu_domain->stage == ARM_SMMU_DOMAIN_S2)
+ cfg->vmid = cfg->cbndx + 1 + smmu->cavium_id_base;
+ else
+ cfg->asid = cfg->cbndx + smmu->cavium_id_base;
+ }
pgtbl_cfg = (struct io_pgtable_cfg) {
.pgsize_bitmap = smmu->pgsize_bitmap,
@@ -917,11 +934,21 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
goto out_clear_smmu;
}
+ /* Cache the TTBR0 for the aux domain */
+ smmu_domain->ttbr0 = pgtbl_cfg.arm_lpae_s1_cfg.ttbr[0];
+
/* Update the domain's page sizes to reflect the page table format */
domain->pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
domain->geometry.aperture_end = (1UL << ias) - 1;
domain->geometry.force_aperture = true;
+ /*
+ * aux domains don't use split tables or program the hardware so we're
+ * done setting it up
+ */
+ if (smmu_domain->is_aux)
+ goto out;
+
/* Initialise the context bank with our page table cfg */
arm_smmu_init_context_bank(smmu_domain, &pgtbl_cfg);
arm_smmu_write_context_bank(smmu, cfg->cbndx);
@@ -939,6 +966,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain,
cfg->irptndx = INVALID_IRPTNDX;
}
+out:
mutex_unlock(&smmu_domain->init_mutex);
/* Publish page table ops for map/unmap */
@@ -962,6 +990,12 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain)
if (!smmu || domain->type == IOMMU_DOMAIN_IDENTITY)
return;
+ /* All we need to do for aux devices is destroy the pagetable */
+ if (smmu_domain->is_aux) {
+ free_io_pgtable_ops(smmu_domain->pgtbl_ops);
+ return;
+ }
+
ret = arm_smmu_rpm_get(smmu);
if (ret < 0)
return;
@@ -1242,14 +1276,17 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain,
struct arm_smmu_client_match_data {
bool direct_mapping;
+ bool allow_aux_domain;
};
static const struct arm_smmu_client_match_data qcom_adreno = {
.direct_mapping = true,
+ .allow_aux_domain = true,
};
static const struct arm_smmu_client_match_data qcom_mdss = {
.direct_mapping = true,
+ .allow_aux_domain = false,
};
static const struct of_device_id arm_smmu_client_of_match[] = {
@@ -1269,6 +1306,55 @@ arm_smmu_client_data(struct device *dev)
return match ? match->data : NULL;
}
+static bool arm_smmu_supports_aux(struct device *dev)
+{
+ const struct arm_smmu_client_match_data *data =
+ arm_smmu_client_data(dev);
+
+ return (data && data->allow_aux_domain);
+}
+
+static bool arm_smmu_dev_has_feat(struct device *dev,
+ enum iommu_dev_features feat)
+{
+ if (feat != IOMMU_DEV_FEAT_AUX)
+ return false;
+
+ return arm_smmu_supports_aux(dev);
+}
+
+static int arm_smmu_dev_enable_feat(struct device *dev,
+ enum iommu_dev_features feat)
+{
+ /* If supported aux domain support is always "on" */
+ if (feat == IOMMU_DEV_FEAT_AUX && arm_smmu_supports_aux(dev))
+ return 0;
+
+ return -ENODEV;
+}
+
+static int arm_smmu_dev_disable_feat(struct device *dev,
+ enum iommu_dev_features feat)
+{
+ return -EBUSY;
+}
+
+/* Set up a new aux domain and create a new pagetable with the same
+ * characteristics as the master
+ */
+static int arm_smmu_aux_attach_dev(struct iommu_domain *domain,
+ struct device *dev)
+{
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ struct arm_smmu_device *smmu = fwspec_smmu(fwspec);
+ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+
+ smmu_domain->is_aux = true;
+
+ /* No power is needed because aux domain doesn't touch the hardware */
+ return arm_smmu_init_domain_context(domain, smmu);
+}
+
static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
{
int ret;
@@ -1631,6 +1717,11 @@ static int arm_smmu_domain_get_attr(struct iommu_domain *domain,
*(int *)data = !!(smmu_domain->attributes &
(1 << DOMAIN_ATTR_SPLIT_TABLES));
return 0;
+ case DOMAIN_ATTR_PTBASE:
+ if (!smmu_domain->is_aux)
+ return -ENODEV;
+ *((u64 *)data) = smmu_domain->ttbr0;
+ return 0;
default:
return -ENODEV;
}
@@ -1741,7 +1832,11 @@ static struct iommu_ops arm_smmu_ops = {
.capable = arm_smmu_capable,
.domain_alloc = arm_smmu_domain_alloc,
.domain_free = arm_smmu_domain_free,
+ .dev_has_feat = arm_smmu_dev_has_feat,
+ .dev_enable_feat = arm_smmu_dev_enable_feat,
+ .dev_disable_feat = arm_smmu_dev_disable_feat,
.attach_dev = arm_smmu_attach_dev,
+ .aux_attach_dev = arm_smmu_aux_attach_dev,
.map = arm_smmu_map,
.unmap = arm_smmu_unmap,
.flush_iotlb_all = arm_smmu_flush_iotlb_all,
--
2.7.4
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related [flat|nested] 5+ messages in thread