Linux EFI development
 help / color / mirror / Atom feed
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


  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