From: Peter Gonda <pgonda@xxxxxxxxxx> Modifies ucall handling for SEV-ES VMs. Instead of using an out instruction and storing the ucall pointer in RDI, SEV-ES guests use a outsb VMGEXIT to move the ucall pointer as the data. Allows for SEV-ES to use ucalls instead of relying the SEV-ES MSR based termination protocol. Cc: Vishal Annapurve <vannapurve@xxxxxxxxxx> Cc: Ackerley Tng <ackerleytng@xxxxxxxxxx> Cc: Paolo Bonzini <pbonzini@xxxxxxxxxx> Cc: Claudio Imbrenda <imbrenda@xxxxxxxxxxxxx> Cc: Sean Christopherson <seanjc@xxxxxxxxxx> Cc: Carlos Bilbao <carlos.bilbao@xxxxxxx> Cc: Tom Lendacky <thomas.lendacky@xxxxxxx> Cc: Michael Roth <michael.roth@xxxxxxx> Cc: kvm@xxxxxxxxxxxxxxx Cc: linux-kselftest@xxxxxxxxxxxxxxx Signed-off-by: Peter Gonda <pgonda@xxxxxxxxxx> Signed-off-by: Neeraj Upadhyay <Neeraj.Upadhyay@xxxxxxx> --- tools/testing/selftests/kvm/include/x86/sev.h | 2 + tools/testing/selftests/kvm/lib/x86/sev.c | 98 +++++++++++++++++-- tools/testing/selftests/kvm/lib/x86/ucall.c | 18 ++++ .../selftests/kvm/x86/sev_smoke_test.c | 27 +---- 4 files changed, 113 insertions(+), 32 deletions(-) diff --git a/tools/testing/selftests/kvm/include/x86/sev.h b/tools/testing/selftests/kvm/include/x86/sev.h index 437e397ddd29..bd6ab3f38679 100644 --- a/tools/testing/selftests/kvm/include/x86/sev.h +++ b/tools/testing/selftests/kvm/include/x86/sev.h @@ -151,4 +151,6 @@ bool is_sev_enabled(void); bool is_sev_es_enabled(void); bool is_sev_snp_enabled(void); +void sev_es_ucall_port_write(uint32_t port, uint64_t data); + #endif /* SELFTEST_KVM_SEV_H */ diff --git a/tools/testing/selftests/kvm/lib/x86/sev.c b/tools/testing/selftests/kvm/lib/x86/sev.c index 0c542eae4184..425ec8a3a3c7 100644 --- a/tools/testing/selftests/kvm/lib/x86/sev.c +++ b/tools/testing/selftests/kvm/lib/x86/sev.c @@ -7,11 +7,18 @@ #include "svm.h" #include "svm_util.h" +#define IOIO_TYPE_STR (1 << 2) +#define IOIO_SEG_DS (1 << 11 | 1 << 10) +#define IOIO_DATA_8 (1 << 4) +#define IOIO_REP (1 << 3) + +#define SW_EXIT_CODE_IOIO 0x7b + struct ghcb_entry { struct ghcb ghcb; /* Guest physical address of this GHCB. */ - void *gpa; + uint64_t gpa; /* Host virtual address of this struct. */ struct ghcb_entry *hva; @@ -35,25 +42,35 @@ void ghcb_init(struct kvm_vm *vm) struct ghcb_entry *entry; vm_vaddr_t vaddr; int i; + size_t sz = align_up(sizeof(struct ghcb_header), vm_guest_mode_params[vm->mode].page_size); - vaddr = vm_vaddr_alloc_shared(vm, sizeof(*hdr), KVM_UTIL_MIN_VADDR, + vaddr = vm_vaddr_alloc_shared(vm, sz, KVM_UTIL_MIN_VADDR, MEM_REGION_DATA); hdr = (struct ghcb_header *)addr_gva2hva(vm, vaddr); - memset(hdr, 0, sizeof(*hdr)); + memset(hdr, 0, sz); for (i = 0; i < KVM_MAX_VCPUS; ++i) { entry = &hdr->ghcbs[i]; entry->hva = entry; - entry->gpa = (void *)addr_hva2gpa(vm, &entry->ghcb); + entry->gpa = (uint64_t)addr_hva2gpa(vm, &entry->ghcb); } + if (is_sev_snp_vm(vm)) + vm_mem_set_shared(vm, addr_hva2gpa(vm, hdr), sz); + write_guest_global(vm, ghcb_pool, (struct ghcb_header *)vaddr); } +static void sev_es_terminate(void) +{ + wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ); +} + static struct ghcb_entry *ghcb_alloc(void) { return &ghcb_pool->ghcbs[0]; struct ghcb_entry *entry; + struct ghcb *ghcb; int i; if (!ghcb_pool) @@ -62,12 +79,18 @@ static struct ghcb_entry *ghcb_alloc(void) for (i = 0; i < KVM_MAX_VCPUS; ++i) { if (!test_and_set_bit(i, ghcb_pool->in_use)) { entry = &ghcb_pool->ghcbs[i]; - memset(&entry->ghcb, 0, sizeof(entry->ghcb)); + ghcb = &entry->ghcb; + + memset(&ghcb, 0, sizeof(*ghcb)); + ghcb->ghcb_usage = 0; + ghcb->protocol_version = 1; + return entry; } } ucall_failed: + sev_es_terminate(); return NULL; } @@ -191,9 +214,6 @@ void sev_vm_launch(struct kvm_vm *vm, uint32_t policy) struct kvm_sev_guest_status status; int ctr; - if (is_sev_es_vm(vm)) - ghcb_init(vm); - vm_sev_ioctl(vm, KVM_SEV_LAUNCH_START, &launch_start); vm_sev_ioctl(vm, KVM_SEV_GUEST_STATUS, &status); @@ -285,6 +305,9 @@ struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, void *guest_code, void vm_sev_launch(struct kvm_vm *vm, uint64_t policy, uint8_t *measurement) { + if (is_sev_es_vm(vm)) + ghcb_init(vm); + if (is_sev_snp_vm(vm)) { vm_enable_cap(vm, KVM_CAP_EXIT_HYPERCALL, (1 << KVM_HC_MAP_GPA_RANGE)); @@ -323,3 +346,62 @@ bool is_sev_snp_enabled(void) return is_sev_es_enabled() && rdmsr(MSR_AMD64_SEV) & MSR_AMD64_SEV_SNP_ENABLED; } + +static uint64_t setup_exitinfo1_portio(uint32_t port) +{ + uint64_t exitinfo1 = 0; + + exitinfo1 |= IOIO_TYPE_STR; + exitinfo1 |= ((port & 0xffff) << 16); + exitinfo1 |= IOIO_SEG_DS; + exitinfo1 |= IOIO_DATA_8; + exitinfo1 |= IOIO_REP; + + return exitinfo1; +} + +#define GHCB_MSR_REG_GPA_REQ 0x012 +#define GHCB_MSR_REG_GPA_REQ_VAL(v) \ + /* GHCBData[63:12] */ \ + (((u64)((v) & GENMASK_ULL(51, 0)) << 12) | \ + /* GHCBData[11:0] */ \ + GHCB_MSR_REG_GPA_REQ) + +static void register_ghcb_page(uint64_t ghcb_gpa) +{ + if (is_sev_snp_enabled()) { + wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_REG_GPA_REQ_VAL(ghcb_gpa >> 12)); + VMGEXIT(); + } +} + +static void do_vmg_exit(uint64_t ghcb_gpa) +{ + wrmsr(MSR_AMD64_SEV_ES_GHCB, ghcb_gpa); + VMGEXIT(); +} + +void sev_es_ucall_port_write(uint32_t port, uint64_t data) +{ + struct ghcb_entry *entry; + struct ghcb *ghcb; + const uint64_t exitinfo1 = setup_exitinfo1_portio(port); + + entry = ghcb_alloc(); + ghcb = &entry->ghcb; + + register_ghcb_page(entry->gpa); + + ghcb_set_sw_exit_code(ghcb, SW_EXIT_CODE_IOIO); + ghcb_set_sw_exit_info_1(ghcb, exitinfo1); + ghcb_set_sw_exit_info_2(ghcb, sizeof(data)); + + // Setup the SW Stratch buffer pointer. + ghcb_set_sw_scratch(ghcb, + entry->gpa + offsetof(struct ghcb, shared_buffer)); + memcpy(&ghcb->shared_buffer, &data, sizeof(data)); + + do_vmg_exit(entry->gpa); + + ghcb_free(entry); +} diff --git a/tools/testing/selftests/kvm/lib/x86/ucall.c b/tools/testing/selftests/kvm/lib/x86/ucall.c index 1265cecc7dd1..711e58a3a356 100644 --- a/tools/testing/selftests/kvm/lib/x86/ucall.c +++ b/tools/testing/selftests/kvm/lib/x86/ucall.c @@ -5,6 +5,8 @@ * Copyright (C) 2018, Red Hat, Inc. */ #include "kvm_util.h" +#include "processor.h" +#include "sev.h" #define UCALL_PIO_PORT ((uint16_t)0x1000) @@ -21,6 +23,11 @@ void ucall_arch_do_ucall(vm_vaddr_t uc) #define HORRIFIC_L2_UCALL_CLOBBER_HACK \ "rcx", "rsi", "r8", "r9", "r10", "r11" + if (is_sev_es_enabled()) { + sev_es_ucall_port_write(UCALL_PIO_PORT, uc); + return; + } + asm volatile("push %%rbp\n\t" "push %%r15\n\t" "push %%r14\n\t" @@ -48,8 +55,19 @@ void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu) if (run->exit_reason == KVM_EXIT_IO && run->io.port == UCALL_PIO_PORT) { struct kvm_regs regs; + uint64_t addr; + + if (is_sev_es_vm(vcpu->vm)) { + TEST_ASSERT( + run->io.count == 8 && run->io.size == 1, + "SEV-ES ucall exit requires 8 byte string out\n"); + + addr = *(uint64_t *)((uint8_t *)(run) + run->io.data_offset); + return (void *)addr; + } vcpu_regs_get(vcpu, ®s); + return (void *)regs.rdi; } return NULL; diff --git a/tools/testing/selftests/kvm/x86/sev_smoke_test.c b/tools/testing/selftests/kvm/x86/sev_smoke_test.c index 29382dcab18c..3834d3664219 100644 --- a/tools/testing/selftests/kvm/x86/sev_smoke_test.c +++ b/tools/testing/selftests/kvm/x86/sev_smoke_test.c @@ -20,8 +20,7 @@ static void guest_snp_code(void) { GUEST_ASSERT(is_sev_snp_enabled()); - wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ); - VMGEXIT(); + GUEST_DONE(); } static void guest_sev_es_code(void) @@ -29,12 +28,7 @@ static void guest_sev_es_code(void) /* TODO: Check CPUID after GHCB-based hypercall support is added. */ GUEST_ASSERT(is_sev_es_enabled()); - /* - * TODO: Add GHCB and ucall support for SEV-ES guests. For now, simply - * force "termination" to signal "done" via the GHCB MSR protocol. - */ - wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ); - VMGEXIT(); + GUEST_DONE(); } static void guest_sev_code(void) @@ -102,12 +96,7 @@ static void __test_sync_vmsa(uint32_t type, uint64_t policy) vcpu_run(vcpu); - TEST_ASSERT(vcpu->run->exit_reason == KVM_EXIT_SYSTEM_EVENT, - "Wanted SYSTEM_EVENT, got %s", - exit_reason_str(vcpu->run->exit_reason)); - TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM); - TEST_ASSERT_EQ(vcpu->run->system_event.ndata, 1); - TEST_ASSERT_EQ(vcpu->run->system_event.data[0], GHCB_MSR_TERM_REQ); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); compare_xsave((u8 *)&xsave, (u8 *)hva); @@ -128,16 +117,6 @@ static void __test_sev(void *guest_code, uint32_t type, uint64_t policy) for (;;) { vcpu_run(vcpu); - if (is_sev_es_vm(vm)) { - TEST_ASSERT(vcpu->run->exit_reason == KVM_EXIT_SYSTEM_EVENT, - "Wanted SYSTEM_EVENT, got %s", - exit_reason_str(vcpu->run->exit_reason)); - TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM); - TEST_ASSERT_EQ(vcpu->run->system_event.ndata, 1); - TEST_ASSERT_EQ(vcpu->run->system_event.data[0], GHCB_MSR_TERM_REQ); - break; - } - switch (get_ucall(vcpu, &uc)) { case UCALL_SYNC: continue; -- 2.34.1