* [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions
@ 2026-08-25 4:04 Wei Hu
2026-08-25 4:04 ` [PATCH v3 1/7] mshv: add SEV-SNP UAPI definitions Wei Hu
` (7 more replies)
0 siblings, 8 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-25 4:04 UTC (permalink / raw)
To: linux-hyperv
Cc: linux-kernel, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, rust-for-linux
This series adds support for creating and managing AMD SEV-SNP
confidential virtual machines through the Microsoft Hypervisor root
partition driver.
The series adds fixed-size MSHV UAPI definitions, the required Microsoft
Hypervisor ABI definitions and hypercall helpers, partition ioctls,
capability discovery, processor-feature handling, ordered encrypted-memory
teardown, and nested-root SynIC handling.
The patches are based on the current hyperv-next branch.
Testing:
- Built the complete seven-patch series on x86_64.
- Verified the installed UAPI in 64-bit and 32-bit userspace builds.
- Generated rust-vmm MSHV bindings from the kernel headers.
- Booted Linux as an MSHV root partition with SEV-SNP available.
- Passed Cloud Hypervisor's common_cvm::test_focal_simple_launch.
- Booted a four-vCPU SEV-SNP guest to login.
The development host needs two additional local patches to boot as a
nested MSHV root partition: the EFI HvLoader enablement and the
non-upstreamable nested-VMBus interrupt-vector workaround. Neither patch
is part of this series.
Changes since v2:
- Rebase onto current hyperv-next.
- Drop the system-property structure fix, which is already upstream as
commit d6f0248f04a9 ("mshv: fix hv_input_get_system_property struct").
- Drop the EFI HvLoader patch as requested; alternative root-partition
boot mechanisms will be explored separately.
- Drop the panic-notifier SNP unlock patch. A panic notifier is not a
guaranteed pre-crash-kexec hook and cannot safely traverse mutable
memory-region state in all contexts.
- Redesign variable-length ioctls to use fixed-size headers and aligned
userspace pointers. Name GPA and PFN inputs explicitly and add the
isolated-page size to the UAPI.
- Replace public bitfields and the large by-value launch payload with a
naturally aligned pointer-based ABI and field-by-field kernel
translation.
- Process userspace page arrays in bounded batches, yield between
batches, validate large-page alignment and contiguity, and improve
partial-progress diagnostics.
- Keep encrypted pages pinned until mappings are removed, the SNP
partition state is destroyed, and host access is restored. Quarantine
the partition and retain the module if a safety-critical phase fails.
- Remove the unbounded delayed-work teardown retry.
- Treat rejected SNP capability queries (including L1VH) as non-fatal,
and copy per-CPU results before restoring interrupts.
- Preserve nested virtualization in the legacy safe feature defaults.
- Reject device opens until parent-partition initialization completes.
- Remove redundant SNP operations from the generic passthrough
hypercall interface.
Link: https://lore.kernel.org/linux-hyperv/20260810124527.1409634-1-weh@linux.microsoft.com/
Wei Hu (1):
mshv: set up own SynIC registers on a nested root partition
Wei Liu (6):
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: use safe partition CPU feature defaults
drivers/hv/mshv_regions.c | 10 +-
drivers/hv/mshv_root.h | 29 ++
drivers/hv/mshv_root_hv_call.c | 190 +++++++-
drivers/hv/mshv_root_main.c | 854 +++++++++++++++++++++++++++++++--
drivers/hv/mshv_synic.c | 16 +-
include/hyperv/hvgdk_mini.h | 31 ++
include/hyperv/hvhdk.h | 124 ++++-
include/hyperv/hvhdk_mini.h | 53 ++
include/uapi/linux/mshv.h | 99 +++-
9 files changed, 1365 insertions(+), 41 deletions(-)
base-commit: be0cfab740e58b70047ef6e7e3d578f00ed5d258
--
2.43.0
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v3 1/7] mshv: add SEV-SNP UAPI definitions
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
@ 2026-08-25 4:04 ` Wei Hu
2026-08-25 4:04 ` [PATCH v3 2/7] mshv: add SEV-SNP PSP request hypercall Wei Hu
` (6 subsequent siblings)
7 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-25 4:04 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 Microsoft Hypervisor ABI definitions required to
create and manage SEV-SNP child partitions.
Use fixed-size ioctl headers with aligned userspace pointers for
variable page arrays and the launch-finish payload. The host-access
interface names its input as guest GPAs, while isolated import takes
guest PFNs and an explicit page-size value. Keep the public launch
structures naturally aligned and represent the SNP policy as a raw
64-bit ABI value rather than compiler-dependent bitfields.
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 | 96 +++++++++++++++++++++++++++++++++++++
4 files changed, 213 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 035ba20870f7..dd84085c0aff 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..5e338c605072 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,96 @@ 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;
+ __aligned_u64 guest_gpas;
+};
+
+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
+};
+
+enum {
+ MSHV_ISOLATED_PAGE_SIZE_4KB,
+ MSHV_ISOLATED_PAGE_SIZE_2MB,
+ MSHV_ISOLATED_PAGE_SIZE_COUNT
+};
+
+struct mshv_import_isolated_pages {
+ __u8 page_type;
+ __u8 page_size;
+ __u8 rsvd[6];
+ __u64 page_count;
+ __aligned_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;
+};
+
+struct mshv_snp_id_block {
+ __u8 launch_digest[48];
+ __u8 family_id[16];
+ __u8 image_id[16];
+ __u32 version;
+ __u32 guest_svn;
+ __u64 policy;
+};
+
+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];
+};
+
+/*
+ * This structure describes the PSP launch-finish wire data. The two trailing
+ * reserved bytes make the userspace object naturally aligned; they must be 0
+ * and are not copied into the packed Hyper-V ABI structure.
+ */
+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;
+ __u8 reserved[2];
+};
+
+struct mshv_complete_isolated_import {
+ __aligned_u64 psp_parameters;
+};
+
/**
* struct mshv_root_hvcall - arguments for MSHV_ROOT_HVCALL
* @code: Hypercall code (HVCALL_*)
@@ -254,6 +345,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] 27+ messages in thread
* [PATCH v3 2/7] mshv: add SEV-SNP PSP request hypercall
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-25 4:04 ` [PATCH v3 1/7] mshv: add SEV-SNP UAPI definitions Wei Hu
@ 2026-08-25 4:04 ` Wei Hu
2026-08-25 4:17 ` sashiko-bot
2026-08-25 4:04 ` [PATCH v3 3/7] mshv: add SEV-SNP isolated page hypercalls Wei Hu
` (5 subsequent siblings)
7 siblings, 1 reply; 27+ messages in thread
From: Wei Hu @ 2026-08-25 4:04 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] 27+ messages in thread
* [PATCH v3 3/7] mshv: add SEV-SNP isolated page hypercalls
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-25 4:04 ` [PATCH v3 1/7] mshv: add SEV-SNP UAPI definitions Wei Hu
2026-08-25 4:04 ` [PATCH v3 2/7] mshv: add SEV-SNP PSP request hypercall Wei Hu
@ 2026-08-25 4:04 ` Wei Hu
2026-08-25 4:04 ` [PATCH v3 4/7] mshv: wire SEV-SNP partition ioctls Wei Hu
` (4 subsequent siblings)
7 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-25 4:04 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 operations can complete asynchronously, so preserve the
rep completion count from the initial pending status unless the final
completion supplies a replacement.
Reject zero or oversized progress, report partial progress on failure,
and yield between successful batches so a large userspace request cannot
monopolize a CPU.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root.h | 12 ++++
drivers/hv/mshv_root_hv_call.c | 104 +++++++++++++++++++++++++++++++++
2 files changed, 116 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..ad5d5089a015 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,106 @@ 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 +
+ min_t(u64, completed, rep_count), num_pages,
+ hv_result_to_string(status));
+ return hv_result_to_errno(status);
+ }
+ if (!completed || completed > rep_count)
+ return -EIO;
+
+ gpa += completed;
+ remaining -= completed;
+ cond_resched();
+ }
+
+ 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] 27+ messages in thread
* [PATCH v3 4/7] mshv: wire SEV-SNP partition ioctls
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (2 preceding siblings ...)
2026-08-25 4:04 ` [PATCH v3 3/7] mshv: add SEV-SNP isolated page hypercalls Wei Hu
@ 2026-08-25 4:04 ` Wei Hu
2026-08-25 4:22 ` sashiko-bot
2026-08-25 4:04 ` [PATCH v3 5/7] mshv: detect and report SEV-SNP support at init Wei Hu
` (3 subsequent siblings)
7 siblings, 1 reply; 27+ messages in thread
From: Wei Hu @ 2026-08-25 4:04 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 ordered SNP
partition teardown.
Process userspace page arrays in bounded batches. The host-access ioctl
validates page-aligned guest GPAs and physical contiguity for large-page
requests; isolated import accepts guest PFNs and an explicit page size.
Translate the userspace launch-finish structure field by field into the
packed hypervisor ABI.
Keep encrypted memory regions pinned until GPA mappings are removed, the
partition reaches INSECURE_DIRTY, and host access has been restored. If
any safety-critical teardown phase fails, retain the partition and a
module reference instead of returning inaccessible pages to the host
allocator.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_regions.c | 10 +-
drivers/hv/mshv_root.h | 9 +
drivers/hv/mshv_root_hv_call.c | 47 ++-
drivers/hv/mshv_root_main.c | 627 ++++++++++++++++++++++++++++++++-
include/hyperv/hvgdk_mini.h | 12 +
5 files changed, 691 insertions(+), 14 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 6d65e5b42152..ed93bae0fa15 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,
@@ -354,17 +354,19 @@ static void mshv_region_destroy(struct kref *ref)
if (region->mreg_type == MSHV_REGION_TYPE_MEM_MOVABLE)
mshv_region_movable_fini(region);
- if (mshv_partition_encrypted(partition)) {
+ if (mshv_partition_encrypted(partition) &&
+ !partition->snp_host_access_restored) {
ret = mshv_region_share(region);
if (ret) {
pt_err(partition,
- "Failed to regain access to memory, unpinning user pages will fail and crash the host error: %d\n",
+ "Failed to regain access to memory, retaining pinned region: %d\n",
ret);
return;
}
}
- mshv_region_unmap(region);
+ if (!partition->snp_regions_unmapped)
+ mshv_region_unmap(region);
mshv_region_invalidate(region);
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 69226179cfbf..fe89c16d622e 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -139,6 +139,9 @@ struct mshv_partition {
struct mshv_girq_routing_table __rcu *pt_girq_tbl;
u64 isolation_type;
bool import_completed;
+ bool snp_regions_unmapped;
+ bool snp_host_access_restored;
+ bool teardown_quarantined;
bool pt_initialized;
#if IS_ENABLED(CONFIG_DEBUG_FS)
struct dentry *pt_stats_dentry;
@@ -370,6 +373,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);
@@ -378,6 +382,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 ad5d5089a015..9791df06e137 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(hv_result(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,
@@ -1194,8 +1229,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]);
@@ -1207,8 +1244,14 @@ int hv_call_modify_spa_host_access(u64 partition_id, struct page **pages,
completed = hv_repcomp(status);
- if (!hv_result_success(status))
+ if (!hv_result_success(status)) {
+ pr_err("%s: completed %d + %lu of %llu pages: %s\n",
+ __func__, done, completed, page_count,
+ hv_result_to_string(status));
return hv_result_to_errno(status);
+ }
+ if (!completed || completed > rep_count)
+ return -EIO;
done += completed;
}
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index cc2cfce2aefd..d946b4592d5b 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -65,6 +65,7 @@ static int mshv_vp_mmap(struct file *file, struct vm_area_struct *vma);
static vm_fault_t mshv_vp_fault(struct vm_fault *vmf);
static int mshv_init_async_handler(struct mshv_partition *partition);
static void mshv_async_hvcall_handler(void *data, u64 *status);
+static void mshv_quarantine_partition(struct mshv_partition *partition);
static const union hv_input_vtl input_vtl_zero;
static const union hv_input_vtl input_vtl_normal = {
@@ -641,6 +642,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.
@@ -1213,12 +1250,7 @@ mshv_partition_ioctl_create_vp(struct mshv_partition *partition,
static int mshv_init_async_handler(struct mshv_partition *partition)
{
- if (completion_done(&partition->async_hypercall)) {
- pt_err(partition,
- "Cannot issue async hypercall while another one in progress!\n");
- return -EPERM;
- }
-
+ /* Partition ioctls are serialized by pt_mutex. */
reinit_completion(&partition->async_hypercall);
return 0;
}
@@ -1434,6 +1466,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);
@@ -1648,6 +1682,409 @@ 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;
+}
+
+#define MSHV_USER_PAGE_BATCH_SIZE 4096
+
+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 __user *guest_gpas;
+ u64 *gpfns;
+ u64 offset = 0;
+ u32 host_access = 0;
+ u32 flags = 0;
+ bool acquire;
+ long ret = 0;
+
+ 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 ||
+ !args.guest_gpas)
+ return -EINVAL;
+ if ((args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_LARGE_PAGE)) &&
+ !IS_ALIGNED(args.page_count,
+ 1ULL << (HV_HYP_LARGE_PAGE_SHIFT - HV_HYP_PAGE_SHIFT)))
+ return -EINVAL;
+
+ guest_gpas = u64_to_user_ptr(args.guest_gpas);
+ gpfns = kvmalloc_array(MSHV_USER_PAGE_BATCH_SIZE, sizeof(*gpfns),
+ GFP_KERNEL);
+ pages = kvmalloc_array(MSHV_USER_PAGE_BATCH_SIZE, sizeof(*pages),
+ GFP_KERNEL);
+ if (!gpfns || !pages) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ 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))
+ flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
+ acquire = args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_ACQUIRE);
+
+ while (offset < args.page_count) {
+ u64 count = min_t(u64, args.page_count - offset,
+ MSHV_USER_PAGE_BATCH_SIZE);
+ u64 i;
+
+ if (copy_from_user(gpfns, guest_gpas + offset,
+ array_size(count, sizeof(*gpfns)))) {
+ ret = -EFAULT;
+ break;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (!IS_ALIGNED(gpfns[i], MSHV_HV_PAGE_SIZE)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ gpfns[i] = HVPFN_DOWN(gpfns[i]);
+ }
+
+ ret = mshv_gpfns_to_pages(partition, gpfns, count, pages);
+ if (ret)
+ break;
+
+ if (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE) {
+ if (!mshv_pages_are_contiguous_2m(pages, count)) {
+ ret = -EINVAL;
+ break;
+ }
+ }
+
+ ret = hv_call_modify_spa_host_access(partition->pt_id, pages,
+ count, host_access, flags,
+ acquire);
+ if (ret)
+ break;
+
+ offset += count;
+ cond_resched();
+ }
+
+out:
+ kvfree(pages);
+ 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 __user *guest_pfns;
+ u64 *pages;
+ u64 offset = 0;
+ long ret = 0;
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+
+ if (args.page_type >= MSHV_ISOLATED_PAGE_COUNT ||
+ args.page_size >= MSHV_ISOLATED_PAGE_SIZE_COUNT ||
+ mshv_field_nonzero(args, rsvd) || !args.page_count ||
+ !args.guest_pfns)
+ return -EINVAL;
+
+ guest_pfns = u64_to_user_ptr(args.guest_pfns);
+ pages = kvmalloc_array(MSHV_USER_PAGE_BATCH_SIZE, sizeof(*pages),
+ GFP_KERNEL);
+ if (!pages)
+ return -ENOMEM;
+
+ while (offset < args.page_count) {
+ u64 count = min_t(u64, args.page_count - offset,
+ MSHV_USER_PAGE_BATCH_SIZE);
+
+ if (copy_from_user(pages, guest_pfns + offset,
+ array_size(count, sizeof(*pages)))) {
+ ret = -EFAULT;
+ break;
+ }
+ if (args.page_size == MSHV_ISOLATED_PAGE_SIZE_2MB) {
+ u64 i;
+
+ for (i = 0; i < count; i++) {
+ if (!IS_ALIGNED(pages[i],
+ 1ULL << (HV_HYP_LARGE_PAGE_SHIFT -
+ HV_HYP_PAGE_SHIFT))) {
+ ret = -EINVAL;
+ goto import_out;
+ }
+ }
+ }
+
+ ret = mshv_init_async_handler(partition);
+ if (ret)
+ break;
+
+ ret = hv_call_import_isolated_pages(partition->pt_id, pages, count,
+ args.page_type, args.page_size,
+ mshv_async_hvcall_handler,
+ partition);
+ if (ret)
+ break;
+
+ offset += count;
+ cond_resched();
+ }
+
+import_out:
+ kvfree(pages);
+ return ret;
+}
+
+static void
+mshv_copy_snp_id_auth_info(struct hv_snp_id_auth_info *dst,
+ const struct mshv_snp_id_auth_info *src)
+{
+ dst->id_key_algorithm = src->id_key_algorithm;
+ dst->auth_key_algorithm = src->auth_key_algorithm;
+ memcpy(dst->reserved0, src->reserved0, sizeof(dst->reserved0));
+ memcpy(dst->id_block_signature, src->id_block_signature,
+ sizeof(dst->id_block_signature));
+ memcpy(dst->id_key, src->id_key, sizeof(dst->id_key));
+ memcpy(dst->reserved1, src->reserved1, sizeof(dst->reserved1));
+ memcpy(dst->id_key_signature, src->id_key_signature,
+ sizeof(dst->id_key_signature));
+ memcpy(dst->author_key, src->author_key, sizeof(dst->author_key));
+}
+
+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_psp_launch_finish_data *psp;
+ struct mshv_complete_isolated_import args;
+ long ret;
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+ if (!args.psp_parameters)
+ return -EINVAL;
+
+ psp = memdup_user(u64_to_user_ptr(args.psp_parameters), sizeof(*psp));
+ if (IS_ERR(psp))
+ return PTR_ERR(psp);
+ if (memchr_inv(psp->reserved, 0, sizeof(psp->reserved))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ import_data = kvzalloc(sizeof(*import_data), GFP_KERNEL);
+ if (!import_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(import_data->psp_parameters.id_block.launch_digest,
+ psp->id_block.launch_digest,
+ sizeof(psp->id_block.launch_digest));
+ memcpy(import_data->psp_parameters.id_block.family_id,
+ psp->id_block.family_id, sizeof(psp->id_block.family_id));
+ memcpy(import_data->psp_parameters.id_block.image_id,
+ psp->id_block.image_id, sizeof(psp->id_block.image_id));
+ import_data->psp_parameters.id_block.version = psp->id_block.version;
+ import_data->psp_parameters.id_block.guest_svn = psp->id_block.guest_svn;
+ import_data->psp_parameters.id_block.policy.as_uint64 =
+ psp->id_block.policy;
+ mshv_copy_snp_id_auth_info(&import_data->psp_parameters.id_auth_info,
+ &psp->id_auth_info);
+ memcpy(import_data->psp_parameters.host_data, psp->host_data,
+ sizeof(psp->host_data));
+ import_data->psp_parameters.id_block_enabled = psp->id_block_enabled;
+ import_data->psp_parameters.author_key_enabled = psp->author_key_enabled;
+
+ ret = mshv_init_async_handler(partition);
+ if (!ret)
+ ret = hv_call_complete_isolated_import(partition->pt_id,
+ import_data,
+ mshv_async_hvcall_handler,
+ partition);
+ if (!ret)
+ partition->import_completed = true;
+
+ kvfree(import_data);
+out:
+ kfree(psp);
+ 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)
+ goto restore_host_access;
+
+ 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:
+ {
+ int restore_ret;
+
+ restore_ret = hv_call_modify_spa_host_access(partition->pt_id,
+ pages, ARRAY_SIZE(pages),
+ host_access, 0, true);
+ if (restore_ret) {
+ pt_err(partition,
+ "Failed to restore PSP request page access: %d\n",
+ restore_ret);
+ mshv_quarantine_partition(partition);
+ if (!ret)
+ ret = restore_ret;
+ }
+ }
+ 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)
{
@@ -1684,6 +2121,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;
}
@@ -1795,6 +2241,79 @@ 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 mshv_quarantine_partition(struct mshv_partition *partition)
+{
+ if (partition->teardown_quarantined)
+ return;
+
+ /* Keep module text and the pinned inaccessible pages alive. */
+ __module_get(THIS_MODULE);
+ partition->teardown_quarantined = true;
+}
+
/*
* Tear down a partition and remove it from the list.
* Partition's refcount must be 0
@@ -1805,6 +2324,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,
@@ -1814,7 +2336,87 @@ static void destroy_partition(struct mshv_partition *partition)
trace_mshv_destroy_partition(partition->pt_id);
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+ if (!partition->pt_initialized && mshv_partition_encrypted(partition)) {
+ int region_ret;
+
+ ret = 0;
+ hlist_for_each_entry_safe(region, n,
+ &partition->pt_mem_regions, hnode) {
+ region_ret = mshv_region_share(region);
+ if (region_ret) {
+ pt_err(partition,
+ "Failed to restore host access at GFN %#llx: %d\n",
+ region->start_gfn, region_ret);
+ ret = region_ret;
+ }
+ }
+ if (ret) {
+ pt_err(partition,
+ "Retaining uninitialized partition with inaccessible SNP pages\n");
+ mshv_quarantine_partition(partition);
+ return;
+ }
+ partition->snp_host_access_restored = true;
+ }
+#endif
+
if (partition->pt_initialized) {
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+ if (mshv_partition_encrypted(partition)) {
+ int region_ret;
+
+ ret = 0;
+ hlist_for_each_entry_safe(region, n,
+ &partition->pt_mem_regions,
+ hnode) {
+ region_ret = mshv_region_unmap(region);
+ if (region_ret) {
+ pt_err(partition,
+ "Failed to unmap SNP region at GFN %#llx: %d\n",
+ region->start_gfn, region_ret);
+ ret = region_ret;
+ }
+ }
+ if (ret) {
+ pt_err(partition,
+ "Retaining partition after SNP region unmap failure\n");
+ mshv_quarantine_partition(partition);
+ return;
+ }
+
+ ret = destroy_snp_partition_state(partition);
+ if (ret) {
+ pt_err(partition,
+ "Failed to destroy SNP state: %d; retaining partition\n",
+ ret);
+ mshv_quarantine_partition(partition);
+ return;
+ }
+
+ ret = 0;
+ hlist_for_each_entry_safe(region, n,
+ &partition->pt_mem_regions,
+ hnode) {
+ region_ret = mshv_region_share(region);
+ if (region_ret) {
+ pt_err(partition,
+ "Failed to restore host access at GFN %#llx: %d\n",
+ region->start_gfn, region_ret);
+ ret = region_ret;
+ }
+ }
+ if (ret) {
+ pt_err(partition,
+ "Retaining partition with inaccessible SNP pages\n");
+ mshv_quarantine_partition(partition);
+ return;
+ }
+
+ partition->snp_regions_unmapped = true;
+ partition->snp_host_access_restored = true;
+ }
+#endif
/*
* We only need to drain signals for root scheduler. This should be
* done before removing the partition from the partition list.
@@ -1822,6 +2424,8 @@ static void destroy_partition(struct mshv_partition *partition)
if (hv_scheduler_type == HV_SCHEDULER_TYPE_ROOT)
drain_all_vps(partition);
+ remove_partition(partition);
+
/* Remove vps */
for (i = 0; i < MSHV_MAX_VPS; ++i) {
vp = partition->pt_vp_array[i];
@@ -1868,10 +2472,10 @@ static void destroy_partition(struct mshv_partition *partition)
hv_call_finalize_partition(partition->pt_id);
partition->pt_initialized = false;
+ } else {
+ remove_partition(partition);
}
- remove_partition(partition);
-
hlist_for_each_entry_safe(region, n, &partition->pt_mem_regions,
hnode) {
hlist_del(®ion->hnode);
@@ -1883,6 +2487,8 @@ static void destroy_partition(struct mshv_partition *partition)
hv_call_delete_partition(partition->pt_id);
mshv_free_routing_table(partition);
+ if (partition->teardown_quarantined)
+ module_put(THIS_MODULE);
kfree(partition);
}
@@ -2039,6 +2645,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;
@@ -2074,6 +2683,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] 27+ messages in thread
* [PATCH v3 5/7] mshv: detect and report SEV-SNP support at init
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (3 preceding siblings ...)
2026-08-25 4:04 ` [PATCH v3 4/7] mshv: wire SEV-SNP partition ioctls Wei Hu
@ 2026-08-25 4:04 ` Wei Hu
2026-08-25 4:19 ` sashiko-bot
2026-08-25 4:04 ` [PATCH v3 6/7] mshv: use safe partition CPU feature defaults Wei Hu
` (2 subsequent siblings)
7 siblings, 1 reply; 27+ messages in thread
From: Wei Hu @ 2026-08-25 4:04 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 MSHV dynamic processor features during parent initialization and
report SEV-SNP availability and the maximum number of encrypted
partitions.
L1VH parents reject the SNP property query, so treat query failures as
"not available" rather than failing MSHV initialization. Copy successful
results from the shared per-CPU output page before restoring local
interrupts.
The misc device is registered before parent initialization is complete.
Reject opens until the partition hash table and interrupt handling are
ready, and clear readiness before teardown starts.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 106 +++++++++++++++++++++++++++++++++++-
1 file changed, 105 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index d946b4592d5b..59f55bd0e7c9 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -100,6 +100,8 @@ static const struct file_operations mshv_dev_fops = {
.llseek = noop_llseek,
};
+static bool mshv_ready;
+
static struct miscdevice mshv_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mshv",
@@ -2757,7 +2759,7 @@ static long mshv_dev_ioctl(struct file *filp, unsigned int ioctl,
static int
mshv_dev_open(struct inode *inode, struct file *filp)
{
- return 0;
+ return READ_ONCE(mshv_ready) ? 0 : -EAGAIN;
}
static int
@@ -2903,6 +2905,104 @@ 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 snp_partition_count = 0;
+ 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);
+ if (hv_result_success(status))
+ snp_partition_count = output->hv_processor_feature_value;
+ 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",
+ snp_partition_count);
+}
+
+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 = HV_SNP_STATUS_NONE;
+ 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);
+ if (hv_result_success(status))
+ snp_status = output->hv_processor_feature_value;
+ local_irq_restore(flags);
+ if (!hv_result_success(status)) {
+ /* L1VH parents reject this property query. */
+ dev_info(dev, "SEV-SNP support is not available\n");
+ return;
+ }
+
+ 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)
{
@@ -3006,6 +3106,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;
@@ -3022,6 +3124,7 @@ static int __init mshv_parent_partition_init(void)
hash_init(mshv_root.pt_htable);
hv_setup_mshv_handler(mshv_isr);
+ WRITE_ONCE(mshv_ready, true);
return 0;
@@ -3038,6 +3141,7 @@ static int __init mshv_parent_partition_init(void)
static void __exit mshv_parent_partition_exit(void)
{
+ WRITE_ONCE(mshv_ready, false);
hv_setup_mshv_handler(NULL);
mshv_port_table_fini();
mshv_debugfs_exit();
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 6/7] mshv: use safe partition CPU feature defaults
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (4 preceding siblings ...)
2026-08-25 4:04 ` [PATCH v3 5/7] mshv: detect and report SEV-SNP support at init Wei Hu
@ 2026-08-25 4:04 ` Wei Hu
2026-08-25 4:04 ` [PATCH v3 7/7] mshv: set up own SynIC registers on a nested root partition Wei Hu
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
7 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-25 4:04 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>
Initialize the complete partition creation properties and build a
conservative x86 disabled-feature mask for legacy callers. Enable only
the default processor and XSAVE features known to be supported, while
preserving nested virtualization when the caller requests it.
Allow extended callers to provide a partial set of processor-feature
banks and retain safe defaults for omitted banks. Define the full
processor and XSAVE feature masks used by the defaults.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 125 +++++++++++++++++++++++++++++-------
include/hyperv/hvhdk.h | 79 ++++++++++++++++++++++-
include/uapi/linux/mshv.h | 3 +-
3 files changed, 181 insertions(+), 26 deletions(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 59f55bd0e7c9..761e6302aab5 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2573,33 +2573,124 @@ 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;
+ if (args.pt_flags & BIT(MSHV_PT_BIT_NESTED_VIRTUALIZATION))
+ disabled_procs->nested_virt_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 */
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;
- 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)
@@ -2614,16 +2705,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 */
@@ -2685,8 +2766,6 @@ 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/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;
};
diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
index 5e338c605072..259e886e9cfa 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] 27+ messages in thread
* [PATCH v3 7/7] mshv: set up own SynIC registers on a nested root partition
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (5 preceding siblings ...)
2026-08-25 4:04 ` [PATCH v3 6/7] mshv: use safe partition CPU feature defaults Wei Hu
@ 2026-08-25 4:04 ` Wei Hu
2026-08-25 4:20 ` sashiko-bot
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
7 siblings, 1 reply; 27+ messages in thread
From: Wei Hu @ 2026-08-25 4:04 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] 27+ messages in thread
* Re: [PATCH v3 2/7] mshv: add SEV-SNP PSP request hypercall
2026-08-25 4:04 ` [PATCH v3 2/7] mshv: add SEV-SNP PSP request hypercall Wei Hu
@ 2026-08-25 4:17 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-25 4:17 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The function misuses the shared per-CPU `hyperv_pcpu_input_arg` for an asynchronous hypercall, potentially corrupting the hypercall input.
- [Medium] Un-ratelimited `pr_err` on the guest-triggerable hypercall failure path allows a malicious guest to flood the host kernel log.
--
--- Patch [2]: [PATCH v3 2/7] mshv: add SEV-SNP PSP request hypercall ---
commit 8b1f140f3a0780e521d7a96627211fe9e4967940
Author: Wei Liu <wei.liu@kernel.org>
mshv: add SEV-SNP PSP request hypercall
This commit adds a helper for HVCALL_ISSUE_SNP_PSP_GUEST_REQUEST so MSHV
can issue asynchronous PSP guest requests on behalf of encrypted partitions.
> diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
> index cb55d4d4be2e5..48abe338c06f8 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);
[Severity: High]
Does this code risk corrupting the hypercall input data?
The function uses the shared per-CPU hyperv_pcpu_input_arg for the hypercall.
However, it restores interrupts and invokes completion_handler() which blocks
while the hypercall is pending.
During this time, another thread scheduled on the same CPU can issue a
hypercall and overwrite the shared per-CPU page while the hypervisor is still
reading the original parameters asynchronously.
> +
> + if (!hv_result_success(status)) {
> + pr_err("%s: status=%s partition_id=%llu\n", __func__,
> + hv_result_to_string(status), partition_id);
[Severity: Medium]
Could this unconditional error print be used to flood the host kernel log?
If a malicious guest VM repeatedly issues invalid SEV-SNP PSP requests, the
hypercall will fail and trigger this error print on the failure path.
Since this can be triggered at will by the guest, it might be safer to
rate-limit this message.
> + return hv_result_to_errno(status);
> + }
> +
> + return 0;
> +}
> +#endif
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825040505.826600-1-weh@linux.microsoft.com?part=2
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 5/7] mshv: detect and report SEV-SNP support at init
2026-08-25 4:04 ` [PATCH v3 5/7] mshv: detect and report SEV-SNP support at init Wei Hu
@ 2026-08-25 4:19 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-25 4:19 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] Missing memory barriers when updating and reading `mshv_ready` allows concurrent userspace opens to access uninitialized driver state on weakly-ordered architectures.
--
--- Patch [5]: [PATCH v3 5/7] mshv: detect and report SEV-SNP support at init ---
commit fb8f9421bc97a7ed49258a973cb3c9b05f5b3049
Author: Wei Liu <wei.liu@kernel.org>
mshv: detect and report SEV-SNP support at init
This commit queries MSHV dynamic processor features during parent
initialization to report SEV-SNP availability and the maximum number
of encrypted partitions. It also introduces a readiness flag to reject
userspace opens until initialization is complete.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> @@ -2757,7 +2759,7 @@ static long mshv_dev_ioctl(struct file *filp, unsigned int ioctl,
> static int
> mshv_dev_open(struct inode *inode, struct file *filp)
> {
> - return 0;
> + return READ_ONCE(mshv_ready) ? 0 : -EAGAIN;
[Severity: High]
Since READ_ONCE() does not provide a load-acquire barrier, could subsequent
loads in the ioctl path be reordered on weakly-ordered architectures like
ARM64?
If a userspace process concurrently opens the device and calls an ioctl
while the module is initializing, it might observe mshv_ready as true but
still access uninitialized driver structures. Would using smp_load_acquire()
here prevent this reordering?
[ ... ]
> @@ -3022,6 +3124,7 @@ static int __init mshv_parent_partition_init(void)
> hash_init(mshv_root.pt_htable);
>
> hv_setup_mshv_handler(mshv_isr);
> + WRITE_ONCE(mshv_ready, true);
[Severity: High]
Because WRITE_ONCE() lacks a store-release barrier, is it possible for the
CPU to commit this store before the preceding stores that initialize the
driver state, such as the workqueue allocation or mshv_root.pt_htable?
Using smp_store_release() instead would ensure all preceding initialization
is globally visible before userspace can successfully open the device.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825040505.826600-1-weh@linux.microsoft.com?part=5
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 7/7] mshv: set up own SynIC registers on a nested root partition
2026-08-25 4:04 ` [PATCH v3 7/7] mshv: set up own SynIC registers on a nested root partition Wei Hu
@ 2026-08-25 4:20 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-25 4:20 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing MEMREMAP_DEC flag and shared GPA boundary masking when mapping hypervisor-shared SynIC pages.
- [High] Missing pointer nullification on the error path of mshv_synic_cpu_init leaves a dangling pointer in a per-cpu variable.
--
--- Patch [7]: [PATCH v3 7/7] mshv: set up own SynIC registers on a nested root partition ---
commit 04d92759c80f66439e3b646d824c274dc6edc45b
Author: Wei Hu <weh@microsoft.com>
mshv: set up own SynIC registers on a nested root partition
The commit fixes SynIC register setup for nested root partitions by programming
non-nested MSRs instead of relying on VMBus's nested MSR setup, restoring
correct intercept and async-hypercall delivery.
> diff --git a/drivers/hv/mshv_synic.c b/drivers/hv/mshv_synic.c
> index 7c168e5a740dd..7d714ee5d2267 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
> * hypervisor pre-provisions the SIMP GPA but may not set
> * simp_enabled — enable it here.
> */
> simp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIMP);
> if (!vmbus_active) {
> simp.simp_enabled = true;
> hv_set_non_nested_msr(HV_MSR_SIMP, simp.as_uint64);
> }
> *msg_page = memremap(simp.base_simp_gpa << HV_HYP_PAGE_SHIFT,
> HV_HYP_PAGE_SIZE,
> MEMREMAP_WB);
[Severity: High]
This isn't a bug introduced by this patch, but does this mapping need to use
MEMREMAP_DEC and mask the shared_gpa_boundary bit?
If the root partition is running as a confidential VM (such as AMD SEV-SNP or
Intel TDX), hypervisor-shared pages need to be mapped as decrypted.
Mapping them as encrypted memory could cause the driver to read garbage
intercept messages and miss doorbell events.
[ ... ]
> cleanup_simp:
> if (*msg_page)
> memunmap(*msg_page);
[Severity: High]
This is a pre-existing issue, but does this leave a dangling pointer in the
per-cpu synic_pages structure?
If memremap() fails for the event flags or ring pages during initialization,
the error path unmaps *msg_page but doesn't set the per-CPU pointer to NULL.
Since the Hyper-V callback vector can be shared globally, a shared interrupt
(like from VMBus) could fire on this CPU after a failure.
If the IDT handler unconditionally calls mshv_isr(), could it bypass a NULL
check and dereference the unmapped pointer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825040505.826600-1-weh@linux.microsoft.com?part=7
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 4/7] mshv: wire SEV-SNP partition ioctls
2026-08-25 4:04 ` [PATCH v3 4/7] mshv: wire SEV-SNP partition ioctls Wei Hu
@ 2026-08-25 4:22 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-25 4:22 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] Revoking host access to guest pages without invalidating them in the kernel's direct map allows stray kernel accesses to crash the host system.
- [High] destroy_partition attempts to restore host access before unmapping memory for uninitialized SEV-SNP partitions, guaranteeing failure and permanent memory leak.
- [Medium] Batched IOCTLs abort on failure midway through without reporting progress or unrolling completed operations, permanently bricking partition state.
Pre-existing issues:
- [Critical] Ignored return value of mshv_region_unmap() in mshv_region_destroy() allows guest access to freed host physical memory.
--
--- Patch [4]: [PATCH v3 4/7] mshv: wire SEV-SNP partition ioctls ---
commit 438c5ba1c23ba880c2f38d767dbf75dd5ce5c64b
Author: Wei Liu <wei.liu@kernel.org>
Subject: 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 ordered SNP
partition teardown. Process userspace page arrays in bounded batches.
> --- a/drivers/hv/mshv_regions.c
> +++ b/drivers/hv/mshv_regions.c
[ ... ]
> @@ -354,17 +354,19 @@ static void mshv_region_destroy(struct kref *ref)
> if (region->mreg_type == MSHV_REGION_TYPE_MEM_MOVABLE)
> mshv_region_movable_fini(region);
>
> - if (mshv_partition_encrypted(partition)) {
> + if (mshv_partition_encrypted(partition) &&
> + !partition->snp_host_access_restored) {
> ret = mshv_region_share(region);
> if (ret) {
> pt_err(partition,
> - "Failed to regain access to memory, unpinning user pages will fail and crash the host error: %d\n",
> + "Failed to regain access to memory, retaining pinned region: %d\n",
> ret);
> return;
> }
> }
>
> - mshv_region_unmap(region);
> + if (!partition->snp_regions_unmapped)
> + mshv_region_unmap(region);
>
> mshv_region_invalidate(region);
[Severity: Critical]
This is a pre-existing issue, but does mshv_region_destroy() ignore the
return value of mshv_region_unmap()?
If a user issues MSHV_SET_GUEST_MEMORY with the UNMAP flag on an active
partition, mshv_unmap_user_memory() is triggered. This removes the region
from the list and calls mshv_region_put().
When the refcount drops to 0, mshv_region_destroy() calls
mshv_region_unmap() to perform HVCALL_UNMAP_GPA_PAGES. If this hypercall
fails due to hypervisor state or invalid parameters, the pages remain mapped
in the guest's GPA space.
Because the return value is ignored, the code unconditionally proceeds to
mshv_region_invalidate(), unpinning the pages and returning them to the
host buddy allocator. Can the running guest then trivially read or write
these freed pages, resulting in host memory corruption via use-after-free?
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> @@ -1648,6 +1682,409 @@ mshv_partition_ioctl_initialize(struct mshv_partition *partition)
> return ret;
> }
>
> +#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
[ ... ]
> +static long
> +mshv_partition_ioctl_modify_gpa_host_access(struct mshv_partition *partition,
> + void __user *user_args)
> +{
[ ... ]
> + ret = hv_call_modify_spa_host_access(partition->pt_id, pages,
> + count, host_access, flags,
> + acquire);
[Severity: Critical]
When revoking host access from guest pages here, shouldn't we invalidate
them in the kernel's direct map?
MSHV_MODIFY_GPA_HOST_ACCESS calls hv_call_modify_spa_host_access() with
HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE to make pages exclusive to
the guest.
If these pages are not removed from the Linux kernel's direct map using
set_direct_map_invalid_noflush(), they remain mapped on the host while
hardware-level access is revoked by the hypervisor.
Could speculative execution, prefetching, or background kernel activity
accessing these virtual addresses trigger a hardware exception because the
underlying physical memory is protected by SEV-SNP RMP rules, leading to a
complete host panic?
> + if (ret)
> + break;
[Severity: Medium]
If the loop breaks midway due to a validation or hypercall failure, are the
completed pages unrolled?
Memory arrays are processed in chunks of 4096 pages. Validation checks like
mshv_pages_are_contiguous_2m() and the hypercalls are performed inside this
loop.
If a check fails on a subsequent iteration, the loop breaks and returns an
error code. Since the input arguments are passed by value, userspace receives
a generic error and cannot determine which pages were modified.
Does this leave the VM in a fragmented state where neither the kernel nor
userspace can safely clean up or resume the operation?
[ ... ]
> +static long
> +mshv_partition_ioctl_import_isolated_pages(struct mshv_partition *partition,
> + void __user *user_args)
> +{
[ ... ]
> + ret = hv_call_import_isolated_pages(partition->pt_id, pages, count,
> + args.page_type, args.page_size,
> + mshv_async_hvcall_handler,
> + partition);
> + if (ret)
> + break;
[Severity: Medium]
Does this loop share the same brittle chunking and abort logic as
mshv_partition_ioctl_modify_gpa_host_access() mentioned above?
[ ... ]
> @@ -1805,6 +2324,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,
> @@ -1814,7 +2336,87 @@ static void destroy_partition(struct mshv_partition *partition)
>
> trace_mshv_destroy_partition(partition->pt_id);
>
> +#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
> + if (!partition->pt_initialized && mshv_partition_encrypted(partition)) {
> + int region_ret;
> +
> + ret = 0;
> + hlist_for_each_entry_safe(region, n,
> + &partition->pt_mem_regions, hnode) {
> + region_ret = mshv_region_share(region);
> + if (region_ret) {
> + pt_err(partition,
> + "Failed to restore host access at GFN %#llx: %d\n",
> + region->start_gfn, region_ret);
> + ret = region_ret;
> + }
> + }
> + if (ret) {
> + pt_err(partition,
> + "Retaining uninitialized partition with inaccessible SNP pages\n");
> + mshv_quarantine_partition(partition);
> + return;
> + }
[Severity: High]
When tearing down an uninitialized SEV-SNP partition, does
destroy_partition() fail to restore host access because the memory hasn't
been unmapped yet?
If a user creates an SNP partition, maps memory (which creates active GPA
mappings via mshv_prepare_pinned_region()), and then closes the fd without
initializing the partition, this path executes.
The code explicitly loops over all memory regions and calls
mshv_region_share() to restore host access before unmapping the GPAs,
which happens later when mshv_region_destroy() is triggered.
Will the hypervisor reject the share request since the pages are still
actively mapped in the guest's SLAT? The resulting mshv_region_share()
failure triggers mshv_quarantine_partition(), intentionally leaking the
partition object and its pinned memory.
Could an unprivileged user exploit this to cause host memory exhaustion by
rapidly creating, mapping, and destroying uninitialized partitions?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825040505.826600-1-weh@linux.microsoft.com?part=4
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (6 preceding siblings ...)
2026-08-25 4:04 ` [PATCH v3 7/7] mshv: set up own SynIC registers on a nested root partition Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 11:26 ` [PATCH v4 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
` (8 more replies)
7 siblings, 9 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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 fixed-size MSHV UAPI definitions, the required Microsoft
Hypervisor ABI definitions and hypercall helpers, partition ioctls,
capability discovery, processor-feature handling, ordered encrypted-memory
teardown, and nested-root SynIC handling.
Two prerequisite fixes now lead the series. The first makes memory-region
unmap ownership explicit and retains mappings, pinned pages, the partition,
and the module whenever checked hypervisor unmap cannot prove cleanup. The
second publishes and withdraws per-CPU SynIC pointers so interrupt readers
cannot observe mappings after initialization failure or CPU teardown.
The patches are based on the current hyperv-next branch.
Testing:
- Built all four affected x86 MSHV objects at every commit.
- Built the complete x86_64 kernel and the affected objects with W=1.
- Ran scripts/checkpatch.pl --strict on every patch with zero findings.
- Verified installed UAPI layouts in 64-bit and 32-bit userspace builds.
- Generated rust-vmm MSHV bindings from the installed kernel headers.
- Built the affected ARM64 objects with W=1 and no warnings.
- Passed Cloud Hypervisor's common_cvm::test_focal_simple_launch.
- Booted a four-vCPU SEV-SNP guest to login.
The development host needs two additional local runtime patches to boot
as a nested MSHV root partition: EFI HvLoader root-partition boot
enablement and the non-upstreamable nested-VMBus interrupt-vector
workaround. Neither patch is part of this series.
Changes since v3:
- Add checked memory-region unmap ownership as prerequisite patch 1. Keep
pages pinned and quarantine uncertain setup or teardown instead of
freeing memory that may remain child-accessible.
- Add SynIC pointer publication/withdrawal cleanup as prerequisite
patch 2.
- Rate-limit guest-triggerable PSP and isolated-page hypercall errors.
- Publish and consume driver readiness with release/acquire ordering.
- Make both array ioctls _IOWR interfaces with an MBZ input/completed
output
field, exact resumable progress, and defined no-rollback semantics.
- Preserve the exact asynchronous completion result, encode only bounded
repetition progress, and keep the distinct substatus out of result
bits.
- Check setup, explicit unmap, and teardown transitions; preserve list
and
kref ownership and quarantine any state whose safe release is
uncertain.
- Unmap child mappings before restoring host access for uninitialized as
well as initialized SNP partitions.
- Map hypervisor SynIC pages as decrypted shared-GPA mappings and
withdraw
per-CPU pointers before unmapping or freeing them.
- Validate VMSA and PSP GPAs before PFN conversion and retain the
existing
large-page alignment and physical-contiguity checks.
- Reject SNP isolation in intermediate commits until its complete ioctl
and
lifecycle implementation is present.
- Guard the SNP-only GPFN conversion helper so ARM64 W=1 builds do not
emit
an unused-function warning.
- Resolve additional adversarial-review findings: initialize common
region
locks for every region type, avoid mmap/remap lock inversion, validate
zero/oversized REP progress, and initialize all common copyout paths.
Two Sashiko findings were investigated and dismissed rather than papered
over.
Hyper-V consumes the PSP request input synchronously before returning
HV_STATUS_CALL_PENDING, so the shared per-CPU input page need not remain
reserved while waiting for completion. Also, MSHV's modify-SPA-host-access
operation transfers root-to-child SPA/SLAT ownership in Hyper-V; it does
not
perform an in-place SNP RMP transition on Linux-owned direct-map pages, so
kernel direct-map invalidation, encryption-attribute changes, and
shared-bit
rewrites would be incorrect here.
Link: https://lore.kernel.org/linux-hyperv/20260825040505.826600-1-weh@linux.microsoft.com/
Wei Hu (3):
mshv: retain memory regions until unmap succeeds
mshv: clear SynIC mappings before freeing them
mshv: set up own SynIC registers on a nested root partition
Wei Liu (6):
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: use safe partition CPU feature defaults
drivers/hv/mshv_regions.c | 160 +++--
drivers/hv/mshv_root.h | 50 +-
drivers/hv/mshv_root_hv_call.c | 303 +++++++--
drivers/hv/mshv_root_main.c | 1045 +++++++++++++++++++++++++++++---
drivers/hv/mshv_synic.c | 172 +++---
include/hyperv/hvgdk_mini.h | 31 +
include/hyperv/hvhdk.h | 124 +++-
include/hyperv/hvhdk_mini.h | 53 ++
include/uapi/linux/mshv.h | 111 +++-
9 files changed, 1813 insertions(+), 236 deletions(-)
base-commit: be0cfab740e58b70047ef6e7e3d578f00ed5d258
--
2.43.0
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v4 1/9] mshv: retain memory regions until unmap succeeds
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 11:48 ` sashiko-bot
2026-08-31 11:26 ` [PATCH v4 2/9] mshv: clear SynIC mappings before freeing them Wei Hu
` (7 subsequent siblings)
8 siblings, 1 reply; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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>
Do not remove a region from the partition list or unpin its pages until
a checked UnmapGpaPages hypercall has completed. Validate repetition
progress so malformed zero or oversized completion counts cannot spin or
conceal partial state.
Map failure cleanup now unmaps the full requested range. If cleanup is
uncertain, retain the region, partition, and module rather than allowing
the child partition to access freed pages. Keep fallible hypervisor
cleanup out of the kref release callback.
For explicit movable-region unmap, take temporary ownership and detach
under the lookup lock, then take the write side of a remap semaphore to
drain fault and MMU-notifier remaps. Reinsert with live list ownership
before re-enabling remaps on failure. Partition teardown uses the same
quiescing while regions remain discoverable.
Signed-off-by: Wei Hu <weh@microsoft.com>
---
drivers/hv/mshv_regions.c | 128 +++++++++++++++++++++++----------
drivers/hv/mshv_root.h | 14 +++-
drivers/hv/mshv_root_hv_call.c | 81 ++++++++++++++-------
drivers/hv/mshv_root_main.c | 109 +++++++++++++++++++++++++---
4 files changed, 256 insertions(+), 76 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 6d65e5b42152..9feb49d9d9d4 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -188,6 +188,8 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
region->hv_map_flags |= HV_MAP_GPA_EXECUTABLE;
kref_init(®ion->mreg_refcount);
+ mutex_init(®ion->mreg_mutex);
+ init_rwsem(®ion->mreg_remap_lock);
return region;
}
@@ -245,13 +247,21 @@ static int mshv_region_chunk_remap(struct mshv_mem_region *region,
u64 page_offset, u64 page_count,
bool huge_page)
{
+ bool chunk_mapping_may_exist;
+ int ret;
+
if (huge_page)
flags |= HV_MAP_GPA_LARGE_PAGE;
- return hv_call_map_gpa_pages(region->partition->pt_id,
- region->start_gfn + page_offset,
- page_count, flags,
- region->mreg_pages + page_offset);
+ ret = hv_call_map_gpa_pages(region->partition->pt_id,
+ region->start_gfn + page_offset,
+ page_count, flags,
+ region->mreg_pages + page_offset,
+ &chunk_mapping_may_exist);
+ if (chunk_mapping_may_exist)
+ region->mapping_may_exist = true;
+
+ return ret;
}
static int mshv_region_remap_pages(struct mshv_mem_region *region,
@@ -337,37 +347,39 @@ 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,
- mshv_region_chunk_unmap);
+ int ret;
+
+ if (!region->mapping_may_exist)
+ return 0;
+
+ if (region->mreg_type == MSHV_REGION_TYPE_MEM_PINNED)
+ ret = mshv_region_process_range(region, 0, 0, region->nr_pages,
+ mshv_region_chunk_unmap);
+ else
+ ret = hv_call_unmap_gpa_pages(region->partition->pt_id,
+ region->start_gfn,
+ region->nr_pages, 0);
+ if (!ret)
+ region->mapping_may_exist = false;
+
+ return ret;
}
static void mshv_region_destroy(struct kref *ref)
{
struct mshv_mem_region *region =
container_of(ref, struct mshv_mem_region, mreg_refcount);
- struct mshv_partition *partition = region->partition;
- int ret;
+
+ /* Callers must retain the list reference until cleanup is certain. */
+ if (WARN_ON_ONCE(region->mapping_may_exist))
+ return;
if (region->mreg_type == MSHV_REGION_TYPE_MEM_MOVABLE)
mshv_region_movable_fini(region);
- if (mshv_partition_encrypted(partition)) {
- ret = mshv_region_share(region);
- if (ret) {
- pt_err(partition,
- "Failed to regain access to memory, unpinning user pages will fail and crash the host error: %d\n",
- ret);
- return;
- }
- }
-
- mshv_region_unmap(region);
-
mshv_region_invalidate(region);
-
vfree(region);
}
@@ -386,15 +398,10 @@ int mshv_region_get(struct mshv_mem_region *region)
* @region: Pointer to the memory region structure
* @range: Pointer to the HMM range structure
*
- * This function performs the following steps:
- * 1. Reads the notifier sequence for the HMM range.
- * 2. Acquires a read lock on the memory map.
- * 3. Handles HMM faults for the specified range.
- * 4. Releases the read lock on the memory map.
- * 5. If successful, locks the memory region mutex.
- * 6. Verifies if the notifier sequence has changed during the operation.
- * If it has, releases the mutex and returns -EBUSY to match with
- * hmm_range_fault() return code for repeating.
+ * The caller holds mmap_lock for read and mreg_remap_lock for read. Read the
+ * notifier sequence, handle HMM faults, and then lock the region mutex. Verify
+ * the sequence after taking the mutex; if it changed, release the mutex and
+ * return -EBUSY to match the hmm_range_fault() retry convention.
*
* Return: 0 on success, a negative error code otherwise.
*/
@@ -404,9 +411,7 @@ static int mshv_region_hmm_fault_and_lock(struct mshv_mem_region *region,
int ret;
range->notifier_seq = mmu_interval_read_begin(range->notifier);
- mmap_read_lock(region->mreg_mni.mm);
ret = hmm_range_fault(range);
- mmap_read_unlock(region->mreg_mni.mm);
if (ret)
return ret;
@@ -440,6 +445,7 @@ static int mshv_region_range_fault(struct mshv_mem_region *region,
.notifier = ®ion->mreg_mni,
.default_flags = HMM_PFN_REQ_FAULT | HMM_PFN_REQ_WRITE,
};
+ struct mm_struct *mm;
unsigned long *pfns;
int ret;
u64 i;
@@ -452,12 +458,35 @@ static int mshv_region_range_fault(struct mshv_mem_region *region,
range.start = region->start_uaddr + page_offset * HV_HYP_PAGE_SIZE;
range.end = range.start + page_count * HV_HYP_PAGE_SIZE;
+ /*
+ * Pin the mm while the remap gate still guarantees that the interval
+ * notifier is registered. Do not acquire mmap_lock while holding the
+ * gate: invalidation enters with mmap_lock held for write.
+ */
+ down_read(®ion->mreg_remap_lock);
+ if (READ_ONCE(region->remap_blocked) ||
+ !mmget_not_zero(region->mreg_mni.mm)) {
+ up_read(®ion->mreg_remap_lock);
+ ret = -ECANCELED;
+ goto out;
+ }
+ mm = region->mreg_mni.mm;
+ up_read(®ion->mreg_remap_lock);
+
+ /* Lock order: mmap_lock -> mreg_remap_lock -> mreg_mutex. */
+ mmap_read_lock(mm);
+ down_read(®ion->mreg_remap_lock);
+ if (READ_ONCE(region->remap_blocked)) {
+ ret = -ECANCELED;
+ goto out_unlock_remap;
+ }
+
do {
ret = mshv_region_hmm_fault_and_lock(region, &range);
} while (ret == -EBUSY);
if (ret)
- goto out;
+ goto out_unlock_remap;
for (i = 0; i < page_count; i++)
region->mreg_pages[page_offset + i] = hmm_pfn_to_page(pfns[i]);
@@ -466,6 +495,10 @@ static int mshv_region_range_fault(struct mshv_mem_region *region,
page_offset, page_count);
mutex_unlock(®ion->mreg_mutex);
+out_unlock_remap:
+ up_read(®ion->mreg_remap_lock);
+ mmap_read_unlock(mm);
+ mmput(mm);
out:
kfree(pfns);
return ret;
@@ -486,10 +519,11 @@ bool mshv_region_handle_gfn_fault(struct mshv_mem_region *region, u64 gfn)
ret = mshv_region_range_fault(region, page_offset, page_count);
- WARN_ONCE(ret,
+ WARN_ONCE(ret && ret != -ECANCELED,
"p%llu: GPA intercept failed: region %#llx-%#llx, gfn %#llx, page_offset %llu, page_count %llu\n",
region->partition->pt_id, region->start_uaddr,
- region->start_uaddr + (region->nr_pages << HV_HYP_PAGE_SHIFT),
+ region->start_uaddr +
+ (region->nr_pages << HV_HYP_PAGE_SHIFT),
gfn, page_offset, page_count);
return !ret;
@@ -524,6 +558,17 @@ static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
unsigned long mstart, mend;
int ret = -EPERM;
+ if (mmu_notifier_range_blockable(range))
+ down_read(®ion->mreg_remap_lock);
+ else if (!down_read_trylock(®ion->mreg_remap_lock))
+ return false;
+
+ /* A completed checked unmap makes notifier remapping unnecessary. */
+ if (READ_ONCE(region->remap_blocked)) {
+ up_read(®ion->mreg_remap_lock);
+ return true;
+ }
+
mstart = max(range->start, region->start_uaddr);
mend = min(range->end, region->start_uaddr +
(region->nr_pages << HV_HYP_PAGE_SHIFT));
@@ -546,12 +591,14 @@ static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
mshv_region_invalidate_pages(region, page_offset, page_count);
mutex_unlock(®ion->mreg_mutex);
+ up_read(®ion->mreg_remap_lock);
return true;
out_unlock:
mutex_unlock(®ion->mreg_mutex);
out_fail:
+ up_read(®ion->mreg_remap_lock);
WARN_ONCE(ret,
"Failed to invalidate region %#llx-%#llx (range %#lx-%#lx, event: %u, pages %#llx-%#llx, mm: %#llx): %d\n",
region->start_uaddr,
@@ -567,7 +614,11 @@ static const struct mmu_interval_notifier_ops mshv_region_mni_ops = {
void mshv_region_movable_fini(struct mshv_mem_region *region)
{
+ if (!region->mni_registered)
+ return;
+
mmu_interval_notifier_remove(®ion->mreg_mni);
+ region->mni_registered = false;
}
bool mshv_region_movable_init(struct mshv_mem_region *region)
@@ -581,7 +632,6 @@ bool mshv_region_movable_init(struct mshv_mem_region *region)
if (ret)
return false;
- mutex_init(®ion->mreg_mutex);
-
+ region->mni_registered = true;
return true;
}
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 1f086dcb7aa1..a1059b578a39 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -8,6 +8,7 @@
#include <linux/spinlock.h>
#include <linux/mutex.h>
+#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <linux/srcu.h>
@@ -88,10 +89,15 @@ struct mshv_mem_region {
u64 start_gfn;
u64 start_uaddr;
u32 hv_map_flags;
+ /* True until a checked hypervisor unmap has completed. */
+ bool mapping_may_exist;
struct mshv_partition *partition;
enum mshv_region_type mreg_type;
struct mmu_interval_notifier mreg_mni;
struct mutex mreg_mutex; /* protects region pages remapping */
+ struct rw_semaphore mreg_remap_lock;
+ bool remap_blocked;
+ bool mni_registered;
struct page *mreg_pages[];
};
@@ -139,6 +145,7 @@ struct mshv_partition {
struct mshv_girq_routing_table __rcu *pt_girq_tbl;
u64 isolation_type;
bool import_completed;
+ bool teardown_quarantined;
bool pt_initialized;
#if IS_ENABLED(CONFIG_DEBUG_FS)
struct dentry *pt_stats_dentry;
@@ -281,9 +288,11 @@ int hv_call_create_partition(u64 flags,
int hv_call_initialize_partition(u64 partition_id);
int hv_call_finalize_partition(u64 partition_id);
int hv_call_delete_partition(u64 partition_id);
-int hv_call_map_mmio_pages(u64 partition_id, u64 gfn, u64 mmio_spa, u64 numpgs);
+int hv_call_map_mmio_pages(u64 partition_id, u64 gfn, u64 mmio_spa,
+ u64 numpgs, bool *mapping_may_exist);
int hv_call_map_gpa_pages(u64 partition_id, u64 gpa_target, u64 page_count,
- u32 flags, struct page **pages);
+ u32 flags, struct page **pages,
+ bool *mapping_may_exist);
int hv_call_unmap_gpa_pages(u64 partition_id, u64 gpa_target, u64 page_count,
u32 flags);
int hv_call_delete_vp(u64 partition_id, u32 vp_index);
@@ -370,6 +379,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_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index cb55d4d4be2e..1fa644e0e437 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -190,14 +190,19 @@ int hv_call_delete_partition(u64 partition_id)
/* Ask the hypervisor to map guest ram pages or the guest mmio space */
static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
- u32 flags, struct page **pages, u64 mmio_spa)
+ u32 flags, struct page **pages, u64 mmio_spa,
+ bool *mapping_may_exist)
{
struct hv_input_map_gpa_pages *input_page;
u64 status, *pfnlist;
unsigned long irq_flags, large_shift = 0;
- int ret = 0, done = 0;
u64 page_count = page_struct_count;
+ u64 done = 0;
+ bool cleanup_needed = false;
+ int ret = 0;
+ if (mapping_may_exist)
+ *mapping_may_exist = false;
if (page_count == 0 || (pages && mmio_spa))
return -EINVAL;
@@ -213,8 +218,9 @@ static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
}
while (done < page_count) {
- ulong i, completed, remain = page_count - done;
- int rep_count = min(remain, HV_MAP_GPA_BATCH_SIZE);
+ u64 i, completed, remain = page_count - done;
+ unsigned int rep_count = min_t(u64, remain,
+ HV_MAP_GPA_BATCH_SIZE);
local_irq_save(irq_flags);
input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
@@ -224,49 +230,62 @@ static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
input_page->map_flags = flags;
pfnlist = input_page->source_gpa_page_list;
- for (i = 0; i < rep_count; i++)
+ for (i = 0; i < rep_count; i++) {
if (flags & HV_MAP_GPA_NO_ACCESS) {
pfnlist[i] = 0;
} else if (pages) {
u64 index = (done + i) << large_shift;
- if (index >= page_struct_count) {
- ret = -EINVAL;
- break;
- }
pfnlist[i] = page_to_pfn(pages[index]);
} else {
pfnlist[i] = mmio_spa + done + i;
}
- if (ret)
- break;
+ }
status = hv_do_rep_hypercall(HVCALL_MAP_GPA_PAGES, rep_count, 0,
input_page, NULL);
local_irq_restore(irq_flags);
completed = hv_repcomp(status);
+ if (completed > rep_count) {
+ cleanup_needed = true;
+ ret = -EIO;
+ break;
+ }
+ done += completed;
if (hv_result_needs_memory(status)) {
ret = hv_call_deposit_pages(NUMA_NO_NODE, partition_id,
HV_MAP_GPA_DEPOSIT_PAGES);
if (ret)
break;
-
- } else if (!hv_result_success(status)) {
+ continue;
+ }
+ if (!hv_result_success(status)) {
ret = hv_result_to_errno(status);
break;
}
-
- done += completed;
+ if (!completed) {
+ cleanup_needed = true;
+ ret = -EIO;
+ break;
+ }
}
- if (ret && done) {
+ if (ret && (done || cleanup_needed)) {
u32 unmap_flags = 0;
+ int unmap_ret;
if (flags & HV_MAP_GPA_LARGE_PAGE)
unmap_flags |= HV_UNMAP_GPA_LARGE_PAGE;
- hv_call_unmap_gpa_pages(partition_id, gfn, done, unmap_flags);
+ /* Unmap the full request so no unknown partial mapping survives. */
+ unmap_ret =
+ hv_call_unmap_gpa_pages(partition_id, gfn,
+ page_struct_count, unmap_flags);
+ if (unmap_ret && mapping_may_exist)
+ *mapping_may_exist = true;
+ } else if (!ret && mapping_may_exist) {
+ *mapping_may_exist = true;
}
return ret;
@@ -274,16 +293,18 @@ static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
/* Ask the hypervisor to map guest ram pages */
int hv_call_map_gpa_pages(u64 partition_id, u64 gpa_target, u64 page_count,
- u32 flags, struct page **pages)
+ u32 flags, struct page **pages,
+ bool *mapping_may_exist)
{
return hv_do_map_gpa_hcall(partition_id, gpa_target, page_count,
- flags, pages, 0);
+ flags, pages, 0, mapping_may_exist);
}
/* Ask the hypervisor to map guest mmio space */
-int hv_call_map_mmio_pages(u64 partition_id, u64 gfn, u64 mmio_spa, u64 numpgs)
+int hv_call_map_mmio_pages(u64 partition_id, u64 gfn, u64 mmio_spa,
+ u64 numpgs, bool *mapping_may_exist)
{
- int i;
+ u64 i;
u32 flags = HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE |
HV_MAP_GPA_NOT_CACHED;
@@ -292,7 +313,7 @@ int hv_call_map_mmio_pages(u64 partition_id, u64 gfn, u64 mmio_spa, u64 numpgs)
return -EINVAL;
return hv_do_map_gpa_hcall(partition_id, gfn, numpgs, flags, NULL,
- mmio_spa);
+ mmio_spa, mapping_may_exist);
}
int hv_call_unmap_gpa_pages(u64 partition_id, u64 gfn, u64 page_count_4k,
@@ -301,7 +322,8 @@ int hv_call_unmap_gpa_pages(u64 partition_id, u64 gfn, u64 page_count_4k,
struct hv_input_unmap_gpa_pages *input_page;
u64 status, page_count = page_count_4k;
unsigned long irq_flags, large_shift = 0;
- int ret = 0, done = 0;
+ u64 done = 0;
+ int ret = 0;
if (page_count == 0)
return -EINVAL;
@@ -315,8 +337,9 @@ int hv_call_unmap_gpa_pages(u64 partition_id, u64 gfn, u64 page_count_4k,
}
while (done < page_count) {
- ulong completed, remain = page_count - done;
- int rep_count = min(remain, HV_UMAP_GPA_PAGES);
+ u64 completed, remain = page_count - done;
+ unsigned int rep_count = min_t(u64, remain,
+ HV_UMAP_GPA_PAGES);
local_irq_save(irq_flags);
input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
@@ -329,10 +352,18 @@ int hv_call_unmap_gpa_pages(u64 partition_id, u64 gfn, u64 page_count_4k,
local_irq_restore(irq_flags);
completed = hv_repcomp(status);
+ if (completed > rep_count) {
+ ret = -EIO;
+ break;
+ }
if (!hv_result_success(status)) {
ret = hv_result_to_errno(status);
break;
}
+ if (!completed) {
+ ret = -EIO;
+ break;
+ }
done += completed;
}
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index cc2cfce2aefd..1ca14660f998 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -65,6 +65,7 @@ static int mshv_vp_mmap(struct file *file, struct vm_area_struct *vma);
static vm_fault_t mshv_vp_fault(struct vm_fault *vmf);
static int mshv_init_async_handler(struct mshv_partition *partition);
static void mshv_async_hvcall_handler(void *data, u64 *status);
+static void mshv_quarantine_partition(struct mshv_partition *partition);
static const union hv_input_vtl input_vtl_zero;
static const union hv_input_vtl input_vtl_normal = {
@@ -1397,13 +1398,15 @@ mshv_map_user_memory(struct mshv_partition *partition,
ret = hv_call_map_gpa_pages(partition->pt_id,
region->start_gfn,
region->nr_pages,
- HV_MAP_GPA_NO_ACCESS, NULL);
+ HV_MAP_GPA_NO_ACCESS, NULL,
+ ®ion->mapping_may_exist);
break;
case MSHV_REGION_TYPE_MMIO:
ret = hv_call_map_mmio_pages(partition->pt_id,
region->start_gfn,
mmio_pfn,
- region->nr_pages);
+ region->nr_pages,
+ ®ion->mapping_may_exist);
break;
}
@@ -1421,7 +1424,17 @@ mshv_map_user_memory(struct mshv_partition *partition,
return 0;
errout:
- mshv_region_put(region);
+ if (region->mapping_may_exist) {
+ spin_lock(&partition->pt_mem_regions_lock);
+ hlist_add_head(®ion->hnode, &partition->pt_mem_regions);
+ spin_unlock(&partition->pt_mem_regions_lock);
+ pt_err(partition,
+ "Retaining region after uncertain map cleanup at GFN %#llx\n",
+ region->start_gfn);
+ mshv_quarantine_partition(partition);
+ } else {
+ mshv_region_put(region);
+ }
return ret;
}
@@ -1431,33 +1444,61 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
struct mshv_user_mem_region *mem)
{
struct mshv_mem_region *region;
+ long ret;
if (!(mem->flags & BIT(MSHV_SET_MEM_BIT_UNMAP)))
return -EINVAL;
+ /*
+ * The list owns one reference. Take a temporary reference and detach
+ * under the lookup lock before waiting for existing remappers. This
+ * prevents a zero-ref region from being reinserted on unmap failure.
+ */
spin_lock(&partition->pt_mem_regions_lock);
-
region = mshv_partition_region_by_gfn(partition, mem->guest_pfn);
- if (!region) {
+ if (!region || !mshv_region_get(region)) {
spin_unlock(&partition->pt_mem_regions_lock);
return -ENOENT;
}
- /* Paranoia check */
if (region->start_uaddr != mem->userspace_addr ||
region->start_gfn != mem->guest_pfn ||
region->nr_pages != HVPFN_DOWN(mem->size)) {
spin_unlock(&partition->pt_mem_regions_lock);
+ mshv_region_put(region);
return -EINVAL;
}
- hlist_del(®ion->hnode);
-
+ WRITE_ONCE(region->remap_blocked, true);
+ hlist_del_init(®ion->hnode);
spin_unlock(&partition->pt_mem_regions_lock);
- mshv_region_put(region);
+ /* Wait for every fault/notifier remap that observed the region. */
+ down_write(®ion->mreg_remap_lock);
+ ret = mshv_region_unmap(region);
+ if (ret)
+ goto restore_region;
+
+ up_write(®ion->mreg_remap_lock);
+
+ if (region->mreg_type == MSHV_REGION_TYPE_MEM_MOVABLE)
+ mshv_region_movable_fini(region);
+ /* Drop the detached list ownership and the temporary reference. */
+ mshv_region_put(region);
+ mshv_region_put(region);
return 0;
+
+restore_region:
+ /* Restore lookup visibility before allowing remappers to proceed. */
+ spin_lock(&partition->pt_mem_regions_lock);
+ hlist_add_head(®ion->hnode, &partition->pt_mem_regions);
+ WRITE_ONCE(region->remap_blocked, false);
+ spin_unlock(&partition->pt_mem_regions_lock);
+ up_write(®ion->mreg_remap_lock);
+ mshv_region_put(region);
+ return ret;
+
}
static long
@@ -1658,6 +1699,11 @@ mshv_partition_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
if (mutex_lock_killable(&partition->pt_mutex))
return -EINTR;
+ if (partition->teardown_quarantined) {
+ ret = -EIO;
+ goto out;
+ }
+
switch (ioctl) {
case MSHV_INITIALIZE_PARTITION:
ret = mshv_partition_ioctl_initialize(partition);
@@ -1688,6 +1734,7 @@ mshv_partition_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
ret = -ENOTTY;
}
+out:
mutex_unlock(&partition->pt_mutex);
return ret;
}
@@ -1795,6 +1842,16 @@ remove_partition(struct mshv_partition *partition)
synchronize_rcu();
}
+static void mshv_quarantine_partition(struct mshv_partition *partition)
+{
+ if (partition->teardown_quarantined)
+ return;
+
+ /* Retain both the partition and the cleanup code indefinitely. */
+ __module_get(THIS_MODULE);
+ partition->teardown_quarantined = true;
+}
+
/*
* Tear down a partition and remove it from the list.
* Partition's refcount must be 0
@@ -1814,6 +1871,36 @@ static void destroy_partition(struct mshv_partition *partition)
trace_mshv_destroy_partition(partition->pt_id);
+ hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) {
+ bool was_blocked;
+ int unmap_ret;
+
+ /* Stop new lookup users before draining in-flight remappers. */
+ spin_lock(&partition->pt_mem_regions_lock);
+ was_blocked = READ_ONCE(region->remap_blocked);
+ WRITE_ONCE(region->remap_blocked, true);
+ spin_unlock(&partition->pt_mem_regions_lock);
+
+ down_write(®ion->mreg_remap_lock);
+ unmap_ret = mshv_region_unmap(region);
+ if (unmap_ret) {
+ spin_lock(&partition->pt_mem_regions_lock);
+ WRITE_ONCE(region->remap_blocked, was_blocked);
+ spin_unlock(&partition->pt_mem_regions_lock);
+ up_write(®ion->mreg_remap_lock);
+
+ pt_err(partition,
+ "Failed to unmap region at GFN %#llx: %d; retaining partition\n",
+ region->start_gfn, unmap_ret);
+ mshv_quarantine_partition(partition);
+ return;
+ }
+ up_write(®ion->mreg_remap_lock);
+
+ if (region->mreg_type == MSHV_REGION_TYPE_MEM_MOVABLE)
+ mshv_region_movable_fini(region);
+ }
+
if (partition->pt_initialized) {
/*
* We only need to drain signals for root scheduler. This should be
@@ -1874,7 +1961,7 @@ static void destroy_partition(struct mshv_partition *partition)
hlist_for_each_entry_safe(region, n, &partition->pt_mem_regions,
hnode) {
- hlist_del(®ion->hnode);
+ hlist_del_init(®ion->hnode);
mshv_region_put(region);
}
@@ -1883,6 +1970,8 @@ static void destroy_partition(struct mshv_partition *partition)
hv_call_delete_partition(partition->pt_id);
mshv_free_routing_table(partition);
+ if (partition->teardown_quarantined)
+ module_put(THIS_MODULE);
kfree(partition);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v4 2/9] mshv: clear SynIC mappings before freeing them
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-31 11:26 ` [PATCH v4 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 11:26 ` [PATCH v4 3/9] mshv: add SEV-SNP UAPI definitions Wei Hu
` (6 subsequent siblings)
8 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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>
Publish and withdraw per-CPU SynIC mapping pointers explicitly so
interrupt readers cannot retain stale addresses across CPU hotplug
teardown or initialization failure. Address the CPUHP callback CPU
directly and guard all cleanup paths against missing pages.
Signed-off-by: Wei Hu <weh@microsoft.com>
---
drivers/hv/mshv_synic.c | 136 ++++++++++++++++++++--------------------
1 file changed, 69 insertions(+), 67 deletions(-)
diff --git a/drivers/hv/mshv_synic.c b/drivers/hv/mshv_synic.c
index 7c168e5a740d..c77688b8d23c 100644
--- a/drivers/hv/mshv_synic.c
+++ b/drivers/hv/mshv_synic.c
@@ -28,33 +28,31 @@ static int mshv_sint_irq = -1; /* Linux IRQ for mshv_sint_vector */
static u32 synic_event_ring_get_queued_port(u32 sint_index)
{
- struct hv_synic_event_ring_page **event_ring_page;
+ struct hv_synic_event_ring_page *event_ring_page;
volatile struct hv_synic_event_ring *ring;
struct hv_synic_pages *spages;
- u8 **synic_eventring_tail;
+ u8 *eventring_tail;
u32 message;
u8 tail;
spages = this_cpu_ptr(synic_pages);
- event_ring_page = &spages->synic_event_ring_page;
- synic_eventring_tail = (u8 **)this_cpu_ptr(hv_synic_eventring_tail);
+ event_ring_page = READ_ONCE(spages->synic_event_ring_page);
+ eventring_tail = READ_ONCE(*this_cpu_ptr(hv_synic_eventring_tail));
- if (unlikely(!*synic_eventring_tail)) {
+ if (unlikely(!eventring_tail)) {
pr_debug("Missing synic event ring tail!\n");
return 0;
}
- tail = (*synic_eventring_tail)[sint_index];
+ tail = eventring_tail[sint_index];
- if (unlikely(!*event_ring_page)) {
+ if (unlikely(!event_ring_page)) {
pr_debug("Missing synic event ring page!\n");
return 0;
}
- ring = &(*event_ring_page)->sint_event_ring[sint_index];
+ ring = &event_ring_page->sint_event_ring[sint_index];
- /*
- * Get the message.
- */
+ /* Get the message. */
message = ring->data[tail];
if (!message) {
@@ -78,9 +76,6 @@ static u32 synic_event_ring_get_queued_port(u32 sint_index)
mb();
message = ring->data[tail];
- /*
- * Ok, lets bail out.
- */
if (!message)
return 0;
}
@@ -88,15 +83,13 @@ static u32 synic_event_ring_get_queued_port(u32 sint_index)
ring->signal_masked = 1;
}
- /*
- * Clear the message in the ring buffer.
- */
+ /* Clear the message in the ring buffer. */
ring->data[tail] = 0;
if (++tail == HV_SYNIC_EVENT_RING_MESSAGE_COUNT)
tail = 0;
- (*synic_eventring_tail)[sint_index] = tail;
+ eventring_tail[sint_index] = tail;
return message;
}
@@ -408,16 +401,17 @@ mshv_intercept_isr(struct hv_message *msg)
void mshv_isr(void)
{
struct hv_synic_pages *spages = this_cpu_ptr(synic_pages);
- struct hv_message_page **msg_page = &spages->hyp_synic_message_page;
+ struct hv_message_page *msg_page;
struct hv_message *msg;
bool handled;
- if (unlikely(!(*msg_page))) {
+ msg_page = READ_ONCE(spages->hyp_synic_message_page);
+ if (unlikely(!msg_page)) {
pr_debug("Missing synic page!\n");
return;
}
- msg = &((*msg_page)->sint_message[HV_SYNIC_INTERCEPTION_SINT_INDEX]);
+ msg = &msg_page->sint_message[HV_SYNIC_INTERCEPTION_SINT_INDEX];
/*
* If the type isn't set, there isn't really a message;
@@ -462,12 +456,10 @@ static int mshv_synic_cpu_init(unsigned int cpu)
union hv_synic_siefp siefp;
union hv_synic_sirbp sirbp;
union hv_synic_sint sint;
- struct hv_synic_pages *spages = this_cpu_ptr(synic_pages);
- struct hv_message_page **msg_page = &spages->hyp_synic_message_page;
- struct hv_synic_event_flags_page **event_flags_page =
- &spages->synic_event_flags_page;
- struct hv_synic_event_ring_page **event_ring_page =
- &spages->synic_event_ring_page;
+ struct hv_synic_pages *spages = per_cpu_ptr(synic_pages, cpu);
+ struct hv_message_page *msg_page;
+ struct hv_synic_event_flags_page *event_flags_page;
+ struct hv_synic_event_ring_page *event_ring_page;
/*
* VMBus owns SIMP/SIEFP/SCONTROL when it is active.
* See hv_hyp_synic_enable_regs() for that initialization.
@@ -484,11 +476,11 @@ static int mshv_synic_cpu_init(unsigned int cpu)
simp.simp_enabled = true;
hv_set_non_nested_msr(HV_MSR_SIMP, simp.as_uint64);
}
- *msg_page = memremap(simp.base_simp_gpa << HV_HYP_PAGE_SHIFT,
- HV_HYP_PAGE_SIZE,
- MEMREMAP_WB);
+ msg_page = memremap(simp.base_simp_gpa << HV_HYP_PAGE_SHIFT,
+ HV_HYP_PAGE_SIZE, MEMREMAP_WB);
+ WRITE_ONCE(spages->hyp_synic_message_page, msg_page);
- if (!(*msg_page))
+ if (!msg_page)
goto cleanup_simp;
/*
@@ -500,35 +492,37 @@ static int mshv_synic_cpu_init(unsigned int cpu)
siefp.siefp_enabled = true;
hv_set_non_nested_msr(HV_MSR_SIEFP, siefp.as_uint64);
}
- *event_flags_page = memremap(siefp.base_siefp_gpa << HV_HYP_PAGE_SHIFT,
- HV_HYP_PAGE_SIZE, MEMREMAP_WB);
+ event_flags_page = memremap(siefp.base_siefp_gpa << HV_HYP_PAGE_SHIFT,
+ HV_HYP_PAGE_SIZE, MEMREMAP_WB);
+ WRITE_ONCE(spages->synic_event_flags_page, event_flags_page);
- if (!(*event_flags_page))
+ if (!event_flags_page)
goto cleanup_siefp;
/* Setup the Synic's event ring page */
sirbp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIRBP);
if (hv_root_partition()) {
- *event_ring_page = memremap(sirbp.base_sirbp_gpa << HV_HYP_PAGE_SHIFT,
- HV_HYP_PAGE_SIZE, MEMREMAP_WB);
+ event_ring_page = memremap(sirbp.base_sirbp_gpa << HV_HYP_PAGE_SHIFT,
+ HV_HYP_PAGE_SIZE, MEMREMAP_WB);
- if (!(*event_ring_page))
+ if (!event_ring_page)
goto cleanup_siefp;
} else {
/*
* On L1VH the hypervisor does not provide a SIRBP page.
* Allocate one and program its GPA into the MSR.
*/
- *event_ring_page = (struct hv_synic_event_ring_page *)
+ event_ring_page = (struct hv_synic_event_ring_page *)
get_zeroed_page(GFP_KERNEL);
- if (!(*event_ring_page))
+ if (!event_ring_page)
goto cleanup_siefp;
- sirbp.base_sirbp_gpa = virt_to_phys(*event_ring_page)
+ sirbp.base_sirbp_gpa = virt_to_phys(event_ring_page)
>> HV_HYP_PAGE_SHIFT;
}
+ WRITE_ONCE(spages->synic_event_ring_page, event_ring_page);
sirbp.sirbp_enabled = true;
hv_set_non_nested_msr(HV_MSR_SIRBP, sirbp.as_uint64);
@@ -565,19 +559,22 @@ static int mshv_synic_cpu_init(unsigned int cpu)
return 0;
cleanup_siefp:
- if (*event_flags_page)
- memunmap(*event_flags_page);
if (!vmbus_active) {
siefp.siefp_enabled = false;
hv_set_non_nested_msr(HV_MSR_SIEFP, siefp.as_uint64);
}
+ WRITE_ONCE(spages->synic_event_ring_page, NULL);
+ WRITE_ONCE(spages->synic_event_flags_page, NULL);
+ if (event_flags_page)
+ memunmap(event_flags_page);
cleanup_simp:
- if (*msg_page)
- memunmap(*msg_page);
if (!vmbus_active) {
simp.simp_enabled = false;
hv_set_non_nested_msr(HV_MSR_SIMP, simp.as_uint64);
}
+ WRITE_ONCE(spages->hyp_synic_message_page, NULL);
+ if (msg_page)
+ memunmap(msg_page);
return -EFAULT;
}
@@ -586,15 +583,17 @@ static int mshv_synic_cpu_exit(unsigned int cpu)
{
union hv_synic_sint sint;
union hv_synic_sirbp sirbp;
- struct hv_synic_pages *spages = this_cpu_ptr(synic_pages);
- struct hv_message_page **msg_page = &spages->hyp_synic_message_page;
- struct hv_synic_event_flags_page **event_flags_page =
- &spages->synic_event_flags_page;
- struct hv_synic_event_ring_page **event_ring_page =
- &spages->synic_event_ring_page;
+ struct hv_synic_pages *spages = per_cpu_ptr(synic_pages, cpu);
+ struct hv_message_page *msg_page;
+ struct hv_synic_event_flags_page *event_flags_page;
+ struct hv_synic_event_ring_page *event_ring_page;
/* VMBus owns SIMP/SIEFP/SCONTROL when it is active */
bool vmbus_active = hv_vmbus_exists();
+ msg_page = READ_ONCE(spages->hyp_synic_message_page);
+ event_flags_page = READ_ONCE(spages->synic_event_flags_page);
+ event_ring_page = READ_ONCE(spages->synic_event_ring_page);
+
/* Disable the interrupt */
sint.as_uint64 = hv_get_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX);
sint.masked = true;
@@ -614,24 +613,18 @@ static int mshv_synic_cpu_exit(unsigned int cpu)
sirbp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIRBP);
sirbp.sirbp_enabled = false;
- if (hv_root_partition()) {
- hv_set_non_nested_msr(HV_MSR_SIRBP, sirbp.as_uint64);
- memunmap(*event_ring_page);
- } else {
+ if (!hv_root_partition())
sirbp.base_sirbp_gpa = 0;
- hv_set_non_nested_msr(HV_MSR_SIRBP, sirbp.as_uint64);
- free_page((unsigned long)*event_ring_page);
- }
+ hv_set_non_nested_msr(HV_MSR_SIRBP, sirbp.as_uint64);
/*
- * Release our mappings of the message and event flags pages.
- * When VMBus is not active, we enabled SIMP/SIEFP — disable
- * them. Otherwise VMBus owns the MSRs — leave them.
+ * When VMBus is not active, disable registers before withdrawing the
+ * pointers visible to interrupt readers.
*/
- memunmap(*event_flags_page);
if (!vmbus_active) {
union hv_synic_simp simp;
union hv_synic_siefp siefp;
+ union hv_synic_scontrol sctrl;
siefp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIEFP);
siefp.siefp_enabled = false;
@@ -640,18 +633,27 @@ static int mshv_synic_cpu_exit(unsigned int cpu)
simp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIMP);
simp.simp_enabled = false;
hv_set_non_nested_msr(HV_MSR_SIMP, simp.as_uint64);
- }
- memunmap(*msg_page);
-
- /* When VMBus is active it owns SCONTROL — leave it. */
- if (!vmbus_active) {
- union hv_synic_scontrol sctrl;
sctrl.as_uint64 = hv_get_non_nested_msr(HV_MSR_SCONTROL);
sctrl.enable = 0;
hv_set_non_nested_msr(HV_MSR_SCONTROL, sctrl.as_uint64);
}
+ WRITE_ONCE(spages->synic_event_ring_page, NULL);
+ WRITE_ONCE(spages->synic_event_flags_page, NULL);
+ WRITE_ONCE(spages->hyp_synic_message_page, NULL);
+
+ if (event_ring_page) {
+ if (hv_root_partition())
+ memunmap(event_ring_page);
+ else
+ free_page((unsigned long)event_ring_page);
+ }
+ if (event_flags_page)
+ memunmap(event_flags_page);
+ if (msg_page)
+ memunmap(msg_page);
+
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v4 3/9] mshv: add SEV-SNP UAPI definitions
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-31 11:26 ` [PATCH v4 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
2026-08-31 11:26 ` [PATCH v4 2/9] mshv: clear SynIC mappings before freeing them Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 11:26 ` [PATCH v4 4/9] mshv: add SEV-SNP PSP request hypercall Wei Hu
` (5 subsequent siblings)
8 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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 Microsoft Hypervisor ABI definitions required to
create and manage SEV-SNP child partitions.
Use fixed-size ioctl headers with aligned userspace pointers for variable
page arrays and the launch-finish payload. The host-access interface
names its input as guest GPAs, while isolated import takes guest PFNs and
an explicit page-size value. Keep the public launch structures naturally
aligned and represent the SNP policy as a raw 64-bit ABI value rather
than compiler-dependent bitfields.
Until the implementation is added later in the series, continue to reject
every non-NONE isolation type rather than silently creating an unisolated
partition.
Both array ioctls return the exact number of input entries completed. The
field is MBZ on input; userspace may resume at array + completed with
count - completed. Completed operations are not rolled back when a later
entry fails.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 4 +-
include/hyperv/hvgdk_mini.h | 19 +++++++
include/hyperv/hvhdk.h | 45 +++++++++++++++
include/hyperv/hvhdk_mini.h | 53 ++++++++++++++++++
include/uapi/linux/mshv.h | 108 ++++++++++++++++++++++++++++++++++++
5 files changed, 227 insertions(+), 2 deletions(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 1ca14660f998..4a15d4bfa925 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2051,7 +2051,7 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
return -EFAULT;
if ((args.pt_flags & ~MSHV_PT_FLAGS_MASK) ||
- args.pt_isolation >= MSHV_PT_ISOLATION_COUNT)
+ args.pt_isolation != MSHV_PT_ISOLATION_NONE)
return -EINVAL;
disabled_procs = &cr_props->disabled_processor_features;
@@ -2064,7 +2064,7 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
/* Re-validate v1 fields after second copy_from_user() */
if ((args.pt_flags & ~MSHV_PT_FLAGS_MASK) ||
- args.pt_isolation >= MSHV_PT_ISOLATION_COUNT)
+ args.pt_isolation != MSHV_PT_ISOLATION_NONE)
return -EINVAL;
if (args.pt_num_cpu_fbanks != MSHV_NUM_CPU_FEATURES_BANKS ||
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 035ba20870f7..dd84085c0aff 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..9c665f47ce71 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,108 @@ 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;
+ /*
+ * MBZ on input; number of guest_gpas entries completed on output.
+ * Completed entries are not rolled back; resume at guest_gpas +
+ * completed with page_count - completed entries.
+ */
+ __u64 completed;
+ __aligned_u64 guest_gpas;
+};
+
+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
+};
+
+enum {
+ MSHV_ISOLATED_PAGE_SIZE_4KB,
+ MSHV_ISOLATED_PAGE_SIZE_2MB,
+ MSHV_ISOLATED_PAGE_SIZE_COUNT
+};
+
+struct mshv_import_isolated_pages {
+ __u8 page_type;
+ __u8 page_size;
+ __u8 rsvd[6];
+ __u64 page_count;
+ /*
+ * MBZ on input; number of guest_pfns entries completed on output.
+ * Completed entries are not rolled back; resume at guest_pfns +
+ * completed with page_count - completed entries.
+ */
+ __u64 completed;
+ __aligned_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;
+};
+
+struct mshv_snp_id_block {
+ __u8 launch_digest[48];
+ __u8 family_id[16];
+ __u8 image_id[16];
+ __u32 version;
+ __u32 guest_svn;
+ __u64 policy;
+};
+
+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];
+};
+
+/*
+ * This structure describes the PSP launch-finish wire data. The two trailing
+ * reserved bytes make the userspace object naturally aligned; they must be 0
+ * and are not copied into the packed Hyper-V ABI structure.
+ */
+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;
+ __u8 reserved[2];
+};
+
+struct mshv_complete_isolated_import {
+ __aligned_u64 psp_parameters;
+};
+
/**
* struct mshv_root_hvcall - arguments for MSHV_ROOT_HVCALL
* @code: Hypercall code (HVCALL_*)
@@ -254,6 +357,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 _IOWR(MSHV_IOCTL, 0x09, struct mshv_modify_gpa_host_access)
+#define MSHV_IMPORT_ISOLATED_PAGES _IOWR(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] 27+ messages in thread
* [PATCH v4 4/9] mshv: add SEV-SNP PSP request hypercall
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (2 preceding siblings ...)
2026-08-31 11:26 ` [PATCH v4 3/9] mshv: add SEV-SNP UAPI definitions Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 11:26 ` [PATCH v4 5/9] mshv: add SEV-SNP isolated page hypercalls Wei Hu
` (4 subsequent siblings)
8 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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 | 41 ++++++++++++++++++++++++++++++++++
2 files changed, 49 insertions(+)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index a1059b578a39..2150019b588c 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -388,4 +388,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 1fa644e0e437..fca2419df397 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -1040,6 +1040,47 @@ 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);
+
+ /* Hyper-V consumes the input before returning CALL_PENDING. */
+ if (hv_result(status) == HV_STATUS_CALL_PENDING)
+ completion_handler(completion_data, &status);
+
+ if (!hv_result_success(status)) {
+ pr_err_ratelimited("%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] 27+ messages in thread
* [PATCH v4 5/9] mshv: add SEV-SNP isolated page hypercalls
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (3 preceding siblings ...)
2026-08-31 11:26 ` [PATCH v4 4/9] mshv: add SEV-SNP PSP request hypercall Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 11:53 ` sashiko-bot
2026-08-31 11:26 ` [PATCH v4 6/9] mshv: wire SEV-SNP partition ioctls Wei Hu
` (3 subsequent siblings)
8 siblings, 1 reply; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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 wrappers for importing isolated pages, completing the import,
modifying sparse SPA host access, and setting partition properties.
Support synchronous and pending completion and batch repetition calls
within the Hyper-V input page.
Preserve the asynchronous completion result and encode its bounded
repetition count in the standard hypercall status field. Keep the
distinct, currently unused substatus out of the result bits. Return exact
bounded import progress on both success and failure, reject malformed
completion counts, and rate-limit guest-driven failures.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root.h | 12 ++++
drivers/hv/mshv_root_hv_call.c | 110 +++++++++++++++++++++++++++++++++
drivers/hv/mshv_synic.c | 10 ++-
3 files changed, 131 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 2150019b588c..a87d9773093c 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -389,6 +389,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, u64 *completed_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 fca2419df397..cb5727529355 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
@@ -1041,6 +1045,112 @@ 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, u64 *completed_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;
+ u64 remaining = num_pages;
+ unsigned long flags;
+ u64 *gpa = pages;
+ u64 completed;
+ u64 status;
+ int rep_count;
+
+ if (!num_pages || !completed_pages)
+ return -EINVAL;
+ *completed_pages = 0;
+
+ if (!completion_handler) {
+ pr_err("%s: missing completion handler, page_type=%u\n",
+ __func__, page_type);
+ return -EINVAL;
+ }
+
+ while (remaining) {
+ rep_count = min_t(u64, 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);
+
+ if (hv_result(status) == HV_STATUS_CALL_PENDING)
+ completion_handler(completion_data, &status);
+
+ completed = hv_repcomp(status);
+ if (completed > rep_count) {
+ pr_err_ratelimited("%s: invalid completion count %llu/%d\n",
+ __func__, completed, rep_count);
+ return -EPROTO;
+ }
+ *completed_pages += completed;
+
+ if (!hv_result_success(status)) {
+ pr_err_ratelimited("%s: completed %llu of %llu, %s\n",
+ __func__, *completed_pages, num_pages,
+ hv_result_to_string(status));
+ return hv_result_to_errno(status);
+ }
+ if (!completed)
+ return -EPROTO;
+
+ gpa += completed;
+ remaining -= completed;
+ cond_resched();
+ }
+
+ 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_ratelimited("%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,
diff --git a/drivers/hv/mshv_synic.c b/drivers/hv/mshv_synic.c
index c77688b8d23c..0fdbae1e053c 100644
--- a/drivers/hv/mshv_synic.c
+++ b/drivers/hv/mshv_synic.c
@@ -159,7 +159,15 @@ static bool mshv_async_call_completion_isr(struct hv_message *msg)
goto unlock_out;
}
- partition->async_hypercall_status = async_msg->status;
+ /* No current completion caller consumes the distinct substatus. */
+ (void)async_msg->sub_status;
+ partition->async_hypercall_status =
+ (u64)async_msg->status & ~HV_HYPERCALL_REP_COMP_MASK;
+ partition->async_hypercall_status |=
+ (min_t(u64, async_msg->completion_count,
+ HV_HYPERCALL_REP_COMP_MASK >>
+ HV_HYPERCALL_REP_COMP_OFFSET) <<
+ HV_HYPERCALL_REP_COMP_OFFSET);
complete(&partition->async_hypercall);
handled = true;
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v4 6/9] mshv: wire SEV-SNP partition ioctls
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (4 preceding siblings ...)
2026-08-31 11:26 ` [PATCH v4 5/9] mshv: add SEV-SNP isolated page hypercalls Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 12:07 ` sashiko-bot
2026-08-31 11:26 ` [PATCH v4 7/9] mshv: detect and report SEV-SNP support at init Wei Hu
` (2 subsequent siblings)
8 siblings, 1 reply; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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 UAPI into partition creation, memory ownership,
isolated-page import, launch completion, PSP guest requests, AP creation,
and teardown.
Publish exact array-entry progress on success and partial failure so
userspace can resume at array + completed. Completed operations are not
rolled back. Validate MBZ fields, initialize completed before every
common copyout, and validate repetition progress, large-page conversion,
PSP GPA alignment, and VMSA GPA alignment. Quarantine uncertain ownership
or unreportable progress.
Track child mappings and released host access per region. On setup
failure, remove any partial child mapping before reacquiring host access.
For initialized and uninitialized SNP partitions alike, unmap every child
mapping, destroy initialized isolation state, restore host access, and
only then unpin. Retain the partition and module on any safety-critical
failure.
Hyper-V transfers root-to-child SPA/SLAT ownership directly; this
intentionally does not alter the root direct map, memory encryption
attributes, or GPA shared-bit encoding.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_regions.c | 34 +-
drivers/hv/mshv_root.h | 16 +-
drivers/hv/mshv_root_hv_call.c | 71 +++-
drivers/hv/mshv_root_main.c | 715 +++++++++++++++++++++++++++++++--
include/hyperv/hvgdk_mini.h | 12 +
5 files changed, 782 insertions(+), 66 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 9feb49d9d9d4..01972d77881b 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -199,12 +199,14 @@ static int mshv_region_chunk_share(struct mshv_mem_region *region,
u64 page_offset, u64 page_count,
bool huge_page)
{
+ u64 completed;
+
if (huge_page)
flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
return hv_call_modify_spa_host_access(region->partition->pt_id,
region->mreg_pages + page_offset,
- page_count,
+ page_count, &completed,
HV_MAP_GPA_READABLE |
HV_MAP_GPA_WRITABLE,
flags, true);
@@ -213,10 +215,14 @@ static int mshv_region_chunk_share(struct mshv_mem_region *region,
int mshv_region_share(struct mshv_mem_region *region)
{
u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED;
+ int ret;
- return mshv_region_process_range(region, flags,
- 0, region->nr_pages,
- mshv_region_chunk_share);
+ ret = mshv_region_process_range(region, flags, 0, region->nr_pages,
+ mshv_region_chunk_share);
+ if (!ret)
+ region->host_access_released = false;
+
+ return ret;
}
static int mshv_region_chunk_unshare(struct mshv_mem_region *region,
@@ -224,21 +230,26 @@ static int mshv_region_chunk_unshare(struct mshv_mem_region *region,
u64 page_offset, u64 page_count,
bool huge_page)
{
+ u64 completed;
+ int ret;
+
if (huge_page)
flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
- return hv_call_modify_spa_host_access(region->partition->pt_id,
- region->mreg_pages + page_offset,
- page_count, 0,
- flags, false);
+ /* A failed release may have made a prefix inaccessible to the host. */
+ region->host_access_released = true;
+ ret = hv_call_modify_spa_host_access(region->partition->pt_id,
+ region->mreg_pages + page_offset,
+ page_count, &completed, 0,
+ flags, false);
+ return ret;
}
int mshv_region_unshare(struct mshv_mem_region *region)
{
u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE;
- return mshv_region_process_range(region, flags,
- 0, region->nr_pages,
+ return mshv_region_process_range(region, flags, 0, region->nr_pages,
mshv_region_chunk_unshare);
}
@@ -373,7 +384,8 @@ static void mshv_region_destroy(struct kref *ref)
container_of(ref, struct mshv_mem_region, mreg_refcount);
/* Callers must retain the list reference until cleanup is certain. */
- if (WARN_ON_ONCE(region->mapping_may_exist))
+ if (WARN_ON_ONCE(region->mapping_may_exist ||
+ region->host_access_released))
return;
if (region->mreg_type == MSHV_REGION_TYPE_MEM_MOVABLE)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index a87d9773093c..89753dcbcc07 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -91,6 +91,7 @@ struct mshv_mem_region {
u32 hv_map_flags;
/* True until a checked hypervisor unmap has completed. */
bool mapping_may_exist;
+ bool host_access_released;
struct mshv_partition *partition;
enum mshv_region_type mreg_type;
struct mmu_interval_notifier mreg_mni;
@@ -164,6 +165,12 @@ do { \
#define pt_crit(p, fmt, ...) pt_devprintk(crit, p, fmt, ##__VA_ARGS__)
#define pt_alert(p, fmt, ...) pt_devprintk(alert, p, fmt, ##__VA_ARGS__)
#define pt_err(p, fmt, ...) pt_devprintk(err, p, fmt, ##__VA_ARGS__)
+#define pt_err_ratelimited(p, fmt, ...) \
+do { \
+ const struct mshv_partition *__pt = (p); \
+ dev_err_ratelimited(__pt->pt_module_dev, pt_fmt(fmt), __pt->pt_id, \
+ ##__VA_ARGS__); \
+} while (0)
#define pt_warn(p, fmt, ...) pt_devprintk(warn, p, fmt, ##__VA_ARGS__)
#define pt_notice(p, fmt, ...) pt_devprintk(notice, p, fmt, ##__VA_ARGS__)
#define pt_info(p, fmt, ...) pt_devprintk(info, p, fmt, ##__VA_ARGS__)
@@ -338,8 +345,8 @@ int hv_unmap_stats_page(enum hv_stats_object_type type,
struct hv_stats_page *page_addr,
const union hv_stats_object_identity *identity);
int hv_call_modify_spa_host_access(u64 partition_id, struct page **pages,
- u64 page_struct_count, u32 host_access,
- u32 flags, u8 acquire);
+ u64 page_struct_count, u64 *completed_pages,
+ u32 host_access, u32 flags, u8 acquire);
int hv_call_get_partition_property_ex(u64 partition_id, u64 property_code, u64 arg,
void *property_value, size_t property_value_sz);
@@ -388,6 +395,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, u64 *completed_pages,
diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index cb5727529355..48ac6fb808f4 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -1046,6 +1046,42 @@ 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(hv_result(status) == HV_STATUS_CALL_PENDING))
+ completion_handler(completion_data, &status);
+
+ if (!hv_result_success(status)) {
+ pr_err_ratelimited("%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, u64 *completed_pages,
enum hv_isolated_page_type page_type,
@@ -1192,19 +1228,20 @@ int hv_call_issue_psp_guest_request(u64 partition_id, u64 req_pfn,
#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)
+ u64 page_struct_count, u64 *completed_pages,
+ u32 host_access, u32 flags, u8 acquire)
{
struct hv_input_modify_sparse_spa_page_host_access *input_page;
u64 status;
- int done = 0;
+ u64 done = 0;
unsigned long irq_flags, large_shift = 0;
u64 page_count = page_struct_count;
u16 code = acquire ? HVCALL_ACQUIRE_SPARSE_SPA_PAGE_HOST_ACCESS :
HVCALL_RELEASE_SPARSE_SPA_PAGE_HOST_ACCESS;
- if (page_count == 0)
+ if (page_count == 0 || !completed_pages)
return -EINVAL;
+ *completed_pages = 0;
if (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE) {
if (!HV_PAGE_COUNT_2M_ALIGNED(page_count))
@@ -1214,8 +1251,8 @@ int hv_call_modify_spa_host_access(u64 partition_id, struct page **pages,
}
while (done < page_count) {
- ulong i, completed, remain = page_count - done;
- int rep_count = min(remain,
+ u64 i, completed, remain = page_count - done;
+ unsigned int rep_count = min_t(u64, remain,
HV_MODIFY_SPARSE_SPA_PAGE_HOST_ACCESS_MAX_PAGE_COUNT);
local_irq_save(irq_flags);
@@ -1233,8 +1270,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]);
@@ -1245,11 +1284,23 @@ int hv_call_modify_spa_host_access(u64 partition_id, struct page **pages,
local_irq_restore(irq_flags);
completed = hv_repcomp(status);
-
- if (!hv_result_success(status))
- return hv_result_to_errno(status);
+ if (completed > rep_count) {
+ pr_err_ratelimited("%s: invalid completion count %llu/%u\n",
+ __func__, completed, rep_count);
+ return -EPROTO;
+ }
done += completed;
+ *completed_pages += (u64)completed << large_shift;
+ if (!hv_result_success(status)) {
+ pr_err_ratelimited("%s: completed %llu of %llu pages: %s\n",
+ __func__, *completed_pages,
+ page_struct_count,
+ hv_result_to_string(status));
+ return hv_result_to_errno(status);
+ }
+ if (!completed)
+ return -EPROTO;
}
return 0;
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 4a15d4bfa925..424b7da680c1 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -642,6 +642,45 @@ mshv_partition_region_by_gfn_get(struct mshv_partition *p, u64 gfn)
return region;
}
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+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_ratelimited(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;
+}
+#endif
+
/**
* mshv_handle_gpa_intercept - Handle GPA (Guest Physical Address) intercepts.
* @vp: Pointer to the virtual processor structure.
@@ -1214,12 +1253,7 @@ mshv_partition_ioctl_create_vp(struct mshv_partition *partition,
static int mshv_init_async_handler(struct mshv_partition *partition)
{
- if (completion_done(&partition->async_hypercall)) {
- pt_err(partition,
- "Cannot issue async hypercall while another one in progress!\n");
- return -EPERM;
- }
-
+ /* Partition ioctls are serialized by pt_mutex. */
reinit_completion(&partition->async_hypercall);
return 0;
}
@@ -1297,51 +1331,51 @@ static int mshv_prepare_pinned_region(struct mshv_mem_region *region)
ret = mshv_region_pin(region);
if (ret) {
- pt_err(partition, "Failed to pin memory region: %d\n",
- ret);
- goto err_out;
+ pt_err(partition, "Failed to pin memory region: %d\n", ret);
+ return ret;
}
- /*
- * For an SNP partition it is a requirement that for every memory region
- * that we are going to map for this partition we should make sure that
- * host access to that region is released. This is ensured by doing an
- * additional hypercall which will update the SLAT to release host
- * access to guest memory regions.
- */
if (mshv_partition_encrypted(partition)) {
+ int recovery_ret;
+
ret = mshv_region_unshare(region);
if (ret) {
- pt_err(partition,
- "Failed to unshare memory region (guest_pfn: %llu): %d\n",
- region->start_gfn, ret);
+ pt_err_ratelimited(partition,
+ "Failed to release host access at GFN %#llx: %d\n",
+ region->start_gfn, ret);
+ recovery_ret = mshv_region_share(region);
+ if (recovery_ret) {
+ pt_err(partition,
+ "Failed to recover host access at GFN %#llx: %d; retaining region\n",
+ region->start_gfn, recovery_ret);
+ return ret;
+ }
goto invalidate_region;
}
}
ret = mshv_region_map(region);
- if (ret && mshv_partition_encrypted(partition)) {
- int shrc;
+ if (!ret)
+ return 0;
- shrc = mshv_region_share(region);
- if (!shrc)
- goto invalidate_region;
+ /* A partial map must be removed before host access is reacquired. */
+ if (region->mapping_may_exist)
+ return ret;
- pt_err(partition,
- "Failed to share memory region (guest_pfn: %llu): %d\n",
- region->start_gfn, shrc);
- /*
- * Don't unpin if marking shared failed because pages are no
- * longer mapped in the host, ie root, anymore.
- */
- goto err_out;
- }
+ if (mshv_partition_encrypted(partition) &&
+ region->host_access_released) {
+ int recovery_ret = mshv_region_share(region);
- return 0;
+ if (recovery_ret) {
+ pt_err(partition,
+ "Failed to recover host access after map failure at GFN %#llx: %d; retaining region\n",
+ region->start_gfn, recovery_ret);
+ return ret;
+ }
+ }
invalidate_region:
mshv_region_invalidate(region);
-err_out:
return ret;
}
@@ -1424,12 +1458,12 @@ mshv_map_user_memory(struct mshv_partition *partition,
return 0;
errout:
- if (region->mapping_may_exist) {
+ if (region->mapping_may_exist || region->host_access_released) {
spin_lock(&partition->pt_mem_regions_lock);
hlist_add_head(®ion->hnode, &partition->pt_mem_regions);
spin_unlock(&partition->pt_mem_regions_lock);
pt_err(partition,
- "Retaining region after uncertain map cleanup at GFN %#llx\n",
+ "Retaining region with uncertain ownership at GFN %#llx\n",
region->start_gfn);
mshv_quarantine_partition(partition);
} else {
@@ -1448,6 +1482,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;
/*
* The list owns one reference. Take a temporary reference and detach
@@ -1479,6 +1515,17 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
if (ret)
goto restore_region;
+ if (region->host_access_released) {
+ ret = mshv_region_share(region);
+ if (ret) {
+ pt_err_ratelimited(partition,
+ "Failed to restore host access at GFN %#llx: %ld\n",
+ region->start_gfn, ret);
+ mshv_quarantine_partition(partition);
+ goto retain_blocked;
+ }
+ }
+
up_write(®ion->mreg_remap_lock);
if (region->mreg_type == MSHV_REGION_TYPE_MEM_MOVABLE)
@@ -1499,6 +1546,14 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
mshv_region_put(region);
return ret;
+retain_blocked:
+ /* Keep list ownership for quarantined teardown, but forbid remapping. */
+ spin_lock(&partition->pt_mem_regions_lock);
+ hlist_add_head(®ion->hnode, &partition->pt_mem_regions);
+ spin_unlock(&partition->pt_mem_regions_lock);
+ up_write(®ion->mreg_remap_lock);
+ mshv_region_put(region);
+ return ret;
}
static long
@@ -1689,6 +1744,473 @@ 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 ||
+ !IS_ALIGNED(req.vmsa_gpa, MSHV_HV_PAGE_SIZE))
+ 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;
+}
+
+#define MSHV_USER_PAGE_BATCH_SIZE 4096
+
+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 = NULL;
+ u64 __user *guest_gpas;
+ u64 *gpfns = NULL;
+ u64 input_completed;
+ u64 offset = 0;
+ u32 host_access = 0;
+ u32 flags = 0;
+ bool acquire;
+ long ret = 0;
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+
+ input_completed = args.completed;
+ args.completed = 0;
+ if ((args.flags & ~MSHV_GPA_HOST_ACCESS_FLAGS_MASK) ||
+ mshv_field_nonzero(args, rsvd) || input_completed ||
+ !args.page_count || !args.guest_gpas) {
+ ret = -EINVAL;
+ goto out;
+ }
+ if ((args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_LARGE_PAGE)) &&
+ !IS_ALIGNED(args.page_count,
+ 1ULL << (HV_HYP_LARGE_PAGE_SHIFT -
+ HV_HYP_PAGE_SHIFT))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ guest_gpas = u64_to_user_ptr(args.guest_gpas);
+ gpfns = kvmalloc_array(MSHV_USER_PAGE_BATCH_SIZE, sizeof(*gpfns),
+ GFP_KERNEL);
+ pages = kvmalloc_array(MSHV_USER_PAGE_BATCH_SIZE, sizeof(*pages),
+ GFP_KERNEL);
+ if (!gpfns || !pages) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ 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))
+ flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
+ acquire = args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_ACQUIRE);
+
+ while (offset < args.page_count) {
+ u64 count = min_t(u64, args.page_count - offset,
+ MSHV_USER_PAGE_BATCH_SIZE);
+ u64 completed = 0;
+ u64 i;
+
+ if (copy_from_user(gpfns, guest_gpas + offset,
+ array_size(count, sizeof(*gpfns)))) {
+ ret = -EFAULT;
+ break;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (!IS_ALIGNED(gpfns[i], MSHV_HV_PAGE_SIZE)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ gpfns[i] = HVPFN_DOWN(gpfns[i]);
+ }
+
+ ret = mshv_gpfns_to_pages(partition, gpfns, count, pages);
+ if (ret)
+ break;
+
+ if ((flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE) &&
+ !mshv_pages_are_contiguous_2m(pages, count)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ ret = hv_call_modify_spa_host_access(partition->pt_id, pages,
+ count, &completed,
+ host_access, flags, acquire);
+ if (completed > count) {
+ ret = -EPROTO;
+ completed = 0;
+ }
+ offset += completed;
+ if (ret) {
+ if (ret == -EPROTO)
+ mshv_quarantine_partition(partition);
+ break;
+ }
+ if (completed != count) {
+ ret = -EPROTO;
+ mshv_quarantine_partition(partition);
+ break;
+ }
+ cond_resched();
+ }
+
+out:
+ args.completed = offset;
+ if (copy_to_user(user_args, &args, sizeof(args))) {
+ if (offset)
+ mshv_quarantine_partition(partition);
+ ret = -EFAULT;
+ }
+ kvfree(pages);
+ 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 __user *guest_pfns;
+ u64 *pages = NULL;
+ u64 input_completed;
+ u64 offset = 0;
+ long ret = 0;
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+
+ input_completed = args.completed;
+ args.completed = 0;
+ if (args.page_type >= MSHV_ISOLATED_PAGE_COUNT ||
+ args.page_size >= MSHV_ISOLATED_PAGE_SIZE_COUNT ||
+ mshv_field_nonzero(args, rsvd) || input_completed ||
+ !args.page_count || !args.guest_pfns) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ guest_pfns = u64_to_user_ptr(args.guest_pfns);
+ pages = kvmalloc_array(MSHV_USER_PAGE_BATCH_SIZE, sizeof(*pages),
+ GFP_KERNEL);
+ if (!pages) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ while (offset < args.page_count) {
+ u64 count = min_t(u64, args.page_count - offset,
+ MSHV_USER_PAGE_BATCH_SIZE);
+ u64 completed = 0;
+
+ if (copy_from_user(pages, guest_pfns + offset,
+ array_size(count, sizeof(*pages)))) {
+ ret = -EFAULT;
+ break;
+ }
+ if (args.page_size == MSHV_ISOLATED_PAGE_SIZE_2MB) {
+ u64 i;
+
+ for (i = 0; i < count; i++) {
+ if (!IS_ALIGNED(pages[i],
+ 1ULL <<
+ (HV_HYP_LARGE_PAGE_SHIFT -
+ HV_HYP_PAGE_SHIFT))) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+ }
+
+ ret = mshv_init_async_handler(partition);
+ if (ret)
+ break;
+
+ ret = hv_call_import_isolated_pages(partition->pt_id, pages,
+ count, &completed,
+ args.page_type,
+ args.page_size,
+ mshv_async_hvcall_handler,
+ partition);
+ if (completed > count) {
+ ret = -EPROTO;
+ completed = 0;
+ }
+ offset += completed;
+ if (ret) {
+ if (ret == -EPROTO)
+ mshv_quarantine_partition(partition);
+ break;
+ }
+ if (completed != count) {
+ ret = -EPROTO;
+ mshv_quarantine_partition(partition);
+ break;
+ }
+ cond_resched();
+ }
+
+out:
+ args.completed = offset;
+ if (copy_to_user(user_args, &args, sizeof(args))) {
+ if (offset)
+ mshv_quarantine_partition(partition);
+ ret = -EFAULT;
+ }
+ kvfree(pages);
+ return ret;
+}
+
+static void
+mshv_copy_snp_id_auth_info(struct hv_snp_id_auth_info *dst,
+ const struct mshv_snp_id_auth_info *src)
+{
+ dst->id_key_algorithm = src->id_key_algorithm;
+ dst->auth_key_algorithm = src->auth_key_algorithm;
+ memcpy(dst->reserved0, src->reserved0, sizeof(dst->reserved0));
+ memcpy(dst->id_block_signature, src->id_block_signature,
+ sizeof(dst->id_block_signature));
+ memcpy(dst->id_key, src->id_key, sizeof(dst->id_key));
+ memcpy(dst->reserved1, src->reserved1, sizeof(dst->reserved1));
+ memcpy(dst->id_key_signature, src->id_key_signature,
+ sizeof(dst->id_key_signature));
+ memcpy(dst->author_key, src->author_key, sizeof(dst->author_key));
+}
+
+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_psp_launch_finish_data *psp;
+ struct mshv_complete_isolated_import args;
+ long ret;
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+ if (!args.psp_parameters)
+ return -EINVAL;
+
+ psp = memdup_user(u64_to_user_ptr(args.psp_parameters), sizeof(*psp));
+ if (IS_ERR(psp))
+ return PTR_ERR(psp);
+ if (memchr_inv(psp->reserved, 0, sizeof(psp->reserved))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ import_data = kvzalloc(sizeof(*import_data), GFP_KERNEL);
+ if (!import_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(import_data->psp_parameters.id_block.launch_digest,
+ psp->id_block.launch_digest,
+ sizeof(psp->id_block.launch_digest));
+ memcpy(import_data->psp_parameters.id_block.family_id,
+ psp->id_block.family_id, sizeof(psp->id_block.family_id));
+ memcpy(import_data->psp_parameters.id_block.image_id,
+ psp->id_block.image_id, sizeof(psp->id_block.image_id));
+ import_data->psp_parameters.id_block.version = psp->id_block.version;
+ import_data->psp_parameters.id_block.guest_svn = psp->id_block.guest_svn;
+ import_data->psp_parameters.id_block.policy.as_uint64 =
+ psp->id_block.policy;
+ mshv_copy_snp_id_auth_info(&import_data->psp_parameters.id_auth_info,
+ &psp->id_auth_info);
+ memcpy(import_data->psp_parameters.host_data, psp->host_data,
+ sizeof(psp->host_data));
+ import_data->psp_parameters.id_block_enabled = psp->id_block_enabled;
+ import_data->psp_parameters.author_key_enabled = psp->author_key_enabled;
+
+ ret = mshv_init_async_handler(partition);
+ if (!ret)
+ ret = hv_call_complete_isolated_import(partition->pt_id,
+ import_data,
+ mshv_async_hvcall_handler,
+ partition);
+ if (!ret)
+ partition->import_completed = true;
+
+ kvfree(import_data);
+out:
+ kfree(psp);
+ 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 completed;
+ u64 gpfns[2];
+ long ret;
+
+ if (copy_from_user(&req, user_args, sizeof(req)))
+ return -EFAULT;
+ if (!IS_ALIGNED(req.req_gpa, MSHV_HV_PAGE_SIZE) ||
+ !IS_ALIGNED(req.rsp_gpa, MSHV_HV_PAGE_SIZE)) {
+ pt_err_ratelimited(partition,
+ "Unaligned PSP request GPAs %#llx/%#llx\n",
+ req.req_gpa, req.rsp_gpa);
+ return -EINVAL;
+ }
+
+ 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), &completed,
+ 0, 0, false);
+ if (ret)
+ goto restore_host_access;
+
+ 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:
+ {
+ int restore_ret;
+
+ restore_ret = hv_call_modify_spa_host_access(partition->pt_id,
+ pages, ARRAY_SIZE(pages),
+ &completed, host_access,
+ 0, true);
+ if (restore_ret) {
+ pt_err(partition,
+ "Failed to restore PSP request page access: %d\n",
+ restore_ret);
+ mshv_quarantine_partition(partition);
+ if (!ret)
+ ret = restore_ret;
+ }
+ }
+ 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)
{
@@ -1730,6 +2252,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;
}
@@ -1842,12 +2373,75 @@ 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 mshv_quarantine_partition(struct mshv_partition *partition)
{
if (partition->teardown_quarantined)
return;
- /* Retain both the partition and the cleanup code indefinitely. */
+ /* Retain the partition, pinned pages, and cleanup code indefinitely. */
__module_get(THIS_MODULE);
partition->teardown_quarantined = true;
}
@@ -1862,6 +2456,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,
@@ -1901,6 +2498,34 @@ static void destroy_partition(struct mshv_partition *partition)
mshv_region_movable_fini(region);
}
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+ if (mshv_partition_encrypted(partition)) {
+ if (partition->pt_initialized) {
+ ret = destroy_snp_partition_state(partition);
+ if (ret) {
+ pt_err(partition,
+ "Failed to destroy SNP state: %d; retaining partition\n",
+ ret);
+ mshv_quarantine_partition(partition);
+ return;
+ }
+ }
+
+ hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) {
+ if (!region->host_access_released)
+ continue;
+ ret = mshv_region_share(region);
+ if (ret) {
+ pt_err(partition,
+ "Failed to restore host access at GFN %#llx: %d; retaining partition\n",
+ region->start_gfn, ret);
+ mshv_quarantine_partition(partition);
+ return;
+ }
+ }
+ }
+#endif
+
if (partition->pt_initialized) {
/*
* We only need to drain signals for root scheduler. This should be
@@ -1909,6 +2534,8 @@ static void destroy_partition(struct mshv_partition *partition)
if (hv_scheduler_type == HV_SCHEDULER_TYPE_ROOT)
drain_all_vps(partition);
+ remove_partition(partition);
+
/* Remove vps */
for (i = 0; i < MSHV_MAX_VPS; ++i) {
vp = partition->pt_vp_array[i];
@@ -1955,10 +2582,10 @@ static void destroy_partition(struct mshv_partition *partition)
hv_call_finalize_partition(partition->pt_id);
partition->pt_initialized = false;
+ } else {
+ remove_partition(partition);
}
- remove_partition(partition);
-
hlist_for_each_entry_safe(region, n, &partition->pt_mem_regions,
hnode) {
hlist_del_init(®ion->hnode);
@@ -2051,7 +2678,7 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
return -EFAULT;
if ((args.pt_flags & ~MSHV_PT_FLAGS_MASK) ||
- args.pt_isolation != MSHV_PT_ISOLATION_NONE)
+ args.pt_isolation >= MSHV_PT_ISOLATION_COUNT)
return -EINVAL;
disabled_procs = &cr_props->disabled_processor_features;
@@ -2064,7 +2691,7 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags,
/* Re-validate v1 fields after second copy_from_user() */
if ((args.pt_flags & ~MSHV_PT_FLAGS_MASK) ||
- args.pt_isolation != MSHV_PT_ISOLATION_NONE)
+ args.pt_isolation >= MSHV_PT_ISOLATION_COUNT)
return -EINVAL;
if (args.pt_num_cpu_fbanks != MSHV_NUM_CPU_FEATURES_BANKS ||
@@ -2128,6 +2755,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;
@@ -2163,7 +2793,6 @@ mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev)
mutex_init(&partition->pt_irq_lock);
init_completion(&partition->async_hypercall);
-
INIT_HLIST_HEAD(&partition->irq_ack_notifier_list);
INIT_HLIST_HEAD(&partition->pt_devices);
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] 27+ messages in thread
* [PATCH v4 7/9] mshv: detect and report SEV-SNP support at init
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (5 preceding siblings ...)
2026-08-31 11:26 ` [PATCH v4 6/9] mshv: wire SEV-SNP partition ioctls Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 11:26 ` [PATCH v4 8/9] mshv: use safe partition CPU feature defaults Wei Hu
2026-08-31 11:26 ` [PATCH v4 9/9] mshv: set up own SynIC registers on a nested root partition Wei Hu
8 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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 MSHV dynamic processor features during parent initialization and
report SEV-SNP availability and the maximum number of encrypted
partitions.
L1VH parents reject the SNP property query, so treat query failures as
"not available" rather than failing MSHV initialization. Copy successful
results from the shared per-CPU output page before restoring local
interrupts.
The misc device is registered before parent initialization is complete.
Reject opens until the partition hash table and interrupt handling are
ready, and clear readiness before teardown starts.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 108 +++++++++++++++++++++++++++++++++++-
1 file changed, 107 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 424b7da680c1..26e92d7721a7 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -100,6 +100,8 @@ static const struct file_operations mshv_dev_fops = {
.llseek = noop_llseek,
};
+static bool mshv_ready;
+
static struct miscdevice mshv_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mshv",
@@ -2864,7 +2866,8 @@ static long mshv_dev_ioctl(struct file *filp, unsigned int ioctl,
static int
mshv_dev_open(struct inode *inode, struct file *filp)
{
- return 0;
+ /* Pairs with init's release after all device state is ready. */
+ return smp_load_acquire(&mshv_ready) ? 0 : -EAGAIN;
}
static int
@@ -3010,6 +3013,104 @@ 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 snp_partition_count = 0;
+ 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);
+ if (hv_result_success(status))
+ snp_partition_count = output->hv_processor_feature_value;
+ 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",
+ snp_partition_count);
+}
+
+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 = HV_SNP_STATUS_NONE;
+ 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);
+ if (hv_result_success(status))
+ snp_status = output->hv_processor_feature_value;
+ local_irq_restore(flags);
+ if (!hv_result_success(status)) {
+ /* L1VH parents reject this property query. */
+ dev_info(dev, "SEV-SNP support is not available\n");
+ return;
+ }
+
+ 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)
{
@@ -3113,6 +3214,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;
@@ -3129,6 +3232,8 @@ static int __init mshv_parent_partition_init(void)
hash_init(mshv_root.pt_htable);
hv_setup_mshv_handler(mshv_isr);
+ /* Publish all initialized state before accepting device opens. */
+ smp_store_release(&mshv_ready, true);
return 0;
@@ -3145,6 +3250,7 @@ static int __init mshv_parent_partition_init(void)
static void __exit mshv_parent_partition_exit(void)
{
+ WRITE_ONCE(mshv_ready, false);
hv_setup_mshv_handler(NULL);
mshv_port_table_fini();
mshv_debugfs_exit();
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v4 8/9] mshv: use safe partition CPU feature defaults
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (6 preceding siblings ...)
2026-08-31 11:26 ` [PATCH v4 7/9] mshv: detect and report SEV-SNP support at init Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 11:26 ` [PATCH v4 9/9] mshv: set up own SynIC registers on a nested root partition Wei Hu
8 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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>
Initialize the complete partition creation properties and build a
conservative x86 disabled-feature mask for legacy callers. Enable only
the default processor and XSAVE features known to be supported, while
preserving nested virtualization when the caller requests it.
Allow extended callers to provide a partial set of processor-feature
banks and retain safe defaults for omitted banks. Define the full
processor and XSAVE feature masks used by the defaults.
Signed-off-by: Wei Liu <wei.liu@kernel.org>
---
drivers/hv/mshv_root_main.c | 123 ++++++++++++++++++++++++++++++------
include/hyperv/hvhdk.h | 79 ++++++++++++++++++++++-
include/uapi/linux/mshv.h | 3 +-
3 files changed, 181 insertions(+), 24 deletions(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 26e92d7721a7..1de3c06aaafa 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2683,33 +2683,124 @@ 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;
+ if (args.pt_flags & BIT(MSHV_PT_BIT_NESTED_VIRTUALIZATION))
+ disabled_procs->nested_virt_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 */
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;
- 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)
@@ -2724,16 +2815,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 */
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;
};
diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
index 9c665f47ce71..02ee904e2bb1 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] 27+ messages in thread
* [PATCH v4 9/9] mshv: set up own SynIC registers on a nested root partition
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (7 preceding siblings ...)
2026-08-31 11:26 ` [PATCH v4 8/9] mshv: use safe partition CPU feature defaults Wei Hu
@ 2026-08-31 11:26 ` Wei Hu
2026-08-31 12:09 ` sashiko-bot
8 siblings, 1 reply; 27+ messages in thread
From: Wei Hu @ 2026-08-31 11:26 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, ...) through
hv_set_msr()'s nested remap, while mshv_synic reads and writes the
non-nested SynIC MSRs. As a result MSHV maps the wrong message page and
never receives the async hypercall completion, hanging guest creation.
Gate the "VMBus owns the SynIC registers" optimization on !hv_nested so
a nested root partition programs its own non-nested SynIC registers.
There is no behavior change for a non-nested root.
Map hypervisor-provided SIMP, SIEFP, and root SIRBP pages with the shared
GPA boundary removed and MEMREMAP_DEC, matching drivers/hv/hv.c for
confidential hosts. The L1VH SIRBP remains locally allocated.
Signed-off-by: Wei Hu <weh@microsoft.com>
---
drivers/hv/mshv_synic.c | 38 +++++++++++++++++++++++++-------------
1 file changed, 25 insertions(+), 13 deletions(-)
diff --git a/drivers/hv/mshv_synic.c b/drivers/hv/mshv_synic.c
index 0fdbae1e053c..8470cf958f21 100644
--- a/drivers/hv/mshv_synic.c
+++ b/drivers/hv/mshv_synic.c
@@ -458,6 +458,25 @@ void mshv_isr(void)
}
}
+static bool mshv_synic_vmbus_owns_registers(void)
+{
+ /* Nested VMBus programs nested MSRs, while MSHV uses non-nested MSRs. */
+ return hv_vmbus_exists() && !hv_nested;
+}
+
+static void *mshv_synic_map_shared_page(u64 pfn)
+{
+ u64 base;
+
+ if (!pfn)
+ return NULL;
+
+ /* Match Hyper-V's established confidential SynIC mapping convention. */
+ base = (pfn << HV_HYP_PAGE_SHIFT) &
+ ~ms_hyperv.shared_gpa_boundary;
+ return memremap(base, HV_HYP_PAGE_SIZE, MEMREMAP_WB | MEMREMAP_DEC);
+}
+
static int mshv_synic_cpu_init(unsigned int cpu)
{
union hv_synic_simp simp;
@@ -468,11 +487,7 @@ static int mshv_synic_cpu_init(unsigned int cpu)
struct hv_message_page *msg_page;
struct hv_synic_event_flags_page *event_flags_page;
struct hv_synic_event_ring_page *event_ring_page;
- /*
- * VMBus owns SIMP/SIEFP/SCONTROL when it is active.
- * See hv_hyp_synic_enable_regs() for that initialization.
- */
- bool vmbus_active = hv_vmbus_exists();
+ bool vmbus_active = mshv_synic_vmbus_owns_registers();
/*
* Map the SYNIC message page. When VMBus is not active the
@@ -484,8 +499,7 @@ static int mshv_synic_cpu_init(unsigned int cpu)
simp.simp_enabled = true;
hv_set_non_nested_msr(HV_MSR_SIMP, simp.as_uint64);
}
- msg_page = memremap(simp.base_simp_gpa << HV_HYP_PAGE_SHIFT,
- HV_HYP_PAGE_SIZE, MEMREMAP_WB);
+ msg_page = mshv_synic_map_shared_page(simp.base_simp_gpa);
WRITE_ONCE(spages->hyp_synic_message_page, msg_page);
if (!msg_page)
@@ -500,8 +514,7 @@ static int mshv_synic_cpu_init(unsigned int cpu)
siefp.siefp_enabled = true;
hv_set_non_nested_msr(HV_MSR_SIEFP, siefp.as_uint64);
}
- event_flags_page = memremap(siefp.base_siefp_gpa << HV_HYP_PAGE_SHIFT,
- HV_HYP_PAGE_SIZE, MEMREMAP_WB);
+ event_flags_page = mshv_synic_map_shared_page(siefp.base_siefp_gpa);
WRITE_ONCE(spages->synic_event_flags_page, event_flags_page);
if (!event_flags_page)
@@ -511,8 +524,8 @@ static int mshv_synic_cpu_init(unsigned int cpu)
sirbp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIRBP);
if (hv_root_partition()) {
- event_ring_page = memremap(sirbp.base_sirbp_gpa << HV_HYP_PAGE_SHIFT,
- HV_HYP_PAGE_SIZE, MEMREMAP_WB);
+ event_ring_page =
+ mshv_synic_map_shared_page(sirbp.base_sirbp_gpa);
if (!event_ring_page)
goto cleanup_siefp;
@@ -595,8 +608,7 @@ static int mshv_synic_cpu_exit(unsigned int cpu)
struct hv_message_page *msg_page;
struct hv_synic_event_flags_page *event_flags_page;
struct hv_synic_event_ring_page *event_ring_page;
- /* VMBus owns SIMP/SIEFP/SCONTROL when it is active */
- bool vmbus_active = hv_vmbus_exists();
+ bool vmbus_active = mshv_synic_vmbus_owns_registers();
msg_page = READ_ONCE(spages->hyp_synic_message_page);
event_flags_page = READ_ONCE(spages->synic_event_flags_page);
--
2.43.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v4 1/9] mshv: retain memory regions until unmap succeeds
2026-08-31 11:26 ` [PATCH v4 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
@ 2026-08-31 11:48 ` sashiko-bot
2026-09-01 12:04 ` [EXTERNAL] " Wei Hu
0 siblings, 1 reply; 27+ messages in thread
From: sashiko-bot @ 2026-08-31 11:48 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] Early return in MMU interval notifier allows guest-to-host use-after-free because it falsely signals that guest access has been revoked before the hypervisor unmap completes.
- [High] ABBA deadlock between `mreg_remap_lock` rwsem fair-queueing and HMM sequence counter invalidation due to holding locks across a retry loop.
Pre-existing issues:
- [Critical] mshv_region_interval_invalidate() returns false for blockable ranges if remapping fails, leading to silent UAF.
- [Critical] Incorrect MMIO physical address mapped for user memory region, allowing arbitrary physical memory access.
--
commit bdfaa07680341c254374f6fa979c3fcd550c4840
Author: Wei Hu <weh@microsoft.com>
mshv: retain memory regions until unmap succeeds
Do not remove a region from the partition list or unpin its pages until
a checked UnmapGpaPages hypercall has completed. Validate repetition
progress so malformed zero or oversized completion counts cannot spin or
conceal partial state.
[ ... ]
> diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
> index 6d65e5b421529..9feb49d9d9d49 100644
> --- a/drivers/hv/mshv_regions.c
> +++ b/drivers/hv/mshv_regions.c
[ ... ]
> @@ -452,12 +458,35 @@ static int mshv_region_range_fault(struct mshv_mem_region *region,
> range.start = region->start_uaddr + page_offset * HV_HYP_PAGE_SIZE;
> range.end = range.start + page_count * HV_HYP_PAGE_SIZE;
>
> + /*
> + * Pin the mm while the remap gate still guarantees that the interval
> + * notifier is registered. Do not acquire mmap_lock while holding the
> + * gate: invalidation enters with mmap_lock held for write.
> + */
> + down_read(®ion->mreg_remap_lock);
> + if (READ_ONCE(region->remap_blocked) ||
> + !mmget_not_zero(region->mreg_mni.mm)) {
> + up_read(®ion->mreg_remap_lock);
> + ret = -ECANCELED;
> + goto out;
> + }
> + mm = region->mreg_mni.mm;
> + up_read(®ion->mreg_remap_lock);
> +
> + /* Lock order: mmap_lock -> mreg_remap_lock -> mreg_mutex. */
> + mmap_read_lock(mm);
> + down_read(®ion->mreg_remap_lock);
> + if (READ_ONCE(region->remap_blocked)) {
> + ret = -ECANCELED;
> + goto out_unlock_remap;
> + }
> +
> do {
> ret = mshv_region_hmm_fault_and_lock(region, &range);
> } while (ret == -EBUSY);
[Severity: High]
Does holding mreg_remap_lock across this retry loop create an ABBA deadlock?
If a concurrent page fault and region unmap occur:
1. The fault handler acquires mreg_remap_lock for read and blocks here waiting
for mmu_interval_read_retry() to clear.
2. The unmap thread queues for the mreg_remap_lock write lock in
mshv_unmap_user_memory(), blocking new readers due to fair-queueing.
3. The MMU interval invalidation callback mshv_region_interval_invalidate()
(which would clear the retry loop) attempts to acquire mreg_remap_lock for
read, but gets stuck behind the queued writer.
Can the fault handler loop forever holding the read lock, freezing the host
kernel?
[ ... ]
> @@ -524,6 +558,17 @@ static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
> unsigned long mstart, mend;
> int ret = -EPERM;
>
> + if (mmu_notifier_range_blockable(range))
> + down_read(®ion->mreg_remap_lock);
> + else if (!down_read_trylock(®ion->mreg_remap_lock))
> + return false;
> +
> + /* A completed checked unmap makes notifier remapping unnecessary. */
> + if (READ_ONCE(region->remap_blocked)) {
> + up_read(®ion->mreg_remap_lock);
> + return true;
> + }
[Severity: Critical]
Does this early return falsely signal that guest access has been revoked before
the hypervisor unmap completes?
If mshv_unmap_user_memory() sets remap_blocked = true but has not yet acquired
the mreg_remap_lock write lock to issue the hypervisor unmap, a racing MMU
invalidation could acquire the read lock first, see remap_blocked == true, and
immediately return true.
Could the MM subsystem then free the page while the hypervisor unmap has not
yet executed, leading to a guest-to-host use-after-free?
[ ... ]
> out_unlock:
> mutex_unlock(®ion->mreg_mutex);
> out_fail:
> + up_read(®ion->mreg_remap_lock);
> WARN_ONCE(ret,
> "Failed to invalidate region %#llx-%#llx (range %#lx-%#lx, event: %u, pages %#llx-%#llx, mm: %#llx): %d\n",
> region->start_uaddr,
[Severity: Critical]
This is a pre-existing issue, but does mshv_region_interval_invalidate()
return false on a blockable range if mshv_region_remap_pages() fails?
The core MM __mmu_notifier_invalidate_range_start() explicitly ignores the
return value of blockable notifiers. By returning false instead of ensuring
the mapping is torn down, the driver silently fails the invalidation contract.
Does this allow the core MM subsystem to free or reuse a host page while the
guest VM retains mapped access to it, resulting in a guest-to-host
use-after-free?
[ ... ]
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index cc2cfce2aefdb..1ca14660f9985 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> @@ -1397,13 +1398,15 @@ mshv_map_user_memory(struct mshv_partition *partition,
> ret = hv_call_map_gpa_pages(partition->pt_id,
> region->start_gfn,
> region->nr_pages,
> - HV_MAP_GPA_NO_ACCESS, NULL);
> + HV_MAP_GPA_NO_ACCESS, NULL,
> + ®ion->mapping_may_exist);
> break;
> case MSHV_REGION_TYPE_MMIO:
> ret = hv_call_map_mmio_pages(partition->pt_id,
> region->start_gfn,
> mmio_pfn,
> - region->nr_pages);
> + region->nr_pages,
> + ®ion->mapping_may_exist);
> break;
> }
[Severity: Critical]
This is a pre-existing issue, but does mshv_map_user_memory() allow mapping of
arbitrary physical memory by passing an unadjusted MMIO PFN and unvalidated
size directly to the hypervisor?
By using vma->vm_pgoff directly without adding the intra-VMA offset
corresponding to the userspace_addr, and by failing to validate that the
requested size fits inside vma->vm_end:
drivers/hv/mshv_root_main.c:mshv_map_user_memory() {
vma = vma_lookup(current->mm, mem->userspace_addr);
is_mmio = vma ? !!(vma->vm_flags & (VM_IO | VM_PFNMAP)) : 0;
mmio_pfn = is_mmio ? vma->vm_pgoff : 0;
...
}
Could a malicious userspace process set userspace_addr to an offset within a
legitimate MMIO VMA (or beyond it) and map arbitrary contiguous physical
memory into the guest VM?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831112704.2851147-1-weh@linux.microsoft.com?part=1
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v4 5/9] mshv: add SEV-SNP isolated page hypercalls
2026-08-31 11:26 ` [PATCH v4 5/9] mshv: add SEV-SNP isolated page hypercalls Wei Hu
@ 2026-08-31 11:53 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-31 11:53 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The commit message explicitly claims to add wrappers for 'modifying sparse SPA host access' and 'setting partition properties', but these functions are completely absent from the patch.
--
commit 39272e85622d93556ea19a15d2e28e9556ab8c29
Author: Wei Liu <wei.liu@kernel.org>
mshv: add SEV-SNP isolated page hypercalls
This patch adds wrappers for importing isolated pages and handling their
completions for SEV-SNP guests. It supports synchronous and pending completions
and preserves the asynchronous completion result while rejecting malformed counts.
> mshv: add SEV-SNP isolated page hypercalls
>
> Add wrappers for importing isolated pages, completing the import,
> modifying sparse SPA host access, and setting partition properties.
[Severity: Medium]
Does this commit message accurately reflect the changes in this patch?
The commit message explicitly claims to add wrappers for modifying sparse SPA
host access and setting partition properties, but these functions are completely
absent from the patch payload.
Looking at the changes introduced in drivers/hv/mshv_root_hv_call.c, only the
hv_call_import_isolated_pages() and hv_call_complete_isolated_import() wrappers
are actually implemented.
Are the missing wrappers (such as hv_call_set_partition_property and
modifications to hv_call_modify_spa_host_access) perhaps introduced in a
subsequent patch, such as 'mshv: wire SEV-SNP partition ioctls'?
If so, could the commit message for this patch be updated to only describe the
two import wrappers actually added here? This would ensure the patch and its
history remain accurate and self-contained.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831112704.2851147-1-weh@linux.microsoft.com?part=5
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v4 6/9] mshv: wire SEV-SNP partition ioctls
2026-08-31 11:26 ` [PATCH v4 6/9] mshv: wire SEV-SNP partition ioctls Wei Hu
@ 2026-08-31 12:07 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-31 12:07 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Error recovery paths unconditionally attempt to re-acquire host access for all pages in a request or region, guaranteeing failure and triggering unwarranted partition quarantine.
- [High] `MSHV_MODIFY_GPA_HOST_ACCESS` alters host access without updating the region's `host_access_released` flag, causing host access to be permanently leaked or double-acquired during teardown.
- [High] Releasing host access fails to set the MAKE_EXCLUSIVE flag, causing the hypercall to use partition_id 0 and failing to assign the memory to the guest partition.
--
commit f76d3f5042ec21e318f98c3b6d0b444fad2c8e2d
Author: Wei Liu <wei.liu@kernel.org>
mshv: wire SEV-SNP partition ioctls
This commit wires the SEV-SNP UAPI into partition creation, memory ownership,
isolated-page import, launch completion, PSP guest requests, AP creation,
and teardown. It implements tracking for child mappings and released host
access per region, with error handling for partial maps and teardowns.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index 4a15d4bfa925a..424b7da680c16 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> @@ -1297,51 +1331,51 @@ static int mshv_prepare_pinned_region(struct mshv_mem_region *region)
[ ... ]
> if (mshv_partition_encrypted(partition)) {
> + int recovery_ret;
> +
> ret = mshv_region_unshare(region);
> if (ret) {
> - pt_err(partition,
> - "Failed to unshare memory region (guest_pfn: %llu): %d\n",
> - region->start_gfn, ret);
> + pt_err_ratelimited(partition,
> + "Failed to release host access at GFN %#llx: %d\n",
> + region->start_gfn, ret);
> + recovery_ret = mshv_region_share(region);
[Severity: High]
Does the error recovery path in mshv_prepare_pinned_region() (in
drivers/hv/mshv_root_main.c) unconditionally attempt to re-acquire host
access for all pages in the region?
If mshv_region_unshare(region) fails partway through the region,
mshv_region_share(region) will iterate over the entire region from 0 to
nr_pages.
Will this blindly hit the exact same page that caused the initial unshare to
fail, predictably turning a localized failure into an unrecoverable failure
that permanently quarantines the partition?
> + if (recovery_ret) {
> + pt_err(partition,
> + "Failed to recover host access at GFN %#llx: %d; retaining region\n",
> + region->start_gfn, recovery_ret);
> + return ret;
> + }
> goto invalidate_region;
> }
> }
[ ... ]
> @@ -1689,6 +1744,473 @@ 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)
> +{
[ ... ]
> + while (offset < args.page_count) {
> + u64 count = min_t(u64, args.page_count - offset,
> + MSHV_USER_PAGE_BATCH_SIZE);
> + u64 completed = 0;
> + u64 i;
[ ... ]
> + ret = hv_call_modify_spa_host_access(partition->pt_id, pages,
> + count, &completed,
> + host_access, flags, acquire);
[Severity: High]
Will releasing host access via mshv_partition_ioctl_modify_gpa_host_access()
in drivers/hv/mshv_root_main.c fail to assign the memory to the guest
partition?
When userspace issues this ioctl with acquire=false to make pages exclusive
to the guest, the flags variable lacks the
HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE bit.
Because the MAKE_EXCLUSIVE flag is omitted, the underlying helper
hv_call_modify_spa_host_access() will not populate
input_page->partition_id and it will default to 0. Does this mistakenly
assign the memory to the root partition instead of the guest?
[Severity: High]
Does mshv_partition_ioctl_modify_gpa_host_access() in
drivers/hv/mshv_root_main.c alter host access without updating the region's
host_access_released flag?
When userspace successfully unshares pages here, the change in hypervisor
state is not recorded in the kernel's region tracking.
Because destroy_partition() relies on the host_access_released boolean,
will it skip restoring host access during teardown, leading to host access
being permanently leaked or double-acquired, and causing a host hardware
memory encryption violation when the memory is later reused?
> + if (completed > count) {
> + ret = -EPROTO;
> + completed = 0;
> + }
[ ... ]
> +static long
> +mshv_partition_ioctl_issue_psp_guest_request(struct mshv_partition *partition,
> + void __user *user_args)
> +{
[ ... ]
> + ret = hv_call_modify_spa_host_access(partition->pt_id, pages,
> + ARRAY_SIZE(pages), &completed,
> + 0, 0, false);
[Severity: High]
Is the HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE flag similarly missing
here in mshv_partition_ioctl_issue_psp_guest_request()
(drivers/hv/mshv_root_main.c)?
The flags parameter is passed as 0. Will this also cause the hypercall to
use partition_id 0 and fail to assign the memory to the guest partition?
> + if (ret)
> + goto restore_host_access;
> +
> + 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:
> + {
> + int restore_ret;
> +
> + restore_ret = hv_call_modify_spa_host_access(partition->pt_id,
> + pages, ARRAY_SIZE(pages),
> + &completed, host_access,
> + 0, true);
[Severity: High]
Does this recovery path in mshv_partition_ioctl_issue_psp_guest_request()
ignore the completed variable from the original failure?
By passing ARRAY_SIZE(pages) instead of the number of successfully released
pages, it attempts to restore pages that were never released (or failed to
release), guaranteeing that the hypervisor will reject this operation again.
Could this predictable secondary failure trigger an unwarranted partition
quarantine and memory leak?
> + if (restore_ret) {
> + pt_err(partition,
> + "Failed to restore PSP request page access: %d\n",
> + restore_ret);
> + mshv_quarantine_partition(partition);
> + if (!ret)
> + ret = restore_ret;
> + }
> + }
> + return ret;
> +}
[ ... ]
> @@ -1901,6 +2498,34 @@ static void destroy_partition(struct mshv_partition *partition)
[ ... ]
> +#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
> + if (mshv_partition_encrypted(partition)) {
> + if (partition->pt_initialized) {
> + ret = destroy_snp_partition_state(partition);
> + if (ret) {
> + pt_err(partition,
> + "Failed to destroy SNP state: %d; retaining partition\n",
> + ret);
> + mshv_quarantine_partition(partition);
> + return;
> + }
> + }
> +
> + hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) {
> + if (!region->host_access_released)
> + continue;
> + ret = mshv_region_share(region);
[Severity: High]
Does this teardown code in destroy_partition()
(drivers/hv/mshv_root_main.c) rely entirely on the desynchronized
host_access_released boolean?
Because mshv_partition_ioctl_modify_gpa_host_access() alters host access
without updating this flag, will this skip restoring host access for regions
that were dynamically unshared by userspace, leading to improper cleanup and
host kernel crashes upon page reuse?
> + if (ret) {
> + pt_err(partition,
> + "Failed to restore host access at GFN %#llx: %d; retaining partition\n",
> + region->start_gfn, ret);
> + mshv_quarantine_partition(partition);
> + return;
> + }
> + }
> + }
> +#endif
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831112704.2851147-1-weh@linux.microsoft.com?part=6
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v4 9/9] mshv: set up own SynIC registers on a nested root partition
2026-08-31 11:26 ` [PATCH v4 9/9] mshv: set up own SynIC registers on a nested root partition Wei Hu
@ 2026-08-31 12:09 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-31 12:09 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] Missing memory decryption for locally allocated L1VH SIRBP in Confidential VMs.
--
commit 0a5809fa9d9b76912e599a03beec81fda2326b1b
Author: Wei Hu <weh@microsoft.com>
mshv: set up own SynIC registers on a nested root partition
This patch modifies mshv_synic to set up its own SynIC registers on nested
root partitions instead of assuming VMBus has provisioned them. It also
updates the mapping of hypervisor-provided SIMP, SIEFP, and root SIRBP pages
to use MEMREMAP_DEC to support confidential hosts.
> diff --git a/drivers/hv/mshv_synic.c b/drivers/hv/mshv_synic.c
> index 0fdbae1e053c3..8470cf958f21b 100644
> --- a/drivers/hv/mshv_synic.c
> +++ b/drivers/hv/mshv_synic.c
[ ... ]
> @@ -511,8 +524,8 @@ static int mshv_synic_cpu_init(unsigned int cpu)
> sirbp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIRBP);
>
> if (hv_root_partition()) {
> - event_ring_page = memremap(sirbp.base_sirbp_gpa << HV_HYP_PAGE_SHIFT,
> - HV_HYP_PAGE_SIZE, MEMREMAP_WB);
> + event_ring_page =
> + mshv_synic_map_shared_page(sirbp.base_sirbp_gpa);
>
> if (!event_ring_page)
> goto cleanup_siefp;
[Severity: High]
Since this patch adds support for confidential hosts by mapping
hypervisor-provided pages with MEMREMAP_DEC, what happens to the locally
allocated SIRBP in the else branch for L1VH partitions?
If we look further down in mshv_synic_cpu_init():
} else {
/*
* On L1VH the hypervisor does not provide a SIRBP page.
* Allocate one and program its GPA into the MSR.
*/
event_ring_page = (struct hv_synic_event_ring_page *)
get_zeroed_page(GFP_KERNEL);
In a Confidential VM, memory allocated via get_zeroed_page() is encrypted by
default. Because set_memory_decrypted() is never called here, will the
hypervisor be unable to access the event ring, causing a regression in event
delivery for L1VH?
For comparison, drivers/hv/hv.c decrypts its locally allocated SynIC pages
via hv_alloc_page(..., decrypt=true, ...).
[ ... ]
> @@ -595,8 +608,7 @@ static int mshv_synic_cpu_exit(unsigned int cpu)
> struct hv_message_page *msg_page;
> struct hv_synic_event_flags_page *event_flags_page;
> struct hv_synic_event_ring_page *event_ring_page;
> - /* VMBus owns SIMP/SIEFP/SCONTROL when it is active */
> - bool vmbus_active = hv_vmbus_exists();
> + bool vmbus_active = mshv_synic_vmbus_owns_registers();
>
> msg_page = READ_ONCE(spages->hyp_synic_message_page);
> event_flags_page = READ_ONCE(spages->synic_event_flags_page);
[Severity: High]
If the allocation in mshv_synic_cpu_init() is updated to decrypt the page,
does the cleanup path in mshv_synic_cpu_exit() need a corresponding change?
Looking at the cleanup path in mshv_synic_cpu_exit():
if (event_ring_page) {
if (hv_root_partition())
memunmap(event_ring_page);
else
free_page((unsigned long)event_ring_page);
}
If the page is decrypted during initialization, will freeing it here without
first calling set_memory_encrypted() return a decrypted page to the kernel
allocator and cause a memory corruption regression?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831112704.2851147-1-weh@linux.microsoft.com?part=9
^ permalink raw reply [flat|nested] 27+ messages in thread
* RE: [EXTERNAL] Re: [PATCH v4 1/9] mshv: retain memory regions until unmap succeeds
2026-08-31 11:48 ` sashiko-bot
@ 2026-09-01 12:04 ` Wei Hu
0 siblings, 0 replies; 27+ messages in thread
From: Wei Hu @ 2026-09-01 12:04 UTC (permalink / raw)
To: sashiko-reviews@lists.linux.dev, Wei Hu; +Cc: linux-hyperv@vger.kernel.org
> -----Original Message-----
> From: sashiko-bot@kernel.org <sashiko-bot@kernel.org>
> Sent: Monday, August 31, 2026 7:49 PM
> To: Wei Hu <weh@linux.microsoft.com>
> Cc: linux-hyperv@vger.kernel.org
> Subject: [EXTERNAL] Re: [PATCH v4 1/9] mshv: retain memory regions until
> unmap succeeds
>
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s)
> to consider:
>
> New issues:
> - [Critical] Early return in MMU interval notifier allows guest-to-host use-after-
> free because it falsely signals that guest access has been revoked before the
> hypervisor unmap completes.
> - [High] ABBA deadlock between `mreg_remap_lock` rwsem fair-queueing and
> HMM sequence counter invalidation due to holding locks across a retry loop.
>
> Pre-existing issues:
> - [Critical] mshv_region_interval_invalidate() returns false for blockable ranges
> if remapping fails, leading to silent UAF.
> - [Critical] Incorrect MMIO physical address mapped for user memory region,
> allowing arbitrary physical memory access.
Hello Sashiko,
The pre-existing issues will be addressed in a separate patch set. They will not be resolved in the future revisions of this patch set.
Thanks,
Wei
> --
>
> commit bdfaa07680341c254374f6fa979c3fcd550c4840
> Author: Wei Hu <weh@microsoft.com>
>
> mshv: retain memory regions until unmap succeeds
>
> Do not remove a region from the partition list or unpin its pages until a
> checked UnmapGpaPages hypercall has completed. Validate repetition
> progress so malformed zero or oversized completion counts cannot spin or
> conceal partial state.
>
> [ ... ]
>
> > diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
> > index 6d65e5b421529..9feb49d9d9d49 100644
> > --- a/drivers/hv/mshv_regions.c
> > +++ b/drivers/hv/mshv_regions.c
>
> [ ... ]
>
> > @@ -452,12 +458,35 @@ static int mshv_region_range_fault(struct
> mshv_mem_region *region,
> > range.start = region->start_uaddr + page_offset * HV_HYP_PAGE_SIZE;
> > range.end = range.start + page_count * HV_HYP_PAGE_SIZE;
> >
> > + /*
> > + * Pin the mm while the remap gate still guarantees that the interval
> > + * notifier is registered. Do not acquire mmap_lock while holding the
> > + * gate: invalidation enters with mmap_lock held for write.
> > + */
> > + down_read(®ion->mreg_remap_lock);
> > + if (READ_ONCE(region->remap_blocked) ||
> > + !mmget_not_zero(region->mreg_mni.mm)) {
> > + up_read(®ion->mreg_remap_lock);
> > + ret = -ECANCELED;
> > + goto out;
> > + }
> > + mm = region->mreg_mni.mm;
> > + up_read(®ion->mreg_remap_lock);
> > +
> > + /* Lock order: mmap_lock -> mreg_remap_lock -> mreg_mutex. */
> > + mmap_read_lock(mm);
> > + down_read(®ion->mreg_remap_lock);
> > + if (READ_ONCE(region->remap_blocked)) {
> > + ret = -ECANCELED;
> > + goto out_unlock_remap;
> > + }
> > +
> > do {
> > ret = mshv_region_hmm_fault_and_lock(region, &range);
> > } while (ret == -EBUSY);
>
> [Severity: High]
> Does holding mreg_remap_lock across this retry loop create an ABBA
> deadlock?
>
> If a concurrent page fault and region unmap occur:
> 1. The fault handler acquires mreg_remap_lock for read and blocks here
> waiting
> for mmu_interval_read_retry() to clear.
> 2. The unmap thread queues for the mreg_remap_lock write lock in
> mshv_unmap_user_memory(), blocking new readers due to fair-queueing.
> 3. The MMU interval invalidation callback mshv_region_interval_invalidate()
> (which would clear the retry loop) attempts to acquire mreg_remap_lock for
> read, but gets stuck behind the queued writer.
>
> Can the fault handler loop forever holding the read lock, freezing the host
> kernel?
>
> [ ... ]
>
> > @@ -524,6 +558,17 @@ static bool mshv_region_interval_invalidate(struct
> mmu_interval_notifier *mni,
> > unsigned long mstart, mend;
> > int ret = -EPERM;
> >
> > + if (mmu_notifier_range_blockable(range))
> > + down_read(®ion->mreg_remap_lock);
> > + else if (!down_read_trylock(®ion->mreg_remap_lock))
> > + return false;
> > +
> > + /* A completed checked unmap makes notifier remapping
> unnecessary. */
> > + if (READ_ONCE(region->remap_blocked)) {
> > + up_read(®ion->mreg_remap_lock);
> > + return true;
> > + }
>
> [Severity: Critical]
> Does this early return falsely signal that guest access has been revoked before
> the hypervisor unmap completes?
>
> If mshv_unmap_user_memory() sets remap_blocked = true but has not yet
> acquired the mreg_remap_lock write lock to issue the hypervisor unmap, a
> racing MMU invalidation could acquire the read lock first, see remap_blocked
> == true, and immediately return true.
>
> Could the MM subsystem then free the page while the hypervisor unmap has
> not yet executed, leading to a guest-to-host use-after-free?
>
> [ ... ]
>
> > out_unlock:
> > mutex_unlock(®ion->mreg_mutex);
> > out_fail:
> > + up_read(®ion->mreg_remap_lock);
> > WARN_ONCE(ret,
> > "Failed to invalidate region %#llx-%#llx (range %#lx-%#lx,
> event: %u, pages %#llx-%#llx, mm: %#llx): %d\n",
> > region->start_uaddr,
>
> [Severity: Critical]
> This is a pre-existing issue, but does mshv_region_interval_invalidate() return
> false on a blockable range if mshv_region_remap_pages() fails?
>
> The core MM __mmu_notifier_invalidate_range_start() explicitly ignores the
> return value of blockable notifiers. By returning false instead of ensuring the
> mapping is torn down, the driver silently fails the invalidation contract.
>
> Does this allow the core MM subsystem to free or reuse a host page while the
> guest VM retains mapped access to it, resulting in a guest-to-host use-after-
> free?
>
> [ ... ]
>
> > diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> > index cc2cfce2aefdb..1ca14660f9985 100644
> > --- a/drivers/hv/mshv_root_main.c
> > +++ b/drivers/hv/mshv_root_main.c
>
> [ ... ]
>
> > @@ -1397,13 +1398,15 @@ mshv_map_user_memory(struct
> mshv_partition *partition,
> > ret = hv_call_map_gpa_pages(partition->pt_id,
> > region->start_gfn,
> > region->nr_pages,
> > - HV_MAP_GPA_NO_ACCESS, NULL);
> > + HV_MAP_GPA_NO_ACCESS, NULL,
> > + ®ion->mapping_may_exist);
> > break;
> > case MSHV_REGION_TYPE_MMIO:
> > ret = hv_call_map_mmio_pages(partition->pt_id,
> > region->start_gfn,
> > mmio_pfn,
> > - region->nr_pages);
> > + region->nr_pages,
> > + ®ion->mapping_may_exist);
> > break;
> > }
>
> [Severity: Critical]
> This is a pre-existing issue, but does mshv_map_user_memory() allow
> mapping of arbitrary physical memory by passing an unadjusted MMIO PFN
> and unvalidated size directly to the hypervisor?
>
> By using vma->vm_pgoff directly without adding the intra-VMA offset
> corresponding to the userspace_addr, and by failing to validate that the
> requested size fits inside vma->vm_end:
>
> drivers/hv/mshv_root_main.c:mshv_map_user_memory() {
> vma = vma_lookup(current->mm, mem->userspace_addr);
> is_mmio = vma ? !!(vma->vm_flags & (VM_IO | VM_PFNMAP)) : 0;
> mmio_pfn = is_mmio ? vma->vm_pgoff : 0;
> ...
> }
>
> Could a malicious userspace process set userspace_addr to an offset within a
> legitimate MMIO VMA (or beyond it) and map arbitrary contiguous physical
> memory into the guest VM?
>
> --
> Sashiko AI
> review * https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F
> %2Fsashiko.dev%2F%23%2Fpatchset%2F20260831112704.2851147-1-
> weh%40linux.microsoft.com%3Fpart%3D1&data=05%7C02%7Cweh%40micr
> osoft.com%7Cf41ea7e6afe44d8b0d6208df0755cacd%7C72f988bf86f141af
> 91ab2d7cd011db47%7C1%7C0%7C639237737234695849%7CUnknown%
> 7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAi
> OiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdat
> a=nKKIfw5wbEV33vK8LJGwQNDyA3zm%2F5DYBZgHWDH0x2U%3D&reserve
> d=0
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-09-01 12:05 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25 4:04 [PATCH v3 0/7] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-25 4:04 ` [PATCH v3 1/7] mshv: add SEV-SNP UAPI definitions Wei Hu
2026-08-25 4:04 ` [PATCH v3 2/7] mshv: add SEV-SNP PSP request hypercall Wei Hu
2026-08-25 4:17 ` sashiko-bot
2026-08-25 4:04 ` [PATCH v3 3/7] mshv: add SEV-SNP isolated page hypercalls Wei Hu
2026-08-25 4:04 ` [PATCH v3 4/7] mshv: wire SEV-SNP partition ioctls Wei Hu
2026-08-25 4:22 ` sashiko-bot
2026-08-25 4:04 ` [PATCH v3 5/7] mshv: detect and report SEV-SNP support at init Wei Hu
2026-08-25 4:19 ` sashiko-bot
2026-08-25 4:04 ` [PATCH v3 6/7] mshv: use safe partition CPU feature defaults Wei Hu
2026-08-25 4:04 ` [PATCH v3 7/7] mshv: set up own SynIC registers on a nested root partition Wei Hu
2026-08-25 4:20 ` sashiko-bot
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-08-31 11:26 ` [PATCH v4 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
2026-08-31 11:48 ` sashiko-bot
2026-09-01 12:04 ` [EXTERNAL] " Wei Hu
2026-08-31 11:26 ` [PATCH v4 2/9] mshv: clear SynIC mappings before freeing them Wei Hu
2026-08-31 11:26 ` [PATCH v4 3/9] mshv: add SEV-SNP UAPI definitions Wei Hu
2026-08-31 11:26 ` [PATCH v4 4/9] mshv: add SEV-SNP PSP request hypercall Wei Hu
2026-08-31 11:26 ` [PATCH v4 5/9] mshv: add SEV-SNP isolated page hypercalls Wei Hu
2026-08-31 11:53 ` sashiko-bot
2026-08-31 11:26 ` [PATCH v4 6/9] mshv: wire SEV-SNP partition ioctls Wei Hu
2026-08-31 12:07 ` sashiko-bot
2026-08-31 11:26 ` [PATCH v4 7/9] mshv: detect and report SEV-SNP support at init Wei Hu
2026-08-31 11:26 ` [PATCH v4 8/9] mshv: use safe partition CPU feature defaults Wei Hu
2026-08-31 11:26 ` [PATCH v4 9/9] mshv: set up own SynIC registers on a nested root partition Wei Hu
2026-08-31 12:09 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox