* [PATCH v3 1/8] lib/alloc_page: check for NULL ptr passed to free_pages()
2026-10-01 15:16 [PATCH v3 0/8] arm64: Add Stage-2 MMU and Nested Guest Framework Joey Gouly
@ 2026-10-01 15:16 ` Joey Gouly
2026-10-01 15:42 ` Mark Brown
2026-10-01 15:16 ` [PATCH v3 2/8] lib: arm64: Generalize exception vector definitions for EL2 support Joey Gouly
` (6 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:16 UTC (permalink / raw)
To: kvmarm
Cc: Jing Zhang, Marc Zyngier, Oliver Upton, Mark Brown, Wei-Lin Chang,
andrew.jones, Joey Gouly
Don't call virt_to_pfn() before checking for NULL.
---
lib/alloc_page.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/alloc_page.c b/lib/alloc_page.c
index 84f01e11..cbada1f7 100644
--- a/lib/alloc_page.c
+++ b/lib/alloc_page.c
@@ -265,13 +265,14 @@ static bool coalesce(struct mem_area *a, u8 order, pfn_t pfn, pfn_t pfn2)
*/
static void _free_pages(void *mem)
{
- pfn_t pfn2, pfn = virt_to_pfn(mem);
+ pfn_t pfn2, pfn;
struct mem_area *a = NULL;
uintptr_t i, p;
u8 order;
if (!mem)
return;
+ pfn = virt_to_pfn(mem);
assert(IS_ALIGNED((uintptr_t)mem, PAGE_SIZE));
/* find which area this pointer belongs to*/
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH v3 1/8] lib/alloc_page: check for NULL ptr passed to free_pages()
2026-10-01 15:16 ` [PATCH v3 1/8] lib/alloc_page: check for NULL ptr passed to free_pages() Joey Gouly
@ 2026-10-01 15:42 ` Mark Brown
2026-10-01 15:53 ` Joey Gouly
0 siblings, 1 reply; 11+ messages in thread
From: Mark Brown @ 2026-10-01 15:42 UTC (permalink / raw)
To: Joey Gouly
Cc: kvmarm, Jing Zhang, Marc Zyngier, Oliver Upton, Wei-Lin Chang,
andrew.jones
[-- Attachment #1: Type: text/plain, Size: 223 bytes --]
On Thu, Oct 01, 2026 at 04:16:38PM +0100, Joey Gouly wrote:
> Don't call virt_to_pfn() before checking for NULL.
> ---
> lib/alloc_page.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
There's no signoff here.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 1/8] lib/alloc_page: check for NULL ptr passed to free_pages()
2026-10-01 15:42 ` Mark Brown
@ 2026-10-01 15:53 ` Joey Gouly
0 siblings, 0 replies; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:53 UTC (permalink / raw)
To: Mark Brown
Cc: kvmarm, Jing Zhang, Marc Zyngier, Oliver Upton, Wei-Lin Chang,
andrew.jones
On Thu, Oct 01, 2026 at 04:42:33PM +0100, Mark Brown wrote:
> On Thu, Oct 01, 2026 at 04:16:38PM +0100, Joey Gouly wrote:
> > Don't call virt_to_pfn() before checking for NULL.
> > ---
> > lib/alloc_page.c | 3 ++-
> > 1 file changed, 2 insertions(+), 1 deletion(-)
>
> There's no signoff here.
Agh, relatively new setup and clearly some things still not setup
correctly!
Just in case:
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
Thanks,
Joey
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 2/8] lib: arm64: Generalize exception vector definitions for EL2 support
2026-10-01 15:16 [PATCH v3 0/8] arm64: Add Stage-2 MMU and Nested Guest Framework Joey Gouly
2026-10-01 15:16 ` [PATCH v3 1/8] lib/alloc_page: check for NULL ptr passed to free_pages() Joey Gouly
@ 2026-10-01 15:16 ` Joey Gouly
2026-10-01 15:16 ` [PATCH v3 3/8] lib: arm64: Add stage2 page table management library Joey Gouly
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:16 UTC (permalink / raw)
To: kvmarm
Cc: Jing Zhang, Marc Zyngier, Oliver Upton, Mark Brown, Wei-Lin Chang,
andrew.jones, Joey Gouly
From: Jing Zhang <jingzhangos@google.com>
Rename EL1-specific exception vector enumerations to ELx equivalents (e.g.,
EL1H to ELxH and EL0 to ELx_LOW) to make them level-agnostic. This allows
these identifiers to be reused when running at other exception levels, such
as EL2, supporting the upcoming guest management library.
Signed-off-by: Jing Zhang <jingzhangos@google.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
---
arm/debug.c | 6 +++---
arm/gic.c | 2 +-
arm/micro-bench.c | 4 ++--
arm/mte.c | 6 +++---
arm/pl031.c | 2 +-
arm/pmu.c | 2 +-
arm/psci.c | 2 +-
arm/selftest.c | 6 +++---
arm/timer.c | 6 +++---
lib/arm64/asm/processor.h | 32 ++++++++++++++++----------------
lib/arm64/processor.c | 8 ++++----
11 files changed, 38 insertions(+), 38 deletions(-)
diff --git a/arm/debug.c b/arm/debug.c
index 0299fd28..9ba93ac2 100644
--- a/arm/debug.c
+++ b/arm/debug.c
@@ -272,7 +272,7 @@ static noinline void test_hw_bp(bool migrate)
return;
}
- install_exception_handler(EL1H_SYNC, ESR_EC_HW_BP_CURRENT, hw_bp_handler);
+ install_exception_handler(ELxH_SYNC, ESR_EC_HW_BP_CURRENT, hw_bp_handler);
reset_debug_state();
@@ -324,7 +324,7 @@ static noinline void test_wp(bool migrate)
return;
}
- install_exception_handler(EL1H_SYNC, ESR_EC_WP_CURRENT, wp_handler);
+ install_exception_handler(ELxH_SYNC, ESR_EC_WP_CURRENT, wp_handler);
reset_debug_state();
@@ -365,7 +365,7 @@ static noinline void test_ss(bool migrate)
extern unsigned char ss_start;
uint32_t mdscr;
- install_exception_handler(EL1H_SYNC, ESR_EC_SSTEP_CURRENT, ss_handler);
+ install_exception_handler(ELxH_SYNC, ESR_EC_SSTEP_CURRENT, ss_handler);
reset_debug_state();
diff --git a/arm/gic.c b/arm/gic.c
index 256dd80d..cccc5ae1 100644
--- a/arm/gic.c
+++ b/arm/gic.c
@@ -180,7 +180,7 @@ static void setup_irq(irq_handler_fn handler)
#ifdef __arm__
install_exception_handler(EXCPTN_IRQ, handler);
#else
- install_irq_handler(EL1H_IRQ, handler);
+ install_irq_handler(ELxH_IRQ, handler);
#endif
local_irq_enable();
}
diff --git a/arm/micro-bench.c b/arm/micro-bench.c
index a6a78f20..6ca36c55 100644
--- a/arm/micro-bench.c
+++ b/arm/micro-bench.c
@@ -52,7 +52,7 @@ static void gic_irq_handler(struct pt_regs *regs)
static void gic_secondary_entry(void *data)
{
- install_irq_handler(EL1H_IRQ, gic_irq_handler);
+ install_irq_handler(ELxH_IRQ, gic_irq_handler);
gic_enable_defaults();
local_irq_enable();
irq_ready = true;
@@ -212,7 +212,7 @@ static void lpi_exec(void)
static bool timer_prep(void)
{
gic_enable_defaults();
- install_irq_handler(EL1H_IRQ, gic_irq_handler);
+ install_irq_handler(ELxH_IRQ, gic_irq_handler);
local_irq_enable();
if (current_level() == CurrentEL_EL1)
diff --git a/arm/mte.c b/arm/mte.c
index 35707fc6..ad71b104 100644
--- a/arm/mte.c
+++ b/arm/mte.c
@@ -192,7 +192,7 @@ static void mte_sync_test(void)
mte_exception = false;
- install_exception_handler(EL1H_SYNC, ESR_ELx_EC_DABT_CUR, mte_fault_handler);
+ install_exception_handler(ELxH_SYNC, ESR_ELx_EC_DABT_CUR, mte_fault_handler);
mem_read(tagged(mem, 2), &val);
@@ -218,12 +218,12 @@ static void mte_asymm_test(void)
mte_set_tcf(MTE_TCF_ASYMM);
mte_exception = false;
- install_exception_handler(EL1H_SYNC, ESR_ELx_EC_DABT_CUR, mte_fault_handler);
+ install_exception_handler(ELxH_SYNC, ESR_ELx_EC_DABT_CUR, mte_fault_handler);
mem_read(tagged(mem, 3), &val);
report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
- install_exception_handler(EL1H_SYNC, ESR_ELx_EC_DABT_CUR, NULL);
+ install_exception_handler(ELxH_SYNC, ESR_ELx_EC_DABT_CUR, NULL);
mem_write(tagged(mem, 4), 0xaaaaaaaa);
report((*mem == 0xaaaaaaaa) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
diff --git a/arm/pl031.c b/arm/pl031.c
index c56805e4..3c739d4d 100644
--- a/arm/pl031.c
+++ b/arm/pl031.c
@@ -162,7 +162,7 @@ static int check_rtc_irq(void)
writel(before + seconds_to_wait, &pl031->mr);
#ifdef __aarch64__
- install_irq_handler(EL1H_IRQ, irq_handler);
+ install_irq_handler(ELxH_IRQ, irq_handler);
#else
install_exception_handler(EXCPTN_IRQ, irq_handler);
#endif
diff --git a/arm/pmu.c b/arm/pmu.c
index 2fcec71a..90878641 100644
--- a/arm/pmu.c
+++ b/arm/pmu.c
@@ -968,7 +968,7 @@ static void test_overflow_interrupt(bool overflow_at_64bits)
return;
gic_enable_defaults();
- install_irq_handler(EL1H_IRQ, irq_handler);
+ install_irq_handler(ELxH_IRQ, irq_handler);
local_irq_enable();
gic_enable_irq(23);
diff --git a/arm/psci.c b/arm/psci.c
index 5800c0ac..1f87736a 100644
--- a/arm/psci.c
+++ b/arm/psci.c
@@ -36,7 +36,7 @@ static void install_invalid_function_handler(exception_fn handler)
#ifdef __arm__
install_exception_handler(EXCPTN_UND, handler);
#else
- install_exception_handler(EL1H_SYNC, ESR_ELx_EC_UNKNOWN, handler);
+ install_exception_handler(ELxH_SYNC, ESR_ELx_EC_UNKNOWN, handler);
#endif
}
diff --git a/arm/selftest.c b/arm/selftest.c
index d9f41e8e..ba81d556 100644
--- a/arm/selftest.c
+++ b/arm/selftest.c
@@ -305,7 +305,7 @@ static enum vector check_vector_prep(void)
unsigned long daif;
if (is_user())
- return EL0_SYNC_64;
+ return ELx_LOW_SYNC_64;
asm volatile("mrs %0, daif" : "=r" (daif) ::);
expected_regs.pstate = daif;
@@ -313,7 +313,7 @@ static enum vector check_vector_prep(void)
expected_regs.pstate |= PSR_MODE_EL1h;
else
expected_regs.pstate |= PSR_MODE_EL2h;
- return EL1H_SYNC;
+ return ELxH_SYNC;
}
static void unknown_handler(struct pt_regs *regs, unsigned int esr __unused)
@@ -400,7 +400,7 @@ static void check_vectors(void *arg __unused)
#ifdef __arm__
install_exception_handler(EXCPTN_UND, user_psci_system_off);
#else
- install_exception_handler(EL0_SYNC_64, ESR_ELx_EC_UNKNOWN,
+ install_exception_handler(ELx_LOW_SYNC_64, ESR_ELx_EC_UNKNOWN,
user_psci_system_off);
#endif
} else {
diff --git a/arm/timer.c b/arm/timer.c
index 898043f0..ed332b93 100644
--- a/arm/timer.c
+++ b/arm/timer.c
@@ -356,9 +356,9 @@ static void test_init(void)
vtimer_info.irq = TIMER_HVTIMER_IRQ;
}
- install_exception_handler(EL1H_SYNC, ESR_ELx_EC_UNKNOWN, ptimer_unsupported_handler);
+ install_exception_handler(ELxH_SYNC, ESR_ELx_EC_UNKNOWN, ptimer_unsupported_handler);
ptimer_info.read_ctl();
- install_exception_handler(EL1H_SYNC, ESR_ELx_EC_UNKNOWN, NULL);
+ install_exception_handler(ELxH_SYNC, ESR_ELx_EC_UNKNOWN, NULL);
if (ptimer_unsupported && !ERRATA(7b6b46311a85)) {
report_skip("Skipping ptimer tests. Set ERRATA_7b6b46311a85=y to enable.");
@@ -369,7 +369,7 @@ static void test_init(void)
gic_enable_defaults();
- install_irq_handler(EL1H_IRQ, irq_handler);
+ install_irq_handler(ELxH_IRQ, irq_handler);
set_timer_irq_enabled(&ptimer_info, true);
set_timer_irq_enabled(&vtimer_info, true);
local_irq_enable();
diff --git a/lib/arm64/asm/processor.h b/lib/arm64/asm/processor.h
index 32ddc1b3..c20dd599 100644
--- a/lib/arm64/asm/processor.h
+++ b/lib/arm64/asm/processor.h
@@ -13,22 +13,22 @@
#include <asm/barrier.h>
enum vector {
- EL1T_SYNC,
- EL1T_IRQ,
- EL1T_FIQ,
- EL1T_ERROR,
- EL1H_SYNC,
- EL1H_IRQ,
- EL1H_FIQ,
- EL1H_ERROR,
- EL0_SYNC_64,
- EL0_IRQ_64,
- EL0_FIQ_64,
- EL0_ERROR_64,
- EL0_SYNC_32,
- EL0_IRQ_32,
- EL0_FIQ_32,
- EL0_ERROR_32,
+ ELxT_SYNC,
+ ELxT_IRQ,
+ ELxT_FIQ,
+ ELxT_ERROR,
+ ELxH_SYNC,
+ ELxH_IRQ,
+ ELxH_FIQ,
+ ELxH_ERROR,
+ ELx_LOW_SYNC_64,
+ ELx_LOW_IRQ_64,
+ ELx_LOW_FIQ_64,
+ ELx_LOW_ERROR_64,
+ ELx_LOW_SYNC_32,
+ ELx_LOW_IRQ_32,
+ ELx_LOW_FIQ_32,
+ ELx_LOW_ERROR_32,
VECTOR_MAX,
};
diff --git a/lib/arm64/processor.c b/lib/arm64/processor.c
index 86328c01..5e5cdc77 100644
--- a/lib/arm64/processor.c
+++ b/lib/arm64/processor.c
@@ -198,10 +198,10 @@ void default_vector_irq_handler(enum vector v, struct pt_regs *regs,
void vector_handlers_default_init(vector_fn *handlers)
{
- handlers[EL1H_SYNC] = default_vector_sync_handler;
- handlers[EL1H_IRQ] = default_vector_irq_handler;
- handlers[EL0_SYNC_64] = default_vector_sync_handler;
- handlers[EL0_IRQ_64] = default_vector_irq_handler;
+ handlers[ELxH_SYNC] = default_vector_sync_handler;
+ handlers[ELxH_IRQ] = default_vector_irq_handler;
+ handlers[ELx_LOW_SYNC_64] = default_vector_sync_handler;
+ handlers[ELx_LOW_IRQ_64] = default_vector_irq_handler;
}
/* Needed to compile with -Wmissing-prototypes */
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 3/8] lib: arm64: Add stage2 page table management library
2026-10-01 15:16 [PATCH v3 0/8] arm64: Add Stage-2 MMU and Nested Guest Framework Joey Gouly
2026-10-01 15:16 ` [PATCH v3 1/8] lib/alloc_page: check for NULL ptr passed to free_pages() Joey Gouly
2026-10-01 15:16 ` [PATCH v3 2/8] lib: arm64: Generalize exception vector definitions for EL2 support Joey Gouly
@ 2026-10-01 15:16 ` Joey Gouly
2026-10-01 15:16 ` [PATCH v3 4/8] lib: arm64: Add foundational guest execution framework Joey Gouly
` (4 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:16 UTC (permalink / raw)
To: kvmarm
Cc: Jing Zhang, Marc Zyngier, Oliver Upton, Mark Brown, Wei-Lin Chang,
andrew.jones, Joey Gouly
From: Jing Zhang <jingzhangos@google.com>
Tests running at EL2 (hypervisor level) require the ability to manage Stage
2 translation tables to control Guest Physical Address (IPA) to Host
Physical Address (PA) translation.
Add a generic Stage 2 MMU library that provides software management of
ARM64 Stage-2 translation tables.
The library features include:
- Support for 4K translation granules, further work needed for 16K/64K.
- Dynamic page table allocation using the allocator.
- Basic support for block mappings where applicable.
- APIs for mapping, unmapping, and loading the Stage 2 MMU.
- Basic fault info reporting (ESR, FAR, HPFAR).
Signed-off-by: Jing Zhang <jingzhangos@google.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
---
arm/Makefile.arm64 | 1 +
lib/arm64/asm/stage2_mmu.h | 76 +++++++
lib/arm64/asm/sysreg.h | 29 +++
lib/arm64/stage2_mmu.c | 406 +++++++++++++++++++++++++++++++++++++
4 files changed, 512 insertions(+)
create mode 100644 lib/arm64/asm/stage2_mmu.h
create mode 100644 lib/arm64/stage2_mmu.c
diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
index a40c830d..5e50f5ba 100644
--- a/arm/Makefile.arm64
+++ b/arm/Makefile.arm64
@@ -40,6 +40,7 @@ cflatobjs += lib/arm64/stack.o
cflatobjs += lib/arm64/processor.o
cflatobjs += lib/arm64/spinlock.o
cflatobjs += lib/arm64/gic-v3-its.o lib/arm64/gic-v3-its-cmd.o
+cflatobjs += lib/arm64/stage2_mmu.o
ifeq ($(CONFIG_EFI),y)
cflatobjs += lib/acpi.o
diff --git a/lib/arm64/asm/stage2_mmu.h b/lib/arm64/asm/stage2_mmu.h
new file mode 100644
index 00000000..182225e1
--- /dev/null
+++ b/lib/arm64/asm/stage2_mmu.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2026, Google LLC.
+ * Author: Jing Zhang <jingzhangos@google.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#ifndef _ASMARM64_STAGE2_MMU_H_
+#define _ASMARM64_STAGE2_MMU_H_
+
+#include <libcflat.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+
+#define pte_is_table(pte) (pte_val(pte) & PTE_TABLE_BIT)
+
+/* Stage-2 Memory Attributes (MemAttr[3:0]) */
+#define S2_MEMATTR_NORMAL (0xFUL << 2) /* Normal Memory, Outer/Inner Write-Back */
+#define S2_MEMATTR_DEVICE (0x0UL << 2) /* Device-nGnRnE */
+
+/* Stage-2 Access Permissions (S2AP[1:0]) */
+#define S2AP_NONE (0UL << 6)
+#define S2AP_RO (1UL << 6) /* Read-only */
+#define S2AP_WO (2UL << 6) /* Write-only */
+#define S2AP_RW (3UL << 6) /* Read-Write */
+
+/* Flags for mapping */
+#define S2_MAP_RW (S2AP_RW | S2_MEMATTR_NORMAL | PTE_AF | PTE_SHARED)
+#define S2_MAP_DEVICE (S2AP_RW | S2_MEMATTR_DEVICE | PTE_AF)
+
+enum s2_granule {
+ S2_PAGE_4K,
+ S2_PAGE_16K,
+ S2_PAGE_64K,
+};
+
+enum s2mmu_error {
+ S2MMU_ERR_NOMEM = -1,
+ S2MMU_ERR_MAPPING_CONFLICT = -2,
+};
+
+/* Main Stage-2 MMU Structure */
+struct s2_mmu {
+ pgd_t *pgd;
+ int vmid;
+ unsigned long vtcr_el2;
+ unsigned long vttbr_el2;
+
+ /* Configuration */
+ enum s2_granule granule;
+ bool allow_block_mappings;
+
+ /* Internal helpers calculated from granule & VA_BITS */
+ unsigned int page_shift;
+ unsigned int level_shift;
+ int root_level; /* 0, 1, or 2 */
+ unsigned long page_size;
+ unsigned long block_size;
+};
+
+/* API */
+/* Initialize an s2_mmu struct with specific settings */
+struct s2_mmu *s2mmu_init(int vmid, enum s2_granule granule, bool allow_block_mappings);
+
+/* Management */
+void s2mmu_destroy(struct s2_mmu *mmu);
+int s2mmu_map(struct s2_mmu *mmu, unsigned long ipa, unsigned long pa,
+ unsigned long size, unsigned long flags);
+void s2mmu_unmap(struct s2_mmu *mmu, unsigned long ipa, unsigned long size);
+
+/* Install this context on the current CPU. */
+void s2mmu_load(const struct s2_mmu *mmu);
+
+/* Debug */
+void s2mmu_print_fault_info(void);
+
+#endif /* _ASMARM64_STAGE2_MMU_H_ */
diff --git a/lib/arm64/asm/sysreg.h b/lib/arm64/asm/sysreg.h
index f2d05018..72767cf0 100644
--- a/lib/arm64/asm/sysreg.h
+++ b/lib/arm64/asm/sysreg.h
@@ -118,6 +118,35 @@ asm(
#define SCTLR_EL1_TCF0_SHIFT 38
#define SCTLR_EL1_TCF0_MASK GENMASK_ULL(39, 38)
+#define VTCR_EL2_RES1 _BITULL(31)
+
+#define VTCR_EL2_T0SZ_MASK GENMASK_ULL(5, 0)
+
+#define VTCR_EL2_SL0_SHIFT 6
+
+#define VTCR_EL2_IRGN0_SHIFT 8
+#define VTCR_EL2_IRGN0_WBWA (UL(1) << VTCR_EL2_IRGN0_SHIFT)
+
+#define VTCR_EL2_ORGN0_SHIFT 10
+#define VTCR_EL2_ORGN0_WBWA (UL(1) << VTCR_EL2_ORGN0_SHIFT)
+
+#define VTCR_EL2_SH0_SHIFT 12
+#define VTCR_EL2_SH0_INNER (UL(3) << VTCR_EL2_SH0_SHIFT)
+
+#define VTCR_EL2_TG0_SHIFT 14
+#define VTCR_EL2_TG0_4K (UL(0) << VTCR_EL2_TG0_SHIFT)
+#define VTCR_EL2_TG0_64K (UL(1) << VTCR_EL2_TG0_SHIFT)
+#define VTCR_EL2_TG0_16K (UL(2) << VTCR_EL2_TG0_SHIFT)
+
+#define VTCR_EL2_PS_SHIFT 16
+#define VTCR_EL2_PS_40BIT (UL(2) << VTCR_EL2_PS_SHIFT)
+#define VTCR_EL2_PS_48BIT (UL(5) << VTCR_EL2_PS_SHIFT)
+
+#define VTTBR_EL2_CNP _BITULL(0)
+#define VTTBR_EL2_BADDR_MASK GENMASK_ULL(47, 1)
+#define VTTBR_EL2_VMID_SHIFT 48
+#define VTTBR_EL2_VMID_MASK GENMASK_ULL(63, 48)
+
#define HCR_EL2_TGE _BITULL(27)
#define HCR_EL2_RW _BITULL(31)
#define HCR_EL2_E2H _BITULL(34)
diff --git a/lib/arm64/stage2_mmu.c b/lib/arm64/stage2_mmu.c
new file mode 100644
index 00000000..c1c371f4
--- /dev/null
+++ b/lib/arm64/stage2_mmu.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2026, Google LLC.
+ * Author: Jing Zhang <jingzhangos@google.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#include <libcflat.h>
+#include <alloc.h>
+#include <asm/stage2_mmu.h>
+#include <asm/sysreg.h>
+#include <asm/io.h>
+#include <asm/barrier.h>
+#include <alloc_page.h>
+
+/* Physical Address Size (PS) - Derive from VA_BITS for simplicity or max */
+#if VA_BITS > 40
+#define VTCR_PS_VAL VTCR_EL2_PS_48BIT
+#else
+#define VTCR_PS_VAL VTCR_EL2_PS_40BIT
+#endif
+
+#define S2MMU_SUPPORTED_IPA_BITS 48
+#define S2MMU_MAX_VMID 255
+
+#if VA_BITS != S2MMU_SUPPORTED_IPA_BITS
+#error "The stage-2 MMU currently supports only a 48-bit IPA"
+#endif
+
+static unsigned long s2mmu_build_vtcr(const struct s2_mmu *mmu)
+{
+ unsigned long vtcr = VTCR_EL2_RES1 | VTCR_PS_VAL |
+ VTCR_EL2_SH0_INNER | VTCR_EL2_ORGN0_WBWA |
+ VTCR_EL2_IRGN0_WBWA;
+
+ switch (mmu->granule) {
+ case S2_PAGE_4K:
+ vtcr |= VTCR_EL2_TG0_4K;
+ /* SL0 Encodings for 4K: 0=L2, 1=L1, 2=L0 */
+ if (mmu->root_level == 0)
+ vtcr |= (2UL << VTCR_EL2_SL0_SHIFT); /* Start L0 */
+ else if (mmu->root_level == 1)
+ vtcr |= (1UL << VTCR_EL2_SL0_SHIFT); /* Start L1 */
+ else
+ vtcr |= (0UL << VTCR_EL2_SL0_SHIFT); /* Start L2 */
+ break;
+ case S2_PAGE_16K:
+ vtcr |= VTCR_EL2_TG0_16K;
+ /* SL0 Encodings for 16K: 1=L2, 2=L1 */
+ if (mmu->root_level == 1)
+ vtcr |= (2UL << VTCR_EL2_SL0_SHIFT); /* Start L1 */
+ else
+ vtcr |= (1UL << VTCR_EL2_SL0_SHIFT); /* Start L2 */
+ break;
+ case S2_PAGE_64K:
+ vtcr |= VTCR_EL2_TG0_64K;
+ /* SL0 Encodings for 64K: 1=L2, 2=L1 */
+ if (mmu->root_level == 1)
+ vtcr |= (2UL << VTCR_EL2_SL0_SHIFT); /* Start L1 */
+ else
+ vtcr |= (1UL << VTCR_EL2_SL0_SHIFT); /* Start L2 */
+ break;
+ }
+
+ return vtcr | ((64 - VA_BITS) & VTCR_EL2_T0SZ_MASK);
+}
+
+struct s2_mmu *s2mmu_init(int vmid, enum s2_granule granule, bool allow_block_mappings)
+{
+ struct s2_mmu *mmu;
+ int order = 0;
+
+ /*
+ * Only accept limited configurations for now.
+ */
+ if (vmid < 0 || vmid > S2MMU_MAX_VMID || granule != S2_PAGE_4K)
+ return NULL;
+
+ mmu = calloc(1, sizeof(*mmu));
+ if (!mmu)
+ return NULL;
+
+ mmu->vmid = vmid;
+ mmu->granule = granule;
+ mmu->allow_block_mappings = allow_block_mappings;
+
+ /* Configure shifts based on granule */
+ switch (granule) {
+ case S2_PAGE_4K:
+ mmu->page_shift = 12;
+ mmu->level_shift = 9;
+ /*
+ * Determine Root Level for 4K:
+ * VA_BITS > 39 (e.g. 48) -> Start L0
+ * VA_BITS <= 39 (e.g. 32, 36) -> Start L1
+ */
+ mmu->root_level = (VA_BITS > 39) ? 0 : 1;
+ break;
+ case S2_PAGE_16K:
+ mmu->page_shift = 14;
+ mmu->level_shift = 11;
+ /*
+ * 16K: L1 covers 47 bits. L0 not valid for 16K
+ * Start L1 for 47 bits. Start L2 for 36 bits.
+ */
+ mmu->root_level = (VA_BITS > 36) ? 1 : 2;
+ break;
+ case S2_PAGE_64K:
+ mmu->page_shift = 16;
+ mmu->level_shift = 13;
+ /* 64K: L1 covers 52 bits. L2 covers 42 bits. */
+ mmu->root_level = (VA_BITS > 42) ? 1 : 2;
+ break;
+ }
+
+ mmu->page_size = 1UL << mmu->page_shift;
+ mmu->block_size = 1UL << (mmu->page_shift + mmu->level_shift);
+
+ /* Alloc PGD. Use order for allocation size */
+ if (mmu->page_size > PAGE_SIZE) {
+ order = __builtin_ctz(mmu->page_size / PAGE_SIZE);
+ }
+ mmu->pgd = (pgd_t *)alloc_pages(order);
+ if (mmu->pgd) {
+ memset(mmu->pgd, 0, mmu->page_size);
+ } else {
+ free(mmu);
+ return NULL;
+ }
+
+ mmu->vtcr_el2 = s2mmu_build_vtcr(mmu);
+ mmu->vttbr_el2 = (virt_to_phys(mmu->pgd) & VTTBR_EL2_BADDR_MASK) |
+ (((unsigned long)mmu->vmid << VTTBR_EL2_VMID_SHIFT) &
+ VTTBR_EL2_VMID_MASK);
+
+ return mmu;
+}
+
+static unsigned long s2mmu_get_addr_mask(struct s2_mmu *mmu)
+{
+ switch (mmu->granule) {
+ case S2_PAGE_16K:
+ return GENMASK_ULL(47, 14);
+ case S2_PAGE_64K:
+ return GENMASK_ULL(47, 16);
+ default:
+ return GENMASK_ULL(47, 12); /* 4K */
+ }
+}
+
+static void s2mmu_tlbi_all(void)
+{
+ /* Publish page-table writes before invalidating cached translations. */
+ dsb(ishst);
+ asm volatile("tlbi vmalls12e1is" : : : "memory");
+ dsb(ish);
+ isb();
+}
+
+static void s2mmu_free_tables(struct s2_mmu *mmu, pte_t *table, int level)
+{
+ unsigned long entries = 1UL << mmu->level_shift;
+ unsigned long mask = s2mmu_get_addr_mask(mmu);
+ unsigned long i;
+
+ /*
+ * Recurse if not leaf level
+ * Level 3 is always leaf page. Levels 0-2 can be Table or Block.
+ */
+ if (level < 3) {
+ for (i = 0; i < entries; i++) {
+ pte_t entry = table[i];
+ if ((pte_valid(entry) && pte_is_table(entry))) {
+ pte_t *next = (pte_t *)phys_to_virt(pte_val(entry) & mask);
+ s2mmu_free_tables(mmu, next, level + 1);
+ }
+ }
+ }
+
+ free_pages(table);
+}
+
+void s2mmu_destroy(struct s2_mmu *mmu)
+{
+ if (mmu->pgd)
+ s2mmu_free_tables(mmu, (pte_t *)mmu->pgd, mmu->root_level);
+ free(mmu);
+}
+
+void s2mmu_load(const struct s2_mmu *mmu)
+{
+ write_sysreg(mmu->vtcr_el2, vtcr_el2);
+ write_sysreg(mmu->vttbr_el2, vttbr_el2);
+ isb();
+ s2mmu_tlbi_all();
+}
+
+static int get_pte(struct s2_mmu *mmu, pte_t *table, unsigned long idx,
+ pte_t **next_table)
+{
+ unsigned long mask = s2mmu_get_addr_mask(mmu);
+ pte_t entry = table[idx];
+ int order = 0;
+
+ if (pte_valid(entry)) {
+ if (!pte_is_table(entry))
+ return S2MMU_ERR_MAPPING_CONFLICT;
+
+ *next_table = (pte_t *)phys_to_virt(pte_val(entry) & mask);
+ return 0;
+ }
+
+ /* Allocate table memory covering the Stage-2 Granule size */
+ if (mmu->page_size > PAGE_SIZE)
+ order = __builtin_ctz(mmu->page_size / PAGE_SIZE);
+
+ *next_table = (pte_t *)alloc_pages(order);
+ if (!*next_table)
+ return S2MMU_ERR_NOMEM;
+
+ memset(*next_table, 0, mmu->page_size);
+
+ pte_val(entry) = virt_to_phys(*next_table) | PTE_TABLE_BIT | PTE_VALID;
+ WRITE_ONCE(table[idx], entry);
+
+ return 0;
+}
+
+int s2mmu_map(struct s2_mmu *mmu, unsigned long ipa, unsigned long pa,
+ unsigned long size, unsigned long flags)
+{
+ unsigned long level_mask, level_shift, level_size, level;
+ unsigned long start_ipa, end_ipa, idx;
+ pte_t entry, *table, *next_table;
+ bool is_block_level;
+ int ret;
+
+ start_ipa = ipa;
+ end_ipa = ipa + size;
+ level_mask = (1UL << mmu->level_shift) - 1;
+
+ while (start_ipa < end_ipa) {
+ table = (pte_t *)mmu->pgd;
+
+ /* Walk from Root to Leaf */
+ for (level = mmu->root_level; level < 3; level++) {
+ level_shift = mmu->page_shift + (3 - level) * mmu->level_shift;
+ idx = (start_ipa >> level_shift) & level_mask;
+ level_size = 1UL << level_shift;
+
+ /*
+ * Check for Block Mapping
+ * Valid Block Levels:
+ * 4K: L1 (1G), L2 (2MB)
+ * 16K: L2 (32MB)
+ * 64K: L2 (512MB)
+ */
+ is_block_level = (level == 2) ||
+ (mmu->granule == S2_PAGE_4K && level == 1);
+
+ if (mmu->allow_block_mappings && is_block_level) {
+ if ((start_ipa & (level_size - 1)) == 0 &&
+ (pa & (level_size - 1)) == 0 &&
+ (start_ipa + level_size) <= end_ipa) {
+ /* Map Block */
+ pte_val(entry) = (pa & ~(level_size - 1)) |
+ flags | PTE_VALID;
+ WRITE_ONCE(table[idx], entry);
+ start_ipa += level_size;
+ pa += level_size;
+ goto next_chunk; /* Continue outer loop */
+ }
+ }
+
+ /* Move to next level */
+ ret = get_pte(mmu, table, idx, &next_table);
+ if (ret)
+ return ret;
+ table = next_table;
+ }
+
+ /* Leaf Level (Level 3 PTE) */
+ if (level == 3) {
+ idx = (start_ipa >> mmu->page_shift) & level_mask;
+ pte_val(entry) = (pa & ~(mmu->page_size - 1)) | flags | PTE_TYPE_PAGE;
+ WRITE_ONCE(table[idx], entry);
+ start_ipa += mmu->page_size;
+ pa += mmu->page_size;
+ }
+
+next_chunk:
+ continue;
+ }
+
+ s2mmu_tlbi_all();
+ return 0;
+}
+
+/*
+ * Recursive helper to unmap a range within a specific table.
+ * Returns true if the table at this level is now completely empty
+ * and should be freed by the caller.
+ */
+static bool s2mmu_unmap_level(struct s2_mmu *mmu, pte_t *table,
+ unsigned long current_ipa, int level,
+ unsigned long start_ipa, unsigned long end_ipa,
+ unsigned long mask)
+{
+ unsigned long level_size, entry_ipa, entry_end;
+ bool child_empty, table_empty = true;
+ pte_t entry, *next_table;
+ unsigned int level_shift;
+ unsigned long i;
+
+ /* Calculate shift and size for this level */
+ if (level == 3) {
+ level_shift = mmu->page_shift;
+ } else {
+ level_shift = mmu->page_shift + (3 - level) * mmu->level_shift;
+ }
+ level_size = 1UL << level_shift;
+
+ /* Iterate over all entries in this table */
+ for (i = 0; i < (1UL << mmu->level_shift); i++) {
+ entry = table[i];
+ entry_ipa = current_ipa + (i * level_size);
+ entry_end = entry_ipa + level_size;
+
+ /* Skip entries completely outside our target range */
+ if (entry_end <= start_ipa || entry_ipa >= end_ipa) {
+ if (pte_valid(entry))
+ table_empty = false;
+ continue;
+ }
+
+ /*
+ * If the entry is fully covered by the unmap range,
+ * we can clear it (leaf) or recurse and free (table).
+ */
+ if (entry_ipa >= start_ipa && entry_end <= end_ipa) {
+ if (pte_valid(entry)) {
+ if (pte_is_table(entry) && level < 3) {
+ /* Recurse to free children first */
+ next_table = (pte_t *)phys_to_virt(pte_val(entry) & mask);
+ s2mmu_free_tables(mmu, next_table, level + 1);
+ }
+ /* Invalidate the entry */
+ WRITE_ONCE(table[i], __pte(0));
+ }
+ continue;
+ }
+
+ /*
+ * Partial overlap: This must be a table (split required).
+ * If it's a Block, we can't split easily in this context
+ * without complex logic, so we generally skip or fail.
+ * Assuming standard breakdown: recurse into the table.
+ */
+ if (pte_valid(entry) && pte_is_table(entry) && level < 3) {
+ next_table = (pte_t *)phys_to_virt(pte_val(entry) & mask);
+ child_empty = s2mmu_unmap_level(mmu, next_table, entry_ipa, level + 1,
+ start_ipa, end_ipa, mask);
+
+ if (child_empty) {
+ free_pages(next_table);
+ WRITE_ONCE(table[i], __pte(0));
+ } else {
+ table_empty = false;
+ }
+ } else if (pte_valid(entry)) {
+ /*
+ * Overlap on a leaf/block entry that extends
+ * beyond the unmap range. We cannot simply clear it.
+ */
+ table_empty = false;
+ }
+ }
+
+ return table_empty;
+}
+
+void s2mmu_unmap(struct s2_mmu *mmu, unsigned long ipa, unsigned long size)
+{
+ unsigned long end_ipa = ipa + size;
+ unsigned long mask = s2mmu_get_addr_mask(mmu);
+
+ if (!mmu->pgd)
+ return;
+
+ /*
+ * Start recursion from the root level.
+ * We rarely free the PGD itself unless destroying the MMU,
+ * so we ignore the return value here.
+ */
+ s2mmu_unmap_level(mmu, (pte_t *)mmu->pgd, 0, mmu->root_level,
+ ipa, end_ipa, mask);
+
+ s2mmu_tlbi_all();
+}
+
+void s2mmu_print_fault_info(void)
+{
+ unsigned long esr = read_sysreg(esr_el2);
+ unsigned long far = read_sysreg(far_el2);
+ unsigned long hpfar = read_sysreg(hpfar_el2);
+ printf("Stage-2 Fault Info: ESR=0x%lx FAR=0x%lx HPFAR=0x%lx\n", esr, far, hpfar);
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 4/8] lib: arm64: Add foundational guest execution framework
2026-10-01 15:16 [PATCH v3 0/8] arm64: Add Stage-2 MMU and Nested Guest Framework Joey Gouly
` (2 preceding siblings ...)
2026-10-01 15:16 ` [PATCH v3 3/8] lib: arm64: Add stage2 page table management library Joey Gouly
@ 2026-10-01 15:16 ` Joey Gouly
2026-10-01 15:16 ` [PATCH v3 5/8] lib: arm64: Add support for guest exit exception handling Joey Gouly
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:16 UTC (permalink / raw)
To: kvmarm
Cc: Jing Zhang, Marc Zyngier, Oliver Upton, Mark Brown, Wei-Lin Chang,
andrew.jones, Joey Gouly
From: Jing Zhang <jingzhangos@google.com>
Introduce the infrastructure to manage and execute guest when running at
EL2.
The framework includes:
- 'struct guest': Encapsulates vCPU state (GPRs, EL1/EL2 sysregs) and
Stage-2 MMU context.
- guest_create() / guest_destroy(): Handle lifecycle management and
Stage-2 MMU setup.
- guest_create_identity(): helper to create a guest and setup some
mappings.
- guest_run(): entry point that saves host callee-saved
registers, caches the guest context pointer in TPIDR_EL2, and
performs the exception return (ERET) to the guest.
Signed-off-by: Jing Zhang <jingzhangos@google.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
---
arm/Makefile.arm64 | 2 +
lib/arm64/asm-offsets.c | 4 ++
lib/arm64/asm/guest.h | 53 +++++++++++++++++
lib/arm64/asm/sysreg.h | 6 ++
lib/arm64/guest.c | 127 ++++++++++++++++++++++++++++++++++++++++
lib/arm64/guest_arch.S | 51 ++++++++++++++++
6 files changed, 243 insertions(+)
create mode 100644 lib/arm64/asm/guest.h
create mode 100644 lib/arm64/guest.c
create mode 100644 lib/arm64/guest_arch.S
diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
index 5e50f5ba..9026fd71 100644
--- a/arm/Makefile.arm64
+++ b/arm/Makefile.arm64
@@ -41,6 +41,8 @@ cflatobjs += lib/arm64/processor.o
cflatobjs += lib/arm64/spinlock.o
cflatobjs += lib/arm64/gic-v3-its.o lib/arm64/gic-v3-its-cmd.o
cflatobjs += lib/arm64/stage2_mmu.o
+cflatobjs += lib/arm64/guest.o
+cflatobjs += lib/arm64/guest_arch.o
ifeq ($(CONFIG_EFI),y)
cflatobjs += lib/acpi.o
diff --git a/lib/arm64/asm-offsets.c b/lib/arm64/asm-offsets.c
index 80de023c..c55e4bb9 100644
--- a/lib/arm64/asm-offsets.c
+++ b/lib/arm64/asm-offsets.c
@@ -8,6 +8,7 @@
#include <libcflat.h>
#include <kbuild.h>
#include <asm/ptrace.h>
+#include <asm/guest.h>
int main(void)
{
@@ -30,5 +31,8 @@ int main(void)
DEFINE(S_FP, sizeof(struct pt_regs));
DEFINE(S_FRAME_SIZE, (sizeof(struct pt_regs) + 16));
+ OFFSET(GUEST_X_OFFSET, guest, x);
+ OFFSET(GUEST_HCR_OFFSET, guest, hcr_el2);
+
return 0;
}
diff --git a/lib/arm64/asm/guest.h b/lib/arm64/asm/guest.h
new file mode 100644
index 00000000..50fc3338
--- /dev/null
+++ b/lib/arm64/asm/guest.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2026, Google LLC.
+ * Author: Jing Zhang <jingzhangos@google.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#ifndef _ASMARM64_GUEST_H_
+#define _ASMARM64_GUEST_H_
+
+#include <libcflat.h>
+#include <asm/processor.h>
+#include <asm/stage2_mmu.h>
+
+#define HCR_GUEST_FLAGS (HCR_EL2_VM | HCR_EL2_FMO | HCR_EL2_IMO | \
+ HCR_EL2_AMO | HCR_EL2_RW | HCR_EL2_E2H)
+/* Guest stack size */
+#define GUEST_STACK_SIZE SZ_64K
+
+struct guest {
+ /* General Purpose Registers */
+ unsigned long x[31]; /* x0..x30 */
+
+ /* Execution State */
+ unsigned long elr_el2;
+ unsigned long spsr_el2;
+
+ /* Control Registers */
+ unsigned long hcr_el2;
+ unsigned long sctlr_el1;
+ unsigned long sp_el1;
+
+ /* Exit Information */
+ unsigned long esr_el2;
+ unsigned long far_el2;
+ unsigned long hpfar_el2;
+ unsigned long exit_code;
+
+ struct s2_mmu *s2mmu;
+ void *guest_stack;
+};
+
+/*
+ * Create a guest from a stage-2 context and entry IPA.
+ */
+struct guest *guest_create(struct s2_mmu *s2mmu, unsigned long entry_ipa);
+
+/* Helper for tests using identity-mapped host code and a stack. */
+struct guest *guest_create_identity(int vmid, void (*guest_func)(void),
+ enum s2_granule granule);
+void guest_destroy(struct guest *guest);
+void guest_run(struct guest *guest);
+
+#endif /* _ASMARM64_GUEST_H_ */
diff --git a/lib/arm64/asm/sysreg.h b/lib/arm64/asm/sysreg.h
index 72767cf0..b3b7e221 100644
--- a/lib/arm64/asm/sysreg.h
+++ b/lib/arm64/asm/sysreg.h
@@ -147,6 +147,10 @@ asm(
#define VTTBR_EL2_VMID_SHIFT 48
#define VTTBR_EL2_VMID_MASK GENMASK_ULL(63, 48)
+#define HCR_EL2_VM _BITULL(0)
+#define HCR_EL2_FMO _BITULL(3)
+#define HCR_EL2_IMO _BITULL(4)
+#define HCR_EL2_AMO _BITULL(5)
#define HCR_EL2_TGE _BITULL(27)
#define HCR_EL2_RW _BITULL(31)
#define HCR_EL2_E2H _BITULL(34)
@@ -161,6 +165,8 @@ asm(
#define SYS_HFGWTR2_EL2 sys_reg(3, 4, 3, 1, 3)
#define SYS_HFGITR2_EL2 sys_reg(3, 4, 3, 1, 7)
+#define SYS_SCTLR_EL12 sys_reg(3, 5, 1, 0, 0)
+
#define INIT_SCTLR_EL1_MMU_OFF \
(SCTLR_EL1_ITD | SCTLR_EL1_SED | SCTLR_EL1_EOS | \
SCTLR_EL1_TSCXT | SCTLR_EL1_EIS | SCTLR_EL1_SPAN | \
diff --git a/lib/arm64/guest.c b/lib/arm64/guest.c
new file mode 100644
index 00000000..b348e628
--- /dev/null
+++ b/lib/arm64/guest.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2026, Google LLC.
+ * Author: Jing Zhang <jingzhangos@google.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#include <libcflat.h>
+#include <asm/guest.h>
+#include <asm/io.h>
+#include <asm/sysreg.h>
+#include <asm/barrier.h>
+#include <alloc_page.h>
+#include <alloc.h>
+
+extern void __guest_switch_regs(struct guest *guest);
+
+static void guest_restore_state(const struct guest *guest)
+{
+ write_sysreg(guest->elr_el2, elr_el2);
+ write_sysreg(guest->spsr_el2, spsr_el2);
+ write_sysreg_s(guest->sctlr_el1, SYS_SCTLR_EL12);
+ write_sysreg(guest->sp_el1, sp_el1);
+}
+
+static void guest_arch_enter(struct guest *guest)
+{
+ s2mmu_load(guest->s2mmu);
+ guest_restore_state(guest);
+ __guest_switch_regs(guest);
+}
+
+void guest_run(struct guest *guest)
+{
+ guest_arch_enter(guest);
+}
+
+struct guest *guest_create(struct s2_mmu *s2_ctx, unsigned long entry_ipa)
+{
+ struct guest *guest;
+
+ if (!s2_ctx)
+ return NULL;
+
+ guest = calloc(1, sizeof(*guest));
+ if (!guest)
+ return NULL;
+
+ guest->elr_el2 = entry_ipa;
+ guest->spsr_el2 = PSR_MODE_EL1h |
+ PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT;
+ guest->hcr_el2 = HCR_GUEST_FLAGS;
+
+ guest->sctlr_el1 = read_sysreg_s(SYS_SCTLR_EL12);
+ /* Disable guest stage 1 translation */
+ guest->sctlr_el1 &= ~(SCTLR_EL1_M | SCTLR_EL1_C);
+ guest->sctlr_el1 |= SCTLR_EL1_I;
+
+ guest->s2mmu = s2_ctx;
+
+ return guest;
+}
+
+struct guest *guest_create_identity(int vmid, void (*guest_func)(void),
+ enum s2_granule granule)
+{
+ unsigned long guest_pa, code_base, stack_pa;
+ unsigned long *stack_page;
+ struct guest *guest;
+ struct s2_mmu *ctx;
+ int ret;
+
+ ctx = s2mmu_init(vmid, granule, true);
+ if (!ctx)
+ return NULL;
+
+ /*
+ * Map the Host's code segment Identity Mapped (IPA=PA).
+ * To be safe, we map a large chunk (e.g., 2MB) around the function
+ * to capture any helper functions the compiler might generate calls to.
+ */
+ guest_pa = virt_to_phys((void *)guest_func);
+ code_base = guest_pa & ~(SZ_2M - 1);
+ ret = s2mmu_map(ctx, code_base, code_base, SZ_2M, S2_MAP_RW);
+ if (ret)
+ goto destroy_s2;
+
+ /*
+ * Map Stack
+ * Allocate 16 pages (64K) in Host, get its PA, and map it for Guest.
+ */
+ stack_page = alloc_pages(get_order(GUEST_STACK_SIZE >> PAGE_SHIFT));
+ if (!stack_page)
+ goto destroy_s2;
+ stack_pa = virt_to_phys(stack_page);
+ /* Identity Map it (IPA = PA) */
+ ret = s2mmu_map(ctx, stack_pa, stack_pa, GUEST_STACK_SIZE, S2_MAP_RW);
+ if (ret)
+ goto free_stack;
+
+ /* Create Guest */
+ /* Entry point is the PA of the function (Identity Mapped) */
+ guest = guest_create(ctx, guest_pa);
+ if (!guest)
+ goto free_stack;
+
+ /*
+ * Setup Guest Stack Pointer
+ * Must match where we mapped the stack + Offset
+ */
+ guest->sp_el1 = stack_pa + GUEST_STACK_SIZE;
+ guest->guest_stack = stack_page;
+
+ return guest;
+
+free_stack:
+ free_pages(stack_page);
+destroy_s2:
+ s2mmu_destroy(ctx);
+ return NULL;
+}
+
+void guest_destroy(struct guest *guest)
+{
+ s2mmu_destroy(guest->s2mmu);
+ free_pages(guest->guest_stack);
+ free(guest);
+}
diff --git a/lib/arm64/guest_arch.S b/lib/arm64/guest_arch.S
new file mode 100644
index 00000000..16b95835
--- /dev/null
+++ b/lib/arm64/guest_arch.S
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2026, Google LLC.
+ * Author: Jing Zhang <jingzhangos@google.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#define __ASSEMBLY__
+#include <asm/asm-offsets.h>
+#include <asm/sysreg.h>
+
+#define GUEST_XREG_OFFSET(x) (GUEST_X_OFFSET + 8 * (x))
+
+.global __guest_switch_regs
+__guest_switch_regs:
+ /* x0 = struct guest pointer */
+
+ /* Save Host Callee-Saved Regs */
+ stp x29, x30, [sp, #-16]!
+ stp x27, x28, [sp, #-16]!
+ stp x25, x26, [sp, #-16]!
+ stp x23, x24, [sp, #-16]!
+ stp x21, x22, [sp, #-16]!
+ stp x19, x20, [sp, #-16]!
+
+ /*
+ * Commit the guest EL2 state only after the world-switch frame is valid.
+ */
+ ldr x1, [x0, #GUEST_HCR_OFFSET]
+ msr tpidr_el2, x0
+ msr hcr_el2, x1
+ isb
+
+ /* Load Guest GPRs */
+ ldp x1, x2, [x0, #GUEST_XREG_OFFSET(1)]
+ ldp x3, x4, [x0, #GUEST_XREG_OFFSET(3)]
+ ldp x5, x6, [x0, #GUEST_XREG_OFFSET(5)]
+ ldp x7, x8, [x0, #GUEST_XREG_OFFSET(7)]
+ ldp x9, x10, [x0, #GUEST_XREG_OFFSET(9)]
+ ldp x11, x12, [x0, #GUEST_XREG_OFFSET(11)]
+ ldp x13, x14, [x0, #GUEST_XREG_OFFSET(13)]
+ ldp x15, x16, [x0, #GUEST_XREG_OFFSET(15)]
+ ldp x17, x18, [x0, #GUEST_XREG_OFFSET(17)]
+ ldp x19, x20, [x0, #GUEST_XREG_OFFSET(19)]
+ ldp x21, x22, [x0, #GUEST_XREG_OFFSET(21)]
+ ldp x23, x24, [x0, #GUEST_XREG_OFFSET(23)]
+ ldp x25, x26, [x0, #GUEST_XREG_OFFSET(25)]
+ ldp x27, x28, [x0, #GUEST_XREG_OFFSET(27)]
+ ldp x29, x30, [x0, #GUEST_XREG_OFFSET(29)]
+ ldr x0, [x0, #GUEST_XREG_OFFSET(0)]
+
+ eret
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 5/8] lib: arm64: Add support for guest exit exception handling
2026-10-01 15:16 [PATCH v3 0/8] arm64: Add Stage-2 MMU and Nested Guest Framework Joey Gouly
` (3 preceding siblings ...)
2026-10-01 15:16 ` [PATCH v3 4/8] lib: arm64: Add foundational guest execution framework Joey Gouly
@ 2026-10-01 15:16 ` Joey Gouly
2026-10-01 15:16 ` [PATCH v3 6/8] lib: arm64: Add guest-internal " Joey Gouly
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:16 UTC (permalink / raw)
To: kvmarm
Cc: Jing Zhang, Marc Zyngier, Oliver Upton, Mark Brown, Wei-Lin Chang,
andrew.jones, Joey Gouly
From: Jing Zhang <jingzhangos@google.com>
Implement the logic to intercept and handle exceptions from managed
guests at EL2. This completes the context switch loop by providing a
mechanism to save guest state, route exceptions to C handlers, and
either resume the guest or return to the host.
Key additions:
- 'hyp_el2_vectors': An EL2 vector table installed during guest
execution to trap guest exits (Synchronous, IRQ, etc.).
- 'guest_c_exception_handler': A C-level dispatcher that updates the
guest context with exception info (ESR, FAR, HPFAR) and invokes
registered per-vector handlers.
- Integrated save/restore logic: guest_run() now handles the full
cycle of saving host state, loading guest state, trapping exits,
and optionally resuming or returning.
- Handler API: Added guest_install_handler() to allow tests to register
custom logic for specific exception types.
Signed-off-by: Jing Zhang <jingzhangos@google.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
---
lib/arm64/asm-offsets.c | 1 +
lib/arm64/asm/guest.h | 19 +++++++
lib/arm64/guest.c | 94 +++++++++++++++++++++++++++++++++--
lib/arm64/guest_arch.S | 106 ++++++++++++++++++++++++++++++++++++++--
4 files changed, 212 insertions(+), 8 deletions(-)
diff --git a/lib/arm64/asm-offsets.c b/lib/arm64/asm-offsets.c
index c55e4bb9..1fc595ff 100644
--- a/lib/arm64/asm-offsets.c
+++ b/lib/arm64/asm-offsets.c
@@ -33,6 +33,7 @@ int main(void)
OFFSET(GUEST_X_OFFSET, guest, x);
OFFSET(GUEST_HCR_OFFSET, guest, hcr_el2);
+ OFFSET(GUEST_EXIT_CODE_OFFSET, guest, exit_code);
return 0;
}
diff --git a/lib/arm64/asm/guest.h b/lib/arm64/asm/guest.h
index 50fc3338..9ad344e1 100644
--- a/lib/arm64/asm/guest.h
+++ b/lib/arm64/asm/guest.h
@@ -16,6 +16,19 @@
/* Guest stack size */
#define GUEST_STACK_SIZE SZ_64K
+/*
+ * Result from Handler:
+ * RESUME: Keep guest running (ERET immediately)
+ * EXIT: Return to Host C caller
+ */
+enum guest_handler_result {
+ GUEST_ACTION_RESUME,
+ GUEST_ACTION_EXIT
+};
+
+struct guest;
+typedef enum guest_handler_result (*guest_handler_t)(struct guest *guest);
+
struct guest {
/* General Purpose Registers */
unsigned long x[31]; /* x0..x30 */
@@ -35,6 +48,9 @@ struct guest {
unsigned long hpfar_el2;
unsigned long exit_code;
+ /* Exception Handlers in EL2 */
+ guest_handler_t handlers[VECTOR_MAX];
+
struct s2_mmu *s2mmu;
void *guest_stack;
};
@@ -50,4 +66,7 @@ struct guest *guest_create_identity(int vmid, void (*guest_func)(void),
void guest_destroy(struct guest *guest);
void guest_run(struct guest *guest);
+unsigned long guest_c_exception_handler(struct guest *guest);
+void guest_install_handler(struct guest *guest, enum vector v, guest_handler_t handler);
+
#endif /* _ASMARM64_GUEST_H_ */
diff --git a/lib/arm64/guest.c b/lib/arm64/guest.c
index b348e628..afb91559 100644
--- a/lib/arm64/guest.c
+++ b/lib/arm64/guest.c
@@ -12,7 +12,47 @@
#include <alloc_page.h>
#include <alloc.h>
-extern void __guest_switch_regs(struct guest *guest);
+/* Host state owned by a guest_run() invocation, not by the guest. */
+struct guest_host_state {
+ unsigned long hcr_el2;
+ unsigned long vtcr_el2;
+ unsigned long vttbr_el2;
+ unsigned long vbar_el2;
+ unsigned long tpidr_el2;
+ unsigned long daif;
+};
+
+extern void __guest_switch_regs(struct guest *guest,
+ struct guest_host_state *host_state);
+
+static void guest_save_host_state(struct guest_host_state *state)
+{
+ state->hcr_el2 = read_sysreg(hcr_el2);
+ state->vtcr_el2 = read_sysreg(vtcr_el2);
+ state->vttbr_el2 = read_sysreg(vttbr_el2);
+ state->vbar_el2 = read_sysreg(vbar_el2);
+ state->tpidr_el2 = read_sysreg(tpidr_el2);
+ state->daif = read_sysreg(daif);
+}
+
+static void guest_restore_host_state(const struct guest_host_state *state)
+{
+ write_sysreg(state->vtcr_el2, vtcr_el2);
+ write_sysreg(state->vttbr_el2, vttbr_el2);
+ write_sysreg(state->vbar_el2, vbar_el2);
+ write_sysreg(state->hcr_el2, hcr_el2);
+ write_sysreg(state->tpidr_el2, tpidr_el2);
+ isb();
+ write_sysreg(state->daif, daif);
+}
+
+static void guest_save_state(struct guest *guest)
+{
+ guest->elr_el2 = read_sysreg(elr_el2);
+ guest->spsr_el2 = read_sysreg(spsr_el2);
+ guest->sctlr_el1 = read_sysreg_s(SYS_SCTLR_EL12);
+ guest->sp_el1 = read_sysreg(sp_el1);
+}
static void guest_restore_state(const struct guest *guest)
{
@@ -22,18 +62,58 @@ static void guest_restore_state(const struct guest *guest)
write_sysreg(guest->sp_el1, sp_el1);
}
-static void guest_arch_enter(struct guest *guest)
+static void guest_arch_enter(struct guest *guest,
+ struct guest_host_state *host_state)
{
s2mmu_load(guest->s2mmu);
guest_restore_state(guest);
- __guest_switch_regs(guest);
+ __guest_switch_regs(guest, host_state);
+}
+
+void guest_arch_exit(const struct guest_host_state *state)
+{
+ guest_restore_host_state(state);
}
void guest_run(struct guest *guest)
{
- guest_arch_enter(guest);
+ struct guest_host_state host_state;
+
+ guest_save_host_state(&host_state);
+ guest_arch_enter(guest, &host_state);
}
+/*
+ * C-Entry for Exception Handling
+ * Returns 0 to Resume Guest, 1 to Exit to Host Caller
+ */
+unsigned long guest_c_exception_handler(struct guest *guest)
+{
+ enum vector vector;
+
+ /* Save guest system-register state before invoking the exit handler. */
+ guest_save_state(guest);
+ guest->esr_el2 = read_sysreg(esr_el2);
+ guest->far_el2 = read_sysreg(far_el2);
+ guest->hpfar_el2 = read_sysreg(hpfar_el2);
+
+ if (guest->exit_code >= VECTOR_MAX)
+ return 1;
+ vector = (enum vector)guest->exit_code;
+
+ /* Invoke Handler if registered */
+ if (guest->handlers[vector]) {
+ if (guest->handlers[vector](guest) == GUEST_ACTION_RESUME) {
+ guest_restore_state(guest);
+ return 0; /* ASM stub will restore and ERET */
+ }
+ }
+
+ /* Default: Exit to caller */
+ return 1;
+}
+
+
struct guest *guest_create(struct s2_mmu *s2_ctx, unsigned long entry_ipa)
{
struct guest *guest;
@@ -125,3 +205,9 @@ void guest_destroy(struct guest *guest)
free_pages(guest->guest_stack);
free(guest);
}
+
+void guest_install_handler(struct guest *guest, enum vector v, guest_handler_t handler)
+{
+ if (v < VECTOR_MAX)
+ guest->handlers[v] = handler;
+}
diff --git a/lib/arm64/guest_arch.S b/lib/arm64/guest_arch.S
index 16b95835..33d0dc53 100644
--- a/lib/arm64/guest_arch.S
+++ b/lib/arm64/guest_arch.S
@@ -12,7 +12,10 @@
.global __guest_switch_regs
__guest_switch_regs:
- /* x0 = struct guest pointer */
+ /*
+ * x0 = struct guest pointer
+ * x1 = host system-register state
+ */
/* Save Host Callee-Saved Regs */
stp x29, x30, [sp, #-16]!
@@ -21,13 +24,18 @@ __guest_switch_regs:
stp x23, x24, [sp, #-16]!
stp x21, x22, [sp, #-16]!
stp x19, x20, [sp, #-16]!
+ stp x1, xzr, [sp, #-16]!
/*
- * Commit the guest EL2 state only after the world-switch frame is valid.
+ * Commit the guest EL2 state only after the exit frame is valid. No
+ * exception may reach the guest vectors without this frame in place.
*/
- ldr x1, [x0, #GUEST_HCR_OFFSET]
+ ldr x2, [x0, #GUEST_HCR_OFFSET]
+ adrp x3, hyp_el2_vectors
+ add x3, x3, :lo12:hyp_el2_vectors
msr tpidr_el2, x0
- msr hcr_el2, x1
+ msr vbar_el2, x3
+ msr hcr_el2, x2
isb
/* Load Guest GPRs */
@@ -49,3 +57,93 @@ __guest_switch_regs:
ldr x0, [x0, #GUEST_XREG_OFFSET(0)]
eret
+
+/* EL2 Hypervisor Vector Table */
+.macro hyp_el2_vector, vector_num
+ .align 7
+ stp x0, x1, [sp, #-16]!
+ mrs x0, tpidr_el2
+ mov x1, \vector_num
+ str x1, [x0, #GUEST_EXIT_CODE_OFFSET]
+ b guest_common_exit
+.endm
+
+ .align 11
+.global hyp_el2_vectors
+hyp_el2_vectors:
+ .skip 0x200
+
+ hyp_el2_vector #4 /* ELxH_SYNC */
+ hyp_el2_vector #5 /* ELxH_IRQ */
+ hyp_el2_vector #6 /* ELxH_FIQ */
+ hyp_el2_vector #7 /* ELxH_ERROR */
+
+ hyp_el2_vector #8 /* ELx_LOW_SYNC_64 */
+ hyp_el2_vector #9 /* ELx_LOW_IRQ_64 */
+ hyp_el2_vector #10 /* ELx_LOW_FIQ_64 */
+ hyp_el2_vector #11 /* ELx_LOW_ERROR_64 */
+
+ .align 11
+
+guest_common_exit:
+ stp x2, x3, [x0, #GUEST_XREG_OFFSET(2)]
+ stp x4, x5, [x0, #GUEST_XREG_OFFSET(4)]
+ stp x6, x7, [x0, #GUEST_XREG_OFFSET(6)]
+ stp x8, x9, [x0, #GUEST_XREG_OFFSET(8)]
+ stp x10, x11, [x0, #GUEST_XREG_OFFSET(10)]
+ stp x12, x13, [x0, #GUEST_XREG_OFFSET(12)]
+ stp x14, x15, [x0, #GUEST_XREG_OFFSET(14)]
+ stp x16, x17, [x0, #GUEST_XREG_OFFSET(16)]
+ stp x18, x19, [x0, #GUEST_XREG_OFFSET(18)]
+ stp x20, x21, [x0, #GUEST_XREG_OFFSET(20)]
+ stp x22, x23, [x0, #GUEST_XREG_OFFSET(22)]
+ stp x24, x25, [x0, #GUEST_XREG_OFFSET(24)]
+ stp x26, x27, [x0, #GUEST_XREG_OFFSET(26)]
+ stp x28, x29, [x0, #GUEST_XREG_OFFSET(28)]
+ str x30, [x0, #GUEST_XREG_OFFSET(30)]
+
+ ldp x2, x3, [sp], #16
+ stp x2, x3, [x0, #GUEST_XREG_OFFSET(0)]
+
+ bl guest_c_exception_handler
+ cbz x0, guest_resume_guest
+
+ /* EXIT */
+ /*
+ * Restore the host system registers while this world-switch frame and
+ * the guest exit vector are still valid.
+ */
+ ldr x0, [sp]
+ bl guest_arch_exit
+ add sp, sp, #16
+
+ /* Restore Host Callee-Saved Regs */
+ ldp x19, x20, [sp], #16
+ ldp x21, x22, [sp], #16
+ ldp x23, x24, [sp], #16
+ ldp x25, x26, [sp], #16
+ ldp x27, x28, [sp], #16
+ ldp x29, x30, [sp], #16
+ ret
+
+ /* RESUME */
+guest_resume_guest:
+ mrs x0, tpidr_el2
+
+ ldp x1, x2, [x0, #GUEST_XREG_OFFSET(1)]
+ ldp x3, x4, [x0, #GUEST_XREG_OFFSET(3)]
+ ldp x5, x6, [x0, #GUEST_XREG_OFFSET(5)]
+ ldp x7, x8, [x0, #GUEST_XREG_OFFSET(7)]
+ ldp x9, x10, [x0, #GUEST_XREG_OFFSET(9)]
+ ldp x11, x12, [x0, #GUEST_XREG_OFFSET(11)]
+ ldp x13, x14, [x0, #GUEST_XREG_OFFSET(13)]
+ ldp x15, x16, [x0, #GUEST_XREG_OFFSET(15)]
+ ldp x17, x18, [x0, #GUEST_XREG_OFFSET(17)]
+ ldp x19, x20, [x0, #GUEST_XREG_OFFSET(19)]
+ ldp x21, x22, [x0, #GUEST_XREG_OFFSET(21)]
+ ldp x23, x24, [x0, #GUEST_XREG_OFFSET(23)]
+ ldp x25, x26, [x0, #GUEST_XREG_OFFSET(25)]
+ ldp x27, x28, [x0, #GUEST_XREG_OFFSET(27)]
+ ldp x29, x30, [x0, #GUEST_XREG_OFFSET(29)]
+ ldr x0, [x0, #GUEST_XREG_OFFSET(0)]
+ eret
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 6/8] lib: arm64: Add guest-internal exception handling
2026-10-01 15:16 [PATCH v3 0/8] arm64: Add Stage-2 MMU and Nested Guest Framework Joey Gouly
` (4 preceding siblings ...)
2026-10-01 15:16 ` [PATCH v3 5/8] lib: arm64: Add support for guest exit exception handling Joey Gouly
@ 2026-10-01 15:16 ` Joey Gouly
2026-10-01 15:16 ` [PATCH v3 7/8] arm64: Add Stage-2 MMU demand paging test Joey Gouly
2026-10-01 15:16 ` [PATCH v3 8/8] arm64: Add basic guest execution tests Joey Gouly
7 siblings, 0 replies; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:16 UTC (permalink / raw)
To: kvmarm
Cc: Jing Zhang, Marc Zyngier, Oliver Upton, Mark Brown, Wei-Lin Chang,
andrew.jones, Joey Gouly
From: Jing Zhang <jingzhangos@google.com>
Implement the infrastructure for managed guests to handle their own
exceptions at EL1. This allows guests to manage internal traps
independently of the EL2 host.
Changes include:
- 'guest_el1_vectors': A dedicated EL1 vector table for guests,
supporting synchronous exceptions, IRQs, FIQs, and SErrors.
- 'guest_context': A per-guest metadata structure tracked via
TPIDR_EL1, containing a dispatch table for guest-internal handlers.
- 'guest_el1_c_handler': C-level dispatcher that executes registered
guest handlers or triggers an HVC trap back to the host for unhandled
exceptions.
- Integrated setup: guest_create() now allocates and maps the guest
context, while the context switch logic ensures VBAR_EL1 and
TPIDR_EL1 are correctly saved and restored.
Signed-off-by: Jing Zhang <jingzhangos@google.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
---
lib/arm64/asm-offsets.c | 5 +++
lib/arm64/asm/guest.h | 27 +++++++++++++++
lib/arm64/asm/sysreg.h | 1 +
lib/arm64/guest.c | 51 ++++++++++++++++++++++++++++
lib/arm64/guest_arch.S | 74 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 158 insertions(+)
diff --git a/lib/arm64/asm-offsets.c b/lib/arm64/asm-offsets.c
index 1fc595ff..5081f1df 100644
--- a/lib/arm64/asm-offsets.c
+++ b/lib/arm64/asm-offsets.c
@@ -35,5 +35,10 @@ int main(void)
OFFSET(GUEST_HCR_OFFSET, guest, hcr_el2);
OFFSET(GUEST_EXIT_CODE_OFFSET, guest, exit_code);
+ OFFSET(GUEST_EL1_X_OFFSET, guest_el1_regs, regs);
+ OFFSET(GUEST_EL1_ELR_OFFSET, guest_el1_regs, elr);
+ OFFSET(GUEST_EL1_SPSR_OFFSET, guest_el1_regs, spsr);
+ DEFINE(GUEST_EL1_REGS_SIZE, sizeof(struct guest_el1_regs));
+
return 0;
}
diff --git a/lib/arm64/asm/guest.h b/lib/arm64/asm/guest.h
index 9ad344e1..d62fdc65 100644
--- a/lib/arm64/asm/guest.h
+++ b/lib/arm64/asm/guest.h
@@ -29,6 +29,27 @@ enum guest_handler_result {
struct guest;
typedef enum guest_handler_result (*guest_handler_t)(struct guest *guest);
+/*
+ * Guest EL1 Exception Frame (pushed to guest stack by asm stub)
+ * We use a simplified frame: x0-x30, elr, spsr. size = 34*8
+ */
+struct guest_el1_regs {
+ unsigned long regs[31];
+ unsigned long elr;
+ unsigned long spsr;
+ unsigned long reserved;
+};
+
+typedef void (*guest_el1_handler_t)(struct guest_el1_regs *regs, unsigned int esr);
+
+/*
+ * Guest Context Structure
+ * This will be pointed to by TPIDR_EL1 while the guest is running.
+ */
+struct guest_context {
+ guest_el1_handler_t handlers[VECTOR_MAX];
+};
+
struct guest {
/* General Purpose Registers */
unsigned long x[31]; /* x0..x30 */
@@ -41,15 +62,18 @@ struct guest {
unsigned long hcr_el2;
unsigned long sctlr_el1;
unsigned long sp_el1;
+ unsigned long vbar_el1;
/* Exit Information */
unsigned long esr_el2;
unsigned long far_el2;
unsigned long hpfar_el2;
unsigned long exit_code;
+ unsigned long tpidr_el1;
/* Exception Handlers in EL2 */
guest_handler_t handlers[VECTOR_MAX];
+ struct guest_context *guest_context;
struct s2_mmu *s2mmu;
void *guest_stack;
@@ -69,4 +93,7 @@ void guest_run(struct guest *guest);
unsigned long guest_c_exception_handler(struct guest *guest);
void guest_install_handler(struct guest *guest, enum vector v, guest_handler_t handler);
+void guest_el1_c_handler(struct guest_el1_regs *regs, unsigned int vector);
+void guest_install_el1_handler(struct guest *guest, enum vector v, guest_el1_handler_t handler);
+
#endif /* _ASMARM64_GUEST_H_ */
diff --git a/lib/arm64/asm/sysreg.h b/lib/arm64/asm/sysreg.h
index b3b7e221..8903b630 100644
--- a/lib/arm64/asm/sysreg.h
+++ b/lib/arm64/asm/sysreg.h
@@ -166,6 +166,7 @@ asm(
#define SYS_HFGITR2_EL2 sys_reg(3, 4, 3, 1, 7)
#define SYS_SCTLR_EL12 sys_reg(3, 5, 1, 0, 0)
+#define SYS_VBAR_EL12 sys_reg(3, 5, 12, 0, 0)
#define INIT_SCTLR_EL1_MMU_OFF \
(SCTLR_EL1_ITD | SCTLR_EL1_SED | SCTLR_EL1_EOS | \
diff --git a/lib/arm64/guest.c b/lib/arm64/guest.c
index afb91559..821f281d 100644
--- a/lib/arm64/guest.c
+++ b/lib/arm64/guest.c
@@ -24,6 +24,7 @@ struct guest_host_state {
extern void __guest_switch_regs(struct guest *guest,
struct guest_host_state *host_state);
+void guest_arch_exit(const struct guest_host_state *state);
static void guest_save_host_state(struct guest_host_state *state)
{
@@ -52,6 +53,8 @@ static void guest_save_state(struct guest *guest)
guest->spsr_el2 = read_sysreg(spsr_el2);
guest->sctlr_el1 = read_sysreg_s(SYS_SCTLR_EL12);
guest->sp_el1 = read_sysreg(sp_el1);
+ guest->vbar_el1 = read_sysreg_s(SYS_VBAR_EL12);
+ guest->tpidr_el1 = read_sysreg(tpidr_el1);
}
static void guest_restore_state(const struct guest *guest)
@@ -60,6 +63,8 @@ static void guest_restore_state(const struct guest *guest)
write_sysreg(guest->spsr_el2, spsr_el2);
write_sysreg_s(guest->sctlr_el1, SYS_SCTLR_EL12);
write_sysreg(guest->sp_el1, sp_el1);
+ write_sysreg_s(guest->vbar_el1, SYS_VBAR_EL12);
+ write_sysreg(guest->tpidr_el1, tpidr_el1);
}
static void guest_arch_enter(struct guest *guest,
@@ -113,10 +118,32 @@ unsigned long guest_c_exception_handler(struct guest *guest)
return 1;
}
+void guest_install_el1_handler(struct guest *guest, enum vector v, guest_el1_handler_t handler)
+{
+ if (guest && guest->guest_context && v < VECTOR_MAX)
+ guest->guest_context->handlers[v] = handler;
+}
+
+void guest_el1_c_handler(struct guest_el1_regs *regs, unsigned int vector)
+{
+ struct guest_context *ctx = (struct guest_context *)read_sysreg(tpidr_el1);
+ unsigned int esr = read_sysreg(esr_el1);
+
+ if (ctx && vector < VECTOR_MAX && ctx->handlers[vector]) {
+ ctx->handlers[vector](regs, esr);
+ } else {
+ asm volatile("hvc #0xFFFF");
+ }
+}
+
+extern void guest_el1_vectors(void);
struct guest *guest_create(struct s2_mmu *s2_ctx, unsigned long entry_ipa)
{
struct guest *guest;
+ struct guest_context *guest_ctx;
+ unsigned long guest_ctx_pa;
+ int ret;
if (!s2_ctx)
return NULL;
@@ -125,6 +152,21 @@ struct guest *guest_create(struct s2_mmu *s2_ctx, unsigned long entry_ipa)
if (!guest)
return NULL;
+ /* Allocate the internal context table */
+ guest_ctx = (void *)alloc_page();
+ if (!guest_ctx)
+ goto free_guest;
+ memset(guest_ctx, 0, PAGE_SIZE);
+ guest->guest_context = guest_ctx;
+
+ guest_ctx_pa = virt_to_phys(guest_ctx);
+ ret = s2mmu_map(s2_ctx, guest_ctx_pa, guest_ctx_pa, PAGE_SIZE,
+ S2_MAP_RW);
+ if (ret)
+ goto free_guest_ctx;
+
+ guest->tpidr_el1 = guest_ctx_pa;
+
guest->elr_el2 = entry_ipa;
guest->spsr_el2 = PSR_MODE_EL1h |
PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT;
@@ -135,9 +177,16 @@ struct guest *guest_create(struct s2_mmu *s2_ctx, unsigned long entry_ipa)
guest->sctlr_el1 &= ~(SCTLR_EL1_M | SCTLR_EL1_C);
guest->sctlr_el1 |= SCTLR_EL1_I;
+ guest->vbar_el1 = virt_to_phys((void *)guest_el1_vectors);
guest->s2mmu = s2_ctx;
return guest;
+
+free_guest_ctx:
+ free_page(guest_ctx);
+free_guest:
+ free(guest);
+ return NULL;
}
struct guest *guest_create_identity(int vmid, void (*guest_func)(void),
@@ -203,6 +252,8 @@ void guest_destroy(struct guest *guest)
{
s2mmu_destroy(guest->s2mmu);
free_pages(guest->guest_stack);
+ if (guest->guest_context)
+ free_page(guest->guest_context);
free(guest);
}
diff --git a/lib/arm64/guest_arch.S b/lib/arm64/guest_arch.S
index 33d0dc53..1ccba114 100644
--- a/lib/arm64/guest_arch.S
+++ b/lib/arm64/guest_arch.S
@@ -9,6 +9,7 @@
#include <asm/sysreg.h>
#define GUEST_XREG_OFFSET(x) (GUEST_X_OFFSET + 8 * (x))
+#define GUEST_EL1_XREG_OFFSET(x) (GUEST_EL1_X_OFFSET + 8 * (x))
.global __guest_switch_regs
__guest_switch_regs:
@@ -147,3 +148,76 @@ guest_resume_guest:
ldp x29, x30, [x0, #GUEST_XREG_OFFSET(29)]
ldr x0, [x0, #GUEST_XREG_OFFSET(0)]
eret
+
+/* EL1 Vector Table */
+.macro guest_el1_vector, vector_num
+ .align 7
+ sub sp, sp, #GUEST_EL1_REGS_SIZE
+ /* Save x29 and x30 before using x29 for the vector number. */
+ stp x29, x30, [sp, #GUEST_EL1_XREG_OFFSET(29)]
+ mov x29, \vector_num
+ b guest_el1_common
+.endm
+
+.align 11
+.global guest_el1_vectors
+guest_el1_vectors:
+ .skip 0x200
+
+ guest_el1_vector #4 /* Sync */
+ guest_el1_vector #5 /* IRQ */
+ guest_el1_vector #6 /* FIQ */
+ guest_el1_vector #7 /* SError */
+
+ .align 11
+
+guest_el1_common:
+ stp x0, x1, [sp, #GUEST_EL1_XREG_OFFSET(0)]
+ stp x2, x3, [sp, #GUEST_EL1_XREG_OFFSET(2)]
+ stp x4, x5, [sp, #GUEST_EL1_XREG_OFFSET(4)]
+ stp x6, x7, [sp, #GUEST_EL1_XREG_OFFSET(6)]
+ stp x8, x9, [sp, #GUEST_EL1_XREG_OFFSET(8)]
+ stp x10, x11, [sp, #GUEST_EL1_XREG_OFFSET(10)]
+ stp x12, x13, [sp, #GUEST_EL1_XREG_OFFSET(12)]
+ stp x14, x15, [sp, #GUEST_EL1_XREG_OFFSET(14)]
+ stp x16, x17, [sp, #GUEST_EL1_XREG_OFFSET(16)]
+ stp x18, x19, [sp, #GUEST_EL1_XREG_OFFSET(18)]
+ stp x20, x21, [sp, #GUEST_EL1_XREG_OFFSET(20)]
+ stp x22, x23, [sp, #GUEST_EL1_XREG_OFFSET(22)]
+ stp x24, x25, [sp, #GUEST_EL1_XREG_OFFSET(24)]
+ stp x26, x27, [sp, #GUEST_EL1_XREG_OFFSET(26)]
+ str x28, [sp, #GUEST_EL1_XREG_OFFSET(28)]
+
+ mrs x0, elr_el1
+ str x0, [sp, #GUEST_EL1_ELR_OFFSET]
+ mrs x0, spsr_el1
+ str x0, [sp, #GUEST_EL1_SPSR_OFFSET]
+
+ mov x0, sp
+ mov x1, x29
+ bl guest_el1_c_handler
+
+ ldr x0, [sp, #GUEST_EL1_ELR_OFFSET]
+ msr elr_el1, x0
+ ldr x0, [sp, #GUEST_EL1_SPSR_OFFSET]
+ msr spsr_el1, x0
+
+ ldp x0, x1, [sp, #GUEST_EL1_XREG_OFFSET(0)]
+ ldp x2, x3, [sp, #GUEST_EL1_XREG_OFFSET(2)]
+ ldp x4, x5, [sp, #GUEST_EL1_XREG_OFFSET(4)]
+ ldp x6, x7, [sp, #GUEST_EL1_XREG_OFFSET(6)]
+ ldp x8, x9, [sp, #GUEST_EL1_XREG_OFFSET(8)]
+ ldp x10, x11, [sp, #GUEST_EL1_XREG_OFFSET(10)]
+ ldp x12, x13, [sp, #GUEST_EL1_XREG_OFFSET(12)]
+ ldp x14, x15, [sp, #GUEST_EL1_XREG_OFFSET(14)]
+ ldp x16, x17, [sp, #GUEST_EL1_XREG_OFFSET(16)]
+ ldp x18, x19, [sp, #GUEST_EL1_XREG_OFFSET(18)]
+ ldp x20, x21, [sp, #GUEST_EL1_XREG_OFFSET(20)]
+ ldp x22, x23, [sp, #GUEST_EL1_XREG_OFFSET(22)]
+ ldp x24, x25, [sp, #GUEST_EL1_XREG_OFFSET(24)]
+ ldp x26, x27, [sp, #GUEST_EL1_XREG_OFFSET(26)]
+ ldp x28, x29, [sp, #GUEST_EL1_XREG_OFFSET(28)]
+ ldr x30, [sp, #GUEST_EL1_XREG_OFFSET(30)]
+
+ add sp, sp, #GUEST_EL1_REGS_SIZE
+ eret
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 7/8] arm64: Add Stage-2 MMU demand paging test
2026-10-01 15:16 [PATCH v3 0/8] arm64: Add Stage-2 MMU and Nested Guest Framework Joey Gouly
` (5 preceding siblings ...)
2026-10-01 15:16 ` [PATCH v3 6/8] lib: arm64: Add guest-internal " Joey Gouly
@ 2026-10-01 15:16 ` Joey Gouly
2026-10-01 15:16 ` [PATCH v3 8/8] arm64: Add basic guest execution tests Joey Gouly
7 siblings, 0 replies; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:16 UTC (permalink / raw)
To: kvmarm
Cc: Jing Zhang, Marc Zyngier, Oliver Upton, Mark Brown, Wei-Lin Chang,
andrew.jones, Joey Gouly
From: Jing Zhang <jingzhangos@google.com>
Introduce a new test case to validate Stage-2 MMU fault handling. The
test verifies that the hypervisor correctly identifies and handles
Stage-2 data aborts triggered by a guest accessing unmapped memory.
The test performs the following:
- Sets up a guest with Stage-1 disabled, using identity-mapped host
code and shared data in the Stage-2 page tables.
- Triggers a Stage-2 data abort by accessing a specific unmapped IPA.
- Catches the exception in the host, verifies the fault address,
and dynamically maps a new page to resolve the fault.
- Resumes the guest to confirm the memory access completes successfully
and the fault handler functioned as expected.
Signed-off-by: Jing Zhang <jingzhangos@google.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
---
arm/Makefile.arm64 | 1 +
arm/stage2-mmu-test.c | 113 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 114 insertions(+)
create mode 100644 arm/stage2-mmu-test.c
diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
index 9026fd71..e547f92d 100644
--- a/arm/Makefile.arm64
+++ b/arm/Makefile.arm64
@@ -67,6 +67,7 @@ tests += $(TEST_DIR)/cache.$(exe)
tests += $(TEST_DIR)/debug.$(exe)
tests += $(TEST_DIR)/fpu.$(exe)
tests += $(TEST_DIR)/mte.$(exe)
+tests += $(TEST_DIR)/stage2-mmu-test.$(exe)
include $(SRCDIR)/$(TEST_DIR)/Makefile.common
diff --git a/arm/stage2-mmu-test.c b/arm/stage2-mmu-test.c
new file mode 100644
index 00000000..91d3de7e
--- /dev/null
+++ b/arm/stage2-mmu-test.c
@@ -0,0 +1,113 @@
+/*
+ * ARM64 Stage-2 MMU Demand Paging Test
+ *
+ * This test validates stage-2 data abort handling by purposefully
+ * accessing unmapped memory in the guest and verifying that the
+ * host correctly handles the fault by mapping the page.
+ *
+ * Copyright (C) 2026 Google LLC.
+ * Author: Jing Zhang <jingzhangos@google.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+#include <libcflat.h>
+#include <alloc_page.h>
+#include <asm/io.h>
+#include <asm/smp.h>
+#include <asm/guest.h>
+#include <asm/stage2_mmu.h>
+
+#define TEST_PAGE_IPA 0x50000000UL
+#define FAULT_ADDR_IPA 0x60000000UL
+#define TEST_DATA 0xBEEFCAFEUL
+
+static volatile bool handled = false;
+
+static void guest_code(void)
+{
+ volatile unsigned long *test_va = (void *)TEST_PAGE_IPA;
+ volatile unsigned long *fault_va = (void *)FAULT_ADDR_IPA;
+
+ *fault_va = *test_va;
+
+ if (*fault_va == *test_va && *fault_va == TEST_DATA)
+ handled = true;
+
+ asm("hvc #0");
+}
+
+static enum guest_handler_result guest_exception_handler(struct guest *guest)
+{
+ unsigned long far, ec;
+ unsigned long *fixup_page;
+
+ ec = guest->esr_el2 >> ESR_ELx_EC_SHIFT;
+
+ if (ec == ESR_ELx_EC_HVC64) {
+ report_info("CPU%d: Guest exited via HVC.", smp_processor_id());
+ return GUEST_ACTION_EXIT;
+ }
+
+ if (ec == ESR_ELx_EC_DABT_LOW) {
+ far = guest->far_el2;
+ if (far == FAULT_ADDR_IPA) {
+ fixup_page = alloc_page();
+ s2mmu_map(guest->s2mmu, FAULT_ADDR_IPA,
+ virt_to_phys(fixup_page), PAGE_SIZE, S2_MAP_RW);
+ report(true, "Caught stage-2 fault at 0x%lx", far);
+ } else {
+ report(false, "Unexpected fault address: 0x%lx", far);
+ }
+ } else {
+ report(false, "Unexpected exception class: 0x%lx", ec);
+ }
+
+ return GUEST_ACTION_RESUME;
+}
+
+int main(int argc, char **argv)
+{
+ struct guest *guest;
+ unsigned long *test_page;
+ unsigned long code_va_base, code_pa_base, data_base;
+
+ report_prefix_push("stage2-mmu");
+
+ if (current_level() != CurrentEL_EL2) {
+ report_skip("EL2 is required");
+ return report_summary();
+ }
+
+ guest = guest_create_identity(smp_processor_id(), guest_code,
+ S2_PAGE_4K);
+
+ /* Map host code: IPA(VA) -> PA */
+ /* We use the host VA as the Guest IPA because guest stage 1 is disabled. */
+ code_va_base = (unsigned long)guest_code;
+ code_pa_base = virt_to_phys((void *)guest_code);
+
+ /* Align to 2MB to use block descriptors where possible */
+ code_va_base = code_va_base & ~(SZ_2M - 1);
+ code_pa_base = code_pa_base & ~(SZ_2M - 1);
+ s2mmu_map(guest->s2mmu, code_va_base, code_pa_base, SZ_2M, S2_MAP_RW);
+
+ /* Identity map the shared variable */
+ data_base = virt_to_phys((void *)&handled) & PAGE_MASK;
+ s2mmu_map(guest->s2mmu, data_base, data_base, PAGE_SIZE, S2_MAP_RW);
+
+ /* Map test data page */
+ test_page = alloc_page();
+ *test_page = TEST_DATA;
+ s2mmu_map(guest->s2mmu, TEST_PAGE_IPA, virt_to_phys(test_page), PAGE_SIZE, S2_MAP_RW);
+
+ guest_install_handler(guest, ELx_LOW_SYNC_64, guest_exception_handler);
+
+ report_info("CPU%d: entering guest...", smp_processor_id());
+
+ guest_run(guest);
+
+ report(handled, "Stage-2 fault handling test completed");
+ guest_destroy(guest);
+
+ return report_summary();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 8/8] arm64: Add basic guest execution tests
2026-10-01 15:16 [PATCH v3 0/8] arm64: Add Stage-2 MMU and Nested Guest Framework Joey Gouly
` (6 preceding siblings ...)
2026-10-01 15:16 ` [PATCH v3 7/8] arm64: Add Stage-2 MMU demand paging test Joey Gouly
@ 2026-10-01 15:16 ` Joey Gouly
7 siblings, 0 replies; 11+ messages in thread
From: Joey Gouly @ 2026-10-01 15:16 UTC (permalink / raw)
To: kvmarm
Cc: Jing Zhang, Marc Zyngier, Oliver Upton, Mark Brown, Wei-Lin Chang,
andrew.jones, Joey Gouly
Add tests that verify that EL2 and EL1 exits can inspect and modify
guest registers.
Assisted-by: LLM
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
---
arm/Makefile.arm64 | 1 +
arm/guest-test.c | 157 ++++++++++++++++++++++++++++++++++++++++++++
lib/arm64/asm/esr.h | 3 +
3 files changed, 161 insertions(+)
create mode 100644 arm/guest-test.c
diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
index e547f92d..5c329588 100644
--- a/arm/Makefile.arm64
+++ b/arm/Makefile.arm64
@@ -68,6 +68,7 @@ tests += $(TEST_DIR)/debug.$(exe)
tests += $(TEST_DIR)/fpu.$(exe)
tests += $(TEST_DIR)/mte.$(exe)
tests += $(TEST_DIR)/stage2-mmu-test.$(exe)
+tests += $(TEST_DIR)/guest-test.$(exe)
include $(SRCDIR)/$(TEST_DIR)/Makefile.common
diff --git a/arm/guest-test.c b/arm/guest-test.c
new file mode 100644
index 00000000..90676bdf
--- /dev/null
+++ b/arm/guest-test.c
@@ -0,0 +1,157 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ *
+ * Copyright (C) 2026 Arm Ltd.
+ */
+#include <libcflat.h>
+#include <asm/guest.h>
+#include <asm/io.h>
+#include <asm/smp.h>
+#include <asm/sysreg.h>
+
+#define HVC_FIRST_EXIT 0x101
+#define HVC_SECOND_EXIT 0x102
+#define HVC_EL1_DONE 0x103
+#define HVC_BAD_EXIT 0x104
+#define EL1_BRK_IMM 0x321
+#define INITIAL_VALUE 0x123
+#define RESUME_VALUE 0x456
+#define EL1_RESUME_VALUE 0x789
+#define TPIDR_EL2_VALUE 0x12345678
+
+static void guest_resume_entry(void)
+{
+ unsigned long value;
+
+ /* Place a specific value in x0, and re-read x0 after the HVC */
+ asm volatile(
+ "mov x0, %1\n"
+ "hvc %2\n"
+ "mov %0, x0"
+ : "=r" (value)
+ : "r" (INITIAL_VALUE), "i" (HVC_FIRST_EXIT)
+ : "x0", "memory");
+
+ if (value == RESUME_VALUE)
+ asm volatile("hvc %0" :: "i" (HVC_SECOND_EXIT));
+ else
+ asm volatile("hvc %0" :: "i" (HVC_BAD_EXIT));
+}
+
+static void guest_el1_exception_entry(void)
+{
+ unsigned long value;
+
+ // BRK, and check that the exception handler modified x0
+ asm volatile(
+ "brk %1\n"
+ "mov %0, x0"
+ : "=r" (value)
+ : "i" (EL1_BRK_IMM)
+ : "x0", "memory");
+
+ if (value == EL1_RESUME_VALUE) {
+ asm volatile(
+ "mov x0, %0\n"
+ "hvc %1"
+ :
+ : "r" (value), "i" (HVC_EL1_DONE)
+ : "x0", "memory");
+ } else {
+ asm volatile("hvc %0" :: "i" (HVC_BAD_EXIT));
+ }
+}
+
+static bool guest_exited_with_hvc(const struct guest *guest,
+ unsigned long expected_imm)
+{
+ unsigned long ec = guest->esr_el2 >> ESR_ELx_EC_SHIFT;
+ unsigned long imm = guest->esr_el2 & ESR_ELx_ISS_IMM_MASK;
+
+ return ec == ESR_ELx_EC_HVC64 && imm == expected_imm;
+}
+
+static enum guest_handler_result guest_sync_handler(struct guest *guest)
+{
+ if (guest_exited_with_hvc(guest, HVC_FIRST_EXIT) &&
+ guest->x[0] == INITIAL_VALUE)
+ guest->x[0] = RESUME_VALUE;
+
+ return GUEST_ACTION_EXIT;
+}
+
+static void guest_el1_sync_handler(struct guest_el1_regs *regs,
+ unsigned int esr)
+{
+ unsigned long ec = esr >> ESR_ELx_EC_SHIFT;
+ unsigned long imm = esr & ESR_ELx_ISS_IMM_MASK;
+
+ if (ec == ESR_ELx_EC_BRK64 && imm == EL1_BRK_IMM)
+ regs->regs[0] = EL1_RESUME_VALUE;
+ else
+ regs->regs[0] = 0;
+
+ regs->elr += 4;
+}
+
+static void test_guest_resume(void)
+{
+ unsigned long old_tpidr_el2 = read_sysreg(tpidr_el2);
+ struct guest *guest;
+ bool tpidr_restored;
+
+ guest = guest_create_identity(smp_processor_id(), guest_resume_entry,
+ S2_PAGE_4K);
+ assert(guest);
+ guest_install_handler(guest, ELx_LOW_SYNC_64, guest_sync_handler);
+
+ guest_run(guest);
+ report(guest_exited_with_hvc(guest, HVC_FIRST_EXIT) &&
+ guest->x[0] == RESUME_VALUE,
+ "exit handler changes guest register state");
+
+ write_sysreg(0, elr_el2);
+ write_sysreg(TPIDR_EL2_VALUE, tpidr_el2);
+ guest_run(guest);
+ tpidr_restored = read_sysreg(tpidr_el2) == TPIDR_EL2_VALUE;
+ write_sysreg(old_tpidr_el2, tpidr_el2);
+
+ report(guest_exited_with_hvc(guest, HVC_SECOND_EXIT),
+ "guest resumes with its saved PC and register state");
+ report(tpidr_restored, "guest run restores host TPIDR_EL2");
+
+ guest_destroy(guest);
+}
+
+static void test_guest_el1_exception(void)
+{
+ struct guest *guest;
+
+ guest = guest_create_identity(smp_processor_id(),
+ guest_el1_exception_entry, S2_PAGE_4K);
+ assert(guest);
+ guest_install_handler(guest, ELx_LOW_SYNC_64, guest_sync_handler);
+ guest_install_el1_handler(guest, ELxH_SYNC, guest_el1_sync_handler);
+
+ guest_run(guest);
+ report(guest_exited_with_hvc(guest, HVC_EL1_DONE) &&
+ guest->x[0] == EL1_RESUME_VALUE,
+ "guest handles an EL1 synchronous exception");
+
+ guest_destroy(guest);
+}
+
+int main(int argc, char **argv)
+{
+ report_prefix_push("guest");
+
+ if (current_level() != CurrentEL_EL2) {
+ report_skip("EL2 is required");
+ return report_summary();
+ }
+
+ test_guest_resume();
+ test_guest_el1_exception();
+
+ return report_summary();
+}
diff --git a/lib/arm64/asm/esr.h b/lib/arm64/asm/esr.h
index 3860eb9d..02a27c24 100644
--- a/lib/arm64/asm/esr.h
+++ b/lib/arm64/asm/esr.h
@@ -72,4 +72,7 @@
#define ESR_ELx_FSC (0x3F)
#define ESR_ELx_FSC_EXTABT (0x10)
+/* Immediate field for HVC, SVC, and BRK instructions. */
+#define ESR_ELx_ISS_IMM_MASK GENMASK(15, 0)
+
#endif /* _ASMARM64_ESR_H_ */
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread