// SPDX-License-Identifier: GPL-2.0-only /* * CPU-agnostic ARM page table allocator. * * Copyright (C) 2014 ARM Limited * * Author: Will Deacon */ #define pr_fmt(fmt) "arm-lpae io-pgtable: " fmt #include #include #include #include #include "io-pgtable-arm.h" static struct io_pgtable_cfg *cfg_cookie; static void dummy_tlb_flush_all(void *cookie) { WARN_ON(cookie != cfg_cookie); } static void dummy_tlb_flush(unsigned long iova, size_t size, size_t granule, void *cookie) { WARN_ON(cookie != cfg_cookie); WARN_ON(!(size & cfg_cookie->pgsize_bitmap)); } static void dummy_tlb_add_page(struct iommu_iotlb_gather *gather, unsigned long iova, size_t granule, void *cookie) { dummy_tlb_flush(iova, granule, granule, cookie); } static const struct iommu_flush_ops dummy_tlb_ops = { .tlb_flush_all = dummy_tlb_flush_all, .tlb_flush_walk = dummy_tlb_flush, .tlb_add_page = dummy_tlb_add_page, }; #define __FAIL(test, i) ({ \ KUNIT_FAIL(test, "test failed for fmt idx %d\n", (i)); \ -EFAULT; \ }) static int arm_lpae_run_tests(struct kunit *test, 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, mapped; struct io_pgtable_ops *ops; for (i = 0; i < ARRAY_SIZE(fmts); ++i) { cfg_cookie = cfg; ops = alloc_io_pgtable_ops(fmts[i], cfg, cfg); if (!ops) { kunit_err(test, "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(test, i); if (ops->iova_to_phys(ops, SZ_1G + 42)) return __FAIL(test, i); if (ops->iova_to_phys(ops, SZ_2G + 42)) return __FAIL(test, i); /* * Distinct mappings of different granule sizes. */ iova = 0; for_each_set_bit(j, &cfg->pgsize_bitmap, BITS_PER_LONG) { size = 1UL << j; if (ops->map_pages(ops, iova, iova, size, 1, IOMMU_READ | IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_CACHE, GFP_KERNEL, &mapped)) return __FAIL(test, i); /* Overlapping mappings */ if (!ops->map_pages(ops, iova, iova + size, size, 1, IOMMU_READ | IOMMU_NOEXEC, GFP_KERNEL, &mapped)) return __FAIL(test, i); if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) return __FAIL(test, i); iova += SZ_1G; } /* Full unmap */ iova = 0; for_each_set_bit(j, &cfg->pgsize_bitmap, BITS_PER_LONG) { size = 1UL << j; if (ops->unmap_pages(ops, iova, size, 1, NULL) != size) return __FAIL(test, i); if (ops->iova_to_phys(ops, iova + 42)) return __FAIL(test, i); /* Remap full block */ if (ops->map_pages(ops, iova, iova, size, 1, IOMMU_WRITE, GFP_KERNEL, &mapped)) return __FAIL(test, i); if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) return __FAIL(test, i); iova += SZ_1G; } /* * Map/unmap the last largest supported page of the IAS, this can * trigger corner cases in the concatednated page tables. */ mapped = 0; size = 1UL << __fls(cfg->pgsize_bitmap); iova = (1UL << cfg->ias) - size; if (ops->map_pages(ops, iova, iova, size, 1, IOMMU_READ | IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_CACHE, GFP_KERNEL, &mapped)) return __FAIL(test, i); if (mapped != size) return __FAIL(test, i); if (ops->unmap_pages(ops, iova, size, 1, NULL) != size) return __FAIL(test, i); free_io_pgtable_ops(ops); } return 0; } static void arm_lpae_do_selftests(struct kunit *test) { static const unsigned long pgsize[] = { SZ_4K | SZ_2M | SZ_1G, SZ_16K | SZ_32M, SZ_64K | SZ_512M, }; static const unsigned int address_size[] = { 32, 36, 40, 42, 44, 48, }; int i, j, k, pass = 0, fail = 0; struct device *dev; struct io_pgtable_cfg cfg = { .tlb = &dummy_tlb_ops, .coherent_walk = true, .quirks = IO_PGTABLE_QUIRK_NO_WARN, }; dev = kunit_device_register(test, "io-pgtable-test"); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, dev); if (IS_ERR_OR_NULL(dev)) return; cfg.iommu_dev = dev; for (i = 0; i < ARRAY_SIZE(pgsize); ++i) { for (j = 0; j < ARRAY_SIZE(address_size); ++j) { /* Don't use ias > oas as it is not valid for stage-2. */ for (k = 0; k <= j; ++k) { cfg.pgsize_bitmap = pgsize[i]; cfg.ias = address_size[k]; cfg.oas = address_size[j]; kunit_info(test, "pgsize_bitmap 0x%08lx, IAS %u OAS %u\n", pgsize[i], cfg.ias, cfg.oas); if (arm_lpae_run_tests(test, &cfg)) fail++; else pass++; } } } kunit_info(test, "completed with %d PASS %d FAIL\n", pass, fail); } static struct kunit_case io_pgtable_arm_test_cases[] = { KUNIT_CASE(arm_lpae_do_selftests), {}, }; static struct kunit_suite io_pgtable_arm_test = { .name = "io-pgtable-arm-test", .test_cases = io_pgtable_arm_test_cases, }; kunit_test_suite(io_pgtable_arm_test); MODULE_DESCRIPTION("io-pgtable-arm library kunit tests"); MODULE_LICENSE("GPL");