KVM: arm64: Expand pkvm proxy module with helpers for init_vm.

In particular add a way to usermore to do vmalloc and alloc_pages_exact
allocations to share or donate to the kernel.

Also allows user mode to query the position of important fields in the struct
kvm to pass the correct indirect arguments to __pkvm_init_vm

Change-Id: Ic672c30eb54142388417e1b2030a7b9e2aeb2c8d
diff --git a/arch/arm64/include/uapi/asm/pkvm_proxy.h b/arch/arm64/include/uapi/asm/pkvm_proxy.h
new file mode 100644
index 0000000..a343ddc
--- /dev/null
+++ b/arch/arm64/include/uapi/asm/pkvm_proxy.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __ASM_PKVM_PROXY_H
+#define __ASM_PKVM_PROXY_H
+
+#include <linux/kvm_host.h>
+
+#define HPROX_HVC_TYPE 0
+#define HPROX_STRUCTS_TYPE 1
+#define HPROX_ALLOC_TYPE 2
+
+
+#define HVC_PROXY_IOCTL(hvcnum, numarg) \
+	_IOC(_IOC_WRITE, HPROX_HVC_TYPE, hvcnum, 8 * numarg)
+
+
+#define HPROX_STRUCT_KVM_GET_SIZE _IO(HPROX_STRUCTS_TYPE, 0)
+#define HPROX_STRUCT_KVM_GET_OFFSET _IO(HPROX_STRUCTS_TYPE, 1)
+#define HPROX_HYP_VM_GET_SIZE _IO(HPROX_STRUCTS_TYPE, 2)
+#define HPROX_PGD_GET_SIZE _IO(HPROX_STRUCTS_TYPE, 3)
+
+enum struct_kvm_fields {
+	HPROX_NR_MEM_SLOT_PAGES,
+	HPROX_VCPU_ARRAY,
+	HPROX_MAX_VCPUS,
+	HPROX_CREATED_VCPUS,
+	HPROX_ARCH_PKVM_ENABLED,
+	HPROX_ARCH_PKVM_TEARDOWN_MC,
+};
+
+// Need to match up kvm_hyp_memcache
+struct hprox_hyp_memcache {
+	unsigned long head; // kernel address, might not be accessible, if not
+			    // donated from a hprox_alloc region.
+	unsigned long nr_pages;
+};
+enum hprox_alloc_type { HPROX_VMALLOC, HPROX_PAGES_EXACT };
+
+#define HPROX_ALLOC(alloc) _IO(HPROX_ALLOC_TYPE, alloc)
+#define HPROX_ALLOC_PAGES HPROX_ALLOC(HPROX_PAGES_EXACT)
+
+// ioctl on the mmapable fd from the HPROX_ALLOC ioctl
+#define HPROX_ALLOC_KADDR _IOR(0,0, void*)
+#define HPROX_ALLOC_PHYS _IOR(0, 1, void *)
+
+#endif /* __ASM_PKVM_PROXY_H */
diff --git a/arch/arm64/kvm/pkvm_proxy.c b/arch/arm64/kvm/pkvm_proxy.c
index d75adcd..e1e9625 100644
--- a/arch/arm64/kvm/pkvm_proxy.c
+++ b/arch/arm64/kvm/pkvm_proxy.c
@@ -14,6 +14,7 @@
 #include <linux/mm.h>
 #include <linux/preempt.h>
 #include <linux/printk.h>
+#include <linux/anon_inodes.h>
 #include <linux/spinlock.h>
 #include <linux/debugfs.h>
 #include <linux/uaccess.h>
@@ -23,40 +24,237 @@
 #include <linux/uaccess.h>
 #include <asm/setup.h>
 #include <asm/kvm_asm.h>
+#include <asm/kvm_host.h>
+#include <asm/kvm_pgtable.h>
+#include <asm/pkvm_proxy.h>
+#include <hyp_constants.h>
 
-#define PKVM_PROXY_HVC_IOC_TYPE 0
-#define PKVM_PROXY_HELPER_IOC_TYPE 0
+struct pkvm_proxy_alloc {
+	enum hprox_alloc_type type;
+	uint size; // in bytes
+	void* kaddr;
+};
 
