[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v3 17/39] xen/riscv: add guest page fault handling stub



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.

A G-stage (stage-2) fault has one of three causes, reported via scause:
- Fetch fault: guest tried to execute from a guest-physical address
  that is unmapped or that G-stage marks non-executable. Never
  emulatable (nothing to emulate a fetch into). On real hardware
  this raises an instruction access fault, so Xen reflects the same
  fault back to the guest. Decided here.

- Load fault / Store fault: left undecided by this patch, this is
  where MMIO emulation will hook in later.

Any of these three faults can instead be reported via a pseudoinstruction
in htinst, when both:

(a) the fault occurred on an implicit access Xen made to walk a
    VS-stage page table, and
(b) htval holds a nonzero value: the guest-physical address of that
    VS-stage PTE, not of the guest's original access.

However, none of these paths consult the p2m first, and the future MMIO path
won't either: RISC-V has no populate-on-demand, no paging, and no
mem_access, so every guest mapping is established eagerly. A G-stage
fault therefore never indicates a mapping Xen could lazily resolve to
let the access complete.

resolve_faulting_gpa() is a stub which always fails for now, so any guest
page fault other than one taken on an implicit access for VS-stage
translation currently crashes the domain. That one is reflected to the
guest through trap_redirect(), which is still a BUG_ON() placeholder
though, and so takes the host down. This is no worse than before this
patch, where these traps fell through to do_unexpected_trap() and die().

Cache the "trap came from the guest" test in a local, as the guest page
fault case needs it too.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@xxxxxxxxx>
---
Changes in v3:
 - Update the commit message.
 - Make resolve_faulting_gpa() return an error, which the caller checks.
 - Add a FIXME about guest page tables residing in emulated MMIO.
 - Don't refer to fixup_exception() in the comment ahead of
   BUG_ON(!from_guest): it isn't consulted for guest page faults yet.
 - Mention the from_guest local in the commit message.
 - Say in the comment above resolve_faulting_gpa() that it stores the address
   in @gf->gpa.
 - Make emulate_store() take a pointer to const, as emulate_load() does:
   neither modifies *gf.
---
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             | 183 +++++++++++++++++++++++++++
 xen/arch/riscv/include/asm/emulate.h |  10 ++
 xen/arch/riscv/include/asm/traps.h   |   3 +
 xen/arch/riscv/traps.c               |  22 +++-
 5 files changed, 218 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 5454517724bd..2b84dace6bd5 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..cc4b1fc54054
--- /dev/null
+++ b/xen/arch/riscv/emulate.c
@@ -0,0 +1,183 @@
+/* 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. */
+    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 special pseudoinstruction values, reported for a guest
+ * page fault taken on an implicit memory access done for VS-stage address
+ * translation?
+ *
+ * It is enough to check only bits[1:0] as according to the spec:
+ *
+ * The value is one of the special pseudoinstructions defined later, all of
+ * which have bits 1:0 equal to 00.
+ */
+static bool htinst_is_pseudo(unsigned long htinst)
+{
+    return htinst && !(htinst & 3);
+}
+
+/*
+ * Reconstruct the guest physical address the access faulted on into @gf->gpa.
+ */
+static int resolve_faulting_gpa(struct guest_fault *gf)
+{
+    return -EOPNOTSUPP;
+}
+
+static int emulate_load(const struct guest_fault *gf)
+{
+    return -EOPNOTSUPP;
+}
+
+static int emulate_store(const struct guest_fault *gf)
+{
+    return -EOPNOTSUPP;
+}
+
+static void inject_access_fault(const struct guest_fault *gf)
+{
+    struct trap_info utrap = {
+        .sepc = gf->regs->sepc,
+        .stval = gf->stval,
+    };
+
+    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;
+    }
+
+    trap_redirect(&utrap);
+}
+
+void handle_guest_page_fault(struct cpu_user_regs *regs, unsigned long cause)
+{
+    struct guest_fault gf = {
+        .regs = regs,
+        .cause = cause,
+        .gpa = INVALID_PADDR,
+    };
+    int rc;
+
+    gf.htinst = csr_read(CSR_HTINST);
+    gf.htval = csr_read(CSR_HTVAL);
+    gf.stval = csr_read(CSR_STVAL);
+
+    /*
+     * 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.
+     *
+     * FIXME: This assumes that guest page tables never reside in an emulated
+     * MMIO region, i.e. that an implicit access faulting at G-stage always
+     * targets an unpopulated GPA. Guests may (even transiently) place page
+     * tables in MMIO-backed memory, e.g. a video frame buffer. Supporting
+     * that would require walking the VS-stage page tables in software,
+     * accessing the PTEs (including A/D updates) through the MMIO handlers,
+     * and then emulating the original access, instead of injecting a fault.
+     */
+    if ( htinst_is_pseudo(gf.htinst) )
+    {
+        inject_access_fault(&gf);
+
+        return;
+    }
+
+    rc = resolve_faulting_gpa(&gf);
+    if ( rc )
+        goto out;
+
+    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);
+        return;
+
+    default:
+        ASSERT_UNREACHABLE();
+        rc = -EOPNOTSUPP;
+
+        break;
+    }
+
+ out:
+    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 144b5beaa2c8..4b9ec246e4c9 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,18 @@ 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:
+        /*
+         * Xen doesn't access guest memory, so it can't take a guest page
+         * fault itself: 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 +265,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 +289,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




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.