diff options
Diffstat (limited to 'drivers/iommu/io-pgtable-arm-selftests.c')
| -rw-r--r-- | drivers/iommu/io-pgtable-arm-selftests.c | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/drivers/iommu/io-pgtable-arm-selftests.c b/drivers/iommu/io-pgtable-arm-selftests.c new file mode 100644 index 000000000000..334e70350924 --- /dev/null +++ b/drivers/iommu/io-pgtable-arm-selftests.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CPU-agnostic ARM page table allocator. + * + * Copyright (C) 2014 ARM Limited + * + * Author: Will Deacon <will.deacon@arm.com> + */ + +#define pr_fmt(fmt) "arm-lpae io-pgtable: " fmt + +#include <kunit/device.h> +#include <kunit/test.h> +#include <linux/io-pgtable.h> +#include <linux/kernel.h> + +#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"); |
