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