diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig index 2f36f3469ca2..63a966d994cd 100644 --- a/arch/arm64/kvm/Kconfig +++ b/arch/arm64/kvm/Kconfig @@ -63,4 +63,10 @@ config KVM_S2MPU hypervisor to restrict DMA access to its memory and the memory of protected guests. +config TEST_KVM_S2MPU + tristate "Test kernel module for S2MPU" + depends on KVM_S2MPU + help + Kernel module for kselftests that exercises the KVM S2MPU driver. + endif # VIRTUALIZATION diff --git a/arch/arm64/kvm/iommu/Makefile b/arch/arm64/kvm/iommu/Makefile index 2a51f8cb2848..cb3242c77b4c 100644 --- a/arch/arm64/kvm/iommu/Makefile +++ b/arch/arm64/kvm/iommu/Makefile @@ -4,3 +4,4 @@ # obj-$(CONFIG_KVM_S2MPU) += s2mpu.o +obj-$(CONFIG_TEST_KVM_S2MPU) += test_kvm_s2mpu.o diff --git a/arch/arm64/kvm/iommu/test_kvm_s2mpu.c b/arch/arm64/kvm/iommu/test_kvm_s2mpu.c new file mode 100644 index 000000000000..67cc37f00dee --- /dev/null +++ b/arch/arm64/kvm/iommu/test_kvm_s2mpu.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 - Google LLC + * Author: David Brazdil + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "../../../tools/testing/selftests/kselftest_module.h" + +#include + +#include + +KSTM_MODULE_GLOBALS(); + +/* + * Kernel module for testing the foobinator + */ + +#define ASSERT(cond) \ + do { \ + if (!(cond)) { \ + pr_err("line %d: assertion failed: %s\n", \ + __LINE__, #cond); \ + return -1; \ + } \ + } while (0) + +static struct fmpt g_fmpt; +static u32 g_smpt[SMPT_NUM_WORDS]; + +static void __init init_smpt(enum mpt_prot prot) +{ + memset(g_smpt, (char)mpt_prot_doubleword[prot], SMPT_SIZE); +} + +static void __init init_fmpt(enum mpt_prot prot, bool gran_1g) +{ + init_smpt(prot); + g_fmpt = (struct fmpt){ + .gran_1g = gran_1g, + .prot = prot, + .smpt = g_smpt, + }; +} + +static enum mpt_prot __init get_prot_at(size_t gb_byte_off) +{ + size_t page_idx = gb_byte_off / SMPT_GRAN; + size_t word_idx = page_idx / SMPT_ELEMS_PER_WORD; + size_t bit_shift = (page_idx % SMPT_ELEMS_PER_WORD) * MPT_PROT_BITS; + + return (g_smpt[word_idx] >> bit_shift) & MPT_PROT_MASK; +} + +static bool __init check_smpt(size_t start_byte, size_t end_byte, + enum mpt_prot prot_out, enum mpt_prot prot_in) +{ + size_t off; + + for (off = 0; off < start_byte; off += PAGE_SIZE) { + if (get_prot_at(off) != prot_out) + return false; + } + for (off = start_byte; off < end_byte; off += PAGE_SIZE) { + if (get_prot_at(off) != prot_in) + return false; + } + for (off = end_byte; off < SZ_1G; off += PAGE_SIZE) { + if (get_prot_at(off) != prot_out) + return false; + } + return true; +} + +/* Start with 1G granule, overwrite the whole 1G. */ +static int __init test_set_fmpt__fmpt_to_fmpt_whole(void) +{ + enum mpt_update_flags flags; + + init_fmpt(MPT_PROT_NONE, /*gran_1g*/ true); + flags = __set_fmpt_range(&g_fmpt, 0, SZ_1G, MPT_PROT_R); + ASSERT(flags == MPT_UPDATE_L1); + ASSERT(g_fmpt.gran_1g); + ASSERT(g_fmpt.prot == MPT_PROT_R); + return 0; +} + +/* Start with 1G granule, overwrite the whole 1G with the same prot. */ +static int __init test_set_fmpt__fmpt_no_change_whole(void) +{ + enum mpt_update_flags flags; + + init_fmpt(MPT_PROT_R, /*gran_1g*/ true); + flags = __set_fmpt_range(&g_fmpt, 0, SZ_1G, MPT_PROT_R); + ASSERT(flags == 0); + ASSERT(g_fmpt.gran_1g); + ASSERT(g_fmpt.prot == MPT_PROT_R); + return 0; +} + +/* Start with 1G granule, partially overwrite with the same prot. */ +static int __init test_set_fmpt__fmpt_no_change_partial(void) +{ + enum mpt_update_flags flags; + + init_fmpt(MPT_PROT_R, /*gran_1g*/ true); + flags = __set_fmpt_range(&g_fmpt, 0, PAGE_SIZE, MPT_PROT_R); + ASSERT(flags == 0); + ASSERT(g_fmpt.gran_1g); + ASSERT(g_fmpt.prot == MPT_PROT_R); + return 0; +} + +/* Convert from 1G to PAGE_SIZE granule. */ +static int __init test_set_fmpt__fmpt_to_smpt(void) +{ + size_t start = 5 * SMPT_WORD_BYTE_RANGE / 2; + size_t end = 20 * SMPT_WORD_BYTE_RANGE; + enum mpt_update_flags flags; + + init_fmpt(MPT_PROT_R, /*gran_1g*/ true); + flags = __set_fmpt_range(&g_fmpt, start, end, MPT_PROT_RW); + ASSERT(flags == (MPT_UPDATE_L1 | MPT_UPDATE_L2)); + ASSERT(!g_fmpt.gran_1g); + return check_smpt(start, end, MPT_PROT_R, MPT_PROT_RW) ? 0 : 1; +} + +/* Convert from PAGE_SIZE to 1G granule by overwriting the whole 1G. */ +static int __init test_set_fmpt__smpt_to_fmpt_whole(void) +{ + enum mpt_update_flags flags; + + init_fmpt(MPT_PROT_NONE, /*gran_1g*/ false); + flags = __set_fmpt_range(&g_fmpt, 0, SZ_1G, MPT_PROT_R); + ASSERT(flags == MPT_UPDATE_L1); + ASSERT(g_fmpt.gran_1g); + ASSERT(g_fmpt.prot == MPT_PROT_R); + return 0; +} + +/* Convert from PAGE_SIZE to 1G granule by making the SMPT uniform. */ +static int __init test_set_fmpt__smpt_to_fmpt_partial(void) +{ + size_t start = 5 * SMPT_WORD_BYTE_RANGE / 2; + size_t end = 20 * SMPT_WORD_BYTE_RANGE; + enum mpt_update_flags flags; + + /* Create SMPT with all PROT_W except a small subrange. */ + init_fmpt(MPT_PROT_W, /*gran_1g*/ false); + __set_smpt_range(g_smpt, start, end, MPT_PROT_RW); + + /* Fill the subrange with PROT_W to make the SMPT uniform. */ + flags = __set_fmpt_range(&g_fmpt, start, end, MPT_PROT_W); + ASSERT(flags == MPT_UPDATE_L1); + ASSERT(g_fmpt.gran_1g); + ASSERT(g_fmpt.prot == MPT_PROT_W); + return 0; +} + +/* Keep PAGE_SIZE granule when SMPT not uniform after update. */ +static int __init test_set_fmpt__smpt_to_smpt(void) +{ + size_t start = SZ_1G - SMPT_GRAN; + size_t end = SZ_1G; + enum mpt_update_flags flags; + + init_fmpt(MPT_PROT_NONE, /*gran_1g*/ false); + ASSERT(__is_smpt_uniform(g_smpt, MPT_PROT_NONE)); + + /* Fill the subrange with PROT_W to make the SMPT uniform. */ + flags = __set_fmpt_range(&g_fmpt, start, end, MPT_PROT_RW); + ASSERT(flags == MPT_UPDATE_L2); + ASSERT(!g_fmpt.gran_1g); + ASSERT(!__is_smpt_uniform(g_smpt, MPT_PROT_NONE)); + return 0; +} + +static int __init __test_set_smpt(size_t start_byte, size_t end_byte) +{ + init_smpt(MPT_PROT_NONE); + __set_smpt_range(g_smpt, start_byte, end_byte, MPT_PROT_W); + return check_smpt(start_byte, end_byte, MPT_PROT_NONE, MPT_PROT_W) ? 0 : 1; +} + +/* Range within one SMPT word, force a fallback to __set_smpt_range_slow. */ +static int __init test_set_smpt__within_one_word(void) +{ + return __test_set_smpt(3 * SMPT_WORD_BYTE_RANGE + 5 * PAGE_SIZE, + 3 * SMPT_WORD_BYTE_RANGE + 6 * PAGE_SIZE); +} + +/* No whole SMPT word, force a fallback to __set_smpt_range_slow. */ +static int __init test_set_smpt__no_whole_word(void) +{ + return __test_set_smpt(3 * SMPT_WORD_BYTE_RANGE + 5 * PAGE_SIZE, + 4 * SMPT_WORD_BYTE_RANGE + 2 * PAGE_SIZE); +} + +/* Both start and end aligned to SMPT word. */ +static int __init test_set_smpt__no_prologue_or_epilogue(void) +{ + return __test_set_smpt(10 * SMPT_WORD_BYTE_RANGE, + 20 * SMPT_WORD_BYTE_RANGE); +} + +/* Start not aligned to SMPT word. */ +static int __init test_set_smpt__prologue(void) +{ + return __test_set_smpt(17 * SMPT_WORD_BYTE_RANGE / 2, + 20 * SMPT_WORD_BYTE_RANGE); +} + +/* End not aligned to SMPT word. */ +static int __init test_set_smpt__epilogue(void) +{ + return __test_set_smpt(0, 17 * SMPT_WORD_BYTE_RANGE / 2); +} + +/* Neither start nor end aligned to SMPT word. */ +static int __init test_set_smpt__prologue_and_epilogue(void) +{ + return __test_set_smpt(17 * SMPT_WORD_BYTE_RANGE / 2, + 31 * SMPT_WORD_BYTE_RANGE / 2); +} + +static int __init __test_set_smpt_slow(size_t start_byte, size_t end_byte) +{ + init_smpt(MPT_PROT_NONE); + __set_smpt_range_slow(g_smpt, start_byte, end_byte, MPT_PROT_RW); + return check_smpt(start_byte, end_byte, MPT_PROT_NONE, MPT_PROT_RW) ? 0 : 1; +} + +static int __init test_set_smpt_slow__empty_word_align(void) +{ + return __test_set_smpt_slow(3 * SMPT_WORD_BYTE_RANGE, + 3 * SMPT_WORD_BYTE_RANGE); +} + +static int __init test_set_smpt_slow__empty_page_align(void) +{ + return __test_set_smpt_slow(3 * SMPT_WORD_BYTE_RANGE + PAGE_SIZE, + 3 * SMPT_WORD_BYTE_RANGE + PAGE_SIZE); +} + +static int __init test_set_smpt_slow__one_whole_word(void) +{ + return __test_set_smpt_slow(3 * SMPT_WORD_BYTE_RANGE, + 4 * SMPT_WORD_BYTE_RANGE); +} + +static int __init test_set_smpt_slow__one_partial_word(void) +{ + return __test_set_smpt_slow(3 * SMPT_WORD_BYTE_RANGE + PAGE_SIZE, + 4 * SMPT_WORD_BYTE_RANGE - PAGE_SIZE); +} + +static int __init test_set_smpt_slow__multiple_whole_words(void) +{ + return __test_set_smpt_slow(13 * SMPT_WORD_BYTE_RANGE, + 17 * SMPT_WORD_BYTE_RANGE); +} + +static int __init test_set_smpt_slow__multiple_partial_words(void) +{ + return __test_set_smpt_slow((13 * 2 + 1) * SMPT_WORD_BYTE_RANGE / 2, + (17 * 4 + 1) * SMPT_WORD_BYTE_RANGE / 4); +} + +static void __init selftest(void) +{ + KSTM_CHECK_ZERO(test_set_fmpt__fmpt_to_fmpt_whole()); + KSTM_CHECK_ZERO(test_set_fmpt__fmpt_no_change_whole()); + KSTM_CHECK_ZERO(test_set_fmpt__fmpt_no_change_partial()); + KSTM_CHECK_ZERO(test_set_fmpt__fmpt_to_smpt()); + KSTM_CHECK_ZERO(test_set_fmpt__smpt_to_fmpt_whole()); + KSTM_CHECK_ZERO(test_set_fmpt__smpt_to_fmpt_partial()); + KSTM_CHECK_ZERO(test_set_fmpt__smpt_to_smpt()); + + KSTM_CHECK_ZERO(test_set_smpt__within_one_word()); + KSTM_CHECK_ZERO(test_set_smpt__no_whole_word()); + KSTM_CHECK_ZERO(test_set_smpt__no_prologue_or_epilogue()); + KSTM_CHECK_ZERO(test_set_smpt__prologue()); + KSTM_CHECK_ZERO(test_set_smpt__epilogue()); + KSTM_CHECK_ZERO(test_set_smpt__prologue_and_epilogue()); + + KSTM_CHECK_ZERO(test_set_smpt_slow__empty_word_align()); + KSTM_CHECK_ZERO(test_set_smpt_slow__empty_page_align()); + KSTM_CHECK_ZERO(test_set_smpt_slow__one_whole_word()); + KSTM_CHECK_ZERO(test_set_smpt_slow__one_partial_word()); + KSTM_CHECK_ZERO(test_set_smpt_slow__multiple_whole_words()); + KSTM_CHECK_ZERO(test_set_smpt_slow__multiple_partial_words()); +} + +KSTM_MODULE_LOADERS(test_kvm_s2mpu); +MODULE_AUTHOR("David Brazdil "); +MODULE_LICENSE("GPL v2"); diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile index d1774f461393..fa5f31c89399 100644 --- a/tools/testing/selftests/kvm/Makefile +++ b/tools/testing/selftests/kvm/Makefile @@ -99,6 +99,7 @@ TEST_GEN_PROGS_aarch64 += rseq_test TEST_GEN_PROGS_aarch64 += set_memory_region_test TEST_GEN_PROGS_aarch64 += steal_time TEST_GEN_PROGS_aarch64 += kvm_binary_stats_test +TEST_PROGS_aarch64 += aarch64/s2mpu.sh TEST_GEN_PROGS_s390x = s390x/memop TEST_GEN_PROGS_s390x += s390x/resets @@ -112,6 +113,7 @@ TEST_GEN_PROGS_s390x += set_memory_region_test TEST_GEN_PROGS_s390x += kvm_binary_stats_test TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M)) +TEST_PROGS += $(TEST_PROGS_$(UNAME_M)) LIBKVM += $(LIBKVM_$(UNAME_M)) INSTALL_HDR_PATH = $(top_srcdir)/usr diff --git a/tools/testing/selftests/kvm/aarch64/s2mpu.sh b/tools/testing/selftests/kvm/aarch64/s2mpu.sh new file mode 100755 index 000000000000..e822965e7ed9 --- /dev/null +++ b/tools/testing/selftests/kvm/aarch64/s2mpu.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Tests the printf infrastructure using test_printf kernel module. +$(dirname $0)/../kselftest/module.sh "s2mpu" test_kvm_s2mpu