| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * KVM host (EL1) interface to Protected KVM (pkvm) code at EL2. |
| * |
| * Copyright (C) 2021 Google LLC |
| * Author: Will Deacon <will@kernel.org> |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/kvm_host.h> |
| #include <linux/mm.h> |
| #include <linux/of_fdt.h> |
| #include <linux/of_reserved_mem.h> |
| |
| static struct reserved_mem *pkvm_firmware_mem; |
| |
| static int __init pkvm_firmware_rmem_err(struct reserved_mem *rmem, |
| const char *reason) |
| { |
| phys_addr_t end = rmem->base + rmem->size; |
| |
| kvm_err("Ignoring pkvm guest firmware memory reservation [%pa - %pa]: %s\n", |
| &rmem->base, &end, reason); |
| return -EINVAL; |
| } |
| |
| static int __init pkvm_firmware_rmem_init(struct reserved_mem *rmem) |
| { |
| unsigned long node = rmem->fdt_node; |
| |
| if (kvm_get_mode() != KVM_MODE_PROTECTED) |
| return pkvm_firmware_rmem_err(rmem, "protected mode not enabled"); |
| |
| if (pkvm_firmware_mem) |
| return pkvm_firmware_rmem_err(rmem, "duplicate reservation"); |
| |
| if (!of_get_flat_dt_prop(node, "no-map", NULL)) |
| return pkvm_firmware_rmem_err(rmem, "missing \"no-map\" property"); |
| |
| if (of_get_flat_dt_prop(node, "reusable", NULL)) |
| return pkvm_firmware_rmem_err(rmem, "\"reusable\" property unsupported"); |
| |
| if (!PAGE_ALIGNED(rmem->base)) |
| return pkvm_firmware_rmem_err(rmem, "base is not page-aligned"); |
| |
| if (!PAGE_ALIGNED(rmem->size)) |
| return pkvm_firmware_rmem_err(rmem, "size is not page-aligned"); |
| |
| pkvm_firmware_mem = rmem; |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(pkvm_firmware, "linux,pkvm-guest-firmware-memory", |
| pkvm_firmware_rmem_init); |
| |
| int kvm_arm_vcpu_pkvm_init(struct kvm_vcpu *vcpu) |
| { |
| struct kvm *kvm = vcpu->kvm; |
| |
| if (!kvm_vm_is_protected(kvm)) |
| return 0; |
| |
| if (!vcpu->vcpu_id) { |
| int i; |
| struct kvm_memory_slot *slot = kvm->arch.pkvm.firmware_slot; |
| struct user_pt_regs *regs = vcpu_gp_regs(vcpu); |
| |
| if (!slot) |
| return 0; |
| |
| /* X0 - X14 provided by VMM (preserved) */ |
| |
| /* X15: Boot protocol version */ |
| regs->regs[15] = 0; |
| |
| /* X16 - X30 reserved (zeroed) */ |
| for (i = 16; i <= 30; ++i) |
| regs->regs[i] = 0; |
| |
| /* PC: IPA base of bootloader memslot */ |
| regs->pc = slot->base_gfn << PAGE_SHIFT; |
| |
| /* SP: IPA end of bootloader memslot */ |
| regs->sp = (slot->base_gfn + slot->npages) << PAGE_SHIFT; |
| } else if (!test_bit(KVM_ARM_VCPU_POWER_OFF, vcpu->arch.features)) { |
| return -EPERM; |
| } |
| |
| return 0; |
| } |
| |
| static int __do_not_call_this_function(struct kvm_memory_slot *slot) |
| { |
| int uncopied; |
| size_t sz = pkvm_firmware_mem->size; |
| void *src, __user *dst = (__force void __user *)slot->userspace_addr; |
| |
| if (clear_user(dst, slot->npages * PAGE_SIZE)) |
| return -EFAULT; |
| |
| src = memremap(pkvm_firmware_mem->base, sz, MEMREMAP_WB); |
| if (!src) |
| return -EFAULT; |
| |
| //((u32 *)src)[0] = 0xaa0f03e0; // MOV X0, X15 |
| //((u32 *)src)[1] = 0xd61f0200; // BR X16 |
| uncopied = copy_to_user(dst, src, sz); |
| memunmap(src); |
| return uncopied ? -EFAULT : 0; |
| } |
| |
| static int pkvm_init_el2_context(struct kvm *kvm) |
| { |
| #if 0 |
| /* |
| * TODO: |
| * Eventually, this will involve a call to EL2 to: |
| * - Register this VM as a protected VM |
| * - Provide pages for the firmware |
| * - Unmap memslots from the host |
| * - Force reset state and lock down access |
| * - Prevent attempts to run unknown vCPUs |
| * - Ensure that no vCPUs have previously entered the VM |
| * - ... |
| */ |
| kvm_pr_unimpl("Stage-2 protection is not yet implemented; ignoring\n"); |
| return 0; |
| #else |
| return __do_not_call_this_function(kvm->arch.pkvm.firmware_slot); |
| #endif |
| } |
| |
| static int pkvm_init_firmware_slot(struct kvm *kvm, u64 slotid) |
| { |
| struct kvm_memslots *slots; |
| struct kvm_memory_slot *slot; |
| |
| if (slotid >= KVM_MEM_SLOTS_NUM || !pkvm_firmware_mem) |
| return -EINVAL; |
| |
| slots = kvm_memslots(kvm); |
| if (!slots) |
| return -ENOENT; |
| |
| slot = id_to_memslot(slots, slotid); |
| if (!slot) |
| return -ENOENT; |
| |
| if (slot->flags) |
| return -EINVAL; |
| |
| if ((slot->npages << PAGE_SHIFT) < pkvm_firmware_mem->size) |
| return -ENOMEM; |
| |
| kvm->arch.pkvm.firmware_slot = slot; |
| return 0; |
| } |
| |
| static void pkvm_teardown_firmware_slot(struct kvm *kvm) |
| { |
| kvm->arch.pkvm.firmware_slot = NULL; |
| } |
| |
| static int pkvm_enable(struct kvm *kvm, u64 slotid) |
| { |
| int ret; |
| |
| ret = pkvm_init_firmware_slot(kvm, slotid); |
| if (ret) |
| return ret; |
| |
| ret = pkvm_init_el2_context(kvm); |
| if (ret) |
| pkvm_teardown_firmware_slot(kvm); |
| |
| return ret; |
| } |
| |
| static int pkvm_vm_ioctl_enable(struct kvm *kvm, u64 slotid) |
| { |
| int ret = 0; |
| |
| mutex_lock(&kvm->lock); |
| if (kvm_vm_is_protected(kvm)) { |
| ret = -EPERM; |
| goto out_kvm_unlock; |
| } |
| |
| mutex_lock(&kvm->slots_lock); |
| ret = pkvm_enable(kvm, slotid); |
| if (ret) |
| goto out_slots_unlock; |
| |
| kvm->arch.pkvm.enabled = true; |
| out_slots_unlock: |
| mutex_unlock(&kvm->slots_lock); |
| out_kvm_unlock: |
| mutex_unlock(&kvm->lock); |
| return ret; |
| } |
| |
| static int pkvm_vm_ioctl_info(struct kvm *kvm, |
| struct kvm_protected_vm_info __user *info) |
| { |
| struct kvm_protected_vm_info kinfo = { |
| .firmware_size = pkvm_firmware_mem ? |
| pkvm_firmware_mem->size : |
| 0, |
| }; |
| |
| return copy_to_user(info, &kinfo, sizeof(kinfo)) ? -EFAULT : 0; |
| } |
| |
| int kvm_arm_vm_ioctl_pkvm(struct kvm *kvm, struct kvm_enable_cap *cap) |
| { |
| if (cap->args[1] || cap->args[2] || cap->args[3]) |
| return -EINVAL; |
| |
| switch (cap->flags) { |
| case KVM_CAP_ARM_PROTECTED_VM_FLAGS_ENABLE: |
| return pkvm_vm_ioctl_enable(kvm, cap->args[0]); |
| case KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: |
| return pkvm_vm_ioctl_info(kvm, (void __user *)cap->args[0]); |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |