diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h index d36bef0d06d1..039d50f6e03a 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h @@ -74,6 +74,8 @@ u64 __pkvm_ptdump_walk_range(pkvm_handle_t handle, struct pkvm_ptdump_log_hdr *l int hyp_check_range_owned(u64 addr, u64 size); int __pkvm_install_guest_mmio(struct pkvm_hyp_vcpu *hyp_vcpu, u64 pfn, u64 gfn); +int __pkvm_guest_get_valid_phys_page(struct pkvm_hyp_vm *vm, u64 *phys, u64 ipa); + bool addr_is_memory(phys_addr_t phys); int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot, diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h index 5c8b6a22ebd4..62d560563190 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h +++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h @@ -162,6 +162,8 @@ int pkvm_host_hvc_pd(u64 device_id, u64 on); int pkvm_init_scmi_pd(struct kvm_power_domain *pd, const struct kvm_power_domain_ops *ops); +bool pkvm_device_request_mmio(struct pkvm_hyp_vcpu *hyp_vcpu, u64 *exit_code); + /* * Register a power domain. When the hypervisor catches power requests from the * host for this power domain, it calls the power ops with @pd as argument. diff --git a/arch/arm64/kvm/hyp/nvhe/device/device.c b/arch/arm64/kvm/hyp/nvhe/device/device.c index 89433ab54ae7..bcaf97515e0e 100644 --- a/arch/arm64/kvm/hyp/nvhe/device/device.c +++ b/arch/arm64/kvm/hyp/nvhe/device/device.c @@ -7,6 +7,7 @@ #include #include +#include #include struct pkvm_device *registered_devices; @@ -234,3 +235,70 @@ out_ret: hyp_spin_unlock(&device_spinlock); return ret; } + +static int pkvm_get_device_pa(struct pkvm_hyp_vcpu *hyp_vcpu, u64 ipa, u64 *pa, u64 *exit_code) +{ + struct kvm_hyp_req *req; + int ret; + struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(hyp_vcpu); + + ret = __pkvm_guest_get_valid_phys_page(vm, pa, ipa); + if (ret == -ENOENT) { + /* Page not mapped, create a request*/ + req = pkvm_hyp_req_reserve(hyp_vcpu, KVM_HYP_REQ_TYPE_MAP); + if (!req) + return -ENOMEM; + + req->map.guest_ipa = ipa; + req->map.size = PAGE_SIZE; + *exit_code = ARM_EXCEPTION_HYP_REQ; + /* Repeat next time. */ + write_sysreg_el2(read_sysreg_el2(SYS_ELR) - 4, SYS_ELR); + } + + return ret; +} + +bool pkvm_device_request_mmio(struct pkvm_hyp_vcpu *hyp_vcpu, u64 *exit_code) +{ + int i, j, ret; + struct kvm_vcpu *vcpu = &hyp_vcpu->vcpu; + struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(hyp_vcpu); + u64 ipa = smccc_get_arg1(vcpu); + u64 token; + + /* arg2 and arg3 reserved for future use. */ + if (smccc_get_arg2(vcpu) || smccc_get_arg3(vcpu) || !PAGE_ALIGNED(ipa)) + goto out_inval; + + ret = pkvm_get_device_pa(hyp_vcpu, ipa, &token, exit_code); + if (ret == -ENOENT) + return false; + else if (ret) + goto out_inval; + + hyp_spin_lock(&device_spinlock); + for (i = 0 ; i < registered_devices_nr ; ++i) { + struct pkvm_device *dev = ®istered_devices[i]; + + if (dev->ctxt != vm) + continue; + + for (j = 0 ; j < dev->nr_resources; ++j) { + struct pkvm_dev_resource *res = &dev->resources[j]; + + if ((token >= res->base) && (token + PAGE_SIZE <= res->base + res->size)) { + smccc_set_retval(vcpu, SMCCC_RET_SUCCESS, token, 0, 0); + goto out_ret; + } + } + } + + smccc_set_retval(vcpu, SMCCC_RET_INVALID_PARAMETER, 0, 0, 0); +out_ret: + hyp_spin_unlock(&device_spinlock); + return true; +out_inval: + smccc_set_retval(vcpu, SMCCC_RET_INVALID_PARAMETER, 0, 0, 0); + return true; +} diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index efddb796c20a..a7c588474c0a 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -1837,6 +1837,18 @@ static int guest_get_valid_pte(struct pkvm_hyp_vm *vm, u64 *phys, u64 ipa, u8 or return 0; } +int __pkvm_guest_get_valid_phys_page(struct pkvm_hyp_vm *vm, u64 *phys, u64 ipa) +{ + kvm_pte_t pte; + int ret; + + guest_lock_component(vm); + ret = guest_get_valid_pte(vm, phys, ipa, 0, &pte); + guest_unlock_component(vm); + + return ret; +} + /* * Ideally we would like to use check_unshare()... but this wouldn't let us * restrict the unshare range to the actual guest stage-2 mapping. diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index f74f8d7ede5a..f2849efd1474 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -9,6 +9,7 @@ #include #include +#include #include @@ -1659,6 +1660,8 @@ bool kvm_handle_pvm_hvc64(struct kvm_vcpu *vcpu, u64 *exit_code) if (smccc_trng_available) return pkvm_forward_trng(vcpu); break; + case ARM_SMCCC_VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID: + return pkvm_device_request_mmio(hyp_vcpu, exit_code); default: return pkvm_handle_psci(hyp_vcpu); } diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h index aabf31064935..2c956188c7cb 100644 --- a/include/linux/arm-smccc.h +++ b/include/linux/arm-smccc.h @@ -177,7 +177,7 @@ #define ARM_SMCCC_KVM_FUNC_PKVM_RESV_60 60 #define ARM_SMCCC_KVM_FUNC_PKVM_RESV_61 61 #define ARM_SMCCC_KVM_FUNC_PKVM_RESV_62 62 -#define ARM_SMCCC_KVM_FUNC_PKVM_RESV_63 63 +#define ARM_SMCCC_KVM_FUNC_DEV_REQ_MMIO 63 /* End of pKVM hypercall range */ #define ARM_SMCCC_KVM_FUNC_FEATURES_2 127 #define ARM_SMCCC_KVM_NUM_FUNCS 128 @@ -227,6 +227,12 @@ ARM_SMCCC_OWNER_VENDOR_HYP, \ ARM_SMCCC_KVM_FUNC_MEM_RELINQUISH) +#define ARM_SMCCC_VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_VENDOR_HYP, \ + ARM_SMCCC_KVM_FUNC_DEV_REQ_MMIO) + /* ptp_kvm counter type ID */ #define KVM_PTP_VIRT_COUNTER 0 #define KVM_PTP_PHYS_COUNTER 1