From: Xiaochun Yu Add focused tests for the correctness boundaries of a resident guest Vector owner. The lifetime test destroys a vCPU with resident Vector data before creating a new vCPU and verifies that the new vCPU cannot inherit it. The ONE_REG test checks that GET preserves guest data and SET becomes visible on the next entry. The fault test crosses into a protected page to verify that a partially completed SET_ONE_REG copy cannot leave stale hardware state visible to the guest. Pin each test to one host CPU so the same-hart owner path is deterministic when the optional optimization is enabled. Size the Vector buffers for the architectural maximum VLEN so the tests are not limited to the VLEN implemented by the development platform. Development note: OpenAI Codex assisted with source analysis, selftest drafting, test orchestration and changelog preparation. Signed-off-by: Xiaochun Yu --- tools/testing/selftests/kvm/Makefile.kvm | 3 + .../kvm/riscv/vector_owner_fault_test.c | 147 ++++++++++++++ .../kvm/riscv/vector_owner_lifetime_test.c | 127 ++++++++++++ .../kvm/riscv/vector_owner_onereg_test.c | 191 ++++++++++++++++++ 4 files changed, 468 insertions(+) create mode 100644 tools/testing/selftests/kvm/riscv/vector_owner_fault_test.c create mode 100644 tools/testing/selftests/kvm/riscv/vector_owner_lifetime_test.c create mode 100644 tools/testing/selftests/kvm/riscv/vector_owner_onereg_test.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 3cb35a6963d5..50a1f247ebc3 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -236,6 +236,9 @@ TEST_GEN_PROGS_s390 += s390/irq_injection TEST_GEN_PROGS_riscv = $(TEST_GEN_PROGS_COMMON) TEST_GEN_PROGS_riscv += riscv/sbi_pmu_test TEST_GEN_PROGS_riscv += riscv/ebreak_test +TEST_GEN_PROGS_riscv += riscv/vector_owner_lifetime_test +TEST_GEN_PROGS_riscv += riscv/vector_owner_onereg_test +TEST_GEN_PROGS_riscv += riscv/vector_owner_fault_test TEST_GEN_PROGS_riscv += access_tracking_perf_test TEST_GEN_PROGS_riscv += arch_timer TEST_GEN_PROGS_riscv += coalesced_io_test diff --git a/tools/testing/selftests/kvm/riscv/vector_owner_fault_test.c b/tools/testing/selftests/kvm/riscv/vector_owner_fault_test.c new file mode 100644 index 000000000000..a25dd21db77f --- /dev/null +++ b/tools/testing/selftests/kvm/riscv/vector_owner_fault_test.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Check that failed Vector SET_ONE_REG copies cannot leave a stale owner. */ +#include +#include +#include +#include +#include + +#include + +#include "kvm_syscalls.h" +#include "kvm_util.h" +#include "processor.h" +#include "ucall_common.h" + +#define OLD_PATTERN 0x31 +#define NEW_PATTERN 0xa7 + +static unsigned char source[RISCV_MAX_VLENB] __aligned(64); +static unsigned char observed[RISCV_MAX_VLENB] __aligned(64); + +static void guest_code(void) +{ + unsigned long i, vlenb; + + csr_set(CSR_SSTATUS, SR_VS_INITIAL); + vlenb = csr_read(CSR_VLENB); + GUEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB); + for (i = 0; i < vlenb; i++) + source[i] = OLD_PATTERN; + + asm volatile(".option push\n" + ".option arch, +v\n" + "vsetvli zero, %1, e8, m1, ta, ma\n" + "vle8.v v0, (%0)\n" + ".option pop\n" + : + : "r"(source), "r"(vlenb) + : "memory"); + GUEST_SYNC(1); + + asm volatile(".option push\n" + ".option arch, +v\n" + "vsetvli zero, %1, e8, m1, ta, ma\n" + "vse8.v v0, (%0)\n" + ".option pop\n" + : + : "r"(observed), "r"(vlenb) + : "memory"); + GUEST_DONE(); +} + +static void pin_to_one_cpu(void) +{ + cpu_set_t allowed, pinned; + int cpu; + + kvm_sched_getaffinity(0, sizeof(allowed), &allowed); + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { + if (CPU_ISSET(cpu, &allowed)) + break; + } + TEST_ASSERT(cpu < CPU_SETSIZE, "no CPU available"); + CPU_ZERO(&pinned); + CPU_SET(cpu, &pinned); + kvm_sched_setaffinity(0, sizeof(pinned), &pinned); +} + +static void run_to(struct kvm_vcpu *vcpu, u64 expected) +{ + struct ucall uc; + u64 cmd; + + vcpu_run(vcpu); + cmd = get_ucall(vcpu, &uc); + if (cmd == UCALL_ABORT) + REPORT_GUEST_ASSERT(uc); + TEST_ASSERT_EQ(cmd, expected); + if (expected == UCALL_SYNC) + TEST_ASSERT_EQ(uc.args[1], 1); +} + +int main(void) +{ + unsigned char expected[RISCV_MAX_VLENB] __aligned(64) = {}; + struct kvm_vm *vm; + struct kvm_vcpu *vcpu; + struct kvm_one_reg reg; + unsigned char *mapping; + unsigned long i, vlenb; + u64 vlenb_id, v0_id, size; + long page_size; + bool changed = false; + int ret, saved_errno; + + pin_to_one_cpu(); + vm = vm_create_with_one_vcpu(&vcpu, guest_code); + TEST_REQUIRE(__vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V)); + vm_init_vector_tables(vm); + vcpu_init_vector_tables(vcpu); + vlenb_id = KVM_REG_RISCV | KVM_REG_RISCV_VECTOR | + KVM_REG_SIZE_ULONG | KVM_REG_RISCV_VECTOR_CSR_REG(vlenb); + ret = __vcpu_get_reg(vcpu, vlenb_id, &vlenb); + TEST_ASSERT(!ret, "GET vlenb failed: %d", ret); + TEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB && + !(vlenb & (vlenb - 1)), "bad vlenb: %lu", vlenb); + size = (u64)__builtin_ctzl(vlenb) << KVM_REG_SIZE_SHIFT; + v0_id = KVM_REG_RISCV | KVM_REG_RISCV_VECTOR | size | + KVM_REG_RISCV_VECTOR_REG(0); + + page_size = sysconf(_SC_PAGESIZE); + TEST_ASSERT(page_size > 0 && vlenb <= 2 * page_size, "bad page size"); + mapping = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + TEST_ASSERT(mapping != MAP_FAILED, "mmap failed: %d", errno); + memset(mapping, NEW_PATTERN, page_size); + ret = mprotect(mapping + page_size, page_size, PROT_NONE); + TEST_ASSERT(!ret, "mprotect failed: %d", errno); + reg.id = v0_id; + reg.addr = (u64)(mapping + page_size - vlenb / 2); + + run_to(vcpu, UCALL_SYNC); + ret = __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, ®); + saved_errno = errno; + TEST_ASSERT(ret == -1 && saved_errno == EFAULT, + "SET should fault: ret=%d errno=%d", ret, saved_errno); + ret = __vcpu_get_reg(vcpu, v0_id, expected); + TEST_ASSERT(!ret, "GET v0 after failed SET: %d", ret); + + /* Compare with memory, without assuming an exact partial-copy length. */ + run_to(vcpu, UCALL_DONE); + sync_global_from_guest(vm, observed); + for (i = 0; i < vlenb; i++) + changed |= expected[i] != OLD_PATTERN; + TEST_ASSERT(changed, "failed copy did not exercise a memory update"); + for (i = 0; i < vlenb; i++) + TEST_ASSERT(observed[i] == expected[i], + "partial fault byte %lu: guest=%#x memory=%#x", + i, observed[i], expected[i]); + + pr_info("Vector SET_ONE_REG partial fault test passed (vlenb=%lu)\n", + vlenb); + kvm_vm_free(vm); + ret = munmap(mapping, 2 * page_size); + TEST_ASSERT(!ret, "munmap failed: %d", errno); + return 0; +} diff --git a/tools/testing/selftests/kvm/riscv/vector_owner_lifetime_test.c b/tools/testing/selftests/kvm/riscv/vector_owner_lifetime_test.c new file mode 100644 index 000000000000..c7b057b27cd0 --- /dev/null +++ b/tools/testing/selftests/kvm/riscv/vector_owner_lifetime_test.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Validate that a stale per-hart Vector owner from a destroyed vCPU cannot + * be treated as the owner of a different vCPU. + */ +#include + +#include + +#include "kvm_syscalls.h" +#include "kvm_util.h" +#include "processor.h" +#include "ucall_common.h" + +#define OLD_PATTERN 0x5d + +static unsigned char old_source[RISCV_MAX_VLENB] __aligned(64); +static unsigned char new_observed[RISCV_MAX_VLENB] __aligned(64); + +static void vector_load(const void *source, unsigned long vlenb) +{ + asm volatile(".option push\n" + ".option arch, +v\n" + "vsetvli zero, %1, e8, m1, ta, ma\n" + "vle8.v v0, (%0)\n" + ".option pop\n" + : + : "r"(source), "r"(vlenb) + : "memory"); +} + +static void vector_store(void *destination, unsigned long vlenb) +{ + asm volatile(".option push\n" + ".option arch, +v\n" + "vsetvli zero, %1, e8, m1, ta, ma\n" + "vse8.v v0, (%0)\n" + ".option pop\n" + : + : "r"(destination), "r"(vlenb) + : "memory"); +} + +static void old_guest(void) +{ + unsigned long i, vlenb; + + csr_set(CSR_SSTATUS, SR_VS_INITIAL); + vlenb = csr_read(CSR_VLENB); + GUEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB); + for (i = 0; i < vlenb; i++) + old_source[i] = OLD_PATTERN; + vector_load(old_source, vlenb); + GUEST_DONE(); +} + +static void new_guest(void) +{ + unsigned long i, vlenb; + + csr_set(CSR_SSTATUS, SR_VS_INITIAL); + vlenb = csr_read(CSR_VLENB); + GUEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB); + vector_store(new_observed, vlenb); + for (i = 0; i < vlenb; i++) + GUEST_ASSERT_EQ(new_observed[i], 0); + GUEST_DONE(); +} + +static void pin_to_one_cpu(void) +{ + cpu_set_t allowed, pinned; + int cpu; + + kvm_sched_getaffinity(0, sizeof(allowed), &allowed); + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { + if (CPU_ISSET(cpu, &allowed)) + break; + } + TEST_ASSERT(cpu < CPU_SETSIZE, "no CPU available"); + + CPU_ZERO(&pinned); + CPU_SET(cpu, &pinned); + kvm_sched_setaffinity(0, sizeof(pinned), &pinned); +} + +static void run_to_done(struct kvm_vcpu *vcpu) +{ + struct ucall uc; + u64 cmd; + + vcpu_run(vcpu); + cmd = get_ucall(vcpu, &uc); + if (cmd == UCALL_ABORT) + REPORT_GUEST_ASSERT(uc); + TEST_ASSERT_EQ(cmd, UCALL_DONE); +} + +static struct kvm_vm *create_vector_vm(struct kvm_vcpu **vcpu, + void (*guest_code)(void)) +{ + struct kvm_vm *vm; + + vm = vm_create_with_one_vcpu(vcpu, guest_code); + TEST_REQUIRE(__vcpu_has_isa_ext(*vcpu, KVM_RISCV_ISA_EXT_V)); + vm_init_vector_tables(vm); + vcpu_init_vector_tables(*vcpu); + return vm; +} + +int main(void) +{ + struct kvm_vm *old_vm, *new_vm; + struct kvm_vcpu *old_vcpu, *new_vcpu; + + pin_to_one_cpu(); + old_vm = create_vector_vm(&old_vcpu, old_guest); + run_to_done(old_vcpu); + kvm_vm_free(old_vm); + + new_vm = create_vector_vm(&new_vcpu, new_guest); + run_to_done(new_vcpu); + kvm_vm_free(new_vm); + + pr_info("Vector owner lifetime test passed\n"); + return 0; +} diff --git a/tools/testing/selftests/kvm/riscv/vector_owner_onereg_test.c b/tools/testing/selftests/kvm/riscv/vector_owner_onereg_test.c new file mode 100644 index 000000000000..a1bd83e4808b --- /dev/null +++ b/tools/testing/selftests/kvm/riscv/vector_owner_onereg_test.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Validate RISC-V KVM Vector KVM_GET/SET_ONE_REG interactions while the + * vCPU repeatedly runs on one physical CPU. + */ +#include +#include + +#include + +#include "kvm_syscalls.h" +#include "kvm_util.h" +#include "processor.h" +#include "ucall_common.h" + +#define GUEST_PATTERN 0x31 +#define SET_PATTERN 0xa7 + +static unsigned char guest_source[RISCV_MAX_VLENB] __aligned(64); +static unsigned char guest_after_get[RISCV_MAX_VLENB] __aligned(64); +static unsigned char guest_after_set[RISCV_MAX_VLENB] __aligned(64); + +static void guest_vector_load(const void *source, unsigned long vlenb) +{ + asm volatile(".option push\n" + ".option arch, +v\n" + "vsetvli zero, %1, e8, m1, ta, ma\n" + "vle8.v v0, (%0)\n" + ".option pop\n" + : + : "r"(source), "r"(vlenb) + : "memory"); +} + +static void guest_vector_store(void *destination, unsigned long vlenb) +{ + asm volatile(".option push\n" + ".option arch, +v\n" + "vsetvli zero, %1, e8, m1, ta, ma\n" + "vse8.v v0, (%0)\n" + ".option pop\n" + : + : "r"(destination), "r"(vlenb) + : "memory"); +} + +static void guest_expect_pattern(const unsigned char *data, + unsigned long vlenb, unsigned char pattern) +{ + unsigned long i; + + for (i = 0; i < vlenb; i++) + GUEST_ASSERT_EQ(data[i], pattern); +} + +static void guest_code(void) +{ + unsigned long i, vlenb; + + csr_set(CSR_SSTATUS, SR_VS_INITIAL); + vlenb = csr_read(CSR_VLENB); + GUEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB); + + for (i = 0; i < vlenb; i++) + guest_source[i] = GUEST_PATTERN; + + guest_vector_load(guest_source, vlenb); + GUEST_SYNC(1); + + guest_vector_store(guest_after_get, vlenb); + guest_expect_pattern(guest_after_get, vlenb, GUEST_PATTERN); + GUEST_SYNC(2); + + guest_vector_store(guest_after_set, vlenb); + guest_expect_pattern(guest_after_set, vlenb, SET_PATTERN); + GUEST_DONE(); +} + +static void pin_to_one_cpu(void) +{ + cpu_set_t allowed, pinned; + int cpu; + + kvm_sched_getaffinity(0, sizeof(allowed), &allowed); + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { + if (CPU_ISSET(cpu, &allowed)) + break; + } + TEST_ASSERT(cpu < CPU_SETSIZE, "no CPU available"); + + CPU_ZERO(&pinned); + CPU_SET(cpu, &pinned); + kvm_sched_setaffinity(0, sizeof(pinned), &pinned); +} + +static void run_to_sync(struct kvm_vcpu *vcpu, unsigned long stage) +{ + struct ucall uc; + u64 cmd; + + vcpu_run(vcpu); + cmd = get_ucall(vcpu, &uc); + if (cmd == UCALL_ABORT) + REPORT_GUEST_ASSERT(uc); + TEST_ASSERT_EQ(cmd, UCALL_SYNC); + TEST_ASSERT_EQ(uc.args[1], stage); +} + +static void run_to_done(struct kvm_vcpu *vcpu) +{ + struct ucall uc; + u64 cmd; + + vcpu_run(vcpu); + cmd = get_ucall(vcpu, &uc); + if (cmd == UCALL_ABORT) + REPORT_GUEST_ASSERT(uc); + TEST_ASSERT_EQ(cmd, UCALL_DONE); +} + +static int set_one_reg_buffer(struct kvm_vcpu *vcpu, u64 id, void *buffer) +{ + struct kvm_one_reg reg = { + .id = id, + .addr = (u64)buffer, + }; + + return __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, ®); +} + +static void expect_pattern(const unsigned char *data, unsigned long length, + unsigned char pattern, const char *where) +{ + unsigned long i; + + for (i = 0; i < length; i++) + TEST_ASSERT(data[i] == pattern, + "%s byte %lu: got %#x, expected %#x", + where, i, data[i], pattern); +} + +int main(void) +{ + unsigned char get_buffer[RISCV_MAX_VLENB] __aligned(64) = {}; + unsigned char set_buffer[RISCV_MAX_VLENB] __aligned(64); + struct kvm_vm *vm; + struct kvm_vcpu *vcpu; + unsigned long vlenb; + u64 vlenb_id, v0_id, size; + int ret; + + pin_to_one_cpu(); + vm = vm_create_with_one_vcpu(&vcpu, guest_code); + TEST_REQUIRE(__vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V)); + + vm_init_vector_tables(vm); + vcpu_init_vector_tables(vcpu); + + vlenb_id = KVM_REG_RISCV | KVM_REG_RISCV_VECTOR | + KVM_REG_SIZE_ULONG | KVM_REG_RISCV_VECTOR_CSR_REG(vlenb); + ret = __vcpu_get_reg(vcpu, vlenb_id, &vlenb); + TEST_ASSERT(!ret, "KVM_GET_ONE_REG(vlenb) failed: %d", ret); + TEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB && + !(vlenb & (vlenb - 1)), "unsupported vlenb: %lu", vlenb); + + size = (u64)__builtin_ctzl(vlenb) << KVM_REG_SIZE_SHIFT; + v0_id = KVM_REG_RISCV | KVM_REG_RISCV_VECTOR | size | + KVM_REG_RISCV_VECTOR_REG(0); + + run_to_sync(vcpu, 1); + ret = __vcpu_get_reg(vcpu, v0_id, get_buffer); + TEST_ASSERT(!ret, "KVM_GET_ONE_REG(v0) failed: %d", ret); + expect_pattern(get_buffer, vlenb, GUEST_PATTERN, "GET v0"); + + run_to_sync(vcpu, 2); + memset(set_buffer, SET_PATTERN, vlenb); + ret = set_one_reg_buffer(vcpu, v0_id, set_buffer); + TEST_ASSERT(!ret, "KVM_SET_ONE_REG(v0) failed: %d", ret); + + run_to_done(vcpu); + sync_global_from_guest(vm, guest_after_get); + sync_global_from_guest(vm, guest_after_set); + expect_pattern(guest_after_get, vlenb, GUEST_PATTERN, + "guest after GET"); + expect_pattern(guest_after_set, vlenb, SET_PATTERN, + "guest after SET"); + + pr_info("Vector ONE_REG owner test passed (vlenb=%lu)\n", vlenb); + kvm_vm_free(vm); + return 0; +} -- 2.43.0