* [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions
@ 2026-08-10 12:44 Wei Hu
2026-08-10 12:44 ` [PATCH v2 01/13] mshv: add SEV-SNP UAPI definitions Wei Hu
` (12 more replies)
0 siblings, 13 replies; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:44 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
This series adds support for creating and managing AMD SEV-SNP
confidential virtual machines through the Microsoft Hypervisor root
partition driver.
The series adds the required Hyper-V ABI and MSHV UAPI definitions,
hypercall helpers, partition ioctls, capability discovery, processor
feature handling, memory teardown, and crash-dump support.
It also adds the x86 EFI HvLoader handoff required to boot Linux as an
MSHV root partition and corrects SynIC register ownership when the root
partition is itself running nested.
The patches are based on Linux v7.2-rc5.
Testing:
- Built the complete 13-patch series on x86_64.
- Verified the installed MSHV UAPI compiles from userspace.
- Booted Linux as an MSHV root partition with SEV-SNP available.
- Passed Cloud Hypervisor's common_cvm::test_focal_simple_launch.
- Created and booted a four-vCPU SEV-SNP guest.
- Verified SNP panic-time page unlock for kdump.
Functional testing on the development host used an additional local
nested-VMBus interrupt workaround required by that nested environment.
That workaround is not part of this series.
Changes since v1:
- Make the isolated-import UAPI self-contained and translate it to
the internal Hyper-V ABI type in the driver.
- Preserve the asynchronous import rep count across CALL_PENDING
completion and reject invalid zero/oversized progress.
- Validate physical contiguity for large-page host-access requests,
use kvcalloc() for userspace-sized page arrays, and restore IRQ state
on defensive error paths.
- Keep encrypted memory regions registered until ordered partition
teardown and retry transient SNP state-destruction failures.
- Make SEV-SNP capability queries informational so unsupported
platforms retain ordinary MSHV functionality.
- Fully initialize partition creation properties on all architectures,
stabilize the extended create-partition userspace fetch, and fix the
VERW feature-field typo.
- Serialize panic-time memory-region traversal.
- Allocate the augmented EFI command line from EFI loader memory,
add command-line bounds checking, correctly append setup_data,
compact legacy resource entries, preserve partially parsed range
state, and release temporary EFI allocations.
- Limit the EFI HvLoader implementation to x86_64; the unhooked ARM64
implementation from v1 was removed.
Link: https://lore.kernel.org/linux-hyperv/20260807135134.303943-1-weh@linux.microsoft.com/
Wei Hu (4):
hyperv: fix hv_input_get_system_property layout for SNP status
mshv: unlock SNP pages on panic for crashdump collection
hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader)
mshv: set up own SynIC registers on a nested root partition
Wei Liu (9):
mshv: add SEV-SNP UAPI definitions
mshv: add SEV-SNP PSP request hypercall
mshv: add SEV-SNP isolated page hypercalls
mshv: wire SEV-SNP partition ioctls
mshv: detect and report SEV-SNP support at init
mshv: default to safe partition CPU features
mshv: accept partial CPU feature banks
mshv: define full processor and xsave feature masks
mshv: unmap SNP memory before state teardown
MAINTAINERS | 2 +
arch/x86/hyperv/hv_init.c | 3 +
arch/x86/include/asm/mshyperv.h | 1 +
arch/x86/include/uapi/asm/setup_data.h | 3 +-
arch/x86/kernel/cpu/mshyperv.c | 81 +-
drivers/firmware/efi/libstub/Makefile | 5 +-
.../firmware/efi/libstub/efi-mshv-common.c | 148 ++++
drivers/firmware/efi/libstub/efi-mshv.h | 109 +++
drivers/firmware/efi/libstub/x86-efi-mshv.c | 217 ++++++
drivers/firmware/efi/libstub/x86-stub.c | 18 +-
drivers/hv/hv_common.c | 82 ++
drivers/hv/mshv_regions.c | 2 +-
drivers/hv/mshv_root.h | 28 +
drivers/hv/mshv_root_hv_call.c | 180 ++++-
drivers/hv/mshv_root_main.c | 729 +++++++++++++++++-
drivers/hv/mshv_synic.c | 16 +-
include/asm-generic/mshyperv.h | 7 +
include/hyperv/hvgdk_mini.h | 31 +
include/hyperv/hvhdk.h | 124 ++-
include/hyperv/hvhdk_mini.h | 56 +-
include/linux/efi.h | 1 +
include/uapi/linux/mshv.h | 104 ++-
22 files changed, 1914 insertions(+), 33 deletions(-)
create mode 100644 drivers/firmware/efi/libstub/efi-mshv-common.c
create mode 100644 drivers/firmware/efi/libstub/efi-mshv.h
create mode 100644 drivers/firmware/efi/libstub/x86-efi-mshv.c
base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v2 01/13] mshv: add SEV-SNP UAPI definitions
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
@ 2026-08-10 12:44 ` Wei Hu
2026-08-10 12:59 ` sashiko-bot
2026-08-10 12:44 ` [PATCH v2 02/13] mshv: add SEV-SNP PSP request hypercall Wei Hu
` (11 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:44 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
Add the MSHV UAPI and Hyper-V ABI definitions needed by the SEV-SNP
partition ioctls: the SNP isolation type, GPA host-access and
isolated-page request structures, PSP request structures, and the SEV
control register definitions.
Also add the SNP launch-data definitions (guest policy, ID block, ID
auth info and launch-finish data) that form the isolated-import
completion parameters, along with the SNP support-status enumeration.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
include/hyperv/hvgdk_mini.h | 19 +++++++
include/hyperv/hvhdk.h | 45 ++++++++++++++++
include/hyperv/hvhdk_mini.h | 53 +++++++++++++++++++
include/uapi/linux/mshv.h | 101 ++++++++++++++++++++++++++++++++++++
4 files changed, 218 insertions(+)
diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h
index 6a4e8b9d570f..a53c0f59994a 100644
--- a/include/hyperv/hvgdk_mini.h
+++ b/include/hyperv/hvgdk_mini.h
@@ -497,6 +497,9 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
#define HVCALL_UNMAP_VP_STATE_PAGE 0x00e2
#define HVCALL_GET_VP_STATE 0x00e3
#define HVCALL_SET_VP_STATE 0x00e4
+#define HVCALL_IMPORT_ISOLATED_PAGES 0x00ef
+#define HVCALL_COMPLETE_ISOLATED_IMPORT 0x00f1
+#define HVCALL_ISSUE_SNP_PSP_GUEST_REQUEST 0x00f2
#define HVCALL_GET_VP_CPUID_VALUES 0x00f4
#define HVCALL_GET_PARTITION_PROPERTY_EX 0x0101
#define HVCALL_MMIO_READ 0x0106
@@ -1065,6 +1068,9 @@ enum hv_register_name {
HV_REGISTER_VSM_PARTITION_CONFIG = 0x000D0007,
#if defined(CONFIG_X86)
+ /* AMD SEV-SNP configuration register */
+ HV_X64_REGISTER_SEV_CONTROL = 0x00090040,
+
/* X64 Debug Registers */
HV_X64_REGISTER_DR0 = 0x00050000,
HV_X64_REGISTER_DR1 = 0x00050001,
@@ -1267,6 +1273,18 @@ union hv_x64_pending_interruption_register {
} __packed;
};
+#ifdef CONFIG_X86
+#define HV_SUPPORTS_SEV_SNP_GUESTS
+union hv_x64_register_sev_control {
+ u64 as_uint64;
+ struct {
+ u64 enable_encrypted_state : 1;
+ u64 reserved_z : 11;
+ u64 vmsa_gpa_page_number : 52;
+ } __packed;
+};
+#endif
+
union hv_register_value {
struct hv_u128 reg128;
u64 reg64;
@@ -1286,6 +1304,7 @@ union hv_register_value {
#ifdef CONFIG_X86
union hv_x64_interrupt_state_register interrupt_state;
union hv_x64_pending_interruption_register pending_interruption;
+ union hv_x64_register_sev_control sev_control;
#endif
union hv_arm64_pending_synthetic_exception_event pending_synthetic_exception_event;
};
diff --git a/include/hyperv/hvhdk.h b/include/hyperv/hvhdk.h
index 0c89c62c9706..691ac495a095 100644
--- a/include/hyperv/hvhdk.h
+++ b/include/hyperv/hvhdk.h
@@ -953,4 +953,49 @@ struct hv_input_modify_sparse_spa_page_host_access {
#define HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE 0x4
#define HV_MODIFY_SPA_PAGE_HOST_ACCESS_HUGE_PAGE 0x8
+enum hv_isolated_page_type {
+ HV_ISOLATED_PAGE_TYPE_NORMAL,
+ HV_ISOLATED_PAGE_TYPE_VMSA,
+ HV_ISOLATED_PAGE_TYPE_ZERO,
+ HV_ISOLATED_PAGE_TYPE_UNMEASURED,
+ HV_ISOLATED_PAGE_TYPE_SECRETS,
+ HV_ISOLATED_PAGE_TYPE_CPUID,
+ HV_ISOLATED_PAGE_TYPE_COUNT
+};
+
+enum hv_isolated_page_size {
+ HV_ISOLATED_PAGE_SIZE_4KB,
+ HV_ISOLATED_PAGE_SIZE_2MB
+};
+
+struct hv_input_import_isolated_pages {
+ u64 partition_id;
+ u32 page_type;
+ u32 page_size;
+ u64 page_number[];
+} __packed;
+
+struct hv_input_issue_psp_guest_request {
+ u64 partition_id;
+ u64 request_page;
+ u64 response_page;
+} __packed;
+
+enum hv_partition_isolation_state {
+ HV_PARTITION_ISOLATION_INVALID,
+ HV_PARTITION_ISOLATION_INSECURE_CLEAN,
+ HV_PARTITION_ISOLATION_INSECURE_DIRTY,
+ HV_PARTITION_ISOLATION_SECURE,
+ HV_PARTITION_ISOLATION_SECURE_DIRTY,
+ HV_PARTITION_ISOLATION_SECURE_TERMINATING,
+};
+
+union hv_partition_isolation_control {
+ u64 as_uint64;
+ struct {
+ u64 runnable : 1;
+ u64 reserved_z : 63;
+ } __packed;
+};
+
#endif /* _HV_HVHDK_H */
diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h
index b4cb2fa26e9b..db83fedce243 100644
--- a/include/hyperv/hvhdk_mini.h
+++ b/include/hyperv/hvhdk_mini.h
@@ -94,6 +94,8 @@ enum hv_partition_property_code {
/* Resource properties */
HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING = 0x00050005,
+ HV_PARTITION_PROPERTY_ISOLATION_STATE = 0x0005000c,
+ HV_PARTITION_PROPERTY_ISOLATION_CONTROL = 0x0005000d,
HV_PARTITION_PROPERTY_UNIMPLEMENTED_MSR_ACTION = 0x00050017,
/* Compatibility properties */
@@ -145,6 +147,57 @@ enum hv_snp_status {
HV_SNP_STATUS_PSP_INIT_LATE_FAILED = 10,
};
+union hv_snp_guest_policy {
+ struct {
+ u64 minor_version : 8;
+ u64 major_version : 8;
+ u64 smt_allowed : 1;
+ u64 vmpls_required : 1;
+ u64 migration_agent_allowed : 1;
+ u64 debug_allowed : 1;
+ u64 reserved : 44;
+ } __packed;
+ u64 as_uint64;
+};
+
+struct hv_snp_id_block {
+ u8 launch_digest[48];
+ u8 family_id[16];
+ u8 image_id[16];
+ u32 version;
+ u32 guest_svn;
+ union hv_snp_guest_policy policy;
+} __packed;
+
+struct hv_snp_id_auth_info {
+ u32 id_key_algorithm;
+ u32 auth_key_algorithm;
+ u8 reserved0[56];
+ u8 id_block_signature[512];
+ u8 id_key[1028];
+ u8 reserved1[60];
+ u8 id_key_signature[512];
+ u8 author_key[1028];
+} __packed;
+
+struct hv_psp_launch_finish_data {
+ struct hv_snp_id_block id_block;
+ struct hv_snp_id_auth_info id_auth_info;
+ u8 host_data[32];
+ u8 id_block_enabled;
+ u8 author_key_enabled;
+} __packed;
+
+union hv_partition_complete_isolated_import_data {
+ u64 reserved;
+ struct hv_psp_launch_finish_data psp_parameters;
+} __packed;
+
+struct hv_input_complete_isolated_import {
+ u64 partition_id;
+ union hv_partition_complete_isolated_import_data import_data;
+} __packed;
+
enum hv_system_property {
/* Add more values when needed */
HV_SYSTEM_PROPERTY_SLEEP_STATE = 3,
diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
index 32ff92b6342b..fc89f6c9a968 100644
--- a/include/uapi/linux/mshv.h
+++ b/include/uapi/linux/mshv.h
@@ -36,6 +36,7 @@ enum {
enum {
MSHV_PT_ISOLATION_NONE,
+ MSHV_PT_ISOLATION_SNP,
MSHV_PT_ISOLATION_COUNT,
};
@@ -219,6 +220,101 @@ struct mshv_gpap_access_bitmap {
__u64 bitmap_ptr;
};
+enum {
+ MSHV_GPA_HOST_ACCESS_BIT_ACQUIRE,
+ MSHV_GPA_HOST_ACCESS_BIT_READABLE,
+ MSHV_GPA_HOST_ACCESS_BIT_WRITABLE,
+ MSHV_GPA_HOST_ACCESS_BIT_LARGE_PAGE,
+ MSHV_GPA_HOST_ACCESS_BIT_COUNT
+};
+
+#define MSHV_GPA_HOST_ACCESS_FLAGS_MASK \
+ ((1 << MSHV_GPA_HOST_ACCESS_BIT_COUNT) - 1)
+
+struct mshv_modify_gpa_host_access {
+ __u8 flags;
+ __u8 rsvd[7];
+ __u64 page_count;
+ __u64 guest_pfns[];
+};
+
+enum {
+ MSHV_ISOLATED_PAGE_NORMAL,
+ MSHV_ISOLATED_PAGE_VMSA,
+ MSHV_ISOLATED_PAGE_ZERO,
+ MSHV_ISOLATED_PAGE_UNMEASURED,
+ MSHV_ISOLATED_PAGE_SECRETS,
+ MSHV_ISOLATED_PAGE_CPUID,
+ MSHV_ISOLATED_PAGE_COUNT
+};
+
+struct mshv_import_isolated_pages {
+ __u8 page_type;
+ __u8 rsvd[7];
+ __u64 page_count;
+ __u64 guest_pfns[];
+};
+
+struct mshv_issue_psp_guest_request {
+ __u64 req_gpa;
+ __u64 rsp_gpa;
+};
+
+struct mshv_sev_snp_ap_create {
+ __u64 vp_id;
+ __u64 vmsa_gpa;
+};
+
+union mshv_snp_guest_policy {
+ struct {
+ __u64 minor_version : 8;
+ __u64 major_version : 8;
+ __u64 smt_allowed : 1;
+ __u64 vmpls_required : 1;
+ __u64 migration_agent_allowed : 1;
+ __u64 debug_allowed : 1;
+ __u64 reserved : 44;
+ } __attribute__((packed));
+ __u64 as_uint64;
+};
+
+struct mshv_snp_id_block {
+ __u8 launch_digest[48];
+ __u8 family_id[16];
+ __u8 image_id[16];
+ __u32 version;
+ __u32 guest_svn;
+ union mshv_snp_guest_policy policy;
+} __attribute__((packed));
+
+struct mshv_snp_id_auth_info {
+ __u32 id_key_algorithm;
+ __u32 auth_key_algorithm;
+ __u8 reserved0[56];
+ __u8 id_block_signature[512];
+ __u8 id_key[1028];
+ __u8 reserved1[60];
+ __u8 id_key_signature[512];
+ __u8 author_key[1028];
+} __attribute__((packed));
+
+struct mshv_psp_launch_finish_data {
+ struct mshv_snp_id_block id_block;
+ struct mshv_snp_id_auth_info id_auth_info;
+ __u8 host_data[32];
+ __u8 id_block_enabled;
+ __u8 author_key_enabled;
+} __attribute__((packed));
+
+union mshv_partition_complete_isolated_import_data {
+ __u64 reserved;
+ struct mshv_psp_launch_finish_data psp_parameters;
+} __attribute__((packed));
+
+struct mshv_complete_isolated_import {
+ union mshv_partition_complete_isolated_import_data import_data;
+};
+
/**
* struct mshv_root_hvcall - arguments for MSHV_ROOT_HVCALL
* @code: Hypercall code (HVCALL_*)
@@ -254,6 +350,11 @@ struct mshv_root_hvcall {
#define MSHV_GET_GPAP_ACCESS_BITMAP _IOWR(MSHV_IOCTL, 0x06, struct mshv_gpap_access_bitmap)
/* Generic hypercall */
#define MSHV_ROOT_HVCALL _IOWR(MSHV_IOCTL, 0x07, struct mshv_root_hvcall)
+#define MSHV_MODIFY_GPA_HOST_ACCESS _IOW(MSHV_IOCTL, 0x09, struct mshv_modify_gpa_host_access)
+#define MSHV_IMPORT_ISOLATED_PAGES _IOW(MSHV_IOCTL, 0x0A, struct mshv_import_isolated_pages)
+#define MSHV_COMPLETE_ISOLATED_IMPORT _IOW(MSHV_IOCTL, 0xF4, struct mshv_complete_isolated_import)
+#define MSHV_ISSUE_PSP_GUEST_REQUEST _IOW(MSHV_IOCTL, 0xF5, struct mshv_issue_psp_guest_request)
+#define MSHV_SEV_SNP_AP_CREATE _IOW(MSHV_IOCTL, 0xF6, struct mshv_sev_snp_ap_create)
/*
********************************
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 02/13] mshv: add SEV-SNP PSP request hypercall
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-10 12:44 ` [PATCH v2 01/13] mshv: add SEV-SNP UAPI definitions Wei Hu
@ 2026-08-10 12:44 ` Wei Hu
2026-08-10 12:44 ` [PATCH v2 03/13] mshv: add SEV-SNP isolated page hypercalls Wei Hu
` (10 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:44 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
Add a helper for HVCALL_ISSUE_SNP_PSP_GUEST_REQUEST so MSHV can
issue asynchronous PSP guest requests on behalf of encrypted
partitions.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root.h | 8 +++++++
drivers/hv/mshv_root_hv_call.c | 39 ++++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 1f086dcb7aa1..8abb080938fd 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -378,4 +378,12 @@ bool mshv_region_handle_gfn_fault(struct mshv_mem_region *region, u64 gfn);
void mshv_region_movable_fini(struct mshv_mem_region *region);
bool mshv_region_movable_init(struct mshv_mem_region *region);
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+int hv_call_issue_psp_guest_request(u64 partition_id, u64 req_pfn,
+ u64 rsp_pfn,
+ void (*completion_handler)(void *data,
+ u64 *status),
+ void *completion_data);
+#endif
+
#endif /* _MSHV_ROOT_H_ */
diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index cb55d4d4be2e..48abe338c06f 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -1009,6 +1009,45 @@ int hv_unmap_stats_page(enum hv_stats_object_type type,
return ret;
}
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+int hv_call_issue_psp_guest_request(u64 partition_id, u64 req_pfn,
+ u64 rsp_pfn,
+ void (*completion_handler)(void *data,
+ u64 *status),
+ void *completion_data)
+{
+ struct hv_input_issue_psp_guest_request *input;
+ unsigned long flags;
+ u64 status;
+
+ if (!completion_handler) {
+ pr_err("%s: missing completion handler\n", __func__);
+ return -EINVAL;
+ }
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->partition_id = partition_id;
+ input->request_page = req_pfn;
+ input->response_page = rsp_pfn;
+ status = hv_do_hypercall(HVCALL_ISSUE_SNP_PSP_GUEST_REQUEST, input,
+ NULL);
+ local_irq_restore(flags);
+
+ if (hv_result(status) == HV_STATUS_CALL_PENDING)
+ completion_handler(completion_data, &status);
+
+ if (!hv_result_success(status)) {
+ pr_err("%s: status=%s partition_id=%llu\n", __func__,
+ hv_result_to_string(status), partition_id);
+ return hv_result_to_errno(status);
+ }
+
+ return 0;
+}
+#endif
+
int hv_call_modify_spa_host_access(u64 partition_id, struct page **pages,
u64 page_struct_count, u32 host_access,
u32 flags, u8 acquire)
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 03/13] mshv: add SEV-SNP isolated page hypercalls
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-10 12:44 ` [PATCH v2 01/13] mshv: add SEV-SNP UAPI definitions Wei Hu
2026-08-10 12:44 ` [PATCH v2 02/13] mshv: add SEV-SNP PSP request hypercall Wei Hu
@ 2026-08-10 12:44 ` Wei Hu
2026-08-10 12:58 ` sashiko-bot
2026-08-10 12:44 ` [PATCH v2 04/13] mshv: wire SEV-SNP partition ioctls Wei Hu
` (9 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:44 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
Add helpers for importing isolated pages and completing isolated
imports. These are used by SEV-SNP guest launch flows to transfer
isolated page state to the hypervisor.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root.h | 12 ++++
drivers/hv/mshv_root_hv_call.c | 102 +++++++++++++++++++++++++++++++++
2 files changed, 114 insertions(+)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 8abb080938fd..69226179cfbf 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -379,6 +379,18 @@ void mshv_region_movable_fini(struct mshv_mem_region *region);
bool mshv_region_movable_init(struct mshv_mem_region *region);
#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+int hv_call_import_isolated_pages(u64 partition_id, u64 *pages,
+ u64 num_pages,
+ enum hv_isolated_page_type page_type,
+ enum hv_isolated_page_size page_size,
+ void (*completion_handler)(void *data,
+ u64 *status),
+ void *completion_data);
+int hv_call_complete_isolated_import(u64 partition_id,
+ union hv_partition_complete_isolated_import_data *import_data,
+ void (*completion_handler)(void *data,
+ u64 *status),
+ void *completion_data);
int hv_call_issue_psp_guest_request(u64 partition_id, u64 req_pfn,
u64 rsp_pfn,
void (*completion_handler)(void *data,
diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index 48abe338c06f..4e48c1a9a630 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -14,6 +14,10 @@
#include "mshv_root.h"
+#define HV_ISOLATED_PAGE_BATCH_SIZE \
+ ((HV_HYP_PAGE_SIZE - sizeof(struct hv_input_import_isolated_pages)) / \
+ sizeof(u64))
+
/* Determined empirically */
#define HV_INIT_PARTITION_DEPOSIT_PAGES 208
#define HV_MAP_GPA_DEPOSIT_PAGES 256
@@ -1010,6 +1014,104 @@ int hv_unmap_stats_page(enum hv_stats_object_type type,
}
#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+int hv_call_import_isolated_pages(u64 partition_id, u64 *pages,
+ u64 num_pages,
+ enum hv_isolated_page_type page_type,
+ enum hv_isolated_page_size page_size,
+ void (*completion_handler)(void *data,
+ u64 *status),
+ void *completion_data)
+{
+ struct hv_input_import_isolated_pages *input;
+ unsigned long remaining = num_pages;
+ unsigned long flags;
+ u64 *gpa = pages;
+ u64 completed;
+ u64 status;
+ int rep_count;
+
+ if (!num_pages)
+ return -EINVAL;
+
+ if (!completion_handler) {
+ pr_err("%s: missing completion handler, page_type=%u\n",
+ __func__, page_type);
+ return -EINVAL;
+ }
+
+ while (remaining) {
+ rep_count = min_t(unsigned long, remaining,
+ HV_ISOLATED_PAGE_BATCH_SIZE);
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->partition_id = partition_id;
+ input->page_type = page_type;
+ input->page_size = page_size;
+ memcpy(input->page_number, gpa, rep_count * sizeof(*gpa));
+ status = hv_do_rep_hypercall(HVCALL_IMPORT_ISOLATED_PAGES,
+ rep_count, 0, input, NULL);
+ local_irq_restore(flags);
+
+ completed = hv_repcomp(status);
+ if (hv_result(status) == HV_STATUS_CALL_PENDING) {
+ completion_handler(completion_data, &status);
+ if (hv_repcomp(status))
+ completed = hv_repcomp(status);
+ }
+
+ if (!hv_result_success(status)) {
+ pr_err("%s: completed %llu of %llu, %s\n", __func__,
+ num_pages - remaining, num_pages,
+ hv_result_to_string(status));
+ return hv_result_to_errno(status);
+ }
+ if (!completed || completed > remaining)
+ return -EIO;
+
+ gpa += completed;
+ remaining -= completed;
+ }
+
+ return 0;
+}
+
+int hv_call_complete_isolated_import(u64 partition_id,
+ union hv_partition_complete_isolated_import_data *import_data,
+ void (*completion_handler)(void *data,
+ u64 *status),
+ void *completion_data)
+{
+ struct hv_input_complete_isolated_import *input;
+ unsigned long flags;
+ u64 status;
+
+ if (!completion_handler) {
+ pr_err("%s: missing completion handler\n", __func__);
+ return -EINVAL;
+ }
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->partition_id = partition_id;
+ input->import_data = *import_data;
+ status = hv_do_hypercall(HVCALL_COMPLETE_ISOLATED_IMPORT, input, NULL);
+ local_irq_restore(flags);
+
+ if (hv_result(status) == HV_STATUS_CALL_PENDING)
+ completion_handler(completion_data, &status);
+
+ if (!hv_result_success(status)) {
+ pr_err("%s: status=%s partition_id=%llu\n", __func__,
+ hv_result_to_string(status), partition_id);
+ return hv_result_to_errno(status);
+ }
+
+ return 0;
+}
+
int hv_call_issue_psp_guest_request(u64 partition_id, u64 req_pfn,
u64 rsp_pfn,
void (*completion_handler)(void *data,
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 04/13] mshv: wire SEV-SNP partition ioctls
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (2 preceding siblings ...)
2026-08-10 12:44 ` [PATCH v2 03/13] mshv: add SEV-SNP isolated page hypercalls Wei Hu
@ 2026-08-10 12:44 ` Wei Hu
2026-08-10 13:07 ` sashiko-bot
2026-08-10 12:44 ` [PATCH v2 05/13] hyperv: fix hv_input_get_system_property layout for SNP status Wei Hu
` (8 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:44 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
Wire the SEV-SNP partition ioctls for GPA host-access changes,
isolated-page import, PSP guest requests, AP creation, and SNP partition
teardown.
The GPA host-access ioctl converts the guest addresses to GFNs before
issuing the hypercall. The PSP guest-request path restores host access
to the request/response pages if the hypercall fails, so a failed request
does not leave guest pages inaccessible to the host.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root.h | 7 +
drivers/hv/mshv_root_hv_call.c | 39 ++-
drivers/hv/mshv_root_main.c | 436 +++++++++++++++++++++++++++++++++
include/hyperv/hvgdk_mini.h | 12 +
4 files changed, 493 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 69226179cfbf..ee52797727cc 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -12,6 +12,7 @@
#include <linux/sched.h>
#include <linux/srcu.h>
#include <linux/wait.h>
+#include <linux/workqueue.h>
#include <linux/hashtable.h>
#include <linux/dev_printk.h>
#include <linux/build_bug.h>
@@ -128,6 +129,7 @@ struct mshv_partition {
*/
struct completion async_hypercall;
u64 async_hypercall_status;
+ struct delayed_work destroy_work;
spinlock_t pt_irqfds_lock;
struct hlist_head pt_irqfds_list;
@@ -378,6 +380,11 @@ bool mshv_region_handle_gfn_fault(struct mshv_mem_region *region, u64 gfn);
void mshv_region_movable_fini(struct mshv_mem_region *region);
bool mshv_region_movable_init(struct mshv_mem_region *region);
+int hv_call_set_partition_property(u64 partition_id, u64 property_code,
+ u64 property_value,
+ void (*completion_handler)(void *, u64 *),
+ void *completion_data);
+
#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
int hv_call_import_isolated_pages(u64 partition_id, u64 *pages,
u64 num_pages,
diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index 4e48c1a9a630..6d42fdd2c573 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -1014,6 +1014,41 @@ int hv_unmap_stats_page(enum hv_stats_object_type type,
}
#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+int hv_call_set_partition_property(u64 partition_id, u64 property_code,
+ u64 property_value,
+ void (*completion_handler)(void *, u64 *),
+ void *completion_data)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_set_partition_property *input;
+
+ if (!completion_handler) {
+ pr_err("%s: Missing completion handler\n", __func__);
+ return -EINVAL;
+ }
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+
+ memset(input, 0, sizeof(*input));
+ input->partition_id = partition_id;
+ input->property_code = property_code;
+ input->property_value = property_value;
+ status = hv_do_hypercall(HVCALL_SET_PARTITION_PROPERTY, input, NULL);
+ local_irq_restore(flags);
+
+ if (unlikely(status == HV_STATUS_CALL_PENDING))
+ completion_handler(completion_data, &status);
+
+ if (!hv_result_success(status)) {
+ pr_err("%s: %s\n", __func__, hv_result_to_string(status));
+ return hv_result_to_errno(status);
+ }
+
+ return 0;
+}
+
int hv_call_import_isolated_pages(u64 partition_id, u64 *pages,
u64 num_pages,
enum hv_isolated_page_type page_type,
@@ -1192,8 +1227,10 @@ int hv_call_modify_spa_host_access(u64 partition_id, struct page **pages,
for (i = 0; i < rep_count; i++) {
u64 index = (done + i) << large_shift;
- if (index >= page_struct_count)
+ if (index >= page_struct_count) {
+ local_irq_restore(irq_flags);
return -EINVAL;
+ }
input_page->spa_page_list[i] =
page_to_pfn(pages[index]);
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 146726cc4e9b..8c87dd8e0378 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -115,6 +115,9 @@ static u16 mshv_passthru_hvcalls[] = {
HVCALL_GET_PARTITION_PROPERTY,
HVCALL_GET_PARTITION_PROPERTY_EX,
HVCALL_SET_PARTITION_PROPERTY,
+ HVCALL_ISSUE_SNP_PSP_GUEST_REQUEST,
+ HVCALL_COMPLETE_ISOLATED_IMPORT,
+ HVCALL_IMPORT_ISOLATED_PAGES,
HVCALL_INSTALL_INTERCEPT,
HVCALL_GET_VP_REGISTERS,
HVCALL_SET_VP_REGISTERS,
@@ -142,6 +145,8 @@ static bool mshv_hvcall_is_async(u16 code)
{
switch (code) {
case HVCALL_SET_PARTITION_PROPERTY:
+ case HVCALL_IMPORT_ISOLATED_PAGES:
+ case HVCALL_ISSUE_SNP_PSP_GUEST_REQUEST:
return true;
default:
break;
@@ -641,6 +646,42 @@ mshv_partition_region_by_gfn_get(struct mshv_partition *p, u64 gfn)
return region;
}
+static int mshv_gpfns_to_pages(struct mshv_partition *partition,
+ const u64 *gpfns, u64 page_count,
+ struct page **pages)
+{
+ struct mshv_mem_region *region;
+ u64 i;
+ int ret = 0;
+
+ for (i = 0; i < page_count; i++) {
+ u64 gfn = gpfns[i];
+ u64 offset;
+
+ region = mshv_partition_region_by_gfn_get(partition, gfn);
+ if (!region) {
+ pt_err(partition, "Failed to find region for GFN %#llx\n",
+ gfn);
+ return -ERANGE;
+ }
+
+ offset = gfn - region->start_gfn;
+ mutex_lock(®ion->mreg_mutex);
+ if (offset >= region->nr_pages || !region->mreg_pages[offset]) {
+ ret = -EFAULT;
+ mutex_unlock(®ion->mreg_mutex);
+ mshv_region_put(region);
+ return ret;
+ }
+
+ pages[i] = region->mreg_pages[offset];
+ mutex_unlock(®ion->mreg_mutex);
+ mshv_region_put(region);
+ }
+
+ return 0;
+}
+
/**
* mshv_handle_gpa_intercept - Handle GPA (Guest Physical Address) intercepts.
* @vp: Pointer to the virtual processor structure.
@@ -1411,6 +1452,8 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
if (!(mem->flags & BIT(MSHV_SET_MEM_BIT_UNMAP)))
return -EINVAL;
+ if (partition->pt_initialized && mshv_partition_encrypted(partition))
+ return -EBUSY;
spin_lock(&partition->pt_mem_regions_lock);
@@ -1625,6 +1668,289 @@ mshv_partition_ioctl_initialize(struct mshv_partition *partition)
return ret;
}
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+static int set_sev_control_register(struct mshv_vp *vp,
+ u64 enable_encrypted_state,
+ u64 vmsa_gpa_page_number)
+{
+ struct hv_register_assoc sev_control = {
+ .name = HV_X64_REGISTER_SEV_CONTROL,
+ };
+
+ sev_control.value.sev_control.enable_encrypted_state =
+ enable_encrypted_state;
+ sev_control.value.sev_control.vmsa_gpa_page_number =
+ vmsa_gpa_page_number;
+
+ return mshv_set_vp_registers(vp->vp_index, vp->vp_partition->pt_id,
+ 1, &sev_control);
+}
+
+static long
+mshv_partition_ioctl_sev_snp_ap_create(struct mshv_partition *partition,
+ void __user *user_args)
+{
+ struct hv_register_assoc internal_activity = {
+ .name = HV_REGISTER_INTERNAL_ACTIVITY_STATE,
+ .value.internal_activity.as_uint64 = 0,
+ };
+ struct mshv_sev_snp_ap_create req;
+ struct mshv_vp *vp;
+ long ret;
+
+ if (copy_from_user(&req, user_args, sizeof(req)))
+ return -EFAULT;
+
+ if (req.vp_id >= MSHV_MAX_VPS)
+ return -EINVAL;
+
+ vp = partition->pt_vp_array[req.vp_id];
+ if (!vp)
+ return -EINVAL;
+
+ ret = set_sev_control_register(vp, 1, HVPFN_DOWN(req.vmsa_gpa));
+ if (ret) {
+ vp_err(vp, "Failed to set SEV control register\n");
+ return ret;
+ }
+
+ ret = mshv_set_vp_registers(vp->vp_index, vp->vp_partition->pt_id, 1,
+ &internal_activity);
+ if (ret)
+ vp_err(vp, "Failed to set internal activity\n");
+
+ return ret;
+}
+
+static bool mshv_pages_are_contiguous_2m(struct page **pages,
+ u64 page_count)
+{
+ const u64 pages_per_large_page =
+ 1ULL << (HV_HYP_LARGE_PAGE_SHIFT - HV_HYP_PAGE_SHIFT);
+ u64 i, j;
+
+ if (!IS_ALIGNED(page_count, pages_per_large_page))
+ return false;
+
+ for (i = 0; i < page_count; i += pages_per_large_page) {
+ u64 base_pfn = page_to_pfn(pages[i]);
+
+ if (!IS_ALIGNED(base_pfn, pages_per_large_page))
+ return false;
+ for (j = 1; j < pages_per_large_page; j++)
+ if (page_to_pfn(pages[i + j]) != base_pfn + j)
+ return false;
+ }
+
+ return true;
+}
+
+static long
+mshv_partition_ioctl_modify_gpa_host_access(struct mshv_partition *partition,
+ void __user *user_args)
+{
+ struct mshv_modify_gpa_host_access args;
+ struct page **pages;
+ u64 *gpfns;
+ u64 i;
+ u32 host_access = 0;
+ u32 flags = 0;
+ bool acquire;
+ long ret;
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+
+ if ((args.flags & ~MSHV_GPA_HOST_ACCESS_FLAGS_MASK) ||
+ mshv_field_nonzero(args, rsvd) || !args.page_count)
+ return -EINVAL;
+
+ gpfns = vmemdup_user((char __user *)user_args +
+ offsetof(struct mshv_modify_gpa_host_access,
+ guest_pfns),
+ size_mul(sizeof(*gpfns), args.page_count));
+ if (IS_ERR(gpfns))
+ return PTR_ERR(gpfns);
+
+ pages = kvcalloc(args.page_count, sizeof(*pages), GFP_KERNEL);
+ if (!pages) {
+ ret = -ENOMEM;
+ goto free_gpfns;
+ }
+
+ for (i = 0; i < args.page_count; i++)
+ gpfns[i] = HVPFN_DOWN(gpfns[i]);
+
+ ret = mshv_gpfns_to_pages(partition, gpfns, args.page_count, pages);
+ if (ret)
+ goto free_pages;
+
+ if (args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_READABLE))
+ host_access |= HV_MAP_GPA_READABLE;
+ if (args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_WRITABLE))
+ host_access |= HV_MAP_GPA_WRITABLE;
+ if (args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_LARGE_PAGE)) {
+ if (!mshv_pages_are_contiguous_2m(pages, args.page_count)) {
+ ret = -EINVAL;
+ goto free_pages;
+ }
+ flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
+ }
+ acquire = args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_ACQUIRE);
+
+ ret = hv_call_modify_spa_host_access(partition->pt_id, pages,
+ args.page_count, host_access,
+ flags, acquire);
+
+free_pages:
+ kvfree(pages);
+free_gpfns:
+ kvfree(gpfns);
+ return ret;
+}
+
+static long
+mshv_partition_ioctl_import_isolated_pages(struct mshv_partition *partition,
+ void __user *user_args)
+{
+ struct mshv_import_isolated_pages args;
+ u64 *pages;
+ long ret;
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+
+ if (args.page_type >= MSHV_ISOLATED_PAGE_COUNT ||
+ mshv_field_nonzero(args, rsvd) || !args.page_count)
+ return -EINVAL;
+
+ pages = vmemdup_user((char __user *)user_args +
+ offsetof(struct mshv_import_isolated_pages,
+ guest_pfns),
+ size_mul(sizeof(*pages), args.page_count));
+ if (IS_ERR(pages))
+ return PTR_ERR(pages);
+
+ ret = mshv_init_async_handler(partition);
+ if (ret)
+ goto out;
+
+ ret = hv_call_import_isolated_pages(partition->pt_id, pages,
+ args.page_count, args.page_type,
+ HV_ISOLATED_PAGE_SIZE_4KB,
+ mshv_async_hvcall_handler,
+ partition);
+
+out:
+ kvfree(pages);
+ return ret;
+}
+
+static long
+mshv_partition_ioctl_complete_isolated_import(struct mshv_partition *partition,
+ void __user *user_args)
+{
+ union hv_partition_complete_isolated_import_data import_data;
+ struct mshv_complete_isolated_import args;
+ long ret;
+
+ static_assert(sizeof(import_data) == sizeof(args.import_data));
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+ memcpy(&import_data, &args.import_data, sizeof(import_data));
+
+ ret = mshv_init_async_handler(partition);
+ if (ret)
+ return ret;
+
+ ret = hv_call_complete_isolated_import(partition->pt_id, &import_data,
+ mshv_async_hvcall_handler,
+ partition);
+
+ if (!ret)
+ partition->import_completed = true;
+
+ return ret;
+}
+
+static long
+mshv_partition_ioctl_issue_psp_guest_request(struct mshv_partition *partition,
+ void __user *user_args)
+{
+ struct mshv_issue_psp_guest_request req;
+ u32 host_access = HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE;
+ struct page *pages[2];
+ u64 gpfns[2];
+ long ret;
+
+ if (copy_from_user(&req, user_args, sizeof(req)))
+ return -EFAULT;
+
+ gpfns[0] = HVPFN_DOWN(req.req_gpa);
+ gpfns[1] = HVPFN_DOWN(req.rsp_gpa);
+
+ ret = mshv_gpfns_to_pages(partition, gpfns, ARRAY_SIZE(gpfns), pages);
+ if (ret)
+ return ret;
+
+ ret = hv_call_modify_spa_host_access(partition->pt_id, pages,
+ ARRAY_SIZE(pages), 0, 0, false);
+ if (ret)
+ return ret;
+
+ ret = mshv_init_async_handler(partition);
+ if (ret)
+ goto restore_host_access;
+
+ ret = hv_call_issue_psp_guest_request(partition->pt_id,
+ HVPFN_DOWN(req.req_gpa),
+ HVPFN_DOWN(req.rsp_gpa),
+ mshv_async_hvcall_handler,
+ partition);
+ if (!ret)
+ return 0;
+
+restore_host_access:
+ hv_call_modify_spa_host_access(partition->pt_id, pages,
+ ARRAY_SIZE(pages), host_access, 0, true);
+ return ret;
+}
+
+static long mshv_partition_snp_ioctl(unsigned int ioctl,
+ struct mshv_partition *partition,
+ unsigned long arg)
+{
+ void __user *uarg = (void __user *)arg;
+
+ if (!mshv_partition_encrypted(partition)) {
+ pt_err(partition,
+ "Ioctl(%u) not supported for non SEV-SNP partition\n",
+ ioctl);
+ return -EOPNOTSUPP;
+ }
+
+ switch (ioctl) {
+ case MSHV_MODIFY_GPA_HOST_ACCESS:
+ return mshv_partition_ioctl_modify_gpa_host_access(partition,
+ uarg);
+ case MSHV_IMPORT_ISOLATED_PAGES:
+ return mshv_partition_ioctl_import_isolated_pages(partition,
+ uarg);
+ case MSHV_COMPLETE_ISOLATED_IMPORT:
+ return mshv_partition_ioctl_complete_isolated_import(partition,
+ uarg);
+ case MSHV_ISSUE_PSP_GUEST_REQUEST:
+ return mshv_partition_ioctl_issue_psp_guest_request(partition,
+ uarg);
+ case MSHV_SEV_SNP_AP_CREATE:
+ return mshv_partition_ioctl_sev_snp_ap_create(partition, uarg);
+ default:
+ return -ENOTTY;
+ }
+}
+#endif
+
static long
mshv_partition_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
@@ -1661,6 +1987,15 @@ mshv_partition_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
case MSHV_ROOT_HVCALL:
ret = mshv_ioctl_passthru_hvcall(partition, true, uarg);
break;
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+ case MSHV_MODIFY_GPA_HOST_ACCESS:
+ case MSHV_IMPORT_ISOLATED_PAGES:
+ case MSHV_COMPLETE_ISOLATED_IMPORT:
+ case MSHV_ISSUE_PSP_GUEST_REQUEST:
+ case MSHV_SEV_SNP_AP_CREATE:
+ ret = mshv_partition_snp_ioctl(ioctl, partition, arg);
+ break;
+#endif
default:
ret = -ENOTTY;
}
@@ -1772,6 +2107,80 @@ remove_partition(struct mshv_partition *partition)
synchronize_rcu();
}
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+static int destroy_snp_partition_state(struct mshv_partition *partition)
+{
+ struct hv_register_assoc explicit_suspend = {
+ .name = HV_REGISTER_EXPLICIT_SUSPEND,
+ .value.explicit_suspend.suspended = 1,
+ };
+ struct mshv_vp *vp;
+ int i, ret;
+
+ for (i = 0; i < MSHV_MAX_VPS; i++) {
+ vp = partition->pt_vp_array[i];
+ if (!vp)
+ continue;
+
+ ret = mshv_set_vp_registers(vp->vp_index,
+ vp->vp_partition->pt_id, 1,
+ &explicit_suspend);
+ if (ret) {
+ vp_err(vp, "Failed to set explicit suspend\n");
+ return ret;
+ }
+
+ ret = set_sev_control_register(vp, 0, 0);
+ if (ret) {
+ vp_err(vp, "Failed to clear SEV control register\n");
+ return ret;
+ }
+ }
+
+ if (partition->import_completed) {
+ union hv_partition_isolation_control isolation_control = {};
+
+ ret = mshv_init_async_handler(partition);
+ if (ret)
+ return ret;
+
+ ret = hv_call_set_partition_property(partition->pt_id,
+ HV_PARTITION_PROPERTY_ISOLATION_CONTROL,
+ isolation_control.as_uint64,
+ mshv_async_hvcall_handler, partition);
+ if (ret) {
+ pt_err(partition, "Failed to clear runnable bit\n");
+ return ret;
+ }
+ }
+
+ ret = mshv_init_async_handler(partition);
+ if (ret)
+ return ret;
+
+ ret = hv_call_set_partition_property(partition->pt_id,
+ HV_PARTITION_PROPERTY_ISOLATION_STATE,
+ HV_PARTITION_ISOLATION_INSECURE_DIRTY,
+ mshv_async_hvcall_handler, partition);
+ if (ret)
+ pt_err(partition,
+ "Failed to set isolation state to INSECURE_DIRTY\n");
+
+ return ret;
+}
+#endif
+
+static void destroy_partition(struct mshv_partition *partition);
+
+static void mshv_partition_destroy_work(struct work_struct *work)
+{
+ struct mshv_partition *partition;
+
+ partition = container_of(to_delayed_work(work), struct mshv_partition,
+ destroy_work);
+ destroy_partition(partition);
+}
+
/*
* Tear down a partition and remove it from the list.
* Partition's refcount must be 0
@@ -1782,6 +2191,9 @@ static void destroy_partition(struct mshv_partition *partition)
struct mshv_mem_region *region;
struct hlist_node *n;
int i;
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+ int ret;
+#endif
if (refcount_read(&partition->pt_ref_count)) {
pt_err(partition,
@@ -1792,6 +2204,25 @@ static void destroy_partition(struct mshv_partition *partition)
trace_mshv_destroy_partition(partition->pt_id);
if (partition->pt_initialized) {
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+ if (mshv_partition_encrypted(partition)) {
+ hlist_for_each_entry_safe(region, n,
+ &partition->pt_mem_regions,
+ hnode) {
+ hlist_del(®ion->hnode);
+ mshv_region_put(region);
+ }
+
+ ret = destroy_snp_partition_state(partition);
+ if (ret) {
+ pt_err(partition,
+ "Failed to destroy SNP state: %d; retrying\n",
+ ret);
+ schedule_delayed_work(&partition->destroy_work, HZ);
+ return;
+ }
+ }
+#endif
/*
* We only need to drain signals for root scheduler. This should be
* done before removing the partition from the partition list.
@@ -2016,6 +2447,9 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
case MSHV_PT_ISOLATION_NONE:
isol_props->isolation_type = HV_PARTITION_ISOLATION_TYPE_NONE;
break;
+ case MSHV_PT_ISOLATION_SNP:
+ isol_props->isolation_type = HV_PARTITION_ISOLATION_TYPE_SNP;
+ break;
}
return 0;
@@ -2051,6 +2485,8 @@ mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev)
mutex_init(&partition->pt_irq_lock);
init_completion(&partition->async_hypercall);
+ INIT_DELAYED_WORK(&partition->destroy_work,
+ mshv_partition_destroy_work);
INIT_HLIST_HEAD(&partition->irq_ack_notifier_list);
diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h
index a53c0f59994a..5553d5c918a7 100644
--- a/include/hyperv/hvgdk_mini.h
+++ b/include/hyperv/hvgdk_mini.h
@@ -975,6 +975,7 @@ enum hv_register_name {
HV_REGISTER_EXPLICIT_SUSPEND = 0x00000000,
HV_REGISTER_INTERCEPT_SUSPEND = 0x00000001,
HV_REGISTER_DISPATCH_SUSPEND = 0x00000003,
+ HV_REGISTER_INTERNAL_ACTIVITY_STATE = 0x00000004,
/* Version - 128-bit result same as CPUID 0x40000002 */
HV_REGISTER_HYPERVISOR_VERSION = 0x00000100,
@@ -1250,6 +1251,16 @@ union hv_arm64_pending_synthetic_exception_event {
} __packed;
};
+union hv_internal_activity_register {
+ u64 as_uint64;
+ struct {
+ u64 startup_suspend : 1;
+ u64 halt_suspend : 1;
+ u64 idle_suspend : 1;
+ u64 rsvd_z : 61;
+ } __packed;
+};
+
union hv_x64_interrupt_state_register {
u64 as_uint64;
struct {
@@ -1297,6 +1308,7 @@ union hv_register_value {
union hv_explicit_suspend_register explicit_suspend;
union hv_intercept_suspend_register intercept_suspend;
union hv_dispatch_suspend_register dispatch_suspend;
+ union hv_internal_activity_register internal_activity;
#ifdef CONFIG_ARM64
union hv_arm64_interrupt_state_register interrupt_state;
union hv_arm64_pending_interruption_register pending_interruption;
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 05/13] hyperv: fix hv_input_get_system_property layout for SNP status
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (3 preceding siblings ...)
2026-08-10 12:44 ` [PATCH v2 04/13] mshv: wire SEV-SNP partition ioctls Wei Hu
@ 2026-08-10 12:44 ` Wei Hu
2026-08-10 18:59 ` Wei Liu
2026-08-10 12:45 ` [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init Wei Hu
` (7 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:44 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Wei Hu
From: Wei Hu <weh@microsoft.com>
The processor-feature selector must sit at offset 8 (property_id + u32
reserved), matching the Microsoft Hypervisor ABI. Upstream 7.1's leaner
struct placed it at offset 4, so HVCALL_GET_SYSTEM_PROPERTY(SNP_STATUS)
failed and mshv aborted (no /dev/mshv). Restore reserved + u64 union.
Signed-off-by: Wei Hu <weh@microsoft.com>
---
include/hyperv/hvhdk_mini.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h
index db83fedce243..dd84085c0aff 100644
--- a/include/hyperv/hvhdk_mini.h
+++ b/include/hyperv/hvhdk_mini.h
@@ -237,8 +237,9 @@ enum hv_dynamic_processor_feature_property {
struct hv_input_get_system_property {
u32 property_id; /* enum hv_system_property */
+ u32 reserved;
union {
- u32 as_uint32;
+ u64 as_uint64;
#if IS_ENABLED(CONFIG_X86)
/* enum hv_dynamic_processor_feature_property */
u32 hv_processor_feature;
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (4 preceding siblings ...)
2026-08-10 12:44 ` [PATCH v2 05/13] hyperv: fix hv_input_get_system_property layout for SNP status Wei Hu
@ 2026-08-10 12:45 ` Wei Hu
2026-08-10 12:55 ` sashiko-bot
2026-08-10 18:53 ` Wei Liu
2026-08-10 12:45 ` [PATCH v2 07/13] mshv: default to safe partition CPU features Wei Hu
` (6 subsequent siblings)
12 siblings, 2 replies; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:45 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
Query Hyper-V dynamic processor features during mshv initialization to
determine SEV-SNP availability, and report the result: on success log the
maximum number of encrypted partitions, otherwise log the decoded SNP
status string so misconfigurations are diagnosable. Cloud Hypervisor
expects the root driver to perform this capability check before SEV-SNP
child partition setup.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 96 +++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 8c87dd8e0378..712f45dcef60 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2705,6 +2705,100 @@ static int mshv_root_scheduler_cleanup(unsigned int cpu)
}
/* Must be called after retrieving the scheduler type */
+#if defined(__x86_64__)
+static const char *hv_snp_status_to_string(enum hv_snp_status status)
+{
+ switch (status) {
+ case HV_SNP_STATUS_NONE:
+ return "not available";
+ case HV_SNP_STATUS_AVAILABLE:
+ return "available";
+ case HV_SNP_STATUS_INCOMPATIBLE:
+ return "incompatible";
+ case HV_SNP_STATUS_PSP_UNAVAILABLE:
+ return "PSP unavailable";
+ case HV_SNP_STATUS_PSP_INIT_FAILED:
+ return "PSP init failed";
+ case HV_SNP_STATUS_PSP_BAD_FW_VERSION:
+ return "bad PSP firmware version";
+ case HV_SNP_STATUS_BAD_CONFIGURATION:
+ return "bad configuration";
+ case HV_SNP_STATUS_PSP_FW_UPDATE_IN_PROGRESS:
+ return "PSP firmware update in progress";
+ case HV_SNP_STATUS_PSP_RB_INIT_FAILED:
+ return "PSP ring buffer init failed";
+ case HV_SNP_STATUS_PSP_PLATFORM_STATUS_FAILED:
+ return "PSP platform status failed";
+ case HV_SNP_STATUS_PSP_INIT_LATE_FAILED:
+ return "PSP late init failed";
+ default:
+ return "unknown";
+ }
+}
+
+static void mshv_print_max_sev_snp_partitions(struct device *dev)
+{
+ struct hv_input_get_system_property *input;
+ struct hv_output_get_system_property *output;
+ unsigned long flags;
+ u64 status;
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ output = *this_cpu_ptr(hyperv_pcpu_output_arg);
+
+ memset(input, 0, sizeof(*input));
+ input->property_id = HV_DYNAMIC_PROCESSOR_FEATURE_PROPERTY;
+ input->hv_processor_feature =
+ HV_X64_DYNAMIC_PROCESSOR_FEATURE_MAX_ENCRYPTED_PARTITIONS;
+
+ status = hv_do_hypercall(HVCALL_GET_SYSTEM_PROPERTY, input, output);
+ local_irq_restore(flags);
+ if (!hv_result_success(status)) {
+ dev_warn(dev, "Failed to get max SNP partitions: %s\n",
+ hv_result_to_string(status));
+ return;
+ }
+
+ dev_info(dev, "Maximum supported SEV-SNP partitions are: %llu\n",
+ output->hv_processor_feature_value);
+}
+
+static void __init mshv_check_sev_snp_support(struct device *dev)
+{
+ struct hv_input_get_system_property *input;
+ struct hv_output_get_system_property *output;
+ unsigned long flags;
+ enum hv_snp_status snp_status;
+ u64 status;
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ output = *this_cpu_ptr(hyperv_pcpu_output_arg);
+
+ memset(input, 0, sizeof(*input));
+ input->property_id = HV_DYNAMIC_PROCESSOR_FEATURE_PROPERTY;
+ input->hv_processor_feature = HV_X64_DYNAMIC_PROCESSOR_FEATURE_SNP_STATUS;
+
+ status = hv_do_hypercall(HVCALL_GET_SYSTEM_PROPERTY, input, output);
+ local_irq_restore(flags);
+ if (!hv_result_success(status)) {
+ dev_warn(dev, "Failed to get SNP support: %s\n",
+ hv_result_to_string(status));
+ return;
+ }
+
+ snp_status = output->hv_processor_feature_value;
+ dev_info(dev, "SEV-SNP support status: %s (%u)\n",
+ hv_snp_status_to_string(snp_status), snp_status);
+
+ if (snp_status == HV_SNP_STATUS_AVAILABLE)
+ mshv_print_max_sev_snp_partitions(dev);
+}
+#else
+static void __init mshv_check_sev_snp_support(struct device *dev) {}
+#endif
+
static int
root_scheduler_init(struct device *dev)
{
@@ -2808,6 +2902,8 @@ static int __init mshv_parent_partition_init(void)
if (ret)
goto synic_cleanup;
+ mshv_check_sev_snp_support(dev);
+
ret = root_scheduler_init(dev);
if (ret)
goto synic_cleanup;
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 07/13] mshv: default to safe partition CPU features
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (5 preceding siblings ...)
2026-08-10 12:45 ` [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init Wei Hu
@ 2026-08-10 12:45 ` Wei Hu
2026-08-10 12:57 ` sashiko-bot
2026-08-10 12:45 ` [PATCH v2 08/13] mshv: accept partial CPU feature banks Wei Hu
` (5 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:45 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
For legacy create-partition callers, disable all processor and xsave
features first, then enable only the default feature set known to be
supported. Passing an all-zero disabled feature mask can make SEV-SNP
partition creation fail on Hyper-V.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 99 ++++++++++++++++++++++++++++++++-----
1 file changed, 88 insertions(+), 11 deletions(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 712f45dcef60..ad2e708fd753 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2373,15 +2373,102 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
args.pt_isolation >= MSHV_PT_ISOLATION_COUNT)
return -EINVAL;
+ memset(cr_props, 0, sizeof(*cr_props));
+ memset(isol_props, 0, sizeof(*isol_props));
disabled_procs = &cr_props->disabled_processor_features;
disabled_xsave = &cr_props->disabled_processor_xsave_features;
+#if IS_ENABLED(CONFIG_X86_64)
+ /* Disable all processor features first. */
+ for (i = 0; i < HV_PARTITION_PROCESSOR_FEATURES_BANKS; i++)
+ disabled_procs->as_uint64[i] = U64_MAX;
+
+ /* Enable default features that are known to be supported. */
+ disabled_procs->sse3_support = 0;
+ disabled_procs->lahf_sahf_support = 0;
+ disabled_procs->ssse3_support = 0;
+ disabled_procs->sse4_1_support = 0;
+ disabled_procs->sse4_2_support = 0;
+ disabled_procs->sse4a_support = 0;
+ disabled_procs->xop_support = 0;
+ disabled_procs->pop_cnt_support = 0;
+ disabled_procs->cmpxchg16b_support = 0;
+ disabled_procs->altmovcr8_support = 0;
+ disabled_procs->lzcnt_support = 0;
+ disabled_procs->mis_align_sse_support = 0;
+ disabled_procs->mmx_ext_support = 0;
+ disabled_procs->amd3dnow_support = 0;
+ disabled_procs->extended_amd3dnow_support = 0;
+ disabled_procs->page_1gb_support = 0;
+ disabled_procs->aes_support = 0;
+ disabled_procs->pclmulqdq_support = 0;
+ disabled_procs->pcid_support = 0;
+ disabled_procs->fma4_support = 0;
+ disabled_procs->f16c_support = 0;
+ disabled_procs->rd_rand_support = 0;
+ disabled_procs->rd_wr_fs_gs_support = 0;
+ disabled_procs->smep_support = 0;
+ disabled_procs->enhanced_fast_string_support = 0;
+ disabled_procs->bmi1_support = 0;
+ disabled_procs->bmi2_support = 0;
+ disabled_procs->hle_support_deprecated = 0;
+ disabled_procs->rtm_support_deprecated = 0;
+ disabled_procs->movbe_support = 0;
+ disabled_procs->npiep1_support = 0;
+ disabled_procs->dep_x87_fpu_save_support = 0;
+ disabled_procs->rd_seed_support = 0;
+ disabled_procs->adx_support = 0;
+ disabled_procs->intel_prefetch_support = 0;
+ disabled_procs->smap_support = 0;
+ disabled_procs->hle_support = 0;
+ disabled_procs->rtm_support = 0;
+ disabled_procs->rdtscp_support = 0;
+ disabled_procs->clflushopt_support = 0;
+ disabled_procs->invpcid_support = 0;
+ disabled_procs->ibrs_support = 0;
+ disabled_procs->stibp_support = 0;
+ disabled_procs->ibpb_support = 0;
+ disabled_procs->unrestricted_guest_support = 0;
+ disabled_procs->mdd_support = 0;
+ disabled_procs->fast_short_rep_mov_support = 0;
+ disabled_procs->l1dcache_flush_support = 0;
+ disabled_procs->rdcl_no_support = 0;
+ disabled_procs->ibrs_all_support = 0;
+ disabled_procs->skip_l1df_support = 0;
+ disabled_procs->ssb_no_support = 0;
+ disabled_procs->rsb_a_no_support = 0;
+ disabled_procs->virt_spec_ctrl_support = 0;
+ disabled_procs->rd_pid_support = 0;
+ disabled_procs->umip_support = 0;
+ disabled_procs->mbs_no_support = 0;
+ disabled_procs->mb_clear_support = 0;
+ disabled_procs->taa_no_support = 0;
+ disabled_procs->tsx_ctrl_support = 0;
+ disabled_procs->tsc_invariant_support = 0;
+ disabled_procs->rdpru_support = 0;
+ disabled_procs->mbec_support = 0;
+ disabled_procs->psfd_support = 0;
+ disabled_procs->cet_ss_support = 0;
+ disabled_procs->cet_ibt_support = 0;
+ disabled_procs->vmx_exception_inject_support = 0;
+
+ disabled_xsave->as_uint64 = U64_MAX;
+ disabled_xsave->xsave_support = 0;
+ disabled_xsave->xsaveopt_support = 0;
+ disabled_xsave->avx_support = 0;
+#endif
+
/* Check if user provided newer struct with feature fields */
if (args.pt_flags & BIT_ULL(MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES)) {
+ u64 pt_flags = args.pt_flags;
+ u64 pt_isolation = args.pt_isolation;
+
if (copy_from_user(&args, user_arg, sizeof(args)))
return -EFAULT;
- /* Re-validate v1 fields after second copy_from_user() */
+ if (args.pt_flags != pt_flags ||
+ args.pt_isolation != pt_isolation)
+ return -EAGAIN;
if ((args.pt_flags & ~MSHV_PT_FLAGS_MASK) ||
args.pt_isolation >= MSHV_PT_ISOLATION_COUNT)
return -EINVAL;
@@ -2414,16 +2501,6 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
if (mshv_field_nonzero(args, pt_rsvd2))
return -EINVAL;
#endif
- } else {
- /*
- * v1 behavior: try to enable everything. The hypervisor will
- * disable features that are not supported. The banks can be
- * queried via the get partition property hypercall.
- */
- for (i = 0; i < HV_PARTITION_PROCESSOR_FEATURES_BANKS; i++)
- disabled_procs->as_uint64[i] = 0;
-
- disabled_xsave->as_uint64 = 0;
}
/* Only support EXO partitions */
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 08/13] mshv: accept partial CPU feature banks
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (6 preceding siblings ...)
2026-08-10 12:45 ` [PATCH v2 07/13] mshv: default to safe partition CPU features Wei Hu
@ 2026-08-10 12:45 ` Wei Hu
2026-08-10 12:45 ` [PATCH v2 09/13] mshv: define full processor and xsave feature masks Wei Hu
` (4 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:45 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
Allow userspace to provide fewer than MSHV_NUM_CPU_FEATURES_BANKS banks
for the create-partition v2 ioctl. Preserve the default disabled-feature
mask for omitted banks instead of rejecting the ioctl or overwriting all
banks.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 12 ++----------
include/uapi/linux/mshv.h | 3 ++-
2 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index ad2e708fd753..6b19cb66b0a1 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2473,20 +2473,12 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
args.pt_isolation >= MSHV_PT_ISOLATION_COUNT)
return -EINVAL;
- if (args.pt_num_cpu_fbanks != MSHV_NUM_CPU_FEATURES_BANKS ||
+ if (args.pt_num_cpu_fbanks > MSHV_NUM_CPU_FEATURES_BANKS ||
mshv_field_nonzero(args, pt_rsvd) ||
mshv_field_nonzero(args, pt_rsvd1))
return -EINVAL;
- /*
- * Note this assumes MSHV_NUM_CPU_FEATURES_BANKS will never
- * change and equals HV_PARTITION_PROCESSOR_FEATURES_BANKS
- * (i.e. 2).
- *
- * Further banks (index >= 2) will be modifiable as 'early'
- * properties via the set partition property hypercall.
- */
- for (i = 0; i < HV_PARTITION_PROCESSOR_FEATURES_BANKS; i++)
+ for (i = 0; i < args.pt_num_cpu_fbanks; i++)
disabled_procs->as_uint64[i] = args.pt_cpu_fbanks[i];
#if IS_ENABLED(CONFIG_X86_64)
diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
index fc89f6c9a968..452297b88484 100644
--- a/include/uapi/linux/mshv.h
+++ b/include/uapi/linux/mshv.h
@@ -66,7 +66,8 @@ struct mshv_create_partition {
* This is extended version of the above initial MSHV_CREATE_PARTITION
* ioctl and allows for following additional parameters:
*
- * @pt_num_cpu_fbanks: Must be set to MSHV_NUM_CPU_FEATURES_BANKS.
+ * @pt_num_cpu_fbanks: Number of processor feature banks provided. Must not
+ * exceed MSHV_NUM_CPU_FEATURES_BANKS.
* @pt_cpu_fbanks: Disabled processor feature banks array.
* @pt_disabled_xsave: Disabled xsave feature bits.
*
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 09/13] mshv: define full processor and xsave feature masks
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (7 preceding siblings ...)
2026-08-10 12:45 ` [PATCH v2 08/13] mshv: accept partial CPU feature banks Wei Hu
@ 2026-08-10 12:45 ` Wei Hu
2026-08-10 12:45 ` [PATCH v2 10/13] mshv: unmap SNP memory before state teardown Wei Hu
` (3 subsequent siblings)
12 siblings, 0 replies; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:45 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
Add the missing processor and xsave feature bit definitions and use
them when constructing the default disabled-feature mask, so legacy
SNP partition creation applies a complete, well-defined feature mask.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 10 +++++
include/hyperv/hvhdk.h | 79 ++++++++++++++++++++++++++++++++++++-
2 files changed, 87 insertions(+), 2 deletions(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 6b19cb66b0a1..c32d3375af8c 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2451,11 +2451,21 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
disabled_procs->cet_ss_support = 0;
disabled_procs->cet_ibt_support = 0;
disabled_procs->vmx_exception_inject_support = 0;
+ disabled_procs->fb_clear_support = 0;
+ disabled_procs->gds_no_support = 0;
+ disabled_procs->bhi_no_support = 0;
+ disabled_procs->bhi_dis_support = 0;
+ disabled_procs->btc_no_support = 0;
+ disabled_procs->mitigation_ctrl_support = 0;
+ disabled_procs->rfds_no_support = 0;
+ disabled_procs->rfds_clear_support = 0;
disabled_xsave->as_uint64 = U64_MAX;
disabled_xsave->xsave_support = 0;
disabled_xsave->xsaveopt_support = 0;
disabled_xsave->avx_support = 0;
+ disabled_xsave->xsave_comp_support = 0;
+ disabled_xsave->xsave_supervisor_support = 0;
#endif
/* Check if user provided newer struct with feature fields */
diff --git a/include/hyperv/hvhdk.h b/include/hyperv/hvhdk.h
index 691ac495a095..c8c0aca48f4e 100644
--- a/include/hyperv/hvhdk.h
+++ b/include/hyperv/hvhdk.h
@@ -231,7 +231,47 @@ union hv_partition_processor_features {
u64 fzlrep_movsb : 1;
u64 fsrep_stosb : 1;
u64 fsrep_cmpsb : 1;
- u64 reserved_bank1 : 42;
+ u64 tsx_ld_trk_support : 1;
+ u64 vmx_ins_outs_exit_info_support : 1;
+ u64 hlat_support : 1;
+ u64 sbdr_ssdp_no_support : 1;
+ u64 fbsdp_no_support : 1;
+ u64 psdp_no_support : 1;
+ u64 fb_clear_support : 1;
+ u64 btc_no_support : 1;
+ u64 ibpb_rsb_flush_support : 1;
+ u64 stibp_always_on_support : 1;
+ u64 perf_global_ctrl_support : 1;
+ u64 npt_execute_only_support : 1;
+ u64 npt_ad_flags_support : 1;
+ u64 npt1_gb_page_support : 1;
+ u64 amd_processor_topology_node_id_support : 1;
+ u64 local_machine_check_support : 1;
+ u64 extended_topology_leaf_fp256_amd_support : 1;
+ u64 gds_no_support : 1;
+ u64 cmpccxadd_support : 1;
+ u64 tsc_aux_virtualization_support : 1;
+ u64 rmp_query_support : 1;
+ u64 bhi_no_support : 1;
+ u64 bhi_dis_support : 1;
+ u64 prefetch_i_support : 1;
+ u64 sha512_support : 1;
+ u64 mitigation_ctrl_support : 1;
+ u64 rfds_no_support : 1;
+ u64 rfds_clear_support : 1;
+ u64 sm3_support : 1;
+ u64 sm4_support : 1;
+ u64 secure_avic_support : 1;
+ u64 guest_intercept_ctrl_support : 1;
+ u64 sbpb_supported : 1;
+ u64 ibpb_br_type_supported : 1;
+ u64 srso_no_supported : 1;
+ u64 srso_user_kernel_no_supported : 1;
+ u64 verw_clear_supported : 1;
+ u64 tsa_l1_no_supported : 1;
+ u64 tsa_sq_no_supported : 1;
+ u64 lass_support : 1;
+ u64 reserved_bank1 : 2;
} __packed;
};
@@ -240,7 +280,42 @@ union hv_partition_processor_xsave_features {
u64 xsave_support : 1;
u64 xsaveopt_support : 1;
u64 avx_support : 1;
- u64 reserved1 : 61;
+ u64 avx2_support : 1;
+ u64 fma_support : 1;
+ u64 mpx_support : 1;
+ u64 avx512_support : 1;
+ u64 avx512_dq_support : 1;
+ u64 avx512_cd_support : 1;
+ u64 avx512_bw_support : 1;
+ u64 avx512_vl_support : 1;
+ u64 xsave_comp_support : 1;
+ u64 xsave_supervisor_support : 1;
+ u64 xcr1_support : 1;
+ u64 avx512_bitalg_support : 1;
+ u64 avx512_i_fma_support : 1;
+ u64 avx512_v_bmi_support : 1;
+ u64 avx512_v_bmi2_support : 1;
+ u64 avx512_vnni_support : 1;
+ u64 gfni_support : 1;
+ u64 vaes_support : 1;
+ u64 avx512_v_popcntdq_support : 1;
+ u64 vpclmulqdq_support : 1;
+ u64 avx512_bf16_support : 1;
+ u64 avx512_vp2_intersect_support : 1;
+ u64 avx512_fp16_support : 1;
+ u64 xfd_support : 1;
+ u64 amx_tile_support : 1;
+ u64 amx_bf16_support : 1;
+ u64 amx_int8_support : 1;
+ u64 avx_vnni_support : 1;
+ u64 avx_ifma_support : 1;
+ u64 avx_ne_convert_support : 1;
+ u64 avx_vnni_int8_support : 1;
+ u64 avx_vnni_int16_support : 1;
+ u64 avx10_1_256_support : 1;
+ u64 avx10_1_512_support : 1;
+ u64 amx_fp16_support : 1;
+ u64 reserved1 : 26;
} __packed;
u64 as_uint64;
};
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 10/13] mshv: unmap SNP memory before state teardown
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (8 preceding siblings ...)
2026-08-10 12:45 ` [PATCH v2 09/13] mshv: define full processor and xsave feature masks Wei Hu
@ 2026-08-10 12:45 ` Wei Hu
2026-08-10 13:13 ` sashiko-bot
2026-08-10 12:45 ` [PATCH v2 11/13] mshv: unlock SNP pages on panic for crashdump collection Wei Hu
` (2 subsequent siblings)
12 siblings, 1 reply; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:45 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li
From: Wei Liu <wei.liu@kernel.org>
Unmap memory regions before destroying SNP partition state, and only
release regions after SNP state is torn down. Dropping regions first
attempts to regain host access while the partition is still secure and
can leave pages inaccessible.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_regions.c | 2 +-
drivers/hv/mshv_root.h | 1 +
drivers/hv/mshv_root_main.c | 6 ++----
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 6d65e5b42152..62c49b4d2a33 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -337,7 +337,7 @@ static int mshv_region_chunk_unmap(struct mshv_mem_region *region,
page_count, flags);
}
-static int mshv_region_unmap(struct mshv_mem_region *region)
+int mshv_region_unmap(struct mshv_mem_region *region)
{
return mshv_region_process_range(region, 0,
0, region->nr_pages,
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index ee52797727cc..0a3e236a7873 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -372,6 +372,7 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
int mshv_region_share(struct mshv_mem_region *region);
int mshv_region_unshare(struct mshv_mem_region *region);
int mshv_region_map(struct mshv_mem_region *region);
+int mshv_region_unmap(struct mshv_mem_region *region);
void mshv_region_invalidate(struct mshv_mem_region *region);
int mshv_region_pin(struct mshv_mem_region *region);
void mshv_region_put(struct mshv_mem_region *region);
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index c32d3375af8c..0dbf509351bb 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2208,10 +2208,8 @@ static void destroy_partition(struct mshv_partition *partition)
if (mshv_partition_encrypted(partition)) {
hlist_for_each_entry_safe(region, n,
&partition->pt_mem_regions,
- hnode) {
- hlist_del(®ion->hnode);
- mshv_region_put(region);
- }
+ hnode)
+ mshv_region_unmap(region);
ret = destroy_snp_partition_state(partition);
if (ret) {
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 11/13] mshv: unlock SNP pages on panic for crashdump collection
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (9 preceding siblings ...)
2026-08-10 12:45 ` [PATCH v2 10/13] mshv: unmap SNP memory before state teardown Wei Hu
@ 2026-08-10 12:45 ` Wei Hu
2026-08-10 13:10 ` sashiko-bot
2026-08-10 12:45 ` [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader) Wei Hu
2026-08-10 12:45 ` [PATCH v2 13/13] mshv: set up own SynIC registers on a nested root partition Wei Hu
12 siblings, 1 reply; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:45 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Wei Hu, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin
From: Wei Hu <weh@microsoft.com>
Add an SNP panic-path page-unlock feature so a root partition running
confidential (SEV-SNP) guests can still collect a Linux root vmcore via
kexec after a crash. On panic, guest pages of every encrypted partition
are unmapped and shared back to the host so kexec can read them.
Register a panic notifier that walks mshv_root.pt_htable and, for each
encrypted partition, unmaps each memory region and marks it shared again.
The notifier is only registered when the hypervisor's own crash path is
not active (hv_crash_enabled) and only built when CONFIG_CRASH_DUMP is
enabled. Declare hv_crash_enabled in asm/mshyperv.h alongside the other
MSHV crashdump symbols.
Tested on a SEV-SNP Dom0: the notifier only runs in its intended fallback
path, i.e. when the hypervisor's own kdump support is inactive
(hv_crash_enabled == false); when it is active the hypervisor stops the
CPUs and kexecs before the Linux panic_notifier_list runs. To exercise the
ported code that fallback was reproduced on a test kernel (hypervisor crash
init disabled so hv_crash_enabled == false). With a confidential guest
running, an intentional Dom0 panic showed the notifier fire, find the
encrypted partition, and unlock its regions with no errors ("SNP pages are
unlocked for panic", no "Unlock snp failed"), after which kdump collected a
valid, readable vmcore. The default active-hypervisor path was also
confirmed to boot cleanly with the notifier correctly not registered.
Signed-off-by: Wei Hu <weh@microsoft.com>
---
arch/x86/include/asm/mshyperv.h | 1 +
drivers/hv/mshv_root_main.c | 78 +++++++++++++++++++++++++++++++++
2 files changed, 79 insertions(+)
diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
index f64393e853ee..052cc44ae1f0 100644
--- a/arch/x86/include/asm/mshyperv.h
+++ b/arch/x86/include/asm/mshyperv.h
@@ -240,6 +240,7 @@ static __always_inline u64 hv_raw_get_msr(unsigned int reg)
int hv_apicid_to_vp_index(u32 apic_id);
#if IS_ENABLED(CONFIG_MSHV_ROOT) && IS_ENABLED(CONFIG_CRASH_DUMP)
+extern bool hv_crash_enabled;
void hv_root_crash_init(void);
void hv_crash_asm32(void);
void hv_crash_asm64(void);
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 0dbf509351bb..6b38a45ad43c 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2941,6 +2941,81 @@ static int __init mshv_init_vmm_caps(struct device *dev)
return 0;
}
+#if defined(CONFIG_X86_64) && IS_ENABLED(CONFIG_CRASH_DUMP)
+static void mshv_panic_unlock_snp(struct mshv_partition *vm)
+{
+ struct mshv_mem_region *memreg;
+ int ret;
+
+ if (!spin_trylock(&vm->pt_mem_regions_lock)) {
+ pt_err(vm, "Cannot lock SNP memory regions during panic\n");
+ return;
+ }
+
+ hlist_for_each_entry(memreg, &vm->pt_mem_regions, hnode) {
+ mshv_region_unmap(memreg);
+ ret = mshv_region_share(memreg);
+ if (ret)
+ pt_err(vm, "Unlock snp failed. ret:0x%x gfn:%llx numpfns:%lld\n",
+ ret, memreg->start_gfn, memreg->nr_pages);
+ }
+
+ spin_unlock(&vm->pt_mem_regions_lock);
+}
+
+static int mshv_root_panic_cb(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ int i, done = 0;
+ struct mshv_partition *pt;
+ struct device *dev = NULL;
+
+ hash_for_each_rcu(mshv_root.pt_htable, i, pt, pt_hnode) {
+ if (!mshv_partition_encrypted(pt))
+ continue;
+
+ done = 1;
+ mshv_panic_unlock_snp(pt);
+ dev = pt->pt_module_dev;
+ }
+ if (done && dev)
+ dev_info(dev, "SNP pages are unlocked for panic\n");
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block mshv_root_panic_blk = {
+ .notifier_call = mshv_root_panic_cb,
+};
+
+/*
+ * If mshv devirt setup failed during boot, or the feature itself is not
+ * available, allow the system to at least collect linux root vmcore. For
+ * that, snp guest pages must be made readable in the panic path so kexec can
+ * collect them.
+ */
+static void mshv_crashdump_init(void)
+{
+ if (hv_crash_enabled)
+ return;
+
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &mshv_root_panic_blk);
+}
+
+static void mshv_crashdump_deinit(void)
+{
+ if (hv_crash_enabled)
+ return;
+
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &mshv_root_panic_blk);
+}
+#else
+static void mshv_crashdump_init(void) {}
+static void mshv_crashdump_deinit(void) {}
+#endif
+
static int __init mshv_parent_partition_init(void)
{
int ret;
@@ -2998,6 +3073,8 @@ static int __init mshv_parent_partition_init(void)
hv_setup_mshv_handler(mshv_isr);
+ mshv_crashdump_init();
+
return 0;
exit_debugfs:
@@ -3013,6 +3090,7 @@ static int __init mshv_parent_partition_init(void)
static void __exit mshv_parent_partition_exit(void)
{
+ mshv_crashdump_deinit();
hv_setup_mshv_handler(NULL);
mshv_port_table_fini();
mshv_debugfs_exit();
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader)
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (10 preceding siblings ...)
2026-08-10 12:45 ` [PATCH v2 11/13] mshv: unlock SNP pages on panic for crashdump collection Wei Hu
@ 2026-08-10 12:45 ` Wei Hu
2026-08-10 13:05 ` sashiko-bot
2026-08-10 18:50 ` Wei Liu
2026-08-10 12:45 ` [PATCH v2 13/13] mshv: set up own SynIC registers on a nested root partition Wei Hu
12 siblings, 2 replies; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:45 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Wei Hu, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Ard Biesheuvel,
Ilias Apalodimas, Arnd Bergmann, linux-efi, linux-arch
From: Wei Hu <weh@microsoft.com>
Port the EFI HvLoader protocol handshake (efi-mshv stub + SETUP_MSHV) and
the hyperv_resvd_new memory reservation so an upstream kernel can boot as
a Microsoft Hypervisor (MSHV) root partition. Mainline lacks this
Linux-side enablement, so lxhvloader declines to hand off a stock kernel
as root.
Signed-off-by: Wei Hu <weh@microsoft.com>
---
MAINTAINERS | 2 +
arch/x86/hyperv/hv_init.c | 3 +
arch/x86/include/uapi/asm/setup_data.h | 3 +-
arch/x86/kernel/cpu/mshyperv.c | 81 ++++++-
drivers/firmware/efi/libstub/Makefile | 5 +-
.../firmware/efi/libstub/efi-mshv-common.c | 148 ++++++++++++
drivers/firmware/efi/libstub/efi-mshv.h | 109 +++++++++
drivers/firmware/efi/libstub/x86-efi-mshv.c | 217 ++++++++++++++++++
drivers/firmware/efi/libstub/x86-stub.c | 18 +-
drivers/hv/hv_common.c | 82 +++++++
include/asm-generic/mshyperv.h | 7 +
include/linux/efi.h | 1 +
12 files changed, 672 insertions(+), 4 deletions(-)
create mode 100644 drivers/firmware/efi/libstub/efi-mshv-common.c
create mode 100644 drivers/firmware/efi/libstub/efi-mshv.h
create mode 100644 drivers/firmware/efi/libstub/x86-efi-mshv.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 716acfc3d7c1..0da3c7929aef 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12098,6 +12098,8 @@ F: arch/x86/include/asm/mshyperv.h
F: arch/x86/include/asm/trace/hyperv.h
F: arch/x86/kernel/cpu/mshyperv.c
F: drivers/clocksource/hyperv_timer.c
+F: drivers/firmware/efi/libstub/*-efi-mshv.c
+F: drivers/firmware/efi/libstub/efi-mshv*
F: drivers/hid/hid-hyperv.c
F: drivers/hv/
F: drivers/input/serio/hyperv-keyboard.c
diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c
index 55a8b6de2865..4ee8035cb990 100644
--- a/arch/x86/hyperv/hv_init.c
+++ b/arch/x86/hyperv/hv_init.c
@@ -557,6 +557,9 @@ void __init hyperv_init(void)
hv_remap_tsc_clocksource();
hv_sleep_notifiers_register();
+
+ /* mark ram reserved for hypervisor as owned by hypervisor */
+ hv_mark_resources();
} else {
hypercall_msr.guest_physical_address = vmalloc_to_pfn(hv_hypercall_pg);
wrmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
diff --git a/arch/x86/include/uapi/asm/setup_data.h b/arch/x86/include/uapi/asm/setup_data.h
index 2671c4e1b3a0..e88f7bf709ab 100644
--- a/arch/x86/include/uapi/asm/setup_data.h
+++ b/arch/x86/include/uapi/asm/setup_data.h
@@ -14,7 +14,8 @@
#define SETUP_IMA 8
#define SETUP_RNG_SEED 9
#define SETUP_KEXEC_KHO 10
-#define SETUP_ENUM_MAX SETUP_KEXEC_KHO
+#define SETUP_MSHV 11
+#define SETUP_ENUM_MAX SETUP_MSHV
#define SETUP_INDIRECT (1<<31)
#define SETUP_TYPE_MAX (SETUP_ENUM_MAX | SETUP_INDIRECT)
diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
index 185d4f677ec0..63504b22b5ba 100644
--- a/arch/x86/kernel/cpu/mshyperv.c
+++ b/arch/x86/kernel/cpu/mshyperv.c
@@ -13,6 +13,8 @@
#include <linux/export.h>
#include <linux/hardirq.h>
#include <linux/efi.h>
+#include <linux/memblock.h>
+#include <linux/crash_dump.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kexec.h>
@@ -22,6 +24,7 @@
#include <asm/cpuid/api.h>
#include <hyperv/hvhdk.h>
#include <asm/mshyperv.h>
+#include <asm/e820/api.h>
#include <asm/desc.h>
#include <asm/idtentry.h>
#include <asm/irq_regs.h>
@@ -496,6 +499,76 @@ EXPORT_SYMBOL_GPL(hv_get_hypervisor_version);
* Reserved vectors hard coded in the hypervisor. If used outside, the hypervisor
* will either crash or hang or attempt to break into debugger.
*/
+bool mshv_loader_new = true;
+
+static int hv_resvd_ranges[HV_MAX_RESVD_RANGES] = {
+ [0 ... HV_MAX_RESVD_RANGES - 1] = -1};
+
+/*
+ * Parse eg "hyperv_resvd=3,7,20" where 3, 7, and 20 are indexes into the e820
+ * table for ranges that are reserved by the loader for the hypervisor
+ */
+static int __init hv_parse_hyperv_resvd(char *arg)
+{
+ int idx, max = ARRAY_SIZE(hv_resvd_ranges);
+ int i = 0;
+
+ mshv_loader_new = false;
+
+ if (is_kdump_kernel())
+ return 0;
+
+ if (hv_resvd_ranges[0] != -1) {
+ pr_err("Hyper-V: multiple hyperv_resvd not supported\n");
+ return 0;
+ }
+
+ while (get_option(&arg, &idx)) {
+ if (i >= max) {
+ pr_err("Hyper-V: resvd ranges tbl full %d\n", idx);
+ break;
+ }
+
+ hv_resvd_ranges[i++] = idx;
+ }
+
+ return 0;
+}
+early_param("hyperv_resvd", hv_parse_hyperv_resvd);
+
+/*
+ * Reserve memory that the hypervisor is using early on. The ranges are marked
+ * reserved by a custom bootloader, change that to usable and reserve that
+ * range. Note, the bootloader sanitizes the e820 before passing on here.
+ */
+static void __init hv_resv_mshv_memory(void)
+{
+ u64 start, end, size;
+ int i, idx, max = ARRAY_SIZE(hv_resvd_ranges);
+ int resource_index = 0;
+
+ for (i = 0; i < max && hv_resvd_ranges[i] != -1; i++) {
+ idx = hv_resvd_ranges[i];
+ if (idx < 0 || idx >= e820_table->nr_entries) {
+ pr_info("Hyper-V: invalid resvd idx %d\n", idx);
+ continue;
+ }
+
+ start = e820_table->entries[idx].addr;
+ size = e820_table->entries[idx].size;
+ end = start + size - 1;
+
+ memblock_reserve(start, size);
+ e820_table->entries[idx].type = E820_TYPE_RAM;
+ pr_info("Hyper-V reserve [mem %#018Lx-%#018Lx]\n", start, end);
+
+ hv_mshv_res[resource_index].name = "Hypervisor Code and Data";
+ hv_mshv_res[resource_index].flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM;
+ hv_mshv_res[resource_index].start = start;
+ hv_mshv_res[resource_index++].end = end;
+ }
+}
+
static void hv_reserve_irq_vectors(void)
{
#define HYPERV_DBG_FASTFAIL_VECTOR 0x29
@@ -547,8 +620,14 @@ static void __init ms_hyperv_init_platform(void)
hv_identify_partition_type();
- if (hv_root_partition())
+ if (hv_root_partition()) {
+ /* very first thing, reserve/log exclusive hypervisor memory */
+ if (mshv_loader_new)
+ hv_dump_mshv_memory();
+ else
+ hv_resv_mshv_memory();
hv_reserve_irq_vectors();
+ }
if (cc_platform_has(CC_ATTR_SNP_SECURE_AVIC))
ms_hyperv.hints |= HV_DEPRECATING_AEOI_RECOMMENDED;
diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
index 77a2b2d74f3f..ee61b4fcf701 100644
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -87,7 +87,10 @@ lib-$(CONFIG_X86) += x86-stub.o smbios.o
lib-$(CONFIG_X86_64) += x86-5lvl.o
lib-$(CONFIG_RISCV) += kaslr.o riscv.o riscv-stub.o
lib-$(CONFIG_LOONGARCH) += loongarch.o loongarch-stub.o
-
+ifdef CONFIG_X86_64
+lib-$(subst m,y,$(CONFIG_MSHV_ROOT)) += efi-mshv-common.o
+lib-$(CONFIG_MSHV_ROOT) += x86-efi-mshv.o
+endif
CFLAGS_arm32-stub.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
zboot-obj-y := zboot-decompress-gzip.o
diff --git a/drivers/firmware/efi/libstub/efi-mshv-common.c b/drivers/firmware/efi/libstub/efi-mshv-common.c
new file mode 100644
index 000000000000..ab6307f7443c
--- /dev/null
+++ b/drivers/firmware/efi/libstub/efi-mshv-common.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "efistub.h"
+#include "efi-mshv.h"
+
+struct efi_hvloader_protocol *efi_mshv;
+
+efi_status_t mshv_efi_init(void)
+{
+ efi_status_t status;
+ static efi_guid_t hv_proto_guid = EFI_MSHV_MEDIA_PROTOCOL_GUID;
+
+ status = efi_bs_call(locate_protocol,
+ &hv_proto_guid, NULL, (void **)&efi_mshv);
+ if (status == EFI_NOT_FOUND) {
+ /* If the protocol is not installed we are in a standard Linux boot */
+ return status;
+ } else if (status != EFI_SUCCESS) {
+ mshv_efi_reboot("LocateProtocol failed unexpectedly with code %d",
+ status);
+ }
+
+ status = efi_mshv->get_loader_init_status();
+ if (status != EFI_SUCCESS)
+ mshv_efi_reboot("mshv protocol installed but seems to have failed with code %d",
+ status);
+
+ return EFI_SUCCESS;
+}
+
+void mshv_get_hv_ranges(efi_memory_desc_t **mem_map, unsigned long *map_sz,
+ unsigned long *desc_sz)
+{
+ efi_status_t status;
+
+ status = efi_mshv->get_hv_ranges((void **)mem_map, map_sz, desc_sz);
+ if (status != EFI_SUCCESS)
+ mshv_efi_reboot("failed to retrieve mshv ranges: error code %d",
+ status);
+}
+
+/*
+ * Concatenate the hypervisor reserved ranges to the command line.
+ *
+ * The reserved ranges are formatted as follows:
+ * 'hyperv_resvd_new=<size>!<address>,<size>!<address>,...'
+ *
+ * @mem_map: EFI memory map with the hypervisor reserved ranges
+ * @map_sz: size of the memory map
+ * @desc_sz: size of each descriptor in the memory map
+ * @old_cmdline: old command line
+ * @buf: buffer to hold the new command line
+ */
+efi_status_t mshv_efi_update_cmdline(efi_memory_desc_t *mem_map,
+ unsigned long map_sz,
+ unsigned long desc_sz,
+ const char *old_cmdline,
+ char *buf, unsigned long buf_sz)
+{
+ unsigned long cmdline_len = 0;
+ int i, nr_desc, written;
+
+ if (old_cmdline) {
+ cmdline_len = strnlen(old_cmdline, buf_sz);
+ if (cmdline_len == buf_sz)
+ return EFI_BUFFER_TOO_SMALL;
+ memcpy(buf, old_cmdline, cmdline_len);
+ }
+
+ written = snprintf(buf + cmdline_len, buf_sz - cmdline_len,
+ "%shyperv_resvd_new=", cmdline_len ? " " : "");
+ if (written < 0 || written >= buf_sz - cmdline_len)
+ return EFI_BUFFER_TOO_SMALL;
+ cmdline_len += written;
+
+ nr_desc = map_sz / desc_sz;
+ for (i = 0; i < nr_desc; ++i) {
+ efi_memory_desc_t *d;
+ u64 start, sz;
+
+ d = efi_memdesc_ptr(mem_map, desc_sz, i);
+ start = d->phys_addr;
+ sz = d->num_pages << PAGE_SHIFT;
+
+ written = snprintf(buf + cmdline_len, buf_sz - cmdline_len,
+ "%s0x%llx!0x%llx", i ? "," : "",
+ sz, start);
+ if (written < 0 || written >= buf_sz - cmdline_len)
+ return EFI_BUFFER_TOO_SMALL;
+ cmdline_len += written;
+ }
+
+ return EFI_SUCCESS;
+}
+
+efi_status_t mshv_set_efi_rt_range(struct efi_boot_memmap *map)
+{
+ u32 nr_desc;
+ int i;
+ efi_status_t status;
+
+ if (!efi_mshv)
+ return EFI_SUCCESS;
+
+ nr_desc = map->map_size / map->desc_size;
+
+ for (i = 0; i < nr_desc; i++) {
+ efi_memory_desc_t *d;
+
+ d = efi_memdesc_ptr(map->map, map->desc_size, i);
+ switch (d->type) {
+ case EFI_RUNTIME_SERVICES_CODE:
+ case EFI_RUNTIME_SERVICES_DATA:
+ status = efi_mshv->register_range(d->phys_addr >> PAGE_SHIFT,
+ d->num_pages);
+ if (status != EFI_SUCCESS)
+ return status;
+ break;
+ default:
+ /* default case: range is not relevant to mshv */
+ break;
+ }
+ }
+
+ return EFI_SUCCESS;
+}
+
+/*
+ * Launch mshv, if enabled.
+ *
+ * If mshv reports a bad status at this point, abort the boot.
+ * To get more information about the failure, the HV loader's internal
+ * logging can be used, which is exposed via efi_hv->get_next_log_msg(...).
+ *
+ */
+efi_status_t mshv_launch(void)
+{
+ struct hvl_return_data ret;
+
+ if (!efi_mshv)
+ return EFI_SUCCESS;
+
+ efi_mshv->launch_hv(NULL, &ret);
+ /* TODO: Where/how do we dump the hv loader logs? */
+ if (ret.launch_data.launch_status != 0)
+ efi_rt_call(reset_system, EFI_RESET_COLD, EFI_ABORTED, 0, NULL);
+ return EFI_SUCCESS;
+}
diff --git a/drivers/firmware/efi/libstub/efi-mshv.h b/drivers/firmware/efi/libstub/efi-mshv.h
new file mode 100644
index 000000000000..3d8f534808ed
--- /dev/null
+++ b/drivers/firmware/efi/libstub/efi-mshv.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _DRIVERS_FIRMWARE_EFI_MSHV_H
+#define _DRIVERS_FIRMWARE_EFI_MSHV_H
+
+#include "efistub.h"
+
+#if IS_ENABLED(CONFIG_MSHV_ROOT)
+
+extern struct efi_hvloader_protocol *efi_mshv;
+
+static inline void mshv_efi_reboot(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ efi_printk(fmt, args);
+ va_end(args);
+
+ efi_bs_call(stall, 5 * EFI_USEC_PER_SEC);
+ efi_rt_call(reset_system, EFI_RESET_COLD, EFI_ABORTED, 0, NULL);
+}
+
+efi_status_t mshv_efi_init(void);
+void mshv_get_hv_ranges(efi_memory_desc_t **mem_map, unsigned long *map_sz,
+ unsigned long *desc_sz);
+efi_status_t mshv_efi_update_cmdline(efi_memory_desc_t *mem_map,
+ unsigned long map_sz,
+ unsigned long desc_sz,
+ const char *cmdline,
+ char *buf, unsigned long buf_sz);
+
+#ifdef CONFIG_X86_64
+efi_status_t mshv_efi_setup(struct boot_params *boot_params);
+#endif /* CONFIG_X86_64 */
+
+efi_status_t mshv_set_efi_rt_range(struct efi_boot_memmap *map);
+efi_status_t mshv_launch(void);
+
+struct hvl_dbg_data {
+ u8 unused[552];
+} __packed;
+
+struct hvl_launch_data {
+ u64 launch_status;
+ u64 launch_substatus1;
+} __packed;
+
+struct hvl_load_data {
+ u32 is_unsafe_config:1;
+ u32 reserved:31;
+} __packed;
+
+struct hvl_return_data {
+ u32 crash_dump_area_page_count;
+ u32 unused;
+ u64 crashdump_area_spa;
+ union {
+ struct hvl_launch_data launch_data;
+ struct hvl_load_data load_data;
+ };
+ struct hvl_dbg_data debug_data;
+ void *spa_page_range_array;
+ u32 range_count;
+
+ struct {
+ u32 base_checksum;
+ u32 base_timestamp;
+ u32 patch_checksum;
+ u32 patch_timestamp;
+ u32 base_hpat_entries_used;
+ u32 patch_hpat_entries_used;
+ u32 patch_sequence_number;
+ } patch_details;
+} __packed;
+
+struct efi_hvloader_protocol {
+ void (__efiapi * launch_hv)(void *, struct hvl_return_data *);
+ efi_status_t (__efiapi * register_range)(u64, u64);
+ efi_status_t (__efiapi * get_memory_map)(unsigned long *, void *,
+ unsigned long *,
+ unsigned long *, u32 *);
+ efi_status_t (__efiapi * get_hv_ranges)(void **,
+ unsigned long *,
+ unsigned long *);
+ efi_status_t (__efiapi * get_loader_init_status)(void);
+ efi_char16_t *(__efiapi * get_next_log_msg)(size_t *);
+};
+
+#else /* CONFIG_MSHV_ROOT */
+#ifdef CONFIG_X86_64
+static inline efi_status_t mshv_efi_setup(struct boot_params *boot_params)
+{
+ return EFI_SUCCESS;
+}
+#endif /* CONFIG_X86_64 */
+
+static inline efi_status_t mshv_set_efi_rt_range(struct efi_boot_memmap *map)
+{
+ return EFI_SUCCESS;
+}
+
+static inline efi_status_t mshv_launch(void)
+{
+ return EFI_SUCCESS;
+}
+#endif /* !CONFIG_MSHV_ROOT */
+
+#endif /* _DRIVERS_FIRMWARE_EFI_MSHV_H */
diff --git a/drivers/firmware/efi/libstub/x86-efi-mshv.c b/drivers/firmware/efi/libstub/x86-efi-mshv.c
new file mode 100644
index 000000000000..771ce3e05c26
--- /dev/null
+++ b/drivers/firmware/efi/libstub/x86-efi-mshv.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <asm/setup.h>
+
+#include "efistub.h"
+#include "efi-mshv.h"
+
+/* Initial number of MSHV reserved ranges, extended as needed */
+#define MSHV_RESERVED_RANGES_COUNT 16
+
+struct mshv_setup_data {
+ struct setup_data sd;
+ struct setup_indirect si;
+} __packed;
+
+static int mshv_realloc_ranges(struct resource **data,
+ unsigned long *data_sz, int nr_ranges)
+{
+ struct resource *new_data;
+ unsigned long new_sz;
+ int status;
+
+ new_sz = sizeof(struct resource) * nr_ranges;
+ status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, new_sz,
+ (void **)&new_data);
+ if (status != EFI_SUCCESS) {
+ efi_err("mshv failed to allocate setup_data\n");
+ return status;
+ }
+
+ memset(new_data, 0, new_sz);
+ if (*data) {
+ memcpy(new_data, *data, *data_sz);
+ efi_bs_call(free_pool, *data);
+ }
+
+ *data = new_data;
+ *data_sz = new_sz;
+
+ return EFI_SUCCESS;
+}
+
+static efi_status_t mshv_populate_ranges(struct boot_params *boot_params,
+ efi_memory_desc_t *mem_map,
+ unsigned long map_sz,
+ unsigned long desc_sz)
+{
+ unsigned long cmdline_ptr;
+ char *mshv_cmdline;
+ efi_status_t status;
+
+ cmdline_ptr = boot_params->hdr.cmd_line_ptr;
+ cmdline_ptr |= (u64)boot_params->ext_cmd_line_ptr << 32;
+
+ status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
+ COMMAND_LINE_SIZE, (void **)&mshv_cmdline);
+ if (status != EFI_SUCCESS)
+ return status;
+
+ status = mshv_efi_update_cmdline(mem_map, map_sz, desc_sz,
+ (char *)cmdline_ptr, mshv_cmdline,
+ COMMAND_LINE_SIZE);
+ if (status != EFI_SUCCESS)
+ goto free_cmdline;
+
+ boot_params->hdr.cmd_line_ptr = (u32)((unsigned long)mshv_cmdline);
+ boot_params->ext_cmd_line_ptr = (u32)((unsigned long)mshv_cmdline >> 32);
+ boot_params->hdr.cmdline_size = COMMAND_LINE_SIZE;
+
+ return EFI_SUCCESS;
+
+free_cmdline:
+ efi_bs_call(free_pool, mshv_cmdline);
+ return status;
+}
+
+/*
+ * Prepare for running as root partition with mshv.
+ * - Open the hypervisor loader EFI protocol, used for launching mshv after
+ * 'exit boot services'.
+ * - Get mshv reserved memory ranges from the loader, and populates those
+ * via a command line parameter 'hyperv_resvd_new'.
+ * If mshv_efi_setup() fails, boot continues as a bare-metal boot.
+ */
+efi_status_t mshv_efi_setup(struct boot_params *boot_params)
+{
+ struct mshv_setup_data *sd_block;
+ efi_memory_desc_t *mem_map;
+ unsigned long map_sz, desc_sz;
+ u64 start, end;
+ struct resource *mshv_range, *prev;
+ struct resource *mshv_reserved;
+ unsigned long mshv_reserved_sz = 0;
+ u32 nr_desc;
+ int i, nr_ranges, max_ranges;
+ efi_status_t status;
+
+ mem_map = NULL;
+ mshv_reserved = NULL;
+
+ status = mshv_efi_init();
+ if (status == EFI_NOT_FOUND) {
+ /*
+ * If the protocol is not installed
+ * we are in a standard Linux boot
+ */
+ return EFI_SUCCESS;
+ }
+
+ /*
+ * Get mshv memory map to figure out mshv reserved ranges.
+ */
+
+ map_sz = 0;
+ mshv_get_hv_ranges((void *)&mem_map, &map_sz, &desc_sz);
+
+ /*
+ * Build an array of kernel 'struct resource' objects that contain mshv
+ * reserved ranges. This array is populated via a command line parameter
+ * called 'hyperv_resvd_new'.
+ */
+
+ status = mshv_realloc_ranges(&mshv_reserved,
+ &mshv_reserved_sz,
+ MSHV_RESERVED_RANGES_COUNT);
+ if (status != EFI_SUCCESS)
+ mshv_efi_reboot("failed to allocate space for hv ranges with code %d",
+ status);
+
+ max_ranges = MSHV_RESERVED_RANGES_COUNT;
+ mshv_range = mshv_reserved;
+ prev = NULL;
+ nr_desc = map_sz / desc_sz;
+ if (!nr_desc)
+ mshv_efi_reboot("hypervisor loader returned no reserved ranges");
+
+ for (i = 0, nr_ranges = 0; i < nr_desc; i++) {
+ efi_memory_desc_t *d;
+
+ d = efi_memdesc_ptr(mem_map, desc_sz, i);
+
+ /* Merge adjacent ranges */
+ if (prev && ((prev->end + 1) == d->phys_addr)) {
+ prev->end += (d->num_pages << PAGE_SHIFT);
+ continue;
+ }
+
+ mshv_range->name = "Hypervisor Code and Data";
+ mshv_range->flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM;
+ mshv_range->start = d->phys_addr;
+ mshv_range->end = d->phys_addr + (d->num_pages << PAGE_SHIFT) - 1;
+
+ prev = mshv_range++;
+ nr_ranges++;
+ if (nr_ranges >= max_ranges) {
+ /* Extend the array to accommodate more ranges */
+ max_ranges += MSHV_RESERVED_RANGES_COUNT;
+ status = mshv_realloc_ranges(&mshv_reserved, &mshv_reserved_sz,
+ max_ranges);
+ if (status != EFI_SUCCESS)
+ mshv_efi_reboot("failed to allocate hv ranges: %d", status);
+
+ prev = &mshv_reserved[nr_ranges - 1];
+ mshv_range = prev + 1;
+ }
+ }
+
+ status = mshv_populate_ranges(boot_params, mem_map, map_sz, desc_sz);
+ if (status != EFI_SUCCESS)
+ mshv_efi_reboot("failed to update command line with code %d",
+ status);
+
+ /* Build an indirect setup_data for each mshv reserved range. */
+ status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
+ nr_ranges * sizeof(struct mshv_setup_data),
+ (void **)&sd_block);
+ if (status != EFI_SUCCESS)
+ mshv_efi_reboot("failed to allocate space for hv ranges: error code %d", status);
+
+ memset((void *)sd_block, 0, nr_ranges * sizeof(struct mshv_setup_data));
+ if (!boot_params->hdr.setup_data) {
+ boot_params->hdr.setup_data = (unsigned long)sd_block;
+ } else {
+ struct setup_data *data;
+
+ data = (struct setup_data *)(unsigned long)boot_params->hdr.setup_data;
+ while (data->next)
+ data = (struct setup_data *)(unsigned long)data->next;
+ data->next = (unsigned long)sd_block;
+ }
+
+ for (i = 0; i < nr_ranges; i++) {
+ start = mshv_reserved[i].start;
+ end = mshv_reserved[i].end;
+
+ sd_block[i].sd.type = SETUP_INDIRECT;
+ sd_block[i].sd.len = sizeof(struct setup_indirect);
+ sd_block[i].sd.next = (__u64)&sd_block[i + 1];
+
+ sd_block[i].si.type = SETUP_MSHV;
+ sd_block[i].si.reserved = 0;
+ sd_block[i].si.len = end - start + 1;
+ sd_block[i].si.addr = start;
+ }
+
+ /*
+ * Remove the trailing 'next' pointer which is currently
+ * outside of the struct mshv_setup_data buffer.
+ */
+
+ sd_block[nr_ranges - 1].sd.next = 0;
+
+ efi_bs_call(free_pool, mem_map);
+ efi_bs_call(free_pool, mshv_reserved);
+
+ return EFI_SUCCESS;
+}
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index cef32e2c82d8..eab2539488ee 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -20,6 +20,7 @@
#include "efistub.h"
#include "x86-stub.h"
+#include "efi-mshv.h"
extern char _bss[], _ebss[];
@@ -737,6 +738,7 @@ static efi_status_t exit_boot_func(struct efi_boot_memmap *map,
void *priv)
{
const char *signature;
+ efi_status_t status;
struct exit_boot_struct *p = priv;
signature = efi_is_64bit() ? EFI64_LOADER_SIGNATURE
@@ -751,6 +753,11 @@ static efi_status_t exit_boot_func(struct efi_boot_memmap *map,
&p->efi->efi_memmap, &p->efi->efi_memmap_hi);
p->efi->efi_memmap_size = map->map_size;
+ /* Notify hypervisor of efi runtime services pages */
+ status = mshv_set_efi_rt_range(map);
+ if (status != EFI_SUCCESS)
+ return status;
+
return EFI_SUCCESS;
}
@@ -918,7 +925,7 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
const struct linux_efi_initrd *initrd = NULL;
unsigned long kernel_entry;
struct setup_header *hdr;
- efi_status_t status;
+ efi_status_t status, mshv_status;
efi_system_table = sys_table_arg;
/* Check if we were booted by the EFI firmware */
@@ -1011,6 +1018,8 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
/* Ask the firmware to clear memory on unclean shutdown */
efi_enable_reset_attack_mitigation();
+ mshv_status = mshv_efi_setup(boot_params);
+
efi_random_get_seed();
efi_retrieve_eventlog();
@@ -1035,6 +1044,13 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
*/
sev_enable(boot_params);
+ /*
+ * Launch the hypervisor before switching to 5 level paging.
+ * The hypervisor does not support being launched with LA57 enabled.
+ */
+ if (mshv_status == EFI_SUCCESS)
+ mshv_status = mshv_launch();
+
efi_5level_switch();
enter_kernel(kernel_entry, boot_params);
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 6b67ac616789..ab301dd602fb 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -26,6 +26,10 @@
#include <linux/kmsg_dump.h>
#include <linux/sizes.h>
#include <linux/slab.h>
+#include <linux/memblock.h>
+#include <linux/crash_dump.h>
+#include <linux/kstrtox.h>
+#include <linux/string.h>
#include <linux/dma-map-ops.h>
#include <linux/set_memory.h>
#include <hyperv/hvhdk.h>
@@ -863,3 +867,81 @@ const char *hv_result_to_string(u64 status)
return "Unknown";
}
EXPORT_SYMBOL_GPL(hv_result_to_string);
+
+struct resource hv_mshv_res[HV_MAX_RESVD_RANGES];
+u32 ranges_nr;
+
+/*
+ * Parse "hyperv_resvd_new=<size>!<address>,<size>!<address>,...", specifying a
+ * list of memory ranges that are reserved by the loader for the hypervisor.
+ */
+static int __init hv_parse_hyperv_resvd_new(char *arg)
+{
+ int i = 0;
+
+ if (is_kdump_kernel())
+ return 0;
+
+ while (arg && *arg) {
+ unsigned long long region_start, region_sz;
+ char *range, *addr_str;
+
+ if (i >= HV_MAX_RESVD_RANGES) {
+ pr_err("Hyper-V: too many hyperv_resvd_new ranges specified\n");
+ break;
+ }
+
+ /* Each range is "<size>!<address>", ranges separated by ','. */
+ range = strsep(&arg, ",");
+ addr_str = strchr(range, '!');
+ if (!addr_str) {
+ pr_err("Hyper-V: invalid format for hyperv_resvd_new: %s\n", range);
+ break;
+ }
+ *addr_str++ = '\0';
+
+ if (kstrtoull(range, 16, ®ion_sz) || !region_sz ||
+ kstrtoull(addr_str, 16, ®ion_start) || !region_start) {
+ pr_err("Hyper-V: invalid format for hyperv_resvd_new\n");
+ break;
+ }
+
+ memblock_reserve(region_start, region_sz);
+
+ hv_mshv_res[i].name = "Hypervisor Code and Data";
+ hv_mshv_res[i].flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM;
+ hv_mshv_res[i].start = region_start;
+ hv_mshv_res[i].end = region_start + region_sz - 1;
+
+ ++i;
+ }
+
+ ranges_nr = i;
+
+ return 0;
+}
+early_param("hyperv_resvd_new", hv_parse_hyperv_resvd_new);
+
+/*
+ * Log memory ranges that the hypervisor uses. The ranges are marked
+ * by a custom bootloader.
+ */
+void __init hv_dump_mshv_memory(void)
+{
+ u64 start, end;
+ int i;
+
+ for (i = 0; i < ranges_nr; i++) {
+ start = hv_mshv_res[i].start;
+ end = hv_mshv_res[i].end;
+ pr_info("Hyper-V reserve [mem %#018Lx-%#018Lx]\n", start, end);
+ }
+}
+
+void __init hv_mark_resources(void)
+{
+ int i, max = ARRAY_SIZE(hv_mshv_res);
+
+ for (i = 0; i < max && hv_mshv_res[i].end; i++)
+ insert_resource(&iomem_resource, &hv_mshv_res[i]);
+}
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index bf601d67cecb..83e0dff5371a 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -212,6 +212,13 @@ int hv_common_cpu_init(unsigned int cpu);
int hv_common_cpu_die(unsigned int cpu);
void hv_identify_partition_type(void);
+#define HV_MAX_RESVD_RANGES 32
+extern struct resource hv_mshv_res[HV_MAX_RESVD_RANGES];
+extern u32 ranges_nr;
+
+void __init hv_dump_mshv_memory(void);
+void __init hv_mark_resources(void);
+
/**
* hv_cpu_number_to_vp_number() - Map CPU to VP.
* @cpu_number: CPU number in Linux terms
diff --git a/include/linux/efi.h b/include/linux/efi.h
index ccbc35479684..be3fb83194b9 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -391,6 +391,7 @@ void efi_native_runtime_setup(void);
#define EFI_LOAD_FILE_PROTOCOL_GUID EFI_GUID(0x56ec3091, 0x954c, 0x11d2, 0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
#define EFI_LOAD_FILE2_PROTOCOL_GUID EFI_GUID(0x4006c0c1, 0xfcb3, 0x403e, 0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d)
#define EFI_RT_PROPERTIES_TABLE_GUID EFI_GUID(0xeb66918a, 0x7eef, 0x402a, 0x84, 0x2e, 0x93, 0x1d, 0x21, 0xc3, 0x8a, 0xe9)
+#define EFI_MSHV_MEDIA_PROTOCOL_GUID EFI_GUID(0x098d423a, 0x6ca5, 0x4ad4, 0x90, 0xfa, 0x72, 0xc3, 0xce, 0x22, 0xc8, 0xd0)
#define EFI_DXE_SERVICES_TABLE_GUID EFI_GUID(0x05ad34ba, 0x6f02, 0x4214, 0x95, 0x2e, 0x4d, 0xa0, 0x39, 0x8e, 0x2b, 0xb9)
#define EFI_SMBIOS_PROTOCOL_GUID EFI_GUID(0x03583ff6, 0xcb36, 0x4940, 0x94, 0x7e, 0xb9, 0xb3, 0x9f, 0x4a, 0xfa, 0xf7)
#define EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID EFI_GUID(0xf4560cf6, 0x40ec, 0x4b4a, 0xa1, 0x92, 0xbf, 0x1d, 0x57, 0xd0, 0xb1, 0x89)
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 13/13] mshv: set up own SynIC registers on a nested root partition
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (11 preceding siblings ...)
2026-08-10 12:45 ` [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader) Wei Hu
@ 2026-08-10 12:45 ` Wei Hu
12 siblings, 0 replies; 25+ messages in thread
From: Wei Hu @ 2026-08-10 12:45 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Wei Hu
From: Wei Hu <weh@microsoft.com>
Upstream mshv_synic_cpu_init()/_exit() skip programming the SynIC
SIMP/SIEFP/SCONTROL registers when VMBus is active (hv_vmbus_exists()),
assuming VMBus's hv_hyp_synic_enable_regs() already provisioned them.
That assumption is wrong for a *nested* root partition. There, VMBus
programs the *nested* SynIC MSRs (HV_X64_MSR_NESTED_SIMP, ...) via
hv_set_msr()'s nested remap, while mshv_synic reads/writes the
*non-nested* SynIC MSRs (hv_get/set_non_nested_msr). As a result mshv
maps the wrong message page, never receives the SYNIC intercept message
that carries the async hypercall completion, and mshv_async_hvcall_handler
blocks forever -- hanging SEV-SNP guest creation from user space.
Gate the "VMBus owns the SynIC registers" optimization on !hv_nested so
that a nested root partition programs its own non-nested SynIC registers,
restoring correct intercept/async-hypercall delivery. No change for a
non-nested (bare-metal) root.
Signed-off-by: Wei Hu <weh@microsoft.com>
---
drivers/hv/mshv_synic.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/drivers/hv/mshv_synic.c b/drivers/hv/mshv_synic.c
index 7c168e5a740d..7d714ee5d226 100644
--- a/drivers/hv/mshv_synic.c
+++ b/drivers/hv/mshv_synic.c
@@ -472,7 +472,13 @@ static int mshv_synic_cpu_init(unsigned int cpu)
* VMBus owns SIMP/SIEFP/SCONTROL when it is active.
* See hv_hyp_synic_enable_regs() for that initialization.
*/
- bool vmbus_active = hv_vmbus_exists();
+ /*
+ * On a nested root partition VMBus programs the *nested* SynIC MSRs
+ * (SIMP/SIEFP/SCONTROL) via hv_set_msr()'s nested remap, whereas the
+ * code below reads/writes the non-nested SynIC MSRs. So when nested,
+ * do not defer SynIC setup to VMBus -- set up our own registers.
+ */
+ bool vmbus_active = hv_vmbus_exists() && !hv_nested;
/*
* Map the SYNIC message page. When VMBus is not active the
@@ -593,7 +599,13 @@ static int mshv_synic_cpu_exit(unsigned int cpu)
struct hv_synic_event_ring_page **event_ring_page =
&spages->synic_event_ring_page;
/* VMBus owns SIMP/SIEFP/SCONTROL when it is active */
- bool vmbus_active = hv_vmbus_exists();
+ /*
+ * On a nested root partition VMBus programs the *nested* SynIC MSRs
+ * (SIMP/SIEFP/SCONTROL) via hv_set_msr()'s nested remap, whereas the
+ * code below reads/writes the non-nested SynIC MSRs. So when nested,
+ * do not defer SynIC setup to VMBus -- set up our own registers.
+ */
+ bool vmbus_active = hv_vmbus_exists() && !hv_nested;
/* Disable the interrupt */
sint.as_uint64 = hv_get_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX);
--
2.43.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init
2026-08-10 12:45 ` [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init Wei Hu
@ 2026-08-10 12:55 ` sashiko-bot
2026-08-10 18:53 ` Wei Liu
1 sibling, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-08-10 12:55 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] The shared per-CPU hypercall output buffer is read after local interrupts are restored, creating a race condition that can lead to data corruption.
Pre-existing issues:
- [High] Userspace can access the `/dev/mshv` character device before the root driver initialization is complete, exposing uninitialized structures.
--
--- Patch [6]: [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init ---
commit e5cd5a244ce2b3b2547714151c9b94d944d9fd7b
Author: Wei Liu <wei.liu@kernel.org>
mshv: detect and report SEV-SNP support at init
Query Hyper-V dynamic processor features during mshv initialization to
determine SEV-SNP availability, and report the result. On success, log the
maximum number of encrypted partitions, otherwise log the decoded SNP status
string so misconfigurations are diagnosable.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index 8c87dd8e0378f..712f45dcef60c 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> @@ -2705,6 +2705,100 @@ static int mshv_root_scheduler_cleanup(unsigned int cpu)
> }
>
> /* Must be called after retrieving the scheduler type */
> +#if defined(__x86_64__)
[ ... ]
> +static void mshv_print_max_sev_snp_partitions(struct device *dev)
> +{
> + struct hv_input_get_system_property *input;
> + struct hv_output_get_system_property *output;
> + unsigned long flags;
> + u64 status;
> +
> + local_irq_save(flags);
> + input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> + output = *this_cpu_ptr(hyperv_pcpu_output_arg);
> +
> + memset(input, 0, sizeof(*input));
> + input->property_id = HV_DYNAMIC_PROCESSOR_FEATURE_PROPERTY;
> + input->hv_processor_feature =
> + HV_X64_DYNAMIC_PROCESSOR_FEATURE_MAX_ENCRYPTED_PARTITIONS;
> +
> + status = hv_do_hypercall(HVCALL_GET_SYSTEM_PROPERTY, input, output);
> + local_irq_restore(flags);
> + if (!hv_result_success(status)) {
> + dev_warn(dev, "Failed to get max SNP partitions: %s\n",
> + hv_result_to_string(status));
> + return;
> + }
> +
> + dev_info(dev, "Maximum supported SEV-SNP partitions are: %llu\n",
> + output->hv_processor_feature_value);
[Severity: Medium]
Could this result in a corrupted value being read from the output buffer?
By calling local_irq_restore(flags) before reading
output->hv_processor_feature_value, an interrupt could occur immediately
after IRQs are restored. If the interrupt handler executes another hypercall,
the per-CPU output buffer (hyperv_pcpu_output_arg) will be overwritten before
the value is logged.
Should the value be copied to a local variable before restoring local
interrupts?
> +}
> +
> +static void __init mshv_check_sev_snp_support(struct device *dev)
> +{
> + struct hv_input_get_system_property *input;
> + struct hv_output_get_system_property *output;
> + unsigned long flags;
> + enum hv_snp_status snp_status;
> + u64 status;
> +
> + local_irq_save(flags);
> + input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> + output = *this_cpu_ptr(hyperv_pcpu_output_arg);
> +
> + memset(input, 0, sizeof(*input));
> + input->property_id = HV_DYNAMIC_PROCESSOR_FEATURE_PROPERTY;
> + input->hv_processor_feature = HV_X64_DYNAMIC_PROCESSOR_FEATURE_SNP_STATUS;
> +
> + status = hv_do_hypercall(HVCALL_GET_SYSTEM_PROPERTY, input, output);
> + local_irq_restore(flags);
> + if (!hv_result_success(status)) {
> + dev_warn(dev, "Failed to get SNP support: %s\n",
> + hv_result_to_string(status));
> + return;
> + }
> +
> + snp_status = output->hv_processor_feature_value;
[Severity: Medium]
If an interrupt overwrites the buffer after local_irq_restore(flags) but
before snp_status is assigned, snp_status could read a corrupted value.
Is there a similar race condition here as seen in
mshv_print_max_sev_snp_partitions()?
> + dev_info(dev, "SEV-SNP support status: %s (%u)\n",
> + hv_snp_status_to_string(snp_status), snp_status);
> +
> + if (snp_status == HV_SNP_STATUS_AVAILABLE)
> + mshv_print_max_sev_snp_partitions(dev);
> +}
[ ... ]
> @@ -2808,6 +2902,8 @@ static int __init mshv_parent_partition_init(void)
> if (ret)
> goto synic_cleanup;
>
> + mshv_check_sev_snp_support(dev);
> +
[Severity: High]
This is a pre-existing issue, but is it safe to expose the character device
before the root driver initialization is complete?
Earlier in mshv_parent_partition_init(), misc_register(&mshv_dev) is called,
which immediately exposes the device to userspace. A racing userspace thread
could open the device and trigger the MSHV_CREATE_PARTITION ioctl before
initialization finishes.
The ioctl invokes add_partition(), which attempts to acquire
mshv_root.pt_ht_lock. The lock initialization spin_lock_init(&mshv_root.pt_ht_lock)
isn't executed until much later in the setup sequence.
Wouldn't this trigger a kernel oops if accessed early?
> ret = root_scheduler_init(dev);
> if (ret)
> goto synic_cleanup;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810124527.1409634-1-weh@linux.microsoft.com?part=6
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 07/13] mshv: default to safe partition CPU features
2026-08-10 12:45 ` [PATCH v2 07/13] mshv: default to safe partition CPU features Wei Hu
@ 2026-08-10 12:57 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-08-10 12:57 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Legacy V1 API callers requesting nested virtualization will no longer receive the nested virtualization CPU feature, breaking ABI compatibility.
--
--- Patch [7]: [PATCH v2 07/13] mshv: default to safe partition CPU features ---
commit 01e208d51ef280f67802036c435ff766571437ae
Author: Wei Liu <wei.liu@kernel.org>
mshv: default to safe partition CPU features
For legacy create-partition callers, disable all processor and xsave
features first, then enable only the default feature set known to be
supported. Passing an all-zero disabled feature mask can make SEV-SNP
partition creation fail on Hyper-V.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index 712f45dcef60c..ad2e708fd7539 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
> @@ -2373,15 +2373,102 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
> args.pt_isolation >= MSHV_PT_ISOLATION_COUNT)
> return -EINVAL;
>
> + memset(cr_props, 0, sizeof(*cr_props));
> + memset(isol_props, 0, sizeof(*isol_props));
> disabled_procs = &cr_props->disabled_processor_features;
> disabled_xsave = &cr_props->disabled_processor_xsave_features;
>
> +#if IS_ENABLED(CONFIG_X86_64)
> + /* Disable all processor features first. */
> + for (i = 0; i < HV_PARTITION_PROCESSOR_FEATURES_BANKS; i++)
> + disabled_procs->as_uint64[i] = U64_MAX;
> +
> + /* Enable default features that are known to be supported. */
> + disabled_procs->sse3_support = 0;
[ ... ]
> + disabled_procs->cet_ss_support = 0;
> + disabled_procs->cet_ibt_support = 0;
> + disabled_procs->vmx_exception_inject_support = 0;
[Severity: High]
Does this omission break legacy callers requesting nested virtualization?
Since all features are disabled by default (U64_MAX), and
disabled_procs->nested_virt_support is not explicitly set to 0 in this
default list, won't nested virtualization remain disabled for the guest CPU
even if the legacy caller sets MSHV_PT_BIT_NESTED_VIRTUALIZATION?
Later in mshv_ioctl_process_pt_flags(), the creation capability is enabled
if requested:
if (args.pt_flags & BIT(MSHV_PT_BIT_NESTED_VIRTUALIZATION))
*pt_flags |= HV_PARTITION_CREATION_FLAG_NESTED_VIRTUALIZATION_CAPABLE;
However, because the CPU feature itself is left disabled by the new safe
defaults list, it appears this will break nested virtualization for V1 ABI
callers.
> +
> + disabled_xsave->as_uint64 = U64_MAX;
> + disabled_xsave->xsave_support = 0;
> + disabled_xsave->xsaveopt_support = 0;
> + disabled_xsave->avx_support = 0;
> +#endif
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810124527.1409634-1-weh@linux.microsoft.com?part=7
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 03/13] mshv: add SEV-SNP isolated page hypercalls
2026-08-10 12:44 ` [PATCH v2 03/13] mshv: add SEV-SNP isolated page hypercalls Wei Hu
@ 2026-08-10 12:58 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-08-10 12:58 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The error log in `hv_call_import_isolated_pages` under-reports the number of successfully imported pages if an error occurs mid-batch.
--
--- Patch [3]: [PATCH v2 03/13] mshv: add SEV-SNP isolated page hypercalls ---
commit 6412d57ab824c1079e692800b1ba551aa8b078c8
Author: Wei Liu <wei.liu@kernel.org>
mshv: add SEV-SNP isolated page hypercalls
This commit adds hypercall helpers for importing isolated pages and
completing isolated imports to transfer state to the hypervisor. These
are used by SEV-SNP guest launch flows.
> diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
> index 48abe338c06f8..4e48c1a9a6308 100644
> --- a/drivers/hv/mshv_root_hv_call.c
> +++ b/drivers/hv/mshv_root_hv_call.c
[ ... ]
> @@ -1010,6 +1014,104 @@ int hv_unmap_stats_page(enum hv_stats_object_type type,
> }
>
> #ifdef HV_SUPPORTS_SEV_SNP_GUESTS
> +int hv_call_import_isolated_pages(u64 partition_id, u64 *pages,
> + u64 num_pages,
> + enum hv_isolated_page_type page_type,
> + enum hv_isolated_page_size page_size,
> + void (*completion_handler)(void *data,
> + u64 *status),
> + void *completion_data)
> +{
[ ... ]
> + completed = hv_repcomp(status);
> + if (hv_result(status) == HV_STATUS_CALL_PENDING) {
> + completion_handler(completion_data, &status);
> + if (hv_repcomp(status))
> + completed = hv_repcomp(status);
> + }
> +
> + if (!hv_result_success(status)) {
> + pr_err("%s: completed %llu of %llu, %s\n", __func__,
> + num_pages - remaining, num_pages,
> + hv_result_to_string(status));
[Severity: Low]
Does this error message under-report the number of completed pages?
If a rep hypercall fails after completing some iterations mid-batch, the
iterations completed in the current batch are stored in the completed
variable via hv_repcomp(status).
Because remaining is only decremented at the end of the loop, calculating
num_pages - remaining here appears to omit the pages successfully completed
in the current failing batch.
> + return hv_result_to_errno(status);
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810124527.1409634-1-weh@linux.microsoft.com?part=3
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 01/13] mshv: add SEV-SNP UAPI definitions
2026-08-10 12:44 ` [PATCH v2 01/13] mshv: add SEV-SNP UAPI definitions Wei Hu
@ 2026-08-10 12:59 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-08-10 12:59 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] The UAPI header uses C bitfields in `union mshv_snp_guest_policy`, violating Linux UAPI design guidelines and risking ABI instability.
- [High] Massively oversized ioctl argument passed by value (3.3 KB), risking kernel stack overflow.
- [High] Structures embed flexible array members (`guest_pfns[]`) but are passed by value in `_IOW` macros, concealing the true payload size from audit tools.
- [Medium] UAPI structs use `__attribute__((packed))` without natural alignment padding, resulting in unaligned boundaries.
- [Medium] Missing `page_size` field in the UAPI struct `mshv_import_isolated_pages`.
--
--- Patch [1]: [PATCH v2 01/13] mshv: add SEV-SNP UAPI definitions ---
commit c9fecd1e92fbda950177d18a68754c3dabf290f1
Author: Wei Liu <wei.liu@kernel.org>
mshv: add SEV-SNP UAPI definitions
Add the MSHV UAPI and Hyper-V ABI definitions needed by the SEV-SNP
partition ioctls: the SNP isolation type, GPA host-access and
isolated-page request structures, PSP request structures, and the SEV
control register definitions.
Also add the SNP launch-data definitions (guest policy, ID block, ID
auth info and launch-finish data) that form the isolated-import
completion parameters, along with the SNP support-status enumeration.
> diff --git a/include/hyperv/hvhdk.h b/include/hyperv/hvhdk.h
> index 0c89c62c97067..691ac495a095d 100644
> --- a/include/hyperv/hvhdk.h
> +++ b/include/hyperv/hvhdk.h
[ ... ]
> @@ -953,4 +953,49 @@ struct hv_input_modify_sparse_spa_page_host_access {
> #define HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE 0x4
> #define HV_MODIFY_SPA_PAGE_HOST_ACCESS_HUGE_PAGE 0x8
>
> +enum hv_isolated_page_type {
> + HV_ISOLATED_PAGE_TYPE_NORMAL,
> + HV_ISOLATED_PAGE_TYPE_VMSA,
> + HV_ISOLATED_PAGE_TYPE_ZERO,
> + HV_ISOLATED_PAGE_TYPE_UNMEASURED,
> + HV_ISOLATED_PAGE_TYPE_SECRETS,
> + HV_ISOLATED_PAGE_TYPE_CPUID,
> + HV_ISOLATED_PAGE_TYPE_COUNT
> +};
> +
> +enum hv_isolated_page_size {
> + HV_ISOLATED_PAGE_SIZE_4KB,
> + HV_ISOLATED_PAGE_SIZE_2MB
> +};
> +
> +struct hv_input_import_isolated_pages {
> + u64 partition_id;
> + u32 page_type;
> + u32 page_size;
> + u64 page_number[];
> +} __packed;
[ ... ]
> diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
> index 32ff92b6342b2..fc89f6c9a968f 100644
> --- a/include/uapi/linux/mshv.h
> +++ b/include/uapi/linux/mshv.h
[ ... ]
> +struct mshv_modify_gpa_host_access {
> + __u8 flags;
> + __u8 rsvd[7];
> + __u64 page_count;
> + __u64 guest_pfns[];
> +};
[ ... ]
> +struct mshv_import_isolated_pages {
> + __u8 page_type;
> + __u8 rsvd[7];
> + __u64 page_count;
> + __u64 guest_pfns[];
> +};
[Severity: Medium]
Will this user-facing structure limit the ability to import 2MB pages?
The underlying kernel hypercall structure hv_input_import_isolated_pages
supports both a page_type and a page_size. By shrinking page_type to a __u8
and omitting page_size in mshv_import_isolated_pages, is the ABI locked into
a single default page size?
[ ... ]
> +union mshv_snp_guest_policy {
> + struct {
> + __u64 minor_version : 8;
> + __u64 major_version : 8;
> + __u64 smt_allowed : 1;
> + __u64 vmpls_required : 1;
> + __u64 migration_agent_allowed : 1;
> + __u64 debug_allowed : 1;
> + __u64 reserved : 44;
> + } __attribute__((packed));
> + __u64 as_uint64;
> +};
[Severity: High]
Could these bitfields introduce ABI instability by exposing compiler-dependent
layouts to userspace?
Bitfield memory layout and packing order can vary depending on the compiler,
its flags, and the architecture. Might this cause critical security policy
flags like debug_allowed to be misinterpreted by the kernel or hypervisor if
userspace is compiled differently?
[ ... ]
> +struct mshv_psp_launch_finish_data {
> + struct mshv_snp_id_block id_block;
> + struct mshv_snp_id_auth_info id_auth_info;
> + __u8 host_data[32];
> + __u8 id_block_enabled;
> + __u8 author_key_enabled;
> +} __attribute__((packed));
[Severity: Medium]
Does using __attribute__((packed)) here create unaligned boundaries?
This strips natural alignment from the UAPI structures, creating arbitrary
sizes that are not multiples of their largest member's alignment. Could this
force unaligned accesses if pointers are cast or structures are arrayed in
userspace code?
[ ... ]
> +struct mshv_complete_isolated_import {
> + union mshv_partition_complete_isolated_import_data import_data;
> +};
> +
> /**
> * struct mshv_root_hvcall - arguments for MSHV_ROOT_HVCALL
> * @code: Hypercall code (HVCALL_*)
[ ... ]
> @@ -254,6 +350,11 @@ struct mshv_root_hvcall {
> #define MSHV_GET_GPAP_ACCESS_BITMAP _IOWR(MSHV_IOCTL, 0x06, struct mshv_gpap_access_bitmap)
> /* Generic hypercall */
> #define MSHV_ROOT_HVCALL _IOWR(MSHV_IOCTL, 0x07, struct mshv_root_hvcall)
> +#define MSHV_MODIFY_GPA_HOST_ACCESS _IOW(MSHV_IOCTL, 0x09, struct mshv_modify_gpa_host_access)
> +#define MSHV_IMPORT_ISOLATED_PAGES _IOW(MSHV_IOCTL, 0x0A, struct mshv_import_isolated_pages)
[Severity: High]
Does encoding flexible array structures in _IOW macros conceal the true
payload size from audit tools?
The _IOC_SIZE encoded in the resulting ioctl command number will only
represent the size of the fixed headers. Standard tools like strace, seccomp,
or eBPF that rely on _IOC_SIZE might truncate the memory access to just the
header and miss the variable-length guest_pfns arrays.
Should these UAPI structures use a userspace pointer for the variable-length
arrays instead?
> +#define MSHV_COMPLETE_ISOLATED_IMPORT _IOW(MSHV_IOCTL, 0xF4, struct mshv_complete_isolated_import)
[Severity: High]
Does passing struct mshv_complete_isolated_import by value risk overflowing
the kernel stack?
This structure is approximately 3.3 KB in size. Pressure on standard ioctl
paths might encourage allocating this buffer on the kernel stack, which could
trigger a stack overflow on systems with small stacks.
Would it be better to pass large payloads via a pointer in the ioctl
structure instead of directly passing them by value?
> +#define MSHV_ISSUE_PSP_GUEST_REQUEST _IOW(MSHV_IOCTL, 0xF5, struct mshv_issue_psp_guest_request)
> +#define MSHV_SEV_SNP_AP_CREATE _IOW(MSHV_IOCTL, 0xF6, struct mshv_sev_snp_ap_create)
>
> /*
> ********************************
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810124527.1409634-1-weh@linux.microsoft.com?part=1
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader)
2026-08-10 12:45 ` [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader) Wei Hu
@ 2026-08-10 13:05 ` sashiko-bot
2026-08-10 18:50 ` Wei Liu
1 sibling, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-08-10 13:05 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Secure Boot bypass and arbitrary memory overwrite via `hyperv_resvd=` early parameter.
- [High] Uninitialized stack variable `ret` used across trust boundary in `mshv_launch`.
- [High] EFI stub passes unmerged hypervisor memory ranges to the kernel, overflowing command-line and parser limits.
- [High] Logic error in `hv_parse_hyperv_resvd_new` rejects valid physical address 0.
- [Medium] Incorrect variadic argument forwarding in `mshv_efi_reboot`.
- [Medium] State corruption in `hv_parse_hyperv_resvd_new` overwriting head without clearing the tail.
- [Low] Incomplete implementation/Dead code: `SETUP_MSHV` is built but never parsed.
--
--- Patch [12]: [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader) ---
commit d68b02782397b0c67109138fd120320cacae1537
Author: Wei Hu <weh@microsoft.com>
hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader)
Port the EFI HvLoader protocol handshake (efi-mshv stub + SETUP_MSHV) and
the hyperv_resvd_new memory reservation so an upstream kernel can boot as
a Microsoft Hypervisor (MSHV) root partition.
> diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
> index 185d4f677ec0f..63504b22b5ba8 100644
> --- a/arch/x86/kernel/cpu/mshyperv.c
> +++ b/arch/x86/kernel/cpu/mshyperv.c
[ ... ]
> +static int __init hv_parse_hyperv_resvd(char *arg)
> +{
> + int idx, max = ARRAY_SIZE(hv_resvd_ranges);
> + int i = 0;
[ ... ]
> + while (get_option(&arg, &idx)) {
> + if (i >= max) {
> + pr_err("Hyper-V: resvd ranges tbl full %d\n", idx);
> + break;
> + }
> +
> + hv_resvd_ranges[i++] = idx;
> + }
> +
> + return 0;
> +}
> +early_param("hyperv_resvd", hv_parse_hyperv_resvd);
> +
> +/*
> + * Reserve memory that the hypervisor is using early on. The ranges are marked
> + * reserved by a custom bootloader, change that to usable and reserve that
> + * range. Note, the bootloader sanitizes the e820 before passing on here.
> + */
> +static void __init hv_resv_mshv_memory(void)
> +{
> + u64 start, end, size;
> + int i, idx, max = ARRAY_SIZE(hv_resvd_ranges);
> + int resource_index = 0;
> +
> + for (i = 0; i < max && hv_resvd_ranges[i] != -1; i++) {
> + idx = hv_resvd_ranges[i];
[ ... ]
> + start = e820_table->entries[idx].addr;
> + size = e820_table->entries[idx].size;
> + end = start + size - 1;
> +
> + memblock_reserve(start, size);
> + e820_table->entries[idx].type = E820_TYPE_RAM;
> + pr_info("Hyper-V reserve [mem %#018Lx-%#018Lx]\n", start, end);
> +
> + hv_mshv_res[resource_index].name = "Hypervisor Code and Data";
> + hv_mshv_res[resource_index].flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM;
> + hv_mshv_res[resource_index].start = start;
> + hv_mshv_res[resource_index++].end = end;
> + }
> +}
[Severity: Critical]
Does this allow bypassing kernel lockdown?
The hv_parse_hyperv_resvd() function takes an unauthenticated index from the
kernel command line. Then hv_resv_mshv_memory() blindly converts that e820
entry to E820_TYPE_RAM and IORESOURCE_SYSTEM_RAM without bounds checks.
Could a user with access to the bootloader command line use this to target
reserved ACPI or firmware regions, map them as System RAM, and overwrite them
via kexec_file_load?
> diff --git a/drivers/firmware/efi/libstub/efi-mshv-common.c b/drivers/firmware/efi/libstub/efi-mshv-common.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/firmware/efi/libstub/efi-mshv-common.c
[ ... ]
> +efi_status_t mshv_launch(void)
> +{
> + struct hvl_return_data ret;
> +
> + if (!efi_mshv)
> + return EFI_SUCCESS;
> +
> + efi_mshv->launch_hv(NULL, &ret);
> + /* TODO: Where/how do we dump the hv loader logs? */
> + if (ret.launch_data.launch_status != 0)
> + efi_rt_call(reset_system, EFI_RESET_COLD, EFI_ABORTED, 0, NULL);
> + return EFI_SUCCESS;
> +}
[Severity: High]
Can this result in evaluating uninitialized stack memory?
The ret structure is allocated on the stack but not zeroed before being
passed to efi_mshv->launch_hv(). If the hypervisor loader protocol fails
early or neglects to fully populate the structure, does checking
ret.launch_data.launch_status read stack garbage and trigger an unexpected
cold reboot?
> diff --git a/drivers/firmware/efi/libstub/efi-mshv.h b/drivers/firmware/efi/libstub/efi-mshv.h
> new file mode 100644
> --- /dev/null
> +++ b/drivers/firmware/efi/libstub/efi-mshv.h
[ ... ]
> +static inline void mshv_efi_reboot(const char *fmt, ...)
> +{
> + va_list args;
> +
> + va_start(args, fmt);
> + efi_printk(fmt, args);
> + va_end(args);
> +
> + efi_bs_call(stall, 5 * EFI_USEC_PER_SEC);
> + efi_rt_call(reset_system, EFI_RESET_COLD, EFI_ABORTED, 0, NULL);
> +}
[Severity: Medium]
Is args correctly passed to efi_printk() here?
efi_printk() is a variadic function rather than a vprintf-style function.
Passing a va_list directly to it as the first argument will cause it to
interpret the va_list structure itself as the format argument, which prints
garbage memory values instead of the actual error codes.
> diff --git a/drivers/firmware/efi/libstub/x86-efi-mshv.c b/drivers/firmware/efi/libstub/x86-efi-mshv.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/firmware/efi/libstub/x86-efi-mshv.c
[ ... ]
> +efi_status_t mshv_efi_setup(struct boot_params *boot_params)
> +{
[ ... ]
> + for (i = 0, nr_ranges = 0; i < nr_desc; i++) {
[ ... ]
> + mshv_range->name = "Hypervisor Code and Data";
> + mshv_range->flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM;
> + mshv_range->start = d->phys_addr;
> + mshv_range->end = d->phys_addr + (d->num_pages << PAGE_SHIFT) - 1;
> +
> + prev = mshv_range++;
> + nr_ranges++;
[ ... ]
> + }
> +
> + status = mshv_populate_ranges(boot_params, mem_map, map_sz, desc_sz);
[Severity: High]
Does this pass the unmerged EFI memory map to the command line?
The code successfully merges adjacent EFI descriptors into the mshv_reserved
array above, but then passes the raw, unmerged mem_map array into
mshv_populate_ranges().
Since mshv_efi_update_cmdline() serializes every descriptor, could this
exceed the 32-range limit (HV_MAX_RESVD_RANGES) in hv_parse_hyperv_resvd_new()
if the unmerged map is highly fragmented, causing the kernel to silently
drop the remaining ranges?
> + if (status != EFI_SUCCESS)
> + mshv_efi_reboot("failed to update command line with code %d",
> + status);
> +
> + /* Build an indirect setup_data for each mshv reserved range. */
> + status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
> + nr_ranges * sizeof(struct mshv_setup_data),
> + (void **)&sd_block);
> + if (status != EFI_SUCCESS)
> + mshv_efi_reboot("failed to allocate space for hv ranges: error code %d", status);
[ ... ]
> + for (i = 0; i < nr_ranges; i++) {
> + start = mshv_reserved[i].start;
> + end = mshv_reserved[i].end;
> +
> + sd_block[i].sd.type = SETUP_INDIRECT;
> + sd_block[i].sd.len = sizeof(struct setup_indirect);
> + sd_block[i].sd.next = (__u64)&sd_block[i + 1];
> +
> + sd_block[i].si.type = SETUP_MSHV;
> + sd_block[i].si.reserved = 0;
> + sd_block[i].si.len = end - start + 1;
> + sd_block[i].si.addr = start;
> + }
[Severity: Low]
Is the SETUP_MSHV data parsed by the kernel later?
The stub builds a SETUP_INDIRECT chain wrapping SETUP_MSHV elements, but it
doesn't look like there is any logic added in the kernel to parse SETUP_MSHV.
Does this leave unused structures in boot memory?
> diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
> index 6b67ac6167891..ab301dd602fbc 100644
> --- a/drivers/hv/hv_common.c
> +++ b/drivers/hv/hv_common.c
[ ... ]
> +static int __init hv_parse_hyperv_resvd_new(char *arg)
> +{
> + int i = 0;
> +
> + if (is_kdump_kernel())
> + return 0;
> +
> + while (arg && *arg) {
> + unsigned long long region_start, region_sz;
> + char *range, *addr_str;
[ ... ]
> + if (kstrtoull(range, 16, ®ion_sz) || !region_sz ||
> + kstrtoull(addr_str, 16, ®ion_start) || !region_start) {
[Severity: High]
Does the !region_start check reject physical address 0?
If the hypervisor reserves a range starting at the beginning of physical
address space (page 0), region_start will be 0. This check will treat it
as a validation failure and break out of the parsing loop.
> + pr_err("Hyper-V: invalid format for hyperv_resvd_new\n");
> + break;
> + }
> +
> + memblock_reserve(region_start, region_sz);
> +
> + hv_mshv_res[i].name = "Hypervisor Code and Data";
> + hv_mshv_res[i].flags = IORESOURCE_BUSY | IORESOURCE_SYSTEM_RAM;
> + hv_mshv_res[i].start = region_start;
> + hv_mshv_res[i].end = region_start + region_sz - 1;
> +
> + ++i;
> + }
> +
> + ranges_nr = i;
> +
> + return 0;
> +}
> +early_param("hyperv_resvd_new", hv_parse_hyperv_resvd_new);
[ ... ]
> +void __init hv_mark_resources(void)
> +{
> + int i, max = ARRAY_SIZE(hv_mshv_res);
> +
> + for (i = 0; i < max && hv_mshv_res[i].end; i++)
[Severity: Medium]
Can stale entries in hv_mshv_res be read during resource marking?
If hv_parse_hyperv_resvd_new() is invoked multiple times (e.g. appended by
bootloader and supplied by user), the local index i resets to 0, overwriting
the head of hv_mshv_res without clearing the tail.
Because hv_mark_resources() iterates until hv_mshv_res[i].end == 0 rather
than using ranges_nr, it will process the remaining stale tail entries from
the previous parse.
> + insert_resource(&iomem_resource, &hv_mshv_res[i]);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810124527.1409634-1-weh@linux.microsoft.com?part=12
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 04/13] mshv: wire SEV-SNP partition ioctls
2026-08-10 12:44 ` [PATCH v2 04/13] mshv: wire SEV-SNP partition ioctls Wei Hu
@ 2026-08-10 13:07 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-08-10 13:07 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [Critical] Guest memory regions are unpinned and freed back to the host allocator before the hypervisor restores host access, leading to fatal RMP violations.
- [High] Inconsistent handling of the user-provided guest_pfns array between SEV-SNP ioctls, causing correct input to be corrupted.
- [High] Potential CPU stall in hv_call_import_isolated_pages() due to unbounded loop over user-provided page count without cond_resched().
- [High] Unbounded retry loop in destroy_partition() if destroy_snp_partition_state() fails.
- [High] Module unload race with the delayed_work 'destroy_work' causes a Use-After-Free kernel panic.
- [High] hv_call_set_partition_property compares the raw 64-bit status to HV_STATUS_CALL_PENDING without masking, breaking the completion handler.
Pre-existing issues:
- [High] Inverted logic in mshv_init_async_handler()'s completion check permanently bricks the partition on stray completions.
--
--- Patch [4]: [PATCH v2 04/13] mshv: wire SEV-SNP partition ioctls ---
commit 903e3452cece23d95187a14a4beb770df83a5add
Author: Wei Liu <wei.liu@kernel.org>
mshv: wire SEV-SNP partition ioctls
Wire the SEV-SNP partition ioctls for GPA host-access changes,
isolated-page import, PSP guest requests, AP creation, and SNP partition
teardown.
The GPA host-access ioctl converts the guest addresses to GFNs before
issuing the hypercall. The PSP guest-request path restores host access
to the request/response pages if the hypercall fails, so a failed request
does not leave guest pages inaccessible to the host.
> diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
> index 4e48c1a9a6308..6d42fdd2c573f 100644
> --- a/drivers/hv/mshv_root_hv_call.c
> +++ b/drivers/hv/mshv_root_hv_call.c
[ ... ]
> @@ -1014,6 +1014,41 @@ int hv_unmap_stats_page(enum hv_stats_object_type type,
> }
>
> #ifdef HV_SUPPORTS_SEV_SNP_GUESTS
> +int hv_call_set_partition_property(u64 partition_id, u64 property_code,
> + u64 property_value,
> + void (*completion_handler)(void *, u64 *),
> + void *completion_data)
> +{
[ ... ]
> + status = hv_do_hypercall(HVCALL_SET_PARTITION_PROPERTY, input, NULL);
> + local_irq_restore(flags);
> +
> + if (unlikely(status == HV_STATUS_CALL_PENDING))
[Severity: High]
Does this code incorrectly compare the raw 64-bit status to
HV_STATUS_CALL_PENDING without masking it?
All other hypercalls use the macro hv_result(status) == HV_STATUS_CALL_PENDING
to strip diagnostic and reserved bits. If this comparison fails because of
upper bits being set, the hypercall completion handler is bypassed, causing
the function to fail with an error when it should wait. This would lead the
partition destruction into an infinite retry loop.
> + completion_handler(completion_data, &status);
[ ... ]
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index 146726cc4e9ba..8c87dd8e0378f 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> @@ -1625,6 +1668,289 @@ mshv_partition_ioctl_initialize(struct mshv_partition *partition)
[ ... ]
> +static long
> +mshv_partition_ioctl_modify_gpa_host_access(struct mshv_partition *partition,
> + void __user *user_args)
> +{
[ ... ]
> + for (i = 0; i < args.page_count; i++)
> + gpfns[i] = HVPFN_DOWN(gpfns[i]);
> +
[Severity: High]
Are we inconsistent in how we handle the user-provided guest_pfns array
between SEV-SNP ioctls?
Here, the code applies HVPFN_DOWN() to a field named guest_pfns, treating it
as a GPA instead of a PFN. This shifts the PFNs right by 12 bits, corrupting
the values.
However, in mshv_partition_ioctl_import_isolated_pages(), the identically
named guest_pfns array is passed directly to the hypercall without
downshifting. This mismatch breaks the API contract and can lead to memory
corruption or hypercall failures when incorrect physical addresses are
provided to the hypervisor.
[ ... ]
> +static long
> +mshv_partition_ioctl_import_isolated_pages(struct mshv_partition *partition,
> + void __user *user_args)
> +{
[ ... ]
> + ret = mshv_init_async_handler(partition);
[Severity: High]
This is a pre-existing issue, but does mshv_init_async_handler() have inverted
logic in its completion check that permanently bricks the partition?
mshv_init_async_handler() has this check:
if (completion_done(&partition->async_hypercall)) {
pt_err(partition, "Cannot issue async hypercall while another one in progress!\n");
return -EPERM;
}
completion_done() returns true if a completion is completed (done > 0). The
function incorrectly assumes another operation is in progress, when in fact it
means the completion is fully resolved. If a spurious completion fires, it
permanently denies service for any further asynchronous hypercalls for the
partition.
> + if (ret)
> + goto out;
> +
> + ret = hv_call_import_isolated_pages(partition->pt_id, pages,
> + args.page_count, args.page_type,
> + HV_ISOLATED_PAGE_SIZE_4KB,
> + mshv_async_hvcall_handler,
> + partition);
[Severity: High]
Can this lead to a CPU stall and kernel watchdog panic?
Userspace can invoke MSHV_IMPORT_ISOLATED_PAGES with a massive page_count.
The while (remaining) loop inside hv_call_import_isolated_pages() will
continuously issue synchronous hypercalls based on this unbounded
user-supplied page count without ever calling cond_resched(), monopolizing
the processor.
[ ... ]
> @@ -1782,6 +2191,9 @@ static void destroy_partition(struct mshv_partition *partition)
[ ... ]
> if (partition->pt_initialized) {
> +#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
> + if (mshv_partition_encrypted(partition)) {
> + hlist_for_each_entry_safe(region, n,
> + &partition->pt_mem_regions,
> + hnode) {
> + hlist_del(®ion->hnode);
> + mshv_region_put(region);
> + }
[Severity: Critical]
Does this unpin and free guest memory regions back to the host allocator
before the hypervisor restores host access?
The code calls mshv_region_put() to free the memory to the host buddy
allocator BEFORE destroy_snp_partition_state() successfully transitions the
partition state to INSECURE_DIRTY. If the host reallocates and writes to
these pages before the transition completes, it triggers a fatal RMP violation
and a Machine Check Exception (MCE).
> +
> + ret = destroy_snp_partition_state(partition);
> + if (ret) {
> + pt_err(partition,
> + "Failed to destroy SNP state: %d; retrying\n",
> + ret);
> + schedule_delayed_work(&partition->destroy_work, HZ);
> + return;
[Severity: High]
Could this error path cause an unbounded infinite retry loop?
If the hypervisor rejects an idempotent state transition during partition
destruction, the error path unconditionally schedules a retry without a maximum
retry limit. Because it reschedules destroy_partition() entirely, previously
successful state transitions will be repeated, potentially failing
indefinitely.
[ ... ]
> @@ -2051,6 +2485,8 @@ mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev)
> mutex_init(&partition->pt_irq_lock);
>
> init_completion(&partition->async_hypercall);
> + INIT_DELAYED_WORK(&partition->destroy_work,
> + mshv_partition_destroy_work);
[Severity: High]
Might this delayed work cause a Use-After-Free kernel panic on module unload?
If partition destruction fails and defers via this destroy_work, and userspace
closes the file descriptor, the module reference count will reach zero. The
module exit routine mshv_parent_partition_exit() fails to iterate over
remaining partitions or call cancel_delayed_work_sync(). If an administrator
unloads the module before the delayed work fires, the workqueue will execute
unmapped text.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810124527.1409634-1-weh@linux.microsoft.com?part=4
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 11/13] mshv: unlock SNP pages on panic for crashdump collection
2026-08-10 12:45 ` [PATCH v2 11/13] mshv: unlock SNP pages on panic for crashdump collection Wei Hu
@ 2026-08-10 13:10 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-08-10 13:10 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Panic notifier fails to set crash_kexec_post_notifiers globally, bypassing memory unmap in default configurations.
- [High] Acquiring spinlock_t via spin_trylock in a panic notifier is structurally unsafe and violates PREEMPT_RT wait-context rules.
- [Medium] Panic path memory unlocking aborts on the first chunk error, failing to execute best-effort vmcore recovery.
- [Low] Missing rcu_read_lock() during RCU hash table traversal.
- [Low] Misleading success message is logged unconditionally even if SNP unlocking completely aborts.
--
--- Patch [11]: [PATCH v2 11/13] mshv: unlock SNP pages on panic for crashdump collection ---
commit b8503cbf714b3ffa7932540f4a80cc94da615bdc
Author: Wei Hu <weh@microsoft.com>
mshv: unlock SNP pages on panic for crashdump collection
Add an SNP panic-path page-unlock feature so a root partition running
confidential (SEV-SNP) guests can still collect a Linux root vmcore via
kexec after a crash. On panic, guest pages of every encrypted partition
are unmapped and shared back to the host so kexec can read them.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index 0dbf509351bb0..6b38a45ad43cc 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
> @@ -2941,6 +2941,81 @@ static int __init mshv_init_vmm_caps(struct device *dev)
> return 0;
> }
>
> +#if defined(CONFIG_X86_64) && IS_ENABLED(CONFIG_CRASH_DUMP)
> +static void mshv_panic_unlock_snp(struct mshv_partition *vm)
> +{
> + struct mshv_mem_region *memreg;
> + int ret;
> +
> + if (!spin_trylock(&vm->pt_mem_regions_lock)) {
> + pt_err(vm, "Cannot lock SNP memory regions during panic\n");
> + return;
> + }
[Severity: High]
Is it safe to acquire a spinlock_t via spin_trylock() in a panic notifier?
Calling spin_trylock() on a spinlock_t from a hardirq or NMI context violates
PREEMPT_RT wait-context rules.
Additionally, if the CPU that panicked happens to hold this lock, the trylock
will fail and bypass the memory unmap entirely, breaking the kexec crashdump.
> +
> + hlist_for_each_entry(memreg, &vm->pt_mem_regions, hnode) {
> + mshv_region_unmap(memreg);
> + ret = mshv_region_share(memreg);
[Severity: Medium]
Will this abort prematurely if an error occurs on a single chunk?
The return value of mshv_region_unmap() is ignored, and both underlying
functions call mshv_region_process_range() which aborts immediately on the
first error.
Could a best-effort approach be used here to continue attempting to unlock
remaining regions instead of halting on the first hypercall failure?
> + if (ret)
> + pt_err(vm, "Unlock snp failed. ret:0x%x gfn:%llx numpfns:%lld\n",
> + ret, memreg->start_gfn, memreg->nr_pages);
> + }
> +
> + spin_unlock(&vm->pt_mem_regions_lock);
> +}
> +
> +static int mshv_root_panic_cb(struct notifier_block *this, unsigned long event,
> + void *ptr)
> +{
> + int i, done = 0;
> + struct mshv_partition *pt;
> + struct device *dev = NULL;
> +
> + hash_for_each_rcu(mshv_root.pt_htable, i, pt, pt_hnode) {
[Severity: Low]
Should this RCU hash table traversal be protected by rcu_read_lock()?
While preemption is stopped in the panic path, RCU subsystem semantics still
dictate explicitly delimiting the critical section to prevent static checker
warnings.
> + if (!mshv_partition_encrypted(pt))
> + continue;
> +
> + done = 1;
> + mshv_panic_unlock_snp(pt);
> + dev = pt->pt_module_dev;
> + }
> + if (done && dev)
> + dev_info(dev, "SNP pages are unlocked for panic\n");
[Severity: Low]
Could this emit a misleading success message?
The done flag is set to 1 before mshv_panic_unlock_snp() is called. If the
spin_trylock() inside mshv_panic_unlock_snp() fails and returns early, or if
an error occurs during unmapping, this will still unconditionally log that SNP
pages are unlocked.
> +
> + return NOTIFY_DONE;
> +}
> +
> +static struct notifier_block mshv_root_panic_blk = {
> + .notifier_call = mshv_root_panic_cb,
> +};
> +
> +/*
> + * If mshv devirt setup failed during boot, or the feature itself is not
> + * available, allow the system to at least collect linux root vmcore. For
> + * that, snp guest pages must be made readable in the panic path so kexec can
> + * collect them.
> + */
> +static void mshv_crashdump_init(void)
> +{
> + if (hv_crash_enabled)
> + return;
> +
> + atomic_notifier_chain_register(&panic_notifier_list,
> + &mshv_root_panic_blk);
[Severity: High]
Does this setup guarantee that the notifier will run before kexec boots?
By default, panic() calls __crash_kexec() before evaluating
panic_notifier_list.
Without explicitly setting crash_kexec_post_notifiers to true here, the
kdump kernel will boot immediately upon panic on standard configurations,
skipping the notifier list and leaving SNP memory locked.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810124527.1409634-1-weh@linux.microsoft.com?part=11
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 10/13] mshv: unmap SNP memory before state teardown
2026-08-10 12:45 ` [PATCH v2 10/13] mshv: unmap SNP memory before state teardown Wei Hu
@ 2026-08-10 13:13 ` sashiko-bot
0 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-08-10 13:13 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [Critical] The `destroy_work` delayed work is never explicitly canceled during module unload, leading to a potential use-after-free and kernel panic.
- [High] Retrying `destroy_partition` on failure causes partial unmap aborts, permanently leaving remaining chunks of a memory region mapped.
- [Low] Intentional early return in `mshv_region_destroy` permanently leaks the `mshv_mem_region` kernel tracking struct.
Pre-existing issues:
- [High] Direct kfree(vp) without waiting for RCU grace period allows Use-After-Free in concurrent ISRs.
--
--- Patch [10]: [PATCH v2 10/13] mshv: unmap SNP memory before state teardown ---
commit fe90624ce2dba03d1ad11c9e5fd88e8f71d8c3d0
Author: Wei Liu <wei.liu@kernel.org>
mshv: unmap SNP memory before state teardown
Unmap memory regions before destroying SNP partition state, and only
release regions after SNP state is torn down. Dropping regions first
attempts to regain host access while the partition is still secure and
can leave pages inaccessible.
> diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
> index 6d65e5b421529..62c49b4d2a33e 100644
> --- a/drivers/hv/mshv_regions.c
> +++ b/drivers/hv/mshv_regions.c
[ ... ]
> @@ -337,7 +337,7 @@ static int mshv_region_chunk_unmap(struct mshv_mem_region *region,
> page_count, flags);
> }
>
> -static int mshv_region_unmap(struct mshv_mem_region *region)
> +int mshv_region_unmap(struct mshv_mem_region *region)
> {
> return mshv_region_process_range(region, 0,
[Severity: Low]
Does this error path leak the kernel tracking structure?
When tearing down an SNP encrypted partition and mshv_region_share()
fails, the function returns early to intentionally leave guest memory
pinned and prevent host crashes:
drivers/hv/mshv_regions.c:mshv_region_destroy() {
...
if (ret) {
pt_err(partition,
"Failed to regain access to memory... %d\n", ret);
return;
}
}
mshv_region_unmap(region);
mshv_region_invalidate(region);
vfree(region);
}
Since the mshv_mem_region structure is already removed from the partition
lists and its refcount is zero, could bypassing the vfree(region) call
result in permanently leaking the mreg_pages array and tracking structure?
[ ... ]
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index c32d3375af8c5..0dbf509351bb0 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
> @@ -2208,10 +2208,8 @@ static void destroy_partition(struct mshv_partition *partition)
> if (mshv_partition_encrypted(partition)) {
> hlist_for_each_entry_safe(region, n,
> &partition->pt_mem_regions,
> - hnode) {
> - hlist_del(®ion->hnode);
> - mshv_region_put(region);
> - }
> + hnode)
> + mshv_region_unmap(region);
>
> ret = destroy_snp_partition_state(partition);
> if (ret) {
[Severity: High]
Can this retry logic lead to a partial unmap regression when a memory region
is processed a second time?
If destroy_snp_partition_state() fails, the teardown is retried via
destroy_work:
drivers/hv/mshv_root_main.c:destroy_partition() {
...
ret = destroy_snp_partition_state(partition);
if (ret) {
...
schedule_delayed_work(&partition->destroy_work, HZ);
return;
}
}
On the subsequent attempt, mshv_region_unmap() will iterate through the
region chunks again starting from offset 0. Since mreg_pages isn't
cleared during the initial successful unmap, the hypervisor will likely
return an error when attempting to unmap the first chunk again.
mshv_region_process_range() aborts processing immediately on error:
drivers/hv/mshv_regions.c:mshv_region_process_range() {
...
ret = mshv_region_process_chunk(region, flags, page_offset, ...);
if (ret < 0)
return ret;
}
Will this completely bypass the unmapping of any remaining chunks that
were not reached in the previous pass?
[Severity: High]
Is it safe to schedule a delayed work task that lacks cancelation during module
unload?
If destroy_snp_partition_state() fails, the teardown is retried via
delayed work. If the partition file descriptor is closed, the module
reference count drops to 0, which allows the administrator to unload the
mshv module.
Looking at the exit path:
drivers/hv/mshv_root_main.c:mshv_parent_partition_exit() {
...
mshv_irqfd_wq_cleanup();
root_scheduler_deinit();
mshv_synic_exit();
}
Since mshv_parent_partition_exit() executes without calling
cancel_delayed_work_sync(&partition->destroy_work), can the timer fire
after the kernel module is unloaded and execute freed memory?
[Severity: High]
This is a pre-existing issue, but would this direct free cause a
use-after-free regression in concurrent interrupt service routines?
In destroy_partition(), vp structures are freed directly:
drivers/hv/mshv_root_main.c:destroy_partition() {
...
kfree(vp);
partition->pt_vp_array[i] = NULL;
...
}
Concurrently, the intercept ISR reads these pointers under an RCU lock:
drivers/hv/mshv_synic.c:mshv_intercept_isr() {
...
vp = partition->pt_vp_array[vp_index];
if (unlikely(!vp)) {
...
}
kick_vp(vp);
...
}
Since drain_all_vps() is skipped for non-root schedulers, intercept
messages can still arrive during partition teardown. If destroy_partition()
executes between the time mshv_intercept_isr() reads the vp pointer and
dereferences it in kick_vp(), could this result in accessing freed memory
because synchronize_rcu() or kfree_rcu() isn't used?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810124527.1409634-1-weh@linux.microsoft.com?part=10
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader)
2026-08-10 12:45 ` [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader) Wei Hu
2026-08-10 13:05 ` sashiko-bot
@ 2026-08-10 18:50 ` Wei Liu
1 sibling, 0 replies; 25+ messages in thread
From: Wei Liu @ 2026-08-10 18:50 UTC (permalink / raw)
To: Wei Hu
Cc: linux-hyperv, linux-kernel, K. Y. Srinivasan, Haiyang Zhang,
Wei Liu, Dexuan Cui, Long Li, Wei Hu, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Ard Biesheuvel, Ilias Apalodimas, Arnd Bergmann, linux-efi,
linux-arch
On Mon, Aug 10, 2026 at 12:45:06PM +0000, Wei Hu wrote:
> From: Wei Hu <weh@microsoft.com>
>
> Port the EFI HvLoader protocol handshake (efi-mshv stub + SETUP_MSHV) and
> the hyperv_resvd_new memory reservation so an upstream kernel can boot as
> a Microsoft Hypervisor (MSHV) root partition. Mainline lacks this
> Linux-side enablement, so lxhvloader declines to hand off a stock kernel
> as root.
>
> Signed-off-by: Wei Hu <weh@microsoft.com>
This patch should be dropped from this series.
I don't think we should modify the EFI stub yet. We should explore other
methods first.
Thanks,
Wei
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init
2026-08-10 12:45 ` [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init Wei Hu
2026-08-10 12:55 ` sashiko-bot
@ 2026-08-10 18:53 ` Wei Liu
1 sibling, 0 replies; 25+ messages in thread
From: Wei Liu @ 2026-08-10 18:53 UTC (permalink / raw)
To: Wei Hu
Cc: linux-hyperv, linux-kernel, K. Y. Srinivasan, Haiyang Zhang,
Wei Liu, Dexuan Cui, Long Li
On Mon, Aug 10, 2026 at 12:45:00PM +0000, Wei Hu wrote:
> From: Wei Liu <wei.liu@kernel.org>
>
> Query Hyper-V dynamic processor features during mshv initialization to
s/Hyper-V/MSHV/
> determine SEV-SNP availability, and report the result: on success log the
> maximum number of encrypted partitions, otherwise log the decoded SNP
> status string so misconfigurations are diagnosable. Cloud Hypervisor
> expects the root driver to perform this capability check before SEV-SNP
> child partition setup.
No need to reference Cloud Hypervisor here. This applies to all VMMs.
>
> Signed-off-by: Wei Liu <wei.liu@kernel.org>
> ---
> drivers/hv/mshv_root_main.c | 96 +++++++++++++++++++++++++++++++++++++
> 1 file changed, 96 insertions(+)
>
[...]
> +static void __init mshv_check_sev_snp_support(struct device *dev)
> +{
> + struct hv_input_get_system_property *input;
> + struct hv_output_get_system_property *output;
> + unsigned long flags;
> + enum hv_snp_status snp_status;
> + u64 status;
> +
> + local_irq_save(flags);
> + input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> + output = *this_cpu_ptr(hyperv_pcpu_output_arg);
> +
> + memset(input, 0, sizeof(*input));
> + input->property_id = HV_DYNAMIC_PROCESSOR_FEATURE_PROPERTY;
> + input->hv_processor_feature = HV_X64_DYNAMIC_PROCESSOR_FEATURE_SNP_STATUS;
> +
> + status = hv_do_hypercall(HVCALL_GET_SYSTEM_PROPERTY, input, output);
> + local_irq_restore(flags);
> + if (!hv_result_success(status)) {
> + dev_warn(dev, "Failed to get SNP support: %s\n",
> + hv_result_to_string(status));
> + return;
> + }
> +
Our internal testing has shown that running this on an L1VH partition
gets an error.
The error should be non-fatal. Please squash my internal patch to this
in the next revision.
Wei
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 05/13] hyperv: fix hv_input_get_system_property layout for SNP status
2026-08-10 12:44 ` [PATCH v2 05/13] hyperv: fix hv_input_get_system_property layout for SNP status Wei Hu
@ 2026-08-10 18:59 ` Wei Liu
0 siblings, 0 replies; 25+ messages in thread
From: Wei Liu @ 2026-08-10 18:59 UTC (permalink / raw)
To: Wei Hu
Cc: linux-hyperv, linux-kernel, K. Y. Srinivasan, Haiyang Zhang,
Wei Liu, Dexuan Cui, Long Li, Wei Hu
On Mon, Aug 10, 2026 at 12:44:59PM +0000, Wei Hu wrote:
> From: Wei Hu <weh@microsoft.com>
>
> The processor-feature selector must sit at offset 8 (property_id + u32
> reserved), matching the Microsoft Hypervisor ABI. Upstream 7.1's leaner
> struct placed it at offset 4, so HVCALL_GET_SYSTEM_PROPERTY(SNP_STATUS)
> failed and mshv aborted (no /dev/mshv). Restore reserved + u64 union.
>
> Signed-off-by: Wei Hu <weh@microsoft.com>
This patch exists in upstream as:
commit d6f0248f04a96249660591e47fcf37ba98ac7ea3
Author: Wei Liu <wei.liu@kernel.org>
AuthorDate: Tue Jun 30 14:57:54 2026 -0700
Commit: Wei Liu <wei.liu@kernel.org>
CommitDate: Wed Jul 22 21:58:21 2026 +0000
mshv: fix hv_input_get_system_property struct
Keep it in sync with the correct definition.
The old code worked by chance.
Fixes: e68bda71a2384 ("hyperv: Add new Hyper-V headers in include/hyperv")
Cc: stable@kernel.org
Signed-off-by: Wei Liu <wei.liu@kernel.org>
You can drop this.
Wei
> ---
> include/hyperv/hvhdk_mini.h | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h
> index db83fedce243..dd84085c0aff 100644
> --- a/include/hyperv/hvhdk_mini.h
> +++ b/include/hyperv/hvhdk_mini.h
> @@ -237,8 +237,9 @@ enum hv_dynamic_processor_feature_property {
>
> struct hv_input_get_system_property {
> u32 property_id; /* enum hv_system_property */
> + u32 reserved;
> union {
> - u32 as_uint32;
> + u64 as_uint64;
> #if IS_ENABLED(CONFIG_X86)
> /* enum hv_dynamic_processor_feature_property */
> u32 hv_processor_feature;
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-08-10 19:00 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 12:44 [PATCH v2 00/13] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-10 12:44 ` [PATCH v2 01/13] mshv: add SEV-SNP UAPI definitions Wei Hu
2026-08-10 12:59 ` sashiko-bot
2026-08-10 12:44 ` [PATCH v2 02/13] mshv: add SEV-SNP PSP request hypercall Wei Hu
2026-08-10 12:44 ` [PATCH v2 03/13] mshv: add SEV-SNP isolated page hypercalls Wei Hu
2026-08-10 12:58 ` sashiko-bot
2026-08-10 12:44 ` [PATCH v2 04/13] mshv: wire SEV-SNP partition ioctls Wei Hu
2026-08-10 13:07 ` sashiko-bot
2026-08-10 12:44 ` [PATCH v2 05/13] hyperv: fix hv_input_get_system_property layout for SNP status Wei Hu
2026-08-10 18:59 ` Wei Liu
2026-08-10 12:45 ` [PATCH v2 06/13] mshv: detect and report SEV-SNP support at init Wei Hu
2026-08-10 12:55 ` sashiko-bot
2026-08-10 18:53 ` Wei Liu
2026-08-10 12:45 ` [PATCH v2 07/13] mshv: default to safe partition CPU features Wei Hu
2026-08-10 12:57 ` sashiko-bot
2026-08-10 12:45 ` [PATCH v2 08/13] mshv: accept partial CPU feature banks Wei Hu
2026-08-10 12:45 ` [PATCH v2 09/13] mshv: define full processor and xsave feature masks Wei Hu
2026-08-10 12:45 ` [PATCH v2 10/13] mshv: unmap SNP memory before state teardown Wei Hu
2026-08-10 13:13 ` sashiko-bot
2026-08-10 12:45 ` [PATCH v2 11/13] mshv: unlock SNP pages on panic for crashdump collection Wei Hu
2026-08-10 13:10 ` sashiko-bot
2026-08-10 12:45 ` [PATCH v2 12/13] hyperv: add MSHV Dom0 root-partition boot enablement (EFI HvLoader) Wei Hu
2026-08-10 13:05 ` sashiko-bot
2026-08-10 18:50 ` Wei Liu
2026-08-10 12:45 ` [PATCH v2 13/13] mshv: set up own SynIC registers on a nested root partition Wei Hu
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.