From: Joey Gouly <joey.gouly@arm.com>
To: Jing Zhang <jingzhangos@google.com>
Cc: KVM <kvm@vger.kernel.org>, KVMARM <kvmarm@lists.linux.dev>,
Marc Zyngier <maz@kernel.org>,
Wei-Lin Chang <weilin.chang@arm.com>,
Yao Yuan <yaoyuan@linux.alibaba.com>,
Oliver Upton <oliver.upton@linux.dev>,
Andrew Jones <andrew.jones@linux.dev>,
Alexandru Elisei <alexandru.elisei@arm.com>,
Mingwei Zhang <mizhang@google.com>,
Raghavendra Rao Ananta <rananta@google.com>,
Colton Lewis <coltonlewis@google.com>
Subject: Re: [kvm-unit-tests PATCH v2 4/7] lib: arm64: Add foundational guest execution framework
Date: Thu, 23 Apr 2026 16:30:03 +0100 [thread overview]
Message-ID: <20260423152947.GA4162415@e124191.cambridge.arm.com> (raw)
In-Reply-To: <20260416161643.GC3142999@e124191.cambridge.arm.com>
On Thu, Apr 16, 2026 at 05:16:43PM +0100, Joey Gouly wrote:
> Hi,
>
> First-look comments below,
>
> On Mon, Apr 13, 2026 at 01:46:27PM -0700, Jing Zhang wrote:
> > Introduce the infrastructure to manage and execute guest when running
> > at EL2. This provides the basis for testing advanced features like
> > nested virtualization and GICv4 direct interrupt injection.
> >
> > 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, including identity mappings for guest code,
> > stack, and UART.
> > - guest_run(): Assembly 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>
> > ---
> > arm/Makefile.arm64 | 2 +
> > lib/arm64/asm-offsets.c | 13 ++++++
> > lib/arm64/asm/guest.h | 46 +++++++++++++++++++++
> > lib/arm64/asm/sysreg.h | 6 +++
> > lib/arm64/guest.c | 89 +++++++++++++++++++++++++++++++++++++++++
> > lib/arm64/guest_arch.S | 59 +++++++++++++++++++++++++++
> > 6 files changed, 215 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..ceeecce5 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,17 @@ 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_ELR_OFFSET, guest, elr_el2);
> > + OFFSET(GUEST_SPSR_OFFSET, guest, spsr_el2);
> > + OFFSET(GUEST_HCR_OFFSET, guest, hcr_el2);
> > + OFFSET(GUEST_VTTBR_OFFSET, guest, vttbr_el2);
> > + OFFSET(GUEST_SCTLR_OFFSET, guest, sctlr_el1);
> > + OFFSET(GUEST_SP_EL1_OFFSET, guest, sp_el1);
> > + OFFSET(GUEST_ESR_OFFSET, guest, esr_el2);
> > + OFFSET(GUEST_FAR_OFFSET, guest, far_el2);
> > + OFFSET(GUEST_HPFAR_OFFSET, guest, hpfar_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
> > new file mode 100644
> > index 00000000..826c44f8
> > --- /dev/null
> > +++ b/lib/arm64/asm/guest.h
> > @@ -0,0 +1,46 @@
> > +/*
> > + * 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 vttbr_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;
> > +};
> > +
> > +struct guest *guest_create(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 f2d05018..857bee98 100644
> > --- a/lib/arm64/asm/sysreg.h
> > +++ b/lib/arm64/asm/sysreg.h
> > @@ -118,6 +118,10 @@ asm(
> > #define SCTLR_EL1_TCF0_SHIFT 38
> > #define SCTLR_EL1_TCF0_MASK GENMASK_ULL(39, 38)
> >
> > +#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)
> > @@ -132,6 +136,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_EL1 sys_reg(3, 5, 1, 0, 0)
>
> This isn't needed, the asm below can use `msr sctlr_el1` directly.
Ok this *is* needed, but this is SYS_SCTLR_EL12, not SYS_SCTLR_EL1. Same for
VBAR_EL1 in the next patch.
Thanks,
Joey
>
> > +
> > #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..68dd449d
> > --- /dev/null
> > +++ b/lib/arm64/guest.c
> > @@ -0,0 +1,89 @@
> > +/*
> > + * 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>
> > +
> > +static struct guest *__guest_create(struct s2_mmu *s2_ctx, void *entry_point)
> > +{
> > + struct guest *guest = calloc(1, sizeof(struct guest));
>
> Missing check if `guest` is NULL.
>
> > +
> > + guest->elr_el2 = (unsigned long)entry_point;
> > + guest->spsr_el2 = 0x3C5; /* M=EL1h, DAIF=Masked */
>
> Check lib/arm64/asm/ptrace.h, we can build this with macros, PSR_MODE_EL1h etc.
>
> > + guest->hcr_el2 = HCR_GUEST_FLAGS;
> > +
> > + if (s2_ctx) {
> > + guest->vttbr_el2 = virt_to_phys(s2_ctx->pgd);
> > + guest->vttbr_el2 |= ((unsigned long)s2_ctx->vmid << 48);
>
> Maybe this should be extracted into a helper in stage2_mmu, to build vttbr.
>
> > + } else {
> > + printf("Stage 2 MMU context missing!");
> > + }
>
> Can probably just assert(s2_ctx) instead of this conditional, don't think you
> can use the guest without one?
>
> > +
> > + guest->sctlr_el1 = read_sysreg(sctlr_el1);
> > + /* 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(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;
> > +
> > + ctx = s2mmu_init(vmid, granule, true);
> > + /*
> > + * 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);
> > + s2mmu_map(ctx, code_base, code_base, SZ_2M, S2_MAP_RW);
> > +
> > + /*
> > + * Map Stack
> > + * Allocate 16 pages (64K) in Host, get its PA, and map it for Guest.
>
> It's not always 16 pages (depends on PAGE_SIZE), can just reword to say
> `Allocate pages in Host..`
>
> > + */
> > + stack_page = alloc_pages(get_order(GUEST_STACK_SIZE >> PAGE_SHIFT));
> > + stack_pa = virt_to_phys(stack_page);
> > + /* Identity Map it (IPA = PA) */
> > + s2mmu_map(ctx, stack_pa, stack_pa, GUEST_STACK_SIZE, S2_MAP_RW);
> > +
> > + s2mmu_enable(ctx);
> > +
> > + /* Create Guest */
> > + /* Entry point is the PA of the function (Identity Mapped) */
> > + guest = __guest_create(ctx, (void *)guest_pa);
> > +
> > + /*
> > + * Setup Guest Stack Pointer
> > + * Must match where we mapped the stack + Offset
> > + */
> > + guest->sp_el1 = stack_pa + GUEST_STACK_SIZE;
> > +
> > + /* Map UART identity mapped, printf() available to guest */
> > + s2mmu_map(ctx, 0x09000000, 0x09000000, PAGE_SIZE, S2_MAP_DEVICE);
>
> This is not portable.
>
> > +
> > + return guest;
> > +}
>
> I'm actually not sure what I think of this function as a whole, maybe it does
> too much and the caller should be expected to map stuff in. Different types of
> tests could have different specific guest_create helpers.
>
> > +
> > +void guest_destroy(struct guest *guest)
> > +{
> > + s2mmu_disable(guest->s2mmu);
> > + s2mmu_destroy(guest->s2mmu);
> > + free(guest);
> > +}
> > diff --git a/lib/arm64/guest_arch.S b/lib/arm64/guest_arch.S
> > new file mode 100644
> > index 00000000..70c19507
> > --- /dev/null
> > +++ b/lib/arm64/guest_arch.S
> > @@ -0,0 +1,59 @@
> > +/*
> > + * 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>
> > +
> > +.global guest_run
> > +guest_run:
> > + /* 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]!
> > +
> > + /* Cache Guest Pointer in TPIDR_EL2 */
> > + msr tpidr_el2, x0
> > +
> > + /* Load Guest System Registers */
> > + ldr x1, [x0, #GUEST_ELR_OFFSET]
> > + msr elr_el2, x1
> > + ldr x1, [x0, #GUEST_SPSR_OFFSET]
> > + msr spsr_el2, x1
> > + ldr x1, [x0, #GUEST_HCR_OFFSET]
> > + msr hcr_el2, x1
> > + ldr x1, [x0, #GUEST_VTTBR_OFFSET]
> > + msr vttbr_el2, x1
> > + ldr x1, [x0, #GUEST_SCTLR_OFFSET]
> > + msr_s SYS_SCTLR_EL1, x1
>
> This part, can use `msr sctlr_el1`.
>
> > + ldr x1, [x0, #GUEST_SP_EL1_OFFSET]
> > + msr sp_el1, x1
> > +
> > + /* Load Guest GPRs */
> > + ldp x1, x2, [x0, #8]
> > + ldp x3, x4, [x0, #24]
> > + ldp x5, x6, [x0, #40]
> > + ldp x7, x8, [x0, #56]
> > + ldp x9, x10, [x0, #72]
> > + ldp x11, x12, [x0, #88]
> > + ldp x13, x14, [x0, #104]
> > + ldp x15, x16, [x0, #120]
> > + ldp x17, x18, [x0, #136]
> > + ldp x19, x20, [x0, #152]
> > + ldp x21, x22, [x0, #168]
> > + ldp x23, x24, [x0, #184]
> > + ldp x25, x26, [x0, #200]
> > + ldp x27, x28, [x0, #216]
> > + ldp x29, x30, [x0, #232]
> > + ldr x0, [x0, #0]
> > +
> > + isb
> > + eret
> > --
> > 2.53.0.1213.gd9a14994de-goog
> >
>
> Thanks,
> Joey
next prev parent reply other threads:[~2026-04-23 15:30 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-13 20:46 [kvm-unit-tests PATCH v2 0/7] arm64: Add Stage-2 MMU and Nested Guest Framework Jing Zhang
2026-04-13 20:46 ` [kvm-unit-tests PATCH v2 1/7] lib: arm64: Generalize ESR exception class definitions for EL2 support Jing Zhang
2026-04-16 15:27 ` Joey Gouly
2026-04-13 20:46 ` [kvm-unit-tests PATCH v2 2/7] lib: arm64: Add stage2 page table management library Jing Zhang
2026-04-16 15:19 ` Joey Gouly
2026-04-13 20:46 ` [kvm-unit-tests PATCH v2 3/7] lib: arm64: Generalize exception vector definitions for EL2 support Jing Zhang
2026-04-13 20:46 ` [kvm-unit-tests PATCH v2 4/7] lib: arm64: Add foundational guest execution framework Jing Zhang
2026-04-16 16:16 ` Joey Gouly
2026-04-23 15:30 ` Joey Gouly [this message]
2026-04-13 20:46 ` [kvm-unit-tests PATCH v2 5/7] lib: arm64: Add support for guest exit exception handling Jing Zhang
2026-04-13 20:46 ` [kvm-unit-tests PATCH v2 6/7] lib: arm64: Add guest-internal exception handling (EL1) Jing Zhang
2026-04-13 20:46 ` [kvm-unit-tests PATCH v2 7/7] arm64: Add Stage-2 MMU demand paging test Jing Zhang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260423152947.GA4162415@e124191.cambridge.arm.com \
--to=joey.gouly@arm.com \
--cc=alexandru.elisei@arm.com \
--cc=andrew.jones@linux.dev \
--cc=coltonlewis@google.com \
--cc=jingzhangos@google.com \
--cc=kvm@vger.kernel.org \
--cc=kvmarm@lists.linux.dev \
--cc=maz@kernel.org \
--cc=mizhang@google.com \
--cc=oliver.upton@linux.dev \
--cc=rananta@google.com \
--cc=weilin.chang@arm.com \
--cc=yaoyuan@linux.alibaba.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox