|
[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
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |