[PATCH v2 18/39] xen/riscv: add guest page fault handling stub

Oleksii Kurochko <[email protected]>
Newsgroups gmane.comp.emulators.xen.devel
Message-ID <42e37df518f1eda9579264b4bae9db3521e1042a.1787838835.git.oleksii.kurochko@gmail.com>
Add a handler for guest page faults and hook it into the trap path,
providing the trap-side entry point which will later feed the MMIO
dispatch.

This will be used, for example, to trap accesses to APLIC registers so
that a guest can initialize and drive an emulated interrupt controller.

Two of the situations handled here are already decided, as neither can
ever be turned into an emulated access:

 - A fault reported with a pseudoinstruction in htinst was taken on an
   implicit access made for VS-stage address translation, so htval holds
   the address of a VS-stage PTE rather than of anything the guest asked
   for, and the guest physical address behind the original access is not
   known. This is orthogonal to the cause and can accompany any of the
   three, which is why it is checked first. scause keeps reporting the
   type of the original access, and on bare hardware a PTE which cannot
   be read raises an access fault of exactly that type, so reflect one
   back to the guest.

 - A fetch fault means the guest tried to execute from a guest physical
   address which is unmapped or which G-stage does not allow to be
   executed. On bare hardware a fetch from physical memory which does
   not exist, or which may not be executed, raises an instruction access
   fault, so reflect one back too.

Explicit loads and stores are where MMIO emulation will hook in.

Neither of the two paths above consults the p2m first, and neither will
the MMIO one: RISC-V has no populate-on-demand, no paging and no
mem_access, so every guest mapping is established eagerly and a G-stage
fault never denotes a mapping Xen could install to let the faulting
access complete.

Both of the helpers this leans on, resolve_faulting_gpa() and
trap_redirect(), are BUG_ON() placeholders for now, so each of the three
causes currently takes the host down rather than the domain. That is no
worse than before this patch, where the same causes fell through to
do_unexpected_trap() and die(). Implementing the helpers is left to
later patches.

Signed-off-by: Oleksii Kurochko <[email protected]>
---
Changes in v2:
 - Introduce struct guest_fault.
 - Change the prototypes of emulate_{load,store}() to take a non-const
   struct guest_fault, as emulation has to write the destination
   register and advance sepc.
 - Add handling of pseudoinstructions before the call of
   emulate_{load,store}.
 - Rename get_fault_gpa to resolve_faulting_gpa and change its prototype
   to take struct guest_fault.
 - Add handling of CAUSE_FETCH_GUEST_PAGE_FAULT now.
 - Document why the p2m is not consulted before a fault is injected, and
   add a BUILD_BUG_ON() on CONFIG_VM_EVENT to catch that assumption
   breaking.
 - Print the fault cause in the domain_crash() message rather than
   deriving an access type string which cannot cover every case.
 - Move code to introduced emulate.c instead of having it in traps.c
---
---
 xen/arch/riscv/Makefile              |   1 +
 xen/arch/riscv/emulate.c             | 179 +++++++++++++++++++++++++++
 xen/arch/riscv/include/asm/emulate.h |  10 ++
 xen/arch/riscv/include/asm/traps.h   |   3 +
 xen/arch/riscv/traps.c               |  23 +++-
 5 files changed, 215 insertions(+), 1 deletion(-)
 create mode 100644 xen/arch/riscv/emulate.c
 create mode 100644 xen/arch/riscv/include/asm/emulate.h

diff --git a/xen/arch/riscv/Makefile b/xen/arch/riscv/Makefile
index ce6410a299a4..4a021ee9eb70 100644
--- a/xen/arch/riscv/Makefile
+++ b/xen/arch/riscv/Makefile
@@ -6,6 +6,7 @@ obj-y += domain.o
 obj-y += domain-build.init.o
 obj-$(CONFIG_DOM0LESS_BOOT) += dom0less-build.init.o
 obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
+obj-y += emulate.o
 obj-y += entry.o
 obj-y += extable.o
 obj-y += guestcopy.o
