| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * ucall support. A ucall is a "hypercall to userspace". |
| * |
| * Copyright (C) 2018, Red Hat, Inc. |
| */ |
| #include "kvm_util.h" |
| #include "kvm_util_internal.h" |
| |
| #define UCALL_PIO_PORT ((uint16_t)0x1000) |
| |
| static ucall_type_t ucall_type; |
| static vm_vaddr_t *ucall_exit_mmio_addr; |
| |
| static bool ucall_mmio_init(struct kvm_vm *vm, vm_paddr_t gpa) |
| { |
| if (kvm_userspace_memory_region_find(vm, gpa, gpa + 1)) |
| return false; |
| |
| virt_pg_map(vm, gpa, gpa, 0); |
| |
| ucall_exit_mmio_addr = (vm_vaddr_t *)gpa; |
| sync_global_to_guest(vm, ucall_exit_mmio_addr); |
| |
| return true; |
| } |
| |
| void ucall_init(struct kvm_vm *vm, ucall_type_t type, void *arg) |
| { |
| ucall_type = type; |
| sync_global_to_guest(vm, ucall_type); |
| |
| if (type == UCALL_PIO) |
| return; |
| |
| if (type == UCALL_MMIO) { |
| vm_paddr_t gpa, start, end, step, offset; |
| unsigned bits; |
| bool ret; |
| |
| if (arg) { |
| gpa = (vm_paddr_t)arg; |
| ret = ucall_mmio_init(vm, gpa); |
| TEST_ASSERT(ret, "Can't set ucall mmio address to %lx", gpa); |
| return; |
| } |
| |
| /* |
| * Find an address within the allowed physical and virtual address |
| * spaces, that does _not_ have a KVM memory region associated with |
| * it. Identity mapping an address like this allows the guest to |
| * access it, but as KVM doesn't know what to do with it, it |
| * will assume it's something userspace handles and exit with |
| * KVM_EXIT_MMIO. Well, at least that's how it works for AArch64. |
| * Here we start with a guess that the addresses around 5/8th |
| * of the allowed space are unmapped and then work both down and |
| * up from there in 1/16th allowed space sized steps. |
| * |
| * Note, we need to use VA-bits - 1 when calculating the allowed |
| * virtual address space for an identity mapping because the upper |
| * half of the virtual address space is the two's complement of the |
| * lower and won't match physical addresses. |
| */ |
| bits = vm->va_bits - 1; |
| bits = vm->pa_bits < bits ? vm->pa_bits : bits; |
| end = 1ul << bits; |
| start = end * 5 / 8; |
| step = end / 16; |
| for (offset = 0; offset < end - start; offset += step) { |
| if (ucall_mmio_init(vm, start - offset)) |
| return; |
| if (ucall_mmio_init(vm, start + offset)) |
| return; |
| } |
| TEST_ASSERT(false, "Can't find a ucall mmio address"); |
| } |
| } |
| |
| void ucall_uninit(struct kvm_vm *vm) |
| { |
| ucall_type = 0; |
| sync_global_to_guest(vm, ucall_type); |
| ucall_exit_mmio_addr = 0; |
| sync_global_to_guest(vm, ucall_exit_mmio_addr); |
| } |
| |
| static void ucall_pio_exit(struct ucall *uc) |
| { |
| #ifdef __x86_64__ |
| asm volatile("in %[port], %%al" |
| : : [port] "d" (UCALL_PIO_PORT), "D" (uc) : "rax"); |
| #endif |
| } |
| |
| static void ucall_mmio_exit(struct ucall *uc) |
| { |
| *ucall_exit_mmio_addr = (vm_vaddr_t)uc; |
| } |
| |
| void ucall(uint64_t cmd, int nargs, ...) |
| { |
| struct ucall uc = { |
| .cmd = cmd, |
| }; |
| va_list va; |
| int i; |
| |
| nargs = nargs <= UCALL_MAX_ARGS ? nargs : UCALL_MAX_ARGS; |
| |
| va_start(va, nargs); |
| for (i = 0; i < nargs; ++i) |
| uc.args[i] = va_arg(va, uint64_t); |
| va_end(va); |
| |
| switch (ucall_type) { |
| case UCALL_PIO: |
| ucall_pio_exit(&uc); |
| break; |
| case UCALL_MMIO: |
| ucall_mmio_exit(&uc); |
| break; |
| }; |
| } |
| |
| uint64_t get_ucall(struct kvm_vm *vm, uint32_t vcpu_id, struct ucall *uc) |
| { |
| struct kvm_run *run = vcpu_state(vm, vcpu_id); |
| struct ucall ucall = {}; |
| bool got_ucall = false; |
| |
| #ifdef __x86_64__ |
| if (ucall_type == UCALL_PIO && run->exit_reason == KVM_EXIT_IO && |
| run->io.port == UCALL_PIO_PORT) { |
| struct kvm_regs regs; |
| vcpu_regs_get(vm, vcpu_id, ®s); |
| memcpy(&ucall, addr_gva2hva(vm, (vm_vaddr_t)regs.rdi), sizeof(ucall)); |
| got_ucall = true; |
| } |
| #endif |
| if (ucall_type == UCALL_MMIO && run->exit_reason == KVM_EXIT_MMIO && |
| run->mmio.phys_addr == (uint64_t)ucall_exit_mmio_addr) { |
| vm_vaddr_t gva; |
| TEST_ASSERT(run->mmio.is_write && run->mmio.len == 8, |
| "Unexpected ucall exit mmio address access"); |
| memcpy(&gva, run->mmio.data, sizeof(gva)); |
| memcpy(&ucall, addr_gva2hva(vm, gva), sizeof(ucall)); |
| got_ucall = true; |
| } |
| |
| if (got_ucall) { |
| vcpu_run_complete_io(vm, vcpu_id); |
| if (uc) |
| memcpy(uc, &ucall, sizeof(ucall)); |
| } |
| |
| return ucall.cmd; |
| } |