blob: e1e962534e3f730a77ee859c0591e64077a5e696 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
#include <linux/atomic.h>
#include <linux/compiler.h>
#include <linux/errno.h>
#include <linux/arm-smccc.h>
#include <linux/export.h>
#include <linux/types.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/hashtable.h>
#include <linux/init.h>
#include <linux/kmsan-checks.h>
#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>
#include <linux/kcov.h>
#include <linux/refcount.h>
#include <linux/log2.h>
#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>
struct pkvm_proxy_alloc {
enum hprox_alloc_type type;
uint size; // in bytes
void* kaddr;
};
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 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 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;
}
}
static int pkvm_proxy_close(struct inode *inode, struct file *filep)
{
return 0;
}
static const struct file_operations pkvm_proxy_fops = {
.open = pkvm_proxy_open,
.unlocked_ioctl = pkvm_proxy_ioctl,
.compat_ioctl = pkvm_proxy_ioctl,
/* .mmap = pkvm_proxy_mmap, */
.release = pkvm_proxy_close,
};
static int __init pkvm_proxy_init(void)
{
/*
* The pkvm_proxy debugfs file won't ever get removed and thus,
* there is no need to protect it against removal races. The
* use of debugfs_create_file_unsafe() is actually safe here.
*/
debugfs_create_file_unsafe("pkvm_proxy", 0600, NULL, NULL, &pkvm_proxy_fops);
return 0;
}
device_initcall(pkvm_proxy_init);