diff --git a/xen/arch/riscv/emulate.c b/xen/arch/riscv/emulate.c
new file mode 100644
index 000000000000..f9da0751049c
--- /dev/null
+++ b/xen/arch/riscv/emulate.c
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/*
+ * RISC-V instruction emulation for trapped guest accesses
+ */
+
+#include <xen/bug.h>
+#include <xen/errno.h>
+#include <xen/sched.h>
+#include <xen/types.h>
+
+#include <asm/csr.h>
+#include <asm/current.h>
+#include <asm/emulate.h>
+#include <asm/riscv_encoding.h>
+#include <asm/traps.h>
+
+/*
+ * The hardware-reported details of a guest page fault, gathered once by
+ * handle_guest_page_fault() and passed down to the emulation of the faulted
+ * access.
+ */
+struct guest_fault {
+    /* The guest register state as saved on entry to do_trap(). */
+    struct cpu_user_regs *regs;
+    /* scause: a fetch, a load or a store/AMO guest page fault. */
+    unsigned long cause;
+    /*
+     * htinst: the trapped instruction in its transformed form, or one of the
+     * special values (zero, or a pseudoinstruction).
+     */
+    unsigned long htinst;
+    /* htval: as written by hardware; see resolve_faulting_gpa(). */
+    unsigned long htval;
+    /* stval: the guest virtual address of the faulting access. */
+    unsigned long stval;
+    /* The faulting guest physical address, filled by resolve_faulting_gpa(). */
+    paddr_t gpa;
+};
+
+/*
+ * Is @htinst one of the pseudoinstructions reported for a guest page fault
+ * taken on an implicit memory access done for VS-stage address translation?
+ *
+ * All four values are recognized regardless of the hypervisor's XLEN: the
+ * width they encode is that of a VS-stage PTE, i.e. it follows the guest's
+ * paging mode (4 bytes for Sv32, 8 otherwise). On RV32 the 64-bit forms
+ * simply never occur.
+ */
+static bool htinst_is_pseudo(unsigned long htinst)
+{
+    switch ( htinst )
+    {
+    case INSN_PSEUDO_VS_LOAD32:
+    case INSN_PSEUDO_VS_STORE32:
+    case INSN_PSEUDO_VS_LOAD64:
+    case INSN_PSEUDO_VS_STORE64:
+        return true;
+
+    default:
+        return false;
+    }
+}
+
+/* Reconstruct the guest physical address of the access which faulted. */
+static void resolve_faulting_gpa(struct guest_fault *gf)
+{
+    BUG_ON("unimplemented");
+}
+
+static int emulate_load(const struct guest_fault *gf)
+{
+    return -EOPNOTSUPP;
+}
+
+static int emulate_store(struct guest_fault *gf)
+{
+    return -EOPNOTSUPP;
+}
+
+static void inject_access_fault(const struct guest_fault *gf)
+{
+    struct trap_info utrap = {};
+
+    switch ( gf->cause )
+    {
+    case CAUSE_FETCH_GUEST_PAGE_FAULT:
+        utrap.scause = CAUSE_FETCH_ACCESS;
+        break;
+
+    case CAUSE_LOAD_GUEST_PAGE_FAULT:
+        utrap.scause = CAUSE_LOAD_ACCESS;
+        break;
+
+    case CAUSE_STORE_GUEST_PAGE_FAULT:
+        utrap.scause = CAUSE_STORE_ACCESS;
+        break;
+
+    default:
+        domain_crash(current->domain, "Impossible cause (%#lx) in %s?\n",
+                     gf->cause, __func__);
+        return;
+    }
+
+    utrap.sepc = gf->regs->sepc;
+    utrap.stval = gf->stval;
+
+    trap_redirect(&utrap);
+}
+
+void handle_guest_page_fault(struct cpu_user_regs *regs, unsigned long cause)
+{
+    struct guest_fault gf = {
+        .regs = regs,
+        .cause = cause,
+        .htinst = csr_read(CSR_HTINST),
+        .htval = csr_read(CSR_HTVAL),
+        .stval = csr_read(CSR_STVAL),
+        .gpa = INVALID_PADDR,
+    };
+    int rc;
+
+    /*
+     * A guest-page fault may arise due to an implicit memory access during
+     * first-stage (VS-stage) address translation, in which case a guest
+     * physical address written to htval is that of the implicit memory
+     * access that faulted - for example, the address of a VS-level page
+     * table entry that could not be read. (The guest physical address
+     * corresponding to the original virtual address is unknown when
+     * VS-stage translation fails to complete)
+     *
+     * In such cases htinst reports one of the pseudoinstructions recognized
+     * by htinst_is_pseudo(), and the fault requires separate handling (since
+     * G-stage translation failed on an unpopulated/unmapped guest physical
+     * address during a hardware page-table walk). To match bare hardware
+     * behavior, we must inject an access fault of the ORIGINAL access type
+     * (Instruction, Load, or Store/AMO) that initiated the address
+     * translation.
+     */
+    if ( htinst_is_pseudo(gf.htinst) )
+    {
+        inject_access_fault(&gf);
+
+        return;
+    }
+
+    resolve_faulting_gpa(&gf);
+
+    switch ( cause )
+    {
+    case CAUSE_LOAD_GUEST_PAGE_FAULT:
+        rc = emulate_load(&gf);
+        break;
+
+    case CAUSE_STORE_GUEST_PAGE_FAULT:
+        rc = emulate_store(&gf);
+        break;
+
+    case CAUSE_FETCH_GUEST_PAGE_FAULT:
+        /*
+         * Guest is trying to reach unmapped/unpopulated or G-stage PTE doesn't
+         * allow execution (X=0). Generate fetch fault in this case.
+         */
+        inject_access_fault(&gf);
+        rc = 0;
+        break;
+
+    default:
+        rc = -EOPNOTSUPP;
+        ASSERT_UNREACHABLE();
+        break;
+    }
+
+    if ( rc )
+        domain_crash(current->domain,
+                     "%s: unable to handle guest page fault (cause=%#lx) at "
+                     "gpa %#"PRIpaddr"\n",
+                     __func__, cause, gf.gpa);
+}
diff --git a/xen/arch/riscv/include/asm/emulate.h b/xen/arch/riscv/include/asm/emulate.h
new file mode 100644
index 000000000000..59e69ca6794c
--- /dev/null
+++ b/xen/arch/riscv/include/asm/emulate.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef RISCV_EMULATE_H
+#define RISCV_EMULATE_H
+
+struct cpu_user_regs;
+
+void handle_guest_page_fault(struct cpu_user_regs *regs, unsigned long cause);
+
+#endif /* RISCV_EMULATE_H */
diff --git a/xen/arch/riscv/include/asm/traps.h b/xen/arch/riscv/include/asm/traps.h
index 8d4ab664bca9..38c6423742e0 100644
--- a/xen/arch/riscv/include/asm/traps.h
+++ b/xen/arch/riscv/include/asm/traps.h
@@ -17,6 +17,9 @@ void do_trap(struct cpu_user_regs *cpu_regs);
 void handle_trap(void);
 void trap_init(void);
 
