|
@@ -193,6 +193,8 @@ struct arm_lpae_io_pgtable {
|
|
|
|
|
|
typedef u64 arm_lpae_iopte;
|
|
|
|
|
|
+static bool selftest_running = false;
|
|
|
+
|
|
|
static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data,
|
|
|
unsigned long iova, phys_addr_t paddr,
|
|
|
arm_lpae_iopte prot, int lvl,
|
|
@@ -201,8 +203,10 @@ static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data,
|
|
|
arm_lpae_iopte pte = prot;
|
|
|
|
|
|
/* We require an unmap first */
|
|
|
- if (WARN_ON(iopte_leaf(*ptep, lvl)))
|
|
|
+ if (iopte_leaf(*ptep, lvl)) {
|
|
|
+ WARN_ON(!selftest_running);
|
|
|
return -EEXIST;
|
|
|
+ }
|
|
|
|
|
|
if (lvl == ARM_LPAE_MAX_LEVELS - 1)
|
|
|
pte |= ARM_LPAE_PTE_TYPE_PAGE;
|
|
@@ -779,3 +783,197 @@ struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns = {
|
|
|
.alloc = arm_32_lpae_alloc_pgtable_s2,
|
|
|
.free = arm_lpae_free_pgtable,
|
|
|
};
|
|
|
+
|
|
|
+#ifdef CONFIG_IOMMU_IO_PGTABLE_LPAE_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 void dummy_flush_pgtable(void *ptr, size_t size, void *cookie)
|
|
|
+{
|
|
|
+ WARN_ON(cookie != cfg_cookie);
|
|
|
+}
|
|
|
+
|
|
|
+static struct iommu_gather_ops dummy_tlb_ops __initdata = {
|
|
|
+ .tlb_flush_all = dummy_tlb_flush_all,
|
|
|
+ .tlb_add_flush = dummy_tlb_add_flush,
|
|
|
+ .tlb_sync = dummy_tlb_sync,
|
|
|
+ .flush_pgtable = dummy_flush_pgtable,
|
|
|
+};
|
|
|
+
|
|
|
+static void __init arm_lpae_dump_ops(struct io_pgtable_ops *ops)
|
|
|
+{
|
|
|
+ struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
|
|
|
+ struct io_pgtable_cfg *cfg = &data->iop.cfg;
|
|
|
+
|
|
|
+ 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",
|
|
|
+ data->levels, data->pgd_size, data->pg_shift,
|
|
|
+ data->bits_per_level, data->pgd);
|
|
|
+}
|
|
|
+
|
|
|
+#define __FAIL(ops, i) ({ \
|
|
|
+ WARN(1, "selftest: test failed for fmt idx %d\n", (i)); \
|
|
|
+ arm_lpae_dump_ops(ops); \
|
|
|
+ selftest_running = false; \
|
|
|
+ -EFAULT; \
|
|
|
+})
|
|
|
+
|
|
|
+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,
|
|
|
+ };
|
|
|
+
|
|
|
+ int i, j;
|
|
|
+ unsigned long iova;
|
|
|
+ size_t size;
|
|
|
+ struct io_pgtable_ops *ops;
|
|
|
+
|
|
|
+ selftest_running = true;
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(fmts); ++i) {
|
|
|
+ cfg_cookie = cfg;
|
|
|
+ ops = alloc_io_pgtable_ops(fmts[i], cfg, cfg);
|
|
|
+ if (!ops) {
|
|
|
+ pr_err("selftest: failed to allocate io pgtable ops\n");
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Initial sanity checks.
|
|
|
+ * Empty page tables shouldn't provide any translations.
|
|
|
+ */
|
|
|
+ if (ops->iova_to_phys(ops, 42))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ if (ops->iova_to_phys(ops, SZ_1G + 42))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ if (ops->iova_to_phys(ops, SZ_2G + 42))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Distinct mappings of different granule sizes.
|
|
|
+ */
|
|
|
+ iova = 0;
|
|
|
+ j = find_first_bit(&cfg->pgsize_bitmap, BITS_PER_LONG);
|
|
|
+ while (j != BITS_PER_LONG) {
|
|
|
+ size = 1UL << j;
|
|
|
+
|
|
|
+ if (ops->map(ops, iova, iova, size, IOMMU_READ |
|
|
|
+ IOMMU_WRITE |
|
|
|
+ IOMMU_NOEXEC |
|
|
|
+ IOMMU_CACHE))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ /* Overlapping mappings */
|
|
|
+ if (!ops->map(ops, iova, iova + size, size,
|
|
|
+ IOMMU_READ | IOMMU_NOEXEC))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ iova += SZ_1G;
|
|
|
+ j++;
|
|
|
+ j = find_next_bit(&cfg->pgsize_bitmap, BITS_PER_LONG, j);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Partial unmap */
|
|
|
+ size = 1UL << __ffs(cfg->pgsize_bitmap);
|
|
|
+ if (ops->unmap(ops, SZ_1G + size, size) != size)
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ /* Remap of partial unmap */
|
|
|
+ if (ops->map(ops, SZ_1G + size, size, size, IOMMU_READ))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ if (ops->iova_to_phys(ops, SZ_1G + size + 42) != (size + 42))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ /* Full unmap */
|
|
|
+ iova = 0;
|
|
|
+ j = find_first_bit(&cfg->pgsize_bitmap, BITS_PER_LONG);
|
|
|
+ while (j != BITS_PER_LONG) {
|
|
|
+ size = 1UL << j;
|
|
|
+
|
|
|
+ if (ops->unmap(ops, iova, size) != size)
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ if (ops->iova_to_phys(ops, iova + 42))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ /* Remap full block */
|
|
|
+ if (ops->map(ops, iova, iova, size, IOMMU_WRITE))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
|
|
|
+ return __FAIL(ops, i);
|
|
|
+
|
|
|
+ iova += SZ_1G;
|
|
|
+ j++;
|
|
|
+ j = find_next_bit(&cfg->pgsize_bitmap, BITS_PER_LONG, j);
|
|
|
+ }
|
|
|
+
|
|
|
+ free_io_pgtable_ops(ops);
|
|
|
+ }
|
|
|
+
|
|
|
+ selftest_running = false;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int __init arm_lpae_do_selftests(void)
|
|
|
+{
|
|
|
+ static const unsigned long pgsize[] = {
|
|
|
+ SZ_4K | SZ_2M | SZ_1G,
|
|
|
+ SZ_16K | SZ_32M,
|
|
|
+ SZ_64K | SZ_512M,
|
|
|
+ };
|
|
|
+
|
|
|
+ static const unsigned int ias[] = {
|
|
|
+ 32, 36, 40, 42, 44, 48,
|
|
|
+ };
|
|
|
+
|
|
|
+ int i, j, pass = 0, fail = 0;
|
|
|
+ struct io_pgtable_cfg cfg = {
|
|
|
+ .tlb = &dummy_tlb_ops,
|
|
|
+ .oas = 48,
|
|
|
+ };
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pgsize); ++i) {
|
|
|
+ for (j = 0; j < ARRAY_SIZE(ias); ++j) {
|
|
|
+ cfg.pgsize_bitmap = pgsize[i];
|
|
|
+ cfg.ias = ias[j];
|
|
|
+ pr_info("selftest: pgsize_bitmap 0x%08lx, IAS %u\n",
|
|
|
+ pgsize[i], ias[j]);
|
|
|
+ if (arm_lpae_run_tests(&cfg))
|
|
|
+ fail++;
|
|
|
+ else
|
|
|
+ pass++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pr_info("selftest: completed with %d PASS %d FAIL\n", pass, fail);
|
|
|
+ return fail ? -EFAULT : 0;
|
|
|
+}
|
|
|
+subsys_initcall(arm_lpae_do_selftests);
|
|
|
+#endif
|