-#define HVC_PROXY_IOCTL(hvcnum, numarg) \
-	_IOC(_IOC_WRITE, PKVM_PROXY_HVC_IOC_TYPE, hvcnum, 8 * numarg)
+static long pkvm_proxy_alloc_fd_ioctl(struct file *filep, unsigned int cmd,
+				      unsigned long uarg)
+{
+	struct pkvm_proxy_alloc *alloc = filep->private_data;
+	void** res = (void**)uarg;
+	phys_addr_t phys;
+	BUG_ON(!alloc);
+	switch (cmd){
+	case HPROX_ALLOC_KADDR:
+		if(copy_to_user(res, &alloc->kaddr, sizeof(void*)))
+			return -EIO;
+		return 0;
+	case HPROX_ALLOC_PHYS:
+		phys = virt_to_phys(alloc->kaddr);
+		if (copy_to_user(res, &phys, sizeof(void *)))
+			return -EIO;
+		return 0;
+	default:
+		return -ENOSYS;
+
+	}
+}
+
+static void pkvm_proxy_alloc_free(struct pkvm_proxy_alloc *alloc)
+{
+	switch (alloc->type) {
+	case HPROX_VMALLOC:
+		vfree(alloc->kaddr);
+		break;
+	case HPROX_PAGES_EXACT:
+		free_pages_exact(alloc->kaddr, alloc->size);
+		break;
+	}
+}
+static int pkvm_proxy_alloc_release(struct inode *inode, struct file *filep)
+{
+	struct pkvm_proxy_alloc *alloc = filep->private_data;
+	pkvm_proxy_alloc_free(alloc);
+	kfree(alloc);
+	return 0;
+}
+
+static struct page* virt_to_page_fn(const void * addr)
+{
+	return virt_to_page(addr) ;
+}
+
+static int pkvm_proxy_alloc_mmap(struct file *filep,
+				 struct vm_area_struct *vma)
+{
+	int res;
+	struct pkvm_proxy_alloc *alloc = filep->private_data;
+	unsigned long off;
+	struct page *page;
+	struct page* (*vtop) (const void*);
+
+	if (vma->vm_pgoff != 0 || vma->vm_end - vma->vm_start != PAGE_ALIGN(alloc->size))
+		return -EINVAL;
+	BUG_ON(!alloc->kaddr);
+
+	switch (alloc->type) {
+	case HPROX_VMALLOC:
+		vtop = vmalloc_to_page;
+		break;
+	case HPROX_PAGES_EXACT:
+		vtop = virt_to_page_fn;
+		break;
+	}
+
+	vm_flags_set(vma, VM_DONTEXPAND);
+
+	for (off = 0; off < alloc->size; off += PAGE_SIZE) {
+		page = vtop(alloc->kaddr + off);
+		res = vm_insert_page(vma, vma->vm_start + off, page);
+		if (res)
+			return res;
+	}
+	return 0;
+
+}
 
 static int pkvm_proxy_open(struct inode *inode, struct file *filep)
 {
 	return nonseekable_open(inode, filep);
 }
 
