diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h index fd60175da295..d5148d006060 100644 --- a/arch/arm64/include/asm/kvm_hyp.h +++ b/arch/arm64/include/asm/kvm_hyp.h @@ -127,6 +127,9 @@ extern u64 kvm_nvhe_sym(id_aa64mmfr2_el1_sys_val); struct kvm_iommu_ops { int (*init)(void); bool (*host_smc_handler)(struct kvm_cpu_context *host_ctxt); + bool (*host_mmio_dabt_handler)(struct kvm_cpu_context *host_ctxt, + phys_addr_t fault_pa, unsigned int len, + bool is_write, int rd); void (*host_stage2_set_owner)(phys_addr_t addr, size_t size, u8 owner_id); }; diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index 230f4f7456ad..badd206be6dc 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -445,16 +446,49 @@ unlock: return ret; } +static int host_mmio_dabt_handler(struct kvm_cpu_context *host_ctxt, u32 esr, + phys_addr_t addr) +{ + bool wnr = esr & ESR_ELx_WNR; + unsigned int len = BIT((esr & ESR_ELx_SAS) >> ESR_ELx_SAS_SHIFT); + int rd = (esr & ESR_ELx_SRT_MASK) >> ESR_ELx_SRT_SHIFT; + bool handled = false; + + if (kvm_iommu_ops.host_mmio_dabt_handler) { + handled = kvm_iommu_ops.host_mmio_dabt_handler(host_ctxt, addr, + len, wnr, rd); + } + + if (!handled) + return -EPERM; + + kvm_skip_host_instr(); + return 0; +} + +static bool is_dabt(u64 esr) +{ + return ESR_ELx_EC(esr) == ESR_ELx_EC_DABT_LOW; +} + void handle_host_mem_abort(struct kvm_cpu_context *host_ctxt) { struct kvm_vcpu_fault_info fault; u64 esr, addr; - int ret = 0; + int ret = -EPERM; esr = read_sysreg_el2(SYS_ESR); BUG_ON(!__get_fault_info(esr, &fault)); addr = (fault.hpfar_el2 & HPFAR_MASK) << 8; - ret = host_stage2_idmap(addr); + + /* See if any subsystem can handle this abort. */ + if (is_dabt(esr) && !addr_is_memory(addr)) + ret = host_mmio_dabt_handler(host_ctxt, esr, addr); + + /* If not handled, attempt to map the page. */ + if (ret == -EPERM) + ret = host_stage2_idmap(addr); + BUG_ON(ret && ret != -EAGAIN); }