| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * CPU-agnostic ARM page table allocator. |
| 4 | * |
| 5 | * Copyright (C) 2014 ARM Limited |
| 6 | * |
| 7 | * Author: Will Deacon <will.deacon@arm.com> |
| 8 | */ |
| 9 | |
| 10 | #define pr_fmt(fmt) "arm-lpae io-pgtable: " fmt |
| 11 | |
| 12 | #include <kunit/device.h> |
| 13 | #include <kunit/test.h> |
| 14 | #include <linux/io-pgtable.h> |
| 15 | #include <linux/kernel.h> |
| 16 | |
| 17 | #include "io-pgtable-arm.h" |
| 18 | |
| 19 | static struct io_pgtable_cfg *cfg_cookie; |
| 20 | |
| 21 | static void dummy_tlb_flush_all(void *cookie) |
| 22 | { |
| 23 | WARN_ON(cookie != cfg_cookie); |
| 24 | } |
| 25 | |
| 26 | static void dummy_tlb_flush(unsigned long iova, size_t size, |
| 27 | size_t granule, void *cookie) |
| 28 | { |
| 29 | WARN_ON(cookie != cfg_cookie); |
| 30 | WARN_ON(!(size & cfg_cookie->pgsize_bitmap)); |
| 31 | } |
| 32 | |
| 33 | static void dummy_tlb_add_page(struct iommu_iotlb_gather *gather, |
| 34 | unsigned long iova, size_t granule, |
| 35 | void *cookie) |
| 36 | { |
| 37 | dummy_tlb_flush(iova, size: granule, granule, cookie); |
| 38 | } |
| 39 | |
| 40 | static const struct iommu_flush_ops dummy_tlb_ops = { |
| 41 | .tlb_flush_all = dummy_tlb_flush_all, |
| 42 | .tlb_flush_walk = dummy_tlb_flush, |
| 43 | .tlb_add_page = dummy_tlb_add_page, |
| 44 | }; |
| 45 | |
| 46 | #define __FAIL(test, i) ({ \ |
| 47 | KUNIT_FAIL(test, "test failed for fmt idx %d\n", (i)); \ |
| 48 | -EFAULT; \ |
| 49 | }) |
| 50 | |
| 51 | static int arm_lpae_run_tests(struct kunit *test, struct io_pgtable_cfg *cfg) |
| 52 | { |
| 53 | static const enum io_pgtable_fmt fmts[] = { |
| 54 | ARM_64_LPAE_S1, |
| 55 | ARM_64_LPAE_S2, |
| 56 | }; |
| 57 | |
| 58 | int i, j; |
| 59 | unsigned long iova; |
| 60 | size_t size, mapped; |
| 61 | struct io_pgtable_ops *ops; |
| 62 | |
| 63 | for (i = 0; i < ARRAY_SIZE(fmts); ++i) { |
| 64 | cfg_cookie = cfg; |
| 65 | ops = alloc_io_pgtable_ops(fmt: fmts[i], cfg, cookie: cfg); |
| 66 | if (!ops) { |
| 67 | kunit_err(test, "failed to allocate io pgtable ops\n" ); |
| 68 | return -ENOMEM; |
| 69 | } |
| 70 | |
| 71 | /* |
| 72 | * Initial sanity checks. |
| 73 | * Empty page tables shouldn't provide any translations. |
| 74 | */ |
| 75 | if (ops->iova_to_phys(ops, 42)) |
| 76 | return __FAIL(test, i); |
| 77 | |
| 78 | if (ops->iova_to_phys(ops, SZ_1G + 42)) |
| 79 | return __FAIL(test, i); |
| 80 | |
| 81 | if (ops->iova_to_phys(ops, SZ_2G + 42)) |
| 82 | return __FAIL(test, i); |
| 83 | |
| 84 | /* |
| 85 | * Distinct mappings of different granule sizes. |
| 86 | */ |
| 87 | iova = 0; |
| 88 | for_each_set_bit(j, &cfg->pgsize_bitmap, BITS_PER_LONG) { |
| 89 | size = 1UL << j; |
| 90 | |
| 91 | if (ops->map_pages(ops, iova, iova, size, 1, |
| 92 | IOMMU_READ | IOMMU_WRITE | |
| 93 | IOMMU_NOEXEC | IOMMU_CACHE, |
| 94 | GFP_KERNEL, &mapped)) |
| 95 | return __FAIL(test, i); |
| 96 | |
| 97 | /* Overlapping mappings */ |
| 98 | if (!ops->map_pages(ops, iova, iova + size, size, 1, |
| 99 | IOMMU_READ | IOMMU_NOEXEC, |
| 100 | GFP_KERNEL, &mapped)) |
| 101 | return __FAIL(test, i); |
| 102 | |
| 103 | if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) |
| 104 | return __FAIL(test, i); |
| 105 | |
| 106 | iova += SZ_1G; |
| 107 | } |
| 108 | |
| 109 | /* Full unmap */ |
| 110 | iova = 0; |
| 111 | for_each_set_bit(j, &cfg->pgsize_bitmap, BITS_PER_LONG) { |
| 112 | size = 1UL << j; |
| 113 | |
| 114 | if (ops->unmap_pages(ops, iova, size, 1, NULL) != size) |
| 115 | return __FAIL(test, i); |
| 116 | |
| 117 | if (ops->iova_to_phys(ops, iova + 42)) |
| 118 | return __FAIL(test, i); |
| 119 | |
| 120 | /* Remap full block */ |
| 121 | if (ops->map_pages(ops, iova, iova, size, 1, |
| 122 | IOMMU_WRITE, GFP_KERNEL, &mapped)) |
| 123 | return __FAIL(test, i); |
| 124 | |
| 125 | if (ops->iova_to_phys(ops, iova + 42) != (iova + 42)) |
| 126 | return __FAIL(test, i); |
| 127 | |
| 128 | iova += SZ_1G; |
| 129 | } |
| 130 | |
| 131 | /* |
| 132 | * Map/unmap the last largest supported page of the IAS, this can |
| 133 | * trigger corner cases in the concatednated page tables. |
| 134 | */ |
| 135 | mapped = 0; |
| 136 | size = 1UL << __fls(word: cfg->pgsize_bitmap); |
| 137 | iova = (1UL << cfg->ias) - size; |
| 138 | if (ops->map_pages(ops, iova, iova, size, 1, |
| 139 | IOMMU_READ | IOMMU_WRITE | |
| 140 | IOMMU_NOEXEC | IOMMU_CACHE, |
| 141 | GFP_KERNEL, &mapped)) |
| 142 | return __FAIL(test, i); |
| 143 | if (mapped != size) |
| 144 | return __FAIL(test, i); |
| 145 | if (ops->unmap_pages(ops, iova, size, 1, NULL) != size) |
| 146 | return __FAIL(test, i); |
| 147 | |
| 148 | free_io_pgtable_ops(ops); |
| 149 | } |
| 150 | |
| 151 | return 0; |
| 152 | } |
| 153 | |
| 154 | static void arm_lpae_do_selftests(struct kunit *test) |
| 155 | { |
| 156 | static const unsigned long pgsize[] = { |
| 157 | SZ_4K | SZ_2M | SZ_1G, |
| 158 | SZ_16K | SZ_32M, |
| 159 | SZ_64K | SZ_512M, |
| 160 | }; |
| 161 | |
| 162 | static const unsigned int address_size[] = { |
| 163 | 32, 36, 40, 42, 44, 48, |
| 164 | }; |
| 165 | |
| 166 | int i, j, k, pass = 0, fail = 0; |
| 167 | struct device *dev; |
| 168 | struct io_pgtable_cfg cfg = { |
| 169 | .tlb = &dummy_tlb_ops, |
| 170 | .coherent_walk = true, |
| 171 | .quirks = IO_PGTABLE_QUIRK_NO_WARN, |
| 172 | }; |
| 173 | |
| 174 | dev = kunit_device_register(test, name: "io-pgtable-test" ); |
| 175 | KUNIT_EXPECT_NOT_ERR_OR_NULL(test, dev); |
| 176 | if (IS_ERR_OR_NULL(ptr: dev)) |
| 177 | return; |
| 178 | |
| 179 | cfg.iommu_dev = dev; |
| 180 | |
| 181 | for (i = 0; i < ARRAY_SIZE(pgsize); ++i) { |
| 182 | for (j = 0; j < ARRAY_SIZE(address_size); ++j) { |
| 183 | /* Don't use ias > oas as it is not valid for stage-2. */ |
| 184 | for (k = 0; k <= j; ++k) { |
| 185 | cfg.pgsize_bitmap = pgsize[i]; |
| 186 | cfg.ias = address_size[k]; |
| 187 | cfg.oas = address_size[j]; |
| 188 | kunit_info(test, "pgsize_bitmap 0x%08lx, IAS %u OAS %u\n" , |
| 189 | pgsize[i], cfg.ias, cfg.oas); |
| 190 | if (arm_lpae_run_tests(test, cfg: &cfg)) |
| 191 | fail++; |
| 192 | else |
| 193 | pass++; |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | kunit_info(test, "completed with %d PASS %d FAIL\n" , pass, fail); |
| 199 | } |
| 200 | |
| 201 | static struct kunit_case io_pgtable_arm_test_cases[] = { |
| 202 | KUNIT_CASE(arm_lpae_do_selftests), |
| 203 | {}, |
| 204 | }; |
| 205 | |
| 206 | static struct kunit_suite io_pgtable_arm_test = { |
| 207 | .name = "io-pgtable-arm-test" , |
| 208 | .test_cases = io_pgtable_arm_test_cases, |
| 209 | }; |
| 210 | |
| 211 | kunit_test_suite(io_pgtable_arm_test); |
| 212 | |
| 213 | MODULE_DESCRIPTION("io-pgtable-arm library kunit tests" ); |
| 214 | MODULE_LICENSE("GPL" ); |
| 215 | |