// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2021, Red Hat Inc. * * Generic tests for KVM CPUID set/get ioctls */ #include #include #include #include "test_util.h" #include "kvm_util.h" #include "processor.h" #define VCPU_ID 0 /* CPUIDs known to differ */ struct { u32 function; u32 index; } mangled_cpuids[] = { /* * These entries depend on the vCPU's XCR0 register and IA32_XSS MSR, * which are not controlled for by this test. */ {.function = 0xd, .index = 0}, {.function = 0xd, .index = 1}, }; static void test_guest_cpuids(struct kvm_cpuid2 *guest_cpuid) { int i; u32 eax, ebx, ecx, edx; for (i = 0; i < guest_cpuid->nent; i++) { eax = guest_cpuid->entries[i].function; ecx = guest_cpuid->entries[i].index; cpuid(&eax, &ebx, &ecx, &edx); GUEST_ASSERT(eax == guest_cpuid->entries[i].eax && ebx == guest_cpuid->entries[i].ebx && ecx == guest_cpuid->entries[i].ecx && edx == guest_cpuid->entries[i].edx); } } static void test_cpuid_40000000(struct kvm_cpuid2 *guest_cpuid) { u32 eax = 0x40000000, ebx, ecx = 0, edx; cpuid(&eax, &ebx, &ecx, &edx); GUEST_ASSERT(eax == 0x40000001); } static void guest_main(struct kvm_cpuid2 *guest_cpuid) { GUEST_SYNC(1); test_guest_cpuids(guest_cpuid); GUEST_SYNC(2); test_cpuid_40000000(guest_cpuid); GUEST_DONE(); } static bool is_cpuid_mangled(struct kvm_cpuid_entry2 *entrie) { int i; for (i = 0; i < sizeof(mangled_cpuids); i++) { if (mangled_cpuids[i].function == entrie->function && mangled_cpuids[i].index == entrie->index) return true; } return false; } static void check_cpuid(struct kvm_cpuid2 *cpuid, struct kvm_cpuid_entry2 *entrie) { int i; for (i = 0; i < cpuid->nent; i++) { if (cpuid->entries[i].function == entrie->function && cpuid->entries[i].index == entrie->index) { if (is_cpuid_mangled(entrie)) return; TEST_ASSERT(cpuid->entries[i].eax == entrie->eax && cpuid->entries[i].ebx == entrie->ebx && cpuid->entries[i].ecx == entrie->ecx && cpuid->entries[i].edx == entrie->edx, "CPUID 0x%x.%x differ: 0x%x:0x%x:0x%x:0x%x vs 0x%x:0x%x:0x%x:0x%x", entrie->function, entrie->index, cpuid->entries[i].eax, cpuid->entries[i].ebx, cpuid->entries[i].ecx, cpuid->entries[i].edx, entrie->eax, entrie->ebx, entrie->ecx, entrie->edx); return; } } TEST_ASSERT(false, "CPUID 0x%x.%x not found", entrie->function, entrie->index); } static void compare_cpuids(struct kvm_cpuid2 *cpuid1, struct kvm_cpuid2 *cpuid2) { int i; for (i = 0; i < cpuid1->nent; i++) check_cpuid(cpuid2, &cpuid1->entries[i]); for (i = 0; i < cpuid2->nent; i++) check_cpuid(cpuid1, &cpuid2->entries[i]); } static void run_vcpu(struct kvm_vm *vm, uint32_t vcpuid, int stage) { struct ucall uc; _vcpu_run(vm, vcpuid); switch (get_ucall(vm, vcpuid, &uc)) { case UCALL_SYNC: TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") && uc.args[1] == stage + 1, "Stage %d: Unexpected register values vmexit, got %lx", stage + 1, (ulong)uc.args[1]); return; case UCALL_DONE: return; case UCALL_ABORT: TEST_ASSERT(false, "%s at %s:%ld\n\tvalues: %#lx, %#lx", (const char *)uc.args[0], __FILE__, uc.args[1], uc.args[2], uc.args[3]); default: TEST_ASSERT(false, "Unexpected exit: %s", exit_reason_str(vcpu_state(vm, vcpuid)->exit_reason)); } } struct kvm_cpuid2 *vcpu_alloc_cpuid(struct kvm_vm *vm, vm_vaddr_t *p_gva, struct kvm_cpuid2 *cpuid) { int size = sizeof(*cpuid) + cpuid->nent * sizeof(cpuid->entries[0]); vm_vaddr_t gva = vm_vaddr_alloc(vm, size, KVM_UTIL_MIN_VADDR); struct kvm_cpuid2 *guest_cpuids = addr_gva2hva(vm, gva); memcpy(guest_cpuids, cpuid, size); *p_gva = gva; return guest_cpuids; } static void set_cpuid_after_run(struct kvm_vm *vm, struct kvm_cpuid2 *cpuid) { struct kvm_cpuid_entry2 *ent; int rc; u32 eax, ebx, x; /* Setting unmodified CPUID is allowed */ rc = __vcpu_set_cpuid(vm, VCPU_ID, cpuid); TEST_ASSERT(!rc, "Setting unmodified CPUID after KVM_RUN failed: %d", rc); /* Changing CPU features is forbidden */ ent = get_cpuid(cpuid, 0x7, 0); ebx = ent->ebx; ent->ebx--; rc = __vcpu_set_cpuid(vm, VCPU_ID, cpuid); TEST_ASSERT(rc, "Changing CPU features should fail"); ent->ebx = ebx; /* Changing MAXPHYADDR is forbidden */ ent = get_cpuid(cpuid, 0x80000008, 0); eax = ent->eax; x = eax & 0xff; ent->eax = (eax & ~0xffu) | (x - 1); rc = __vcpu_set_cpuid(vm, VCPU_ID, cpuid); TEST_ASSERT(rc, "Changing MAXPHYADDR should fail"); ent->eax = eax; } int main(void) { struct kvm_cpuid2 *supp_cpuid, *cpuid2; vm_vaddr_t cpuid_gva; struct kvm_vm *vm; int stage; vm = vm_create_default(VCPU_ID, 0, guest_main); supp_cpuid = kvm_get_supported_cpuid(); cpuid2 = vcpu_get_cpuid(vm, VCPU_ID); compare_cpuids(supp_cpuid, cpuid2); vcpu_alloc_cpuid(vm, &cpuid_gva, cpuid2); vcpu_args_set(vm, VCPU_ID, 1, cpuid_gva); for (stage = 0; stage < 3; stage++) run_vcpu(vm, VCPU_ID, stage); set_cpuid_after_run(vm, cpuid2); kvm_vm_free(vm); }