18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ucall support. A ucall is a "hypercall to userspace". 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2018, Red Hat, Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include "kvm_util.h" 88c2ecf20Sopenharmony_ci#include "../kvm_util_internal.h" 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_cistatic vm_vaddr_t *ucall_exit_mmio_addr; 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_cistatic bool ucall_mmio_init(struct kvm_vm *vm, vm_paddr_t gpa) 138c2ecf20Sopenharmony_ci{ 148c2ecf20Sopenharmony_ci if (kvm_userspace_memory_region_find(vm, gpa, gpa + 1)) 158c2ecf20Sopenharmony_ci return false; 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci virt_pg_map(vm, gpa, gpa, 0); 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci ucall_exit_mmio_addr = (vm_vaddr_t *)gpa; 208c2ecf20Sopenharmony_ci sync_global_to_guest(vm, ucall_exit_mmio_addr); 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci return true; 238c2ecf20Sopenharmony_ci} 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_civoid ucall_init(struct kvm_vm *vm, void *arg) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci vm_paddr_t gpa, start, end, step, offset; 288c2ecf20Sopenharmony_ci unsigned int bits; 298c2ecf20Sopenharmony_ci bool ret; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci if (arg) { 328c2ecf20Sopenharmony_ci gpa = (vm_paddr_t)arg; 338c2ecf20Sopenharmony_ci ret = ucall_mmio_init(vm, gpa); 348c2ecf20Sopenharmony_ci TEST_ASSERT(ret, "Can't set ucall mmio address to %lx", gpa); 358c2ecf20Sopenharmony_ci return; 368c2ecf20Sopenharmony_ci } 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci /* 398c2ecf20Sopenharmony_ci * Find an address within the allowed physical and virtual address 408c2ecf20Sopenharmony_ci * spaces, that does _not_ have a KVM memory region associated with 418c2ecf20Sopenharmony_ci * it. Identity mapping an address like this allows the guest to 428c2ecf20Sopenharmony_ci * access it, but as KVM doesn't know what to do with it, it 438c2ecf20Sopenharmony_ci * will assume it's something userspace handles and exit with 448c2ecf20Sopenharmony_ci * KVM_EXIT_MMIO. Well, at least that's how it works for AArch64. 458c2ecf20Sopenharmony_ci * Here we start with a guess that the addresses around 5/8th 468c2ecf20Sopenharmony_ci * of the allowed space are unmapped and then work both down and 478c2ecf20Sopenharmony_ci * up from there in 1/16th allowed space sized steps. 488c2ecf20Sopenharmony_ci * 498c2ecf20Sopenharmony_ci * Note, we need to use VA-bits - 1 when calculating the allowed 508c2ecf20Sopenharmony_ci * virtual address space for an identity mapping because the upper 518c2ecf20Sopenharmony_ci * half of the virtual address space is the two's complement of the 528c2ecf20Sopenharmony_ci * lower and won't match physical addresses. 538c2ecf20Sopenharmony_ci */ 548c2ecf20Sopenharmony_ci bits = vm->va_bits - 1; 558c2ecf20Sopenharmony_ci bits = vm->pa_bits < bits ? vm->pa_bits : bits; 568c2ecf20Sopenharmony_ci end = 1ul << bits; 578c2ecf20Sopenharmony_ci start = end * 5 / 8; 588c2ecf20Sopenharmony_ci step = end / 16; 598c2ecf20Sopenharmony_ci for (offset = 0; offset < end - start; offset += step) { 608c2ecf20Sopenharmony_ci if (ucall_mmio_init(vm, start - offset)) 618c2ecf20Sopenharmony_ci return; 628c2ecf20Sopenharmony_ci if (ucall_mmio_init(vm, start + offset)) 638c2ecf20Sopenharmony_ci return; 648c2ecf20Sopenharmony_ci } 658c2ecf20Sopenharmony_ci TEST_FAIL("Can't find a ucall mmio address"); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_civoid ucall_uninit(struct kvm_vm *vm) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci ucall_exit_mmio_addr = 0; 718c2ecf20Sopenharmony_ci sync_global_to_guest(vm, ucall_exit_mmio_addr); 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_civoid ucall(uint64_t cmd, int nargs, ...) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct ucall uc = {}; 778c2ecf20Sopenharmony_ci va_list va; 788c2ecf20Sopenharmony_ci int i; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci WRITE_ONCE(uc.cmd, cmd); 818c2ecf20Sopenharmony_ci nargs = nargs <= UCALL_MAX_ARGS ? nargs : UCALL_MAX_ARGS; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci va_start(va, nargs); 848c2ecf20Sopenharmony_ci for (i = 0; i < nargs; ++i) 858c2ecf20Sopenharmony_ci WRITE_ONCE(uc.args[i], va_arg(va, uint64_t)); 868c2ecf20Sopenharmony_ci va_end(va); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci WRITE_ONCE(*ucall_exit_mmio_addr, (vm_vaddr_t)&uc); 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ciuint64_t get_ucall(struct kvm_vm *vm, uint32_t vcpu_id, struct ucall *uc) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci struct kvm_run *run = vcpu_state(vm, vcpu_id); 948c2ecf20Sopenharmony_ci struct ucall ucall = {}; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci if (uc) 978c2ecf20Sopenharmony_ci memset(uc, 0, sizeof(*uc)); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci if (run->exit_reason == KVM_EXIT_MMIO && 1008c2ecf20Sopenharmony_ci run->mmio.phys_addr == (uint64_t)ucall_exit_mmio_addr) { 1018c2ecf20Sopenharmony_ci vm_vaddr_t gva; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci TEST_ASSERT(run->mmio.is_write && run->mmio.len == 8, 1048c2ecf20Sopenharmony_ci "Unexpected ucall exit mmio address access"); 1058c2ecf20Sopenharmony_ci memcpy(&gva, run->mmio.data, sizeof(gva)); 1068c2ecf20Sopenharmony_ci memcpy(&ucall, addr_gva2hva(vm, gva), sizeof(ucall)); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci vcpu_run_complete_io(vm, vcpu_id); 1098c2ecf20Sopenharmony_ci if (uc) 1108c2ecf20Sopenharmony_ci memcpy(uc, &ucall, sizeof(ucall)); 1118c2ecf20Sopenharmony_ci } 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci return ucall.cmd; 1148c2ecf20Sopenharmony_ci} 115