+/* Reflect @trap back to the guest, i.e. enter its VS-mode trap handler. */
+void trap_redirect(const struct trap_info *trap);
+
 #endif /* __ASSEMBLER__ */
 
 #endif /* ASM__RISCV__TRAPS_H */
diff --git a/xen/arch/riscv/traps.c b/xen/arch/riscv/traps.c
index 11a6fa1bc942..9cd37d943be1 100644
--- a/xen/arch/riscv/traps.c
+++ b/xen/arch/riscv/traps.c
@@ -14,6 +14,7 @@
 
 #include <asm/extable.h>
 #include <asm/cpufeature.h>
+#include <asm/emulate.h>
 #include <asm/intc.h>
 #include <asm/processor.h>
 #include <asm/riscv_encoding.h>
@@ -193,6 +194,7 @@ void do_trap(struct cpu_user_regs *cpu_regs)
 {
     register_t pc = cpu_regs->sepc;
     unsigned long cause = csr_read(CSR_SCAUSE);
+    bool from_guest = cpu_regs->hstatus & HSTATUS_SPV;
 
     switch ( cause )
     {
@@ -203,6 +205,19 @@ void do_trap(struct cpu_user_regs *cpu_regs)
         vsbi_handle_ecall(cpu_regs);
         break;
 
+    case CAUSE_FETCH_GUEST_PAGE_FAULT:
+    case CAUSE_LOAD_GUEST_PAGE_FAULT:
+    case CAUSE_STORE_GUEST_PAGE_FAULT:
+        /*
+         * A guest page fault taken in Xen context comes from an hlv/hlvx
+         * access made on a vCPU's behalf and is dealt with by the
+         * fixup_exception() above, so only a guest can get here.
+         */
+        BUG_ON(!from_guest);
+
+        handle_guest_page_fault(cpu_regs, cause);
+        break;
+
     case CAUSE_ILLEGAL_INSTRUCTION:
         if ( do_bug_frame(cpu_regs, pc) >= 0 )
         {
@@ -251,7 +266,7 @@ void do_trap(struct cpu_user_regs *cpu_regs)
         break;
     }
 
-    if ( cpu_regs->hstatus & HSTATUS_SPV )
+    if ( from_guest )
         check_for_pcpu_work();
 }
 
@@ -275,3 +290,9 @@ enum mc_disposition arch_do_multicall_call(struct mc_state *state)
     BUG_ON("unimplemented");
     return mc_continue;
 }
+
+/* Redirect trap to Guest. */
+void trap_redirect(const struct trap_info *trap)
+{
+    BUG_ON("unimplemented");
+}
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.