[RFC PATCH 10/12] arch: x86: hyperv: hv_vtl_vsm: Introduce vtlcall
From: Thara Gopinath <hidden>
Date: 2026-09-01 16:57:32
Also in:
linux-efi, lkml
Subsystem:
the rest, x86 architecture (32-bit and 64-bit) · Maintainers:
Linus Torvalds, Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen
A vtlcall is the mechanism by which a lower VTL (VTL0) transitions
into a higher VTL (VTL1) on Microsoft Hyper-V. It is issued as a
call to a fixed offset within the hypercall page; the hypervisor
saves the lower-VTL context, restores the upper-VTL context, and
resumes execution at the upper VTL. Control returns to VTL0 when
the upper VTL executes a vtlreturn.
Introduce the x86 VTL0-side vtlcall infrastructure:
- hv_vsm_vtlcall(): runtime entry point. Preserves state VTL1 can
clobber (IRQs, FPU, CR2) and invokes the assembly trampoline.
Returns the signed 64-bit status the secure kernel places in a3.
- hv_vsm_init_vtlcall(u64 vtl_call_offset): one-shot initializer
that installs the vtlcall target into a static_call. The offset
is passed in by the caller so the register read stays outside
arch code, mirroring mshv_vtl_return_call_init().
- struct hv_vtlcall_param and CONFIG_HYPERV_VSM API in
include/hyperv/vsm.h, with no-op stubs when VSM is disabled.
- __hv_vsm_vtlcall assembly trampoline in mshv_vtl_asm.S, plus
asm-offsets for the argument block.
Signed-off-by: Thara Gopinath <redacted>
---
arch/x86/hyperv/Makefile | 2 +-
arch/x86/hyperv/hv_vtl_vsm.c | 29 ++++++++++++
arch/x86/hyperv/mshv-asm-offsets.c | 8 ++++
arch/x86/hyperv/mshv_vtl_asm.S | 75 ++++++++++++++++++++++++++++++
include/hyperv/vsm.h | 22 +++++++++
5 files changed, 135 insertions(+), 1 deletion(-)
diff --git a/arch/x86/hyperv/Makefile b/arch/x86/hyperv/Makefile
index 1fdc20e239243..430f5f2bca40a 100644
--- a/arch/x86/hyperv/Makefile
+++ b/arch/x86/hyperv/Makefile@@ -2,7 +2,7 @@ obj-y := hv_init.o mmu.o nested.o irqdomain.o ivm.o obj-$(CONFIG_X86_64) += hv_apic.o obj-$(CONFIG_HYPERV_VTL_MODE) += hv_vtl.o mshv_vtl_asm.o -obj-$(CONFIG_HYPERV_VSM) += hv_vtl_vsm.o +obj-$(CONFIG_HYPERV_VSM) += hv_vtl_vsm.o mshv_vtl_asm.o $(obj)/mshv_vtl_asm.o: $(obj)/mshv-asm-offsets.h
diff --git a/arch/x86/hyperv/hv_vtl_vsm.c b/arch/x86/hyperv/hv_vtl_vsm.c
index edc55264c4d87..d533a8e113935 100644
--- a/arch/x86/hyperv/hv_vtl_vsm.c
+++ b/arch/x86/hyperv/hv_vtl_vsm.c@@ -15,6 +15,7 @@ #include <hyperv/vsm.h> #include <asm/msr-index.h> #include <asm/processor-flags.h> +#include <hyperv/hvgdk_mini.h> #include <asm/mshyperv.h> /* Define PAGE size and related variables for initial secure kernel pages */
@@ -256,3 +257,31 @@ void __init hv_vsm_arch_init_vp(struct hv_init_vp_context *vp_ctx, Elf64_Addr sk hv_vsm_init_gdt(vp_ctx, sk_pa); hv_vsm_init_page_tables(vp_ctx, sk_pa); } + +/* Implemented in mshv_vtl_asm.S */ +void __hv_vsm_vtlcall(struct hv_vtlcall_param *args); + +DEFINE_STATIC_CALL_NULL(__hv_vsm_vtlcall_hypercall, void (*)(void)); + +void __init hv_vsm_init_vtlcall(u64 vtl_call_offset) +{ + static_call_update(__hv_vsm_vtlcall_hypercall, + (void *)((u8 *)hv_hypercall_pg + vtl_call_offset)); +} + +s64 hv_vsm_vtlcall(struct hv_vtlcall_param *args) +{ + unsigned long flags; + u64 cr2; + + local_irq_save(flags); + kernel_fpu_begin_mask(0); + cr2 = native_read_cr2(); + __hv_vsm_vtlcall(args); + native_write_cr2(cr2); + kernel_fpu_end(); + local_irq_restore(flags); + + /* The secure kernel returns a signed 64-bit status in a3. */ + return (s64)args->a3; +}
diff --git a/arch/x86/hyperv/mshv-asm-offsets.c b/arch/x86/hyperv/mshv-asm-offsets.c
index 882c1db6df16c..0f0470ddaee59 100644
--- a/arch/x86/hyperv/mshv-asm-offsets.c
+++ b/arch/x86/hyperv/mshv-asm-offsets.c@@ -12,6 +12,7 @@ #define COMPILE_OFFSETS #include <linux/kbuild.h> +#include <hyperv/vsm.h> #include <asm/mshyperv.h> static void __used common(void)
@@ -34,4 +35,11 @@ static void __used common(void) OFFSET(MSHV_VTL_CPU_CONTEXT_r15, mshv_vtl_cpu_context, r15); OFFSET(MSHV_VTL_CPU_CONTEXT_cr2, mshv_vtl_cpu_context, cr2); } + + if (IS_ENABLED(CONFIG_HYPERV_VSM)) { + OFFSET(HV_VTLCALL_PARAM_a0, hv_vtlcall_param, a0); + OFFSET(HV_VTLCALL_PARAM_a1, hv_vtlcall_param, a1); + OFFSET(HV_VTLCALL_PARAM_a2, hv_vtlcall_param, a2); + OFFSET(HV_VTLCALL_PARAM_a3, hv_vtlcall_param, a3); + } }
diff --git a/arch/x86/hyperv/mshv_vtl_asm.S b/arch/x86/hyperv/mshv_vtl_asm.S
index f595eefad9abf..4d7007848ecd4 100644
--- a/arch/x86/hyperv/mshv_vtl_asm.S
+++ b/arch/x86/hyperv/mshv_vtl_asm.S@@ -15,6 +15,8 @@ #include <asm/frame.h> #include "mshv-asm-offsets.h" +#ifdef CONFIG_HYPERV_VTL_MODE + .text .section .noinstr.text, "ax" /*
@@ -114,3 +116,76 @@ SYM_FUNC_END(__mshv_vtl_return_call) .size mshv_vtl_return_sym, 8 mshv_vtl_return_sym: .quad __SCK____mshv_vtl_return_hypercall + +#endif /* CONFIG_HYPERV_VTL_MODE */ + +#ifdef CONFIG_HYPERV_VSM + .text +/* + * void __hv_vsm_vtlcall(struct hv_vtlcall_param *args) + * + * Perform a VTL call to switch to the upper VTL. + * + * The %rcx register is zeroed before the call and is clobbered by the + * hypercall. %rax is not restored by the upper VTL (passed via the assist + * page) but is unused and can be ignored. + * + * The args pointer is preserved on the stack across the VTL call since all + * argument registers are repurposed during the VTL call. + * + * Microsoft Hypervisor preserves %rsp during VTL switches. + */ +SYM_FUNC_START(__hv_vsm_vtlcall) + /* Save callee-saved registers */ + pushq %rbp + mov %rsp, %rbp + pushq %r12 + pushq %r13 + pushq %r14 + pushq %r15 + pushq %rbx + pushq %rdi + + /* Load struct fields into VTL calling convention registers */ + mov HV_VTLCALL_PARAM_a3(%rdi), %r8 + mov HV_VTLCALL_PARAM_a2(%rdi), %rdx + mov HV_VTLCALL_PARAM_a1(%rdi), %rsi + mov HV_VTLCALL_PARAM_a0(%rdi), %rdi + + /* Zero %rcx */ + xorl %ecx, %ecx + + /* VTL call */ + call STATIC_CALL_TRAMP_STR(__hv_vsm_vtlcall_hypercall) + + /* Restore args pointer from stack */ + popq %rax + + /* Store results back to struct */ + mov %rdi, HV_VTLCALL_PARAM_a0(%rax) + mov %rsi, HV_VTLCALL_PARAM_a1(%rax) + mov %rdx, HV_VTLCALL_PARAM_a2(%rax) + mov %r8, HV_VTLCALL_PARAM_a3(%rax) + + /* Restore callee-saved registers */ + popq %rbx + popq %r15 + popq %r14 + popq %r13 + popq %r12 + + popq %rbp + RET +SYM_FUNC_END(__hv_vsm_vtlcall) + +/* + * Ensure static_call_key symbol __SCK____hv_vsm_vtlcall_hypercall is + * accessible. Inspired by __ADDRESSABLE(sym) macro. + */ + .section .discard.addressable,"aw" + .align 8 + .type hv_vsm_vtlcall_sym, @object + .size hv_vsm_vtlcall_sym, 8 +hv_vsm_vtlcall_sym: + .quad __SCK____hv_vsm_vtlcall_hypercall +#endif /* CONFIG_HYPERV_VSM */
diff --git a/include/hyperv/vsm.h b/include/hyperv/vsm.h
index 51555c09413d5..c1fa4ef3bccde 100644
--- a/include/hyperv/vsm.h
+++ b/include/hyperv/vsm.h@@ -11,6 +11,8 @@ #ifndef _HYPERV_VSM_H #define _HYPERV_VSM_H +#include <linux/types.h> + /* * Size of memory that is initially mapped for the secure kernel by the * VTL0-side loader. The secure kernel image itself may be larger than
@@ -18,4 +20,24 @@ */ #define VSM_SK_INITIAL_MAP_SIZE (16 * 1024 * 1024) +/* + * Argument block passed from VTL0 to VTL1 across a vtlcall. Layout is + * shared with the arch-specific assembly trampoline that marshals these + * into registers. + */ +struct hv_vtlcall_param { + u64 a0; + u64 a1; + u64 a2; + u64 a3; +} __packed; + +#ifdef CONFIG_HYPERV_VSM +s64 hv_vsm_vtlcall(struct hv_vtlcall_param *args); +void hv_vsm_init_vtlcall(u64 vtl_call_offset); +#else +static inline s64 hv_vsm_vtlcall(struct hv_vtlcall_param *args) { return 0; } +static inline void hv_vsm_init_vtlcall(u64 vtl_call_offset) {} +#endif + #endif /* _HYPERV_VSM_H */
--
2.34.1