// SPDX-License-Identifier: GPL-2.0-only /* * Support for the hypercall interface exposed to protected guests by * pKVM. * * Author: Will Deacon * Copyright (C) 2024 Google LLC */ #include #include #include #include #include #include #include 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); }