728 lines
19 KiB
C
728 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* CPU-agnostic ARM page table allocator.
|
|
* Host-specific functions. The rest is in io-pgtable-arm-common.c.
|
|
*
|
|
* Copyright (C) 2014 ARM Limited
|
|
*
|
|
* Author: Will Deacon <[email protected]>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "arm-lpae io-pgtable: " fmt
|
|
|
|
#include <linux/atomic.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/io-pgtable-arm.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <linux/dma-mapping.h>
|
|
|
|
#include "iommu-pages.h"
|
|
|
|
#include <asm/barrier.h>
|
|
|
|
static bool selftest_running = false;
|
|
|
|
int arm_lpae_map_exists(void)
|
|
{
|
|
WARN_ON(!selftest_running);
|
|
return -EEXIST;
|
|
}
|
|
|
|
void arm_lpae_unmap_empty(void)
|
|
{
|
|
WARN_ON(!selftest_running);
|
|
}
|
|
|
|
static dma_addr_t __arm_lpae_dma_addr(void *pages)
|
|
{
|
|
return (dma_addr_t)virt_to_phys(pages);
|
|
}
|
|
|
|
void *__arm_lpae_alloc_pages(size_t size, gfp_t gfp,
|
|
struct io_pgtable_cfg *cfg,
|
|
void *cookie)
|
|
{
|
|
struct device *dev = cfg->iommu_dev;
|
|
int order = get_order(size);
|
|
dma_addr_t dma;
|
|
void *pages;
|
|
|
|
VM_BUG_ON((gfp & __GFP_HIGHMEM));
|
|
|
|
if (cfg->alloc)
|
|
pages = cfg->alloc(cookie, size, gfp);
|
|
else
|
|
pages = iommu_alloc_pages_node(dev_to_node(dev), gfp, order);
|
|
|
|
if (!pages)
|
|
return NULL;
|
|
|
|
if (!cfg->coherent_walk) {
|
|
dma = dma_map_single(dev, pages, size, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(dev, dma))
|
|
goto out_free;
|
|
/*
|
|
* We depend on the IOMMU being able to work with any physical
|
|
* address directly, so if the DMA layer suggests otherwise by
|
|
* translating or truncating them, that bodes very badly...
|
|
*/
|
|
if (dma != virt_to_phys(pages))
|
|
goto out_unmap;
|
|
}
|
|
|
|
return pages;
|
|
|
|
out_unmap:
|
|
dev_err(dev, "Cannot accommodate DMA translation for IOMMU page tables\n");
|
|
dma_unmap_single(dev, dma, size, DMA_TO_DEVICE);
|
|
|
|
out_free:
|
|
if (cfg->free)
|
|
cfg->free(cookie, pages, size);
|
|
else
|
|
iommu_free_pages(pages, order);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void __arm_lpae_free_pages(void *pages, size_t size,
|
|
struct io_pgtable_cfg *cfg,
|
|
void *cookie)
|
|
{
|
|
if (!cfg->coherent_walk)
|
|
dma_unmap_single(cfg->iommu_dev, __arm_lpae_dma_addr(pages),
|
|
size, DMA_TO_DEVICE);
|
|
|
|
if (cfg->free)
|
|
cfg->free(cookie, pages, size);
|
|
else
|
|
iommu_free_pages(pages, get_order(size));
|
|
}
|
|
|
|
void __arm_lpae_sync_pte(arm_lpae_iopte *ptep, int num_entries,
|
|
struct io_pgtable_cfg *cfg)
|
|
{
|
|
dma_sync_single_for_device(cfg->iommu_dev, __arm_lpae_dma_addr(ptep),
|
|
sizeof(*ptep) * num_entries, DMA_TO_DEVICE);
|
|
}
|
|
|
|
static void arm_lpae_free_pgtable(struct io_pgtable *iop)
|
|
{
|
|
struct arm_lpae_io_pgtable *data = io_pgtable_to_data(iop);
|
|
|
|
__arm_lpae_free_pgtable(data, data->start_level, data->pgd);
|
|
kfree(data);
|
|
}
|
|
|
|
static int visit_dirty(struct io_pgtable_walk_data *walk_data, int lvl,
|
|
arm_lpae_iopte *ptep, size_t size)
|
|
{
|
|
struct io_pgtable_walk_common *walker = walk_data->data;
|
|
struct iommu_dirty_bitmap *dirty = walker->data;
|
|
|
|
if (!iopte_leaf(*ptep, lvl, walk_data->iop->fmt))
|
|
return 0;
|
|
|
|
if (iopte_writeable_dirty(*ptep)) {
|
|
iommu_dirty_bitmap_record(dirty, walk_data->addr, size);
|
|
if (!(walk_data->flags & IOMMU_DIRTY_NO_CLEAR))
|
|
iopte_set_writeable_clean(ptep);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int arm_lpae_read_and_clear_dirty(struct io_pgtable_ops *ops,
|
|
unsigned long iova, size_t size,
|
|
unsigned long flags,
|
|
struct iommu_dirty_bitmap *dirty)
|
|
{
|
|
struct arm_lpae_io_pgtable *data = io_pgtable_ops_to_data(ops);
|
|
struct io_pgtable_cfg *cfg = &data->iop.cfg;
|
|
struct io_pgtable_walk_common walker = {
|
|
.data = dirty,
|
|
};
|
|
struct io_pgtable_walk_data walk_data = {
|
|
.iop = &data->iop,
|
|
.data = &walker,
|
|
.visit = visit_dirty,
|
|
.flags = flags,
|
|
.addr = iova,
|
|
.end = iova + size,
|
|
};
|
|
arm_lpae_iopte *ptep = data->pgd;
|
|
int lvl = data->start_level;
|
|
|
|
if (WARN_ON(!size))
|
|
return -EINVAL;
|
|
if (WARN_ON((iova + size - 1) & ~(BIT(cfg->ias) - 1)))
|
|
return -EINVAL;
|
|
if (data->iop.fmt != ARM_64_LPAE_S1)
|
|
return -EINVAL;
|
|
|
|
return __arm_lpae_iopte_walk(data, &walk_data, ptep, lvl);
|
|
}
|
|
|
|
static struct io_pgtable *
|
|
arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie)
|
|
{
|
|
struct arm_lpae_io_pgtable *data;
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return NULL;
|
|
|
|
if (arm_lpae_init_pgtable_s1(cfg, data))
|
|
goto out_free_data;
|
|
|
|
data->iop.ops.read_and_clear_dirty = arm_lpae_read_and_clear_dirty;
|
|
/* Looking good; allocate a pgd */
|
|
data->pgd = __arm_lpae_alloc_pages(ARM_LPAE_PGD_SIZE(data),
|
|
GFP_KERNEL, cfg, cookie);
|
|
if (!data->pgd)
|
|
goto out_free_data;
|
|
|
|
/* Ensure the empty pgd is visible before any actual TTBR write */
|
|
wmb();
|
|
|
|
/* TTBR */
|
|
cfg->arm_lpae_s1_cfg.ttbr = virt_to_phys(data->pgd);
|
|
return &data->iop;
|
|
|
|
out_free_data:
|
|
kfree(data);
|
|
return NULL;
|
|
}
|
|
|
|
static int arm_64_lpae_configure_s1(struct io_pgtable_cfg *cfg)
|
|
{
|
|
struct arm_lpae_io_pgtable data = {};
|
|
|
|
return arm_lpae_init_pgtable_s1(cfg, &data);
|
|
}
|
|
|
|
static struct io_pgtable *
|
|
arm_64_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie)
|
|
{
|
|
struct arm_lpae_io_pgtable *data;
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return NULL;
|
|
|
|
if (arm_lpae_init_pgtable_s2(cfg, data))
|
|
goto out_free_data;
|
|
|
|
data->iop.ops.read_and_clear_dirty = arm_lpae_read_and_clear_dirty;
|
|
/* Allocate pgd pages */
|
|
data->pgd = __arm_lpae_alloc_pages(ARM_LPAE_PGD_SIZE(data),
|
|
GFP_KERNEL, cfg, cookie);
|
|
if (!data->pgd)
|
|
goto out_free_data;
|
|
|
|
/* Ensure the empty pgd is visible before any actual TTBR write */
|
|
wmb();
|
|
|
|
/* VTTBR */
|
|
cfg->arm_lpae_s2_cfg.vttbr = virt_to_phys(data->pgd);
|
|
return &data->iop;
|
|
|
|
out_free_data:
|
|
kfree(data);
|
|
return NULL;
|
|
}
|
|
|
|
static int arm_64_lpae_configure_s2(struct io_pgtable_cfg *cfg)
|
|
{
|
|
struct arm_lpae_io_pgtable data = {};
|
|
|
|
return arm_lpae_init_pgtable_s2(cfg, &data);
|
|
}
|
|
|
|
static struct io_pgtable *
|
|
arm_32_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie)
|
|
{
|
|
if (cfg->ias > 32 || cfg->oas > 40)
|
|
return NULL;
|
|
|
|
cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G);
|
|
return arm_64_lpae_alloc_pgtable_s1(cfg, cookie);
|
|
}
|
|
|
|
static struct io_pgtable *
|
|
arm_32_lpae_alloc_pgtable_s2(struct io_pgtable_cfg *cfg, void *cookie)
|
|
{
|
|
if (cfg->ias > 40 || cfg->oas > 40)
|
|
return NULL;
|
|
|
|
cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G);
|
|
return arm_64_lpae_alloc_pgtable_s2(cfg, cookie);
|
|
}
|
|
|
|
static struct io_pgtable *
|
|
arm_mali_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
|
|
{
|
|
struct arm_lpae_io_pgtable *data;
|
|
|
|
/* No quirks for Mali (hopefully) */
|
|
if (cfg->quirks)
|
|
return NULL;
|
|
|
|
if (cfg->ias > 48 || cfg->oas > 40)
|
|
return NULL;
|
|
|
|
cfg->pgsize_bitmap &= (SZ_4K | SZ_2M | SZ_1G);
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return NULL;
|
|
|
|
if (arm_lpae_init_pgtable(cfg, data))
|
|
return NULL;
|
|
|
|
data->iop.ops.read_and_clear_dirty = arm_lpae_read_and_clear_dirty;
|
|
/* Mali seems to need a full 4-level table regardless of IAS */
|
|
if (data->start_level > 0) {
|
|
data->start_level = 0;
|
|
data->pgd_bits = 0;
|
|
}
|
|
/*
|
|
* MEMATTR: Mali has no actual notion of a non-cacheable type, so the
|
|
* best we can do is mimic the out-of-tree driver and hope that the
|
|
* "implementation-defined caching policy" is good enough. Similarly,
|
|
* we'll use it for the sake of a valid attribute for our 'device'
|
|
* index, although callers should never request that in practice.
|
|
*/
|
|
cfg->arm_mali_lpae_cfg.memattr =
|
|
(ARM_MALI_LPAE_MEMATTR_IMP_DEF
|
|
<< ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_NC)) |
|
|
(ARM_MALI_LPAE_MEMATTR_WRITE_ALLOC
|
|
<< ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_CACHE)) |
|
|
(ARM_MALI_LPAE_MEMATTR_IMP_DEF
|
|
<< ARM_LPAE_MAIR_ATTR_SHIFT(ARM_LPAE_MAIR_ATTR_IDX_DEV));
|
|
|
|
data->pgd = __arm_lpae_alloc_pages(ARM_LPAE_PGD_SIZE(data), GFP_KERNEL,
|
|
cfg, cookie);
|
|
if (!data->pgd)
|
|
goto out_free_data;
|
|
|
|
/* Ensure the empty pgd is visible before TRANSTAB can be written */
|
|
wmb();
|
|
|
|
cfg->arm_mali_lpae_cfg.transtab = virt_to_phys(data->pgd) |
|
|
ARM_MALI_LPAE_TTBR_READ_INNER |
|
|
ARM_MALI_LPAE_TTBR_ADRMODE_TABLE;
|
|
if (cfg->coherent_walk)
|
|
cfg->arm_mali_lpae_cfg.transtab |= ARM_MALI_LPAE_TTBR_SHARE_OUTER;
|
|
|
|
return &data->iop;
|
|
|
|
out_free_data:
|
|
kfree(data);
|
|
return NULL;
|
|
}
|
|
|
|
struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns = {
|
|
.caps = IO_PGTABLE_CAP_CUSTOM_ALLOCATOR,
|
|
.alloc = arm_64_lpae_alloc_pgtable_s1,
|
|
.free = arm_lpae_free_pgtable,
|
|
.configure = arm_64_lpae_configure_s1,
|
|
};
|
|
|
|
struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns = {
|
|
.caps = IO_PGTABLE_CAP_CUSTOM_ALLOCATOR,
|
|
.alloc = arm_64_lpae_alloc_pgtable_s2,
|
|
.free = arm_lpae_free_pgtable,
|
|
.configure = arm_64_lpae_configure_s2,
|
|
};
|
|
|
|
struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns = {
|
|
.caps = IO_PGTABLE_CAP_CUSTOM_ALLOCATOR,
|
|
.alloc = arm_32_lpae_alloc_pgtable_s1,
|
|
.free = arm_lpae_free_pgtable,
|
|
};
|
|
|
|
struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s2_init_fns = {
|
|
.caps = IO_PGTABLE_CAP_CUSTOM_ALLOCATOR,
|
|
.alloc = arm_32_lpae_alloc_pgtable_s2,
|
|
.free = arm_lpae_free_pgtable,
|
|
};
|
|
|
|
struct io_pgtable_init_fns io_pgtable_arm_mali_lpae_init_fns = {
|
|
.caps = IO_PGTABLE_CAP_CUSTOM_ALLOCATOR,
|
|
.alloc = arm_mali_lpae_alloc_pgtable,
|
|
.free = arm_lpae_free_pgtable,
|
|
};
|
|
|
|
#ifdef CONFIG_IOMMU_IO_PGTABLE_LPAE_SELFTEST
|
|
|
|
static struct io_pgtable_cfg *cfg_cookie __initdata;
|
|
static struct io_pgtable_ops *cur_ops;
|
|
|
|
static void __init dummy_tlb_flush_all(void *cookie)
|
|
{
|
|
WARN_ON(cookie != cfg_cookie);
|
|
}
|
|
|
|
static unsigned long skip_addr = 0xFFFF;
|
|
static void arm_lpae_selftest_validate(phys_addr_t addr, size_t size,
|
|
struct io_pgtable_walk_common *data,
|
|
void *wd)
|
|
{
|
|
struct arm_lpae_io_pgtable_walk_data *arm_wd = data->data;
|
|
unsigned long *iova = (unsigned long *)(arm_wd->cookie);
|
|
arm_lpae_iopte *ptep = wd;
|
|
|
|
/* PASS */
|
|
if (*iova == addr)
|
|
*iova = *iova + size;
|
|
|
|
WARN_ON(skip_addr == addr);
|
|
WARN_ON(!(*ptep));
|
|
*ptep = 0;
|
|
}
|
|
|
|
static void __init dummy_tlb_flush(unsigned long iova, size_t size,
|
|
size_t granule, void *cookie)
|
|
|
|
{
|
|
unsigned long iova_cookie = iova;
|
|
struct arm_lpae_io_pgtable_walk_data wd = {
|
|
.cookie = &iova_cookie,
|
|
};
|
|
struct io_pgtable_walk_common walk_data = {
|
|
.visit_leaf = arm_lpae_selftest_validate,
|
|
.data = &wd,
|
|
};
|
|
|
|
if (cur_ops && (cfg_cookie->quirks & IO_PGTABLE_QUIRK_UNMAP_INVAL)) {
|
|
/* Not straight forward to propagate failures, so WARN_ON is noisy enough. */
|
|
cur_ops->pgtable_walk(cur_ops, iova, size, &walk_data);
|
|
}
|
|
|
|
WARN_ON(cookie != cfg_cookie);
|
|
WARN_ON(!(size & cfg_cookie->pgsize_bitmap));
|
|
}
|
|
|
|
static void __init dummy_tlb_add_page(struct iommu_iotlb_gather *gather,
|
|
unsigned long iova, size_t granule,
|
|
void *cookie)
|
|
{
|
|
WARN_ON(cookie != cfg_cookie);
|
|
WARN_ON(!(granule & cfg_cookie->pgsize_bitmap));
|
|
}
|
|
|
|
static const struct iommu_flush_ops dummy_tlb_ops __initconst = {
|
|
.tlb_flush_all = dummy_tlb_flush_all,
|
|
.tlb_flush_walk = dummy_tlb_flush,
|
|
.tlb_add_page = dummy_tlb_add_page,
|
|
};
|
|
|
|
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, %u pg_shift, %u bits_per_level, pgd @ %p\n",
|
|
ARM_LPAE_MAX_LEVELS - data->start_level, ARM_LPAE_PGD_SIZE(data),
|
|
ilog2(ARM_LPAE_GRANULE(data)), 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[] __initconst = {
|
|
ARM_64_LPAE_S1,
|
|
ARM_64_LPAE_S2,
|
|
};
|
|
|
|
int i, j;
|
|
unsigned long iova, iova_cookie;
|
|
size_t size, mapped;
|
|
struct io_pgtable_ops *ops;
|
|
struct arm_lpae_io_pgtable_walk_data arm_wd;
|
|
struct io_pgtable_walk_common common_wd;
|
|
int ret;
|
|
|
|
selftest_running = true;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(fmts); ++i) {
|
|
cfg_cookie = cfg;
|
|
cfg->quirks = 0;
|
|
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;
|
|
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(ops, i);
|
|
|
|
/* Overlapping mappings */
|
|
if (!ops->map_pages(ops, iova, iova + size, size, 1,
|
|
IOMMU_READ | IOMMU_NOEXEC,
|
|
GFP_KERNEL, &mapped))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
|
|
return __FAIL(ops, i);
|
|
|
|
iova += SZ_1G;
|
|
}
|
|
|
|
/* Partial unmap */
|
|
size = 1UL << __ffs(cfg->pgsize_bitmap);
|
|
if (ops->unmap_pages(ops, SZ_1G + size, size, 1, NULL) != size)
|
|
return __FAIL(ops, i);
|
|
|
|
/* Remap of partial unmap */
|
|
if (ops->map_pages(ops, SZ_1G + size, size, size, 1,
|
|
IOMMU_READ, GFP_KERNEL, &mapped))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->iova_to_phys(ops, SZ_1G + size + 42) != (size + 42))
|
|
return __FAIL(ops, i);
|
|
|
|
/* 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(ops, i);
|
|
|
|
if (ops->iova_to_phys(ops, iova + 42))
|
|
return __FAIL(ops, i);
|
|
|
|
/* Remap full block */
|
|
if (ops->map_pages(ops, iova, iova, size, 1,
|
|
IOMMU_WRITE, GFP_KERNEL, &mapped))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
|
|
return __FAIL(ops, i);
|
|
|
|
iova += SZ_1G;
|
|
}
|
|
|
|
free_io_pgtable_ops(ops);
|
|
|
|
/* Test: IO_PGTABLE_QUIRK_UNMAP_INVAL */
|
|
cfg->quirks = IO_PGTABLE_QUIRK_UNMAP_INVAL;
|
|
ops = alloc_io_pgtable_ops(fmts[i], cfg, cfg);
|
|
cur_ops = ops;
|
|
if (!ops) {
|
|
pr_err("selftest: failed to allocate io pgtable ops with IO_PGTABLE_QUIRK_UNMAP_INVAL\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
common_wd.visit_leaf = arm_lpae_selftest_validate;
|
|
common_wd.data = &arm_wd;
|
|
arm_wd.cookie = &iova_cookie;
|
|
|
|
/*
|
|
* Map with leaf size => unmap with leaf size
|
|
* Then walk the table to check the pages
|
|
*/
|
|
size = 1UL << __ffs(cfg->pgsize_bitmap);
|
|
iova = size * 3; /* Arbitrary aligned address. */
|
|
if (ops->map_pages(ops, iova, iova, size, 1,
|
|
IOMMU_READ | IOMMU_WRITE |
|
|
IOMMU_NOEXEC | IOMMU_CACHE,
|
|
GFP_KERNEL, &mapped))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
|
|
return __FAIL(ops, i);
|
|
|
|
iova_cookie = iova;
|
|
ret = ops->pgtable_walk(ops, iova, size, &common_wd);
|
|
if (ret || (iova_cookie != iova + size))
|
|
return __FAIL(ops, i);
|
|
|
|
/*
|
|
* Map with leaf size => partial unmap with leaf size
|
|
* Then walk the table to check the pages
|
|
*/
|
|
if (ops->map_pages(ops, iova, iova, size, 42,
|
|
IOMMU_READ | IOMMU_WRITE |
|
|
IOMMU_NOEXEC | IOMMU_CACHE,
|
|
GFP_KERNEL, &mapped))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->unmap_pages(ops, iova + 41 * size, size, 1, NULL) != size)
|
|
return __FAIL(ops, i);
|
|
|
|
iova_cookie = iova + 41 * size;
|
|
ret = ops->pgtable_walk(ops, iova_cookie, size, &common_wd);
|
|
if (ret || (iova_cookie != (iova + 42 * size)))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->unmap_pages(ops, iova, size, 41, NULL) != 41 * size)
|
|
return __FAIL(ops, i);
|
|
|
|
iova_cookie = iova;
|
|
ret = ops->pgtable_walk(ops, iova, size * 41, &common_wd);
|
|
if (ret || (iova_cookie != (iova + 41 * size)))
|
|
return __FAIL(ops, 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(ops, i);
|
|
|
|
/* Overlapping mappings */
|
|
if (!ops->map_pages(ops, iova, iova + size, size, 1,
|
|
IOMMU_READ | IOMMU_NOEXEC,
|
|
GFP_KERNEL, &mapped))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->iova_to_phys(ops, iova + 42) != (iova + 42))
|
|
return __FAIL(ops, i);
|
|
|
|
iova += SZ_1G;
|
|
}
|
|
|
|
/* Partial unmap (split blk) */
|
|
size = 1UL << __ffs(cfg->pgsize_bitmap);
|
|
iova = SZ_1G + size;
|
|
if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
|
|
return __FAIL(ops, i);
|
|
|
|
/* Now we have table instead of block, with missing entry let's see */
|
|
iova_cookie = iova;
|
|
ret = ops->pgtable_walk(ops, iova, size, &common_wd);
|
|
if (ret || (iova_cookie != (iova + size)))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->iova_to_phys(ops, iova))
|
|
return __FAIL(ops, i);
|
|
|
|
/*
|
|
* Let's replace with a block again.
|
|
* We expect the freed table will be called in tlb_flush_walk()
|
|
* that's how we can track the unmapped pages.
|
|
*/
|
|
size = 1ULL << __ffs(cfg->pgsize_bitmap & ~(1UL << __ffs(cfg->pgsize_bitmap)));
|
|
/* Already unmapped shouldn't walk it again! */
|
|
skip_addr = SZ_1G + size;
|
|
iova = SZ_1G;
|
|
iova_cookie = iova;
|
|
if (ops->map_pages(ops, iova, iova, size, 1,
|
|
IOMMU_READ, GFP_KERNEL, &mapped))
|
|
return __FAIL(ops, i);
|
|
skip_addr = 0XFFFF;
|
|
|
|
/* Let's break the block to table again, this time at the start. */
|
|
size = 1UL << __ffs(cfg->pgsize_bitmap);
|
|
|
|
if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
|
|
return __FAIL(ops, i);
|
|
|
|
/* Now we have table instead of block, with missing entry let's see */
|
|
iova_cookie = iova;
|
|
ret = ops->pgtable_walk(ops, iova_cookie, size, &common_wd);
|
|
if (ret || (iova_cookie != (iova + size)))
|
|
return __FAIL(ops, i);
|
|
|
|
if (ops->iova_to_phys(ops, iova))
|
|
return __FAIL(ops, i);
|
|
|
|
/* Let's unmap the whole table at once. */
|
|
size = 1ULL << __ffs(cfg->pgsize_bitmap & ~(1UL << __ffs(cfg->pgsize_bitmap)));
|
|
skip_addr = iova;
|
|
if (ops->unmap_pages(ops, iova, size, 1, NULL) != size)
|
|
return __FAIL(ops, i);
|
|
skip_addr = 0xFFFF;
|
|
cur_ops = NULL;
|
|
free_io_pgtable_ops(ops);
|
|
}
|
|
|
|
selftest_running = false;
|
|
return 0;
|
|
}
|
|
|
|
static int __init arm_lpae_do_selftests(void)
|
|
{
|
|
static const unsigned long pgsize[] __initconst = {
|
|
SZ_4K | SZ_2M | SZ_1G,
|
|
SZ_16K | SZ_32M,
|
|
SZ_64K | SZ_512M,
|
|
};
|
|
|
|
static const unsigned int ias[] __initconst = {
|
|
32, 36, 40, 42, 44, 48,
|
|
};
|
|
|
|
int i, j, pass = 0, fail = 0;
|
|
struct device dev;
|
|
struct io_pgtable_cfg cfg = {
|
|
.tlb = &dummy_tlb_ops,
|
|
.oas = 48,
|
|
.coherent_walk = true,
|
|
.iommu_dev = &dev,
|
|
};
|
|
|
|
/* __arm_lpae_alloc_pages() merely needs dev_to_node() to work */
|
|
set_dev_node(&dev, NUMA_NO_NODE);
|
|
|
|
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
|