diff --git a/arch/s390/kvm/arm64/handle_exit.c b/arch/s390/kvm/arm64/handle_exit.c
index 1286a64783e0..8b03e8ee5acf 100644
--- a/arch/s390/kvm/arm64/handle_exit.c
+++ b/arch/s390/kvm/arm64/handle_exit.c
@@ -55,6 +55,8 @@ static int handle_hvc(struct kvm_vcpu *vcpu)
static exit_handle_fn arm_exit_handlers[] = {
[0 ... ESR_ELx_EC_MAX] = kvm_handle_unknown_ec,
[ESR_ELx_EC_HVC64] = handle_hvc,
+ [ESR_ELx_EC_IABT_LOW] = kvm_handle_guest_abort,
+ [ESR_ELx_EC_DABT_LOW] = kvm_handle_guest_abort,
};
/*diff --git a/arch/s390/kvm/arm64/mmio.c b/arch/s390/kvm/arm64/mmio.c
new file mode 100644
index 000000000000..908a6b43ddf7
--- /dev/null
+++ b/arch/s390/kvm/arm64/mmio.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/kvm_host.h>
+#include <linux/bitfield.h>
+
+#include <trace/events/kvm.h>
+
+#include <arm64/kvm_emulate.h>
+#include <arm64/sysreg.h>
+
+#include "trace.h"
+
+#define __INCL_GEN_ARM_FILE
+#include "generated/mmio.inc"
+#undef __INCL_GEN_ARM_FILE
diff --git a/arch/s390/kvm/arm64/mmu.c b/arch/s390/kvm/arm64/mmu.c
new file mode 100644
index 000000000000..bf3442d3609b
--- /dev/null
+++ b/arch/s390/kvm/arm64/mmu.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kvm_host.h>
+
+#include <arm64/kvm_emulate.h>
+
+#include <faultin.h>
+
+static inline bool kvm_s390_cur_gmap_fault_is_write(struct kvm_vcpu *vcpu)
+{
+ return vcpu->arch.sae_block.hai.pic == PGM_PROTECTION ||
+ vcpu->arch.sae_block.hai.teid.fsi == TEID_FSI_STORE;
+}
+
+/*
+ * user_mem_abort() - handle a dat fault for the gmap of a vcpu
+ *
+ * Return: 0 on success, < 0 in case of error.
+ * Context: The mm lock must not be held before calling. May sleep.
+ */
+static int user_mem_abort(struct kvm_vcpu *vcpu, gpa_t fault_ipa,
+ struct kvm_memory_slot *slot, hva_t hva)
+{
+ struct guest_fault f = { };
+ int ret;
+
+ if (kvm_s390_cur_gmap_fault_is_write(vcpu))
+ f.write_attempt = FOLL_WRITE;
+ f.gfn = gpa_to_gfn(fault_ipa);
+
+ ret = kvm_s390_faultin_gfn(vcpu, NULL, &f);
+ if (ret <= 0)
+ return ret;
+ if (ret == PGM_ADDRESSING)
+ /*
+ * Without the relevant sysregs we cannot do anything for now.
+ * Go back to userspace with an error. TODO sysreg handling
+ */
+ return -ENOEXEC;
+ KVM_BUG_ON(ret, vcpu->kvm);
+ return -EINVAL;
+}
+
+static int kvm_handle_pic(struct kvm_vcpu *vcpu, bool *translation)
+{
+ switch (kvm_vcpu_fault_pic(vcpu)) {
+ /* expected cases: */
+ case PGM_ASCE_TYPE:
+ case PGM_REGION_FIRST_TRANS:
+ case PGM_REGION_SECOND_TRANS:
+ case PGM_REGION_THIRD_TRANS:
+ case PGM_SEGMENT_TRANSLATION:
+ case PGM_PAGE_TRANSLATION:
+ *translation = true;
+ break;
+ case PGM_PROTECTION:
+ break;
+ /* unexpected cases: */
+ case 0:
+ KVM_BUG(1, vcpu->kvm, "On MMU fault path but no fault occurred");
+ return -EFAULT;
+ default:
+ KVM_BUG(1, vcpu->kvm, "Unexpected program interrupt 0x%x, TEID 0x%016lx",
+ vcpu->arch.sae_block.hai.pic, vcpu->arch.sae_block.hai.teid.val);
+ send_sig(SIGSEGV, current, 0);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
+{
+ struct kvm_memory_slot *memslot;
+ bool translation = false;
+ phys_addr_t fault_ipa;
+ unsigned long esr;
+ unsigned long hva;
+ bool write_fault;
+ bool guest_size_err;
+ bool writable;
+ bool is_iabt;
+ int ret;
+ gfn_t gfn;
+ int idx;
+
+ esr = kvm_vcpu_get_esr(vcpu);
+ fault_ipa = kvm_vcpu_get_fault_ipa(vcpu);
+ is_iabt = kvm_vcpu_trap_is_iabt(vcpu);
+ guest_size_err = vcpu->arch.sae_block.icptr == SAE_ICPTR_GUEST_ADDRESS_SIZE;
+
+ if (guest_size_err) {
+ translation = true;
+ } else {
+ ret = kvm_handle_pic(vcpu, &translation);
+ if (ret)
+ return ret;
+ }
+
+ if (translation) {
+ /*
+ * For both cases:
+ * Without the relevant sysregs we cannot do anything for now.
+ * Go back to userspace with an error. TODO sysreg handling
+ */
+ if (fault_ipa >= BIT_ULL(get_kvm_ipa_limit()))
+ return -ENOEXEC;
+
+ if (fault_ipa >= vcpu->kvm->arch.guest_phys_size)
+ return -ENOEXEC;
+ }
+
+ idx = srcu_read_lock(&vcpu->kvm->srcu);
+
+ gfn = fault_ipa >> PAGE_SHIFT;
+
+ memslot = gfn_to_memslot(vcpu->kvm, gfn);
+ hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
+ write_fault = kvm_is_write_fault(vcpu);
+ if (kvm_is_error_hva(hva) || (write_fault && !writable)) {
+ ret = -ENOEXEC;
+ /*
+ * The guest has put either its instructions or its page-tables
+ * somewhere it shouldn't have. Userspace won't be able to do
+ * anything about this (there's no syndrome for a start).
+ *
+ * Without the relevant sysregs we cannot do anything for now.
+ * Go back to userspace with an error. TODO sysreg handling
+ */
+ if (is_iabt)
+ goto out_unlock;
+
+ if (kvm_vcpu_abt_iss1tw(vcpu)) {
+ /*
+ * Without the relevant sysregs we cannot do anything for now.
+ * Go back to userspace with an error. TODO sysreg handling
+ */
+ goto out_unlock;
+ }
+
+ /*
+ * Check for a cache maintenance operation. Assume the guest is
+ * cautious and skip instruction
+ */
+ if (kvm_is_error_hva(hva) && kvm_vcpu_dabt_is_cm(vcpu)) {
+ kvm_incr_pc(vcpu);
+ ret = 1;
+ goto out_unlock;
+ }
+
+ /*
+ * The IPA is reported as [MAX:12], so we need to
+ * complement it with the bottom 12 bits from the
+ * faulting VA. This is always 12 bits, irrespective
+ * of the page size.
+ */
+ fault_ipa |= kvm_vcpu_get_hfar(vcpu) & ((1 << 12) - 1);
+ ret = io_mem_abort(vcpu, fault_ipa);
+ goto out_unlock;
+ }
+
+ /* Userspace should not be able to register out-of-bounds IPAs */
+ VM_BUG_ON(fault_ipa >= vcpu->kvm->arch.guest_phys_size);
+ /*
+ * Proper guest size faults have been injected.
+ * In theory it's fine to have device memory higher than MSL,
+ * even if not currently possible, but that would have been handled above.
+ * So if we get here with a guest size intercept, we have a bug somewhere.
+ */
+ VM_BUG_ON(guest_size_err);
+
+ ret = user_mem_abort(vcpu, fault_ipa, memslot, hva);
+ if (!ret)
+ ret = 1;
+out_unlock:
+ srcu_read_unlock(&vcpu->kvm->srcu, idx);
+ return ret;
+}diff --git a/arch/s390/kvm/arm64/trace.h b/arch/s390/kvm/arm64/trace.h
index 1e3cb6108196..2a205ba0165c 100644
--- a/arch/s390/kvm/arm64/trace.h
+++ b/arch/s390/kvm/arm64/trace.h
@@ -7,6 +7,30 @@
#undef TRACE_SYSTEM
#define TRACE_SYSTEM kvm_arm64
+TRACE_EVENT(kvm_mmio_nisv,
+ TP_PROTO(unsigned long vcpu_pc, unsigned long esr,
+ unsigned long far, unsigned long ipa),
+ TP_ARGS(vcpu_pc, esr, far, ipa),
+
+ TP_STRUCT__entry(
+ __field( unsigned long, vcpu_pc )
+ __field( unsigned long, esr )
+ __field( unsigned long, far )
+ __field( unsigned long, ipa )
+ ),
+
+ TP_fast_assign(
+ __entry->vcpu_pc = vcpu_pc;
+ __entry->esr = esr;
+ __entry->far = far;
+ __entry->ipa = ipa;
+ ),
+
+ TP_printk("ipa %#016lx, esr %#016lx, far %#016lx, pc %#016lx",
+ __entry->ipa, __entry->esr,
+ __entry->far, __entry->vcpu_pc)
+);
+
#endif /* KVM_ARM64_TRACE_KVM_H */
#undef TRACE_INCLUDE_PATH--
2.53.0