linux/drivers/virt/coco/pkvm-guest/arm-pkvm-guest.c
Will Deacon 0f12694958 drivers/virt: pkvm: Intercept ioremap using pKVM MMIO_GUARD hypercall
Hook up pKVM's MMIO_GUARD hypercall so that ioremap() and friends will
register the target physical address as MMIO with the hypervisor,
allowing guest exits to that page to be emulated by the host with full
syndrome information.

Acked-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20240830130150.8568-7-will@kernel.org
Signed-off-by: Will Deacon <will@kernel.org>
2024-08-30 16:30:41 +01:00

128 lines
2.7 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Support for the hypercall interface exposed to protected guests by
* pKVM.
*
* Author: Will Deacon <will@kernel.org>
* Copyright (C) 2024 Google LLC
*/
#include <linux/arm-smccc.h>
#include <linux/array_size.h>
#include <linux/io.h>
#include <linux/mem_encrypt.h>
#include <linux/mm.h>
#include <linux/pgtable.h>
#include <asm/hypervisor.h>
static size_t pkvm_granule;
static int arm_smccc_do_one_page(u32 func_id, phys_addr_t phys)
{
phys_addr_t end = phys + PAGE_SIZE;
while (phys < end) {
struct arm_smccc_res res;
arm_smccc_1_1_invoke(func_id, phys, 0, 0, &res);
if (res.a0 != SMCCC_RET_SUCCESS)
return -EPERM;
phys += pkvm_granule;
}
return 0;
}
static int __set_memory_range(u32 func_id, unsigned long start, int numpages)
{
void *addr = (void *)start, *end = addr + numpages * PAGE_SIZE;
while (addr < end) {
int err;
err = arm_smccc_do_one_page(func_id, virt_to_phys(addr));
if (err)
return err;
addr += PAGE_SIZE;
}
return 0;
}
static int pkvm_set_memory_encrypted(unsigned long addr, int numpages)
{
return __set_memory_range(ARM_SMCCC_VENDOR_HYP_KVM_MEM_UNSHARE_FUNC_ID,
addr, numpages);
}
static int pkvm_set_memory_decrypted(unsigned long addr, int numpages)
{
return __set_memory_range(ARM_SMCCC_VENDOR_HYP_KVM_MEM_SHARE_FUNC_ID,
addr, numpages);
}
static const struct arm64_mem_crypt_ops pkvm_crypt_ops = {
.encrypt = pkvm_set_memory_encrypted,
.decrypt = pkvm_set_memory_decrypted,
};
static int mmio_guard_ioremap_hook(phys_addr_t phys, size_t size,
pgprot_t *prot)
{
phys_addr_t end;
pteval_t protval = pgprot_val(*prot);
/*
* We only expect MMIO emulation for regions mapped with device
* attributes.
*/
if (protval != PROT_DEVICE_nGnRE && protval != PROT_DEVICE_nGnRnE)
return 0;
phys = PAGE_ALIGN_DOWN(phys);
end = phys + PAGE_ALIGN(size);
while (phys < end) {
const int func_id = ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_FUNC_ID;
int err;
err = arm_smccc_do_one_page(func_id, phys);
if (err)
return err;
phys += PAGE_SIZE;
}
return 0;
}
void pkvm_init_hyp_services(void)
{
int i;
struct arm_smccc_res res;
const u32 funcs[] = {
ARM_SMCCC_KVM_FUNC_HYP_MEMINFO,
ARM_SMCCC_KVM_FUNC_MEM_SHARE,
ARM_SMCCC_KVM_FUNC_MEM_UNSHARE,
};
for (i = 0; i < ARRAY_SIZE(funcs); ++i) {
if (!kvm_arm_hyp_service_available(funcs[i]))
return;
}
arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_KVM_HYP_MEMINFO_FUNC_ID,
0, 0, 0, &res);
if (res.a0 > PAGE_SIZE) /* Includes error codes */
return;
pkvm_granule = res.a0;
arm64_mem_crypt_ops_register(&pkvm_crypt_ops);
if (kvm_arm_hyp_service_available(ARM_SMCCC_KVM_FUNC_MMIO_GUARD))
arm64_ioremap_prot_hook_register(&mmio_guard_ioremap_hook);
}