From: Thara Gopinath <tgopinath@linux.microsoft.com>
To: kys@microsoft.com, haiyangz@microsoft.com, wei.liu@kernel.org,
decui@microsoft.com, tglx@kernel.org, mingo@redhat.com,
bp@alien8.de, dave.hansen@linux.intel.com, hpa@zytor.com,
ardb@kernel.org, ilias.apalodimas@linaro.org
Cc: James.Bottomley@HansenPartnership.com,
"longli@microsoft.com--cc=tzimmermann"@suse.de,
javierm@redhat.com, lszubowi@redhat.com,
francescopompo2@gmail.com, tgopinath@microsoft.com,
x86@kernel.org, linux-hyperv@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-efi@vger.kernel.org,
Thara Gopinath <tgopinath@linux.microsoft.com>
Subject: [RFC PATCH 08/12] arch: x86: hyperv: Build initial vCPU context for VTL1 secure kernel
Date: Tue, 1 Sep 2026 09:55:22 -0700 [thread overview]
Message-ID: <20260901165647.3160413-9-tgopinath@linux.microsoft.com> (raw)
In-Reply-To: <20260901165647.3160413-1-tgopinath@linux.microsoft.com>
Enabling VTL1 on the boot processor requires handing Hyper-V a fully
populated hv_init_vp_context describing the state VTL1 should start in:
initial page tables, GDT/TSS, control registers and entry point. Add
the arch-specific builder that assembles this context using the memory
region reserved for the secure kernel.
Actual enablement of VTL1 using this context is done in a subsequent
patch.
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
arch/x86/hyperv/Makefile | 1 +
arch/x86/hyperv/hv_vtl_vsm.c | 258 ++++++++++++++++++++++++++++++++
arch/x86/include/asm/mshyperv.h | 9 ++
3 files changed, 268 insertions(+)
create mode 100644 arch/x86/hyperv/hv_vtl_vsm.c
diff --git a/arch/x86/hyperv/Makefile b/arch/x86/hyperv/Makefile
index 56292102af623..1fdc20e239243 100644
--- a/arch/x86/hyperv/Makefile
+++ b/arch/x86/hyperv/Makefile
@@ -2,6 +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)/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
new file mode 100644
index 0000000000000..edc55264c4d87
--- /dev/null
+++ b/arch/x86/hyperv/hv_vtl_vsm.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Architecture-specific bring-up state for the VTL1 secure kernel: build
+ * the page tables, GDT/TSS and initial vCPU register context that Hyper-V
+ * loads when transitioning the boot processor to VTL1.
+ *
+ * Copyright (c) 2025-2026, Microsoft Corporation.
+ *
+ * Author: Thara Gopinath <tgopinath@linux.microsoft.com>
+ */
+
+#include <linux/align.h>
+#include <linux/bits.h>
+#include <linux/init.h>
+#include <hyperv/vsm.h>
+#include <asm/msr-index.h>
+#include <asm/processor-flags.h>
+#include <asm/mshyperv.h>
+
+/* Define PAGE size and related variables for initial secure kernel pages */
+#define VSM_PAGE_SHIFT 12
+#define VSM_PAGE_SIZE BIT(VSM_PAGE_SHIFT)
+#define PAGE_AT(addr, idx) ((addr) + (idx) * VSM_PAGE_SIZE)
+#define VSM_VA_FROM_PA(pa) (pa) /* Assumes identity mapping in secure kernel */
+
+/* Number of entries in a page table (all levels) */
+#define VSM_ENTRIES_PER_PT 512
+#define VSM_PMD_SIZE (VSM_PAGE_SIZE * VSM_ENTRIES_PER_PT)
+
+/*
+ * Initial memory that will be mapped for secure kernel.
+ * Secure Kernel memory can be larger than this.
+ */
+#define VSM_SK_PTE_PAGES_COUNT (ALIGN(VSM_SK_INITIAL_MAP_SIZE, VSM_PMD_SIZE) / VSM_PMD_SIZE)
+
+/* VSM pages */
+enum {
+ VSM_GDT_PAGE,
+ VSM_TSS_PAGE,
+ VSM_PML4E_PAGE,
+ VSM_PDPE_PAGE,
+ VSM_PDE_PAGE,
+ VSM_PTE_PAGES,
+ /* PTE tables consume several pages */
+ VSM_KERNEL_STACK_PAGE = VSM_PTE_PAGES + VSM_SK_PTE_PAGES_COUNT,
+ VSM_PAGES_COUNT
+};
+
+#define VSM_PT_FLAGS (_PAGE_PRESENT | _PAGE_RW)
+#define VSM_PTE_FLAGS (VSM_PT_FLAGS | _PAGE_ACCESSED | _PAGE_DIRTY)
+
+/* Shifts to compute page table mapping */
+#define VSM_PD_TABLE_SHIFT 21
+#define VSM_PDP_TABLE_SHIFT 30
+#define VSM_PML4_TABLE_SHIFT 39
+
+/* Given VA, get index into the page table at a given level */
+#define VSM_GET_PML4_INDEX(addr) (((addr) >> VSM_PML4_TABLE_SHIFT) & 0x1FF)
+#define VSM_GET_PDP_INDEX(addr) (((addr) >> VSM_PDP_TABLE_SHIFT) & 0x1FF)
+#define VSM_GET_PD_INDEX(addr) (((addr) >> VSM_PD_TABLE_SHIFT) & 0x1FF)
+
+static void __init hv_vsm_fill_pte_tables(phys_addr_t sk_pa, u64 *pde,
+ int pd_index, int num_pte_tables)
+{
+ u16 i, j;
+ phys_addr_t pte_pa;
+ u64 *pte;
+
+ /* Fill page tables with entries */
+ for (i = 0; i < num_pte_tables; i++) {
+ pte_pa = PAGE_AT(sk_pa, VSM_PTE_PAGES + i);
+ pte = phys_to_virt(pte_pa);
+ *(pde + pd_index + i) = pte_pa | VSM_PTE_FLAGS;
+ for (j = 0; j < VSM_ENTRIES_PER_PT; j++) {
+ *(pte + j) =
+ (sk_pa + ((j + (i * VSM_ENTRIES_PER_PT)) * VSM_PAGE_SIZE)) |
+ VSM_PTE_FLAGS;
+ }
+ }
+}
+
+static void __init hv_vsm_init_page_tables(struct hv_init_vp_context *vp_ctx, phys_addr_t sk_pa)
+{
+ unsigned int pml4_index;
+ unsigned int pdp_index;
+ unsigned int pd_index;
+ phys_addr_t pml4e_pa;
+ phys_addr_t pdpe_pa;
+ phys_addr_t pde_pa;
+ u64 *pml4e;
+ u64 *pdpe;
+ u64 *pde;
+ int num_pte_tables;
+
+ /* Compute the page-table indices at which the secure kernel mapping starts. */
+ pml4_index = VSM_GET_PML4_INDEX(sk_pa);
+ pdp_index = VSM_GET_PDP_INDEX(sk_pa);
+ pd_index = VSM_GET_PD_INDEX(sk_pa);
+
+ pml4e_pa = PAGE_AT(sk_pa, VSM_PML4E_PAGE);
+ pdpe_pa = PAGE_AT(sk_pa, VSM_PDPE_PAGE);
+ pde_pa = PAGE_AT(sk_pa, VSM_PDE_PAGE);
+
+ pml4e = phys_to_virt(pml4e_pa);
+ pdpe = phys_to_virt(pdpe_pa);
+ pde = phys_to_virt(pde_pa);
+
+ /*
+ * Zero the PML4, PDP, PD and PTE pages before populating them so that
+ * any entry not explicitly written below has its present bit clear.
+ */
+ memset(pml4e, 0,
+ (VSM_KERNEL_STACK_PAGE - VSM_PML4E_PAGE) * VSM_PAGE_SIZE);
+
+ *(pml4e + pml4_index) = pdpe_pa | VSM_PT_FLAGS;
+ *(pdpe + pdp_index) = pde_pa | VSM_PT_FLAGS;
+
+ /*
+ * Initial page tables map only the first VSM_SK_INITIAL_MAP_SIZE size of memory.
+ * This memory will be used for the Secure Loader and initial Secure Kernel.
+ */
+ num_pte_tables = (VSM_SK_INITIAL_MAP_SIZE / VSM_PAGE_SIZE) / VSM_ENTRIES_PER_PT;
+ hv_vsm_fill_pte_tables(sk_pa, pde, pd_index, num_pte_tables);
+
+ vp_ctx->cr3 = pml4e_pa;
+}
+
+static void __init hv_vsm_init_gdt(struct hv_init_vp_context *vp_ctx, phys_addr_t sk_pa)
+{
+ phys_addr_t gdt_pa, tss_pa, kstack_pa;
+ void *gdt_va;
+ u64 tss_sk_va, gdt;
+ struct x86_hw_tss *tss;
+ size_t gdt_size = sizeof(gdt), tss_size = sizeof(*tss), gdt_offset = 0;
+
+ /* Get a page for the GDT */
+ gdt_pa = PAGE_AT(sk_pa, VSM_GDT_PAGE);
+ gdt_va = phys_to_virt(gdt_pa);
+ /* Get a page for the TSS */
+ tss_pa = PAGE_AT(sk_pa, VSM_TSS_PAGE);
+ tss = phys_to_virt(tss_pa);
+ /* Compute the VA that secure kernel will see for the TSS */
+ tss_sk_va = VSM_VA_FROM_PA(tss_pa);
+ /* Get a page for the secure kernel initial stack */
+ kstack_pa = PAGE_AT(sk_pa, VSM_KERNEL_STACK_PAGE);
+ /* Set the initial stack pointer for the kernel to point to bottom of kernel stack */
+ tss->sp0 = VSM_VA_FROM_PA(kstack_pa) + VSM_PAGE_SIZE;
+ vp_ctx->rsp = tss->sp0;
+
+ /* Make and add the NULL descriptor to the GDT */
+ gdt = 0;
+ memcpy(gdt_va + gdt_offset, &gdt, gdt_size);
+ gdt_offset += gdt_size;
+
+ /* Make and add a code segment descriptor to the GDT */
+ gdt = GDT_ENTRY(DESC_CODE64, 0, 0);
+ memcpy(gdt_va + gdt_offset, &gdt, gdt_size);
+ gdt_offset += gdt_size;
+
+ /* Make and add a data segment descriptor to the GDT */
+ gdt = GDT_ENTRY(DESC_DATA64, 0, 0);
+ memcpy(gdt_va + gdt_offset, &gdt, gdt_size);
+ gdt_offset += gdt_size;
+
+ /*
+ * Make and add a system segment descriptor for the TSS in the GDT.
+ *
+ * In 64-bit mode a system-segment descriptor (TSS/LDT) is 16 bytes
+ * wide: the lower 8 bytes have the same layout as the legacy 32-bit
+ * descriptor (produced by GDT_ENTRY), and the upper 8 bytes hold
+ * base[63:32] in the low 32 bits with the high 32 bits reserved 0.
+ * GDT_ENTRY masks base to 32 bits, so the upper half must be written
+ * explicitly.
+ */
+ gdt = GDT_ENTRY(DESC_TSS32, tss_sk_va, tss_size);
+ memcpy(gdt_va + gdt_offset, &gdt, gdt_size);
+ gdt_offset += gdt_size;
+ gdt = tss_sk_va >> 32;
+ memcpy(gdt_va + gdt_offset, &gdt, gdt_size);
+ gdt_offset += gdt_size;
+
+ /* Set up the GDT register */
+ vp_ctx->gdtr.base = VSM_VA_FROM_PA(gdt_pa);
+ vp_ctx->gdtr.limit = gdt_offset - 1;
+
+ /* Set the code segment (CS) selector */
+ vp_ctx->cs.base = 0;
+ vp_ctx->cs.limit = 0;
+ vp_ctx->cs.selector = 1 << 3;
+ vp_ctx->cs.attributes = _DESC_S | _DESC_PRESENT | _DESC_ACCESSED |
+ _DESC_CODE_READABLE | _DESC_CODE_EXECUTABLE |
+ _DESC_LONG_CODE | _DESC_GRANULARITY_4K;
+
+ /* Set the data segment (DS) selector */
+ vp_ctx->ds.base = 0;
+ vp_ctx->ds.limit = 0;
+ vp_ctx->ds.selector = 2 << 3;
+ vp_ctx->ds.attributes = _DESC_S | _DESC_PRESENT | _DESC_ACCESSED |
+ _DESC_DATA_WRITABLE | _DESC_GRANULARITY_4K | _DESC_DB;
+
+ /* Set the ES, FS and GS to be the same as DS, for now */
+ vp_ctx->es = vp_ctx->ds;
+ vp_ctx->fs = vp_ctx->ds;
+ vp_ctx->gs = vp_ctx->ds;
+
+ /* Set the stack selector to 0 (unused in long mode) */
+ vp_ctx->ss.selector = 0;
+
+ /* Set the task register selector */
+ vp_ctx->tr.base = tss_sk_va;
+ vp_ctx->tr.limit = tss_size - 1;
+ vp_ctx->tr.selector = 3 << 3;
+ vp_ctx->tr.attributes = _DESC_PRESENT | _DESC_SYSTEM(11);
+}
+
+static void __init hv_vsm_init_cpu(struct hv_init_vp_context *vp_ctx, Elf64_Addr sk_entry_pa)
+{
+ /* Offset rip by any secure kernel header length */
+ vp_ctx->rip = VSM_VA_FROM_PA(sk_entry_pa);
+
+ /* ToDo: Check if can be replaced with CR0_STATE */
+ vp_ctx->cr0 =
+ X86_CR0_PG | /* Paging */
+ X86_CR0_WP | /* Write Protect */
+ X86_CR0_NE | /* Numeric Error */
+ X86_CR0_ET | /* Extension Type */
+ X86_CR0_MP | /* Math Present */
+ X86_CR0_PE; /* Protection Enable */
+
+ vp_ctx->cr4 =
+ X86_CR4_PSE | /* Page Size Extensions */
+ X86_CR4_PGE | /* Page Global Enable */
+ X86_CR4_PAE; /* Physical Address Extensions */
+
+ vp_ctx->efer =
+ EFER_LMA | /* Long Mode Active */
+ EFER_LME | /* Long Mode Enable */
+ EFER_NX | /* No Execute Enable */
+ EFER_SCE; /* System Call Enable */
+
+ /*
+ * Intel CPUs fail if the architectural read-as-one bit 1 of RFLAGS is not
+ * set. See Intel SDM Vol 3C, 26.3.1.4 (RFLAGS).
+ *
+ * TODO: Has Hyper-V implemented setting this automatically?
+ */
+ vp_ctx->rflags = X86_EFLAGS_FIXED;
+
+ vp_ctx->msr_cr_pat = PAT_VALUE(WB, WT, UC_MINUS, UC, WB, WT, UC_MINUS, UC);
+}
+
+void __init hv_vsm_arch_init_vp(struct hv_init_vp_context *vp_ctx, Elf64_Addr sk_entry_pa,
+ phys_addr_t sk_pa)
+{
+ hv_vsm_init_cpu(vp_ctx, sk_entry_pa);
+ hv_vsm_init_gdt(vp_ctx, sk_pa);
+ hv_vsm_init_page_tables(vp_ctx, sk_pa);
+}
diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
index f64393e853ee3..5f0d689641f05 100644
--- a/arch/x86/include/asm/mshyperv.h
+++ b/arch/x86/include/asm/mshyperv.h
@@ -7,6 +7,7 @@
#include <linux/msi.h>
#include <linux/io.h>
#include <linux/static_call.h>
+#include <linux/elf.h>
#include <asm/nospec-branch.h>
#include <asm/msr.h>
#include <hyperv/hvhdk.h>
@@ -248,6 +249,14 @@ void hv_crash_asm_end(void);
static inline void hv_root_crash_init(void) {}
#endif /* CONFIG_MSHV_ROOT && CONFIG_CRASH_DUMP */
+#ifdef CONFIG_HYPERV_VSM
+void __init hv_vsm_arch_init_vp(struct hv_init_vp_context *vp_ctx, Elf64_Addr sk_entry_pa,
+ phys_addr_t sk_pa);
+#else /* CONFIG_HYPERV_VSM */
+static inline void __init hv_vsm_arch_init_vp(struct hv_init_vp_context *vp_ctx,
+ Elf64_Addr sk_entry_pa, phys_addr_t sk_pa) {}
+#endif
+
#else /* CONFIG_HYPERV */
static inline void hyperv_init(void) {}
static inline void hyperv_setup_mmu_ops(void) {}
--
2.34.1
next prev parent reply other threads:[~2026-09-01 16:57 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 01/12] drivers: hv: Add HYPERV_VSM kconfig option Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 02/12] drivers: hv: hv_common: Allocate Hyper-V output arg page when VSM is enabled Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 03/12] drivers: hv: Reserve memory for VSM secure kernel during early boot Thara Gopinath
2026-09-02 0:59 ` Wei Liu
2026-09-02 13:38 ` Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 04/12] firmware: efi: libstub: x86-stub: Enable VSM awareness in efi os indications variable Thara Gopinath
2026-09-02 1:09 ` Wei Liu
2026-09-02 14:23 ` Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 05/12] include: hyperv: hvgdk_mini.h: Add VTL-specific structures and bits Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 06/12] drivers: hv: Add VSM boot driver and enable VTL1 at the partition level Thara Gopinath
2026-09-02 1:16 ` Wei Liu
2026-09-02 14:28 ` Thara Gopinath
2026-09-02 4:43 ` Wei Liu
2026-09-04 13:23 ` Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 07/12] drivers: hv: hv_vsm_boot: load secure kernel image from firmware Thara Gopinath
2026-09-02 4:37 ` Wei Liu
2026-09-02 16:22 ` Thara Gopinath
2026-09-02 22:58 ` Wei Liu
2026-09-01 16:55 ` Thara Gopinath [this message]
2026-09-01 16:55 ` [RFC PATCH 09/12] drivers: hv: hv_vsm_boot: Enable VTL1 on the boot processor Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 10/12] arch: x86: hyperv: hv_vtl_vsm: Introduce vtlcall Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 11/12] drivers: hv: hv_vsm_boot: Boot primary processor in VTL1 Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 12/12] drivers: hv: hv_vsm_boot: Boot secondary processors " Thara Gopinath
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=20260901165647.3160413-9-tgopinath@linux.microsoft.com \
--to=tgopinath@linux.microsoft.com \
--cc="longli@microsoft.com--cc=tzimmermann"@suse.de \
--cc=James.Bottomley@HansenPartnership.com \
--cc=ardb@kernel.org \
--cc=bp@alien8.de \
--cc=dave.hansen@linux.intel.com \
--cc=decui@microsoft.com \
--cc=francescopompo2@gmail.com \
--cc=haiyangz@microsoft.com \
--cc=hpa@zytor.com \
--cc=ilias.apalodimas@linaro.org \
--cc=javierm@redhat.com \
--cc=kys@microsoft.com \
--cc=linux-efi@vger.kernel.org \
--cc=linux-hyperv@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lszubowi@redhat.com \
--cc=mingo@redhat.com \
--cc=tglx@kernel.org \
--cc=tgopinath@microsoft.com \
--cc=wei.liu@kernel.org \
--cc=x86@kernel.org \
/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