mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 14:41:39 +00:00
iommu: add self-consistency tests to ARM LPAE IO page table allocator
This patch adds a series of basic self-consistency tests to the ARM LPAE IO page table allocator that exercise corner cases in map/unmap, as well as testing all valid configurations of pagesize, ias and stage. Signed-off-by: Will Deacon <will.deacon@arm.com>
This commit is contained in:
parent
e1d3c0fd70
commit
fe4b991dcd
@ -28,6 +28,15 @@ config IOMMU_IO_PGTABLE_LPAE
|
||||
sizes at both stage-1 and stage-2, as well as address spaces
|
||||
up to 48-bits in size.
|
||||
|
||||
config IOMMU_IO_PGTABLE_LPAE_SELFTEST
|
||||
bool "LPAE selftests"
|
||||
depends on IOMMU_IO_PGTABLE_LPAE
|
||||
help
|
||||
Enable self-tests for LPAE page table allocator. This performs
|
||||
a series of page-table consistency checks during boot.
|
||||
|
||||
If unsure, say N here.
|
||||
|
||||
endmenu
|
||||
|
||||
config OF_IOMMU
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user