root/drivers/iommu/io-pgtable-arm-selftests.c
// 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");