| // SPDX-License-Identifier: GPL-2.0-only |
| #include "kvm_util.h" |
| #include "linux/types.h" |
| #include "linux/bitmap.h" |
| #include "linux/atomic.h" |
| |
| #define GUEST_UCALL_FAILED -1 |
| |
| struct ucall_header { |
| DECLARE_BITMAP(in_use, KVM_MAX_VCPUS); |
| struct ucall ucalls[KVM_MAX_VCPUS]; |
| }; |
| |
| /* |
| * ucall_pool holds per-VM values (global data is duplicated by each VM), it |
| * must not be accessed from host code. |
| */ |
| static struct ucall_header *ucall_pool; |
| |
| vm_paddr_t get_ucall_pool_gpa(struct kvm_vm *vm) |
| { |
| return addr_gva2gpa(vm, (vm_vaddr_t) read_guest_global(vm, ucall_pool)); |
| } |
| |
| size_t get_ucall_pool_size(void) |
| { |
| return sizeof(struct ucall_header); |
| } |
| |
| vm_paddr_t get_ucall_mmio_gpa(struct kvm_vm *vm) |
| { |
| struct userspace_mem_region *slot0 = memslot2region(vm, 0); |
| |
| return slot0->region.guest_phys_addr + slot0->region.memory_size; |
| } |
| |
| void ucall_init(struct kvm_vm *vm, vm_paddr_t mmio_gpa) |
| { |
| struct ucall_header *hdr; |
| struct ucall *uc; |
| vm_vaddr_t vaddr; |
| int i; |
| |
| vaddr = __vm_vaddr_alloc(vm, sizeof(*hdr), KVM_UTIL_MIN_VADDR, MEM_REGION_DATA); |
| hdr = (struct ucall_header *)addr_gva2hva(vm, vaddr); |
| memset(hdr, 0, sizeof(*hdr)); |
| |
| for (i = 0; i < KVM_MAX_VCPUS; ++i) { |
| uc = &hdr->ucalls[i]; |
| uc->hva = uc; |
| } |
| |
| write_guest_global(vm, ucall_pool, (struct ucall_header *)vaddr); |
| |
| ucall_arch_init(vm, mmio_gpa); |
| } |
| |
| static struct ucall *ucall_alloc(void) |
| { |
| struct ucall *uc; |
| int i; |
| |
| if (!ucall_pool) |
| goto ucall_failed; |
| |
| for (i = 0; i < KVM_MAX_VCPUS; ++i) { |
| if (!test_and_set_bit(i, ucall_pool->in_use)) { |
| uc = &ucall_pool->ucalls[i]; |
| memset(uc->args, 0, sizeof(uc->args)); |
| return uc; |
| } |
| } |
| |
| ucall_failed: |
| /* |
| * If the vCPU cannot grab a ucall structure, make a bare ucall with a |
| * magic value to signal to get_ucall() that things went sideways. |
| * GUEST_ASSERT() depends on ucall_alloc() and so cannot be used here. |
| */ |
| ucall_arch_do_ucall(GUEST_UCALL_FAILED); |
| return NULL; |
| } |
| |
| static void ucall_free(struct ucall *uc) |
| { |
| /* Beware, here be pointer arithmetic. */ |
| clear_bit(uc - ucall_pool->ucalls, ucall_pool->in_use); |
| } |
| |
| void ucall(uint64_t cmd, int nargs, ...) |
| { |
| struct ucall *uc; |
| va_list va; |
| int i; |
| |
| uc = ucall_alloc(); |
| |
| WRITE_ONCE(uc->cmd, cmd); |
| |
| nargs = min(nargs, UCALL_MAX_ARGS); |
| |
| va_start(va, nargs); |
| for (i = 0; i < nargs; ++i) |
| WRITE_ONCE(uc->args[i], va_arg(va, uint64_t)); |
| va_end(va); |
| |
| ucall_arch_do_ucall((vm_vaddr_t)uc->hva); |
| |
| ucall_free(uc); |
| } |
| |
| uint64_t get_ucall(struct kvm_vcpu *vcpu, struct ucall *uc) |
| { |
| struct ucall ucall; |
| void *addr; |
| |
| if (!uc) |
| uc = &ucall; |
| |
| addr = ucall_arch_get_ucall(vcpu); |
| if (addr) { |
| TEST_ASSERT(addr != (void *)GUEST_UCALL_FAILED, |
| "Guest failed to allocate ucall struct"); |
| |
| memcpy(uc, addr, sizeof(*uc)); |
| vcpu_run_complete_io(vcpu); |
| } else { |
| memset(uc, 0, sizeof(*uc)); |
| } |
| |
| return uc->cmd; |
| } |