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 <[email protected]>
---
 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


Reply via email to