* [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests
@ 2026-09-01 16:55 Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 01/12] drivers: hv: Add HYPERV_VSM kconfig option Thara Gopinath
` (11 more replies)
0 siblings, 12 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
Introduction
=============
Microsoft Hyper-V implements Virtualization-Based Security (VBS) through
Virtual Secure Mode (VSM), which uses Virtual Trust Levels (VTLs) to
establish isolated execution environments enforced by the hypervisor. The
primary operating system executes in VTL0, while security-sensitive
components can execute in higher trust levels such as VTL1.
Linux Virtualization-Based Security (LVBS) extends this model to Linux
Hyper-V guests by introducing a secure kernel running in VTL1 alongside the
primary Linux kernel running in VTL0. The hypervisor enforces isolation
between the two trust levels, allowing security-sensitive functionality to
execute independently of the primary operating system.
This RFC series introduces the foundational infrastructure required to
support LVBS and boot a secure kernel in VTL1 from VTL0. Before any LVBS
service can exist, VTL0 has to stand VTL1 up, load the secure kernel and
boot all CPUs in VTL1.
This series intentionally focuses on establishing the VTL1 execution
environment and does not yet introduce higher-level LVBS services. Future
RFCs will build on this foundation by adding support for VTL0 to request
secure services from VTL1, attestation support, memory protection services,
and other security capabilities.
The goal of this RFC is to gather feedback on the overall LVBS
architecture, VTL1 boot model, processor bring-up sequence, and Linux
kernel integration before additional functionality is introduced.
Threat Model
=============
The security of the whole construction rests on the early boot path
being trusted. On a VSM-capable Hyper-V guest, the VTL0 kernel is
launched via Secure Boot, so the kernel image that enables VTL1
and loads the secure kernel is itself measured and signature-checked
before it runs. Enabling VTL1 and populating it from VTL0 during
early boot is therefore no weaker than Secure Boot itself: an attacker
who can subvert this stage can already subvert the kernel before VTL1
exists.
For the same reason, a Unified Kernel Image (UKI) is the preferred
delivery vehicle for LVBS. A UKI bundles the kernel, initrd,
cmdline, and (optionally) devicetree into a single PE binary that is
signed and verified as one unit by Secure Boot. This extends the
signature-verified boundary to cover the initrd, which is where the
secure kernel image is staged before VTL0 hands it to VTL1. Without
a UKI (or an equivalent measured-initrd scheme), an unsigned initrd
would be an obvious weak spot in the chain.
The series is based on hyperv-next (tag hyperv-next-signed-20260826) and
boot-tested on an x86_64 Hyper-V VTL0 guest with litebox [1] as the VTL1
secure kernel.
[1] https://github.com/microsoft/litebox
Comments and feedback are greatly appreciated.
Thara Gopinath (12):
drivers: hv: Add HYPERV_VSM kconfig option
drivers: hv: hv_common: Allocate Hyper-V output arg page when VSM is
enabled
drivers: hv: Reserve memory for VSM secure kernel during early boot
firmware: efi: libstub: x86-stub: Enable VSM awareness in efi os
indications variable
include: hyperv: hvgdk_mini.h: Add VTL-specific structures and bits
drivers: hv: Add VSM boot driver and enable VTL1 at the partition
level
drivers: hv: hv_vsm_boot: load secure kernel image from firmware
arch: x86: hyperv: Build initial vCPU context for VTL1 secure kernel
drivers: hv: hv_vsm_boot: Enable VTL1 on the boot processor
arch: x86: hyperv: hv_vtl_vsm: Introduce vtlcall
drivers: hv: hv_vsm_boot: Boot primary processor in VTL1
drivers: hv: hv_vsm_boot: Boot secondary processors in VTL1
arch/x86/hyperv/Makefile | 1 +
arch/x86/hyperv/hv_vtl_vsm.c | 287 +++++++++++
arch/x86/hyperv/mshv-asm-offsets.c | 8 +
arch/x86/hyperv/mshv_vtl_asm.S | 75 +++
arch/x86/include/asm/mshyperv.h | 9 +
drivers/firmware/efi/libstub/x86-stub.c | 57 +++
drivers/hv/Kconfig | 9 +
drivers/hv/Makefile | 3 +-
drivers/hv/hv_common.c | 3 +-
drivers/hv/hv_vsm.h | 19 +
drivers/hv/hv_vsm_boot.c | 630 ++++++++++++++++++++++++
drivers/hv/hv_vsm_securekernel.c | 188 +++++++
include/hyperv/hvgdk_mini.h | 46 ++
include/hyperv/vsm.h | 43 ++
14 files changed, 1376 insertions(+), 2 deletions(-)
create mode 100644 arch/x86/hyperv/hv_vtl_vsm.c
create mode 100644 drivers/hv/hv_vsm.h
create mode 100644 drivers/hv/hv_vsm_boot.c
create mode 100644 drivers/hv/hv_vsm_securekernel.c
create mode 100644 include/hyperv/vsm.h
base-commit: be0cfab740e58b70047ef6e7e3d578f00ed5d258
--
2.34.1
^ permalink raw reply [flat|nested] 24+ messages in thread
* [RFC PATCH 01/12] drivers: hv: Add HYPERV_VSM kconfig option
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
@ 2026-09-01 16:55 ` 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
` (10 subsequent siblings)
11 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
Introduce CONFIG_HYPERV_VSM, the top-level switch that gates
VTL0-side support for Microsoft Hyper-V's Virtual Secure Mode (VSM).
Enabling it lets the VTL0 kernel load a small secure kernel into
VTL1 and stand up the VTL0<->VTL1 interfaces needed to request
VSM services.
Depends on HYPERV and on X86_64 which is the only architecture currently
implemented.
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/hv/Kconfig | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/hv/Kconfig b/drivers/hv/Kconfig
index aa11bcefddf2c..a325fc61be604 100644
--- a/drivers/hv/Kconfig
+++ b/drivers/hv/Kconfig
@@ -40,6 +40,15 @@ config HYPERV_VTL_MODE
If unsure, say N
+config HYPERV_VSM
+ bool "Microsoft Hyper-V VSM driver"
+ depends on HYPERV && X86_64
+ help
+ Select this option to enable Hyper-V Virtual Secure Mode.
+ Enabling this option will load a secure kernel in VTL1 and
+ establish an interface between VTL0 and VTL1 to request for
+ VSM services.
+
config HYPERV_TIMER
def_bool HYPERV && X86
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 02/12] drivers: hv: hv_common: Allocate Hyper-V output arg page when VSM is enabled
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 ` Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 03/12] drivers: hv: Reserve memory for VSM secure kernel during early boot Thara Gopinath
` (9 subsequent siblings)
11 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
hv_output_page_exists() controls whether the per-CPU Hyper-V output
argument page is allocated at boot. This page is required for hypercalls
that return results via an output GPA, like HvCallGetVpRegisters.
The VSM drivers read VSM-specific VP registers (e.g. HvRegisterVsmVpStatus,
HvRegisterVsmCapabilities) via get_vp_registers(), which issues
HvCallGetVpRegisters and therefore depends on the output argument page
being present. Without it, those hypercalls fail and VSM initialization
cannot proceed.
Extend hv_output_page_exists() to return true when CONFIG_HYPERV_VSM is
enabled.
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/hv/hv_common.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 31256cb22b39e..44f4a10984618 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -259,7 +259,8 @@ static void hv_kmsg_dump_register(void)
static inline bool hv_output_page_exists(void)
{
- return hv_parent_partition() || IS_ENABLED(CONFIG_HYPERV_VTL_MODE);
+ return hv_parent_partition() || IS_ENABLED(CONFIG_HYPERV_VTL_MODE) ||
+ IS_ENABLED(CONFIG_HYPERV_VSM);
}
void __init hv_get_partition_id(void)
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 03/12] drivers: hv: Reserve memory for VSM secure kernel during early boot
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 ` Thara Gopinath
2026-09-02 0:59 ` Wei Liu
2026-09-01 16:55 ` [RFC PATCH 04/12] firmware: efi: libstub: x86-stub: Enable VSM awareness in efi os indications variable Thara Gopinath
` (8 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath, Anna Trikalinou
The VSM secure kernel runs in VTL1 and cannot use the normal Linux
kernel memory allocators — its memory must be reserved before the
general-purpose allocator takes ownership of the physical address space.
Add hv_vsm_securekernel.c to handle this early boot reservation. The
driver parses a new "securekernel=<size>[K|M|G][@<addr>]" kernel command
line argument to allow explicit control over the reserved region's size
and base address. If the argument is absent or specifies a region smaller
than the computed minimum, the driver falls back to an automatic
allocation.
The minimum reservation is calculated as:
16 MB (base) + (num_possible_cpus * 4 MB per-CPU)
Memory is reserved via memblock_phys_alloc_range() with 2 MB alignment
(SECKERNEL_ALIGN) and registered as an IORESOURCE_SYSTEM_RAM entry named
"vsm" in the iomem resource tree so it is visible in /proc/iomem and
protected from reuse.
The reservation is skipped if sk_res.start is already set, preventing
duplicate reservations if the setup hook is called more than once.
Co-developed-by: Anna Trikalinou <atrikalinou@microsoft.com>
Signed-off-by: Anna Trikalinou <atrikalinou@microsoft.com>
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/hv/Makefile | 1 +
drivers/hv/hv_vsm_securekernel.c | 188 +++++++++++++++++++++++++++++++
2 files changed, 189 insertions(+)
create mode 100644 drivers/hv/hv_vsm_securekernel.c
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 888a748cc7cb9..880c570832381 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -25,3 +25,4 @@ obj-$(subst m,y,$(CONFIG_MSHV_ROOT)) += hv_proc.o
ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL),)
obj-y += mshv_common.o
endif
+obj-$(subst m,y,$(CONFIG_HYPERV_VSM)) += hv_vsm_securekernel.o
diff --git a/drivers/hv/hv_vsm_securekernel.c b/drivers/hv/hv_vsm_securekernel.c
new file mode 100644
index 0000000000000..f90f6204b4d18
--- /dev/null
+++ b/drivers/hv/hv_vsm_securekernel.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024, Microsoft Corporation.
+ *
+ * Authors:
+ * Anna Trikalinou <atrikalinou@microsoft.com>
+ * Thara Gopinath <tgopinath@linux.microsoft.com>
+ */
+
+#include <linux/memblock.h>
+
+/* Define Memory Reservation for Secure Kernel */
+#define SECKERNEL_ALIGN SZ_2M
+#define SECKERNEL_ADDR_MAX (max_low_pfn_mapped << PAGE_SHIFT)
+/* Secure kernel map (16MB minimum)
+ * Region Offset Size
+ * VSM PAGES 0 < 2MB
+ * SKERNEL ?(ELF) 16MB+ (based on config)
+ */
+#define SECKERNEL_BASE_SIZE (16 * 1024 * 1024)
+#define SECKERNEL_PERCPU_SIZE (4 * 1024 * 1024)
+
+/* Estimate amount of memory needed for Secure Kernel */
+#define SECKERNEL_MIN_SIZE (SECKERNEL_BASE_SIZE + num_possible_cpus() * SECKERNEL_PERCPU_SIZE)
+
+struct resource sk_res = {
+ .name = "vsm",
+ .start = 0,
+ .end = 0,
+ .flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM,
+ .desc = IORES_DESC_RESERVED
+};
+
+/*
+ * That function parses "simple" securekernel command lines like
+ *
+ * securekernel=size[@offset]
+ *
+ * It returns 0 on success and -EINVAL on failure.
+ */
+static int __init parse_securekernel_simple(char *cmdline,
+ unsigned long long *securekernel_size,
+ unsigned long long *securekernel_base)
+{
+ char *cur = cmdline;
+
+ *securekernel_size = memparse(cmdline, &cur);
+ if (cmdline == cur) {
+ pr_warn("securekernel: memory value expected\n");
+ return -EINVAL;
+ }
+
+ if (*cur == '@') {
+ *securekernel_base = memparse(cur + 1, &cur);
+ } else if (*cur != ' ' && *cur != '\0') {
+ pr_warn("securekernel: unrecognized char: %c\n", *cur);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static __init char *get_last_securekernel(char *cmdline, const char *name)
+{
+ char *p = cmdline, *sk_cmdline = NULL;
+
+ /* find securekernel and use the last one if there are more */
+ p = strstr(p, name);
+ while (p) {
+ sk_cmdline = p;
+ p = strstr(p + 1, name);
+ }
+
+ if (!sk_cmdline)
+ return NULL;
+
+ return sk_cmdline;
+}
+
+static int __init __parse_securekernel(char *cmdline,
+ unsigned long long *securekernel_size,
+ unsigned long long *securekernel_base,
+ const char *name)
+{
+ char *sk_cmdline;
+
+ if (!securekernel_size || !securekernel_base)
+ return -EINVAL;
+
+ *securekernel_size = 0;
+ *securekernel_base = 0;
+
+ sk_cmdline = get_last_securekernel(cmdline, name);
+
+ if (!sk_cmdline)
+ return -EINVAL;
+
+ sk_cmdline += strlen(name);
+
+ return parse_securekernel_simple(sk_cmdline, securekernel_size, securekernel_base);
+}
+
+/*
+ * That function is the entry point for command line parsing and should be
+ * called from the arch-specific code.
+ */
+static int __init parse_securekernel(char *cmdline,
+ unsigned long long *securekernel_size,
+ unsigned long long *securekernel_base)
+{
+ return __parse_securekernel(cmdline, securekernel_size, securekernel_base,
+ "securekernel=");
+}
+
+static int __init hv_vsm_seckernel_mem_init(char *__unused)
+{
+ unsigned long long securekernel_size = 0, securekernel_base = 0;
+ int ret;
+
+ /* Secure Kernel memory is already reserved. Avoid duplicate reservation */
+ if (sk_res.start)
+ return 0;
+ /*
+ * Reserve Secure Kernel memory.
+ * Check command line first, if secure kernel memory was defined
+ */
+ ret = parse_securekernel(boot_command_line, &securekernel_size,
+ &securekernel_base);
+
+ if (ret != 0 || securekernel_size < SECKERNEL_MIN_SIZE) {
+ if (ret != 0)
+ pr_info("%s: securekernel cmd line not defined. Falling back to default.\n",
+ __func__);
+ else if (securekernel_size < SECKERNEL_MIN_SIZE)
+ pr_info("%s: securekernel cmd line too small. Falling back to default.\n",
+ __func__);
+
+ securekernel_size = SECKERNEL_MIN_SIZE;
+ securekernel_base = 0;
+ }
+
+ /* If securekernel_base was specified from command line,
+ * try to reserve memory starting from that address
+ */
+ if (securekernel_base) {
+ unsigned long long start, end;
+
+ end = securekernel_base + securekernel_size;
+ if (end > SECKERNEL_ADDR_MAX || end < securekernel_base) {
+ pr_warn("%s: Invalid Securekernel base address %llx. Falling back to default.\n",
+ __func__, securekernel_base);
+ securekernel_base = 0;
+ } else {
+ start = memblock_phys_alloc_range(securekernel_size, SECKERNEL_ALIGN,
+ securekernel_base,
+ securekernel_base + securekernel_size);
+ if (start != securekernel_base) {
+ pr_warn("%s: memory reservation @ %llx failed-memory is in use\n",
+ __func__, securekernel_base);
+ pr_warn("%s:Falling back to default mem allocation\n", __func__);
+ securekernel_base = 0;
+ }
+ }
+ }
+ /* Default: Find the base address automatically */
+ if (!securekernel_base) {
+ securekernel_base = memblock_phys_alloc_range(securekernel_size, SECKERNEL_ALIGN,
+ 0, SECKERNEL_ADDR_MAX);
+ if (!securekernel_base) {
+ pr_err("%s: Securekernel reservation failed-VSM will not be enabled.\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+
+ pr_info("Reserving %ldMB of memory at 0x%llx(%ld MB) for securekernel(System RAM:%ldMB)\n",
+ (unsigned long)(securekernel_size >> 20),
+ securekernel_base,
+ (unsigned long)(securekernel_base >> 20),
+ (unsigned long)(memblock_phys_mem_size() >> 20));
+
+ sk_res.start = securekernel_base;
+ sk_res.end = securekernel_base + securekernel_size - 1;
+ insert_resource(&iomem_resource, &sk_res);
+
+ return 0;
+}
+__setup("securekernel", hv_vsm_seckernel_mem_init);
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 04/12] firmware: efi: libstub: x86-stub: Enable VSM awareness in efi os indications variable
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (2 preceding siblings ...)
2026-09-01 16:55 ` [RFC PATCH 03/12] drivers: hv: Reserve memory for VSM secure kernel during early boot Thara Gopinath
@ 2026-09-01 16:55 ` Thara Gopinath
2026-09-02 1:09 ` Wei Liu
2026-09-01 16:55 ` [RFC PATCH 05/12] include: hyperv: hvgdk_mini.h: Add VTL-specific structures and bits Thara Gopinath
` (7 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
Set bit 0 of the Hyper-V private OsLoaderIndications EFI variable
during exit_boot() so the bootloader/firmware knows the OS intends
to enable VTL1. Without this, VTL1 cannot be brought up from the
Linux kernel.
The support bit is first checked in OsLoaderIndicationsSupported,
and the variable is only written when the VSM bit is not already
set.
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/firmware/efi/libstub/x86-stub.c | 57 +++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index cef32e2c82d8f..ab3cd4fe36599 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -21,6 +21,17 @@
#include "efistub.h"
#include "x86-stub.h"
+#ifdef CONFIG_HYPERV_VSM
+#define HYPERV_PRIVATE_EFI_NAMESPACE_GUID \
+ EFI_GUID(0x610b9e98, 0xc6f6, 0x47f8, 0x8b, 0x47, 0x2d, 0x2d, 0xa0, 0xd5, 0x2a, 0x91)
+
+static const efi_char16_t efi_HvPrivOsloaderIndications_name[] = L"OsLoaderIndications";
+static const efi_char16_t efi_HvPrivOsloaderIndicationsSupported_name[] =
+ L"OsLoaderIndicationsSupported";
+#define HV_OSLOADER_INDICATION_VSM BIT(0)
+
+#endif
+
extern char _bss[], _ebss[];
const efi_system_table_t *efi_system_table;
@@ -754,6 +765,47 @@ static efi_status_t exit_boot_func(struct efi_boot_memmap *map,
return EFI_SUCCESS;
}
+#ifdef CONFIG_HYPERV_VSM
+static void efi_set_hv_os_indications(void)
+{
+ efi_guid_t guid = HYPERV_PRIVATE_EFI_NAMESPACE_GUID;
+ efi_status_t status;
+ unsigned long size;
+ u32 attr, val;
+
+ size = sizeof(val);
+ status = get_efi_var(efi_HvPrivOsloaderIndicationsSupported_name,
+ &guid, &attr, &size, &val);
+ if (status != EFI_SUCCESS) {
+ efi_err("Could not read Hyper-V OsloaderIndicationsSupported\n");
+ return;
+ }
+
+ if (!(val & HV_OSLOADER_INDICATION_VSM)) {
+ efi_info("Hyper-V does not support VSM in OsloaderIndicationsSupported\n");
+ return;
+ }
+
+ size = sizeof(val);
+ status = get_efi_var(efi_HvPrivOsloaderIndications_name, &guid, &attr, &size, &val);
+ if (status != EFI_SUCCESS) {
+ efi_err("Could not read Hyper-V OsLoaderIndications\n");
+ return;
+ }
+
+ if (val & HV_OSLOADER_INDICATION_VSM) {
+ efi_info("VSM is already supported in OsLoaderIndications.");
+ return;
+ }
+
+ val |= HV_OSLOADER_INDICATION_VSM;
+ size = sizeof(val);
+ status = set_efi_var(efi_HvPrivOsloaderIndications_name, &guid, attr, size, &val);
+ if (status != EFI_SUCCESS)
+ efi_err("Could not set Hyper-V OsLoaderIndications to indicate VSM support\n");
+}
+#endif
+
static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
{
struct setup_data *e820ext = NULL;
@@ -768,6 +820,11 @@ static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
if (status != EFI_SUCCESS)
return status;
+#ifdef CONFIG_HYPERV_VSM
+ /* Indicate to bootloader that we will be enabling VTL1 before exiting boot services */
+ efi_set_hv_os_indications();
+#endif
+
/* Might as well exit boot services now */
status = efi_exit_boot_services(handle, &priv, exit_boot_func);
if (status != EFI_SUCCESS)
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 05/12] include: hyperv: hvgdk_mini.h: Add VTL-specific structures and bits
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (3 preceding siblings ...)
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-01 16:55 ` 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
` (6 subsequent siblings)
11 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
Add the definitions needed by the upcoming VSM / VTL1 bring-up code:
- HV_ACCESS_VSM and HV_ACCESS_VP_REGS partition privilege bits
- HVCALL_ENABLE_PARTITION_VTL and
HVCALL_MODIFY_VTL_PROTECTION_MASK hypercall IDs
- hv_input_enable_partition_vtl input structure and its flags union
- hv_register_vsm_{partition_status,vp_status} register layout unions
- HV_REGISTER_VSM_PARTITION_STATUS and
HV_REGISTER_VSM_VP_SECURE_CONFIG_VTL0 register names
Definitions taken from the Hyper-V TLFS.
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
include/hyperv/hvgdk_mini.h | 46 +++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h
index 6a4e8b9d570fd..a50af532fc7f9 100644
--- a/include/hyperv/hvgdk_mini.h
+++ b/include/hyperv/hvgdk_mini.h
@@ -303,6 +303,8 @@ union hv_hypervisor_version_info {
#define HV_ACCESS_STATS BIT(8)
#define HV_DEBUGGING BIT(11)
#define HV_CPU_MANAGEMENT BIT(12)
+#define HV_ACCESS_VSM BIT(16)
+#define HV_ACCESS_VP_REGS BIT(17)
#define HV_ENABLE_EXTENDED_HYPERCALLS BIT(20)
#define HV_ISOLATION BIT(22)
@@ -438,6 +440,8 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
#define HVCALL_GET_LOGICAL_PROCESSOR_RUN_TIME 0x0004
#define HVCALL_NOTIFY_LONG_SPIN_WAIT 0x0008
#define HVCALL_SEND_IPI 0x000b
+#define HVCALL_MODIFY_VTL_PROTECTION_MASK 0x000c
+#define HVCALL_ENABLE_PARTITION_VTL 0x000d
#define HVCALL_ENABLE_VP_VTL 0x000f
#define HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX 0x0013
#define HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX 0x0014
@@ -878,6 +882,46 @@ struct hv_init_vp_context {
u64 msr_cr_pat;
} __packed;
+union hv_enable_partition_vtl_flags {
+ u8 as_uint8;
+ struct {
+ u8 enable_mbec:1;
+ u8 enable_supervisor_shadow_stack:1;
+ u8 enable_hardware_hvpt:1;
+ u8 reserved:5;
+ };
+} __packed;
+
+struct hv_input_enable_partition_vtl {
+ u64 partition_id;
+ union hv_input_vtl target_vtl;
+ union hv_enable_partition_vtl_flags flags;
+ u16 rsvd_z16;
+ u32 rsvd_z32;
+} __packed;
+
+union hv_register_vsm_partition_status {
+ u64 as_uint64;
+ struct {
+ u64 enabled_vtl_set : 16;
+ u64 max_vtl : 4;
+ u64 mbec_enabled_vtl_set: 16;
+ u64 supervisor_shadow_stack_enabled_vtl_set : 4;
+ u64 reserved : 24;
+ };
+} __packed;
+
+union hv_register_vsm_vp_status {
+ u64 as_uint64;
+ struct {
+ u64 active_vtl : 4;
+ u64 active_mbec_enabled : 1;
+ u64 reserved_z0 : 11;
+ u64 enabled_vtl_set : 16;
+ u64 reserved_z1 : 32;
+ };
+} __packed;
+
struct hv_enable_vp_vtl {
u64 partition_id;
u32 vp_index;
@@ -1061,8 +1105,10 @@ enum hv_register_name {
/* Synthetic VSM registers */
HV_REGISTER_VSM_CODE_PAGE_OFFSETS = 0x000D0002,
+ HV_REGISTER_VSM_PARTITION_STATUS = 0x000D0004,
HV_REGISTER_VSM_CAPABILITIES = 0x000D0006,
HV_REGISTER_VSM_PARTITION_CONFIG = 0x000D0007,
+ HV_REGISTER_VSM_VP_SECURE_CONFIG_VTL0 = 0x000D0010,
#if defined(CONFIG_X86)
/* X64 Debug Registers */
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 06/12] drivers: hv: Add VSM boot driver and enable VTL1 at the partition level
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (4 preceding siblings ...)
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 ` Thara Gopinath
2026-09-02 1:16 ` Wei Liu
2026-09-02 4:43 ` Wei Liu
2026-09-01 16:55 ` [RFC PATCH 07/12] drivers: hv: hv_vsm_boot: load secure kernel image from firmware Thara Gopinath
` (5 subsequent siblings)
11 siblings, 2 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
Linux VBS (LVBS) uses Hyper-V's Virtual Secure Mode to run a small
trusted kernel in VTL1 alongside the regular VTL0 kernel, so that
security-sensitive state (e.g. hypervisor-enforced code integrity,
credential isolation) can live behind a higher-privilege boundary
that VTL0 compromise cannot cross. Bringing that up from Linux
requires the VTL0 kernel to drive the VSM setup itself.
Add drivers/hv/hv_vsm_boot.c as the entry point for that sequence.
This first step handles partition-level VTL1 enable only:
- Probe VSM / VP-register privileges and SynIC availability before
doing anything.
- Pin init to the VTL0 boot CPU so VTL1 comes up on the same CPU
(later patches rely on this).
- Read HV_REGISTER_VSM_PARTITION_STATUS, and if VTL1 is not already
enabled, issue HVCALL_ENABLE_PARTITION_VTL with MBEC and confirm
by re-reading the register.
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/hv/Makefile | 4 +-
drivers/hv/hv_vsm_boot.c | 178 +++++++++++++++++++++++++++++++++++++++
2 files changed, 180 insertions(+), 2 deletions(-)
create mode 100644 drivers/hv/hv_vsm_boot.c
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 880c570832381..563ebc36d2700 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -22,7 +22,7 @@ mshv_vtl-y := mshv_vtl_main.o
# Code that must be built-in
obj-$(CONFIG_HYPERV) += hv_common.o
obj-$(subst m,y,$(CONFIG_MSHV_ROOT)) += hv_proc.o
-ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL),)
+ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL)$(CONFIG_HYPERV_VSM),)
obj-y += mshv_common.o
endif
-obj-$(subst m,y,$(CONFIG_HYPERV_VSM)) += hv_vsm_securekernel.o
+obj-$(subst m,y,$(CONFIG_HYPERV_VSM)) += hv_vsm_securekernel.o hv_vsm_boot.o
diff --git a/drivers/hv/hv_vsm_boot.c b/drivers/hv/hv_vsm_boot.c
new file mode 100644
index 0000000000000..99e4dc8695837
--- /dev/null
+++ b/drivers/hv/hv_vsm_boot.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * VSM boot framework that enables VTL1, loads secure kernel
+ * and boots VTL1.
+ *
+ * Copyright (c) 2023-2025, Microsoft Corporation.
+ *
+ * Author: Thara Gopinath <tgopinath@linux.microsoft.com>
+ *
+ */
+
+#define pr_fmt(fmt) "vsm: " fmt
+
+#include <linux/hyperv.h>
+#include <linux/cpumask.h>
+#include <asm/mshyperv.h>
+#include "mshv.h"
+
+#define HV_VTL1_ENABLE_BIT BIT(1)
+
+static int hv_vsm_get_register(u32 reg_name, u64 *result)
+{
+ struct hv_register_assoc reg = {
+ .name = reg_name,
+ };
+ union hv_input_vtl input_vtl = {
+ .as_uint8 = 0,
+ };
+ int ret;
+
+ ret = hv_call_get_vp_registers(HV_VP_INDEX_SELF,
+ HV_PARTITION_ID_SELF,
+ 1, input_vtl, ®);
+ if (ret)
+ return ret;
+
+ *result = reg.value.reg64;
+ return 0;
+}
+
+static int __init hv_vsm_enable_partition_vtl(void)
+{
+ u64 status = 0;
+ unsigned long flags;
+ struct hv_input_enable_partition_vtl *hvin = NULL;
+
+ local_irq_save(flags);
+
+ hvin = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(hvin, 0, sizeof(*hvin));
+
+ hvin->partition_id = HV_PARTITION_ID_SELF;
+ hvin->target_vtl.as_uint8 = 1;
+ hvin->flags.enable_mbec = 1;
+
+ status = hv_do_hypercall(HVCALL_ENABLE_PARTITION_VTL, hvin, NULL);
+ if (hv_result(status))
+ pr_err("Enable Partition VTL failed. status=0x%x\n",
+ hv_result(status));
+
+ local_irq_restore(flags);
+
+ return hv_result(status);
+}
+
+static int __init hv_vsm_get_partition_status(u16 *enabled_vtl_set, u8 *max_vtl,
+ u16 *mbec_enabled_vtl_set)
+{
+ u64 result;
+ int ret;
+ union hv_register_vsm_partition_status vsm_partition_status = { 0 };
+
+ ret = hv_vsm_get_register(HV_REGISTER_VSM_PARTITION_STATUS, &result);
+ if (ret)
+ return ret;
+
+ vsm_partition_status = (union hv_register_vsm_partition_status)result;
+ *enabled_vtl_set = vsm_partition_status.enabled_vtl_set;
+ *max_vtl = vsm_partition_status.max_vtl;
+ *mbec_enabled_vtl_set = vsm_partition_status.mbec_enabled_vtl_set;
+ return 0;
+}
+
+static int __init hv_vsm_bootstrap_vtl(void)
+{
+ u16 partition_enabled_vtl_set = 0, partition_mbec_enabled_vtl_set = 0;
+ u8 partition_max_vtl;
+ int ret;
+
+ /* Check and enable VTL1 at the partition level */
+ ret = hv_vsm_get_partition_status(&partition_enabled_vtl_set, &partition_max_vtl,
+ &partition_mbec_enabled_vtl_set);
+ if (ret)
+ return ret;
+
+ if (partition_max_vtl < HV_VTL_SECURE) {
+ pr_err("VTL1 is not supported by the partition\n");
+ return -EINVAL;
+ }
+
+ if (partition_enabled_vtl_set & HV_VTL1_ENABLE_BIT) {
+ pr_info("Partition VTL1 is already enabled\n");
+ } else {
+ ret = hv_vsm_enable_partition_vtl();
+ if (ret) {
+ pr_err("Enabling Partition VTL1 failed with status 0x%x\n",
+ ret);
+ return -EINVAL;
+ }
+ ret = hv_vsm_get_partition_status(&partition_enabled_vtl_set, &partition_max_vtl,
+ &partition_mbec_enabled_vtl_set);
+ if (ret)
+ return ret;
+ if (!(partition_enabled_vtl_set & HV_VTL1_ENABLE_BIT)) {
+ pr_err("Tried Enabling Partition VTL 1 and still failed\n");
+ return -EINVAL;
+ }
+ if (!partition_mbec_enabled_vtl_set) {
+ pr_err("Tried Enabling Partition MBEC and failed\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static bool __init vsm_arch_has_vsm_access(void)
+{
+ if (!(ms_hyperv.features & HV_MSR_SYNIC_AVAILABLE))
+ return false;
+ if (!(ms_hyperv.priv_high & HV_ACCESS_VSM))
+ return false;
+ if (!(ms_hyperv.priv_high & HV_ACCESS_VP_REGS))
+ return false;
+ return true;
+}
+
+static int __init hv_vsm_boot_init(void)
+{
+ cpumask_var_t mask;
+ unsigned int boot_cpu;
+ int ret;
+
+ if (!vsm_arch_has_vsm_access())
+ return 0;
+
+ /*
+ * Copy the current cpu mask and pin rest of the running code to boot cpu.
+ * Important since we want boot cpu of VTL0 to be the boot cpu for VTL1.
+ * ToDo: Check if copying and restoring current->cpus_mask is enough
+ * ToDo: Verify the assumption that cpumask_first(cpu_online_mask) is
+ * the boot cpu
+ */
+ if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+ panic("Could not allocate cpumask");
+
+ cpumask_copy(mask, ¤t->cpus_mask);
+ boot_cpu = cpumask_first(cpu_online_mask);
+ set_cpus_allowed_ptr(current, cpumask_of(boot_cpu));
+
+ ret = hv_vsm_bootstrap_vtl();
+ /*
+ * At this point VTL0 has already advertised VSM support to the
+ * bootloader/firmware via the Hyper-V OsLoaderIndications EFI
+ * variable (see the x86-stub change). That signals the platform
+ * that a trusted VTL1 will be brought up. If we fail to actually
+ * set VTL1 up here, the partition is left in a state where an
+ * attacker could race to configure VTL1 themselves and gain a
+ * higher-privilege foothold than VTL0. Panic rather than continue
+ * running with that exposure.
+ */
+ if (ret)
+ panic("VTL1 boot failure caused kernel panic; consult log for more details.\n");
+
+ set_cpus_allowed_ptr(current, mask);
+ free_cpumask_var(mask);
+ return ret;
+}
+device_initcall(hv_vsm_boot_init);
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 07/12] drivers: hv: hv_vsm_boot: load secure kernel image from firmware
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (5 preceding siblings ...)
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-01 16:55 ` Thara Gopinath
2026-09-02 4:37 ` Wei Liu
2026-09-01 16:55 ` [RFC PATCH 08/12] arch: x86: hyperv: Build initial vCPU context for VTL1 secure kernel Thara Gopinath
` (4 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath, Stanislav Kinsburskii
LVBS bring-up requires loading a secure kernel image into VTL1 before
starting it. Add the VTL0-side loader that stages the image in the
memory region reserved by hv_vsm_securekernel, in preparation for the
VTL1 bring-up.
The image is a 64-bit ELF fetched via request_firmware("vsm_sk"). It
is expected to ship inside the signed UKI/initramfs so it is
authenticated end-to-end via Secure Boot before the loader consumes
it; sourcing it from an unauthenticated location would break the LVBS
trust model.
The loader validates the ELF header, stages the PT_LOAD segments into
the reserved region and records the entry point as a physical address
for use at VTL1 start time.
If VSM support has been advertised to the hypervisor but no secure
kernel region was reserved on the command line, panic: LVBS bring-up
is committed at this point and there is no safe way to continue.
Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/hv/hv_vsm.h | 17 ++++
drivers/hv/hv_vsm_boot.c | 201 ++++++++++++++++++++++++++++++++++++++-
include/hyperv/vsm.h | 21 ++++
3 files changed, 238 insertions(+), 1 deletion(-)
create mode 100644 drivers/hv/hv_vsm.h
create mode 100644 include/hyperv/vsm.h
diff --git a/drivers/hv/hv_vsm.h b/drivers/hv/hv_vsm.h
new file mode 100644
index 0000000000000..88f099f88eeb4
--- /dev/null
+++ b/drivers/hv/hv_vsm.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2023-2026, Microsoft Corporation.
+ *
+ * Author: Thara Gopinath <tgopinath@linux.microsoft.com>
+ *
+ */
+
+#ifndef _HV_VSM_H
+#define _HV_VSM_H
+
+#include <linux/ioport.h>
+#include <linux/types.h>
+
+extern struct resource sk_res;
+
+#endif /* _HV_VSM_H */
diff --git a/drivers/hv/hv_vsm_boot.c b/drivers/hv/hv_vsm_boot.c
index 99e4dc8695837..dc20f935da5b2 100644
--- a/drivers/hv/hv_vsm_boot.c
+++ b/drivers/hv/hv_vsm_boot.c
@@ -6,6 +6,7 @@
* Copyright (c) 2023-2025, Microsoft Corporation.
*
* Author: Thara Gopinath <tgopinath@linux.microsoft.com>
+ * Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
*
*/
@@ -13,10 +14,26 @@
#include <linux/hyperv.h>
#include <linux/cpumask.h>
+#include <linux/namei.h>
+#include <linux/acpi.h>
+#include <linux/firmware.h>
+#include <hyperv/vsm.h>
+#include <asm/e820/types.h>
#include <asm/mshyperv.h>
#include "mshv.h"
+#include "hv_vsm.h"
#define HV_VTL1_ENABLE_BIT BIT(1)
+/*
+ * Firmware name looked up via request_firmware() under /lib/firmware/.
+ *
+ * The secure kernel image is expected to be delivered inside the signed
+ * UKI/initramfs so that it is authenticated end-to-end via Secure Boot
+ * before request_firmware() returns it.
+ */
+#define SK_FW_NAME "vsm_sk"
+
+static void *vsm_skm_va;
static int hv_vsm_get_register(u32 reg_name, u64 *result)
{
@@ -38,6 +55,167 @@ static int hv_vsm_get_register(u32 reg_name, u64 *result)
return 0;
}
+static Elf64_Addr __init hv_vsm_elf_min_load_paddr(void *image)
+{
+ Elf64_Ehdr *ehdr = image;
+ Elf64_Phdr *phdr = image + ehdr->e_phoff;
+ Elf64_Addr paddr = U64_MAX;
+ int i;
+
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ if (phdr->p_paddr < paddr)
+ paddr = phdr->p_paddr;
+ }
+
+ return paddr;
+}
+
+static size_t __init hv_vsm_elf_binary_size(void *image)
+{
+ Elf64_Ehdr *ehdr = image;
+ Elf64_Phdr *phdr = image + ehdr->e_phoff;
+ Elf64_Addr min_paddr, max_paddr = 0;
+ int i;
+
+ min_paddr = hv_vsm_elf_min_load_paddr(image);
+ if (min_paddr == U64_MAX)
+ return 0;
+
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ max_paddr = max(max_paddr, phdr->p_paddr + phdr->p_memsz);
+ }
+
+ return max_paddr - min_paddr;
+}
+
+static int __init hv_vsm_load_elf(void *image, Elf64_Addr *sk_entry_pa)
+{
+ Elf64_Ehdr *ehdr = image;
+ Elf64_Phdr *phdr = image + ehdr->e_phoff;
+ Elf64_Addr min_paddr;
+ Elf64_Xword first_load_align = 0;
+ size_t size;
+ void *base_addr;
+ int i;
+
+ /* Find alignment of the first PT_LOAD segment. */
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ if (phdr[i].p_type == PT_LOAD) {
+ first_load_align = phdr[i].p_align;
+ break;
+ }
+ }
+ if (!first_load_align) {
+ pr_err("Secure kernel does not have loadable segments\n");
+ return -EINVAL;
+ }
+
+ /* Align the base load address up to the first PT_LOAD segment alignment */
+ base_addr = PTR_ALIGN(vsm_skm_va + first_load_align, first_load_align);
+
+ size = hv_vsm_elf_binary_size(image);
+ if (vsm_skm_va + VSM_SK_INITIAL_MAP_SIZE - base_addr < size) {
+ pr_err("secure kernel does not fit: %zu > %td\n", size,
+ vsm_skm_va + VSM_SK_INITIAL_MAP_SIZE - base_addr);
+ return -EFBIG;
+ }
+
+ pr_debug("secure kernel binary size: %#zx\n", size);
+
+ min_paddr = hv_vsm_elf_min_load_paddr(image);
+ pr_debug("secure kernel minimal paddr: %#llx\n", min_paddr);
+
+ pr_debug("loading secure kernel ELF segments:\n");
+
+ /* Validate PT_LOAD alignment first, before touching any target memory. */
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ if (phdr[i].p_type != PT_LOAD)
+ continue;
+ if (phdr[i].p_align % SZ_2M) {
+ pr_err("LOAD segment is not aligned by 2MB\n");
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ void *load_addr;
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ /*
+ * Adjust the load address by min_paddr to compensate the
+ * offset.
+ */
+ load_addr = base_addr + (phdr->p_paddr - min_paddr);
+
+ pr_debug(" p_offset: %#016llx, p_filesz: %#016llx, p_memsz: %#016llx to pa %#016llx\n",
+ phdr->p_offset, phdr->p_filesz, phdr->p_memsz,
+ virt_to_phys(load_addr));
+ memcpy(load_addr, image + phdr->p_offset, phdr->p_filesz);
+
+ if (phdr->p_memsz == phdr->p_filesz)
+ continue;
+
+ pr_debug(" zeroing %#016llx bytes at pa %#016llx\n",
+ phdr->p_memsz - phdr->p_filesz,
+ virt_to_phys(load_addr + phdr->p_filesz));
+ memset(load_addr + phdr->p_filesz, 0,
+ phdr->p_memsz - phdr->p_filesz);
+ }
+
+ *sk_entry_pa = virt_to_phys(base_addr + (ehdr->e_entry - min_paddr));
+ pr_debug("secure kernel entry pa: %#llx\n", *sk_entry_pa);
+
+ return 0;
+}
+
+static int __init hv_vsm_load_secure_kernel(Elf64_Addr *sk_entry_pa)
+{
+ const struct firmware *fw;
+ Elf64_Ehdr *ehdr;
+ int ret;
+
+ ret = request_firmware(&fw, SK_FW_NAME, NULL);
+ if (ret) {
+ pr_err("Failed to load %s firmware: %d\n", SK_FW_NAME, ret);
+ return ret;
+ }
+
+ ehdr = (Elf64_Ehdr *)fw->data;
+ if (fw->size < sizeof(*ehdr) ||
+ memcmp(ehdr->e_ident, ELFMAG, SELFMAG) ||
+ (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)) {
+ pr_err("Not a valid ELF file: %s\n", SK_FW_NAME);
+ ret = -ENOEXEC;
+ goto out_release;
+ }
+
+ if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
+ pr_err("Not a 64-bit compatible ELF file: %s\n", SK_FW_NAME);
+ ret = -ENOEXEC;
+ goto out_release;
+ }
+
+ if (!elf_check_arch(ehdr)) {
+ pr_err("Not a valid ELF file: %s\n", SK_FW_NAME);
+ ret = -ENOEXEC;
+ goto out_release;
+ }
+
+ ret = hv_vsm_load_elf((void *)fw->data, sk_entry_pa);
+
+out_release:
+ release_firmware(fw);
+ return ret;
+}
+
static int __init hv_vsm_enable_partition_vtl(void)
{
u64 status = 0;
@@ -85,6 +263,7 @@ static int __init hv_vsm_bootstrap_vtl(void)
{
u16 partition_enabled_vtl_set = 0, partition_mbec_enabled_vtl_set = 0;
u8 partition_max_vtl;
+ Elf64_Addr sk_entry_pa;
int ret;
/* Check and enable VTL1 at the partition level */
@@ -120,7 +299,25 @@ static int __init hv_vsm_bootstrap_vtl(void)
return -EINVAL;
}
}
- return 0;
+
+ return hv_vsm_load_secure_kernel(&sk_entry_pa);
+}
+
+static void __init hv_vsm_get_sk_mem(void)
+{
+ /*
+ * The reserved secure kernel region is mandatory once VSM support has
+ * been advertised. Without it we cannot load the secure kernel and
+ * bringing up VTL1 is impossible, so fail hard rather than continuing
+ * in an unusable state.
+ */
+ if (!sk_res.start)
+ panic("No memory reserved in cmdline for secure kernel");
+
+ vsm_skm_va = phys_to_virt(sk_res.start);
+
+ pr_info("secure kernel region: %#llx-%#llx (%lld MB)\n",
+ sk_res.start, sk_res.end, resource_size(&sk_res) >> 20);
}
static bool __init vsm_arch_has_vsm_access(void)
@@ -143,6 +340,8 @@ static int __init hv_vsm_boot_init(void)
if (!vsm_arch_has_vsm_access())
return 0;
+ hv_vsm_get_sk_mem();
+
/*
* Copy the current cpu mask and pin rest of the running code to boot cpu.
* Important since we want boot cpu of VTL0 to be the boot cpu for VTL1.
diff --git a/include/hyperv/vsm.h b/include/hyperv/vsm.h
new file mode 100644
index 0000000000000..51555c09413d5
--- /dev/null
+++ b/include/hyperv/vsm.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Common definitions shared by generic and arch code for enabling VTL1
+ * and the Virtual Secure Mode (VSM) framework on Microsoft Hyper-V.
+ *
+ * Copyright (c) 2025-2026, Microsoft Corporation.
+ *
+ * Author: Thara Gopinath <tgopinath@linux.microsoft.com>
+ */
+
+#ifndef _HYPERV_VSM_H
+#define _HYPERV_VSM_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
+ * this and map additional memory on its own.
+ */
+#define VSM_SK_INITIAL_MAP_SIZE (16 * 1024 * 1024)
+
+#endif /* _HYPERV_VSM_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 08/12] arch: x86: hyperv: Build initial vCPU context for VTL1 secure kernel
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (6 preceding siblings ...)
2026-09-01 16:55 ` [RFC PATCH 07/12] drivers: hv: hv_vsm_boot: load secure kernel image from firmware Thara Gopinath
@ 2026-09-01 16:55 ` Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 09/12] drivers: hv: hv_vsm_boot: Enable VTL1 on the boot processor Thara Gopinath
` (3 subsequent siblings)
11 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 09/12] drivers: hv: hv_vsm_boot: Enable VTL1 on the boot processor
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (7 preceding siblings ...)
2026-09-01 16:55 ` [RFC PATCH 08/12] arch: x86: hyperv: Build initial vCPU context for VTL1 secure kernel Thara Gopinath
@ 2026-09-01 16:55 ` Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 10/12] arch: x86: hyperv: hv_vtl_vsm: Introduce vtlcall Thara Gopinath
` (2 subsequent siblings)
11 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath, Stanislav Kinsburskii
With the partition-level VTL1 enabled and the secure kernel image loaded
into the reserved region, the next step is to enable VTL1 on the boot
virtual processor so that the hypervisor can dispatch it into the secure
kernel entry point.
Introduce two helpers in hv_vsm_boot.c:
- hv_vsm_get_vp_status() reads HV_REGISTER_VSM_VP_STATUS.
- hv_vsm_enable_vp_vtl() issues HVCALL_ENABLE_VP_VTL with an initial
vCPU context built via hv_vsm_arch_init_vp().
Extend hv_vsm_bootstrap_vtl() to enable VTL1 on the boot VP if it is
not already enabled, and re-query the status to confirm the transition.
Signed-off-by: Stanislav Kinsburskii <stanislav.kinsburskii@gmail.com>
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/hv/hv_vsm_boot.c | 73 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 71 insertions(+), 2 deletions(-)
diff --git a/drivers/hv/hv_vsm_boot.c b/drivers/hv/hv_vsm_boot.c
index dc20f935da5b2..abe82e03e1897 100644
--- a/drivers/hv/hv_vsm_boot.c
+++ b/drivers/hv/hv_vsm_boot.c
@@ -216,6 +216,47 @@ static int __init hv_vsm_load_secure_kernel(Elf64_Addr *sk_entry_pa)
return ret;
}
+static int __init hv_vsm_enable_vp_vtl(Elf64_Addr sk_entry_pa)
+{
+ u64 status = 0;
+ unsigned long flags;
+ struct hv_enable_vp_vtl *hvin;
+
+ local_irq_save(flags);
+
+ hvin = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(hvin, 0, sizeof(*hvin));
+
+ hvin->partition_id = HV_PARTITION_ID_SELF;
+ hvin->vp_index = HV_VP_INDEX_SELF;
+ hvin->target_vtl.target_vtl = HV_VTL_SECURE;
+
+ hv_vsm_arch_init_vp(&hvin->vp_context, sk_entry_pa, sk_res.start);
+
+ status = hv_do_hypercall(HVCALL_ENABLE_VP_VTL, hvin, NULL);
+
+ local_irq_restore(flags);
+
+ return hv_result(status);
+}
+
+static int __init hv_vsm_get_vp_status(u16 *enabled_vtl_set, u8 *active_mbec_enabled)
+{
+ u64 result;
+ int ret;
+ union hv_register_vsm_vp_status vsm_vp_status = { 0 };
+
+ ret = hv_vsm_get_register(HV_REGISTER_VSM_VP_STATUS, &result);
+ if (ret)
+ return ret;
+
+ vsm_vp_status = (union hv_register_vsm_vp_status)result;
+ *enabled_vtl_set = vsm_vp_status.enabled_vtl_set;
+ *active_mbec_enabled = vsm_vp_status.active_mbec_enabled;
+
+ return 0;
+}
+
static int __init hv_vsm_enable_partition_vtl(void)
{
u64 status = 0;
@@ -262,7 +303,8 @@ static int __init hv_vsm_get_partition_status(u16 *enabled_vtl_set, u8 *max_vtl,
static int __init hv_vsm_bootstrap_vtl(void)
{
u16 partition_enabled_vtl_set = 0, partition_mbec_enabled_vtl_set = 0;
- u8 partition_max_vtl;
+ u16 vp_enabled_vtl_set = 0;
+ u8 partition_max_vtl, active_mbec_enabled = 0;
Elf64_Addr sk_entry_pa;
int ret;
@@ -300,7 +342,34 @@ static int __init hv_vsm_bootstrap_vtl(void)
}
}
- return hv_vsm_load_secure_kernel(&sk_entry_pa);
+ ret = hv_vsm_load_secure_kernel(&sk_entry_pa);
+ if (ret)
+ return ret;
+
+ /* Check and enable VTL1 for the primary virtual processor */
+ ret = hv_vsm_get_vp_status(&vp_enabled_vtl_set, &active_mbec_enabled);
+ if (ret)
+ return ret;
+
+ if (vp_enabled_vtl_set & HV_VTL1_ENABLE_BIT) {
+ pr_info("VP VTL1 is already enabled\n");
+ } else {
+ ret = hv_vsm_enable_vp_vtl(sk_entry_pa);
+ if (ret) {
+ pr_err("Enabling VP VTL1 failed with status 0x%x\n", ret);
+ /* TODO: Should we disable VTL1 at partition level in this case? */
+ return -EINVAL;
+ }
+ ret = hv_vsm_get_vp_status(&vp_enabled_vtl_set, &active_mbec_enabled);
+ if (ret)
+ return ret;
+
+ if (!(vp_enabled_vtl_set & HV_VTL1_ENABLE_BIT)) {
+ pr_err("Tried Enabling VP VTL1 and still failed\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
}
static void __init hv_vsm_get_sk_mem(void)
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 10/12] arch: x86: hyperv: hv_vtl_vsm: Introduce vtlcall
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (8 preceding siblings ...)
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 ` 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
11 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
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 <tgopinath@linux.microsoft.com>
---
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
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 11/12] drivers: hv: hv_vsm_boot: Boot primary processor in VTL1
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (9 preceding siblings ...)
2026-09-01 16:55 ` [RFC PATCH 10/12] arch: x86: hyperv: hv_vtl_vsm: Introduce vtlcall Thara Gopinath
@ 2026-09-01 16:55 ` Thara Gopinath
2026-09-01 16:55 ` [RFC PATCH 12/12] drivers: hv: hv_vsm_boot: Boot secondary processors " Thara Gopinath
11 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
With partition-level VTL1 enabled, the secure kernel image loaded,
its initial vCPU context built, and VTL1 enabled on the boot CPU,
the final step of the primary-CPU bring-up is to actually transition
into VTL1 and start executing the secure kernel. Hyper-V restores
the CPU state from the vp_context supplied during VTL1 enablement
when the first vtlcall is issued.
Issue that first vtlcall on the primary CPU, passing the number of
possible CPUs and the location of the secure kernel reserved region
as the VTL1 boot handshake.
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/hv/hv_vsm_boot.c | 63 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 62 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/hv_vsm_boot.c b/drivers/hv/hv_vsm_boot.c
index abe82e03e1897..c4f15c42df1f6 100644
--- a/drivers/hv/hv_vsm_boot.c
+++ b/drivers/hv/hv_vsm_boot.c
@@ -35,6 +35,15 @@
static void *vsm_skm_va;
+/*
+ * By default, when a processor boots in VTL1, we assume that MBEC (Mode-Based Execution Control)
+ * support is also enabled. MBEC distinguishes between user and kernel memory execution permissions.
+ * After the processor boots in VTL1, we verify whether MBEC is actually enabled. If it is not,
+ * we set a global flag to false. This flag is shared across all processors—if any processor fails
+ * to enable MBEC, the system treats MBEC as disabled.
+ */
+static bool hv_vsm_mbec_enabled = true;
+
static int hv_vsm_get_register(u32 reg_name, u64 *result)
{
struct hv_register_assoc reg = {
@@ -300,6 +309,51 @@ static int __init hv_vsm_get_partition_status(u16 *enabled_vtl_set, u8 *max_vtl,
return 0;
}
+static int __init hv_vsm_init_code_page_offsets(void)
+{
+ union hv_register_vsm_page_offsets offsets;
+ u64 result;
+ int ret;
+
+ ret = hv_vsm_get_register(HV_REGISTER_VSM_CODE_PAGE_OFFSETS, &result);
+ if (ret) {
+ pr_err("Failed to read VSM code page offsets: %d\n", ret);
+ return ret;
+ }
+
+ offsets.as_uint64 = result;
+ hv_vsm_init_vtlcall(offsets.vtl_call_offset);
+ return 0;
+}
+
+static int __init hv_vsm_boot_vtl1(void)
+{
+ struct hv_vtlcall_param args = {0};
+ u16 vp_enabled_vtl_set = 0;
+ u8 active_mbec_enabled = 0;
+ int ret;
+ s64 sk_status;
+
+ args.a0 = num_possible_cpus();
+ args.a1 = sk_res.start;
+ args.a2 = resource_size(&sk_res);
+
+ /* Kick start vtl1 boot on the primary cpu. */
+ sk_status = hv_vsm_vtlcall(&args);
+ if (sk_status)
+ pr_warn("VTL1 boot returned status %lld\n", sk_status);
+
+ ret = hv_vsm_get_vp_status(&vp_enabled_vtl_set, &active_mbec_enabled);
+ if (ret)
+ return ret;
+
+ if (!active_mbec_enabled) {
+ pr_err("Failed to enable MBEC for VP0\n");
+ hv_vsm_mbec_enabled = false;
+ }
+ return 0;
+}
+
static int __init hv_vsm_bootstrap_vtl(void)
{
u16 partition_enabled_vtl_set = 0, partition_mbec_enabled_vtl_set = 0;
@@ -369,7 +423,14 @@ static int __init hv_vsm_bootstrap_vtl(void)
return -EINVAL;
}
}
- return 0;
+
+ /* Point the vtlcall trampoline at the correct hypercall page offset */
+ ret = hv_vsm_init_code_page_offsets();
+ if (ret)
+ return ret;
+
+ /* Boot primary virtual processor in VTL1 */
+ return hv_vsm_boot_vtl1();
}
static void __init hv_vsm_get_sk_mem(void)
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [RFC PATCH 12/12] drivers: hv: hv_vsm_boot: Boot secondary processors in VTL1
2026-09-01 16:55 [RFC PATCH 00/12] Introduce LVBS support for Hyper-V guests Thara Gopinath
` (10 preceding siblings ...)
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 ` Thara Gopinath
11 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-01 16:55 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas
Cc: James.Bottomley, "longli, javierm, lszubowi, francescopompo2,
tgopinath, x86, linux-hyperv, linux-kernel, linux-efi,
Thara Gopinath
The next step after the primary CPU is running in VTL1 is to boot
the remaining CPUs in VTL1, so the secure kernel executes on every
VP rather than only VP0. Hyper-V does not drive this: VTL1 needs to
know which APs to expect, and each AP must issue its own vtlcall
from VTL0 to actually transition.
As on the boot CPU, VTL1 must be enabled at each VP and its initial
VP context (RIP, GDT, page tables, etc.) built before that VP can
enter VTL1. This can only be done from VTL1 for non boot CPus.
Introduce VSM_VTL_CALL_FUNC_ID_BOOT_APS, a vtlcall the boot cpu issues
from VTL0, which asks the secure kernel to enable VTL1 and set up the
initial VP context for each AP in the supplied online-CPU mask.
The mask is handed to VTL1 via a shared page whose PFN is passed in
the vtlcall arguments; VTL1 copies it synchronously so the page can be
freed as soon as the call returns.
Once VTL1 has prepared the APs, bring them into VTL1 one at a time
using a CPU-bound FIFO kthread and a completion. Serialising this
way keeps VTL1 entries ordered and lets a per-AP failure be
surfaced synchronously to the caller. Uniprocessor systems skip AP
bring-up entirely.
Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
---
drivers/hv/hv_vsm.h | 2 +
drivers/hv/hv_vsm_boot.c | 125 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 126 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/hv_vsm.h b/drivers/hv/hv_vsm.h
index 88f099f88eeb4..679cb9dc9afda 100644
--- a/drivers/hv/hv_vsm.h
+++ b/drivers/hv/hv_vsm.h
@@ -12,6 +12,8 @@
#include <linux/ioport.h>
#include <linux/types.h>
+#define VSM_VTL_CALL_FUNC_ID_BOOT_APS 0x1FFE1
+
extern struct resource sk_res;
#endif /* _HV_VSM_H */
diff --git a/drivers/hv/hv_vsm_boot.c b/drivers/hv/hv_vsm_boot.c
index c4f15c42df1f6..ad161b2f56653 100644
--- a/drivers/hv/hv_vsm_boot.c
+++ b/drivers/hv/hv_vsm_boot.c
@@ -17,6 +17,7 @@
#include <linux/namei.h>
#include <linux/acpi.h>
#include <linux/firmware.h>
+#include <linux/kthread.h>
#include <hyperv/vsm.h>
#include <asm/e820/types.h>
#include <asm/mshyperv.h>
@@ -64,6 +65,20 @@ static int hv_vsm_get_register(u32 reg_name, u64 *result)
return 0;
}
+static __init struct page *hv_vsm_alloc_shared_page(void)
+{
+ struct page *page;
+
+ page = alloc_page(GFP_KERNEL);
+ if (!page) {
+ pr_err("Unable to establish VTL0-VTL1 shared page\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ memset(page_address(page), 0, PAGE_SIZE);
+ return page;
+}
+
static Elf64_Addr __init hv_vsm_elf_min_load_paddr(void *image)
{
Elf64_Ehdr *ehdr = image;
@@ -326,6 +341,107 @@ static int __init hv_vsm_init_code_page_offsets(void)
return 0;
}
+struct hv_vsm_ap_boot_ctx {
+ struct completion done;
+ int ret;
+};
+
+static int __init hv_vsm_boot_sec_vp_thread_fn(void *arg)
+{
+ struct hv_vsm_ap_boot_ctx *ctx = arg;
+ struct hv_vtlcall_param args = {0};
+ int cpu = smp_processor_id();
+ u16 vp_enabled_vtl_set = 0;
+ u8 active_mbec_enabled = 0;
+ s64 sk_status;
+ int ret = 0;
+
+ pr_info("cpu%d entering vtl1 boot thread\n", cpu);
+ sk_status = hv_vsm_vtlcall(&args);
+ if (sk_status)
+ pr_warn("VP%d VTL1 boot returned status %lld\n", cpu, sk_status);
+
+ ret = hv_vsm_get_vp_status(&vp_enabled_vtl_set, &active_mbec_enabled);
+ if (ret)
+ goto out;
+
+ if (!active_mbec_enabled) {
+ pr_err("Failed to enable MBEC for VP%d\n", cpu);
+ hv_vsm_mbec_enabled = false;
+ }
+out:
+ ctx->ret = ret;
+ complete(&ctx->done);
+ return 0;
+}
+
+static int __init hv_vsm_boot_one_ap(unsigned int cpu)
+{
+ struct hv_vsm_ap_boot_ctx ctx;
+ struct task_struct *t;
+
+ init_completion(&ctx.done);
+ ctx.ret = 0;
+
+ t = kthread_create(hv_vsm_boot_sec_vp_thread_fn, &ctx,
+ "hv-vtl1-ap%u", cpu);
+ if (IS_ERR(t))
+ return PTR_ERR(t);
+
+ kthread_bind(t, cpu);
+ sched_set_fifo(t);
+ wake_up_process(t);
+
+ wait_for_completion(&ctx.done);
+ return ctx.ret;
+}
+
+static int __init hv_vsm_boot_ap_vtl(void)
+{
+ struct hv_vtlcall_param args = {0};
+ struct page *cpu_online_page;
+ unsigned int cpu, cur_cpu = smp_processor_id();
+ s64 sk_status;
+ int ret;
+
+ cpu_online_page = hv_vsm_alloc_shared_page();
+ if (IS_ERR(cpu_online_page))
+ return PTR_ERR(cpu_online_page);
+
+ cpumask_copy(page_address(cpu_online_page), cpu_online_mask);
+
+ /*
+ * Hand VTL1 the set of APs to expect. VTL1 copies the mask
+ * synchronously inside this vtlcall and does not reference the
+ * page after it returns, so freeing it here is safe.
+ */
+ args.a0 = VSM_VTL_CALL_FUNC_ID_BOOT_APS;
+ args.a1 = page_to_pfn(cpu_online_page);
+ sk_status = hv_vsm_vtlcall(&args);
+ __free_page(cpu_online_page);
+ if (sk_status) {
+ pr_err("VTL1 refused BOOT_APS: status %lld\n", sk_status);
+ return -EIO;
+ }
+
+ /*
+ * Bring the APs into VTL1 one at a time. Each AP kthread issues
+ * a single vtlcall on its bound CPU and signals completion; wait
+ * for it to finish before starting the next so VTL1 entries stay
+ * serialized.
+ */
+ for_each_online_cpu(cpu) {
+ if (cpu == cur_cpu)
+ continue;
+ ret = hv_vsm_boot_one_ap(cpu);
+ if (ret) {
+ pr_err("Failed to boot VP%u into VTL1: %d\n", cpu, ret);
+ return ret;
+ }
+ }
+ return 0;
+}
+
static int __init hv_vsm_boot_vtl1(void)
{
struct hv_vtlcall_param args = {0};
@@ -430,7 +546,14 @@ static int __init hv_vsm_bootstrap_vtl(void)
return ret;
/* Boot primary virtual processor in VTL1 */
- return hv_vsm_boot_vtl1();
+ ret = hv_vsm_boot_vtl1();
+ if (ret)
+ return ret;
+
+ if (num_present_cpus() == 1)
+ return 0;
+
+ return hv_vsm_boot_ap_vtl();
}
static void __init hv_vsm_get_sk_mem(void)
--
2.34.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 03/12] drivers: hv: Reserve memory for VSM secure kernel during early boot
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
0 siblings, 1 reply; 24+ messages in thread
From: Wei Liu @ 2026-09-02 0:59 UTC (permalink / raw)
To: Thara Gopinath
Cc: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi, Anna Trikalinou
On Tue, Sep 01, 2026 at 09:55:17AM -0700, Thara Gopinath wrote:
> The VSM secure kernel runs in VTL1 and cannot use the normal Linux
> kernel memory allocators — its memory must be reserved before the
> general-purpose allocator takes ownership of the physical address space.
>
> Add hv_vsm_securekernel.c to handle this early boot reservation. The
> driver parses a new "securekernel=<size>[K|M|G][@<addr>]" kernel command
We should add a prefix to it, unless you tell me there is a plan to
extend this to other virtualization solutions.
Wei
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 04/12] firmware: efi: libstub: x86-stub: Enable VSM awareness in efi os indications variable
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
0 siblings, 1 reply; 24+ messages in thread
From: Wei Liu @ 2026-09-02 1:09 UTC (permalink / raw)
To: Thara Gopinath
Cc: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi
On Tue, Sep 01, 2026 at 09:55:18AM -0700, Thara Gopinath wrote:
> Set bit 0 of the Hyper-V private OsLoaderIndications EFI variable
> during exit_boot() so the bootloader/firmware knows the OS intends
> to enable VTL1. Without this, VTL1 cannot be brought up from the
> Linux kernel.
>
> The support bit is first checked in OsLoaderIndicationsSupported,
> and the variable is only written when the VSM bit is not already
> set.
>
> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
> ---
> drivers/firmware/efi/libstub/x86-stub.c | 57 +++++++++++++++++++++++++
> 1 file changed, 57 insertions(+)
>
[...]
> +#ifdef CONFIG_HYPERV_VSM
> +static void efi_set_hv_os_indications(void)
> +{
> + efi_guid_t guid = HYPERV_PRIVATE_EFI_NAMESPACE_GUID;
> + efi_status_t status;
> + unsigned long size;
> + u32 attr, val;
> +
> + size = sizeof(val);
> + status = get_efi_var(efi_HvPrivOsloaderIndicationsSupported_name,
> + &guid, &attr, &size, &val);
> + if (status != EFI_SUCCESS) {
> + efi_err("Could not read Hyper-V OsloaderIndicationsSupported\n");
> + return;
> + }
> +
> + if (!(val & HV_OSLOADER_INDICATION_VSM)) {
> + efi_info("Hyper-V does not support VSM in OsloaderIndicationsSupported\n");
> + return;
> + }
> +
> + size = sizeof(val);
> + status = get_efi_var(efi_HvPrivOsloaderIndications_name, &guid, &attr, &size, &val);
> + if (status != EFI_SUCCESS) {
> + efi_err("Could not read Hyper-V OsLoaderIndications\n");
> + return;
> + }
> +
> + if (val & HV_OSLOADER_INDICATION_VSM) {
> + efi_info("VSM is already supported in OsLoaderIndications.");
> + return;
> + }
> +
> + val |= HV_OSLOADER_INDICATION_VSM;
> + size = sizeof(val);
> + status = set_efi_var(efi_HvPrivOsloaderIndications_name, &guid, attr, size, &val);
I'm not familiar with the security model, so bear with me.
What happens if the VTL0 kernel doesn't use VTL1 at all? Does that
become a security issue, that malware can use the VTL1 to hide itself?
Asking this because I think you will want to enable this in the generic
kernel(s). Not all users have or want to package a secure kernel.
Wei
> + if (status != EFI_SUCCESS)
> + efi_err("Could not set Hyper-V OsLoaderIndications to indicate VSM support\n");
> +}
> +#endif
> +
> static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
> {
> struct setup_data *e820ext = NULL;
> @@ -768,6 +820,11 @@ static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
> if (status != EFI_SUCCESS)
> return status;
>
> +#ifdef CONFIG_HYPERV_VSM
> + /* Indicate to bootloader that we will be enabling VTL1 before exiting boot services */
> + efi_set_hv_os_indications();
> +#endif
> +
> /* Might as well exit boot services now */
> status = efi_exit_boot_services(handle, &priv, exit_boot_func);
> if (status != EFI_SUCCESS)
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 06/12] drivers: hv: Add VSM boot driver and enable VTL1 at the partition level
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
1 sibling, 1 reply; 24+ messages in thread
From: Wei Liu @ 2026-09-02 1:16 UTC (permalink / raw)
To: Thara Gopinath
Cc: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi
On Tue, Sep 01, 2026 at 09:55:20AM -0700, Thara Gopinath wrote:
> Linux VBS (LVBS) uses Hyper-V's Virtual Secure Mode to run a small
> trusted kernel in VTL1 alongside the regular VTL0 kernel, so that
> security-sensitive state (e.g. hypervisor-enforced code integrity,
> credential isolation) can live behind a higher-privilege boundary
> that VTL0 compromise cannot cross. Bringing that up from Linux
> requires the VTL0 kernel to drive the VSM setup itself.
>
> Add drivers/hv/hv_vsm_boot.c as the entry point for that sequence.
> This first step handles partition-level VTL1 enable only:
>
> - Probe VSM / VP-register privileges and SynIC availability before
> doing anything.
> - Pin init to the VTL0 boot CPU so VTL1 comes up on the same CPU
> (later patches rely on this).
> - Read HV_REGISTER_VSM_PARTITION_STATUS, and if VTL1 is not already
> enabled, issue HVCALL_ENABLE_PARTITION_VTL with MBEC and confirm
> by re-reading the register.
>
> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
> ---
[...]
> +static int __init hv_vsm_boot_init(void)
> +{
> + cpumask_var_t mask;
> + unsigned int boot_cpu;
> + int ret;
> +
> + if (!vsm_arch_has_vsm_access())
> + return 0;
> +
> + /*
> + * Copy the current cpu mask and pin rest of the running code to boot cpu.
> + * Important since we want boot cpu of VTL0 to be the boot cpu for VTL1.
> + * ToDo: Check if copying and restoring current->cpus_mask is enough
> + * ToDo: Verify the assumption that cpumask_first(cpu_online_mask) is
> + * the boot cpu
Can we get closures on these todos?
I see a bunch of todos in other patches, too. It would be good to close
them as well.
Wei
> + */
> + if (!alloc_cpumask_var(&mask, GFP_KERNEL))
> + panic("Could not allocate cpumask");
> +
> + cpumask_copy(mask, ¤t->cpus_mask);
> + boot_cpu = cpumask_first(cpu_online_mask);
> + set_cpus_allowed_ptr(current, cpumask_of(boot_cpu));
> +
> + ret = hv_vsm_bootstrap_vtl();
> + /*
> + * At this point VTL0 has already advertised VSM support to the
> + * bootloader/firmware via the Hyper-V OsLoaderIndications EFI
> + * variable (see the x86-stub change). That signals the platform
> + * that a trusted VTL1 will be brought up. If we fail to actually
> + * set VTL1 up here, the partition is left in a state where an
> + * attacker could race to configure VTL1 themselves and gain a
> + * higher-privilege foothold than VTL0. Panic rather than continue
> + * running with that exposure.
> + */
> + if (ret)
> + panic("VTL1 boot failure caused kernel panic; consult log for more details.\n");
> +
> + set_cpus_allowed_ptr(current, mask);
> + free_cpumask_var(mask);
> + return ret;
> +}
> +device_initcall(hv_vsm_boot_init);
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 07/12] drivers: hv: hv_vsm_boot: load secure kernel image from firmware
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
0 siblings, 1 reply; 24+ messages in thread
From: Wei Liu @ 2026-09-02 4:37 UTC (permalink / raw)
To: Thara Gopinath
Cc: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi, Stanislav Kinsburskii
On Tue, Sep 01, 2026 at 09:55:21AM -0700, Thara Gopinath wrote:
> LVBS bring-up requires loading a secure kernel image into VTL1 before
> starting it. Add the VTL0-side loader that stages the image in the
> memory region reserved by hv_vsm_securekernel, in preparation for the
> VTL1 bring-up.
>
> The image is a 64-bit ELF fetched via request_firmware("vsm_sk"). It
> is expected to ship inside the signed UKI/initramfs so it is
> authenticated end-to-end via Secure Boot before the loader consumes
> it; sourcing it from an unauthenticated location would break the LVBS
> trust model.
>
> The loader validates the ELF header, stages the PT_LOAD segments into
> the reserved region and records the entry point as a physical address
> for use at VTL1 start time.
>
> If VSM support has been advertised to the hypervisor but no secure
> kernel region was reserved on the command line, panic: LVBS bring-up
> is committed at this point and there is no safe way to continue.
This conflicts with the memory reservation patch, in which there is an
automatic allocation when no kernel command line is specified.
>
> Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
> ---
> drivers/hv/hv_vsm.h | 17 ++++
> drivers/hv/hv_vsm_boot.c | 201 ++++++++++++++++++++++++++++++++++++++-
> include/hyperv/vsm.h | 21 ++++
> 3 files changed, 238 insertions(+), 1 deletion(-)
> create mode 100644 drivers/hv/hv_vsm.h
> create mode 100644 include/hyperv/vsm.h
[...]
> +
> +static void __init hv_vsm_get_sk_mem(void)
> +{
> + /*
> + * The reserved secure kernel region is mandatory once VSM support has
> + * been advertised. Without it we cannot load the secure kernel and
> + * bringing up VTL1 is impossible, so fail hard rather than continuing
> + * in an unusable state.
> + */
> + if (!sk_res.start)
> + panic("No memory reserved in cmdline for secure kernel");
> +
This log line is wrong.
Wei
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 06/12] drivers: hv: Add VSM boot driver and enable VTL1 at the partition level
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 4:43 ` Wei Liu
2026-09-04 13:23 ` Thara Gopinath
1 sibling, 1 reply; 24+ messages in thread
From: Wei Liu @ 2026-09-02 4:43 UTC (permalink / raw)
To: Thara Gopinath
Cc: kys, haiyangz, wei.liu, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi
On Tue, Sep 01, 2026 at 09:55:20AM -0700, Thara Gopinath wrote:
> Linux VBS (LVBS) uses Hyper-V's Virtual Secure Mode to run a small
> trusted kernel in VTL1 alongside the regular VTL0 kernel, so that
> security-sensitive state (e.g. hypervisor-enforced code integrity,
> credential isolation) can live behind a higher-privilege boundary
> that VTL0 compromise cannot cross. Bringing that up from Linux
> requires the VTL0 kernel to drive the VSM setup itself.
>
> Add drivers/hv/hv_vsm_boot.c as the entry point for that sequence.
> This first step handles partition-level VTL1 enable only:
>
> - Probe VSM / VP-register privileges and SynIC availability before
> doing anything.
> - Pin init to the VTL0 boot CPU so VTL1 comes up on the same CPU
> (later patches rely on this).
> - Read HV_REGISTER_VSM_PARTITION_STATUS, and if VTL1 is not already
> enabled, issue HVCALL_ENABLE_PARTITION_VTL with MBEC and confirm
> by re-reading the register.
>
> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
> ---
[...]
> + cpumask_copy(mask, ¤t->cpus_mask);
> + boot_cpu = cpumask_first(cpu_online_mask);
> + set_cpus_allowed_ptr(current, cpumask_of(boot_cpu));
> +
> + ret = hv_vsm_bootstrap_vtl();
> + /*
> + * At this point VTL0 has already advertised VSM support to the
> + * bootloader/firmware via the Hyper-V OsLoaderIndications EFI
> + * variable (see the x86-stub change). That signals the platform
> + * that a trusted VTL1 will be brought up. If we fail to actually
> + * set VTL1 up here, the partition is left in a state where an
> + * attacker could race to configure VTL1 themselves and gain a
> + * higher-privilege foothold than VTL0. Panic rather than continue
> + * running with that exposure.
> + */
Okay, I think this answers my question in the previous patch -- if VTL1
is not used by us, that's a security problem.
It is unclear to me, if this code is enabled, how a generic kernel can
work without a secure kernel. There should be a way to configure the
system such that not enabling VTL1 is okay. Is there any pre-EFI command
line parsing we can do?
Wei
> + if (ret)
> + panic("VTL1 boot failure caused kernel panic; consult log for more details.\n");
> +
> + set_cpus_allowed_ptr(current, mask);
> + free_cpumask_var(mask);
> + return ret;
> +}
> +device_initcall(hv_vsm_boot_init);
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 03/12] drivers: hv: Reserve memory for VSM secure kernel during early boot
2026-09-02 0:59 ` Wei Liu
@ 2026-09-02 13:38 ` Thara Gopinath
0 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-02 13:38 UTC (permalink / raw)
To: Wei Liu
Cc: kys, haiyangz, decui, tglx, mingo, bp, dave.hansen, hpa, ardb,
ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi, Anna Trikalinou
On 9/1/2026 8:59 PM, Wei Liu wrote:
> On Tue, Sep 01, 2026 at 09:55:17AM -0700, Thara Gopinath wrote:
>> The VSM secure kernel runs in VTL1 and cannot use the normal Linux
>> kernel memory allocators — its memory must be reserved before the
>> general-purpose allocator takes ownership of the physical address space.
>>
>> Add hv_vsm_securekernel.c to handle this early boot reservation. The
>> driver parses a new "securekernel=<size>[K|M|G][@<addr>]" kernel command
>
> We should add a prefix to it, unless you tell me there is a plan to
> extend this to other virtualization solutions.
Thanks for the reviews Wei.. Yes I will add a prefix hv_ and we can
remove it if/when this gets extended to other virtualization solutions.
Warm Regards
Thara
>
> Wei
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 04/12] firmware: efi: libstub: x86-stub: Enable VSM awareness in efi os indications variable
2026-09-02 1:09 ` Wei Liu
@ 2026-09-02 14:23 ` Thara Gopinath
0 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-02 14:23 UTC (permalink / raw)
To: Wei Liu
Cc: kys, haiyangz, decui, tglx, mingo, bp, dave.hansen, hpa, ardb,
ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi
On 9/1/2026 9:09 PM, Wei Liu wrote:
> On Tue, Sep 01, 2026 at 09:55:18AM -0700, Thara Gopinath wrote:
>> Set bit 0 of the Hyper-V private OsLoaderIndications EFI variable
>> during exit_boot() so the bootloader/firmware knows the OS intends
>> to enable VTL1. Without this, VTL1 cannot be brought up from the
>> Linux kernel.
>>
>> The support bit is first checked in OsLoaderIndicationsSupported,
>> and the variable is only written when the VSM bit is not already
>> set.
>>
>> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
>> ---
>> drivers/firmware/efi/libstub/x86-stub.c | 57 +++++++++++++++++++++++++
>> 1 file changed, 57 insertions(+)
>>
> [...]
>> +#ifdef CONFIG_HYPERV_VSM
>> +static void efi_set_hv_os_indications(void)
>> +{
>> + efi_guid_t guid = HYPERV_PRIVATE_EFI_NAMESPACE_GUID;
>> + efi_status_t status;
>> + unsigned long size;
>> + u32 attr, val;
>> +
>> + size = sizeof(val);
>> + status = get_efi_var(efi_HvPrivOsloaderIndicationsSupported_name,
>> + &guid, &attr, &size, &val);
>> + if (status != EFI_SUCCESS) {
>> + efi_err("Could not read Hyper-V OsloaderIndicationsSupported\n");
>> + return;
>> + }
>> +
>> + if (!(val & HV_OSLOADER_INDICATION_VSM)) {
>> + efi_info("Hyper-V does not support VSM in OsloaderIndicationsSupported\n");
>> + return;
>> + }
>> +
>> + size = sizeof(val);
>> + status = get_efi_var(efi_HvPrivOsloaderIndications_name, &guid, &attr, &size, &val);
>> + if (status != EFI_SUCCESS) {
>> + efi_err("Could not read Hyper-V OsLoaderIndications\n");
>> + return;
>> + }
>> +
>> + if (val & HV_OSLOADER_INDICATION_VSM) {
>> + efi_info("VSM is already supported in OsLoaderIndications.");
>> + return;
>> + }
>> +
>> + val |= HV_OSLOADER_INDICATION_VSM;
>> + size = sizeof(val);
>> + status = set_efi_var(efi_HvPrivOsloaderIndications_name, &guid, attr, size, &val);
>
> I'm not familiar with the security model, so bear with me.
>
> What happens if the VTL0 kernel doesn't use VTL1 at all? Does that
> become a security issue, that malware can use the VTL1 to hide itself?
>
> Asking this because I think you will want to enable this in the generic
> kernel(s). Not all users have or want to package a secure kernel.
Yes you are right. If we do this and a secure kernel is not loaded, it is a
security hole. Which is why this is bound by the same config option CONFIG_HYPERV_VSM
that does the secure kernel boot and in that path any error / inability to load
and establish VTL1 is treated as a serious error and we panic. Generic kernels
should not enable this option at all. The CONFIG_HYPERV_VSM should be enabled
only if it is known that VTL1 environment can be established. Otherwise the system
will not boot and will panic.
Warm Regards
Thara
>
> Wei
>
>> + if (status != EFI_SUCCESS)
>> + efi_err("Could not set Hyper-V OsLoaderIndications to indicate VSM support\n");
>> +}
>> +#endif
>> +
>> static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
>> {
>> struct setup_data *e820ext = NULL;
>> @@ -768,6 +820,11 @@ static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
>> if (status != EFI_SUCCESS)
>> return status;
>>
>> +#ifdef CONFIG_HYPERV_VSM
>> + /* Indicate to bootloader that we will be enabling VTL1 before exiting boot services */
>> + efi_set_hv_os_indications();
>> +#endif
>> +
>> /* Might as well exit boot services now */
>> status = efi_exit_boot_services(handle, &priv, exit_boot_func);
>> if (status != EFI_SUCCESS)
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 06/12] drivers: hv: Add VSM boot driver and enable VTL1 at the partition level
2026-09-02 1:16 ` Wei Liu
@ 2026-09-02 14:28 ` Thara Gopinath
0 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-02 14:28 UTC (permalink / raw)
To: Wei Liu
Cc: kys, haiyangz, decui, tglx, mingo, bp, dave.hansen, hpa, ardb,
ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi
On 9/1/2026 9:16 PM, Wei Liu wrote:
> On Tue, Sep 01, 2026 at 09:55:20AM -0700, Thara Gopinath wrote:
>> Linux VBS (LVBS) uses Hyper-V's Virtual Secure Mode to run a small
>> trusted kernel in VTL1 alongside the regular VTL0 kernel, so that
>> security-sensitive state (e.g. hypervisor-enforced code integrity,
>> credential isolation) can live behind a higher-privilege boundary
>> that VTL0 compromise cannot cross. Bringing that up from Linux
>> requires the VTL0 kernel to drive the VSM setup itself.
>>
>> Add drivers/hv/hv_vsm_boot.c as the entry point for that sequence.
>> This first step handles partition-level VTL1 enable only:
>>
>> - Probe VSM / VP-register privileges and SynIC availability before
>> doing anything.
>> - Pin init to the VTL0 boot CPU so VTL1 comes up on the same CPU
>> (later patches rely on this).
>> - Read HV_REGISTER_VSM_PARTITION_STATUS, and if VTL1 is not already
>> enabled, issue HVCALL_ENABLE_PARTITION_VTL with MBEC and confirm
>> by re-reading the register.
>>
>> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
>> ---
> [...]
>> +static int __init hv_vsm_boot_init(void)
>> +{
>> + cpumask_var_t mask;
>> + unsigned int boot_cpu;
>> + int ret;
>> +
>> + if (!vsm_arch_has_vsm_access())
>> + return 0;
>> +
>> + /*
>> + * Copy the current cpu mask and pin rest of the running code to boot cpu.
>> + * Important since we want boot cpu of VTL0 to be the boot cpu for VTL1.
>> + * ToDo: Check if copying and restoring current->cpus_mask is enough
>> + * ToDo: Verify the assumption that cpumask_first(cpu_online_mask) is
>> + * the boot cpu
>
> Can we get closures on these todos?
>
> I see a bunch of todos in other patches, too. It would be good to close
> them as well.
Sorry about those. Most of them are remnants from the when the dev work was
started and are closed or taken care of. I will ensure that these are fixed
and the comments are edited properly in the next revision.
Warm Regards
Thara
>
> Wei
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 07/12] drivers: hv: hv_vsm_boot: load secure kernel image from firmware
2026-09-02 4:37 ` Wei Liu
@ 2026-09-02 16:22 ` Thara Gopinath
2026-09-02 22:58 ` Wei Liu
0 siblings, 1 reply; 24+ messages in thread
From: Thara Gopinath @ 2026-09-02 16:22 UTC (permalink / raw)
To: Wei Liu
Cc: kys, haiyangz, decui, tglx, mingo, bp, dave.hansen, hpa, ardb,
ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi, Stanislav Kinsburskii
On 9/2/2026 12:37 AM, Wei Liu wrote:
> On Tue, Sep 01, 2026 at 09:55:21AM -0700, Thara Gopinath wrote:
>> LVBS bring-up requires loading a secure kernel image into VTL1 before
>> starting it. Add the VTL0-side loader that stages the image in the
>> memory region reserved by hv_vsm_securekernel, in preparation for the
>> VTL1 bring-up.
>>
>> The image is a 64-bit ELF fetched via request_firmware("vsm_sk"). It
>> is expected to ship inside the signed UKI/initramfs so it is
>> authenticated end-to-end via Secure Boot before the loader consumes
>> it; sourcing it from an unauthenticated location would break the LVBS
>> trust model.
>>
>> The loader validates the ELF header, stages the PT_LOAD segments into
>> the reserved region and records the entry point as a physical address
>> for use at VTL1 start time.
>>
>> If VSM support has been advertised to the hypervisor but no secure
>> kernel region was reserved on the command line, panic: LVBS bring-up
>> is committed at this point and there is no safe way to continue.
>
> This conflicts with the memory reservation patch, in which there is an
> automatic allocation when no kernel command line is specified.
Ah no.. So securekernel= has to be specified in the command line for
__setup() to be invoked . It can be left blank without parameters like
securekernel= or with parameters like securekernel=256M@0x80000000. The
reservation logic will take care of reserving the correct memory if there
are no parameters but if there is no command line specified __setup will
not be called. The other way of solving this and invoking the reservations
unconditionally will be to call it from setup_arch like how reserve_crashkernel
is invoked. I am not sure if we want to do that now ?? What do you think ?
But I will reword this and state that the panic happens if there is no
securekernel memory allocated (either because securekernel= was not
specified or because memory reservation itself failed)
>
>>
>> Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
>> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
>> ---
>> drivers/hv/hv_vsm.h | 17 ++++
>> drivers/hv/hv_vsm_boot.c | 201 ++++++++++++++++++++++++++++++++++++++-
>> include/hyperv/vsm.h | 21 ++++
>> 3 files changed, 238 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/hv/hv_vsm.h
>> create mode 100644 include/hyperv/vsm.h
> [...]
>> +
>> +static void __init hv_vsm_get_sk_mem(void)
>> +{
>> + /*
>> + * The reserved secure kernel region is mandatory once VSM support has
>> + * been advertised. Without it we cannot load the secure kernel and
>> + * bringing up VTL1 is impossible, so fail hard rather than continuing
>> + * in an unusable state.
>> + */
>> + if (!sk_res.start)
>> + panic("No memory reserved in cmdline for secure kernel");
>> +
>
> This log line is wrong.
I will fix this
Warm Regards
Thara
>
> Wei
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 07/12] drivers: hv: hv_vsm_boot: load secure kernel image from firmware
2026-09-02 16:22 ` Thara Gopinath
@ 2026-09-02 22:58 ` Wei Liu
0 siblings, 0 replies; 24+ messages in thread
From: Wei Liu @ 2026-09-02 22:58 UTC (permalink / raw)
To: Thara Gopinath
Cc: Wei Liu, kys, haiyangz, decui, tglx, mingo, bp, dave.hansen, hpa,
ardb, ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi, Stanislav Kinsburskii
On Wed, Sep 02, 2026 at 12:22:37PM -0400, Thara Gopinath wrote:
>
>
> On 9/2/2026 12:37 AM, Wei Liu wrote:
> > On Tue, Sep 01, 2026 at 09:55:21AM -0700, Thara Gopinath wrote:
> >> LVBS bring-up requires loading a secure kernel image into VTL1 before
> >> starting it. Add the VTL0-side loader that stages the image in the
> >> memory region reserved by hv_vsm_securekernel, in preparation for the
> >> VTL1 bring-up.
> >>
> >> The image is a 64-bit ELF fetched via request_firmware("vsm_sk"). It
> >> is expected to ship inside the signed UKI/initramfs so it is
> >> authenticated end-to-end via Secure Boot before the loader consumes
> >> it; sourcing it from an unauthenticated location would break the LVBS
> >> trust model.
> >>
> >> The loader validates the ELF header, stages the PT_LOAD segments into
> >> the reserved region and records the entry point as a physical address
> >> for use at VTL1 start time.
> >>
> >> If VSM support has been advertised to the hypervisor but no secure
> >> kernel region was reserved on the command line, panic: LVBS bring-up
> >> is committed at this point and there is no safe way to continue.
> >
> > This conflicts with the memory reservation patch, in which there is an
> > automatic allocation when no kernel command line is specified.
>
> Ah no.. So securekernel= has to be specified in the command line for
> __setup() to be invoked . It can be left blank without parameters like
> securekernel= or with parameters like securekernel=256M@0x80000000. The
> reservation logic will take care of reserving the correct memory if there
> are no parameters but if there is no command line specified __setup will
> not be called. The other way of solving this and invoking the reservations
> unconditionally will be to call it from setup_arch like how reserve_crashkernel
> is invoked. I am not sure if we want to do that now ?? What do you think ?
>
I see. No need to do that now. Let's see if others have opinions.
Wei
> But I will reword this and state that the panic happens if there is no
> securekernel memory allocated (either because securekernel= was not
> specified or because memory reservation itself failed)
>
> >
> >>
> >> Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
> >> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
> >> ---
> >> drivers/hv/hv_vsm.h | 17 ++++
> >> drivers/hv/hv_vsm_boot.c | 201 ++++++++++++++++++++++++++++++++++++++-
> >> include/hyperv/vsm.h | 21 ++++
> >> 3 files changed, 238 insertions(+), 1 deletion(-)
> >> create mode 100644 drivers/hv/hv_vsm.h
> >> create mode 100644 include/hyperv/vsm.h
> > [...]
> >> +
> >> +static void __init hv_vsm_get_sk_mem(void)
> >> +{
> >> + /*
> >> + * The reserved secure kernel region is mandatory once VSM support has
> >> + * been advertised. Without it we cannot load the secure kernel and
> >> + * bringing up VTL1 is impossible, so fail hard rather than continuing
> >> + * in an unusable state.
> >> + */
> >> + if (!sk_res.start)
> >> + panic("No memory reserved in cmdline for secure kernel");
> >> +
> >
> > This log line is wrong.
>
> I will fix this
>
> Warm Regards
> Thara
> >
> > Wei
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [RFC PATCH 06/12] drivers: hv: Add VSM boot driver and enable VTL1 at the partition level
2026-09-02 4:43 ` Wei Liu
@ 2026-09-04 13:23 ` Thara Gopinath
0 siblings, 0 replies; 24+ messages in thread
From: Thara Gopinath @ 2026-09-04 13:23 UTC (permalink / raw)
To: Wei Liu
Cc: kys, haiyangz, decui, tglx, mingo, bp, dave.hansen, hpa, ardb,
ilias.apalodimas, James.Bottomley, javierm, lszubowi,
francescopompo2, tgopinath, x86, linux-hyperv, linux-kernel,
linux-efi
On 9/2/2026 12:43 AM, Wei Liu wrote:
> On Tue, Sep 01, 2026 at 09:55:20AM -0700, Thara Gopinath wrote:
>> Linux VBS (LVBS) uses Hyper-V's Virtual Secure Mode to run a small
>> trusted kernel in VTL1 alongside the regular VTL0 kernel, so that
>> security-sensitive state (e.g. hypervisor-enforced code integrity,
>> credential isolation) can live behind a higher-privilege boundary
>> that VTL0 compromise cannot cross. Bringing that up from Linux
>> requires the VTL0 kernel to drive the VSM setup itself.
>>
>> Add drivers/hv/hv_vsm_boot.c as the entry point for that sequence.
>> This first step handles partition-level VTL1 enable only:
>>
>> - Probe VSM / VP-register privileges and SynIC availability before
>> doing anything.
>> - Pin init to the VTL0 boot CPU so VTL1 comes up on the same CPU
>> (later patches rely on this).
>> - Read HV_REGISTER_VSM_PARTITION_STATUS, and if VTL1 is not already
>> enabled, issue HVCALL_ENABLE_PARTITION_VTL with MBEC and confirm
>> by re-reading the register.
>>
>> Signed-off-by: Thara Gopinath <tgopinath@linux.microsoft.com>
>> ---
> [...]
>> + cpumask_copy(mask, ¤t->cpus_mask);
>> + boot_cpu = cpumask_first(cpu_online_mask);
>> + set_cpus_allowed_ptr(current, cpumask_of(boot_cpu));
>> +
>> + ret = hv_vsm_bootstrap_vtl();
>> + /*
>> + * At this point VTL0 has already advertised VSM support to the
>> + * bootloader/firmware via the Hyper-V OsLoaderIndications EFI
>> + * variable (see the x86-stub change). That signals the platform
>> + * that a trusted VTL1 will be brought up. If we fail to actually
>> + * set VTL1 up here, the partition is left in a state where an
>> + * attacker could race to configure VTL1 themselves and gain a
>> + * higher-privilege foothold than VTL0. Panic rather than continue
>> + * running with that exposure.
>> + */
>
> Okay, I think this answers my question in the previous patch -- if VTL1
> is not used by us, that's a security problem.
>
> It is unclear to me, if this code is enabled, how a generic kernel can
> work without a secure kernel. There should be a way to configure the
> system such that not enabling VTL1 is okay. Is there any pre-EFI command
> line parsing we can do?
Yes, we can do a pre-efi-exit-boot-service command line parsing and not set
the OsLoaderIndications EFI variable asking HYPER-V to allow VTL1 setup. We
can rename the securekernel command line option to mshv_securekernel and
repurpose it for this as well. Basically if CONFIG_HYPER_VSM is enabled but
if mshv_securekernel command line is not set VTL1 support will not be enabled
and kernel will boot. I did quickly prototype this and it is doable.
Having said that generic kernel should not enable CONFIG_HYPERV_VSM at all.
I can update the KConfig to state the same as well.
Warm Regards
Thara
>
> Wei
>
>
>> + if (ret)
>> + panic("VTL1 boot failure caused kernel panic; consult log for more details.\n");
>> +
>> + set_cpus_allowed_ptr(current, mask);
>> + free_cpumask_var(mask);
>> + return ret;
>> +}
>> +device_initcall(hv_vsm_boot_init);
>> --
>> 2.34.1
>>
>>
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-09-04 13:23 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [RFC PATCH 08/12] arch: x86: hyperv: Build initial vCPU context for VTL1 secure kernel Thara Gopinath
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).