-static long pkvm_proxy_ioctl(struct file *filep, unsigned int cmd,
-			     unsigned long uarg)
+static const struct file_operations pkvm_proxy_alloc_fops = {
+	.release = pkvm_proxy_alloc_release,
+	.unlocked_ioctl = pkvm_proxy_alloc_fd_ioctl,
+	.compat_ioctl = pkvm_proxy_alloc_fd_ioctl,
+	.mmap = pkvm_proxy_alloc_mmap,
+};
+
+static long pkvm_proxy_alloc_ioctl(struct file *filep, unsigned int cmd,
+					unsigned long uarg)
+{
+	int ret;
+	struct pkvm_proxy_alloc *alloc;
+	int fd;
+	struct file *file;
+
+	alloc = kmalloc(sizeof(struct pkvm_proxy_alloc), GFP_KERNEL);
+	if(!alloc)
+		return -ENOMEM;
+	alloc->type = _IOC_NR(cmd);
+	alloc->size = uarg;
+
+	if (alloc->type > HPROX_PAGES_EXACT) {
+		ret = -EINVAL;
+		goto alloc_struct_free;
+	}
+	switch(alloc->type){
+	case HPROX_VMALLOC:
+		alloc->kaddr = vmalloc_user(alloc->size);
+		break;
+	case HPROX_PAGES_EXACT:
+		alloc->kaddr = alloc_pages_exact(alloc->size, GFP_KERNEL);
+		break;
+	}
+	if (!alloc->kaddr) {
+		ret = -ENOMEM;
+		goto alloc_struct_free;
+	}
+
+	fd = get_unused_fd_flags(O_CLOEXEC);
+	if (fd < 0) {
+		ret = fd;
+		goto main_free;
+	}
+
+	file = anon_inode_getfile("hprox-alloc", &pkvm_proxy_alloc_fops,
+				  alloc, O_RDWR);
+	if (IS_ERR(file)) {
+		ret = PTR_ERR(file);
+		goto put_fd;
+	}
+	BUG_ON(file->private_data != alloc);
+	fd_install(fd, file);
+	return fd;
+
+put_fd:
+	put_unused_fd(fd);
+main_free:
+	pkvm_proxy_alloc_free(alloc);
+alloc_struct_free:
+	kfree(alloc);
+	return ret;
+}
+
+static long pkvm_proxy_structs_ioctl(struct file *filep, unsigned int cmd,
+					unsigned long uarg)
+{
+	u32 kvm_ipa_limit = get_kvm_ipa_limit();
+	u64 mmfr0, mmfr1, vtcr;
+	switch(cmd){
+	case HPROX_STRUCT_KVM_GET_SIZE:
+		return sizeof(struct kvm);
+	case HPROX_STRUCT_KVM_GET_OFFSET:
+		switch(uarg) {
+		case HPROX_NR_MEM_SLOT_PAGES:
+			return offsetof(struct kvm, nr_memslot_pages);
+		case HPROX_VCPU_ARRAY:
+			return offsetof(struct kvm, vcpu_array);
+		case HPROX_MAX_VCPUS:
+			return offsetof(struct kvm, max_vcpus);
+		case HPROX_CREATED_VCPUS:
+			return offsetof(struct kvm, created_vcpus);
+		case HPROX_ARCH_PKVM_ENABLED:
+			return offsetof(struct kvm, arch) +
+				offsetof(struct kvm_arch, pkvm) +
+				offsetof(struct kvm_protected_vm, enabled);
+		case HPROX_ARCH_PKVM_TEARDOWN_MC:
+			return offsetof(struct kvm, arch) +
+			       offsetof(struct kvm_arch, pkvm) +
+			       offsetof(struct kvm_protected_vm, teardown_mc);
+		default:
+			return -EINVAL;
+		}
+	case HPROX_HYP_VM_GET_SIZE:
+		return PKVM_HYP_VM_SIZE;
+	case HPROX_PGD_GET_SIZE:
+		mmfr0 = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
+		mmfr1 = read_sanitised_ftr_reg(SYS_ID_AA64MMFR1_EL1);
+		vtcr = kvm_get_vtcr(mmfr0, mmfr1, kvm_ipa_limit);
+		return kvm_pgtable_stage2_pgd_size(vtcr);
+	default:
+		return -ENOSYS;
+	}
+}
+
+static long pkvm_proxy_hvc_ioctl(struct file *filep, unsigned int cmd,
+				 unsigned long uarg)
 {
 	uint args_size;
 	u64 args[7] = {};
 	int id;
 	struct arm_smccc_res res;
+	id = _IOC_NR(cmd);
+	args_size = ALIGN(_IOC_SIZE(cmd), sizeof(u64));
+	if (args_size > 7 * sizeof(u64))
+		return -EINVAL;
+	if (copy_from_user(args, (void __user *)uarg, args_size))
+		return -EACCES;
+	arm_smccc_1_1_hvc(KVM_HOST_SMCCC_ID(id), args[0], args[1], args[2],
+			  args[3], args[4], args[5], args[6], &res);
+	if (res.a0 != SMCCC_RET_SUCCESS)
+		return -EINVAL;
+	return res.a1;
+}
+
+static long pkvm_proxy_ioctl(struct file *filep, unsigned int cmd,
+			     unsigned long uarg)
+{
 	switch (_IOC_TYPE(cmd)) {
-	case PKVM_PROXY_HVC_IOC_TYPE:
-		id = _IOC_NR(cmd);
-		args_size = ALIGN(_IOC_SIZE(cmd), sizeof(u64));
-		if (args_size > 7 * sizeof(u64))
-			return -EINVAL;
-		if (copy_from_user(args, (void*)uarg, args_size)) {
-			return -EACCES;
-		}
-		arm_smccc_1_1_hvc(KVM_HOST_SMCCC_ID(id), args[0], args[1],
-				  args[2], args[3], args[4], args[5], args[6],
-				  &res);
-		if(res.a0 != SMCCC_RET_SUCCESS)
-			return -EINVAL;
-		return res.a1;
+	case HPROX_HVC_TYPE:
+		return pkvm_proxy_hvc_ioctl(filep, cmd, uarg);
+	case HPROX_STRUCTS_TYPE:
+		return pkvm_proxy_structs_ioctl(filep, cmd, uarg);
+	case HPROX_ALLOC_TYPE:
+		return pkvm_proxy_alloc_ioctl(filep, cmd, uarg);
 	default:
 		return -ENOSYS;
 	}