* [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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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
` (9 more replies)
7 siblings, 10 replies; 40+ 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] 40+ 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
` (8 subsequent siblings)
9 siblings, 1 reply; 40+ 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] 40+ 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
` (7 subsequent siblings)
9 siblings, 0 replies; 40+ 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] 40+ 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
` (6 subsequent siblings)
9 siblings, 0 replies; 40+ 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] 40+ 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
` (5 subsequent siblings)
9 siblings, 0 replies; 40+ 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] 40+ 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
` (4 subsequent siblings)
9 siblings, 1 reply; 40+ 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] 40+ 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
` (3 subsequent siblings)
9 siblings, 1 reply; 40+ 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] 40+ 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
` (2 subsequent siblings)
9 siblings, 0 replies; 40+ 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] 40+ 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
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
9 siblings, 0 replies; 40+ 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] 40+ 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
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
9 siblings, 1 reply; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ 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; 40+ 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] 40+ messages in thread
* [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions
2026-08-31 11:26 ` [PATCH v4 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (8 preceding siblings ...)
2026-08-31 11:26 ` [PATCH v4 9/9] mshv: set up own SynIC registers on a nested root partition Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:13 ` [PATCH v5 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
` (8 more replies)
9 siblings, 9 replies; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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 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.
Testing:
- Built all four affected x86 MSHV objects with W=1 at every commit.
- Built the complete x86_64 kernel and modules with W=1.
- Built the affected ARM64 objects with W=1.
- Ran scripts/checkpatch.pl --strict on every patch.
- Verified installed UAPI layouts in 64-bit and 32-bit userspace builds.
- Generated rust-vmm MSHV bindings from the installed kernel headers.
- Passed all 8 KUnit host-access tests on a separate test-only branch.
- Passed the corrected single-CVM runtime test.
- 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, the KUnit follow-up, nor any runtime-only commit is part of this
nine-patch series.
Changes since v4:
- Separate faults_blocked admission state from mapping_may_exist proof in
patch 1. The interval notifier no longer takes the fair rwsem, always
publishes its interval sequence after taking the region mutex, and a
movable fault drops all attempt-local locks before retry. This removes
the v4 ABBA cycle while preserving checked-unmap proof requirements.
- Correct patch 5's commit message to describe the isolated-page import
and import-completion wrappers actually added by that patch.
- Replace patch 6's region-wide host-access boolean with per-page READ,
WRITE, EXCLUSIVE, and UNCERTAIN state and exact completed-prefix updates.
Recovery restores only the known completed prefix; teardown restores
only nonbaseline state and retains uncertain pages.
- Make MAKE_EXCLUSIVE and MAKE_SHARED explicit ownership transitions.
Permission-only operations preserve ownership, and the child partition
ID is supplied only for MAKE_EXCLUSIVE. Support arbitrary cross-region
arrays, reject duplicate GPA/PFN entries and pinned aliases, and restore
PSP request pages to their exact original state.
- The first v5 runtime iteration exposed an incorrect forced MAKE_SHARED
and child partition_id model. The corrected wire/state matrix passed the
single-CVM runtime test and a four-vCPU guest boot to login.
- Leave two findings that predate this series unchanged and explicitly out
of scope: MMIO VMA/PFN validation and the blockable notifier remap-failure
contract. The reported L1VH local-SIRBP path is also pre-existing and is
unreachable for the hardware-CVM partition type exercised here.
Link: https://lore.kernel.org/linux-hyperv/20260831112704.2851147-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 | 510 ++++++++++---
drivers/hv/mshv_root.h | 120 ++-
drivers/hv/mshv_root_hv_call.c | 369 ++++++++--
drivers/hv/mshv_root_main.c | 1267 ++++++++++++++++++++++++++++++--
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, 2452 insertions(+), 305 deletions(-)
base-commit: be0cfab740e58b70047ef6e7e3d578f00ed5d258
--
2.43.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v5 1/9] mshv: retain memory regions until unmap succeeds
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:33 ` sashiko-bot
2026-09-08 12:13 ` [PATCH v5 2/9] mshv: clear SynIC mappings before freeing them Wei Hu
` (7 subsequent siblings)
8 siblings, 1 reply; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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 movable regions, use the fault rwsem only as a GPA-fault admission
and notifier-lifetime gate. The MMU interval notifier serializes directly
with the region mutex, publishes its sequence after locking, and skips a
NO_ACCESS remap only after a checked unmap proves no mapping remains.
Explicit unmap and partition teardown block and drain faults with the
write side, then take the region mutex around the checked hypervisor
unmap. Restore list visibility and fault admission on failure. Remove the
interval notifier only after dropping both locks.
Signed-off-by: Wei Hu <weh@microsoft.com>
---
drivers/hv/mshv_regions.c | 180 +++++++++++++++++----------------
drivers/hv/mshv_root.h | 14 ++-
drivers/hv/mshv_root_hv_call.c | 81 ++++++++++-----
drivers/hv/mshv_root_main.c | 115 +++++++++++++++++++--
4 files changed, 268 insertions(+), 122 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 6d65e5b42152..7b1860d3233d 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_fault_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);
}
@@ -382,54 +394,18 @@ int mshv_region_get(struct mshv_mem_region *region)
}
/**
- * mshv_region_hmm_fault_and_lock - Handle HMM faults and lock the memory region
- * @region: Pointer to the memory region structure
- * @range: Pointer to the HMM range structure
+ * mshv_region_range_fault - Fault and map pages for a movable region.
+ * @region: Memory region receiving the GPA fault.
+ * @page_offset: First page in the region.
+ * @page_count: Number of pages to fault.
*
- * 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.
- *
- * Return: 0 on success, a negative error code otherwise.
- */
-static int mshv_region_hmm_fault_and_lock(struct mshv_mem_region *region,
- struct hmm_range *range)
-{
- 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;
-
- mutex_lock(®ion->mreg_mutex);
-
- if (mmu_interval_read_retry(range->notifier, range->notifier_seq)) {
- mutex_unlock(®ion->mreg_mutex);
- cond_resched();
- return -EBUSY;
- }
-
- return 0;
-}
-
-/**
- * mshv_region_range_fault - Handle memory range faults for a given region.
- * @region: Pointer to the memory region structure.
- * @page_offset: Offset of the page within the region.
- * @page_count: Number of pages to handle.
+ * mreg_fault_lock is only an admission and lifetime gate. A teardown writer
+ * sets faults_blocked before taking the write side, which prevents new faults
+ * and drains faults that can still remap the region. On an HMM retry, drop the
+ * gate and mmap_lock before trying again so a waiting writer cannot create a
+ * fairness cycle. The notifier never takes mreg_fault_lock.
*
- * This function resolves memory faults for a specified range of pages
- * within a memory region. It uses HMM (Heterogeneous Memory Management)
- * to fault in the required pages and updates the region's page array.
+ * Lock order for one attempt is mreg_fault_lock -> mmap_lock -> mreg_mutex.
*
* Return: 0 on success, negative error code on failure.
*/
@@ -440,6 +416,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,21 +429,44 @@ 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;
- do {
- ret = mshv_region_hmm_fault_and_lock(region, &range);
- } while (ret == -EBUSY);
+retry:
+ down_read(®ion->mreg_fault_lock);
+ if (READ_ONCE(region->faults_blocked) ||
+ !mmget_not_zero(region->mreg_mni.mm)) {
+ ret = -ECANCELED;
+ goto out_unlock_faults;
+ }
+ mm = region->mreg_mni.mm;
+ mmap_read_lock(mm);
+ range.notifier_seq = mmu_interval_read_begin(range.notifier);
+ ret = hmm_range_fault(&range);
if (ret)
- goto out;
+ goto out_unlock_mmap;
+
+ mutex_lock(®ion->mreg_mutex);
+ if (mmu_interval_read_retry(range.notifier, range.notifier_seq)) {
+ mutex_unlock(®ion->mreg_mutex);
+ ret = -EBUSY;
+ goto out_unlock_mmap;
+ }
for (i = 0; i < page_count; i++)
region->mreg_pages[page_offset + i] = hmm_pfn_to_page(pfns[i]);
ret = mshv_region_remap_pages(region, region->hv_map_flags,
page_offset, page_count);
-
mutex_unlock(®ion->mreg_mutex);
-out:
+
+out_unlock_mmap:
+ mmap_read_unlock(mm);
+ mmput(mm);
+out_unlock_faults:
+ up_read(®ion->mreg_fault_lock);
+ if (ret == -EBUSY) {
+ cond_resched();
+ goto retry;
+ }
kfree(pfns);
return ret;
}
@@ -486,10 +486,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;
@@ -531,6 +532,12 @@ static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
page_offset = HVPFN_DOWN(mstart - region->start_uaddr);
page_count = HVPFN_DOWN(mend - mstart);
+ /*
+ * mmap_lock is already held by the notifier core. Take only the region
+ * mutex here; taking the fair fault rwsem would deadlock behind an unmap
+ * writer that is waiting for this mutex. Publish the sequence after the
+ * mutex is acquired even when a checked unmap proved no mapping remains.
+ */
if (mmu_notifier_range_blockable(range))
mutex_lock(®ion->mreg_mutex);
else if (!mutex_trylock(®ion->mreg_mutex))
@@ -538,15 +545,15 @@ static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
mmu_interval_set_seq(mni, cur_seq);
- ret = mshv_region_remap_pages(region, HV_MAP_GPA_NO_ACCESS,
- page_offset, page_count);
- if (ret)
- goto out_unlock;
+ if (READ_ONCE(region->mapping_may_exist)) {
+ ret = mshv_region_remap_pages(region, HV_MAP_GPA_NO_ACCESS,
+ page_offset, page_count);
+ if (ret)
+ goto out_unlock;
+ }
mshv_region_invalidate_pages(region, page_offset, page_count);
-
mutex_unlock(®ion->mreg_mutex);
-
return true;
out_unlock:
@@ -567,7 +574,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 +592,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..d0770d36defb 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_fault_lock;
+ bool faults_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..bb6f16fac4de 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,64 @@ 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->faults_blocked, true);
+ hlist_del_init(®ion->hnode);
spin_unlock(&partition->pt_mem_regions_lock);
- mshv_region_put(region);
+ /* Drain admitted faults, then serialize against the notifier remap. */
+ down_write(®ion->mreg_fault_lock);
+ mutex_lock(®ion->mreg_mutex);
+ ret = mshv_region_unmap(region);
+ if (ret)
+ goto restore_region;
+
+ mutex_unlock(®ion->mreg_mutex);
+ up_write(®ion->mreg_fault_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->faults_blocked, false);
+ spin_unlock(&partition->pt_mem_regions_lock);
+ mutex_unlock(®ion->mreg_mutex);
+ up_write(®ion->mreg_fault_lock);
+ mshv_region_put(region);
+ return ret;
+
}
static long
@@ -1658,6 +1702,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 +1737,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 +1845,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 +1874,39 @@ 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->faults_blocked);
+ WRITE_ONCE(region->faults_blocked, true);
+ spin_unlock(&partition->pt_mem_regions_lock);
+
+ down_write(®ion->mreg_fault_lock);
+ mutex_lock(®ion->mreg_mutex);
+ unmap_ret = mshv_region_unmap(region);
+ if (unmap_ret) {
+ spin_lock(&partition->pt_mem_regions_lock);
+ WRITE_ONCE(region->faults_blocked, was_blocked);
+ spin_unlock(&partition->pt_mem_regions_lock);
+ mutex_unlock(®ion->mreg_mutex);
+ up_write(®ion->mreg_fault_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;
+ }
+ mutex_unlock(®ion->mreg_mutex);
+ up_write(®ion->mreg_fault_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 +1967,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 +1976,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] 40+ messages in thread
* [PATCH v5 2/9] mshv: clear SynIC mappings before freeing them
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-09-08 12:13 ` [PATCH v5 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:13 ` [PATCH v5 3/9] mshv: add SEV-SNP UAPI definitions Wei Hu
` (6 subsequent siblings)
8 siblings, 0 replies; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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] 40+ messages in thread
* [PATCH v5 3/9] mshv: add SEV-SNP UAPI definitions
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-09-08 12:13 ` [PATCH v5 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
2026-09-08 12:13 ` [PATCH v5 2/9] mshv: clear SynIC mappings before freeing them Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:13 ` [PATCH v5 4/9] mshv: add SEV-SNP PSP request hypercall Wei Hu
` (5 subsequent siblings)
8 siblings, 0 replies; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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 bb6f16fac4de..0a35c73a6349 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2057,7 +2057,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;
@@ -2070,7 +2070,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] 40+ messages in thread
* [PATCH v5 4/9] mshv: add SEV-SNP PSP request hypercall
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (2 preceding siblings ...)
2026-09-08 12:13 ` [PATCH v5 3/9] mshv: add SEV-SNP UAPI definitions Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:13 ` [PATCH v5 5/9] mshv: add SEV-SNP isolated page hypercalls Wei Hu
` (4 subsequent siblings)
8 siblings, 0 replies; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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 d0770d36defb..fdee3629eb0a 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] 40+ messages in thread
* [PATCH v5 5/9] mshv: add SEV-SNP isolated page hypercalls
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (3 preceding siblings ...)
2026-09-08 12:13 ` [PATCH v5 4/9] mshv: add SEV-SNP PSP request hypercall Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:13 ` [PATCH v5 6/9] mshv: wire SEV-SNP partition ioctls Wei Hu
` (3 subsequent siblings)
8 siblings, 0 replies; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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 and completing an isolated
partition import. 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 fdee3629eb0a..78508b57b5ad 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] 40+ messages in thread
* [PATCH v5 6/9] mshv: wire SEV-SNP partition ioctls
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (4 preceding siblings ...)
2026-09-08 12:13 ` [PATCH v5 5/9] mshv: add SEV-SNP isolated page hypercalls Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:29 ` sashiko-bot
2026-09-08 12:13 ` [PATCH v5 7/9] mshv: detect and report SEV-SNP support at init Wei Hu
` (2 subsequent siblings)
8 siblings, 1 reply; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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.
Track host READ/WRITE permissions and shared/exclusive ownership for
every pinned 4K page with compact bitmaps. The initial baseline is host
RW/shared; completed setup releases become host-none/exclusive. Track an
uncertain state and a nonbaseline count so teardown restores only known
changed pages and unpins only after every page is known baseline.
Keep the ACQUIRE/RELEASE opcode independent from ownership. Preserve
caller flags, supply the child partition ID only for MAKE_EXCLUSIVE, and
use partition ID zero for ACQUIRE+MAKE_SHARED and permission-only calls.
State transitions derive READ/WRITE from host_access; explicit
MAKE_EXCLUSIVE or MAKE_SHARED changes ownership, while no ownership flag
preserves the current state.
Report only the trusted completed prefix in 4K-page units, including 2M
conversion. Update state only for that prefix and quarantine impossible
completion counts. Reject duplicate request GPAs, duplicate backing
PFNs, and pinned PFN aliases across regions. Validate 2M GPA grouping,
alignment, and physical contiguity across region boundaries.
On setup failure, restore only the pages whose explicit exclusive release
completed. Generic MODIFY changes permissions while preserving
ownership. PSP requests preserve each page's ownership, save exact
original permissions, transition only pages needing host access removed,
and restore successful transitions with ACQUIRE or RELEASE selected from
the permission delta.
Keep result normalization, current-state transition application, 2M
completion conversion, duplicate detection, and teardown phase
sequencing in small internal helpers. A follow-up KUnit object can inject
completed/error/uncertain results and mock teardown callbacks without
replacing production hypercall code.
For initialized and uninitialized SNP partitions alike, unmap every child
mapping, destroy initialized isolation state, then restore host access.
Exclusive pages use ACQUIRE+RW+MAKE_SHARED; shared pages with reduced
permissions use ACQUIRE+RW without an ownership flag. Group restoration
only across pages with identical tracked state.
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 | 332 ++++++++++-
drivers/hv/mshv_root.h | 86 ++-
drivers/hv/mshv_root_hv_call.c | 137 ++++-
drivers/hv/mshv_root_main.c | 977 ++++++++++++++++++++++++++++++---
include/hyperv/hvgdk_mini.h | 12 +
5 files changed, 1432 insertions(+), 112 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 7b1860d3233d..e0564097f441 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -7,11 +7,13 @@
* Authors: Microsoft Linux virtualization team
*/
+#include <linux/bitmap.h>
#include <linux/hmm.h>
#include <linux/hyperv.h>
#include <linux/kref.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
+#include <linux/xarray.h>
#include <asm/mshyperv.h>
@@ -194,51 +196,327 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
return region;
}
-static int mshv_region_chunk_share(struct mshv_mem_region *region,
- u32 flags,
- u64 page_offset, u64 page_count,
- bool huge_page)
+int mshv_region_host_access_init(struct mshv_mem_region *region)
+{
+ unsigned int nr_pages;
+ unsigned int longs;
+ size_t bytes;
+
+ if (region->nr_pages > UINT_MAX)
+ return -E2BIG;
+
+ nr_pages = region->nr_pages;
+ longs = BITS_TO_LONGS(nr_pages);
+ bytes = array_size(longs, sizeof(unsigned long));
+ region->host_access_read = kvcalloc(4, bytes, GFP_KERNEL);
+ if (!region->host_access_read)
+ return -ENOMEM;
+
+ region->host_access_write = region->host_access_read + longs;
+ region->host_access_exclusive = region->host_access_write + longs;
+ region->host_access_uncertain =
+ region->host_access_exclusive + longs;
+ bitmap_fill(region->host_access_read, nr_pages);
+ bitmap_fill(region->host_access_write, nr_pages);
+ return 0;
+}
+
+int mshv_region_check_duplicate_pfns(struct mshv_mem_region *region)
+{
+ struct mshv_partition *partition = region->partition;
+ struct mshv_mem_region *existing;
+ DEFINE_XARRAY(pfns);
+ u64 i;
+ int ret = 0;
+
+ lockdep_assert_held(&partition->pt_mutex);
+
+ /*
+ * Use a temporary index so alias rejection has no permanent per-page
+ * memory cost. xa_insert() also rejects duplicate PFNs within region.
+ */
+ for (i = 0; i < region->nr_pages; i++) {
+ ret = xa_insert(&pfns, page_to_pfn(region->mreg_pages[i]),
+ region, GFP_KERNEL);
+ if (ret) {
+ if (ret == -EBUSY)
+ ret = -EEXIST;
+ goto out;
+ }
+ }
+
+ hlist_for_each_entry(existing, &partition->pt_mem_regions, hnode) {
+ if (existing->mreg_type != MSHV_REGION_TYPE_MEM_PINNED)
+ continue;
+ for (i = 0; i < existing->nr_pages; i++) {
+ if (!xa_load(&pfns,
+ page_to_pfn(existing->mreg_pages[i])))
+ continue;
+ ret = -EEXIST;
+ goto out;
+ }
+ }
+
+out:
+ xa_destroy(&pfns);
+ return ret;
+}
+
+static bool mshv_host_access_state_is_baseline(u8 state)
+{
+ return state == MSHV_HOST_ACCESS_BASELINE;
+}
+
+u8 mshv_region_host_access_get(const struct mshv_mem_region *region, u64 index)
+{
+ u8 state = 0;
+
+ if (WARN_ON_ONCE(!region->host_access_read ||
+ index >= region->nr_pages))
+ return MSHV_HOST_ACCESS_UNCERTAIN;
+
+ if (test_bit(index, region->host_access_read))
+ state |= MSHV_HOST_ACCESS_READ;
+ if (test_bit(index, region->host_access_write))
+ state |= MSHV_HOST_ACCESS_WRITE;
+ if (test_bit(index, region->host_access_exclusive))
+ state |= MSHV_HOST_ACCESS_EXCLUSIVE;
+ if (test_bit(index, region->host_access_uncertain))
+ state |= MSHV_HOST_ACCESS_UNCERTAIN;
+ return state;
+}
+
+void mshv_region_host_access_set(struct mshv_mem_region *region, u64 index,
+ u8 state)
+{
+ u8 old = mshv_region_host_access_get(region, index);
+
+ if (mshv_host_access_state_is_baseline(old) &&
+ !mshv_host_access_state_is_baseline(state))
+ region->host_nonbaseline_pages++;
+ else if (!mshv_host_access_state_is_baseline(old) &&
+ mshv_host_access_state_is_baseline(state))
+ region->host_nonbaseline_pages--;
+
+ assign_bit(index, region->host_access_read,
+ state & MSHV_HOST_ACCESS_READ);
+ assign_bit(index, region->host_access_write,
+ state & MSHV_HOST_ACCESS_WRITE);
+ assign_bit(index, region->host_access_exclusive,
+ state & MSHV_HOST_ACCESS_EXCLUSIVE);
+ assign_bit(index, region->host_access_uncertain,
+ state & MSHV_HOST_ACCESS_UNCERTAIN);
+}
+
+static void
+mshv_region_host_access_mark_uncertain(struct mshv_mem_region *region,
+ u64 index, u64 count)
+{
+ u64 i;
+
+ for (i = 0; i < count; i++) {
+ u8 state = mshv_region_host_access_get(region, index + i);
+
+ mshv_region_host_access_set(region, index + i, state | MSHV_HOST_ACCESS_UNCERTAIN);
+ }
+}
+
+static int
+mshv_host_access_normalize_result(struct mshv_host_access_result *result,
+ u64 count)
+{
+ if (result->completed > count) {
+ result->completed = 0;
+ result->uncertain = true;
+ result->error = -EPROTO;
+ } else if (!result->error && result->completed != count) {
+ result->uncertain = true;
+ result->error = -EPROTO;
+ }
+
+ return result->error;
+}
+
+u8 mshv_make_host_access_state(u8 old_state, u32 host_access, u32 flags)
+{
+ /* Permissions are replaced; only explicit flags change ownership. */
+ u8 state = old_state & (MSHV_HOST_ACCESS_EXCLUSIVE |
+ MSHV_HOST_ACCESS_UNCERTAIN);
+
+ if (host_access & HV_MAP_GPA_READABLE)
+ state |= MSHV_HOST_ACCESS_READ;
+ if (host_access & HV_MAP_GPA_WRITABLE)
+ state |= MSHV_HOST_ACCESS_WRITE;
+
+ if (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE) {
+ state |= MSHV_HOST_ACCESS_EXCLUSIVE;
+ state &= ~MSHV_HOST_ACCESS_UNCERTAIN;
+ } else if (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED) {
+ state &= ~(MSHV_HOST_ACCESS_EXCLUSIVE |
+ MSHV_HOST_ACCESS_UNCERTAIN);
+ }
+
+ return state;
+}
+
+int
+mshv_apply_region_host_access(struct mshv_mem_region *region, u64 index,
+ u64 count, u32 host_access, u32 flags,
+ struct mshv_host_access_result *result)
+{
+ u64 i;
+
+ mshv_host_access_normalize_result(result, count);
+ for (i = 0; i < result->completed; i++) {
+ u8 old_state = mshv_region_host_access_get(region, index + i);
+ u8 state = mshv_make_host_access_state(old_state, host_access,
+ flags);
+
+ mshv_region_host_access_set(region, index + i, state);
+ }
+ if (result->uncertain) {
+ u64 remaining = count - result->completed;
+ u64 uncertain_index = index + result->completed;
+
+ mshv_region_host_access_mark_uncertain(region, uncertain_index,
+ remaining);
+ }
+
+ return result->error;
+}
+
+static u8 mshv_host_access_page_state(const struct mshv_host_access_page *page)
+{
+ return mshv_region_host_access_get(page->region, page->index);
+}
+
+static void mshv_host_access_page_set(struct mshv_host_access_page *page,
+ u8 state)
+{
+ mshv_region_host_access_set(page->region, page->index, state);
+}
+
+int
+mshv_apply_host_access(struct mshv_host_access_page *host_pages,
+ u64 count, u32 host_access, u32 flags,
+ struct mshv_host_access_result *result)
+{
+ u64 i;
+
+ mshv_host_access_normalize_result(result, count);
+ for (i = 0; i < result->completed; i++) {
+ struct mshv_host_access_page *host_page = &host_pages[i];
+ u8 old_state = mshv_host_access_page_state(host_page);
+ u8 state = mshv_make_host_access_state(old_state, host_access,
+ flags);
+
+ mshv_host_access_page_set(host_page, state);
+ }
+ if (result->uncertain) {
+ for (i = result->completed; i < count; i++) {
+ struct mshv_host_access_page *host_page = &host_pages[i];
+ u8 old = mshv_host_access_page_state(host_page);
+
+ mshv_host_access_page_set(host_page,
+ old | MSHV_HOST_ACCESS_UNCERTAIN);
+ }
+ }
+
+ return result->error;
+}
+
+bool mshv_region_host_access_is_baseline(const struct mshv_mem_region *region)
+{
+ return !region->host_access_read || !region->host_nonbaseline_pages;
+}
+
+static int
+mshv_region_modify_host_access(struct mshv_mem_region *region, u32 flags,
+ u64 page_offset, u64 page_count, bool huge_page,
+ u32 host_access, bool acquire)
{
+ struct mshv_host_access_result result;
+
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,
+ mshv_modify_host_access(region->partition->pt_id,
+ region->mreg_pages + page_offset, page_count,
+ host_access, flags, acquire, &result);
+
+ /*
+ * completed is always a trusted count in 4K pages, including for 2M
+ * entries. The result helper is intentionally hypercall-independent so
+ * KUnit can inject partial and impossible completions.
+ */
+ return mshv_apply_region_host_access(region, page_offset, page_count,
+ host_access, flags, &result);
+}
+
+static int mshv_region_chunk_share(struct mshv_mem_region *region,
+ u32 flags, u64 page_offset,
+ u64 page_count, bool huge_page)
+{
+ return mshv_region_modify_host_access(region, flags, page_offset,
+ page_count, huge_page,
HV_MAP_GPA_READABLE |
- HV_MAP_GPA_WRITABLE,
- flags, true);
+ HV_MAP_GPA_WRITABLE, true);
}
int mshv_region_share(struct mshv_mem_region *region)
{
- u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED;
+ u64 offset = 0;
+ int state_error = 0;
+ int ret;
+
+ while (offset < region->nr_pages) {
+ u64 count;
+ u32 flags;
+ u8 state = mshv_region_host_access_get(region, offset);
- return mshv_region_process_range(region, flags,
- 0, region->nr_pages,
- mshv_region_chunk_share);
+ if (state & MSHV_HOST_ACCESS_UNCERTAIN) {
+ state_error = -EUCLEAN;
+ offset++;
+ continue;
+ }
+ if (mshv_host_access_state_is_baseline(state)) {
+ offset++;
+ continue;
+ }
+
+ flags = state & MSHV_HOST_ACCESS_EXCLUSIVE ?
+ HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED : 0;
+ /* One call may cover only pages with identical restore state. */
+ for (count = 1; offset + count < region->nr_pages; count++) {
+ u8 next = mshv_region_host_access_get(region,
+ offset + count);
+
+ if (next != state)
+ break;
+ }
+
+ ret = mshv_region_process_range(region, flags, offset, count,
+ mshv_region_chunk_share);
+ if (ret)
+ return ret;
+ offset += count;
+ }
+ return state_error;
}
static int mshv_region_chunk_unshare(struct mshv_mem_region *region,
- u32 flags,
- u64 page_offset, u64 page_count,
- bool huge_page)
+ u32 flags, u64 page_offset,
+ u64 page_count, bool huge_page)
{
- 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);
+ return mshv_region_modify_host_access(region, flags, page_offset,
+ page_count, huge_page, 0, false);
}
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,12 +651,14 @@ 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 ||
+ !mshv_region_host_access_is_baseline(region)))
return;
if (region->mreg_type == MSHV_REGION_TYPE_MEM_MOVABLE)
mshv_region_movable_fini(region);
+ kvfree(region->host_access_read);
mshv_region_invalidate(region);
vfree(region);
}
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 78508b57b5ad..d447229dc2f3 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -91,6 +91,15 @@ struct mshv_mem_region {
u32 hv_map_flags;
/* True until a checked hypervisor unmap has completed. */
bool mapping_may_exist;
+ /*
+ * SNP host access is tracked per 4K page. The four bitmaps are one
+ * contiguous allocation rooted at host_access_read.
+ */
+ unsigned long *host_access_read;
+ unsigned long *host_access_write;
+ unsigned long *host_access_exclusive;
+ unsigned long *host_access_uncertain;
+ u64 host_nonbaseline_pages;
struct mshv_partition *partition;
enum mshv_region_type mreg_type;
struct mmu_interval_notifier mreg_mni;
@@ -101,6 +110,42 @@ struct mshv_mem_region {
struct page *mreg_pages[];
};
+enum mshv_host_access_state {
+ MSHV_HOST_ACCESS_READ = BIT(0),
+ MSHV_HOST_ACCESS_WRITE = BIT(1),
+ MSHV_HOST_ACCESS_EXCLUSIVE = BIT(2),
+ MSHV_HOST_ACCESS_UNCERTAIN = BIT(3),
+};
+
+#define MSHV_HOST_ACCESS_BASELINE \
+ (MSHV_HOST_ACCESS_READ | MSHV_HOST_ACCESS_WRITE)
+
+#define MSHV_USER_PAGE_BATCH_SIZE 4096
+
+struct mshv_host_access_page {
+ struct page *page;
+ struct mshv_mem_region *region;
+ u64 index;
+};
+
+struct mshv_host_access_result {
+ u64 completed;
+ int error;
+ bool uncertain;
+};
+
+enum mshv_snp_teardown_phase {
+ MSHV_SNP_TEARDOWN_UNMAP,
+ MSHV_SNP_TEARDOWN_ISOLATION,
+ MSHV_SNP_TEARDOWN_RESTORE,
+};
+
+struct mshv_snp_teardown_ops {
+ int (*unmap)(void *data);
+ int (*isolation)(void *data);
+ int (*restore)(void *data);
+};
+
struct mshv_irq_ack_notifier {
struct hlist_node link;
unsigned int irq_ack_gsi;
@@ -164,6 +209,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 +389,17 @@ 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,
+ bool *completion_uncertain,
+ u32 host_access, u32 flags, bool acquire);
+void mshv_modify_host_access(u64 partition_id, struct page **pages,
+ u64 page_count, u32 host_access, u32 flags,
+ bool acquire, struct mshv_host_access_result *result);
+int mshv_validate_host_access_flags(u32 flags, bool acquire);
+u64 mshv_host_access_partition_id(u64 partition_id, u32 flags);
+int mshv_repcomp_to_page_count(u64 repcomp, u64 rep_count,
+ unsigned int page_shift,
+ u64 *completed_pages);
int hv_call_get_partition_property_ex(u64 partition_id, u64 property_code, u64 arg,
void *property_value, size_t property_value_sz);
@@ -376,6 +436,23 @@ extern u8 * __percpu *hv_synic_eventring_tail;
struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
u64 uaddr, u32 flags);
+int mshv_region_host_access_init(struct mshv_mem_region *region);
+int mshv_region_check_duplicate_pfns(struct mshv_mem_region *region);
+u8 mshv_region_host_access_get(const struct mshv_mem_region *region, u64 index);
+void mshv_region_host_access_set(struct mshv_mem_region *region, u64 index,
+ u8 state);
+u8 mshv_make_host_access_state(u8 old_state, u32 host_access, u32 flags);
+int mshv_apply_region_host_access(struct mshv_mem_region *region,
+ u64 index, u64 count, u32 host_access,
+ u32 flags,
+ struct mshv_host_access_result *result);
+int mshv_apply_host_access(struct mshv_host_access_page *host_pages,
+ u64 count, u32 host_access, u32 flags,
+ struct mshv_host_access_result *result);
+bool mshv_region_host_access_is_baseline(const struct mshv_mem_region *region);
+bool mshv_u64_array_has_duplicates(u64 *values, u64 count);
+int mshv_run_snp_teardown(const struct mshv_snp_teardown_ops *ops, void *data,
+ enum mshv_snp_teardown_phase *failed_phase);
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);
@@ -388,6 +465,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..c2a629bdef33 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,
@@ -1191,20 +1227,55 @@ int hv_call_issue_psp_guest_request(u64 partition_id, u64 req_pfn,
}
#endif
+int mshv_validate_host_access_flags(u32 flags, bool acquire)
+{
+ if ((flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE) &&
+ (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED))
+ return -EINVAL;
+ if ((acquire &&
+ (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE)) ||
+ (!acquire &&
+ (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED)))
+ return -EINVAL;
+ return 0;
+}
+
+u64 mshv_host_access_partition_id(u64 partition_id, u32 flags)
+{
+ return flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE ?
+ partition_id : 0;
+}
+
+int mshv_repcomp_to_page_count(u64 repcomp, u64 rep_count,
+ unsigned int page_shift,
+ u64 *completed_pages)
+{
+ if (repcomp > rep_count)
+ return -EPROTO;
+ if (check_shl_overflow(repcomp, page_shift, completed_pages))
+ return -EOVERFLOW;
+ return 0;
+}
+
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,
+ bool *completion_uncertain,
+ u32 host_access, u32 flags, bool acquire)
{
struct hv_input_modify_sparse_spa_page_host_access *input_page;
u64 status;
- int done = 0;
+ u64 done = 0;
+ u64 completed_4k;
unsigned long irq_flags, large_shift = 0;
+ int ret;
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 || !completed_pages || !completion_uncertain)
return -EINVAL;
+ *completed_pages = 0;
+ *completion_uncertain = false;
if (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE) {
if (!HV_PAGE_COUNT_2M_ALIGNED(page_count))
@@ -1213,31 +1284,34 @@ int hv_call_modify_spa_host_access(u64 partition_id, struct page **pages,
page_count >>= large_shift;
}
+ ret = mshv_validate_host_access_flags(flags, acquire);
+ if (ret)
+ return ret;
+
while (done < page_count) {
- ulong i, completed, remain = page_count - done;
- int rep_count = min(remain,
- HV_MODIFY_SPARSE_SPA_PAGE_HOST_ACCESS_MAX_PAGE_COUNT);
+ 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);
input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
-
memset(input_page, 0, sizeof(*input_page));
- /* Only set the partition id if you are making the pages
- * exclusive
- */
+ /* ACQUIRE and permission-only RELEASE require partition_id zero. */
if (flags & HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE)
- input_page->partition_id = partition_id;
+ input_page->partition_id =
+ mshv_host_access_partition_id(partition_id, flags);
input_page->flags = flags;
input_page->host_access = host_access;
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]);
+ page_to_pfn(pages[index]);
}
status = hv_do_rep_hypercall(code, rep_count, 0, input_page,
@@ -1245,12 +1319,39 @@ 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);
+ ret = mshv_repcomp_to_page_count(completed, rep_count, large_shift, &completed_4k);
+ if (ret) {
+ *completion_uncertain = true;
+ pr_err_ratelimited("%s: invalid completion count %llu/%u\n",
+ __func__, completed, rep_count);
+ return ret;
+ }
done += completed;
+ *completed_pages += completed_4k;
+ 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) {
+ *completion_uncertain = true;
+ return -EPROTO;
+ }
}
return 0;
}
+
+void mshv_modify_host_access(u64 partition_id, struct page **pages,
+ u64 page_count, u32 host_access, u32 flags,
+ bool acquire, struct mshv_host_access_result *result)
+{
+ result->error = hv_call_modify_spa_host_access(partition_id, pages,
+ page_count,
+ &result->completed,
+ &result->uncertain,
+ host_access, flags, acquire);
+}
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 0a35c73a6349..42eb81b25709 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -20,6 +20,7 @@
#include <linux/io.h>
#include <linux/cpuhotplug.h>
#include <linux/random.h>
+#include <linux/sort.h>
#include <asm/mshyperv.h>
#include <linux/hyperv.h>
#include <linux/notifier.h>
@@ -642,6 +643,68 @@ 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_host_pages(struct mshv_partition *partition, const u64 *gpfns,
+ u64 page_count,
+ struct mshv_host_access_page *host_pages)
+{
+ struct mshv_mem_region *region;
+ u64 i;
+ int ret = 0;
+
+ for (i = 0; i < page_count; i++) {
+ u64 offset;
+
+ region = mshv_partition_region_by_gfn_get(partition, gpfns[i]);
+ if (!region) {
+ pt_err_ratelimited(partition,
+ "Failed to find region for GFN %#llx\n",
+ gpfns[i]);
+ ret = -ERANGE;
+ goto put_pages;
+ }
+
+ offset = gpfns[i] - region->start_gfn;
+ mutex_lock(®ion->mreg_mutex);
+ if (region->mreg_type != MSHV_REGION_TYPE_MEM_PINNED ||
+ !region->host_access_read || offset >= region->nr_pages ||
+ !region->mreg_pages[offset]) {
+ ret = -EFAULT;
+ mutex_unlock(®ion->mreg_mutex);
+ mshv_region_put(region);
+ goto put_pages;
+ }
+
+ host_pages[i].page = region->mreg_pages[offset];
+ host_pages[i].region = region;
+ host_pages[i].index = offset;
+ mutex_unlock(®ion->mreg_mutex);
+ }
+ return 0;
+
+put_pages:
+ while (i--)
+ mshv_region_put(host_pages[i].region);
+ return ret;
+}
+
+static void mshv_host_pages_put(struct mshv_host_access_page *host_pages,
+ u64 page_count)
+{
+ u64 i;
+
+ for (i = 0; i < page_count; i++)
+ mshv_region_put(host_pages[i].region);
+}
+
+static u8 mshv_host_page_state(const struct mshv_host_access_page *host_page)
+{
+ return mshv_region_host_access_get(host_page->region, host_page->index);
+}
+
+#endif
+
/**
* mshv_handle_gpa_intercept - Handle GPA (Guest Physical Address) intercepts.
* @vp: Pointer to the virtual processor structure.
@@ -1214,12 +1277,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 +1355,74 @@ 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_host_access_init(region);
+ if (ret)
+ goto invalidate_region;
+
+ ret = mshv_region_check_duplicate_pfns(region);
+ if (ret) {
+ if (ret == -EEXIST)
+ pt_err_ratelimited(partition,
+ "Duplicate pinned PFN\n");
+ else
+ pt_err_ratelimited(partition,
+ "Pinned PFN audit failed: %d\n",
+ ret);
+ goto invalidate_region;
+ }
+
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 ||
+ !mshv_region_host_access_is_baseline(region)) {
+ pt_err(partition,
+ "Failed to recover exact 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) &&
+ !mshv_region_host_access_is_baseline(region)) {
+ int recovery_ret = mshv_region_share(region);
- return 0;
+ if (recovery_ret ||
+ !mshv_region_host_access_is_baseline(region)) {
+ pt_err(partition,
+ "Failed to recover exact host access after map failure at GFN %#llx: %d; retaining region\n",
+ region->start_gfn, recovery_ret);
+ return ret;
+ }
+ }
invalidate_region:
+ kvfree(region->host_access_read);
+ region->host_access_read = NULL;
+ region->host_access_write = NULL;
+ region->host_access_exclusive = NULL;
+ region->host_access_uncertain = NULL;
mshv_region_invalidate(region);
-err_out:
return ret;
}
@@ -1424,12 +1505,13 @@ mshv_map_user_memory(struct mshv_partition *partition,
return 0;
errout:
- if (region->mapping_may_exist) {
+ if (region->mapping_may_exist ||
+ !mshv_region_host_access_is_baseline(region)) {
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 +1530,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
@@ -1480,6 +1564,17 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
if (ret)
goto restore_region;
+ if (!mshv_region_host_access_is_baseline(region)) {
+ ret = mshv_region_share(region);
+ if (ret || !mshv_region_host_access_is_baseline(region)) {
+ 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;
+ }
+ }
+
mutex_unlock(®ion->mreg_mutex);
up_write(®ion->mreg_fault_lock);
@@ -1502,6 +1597,15 @@ 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);
+ mutex_unlock(®ion->mreg_mutex);
+ up_write(®ion->mreg_fault_lock);
+ mshv_region_put(region);
+ return ret;
}
static long
@@ -1692,6 +1796,582 @@ 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 int mshv_u64_cmp(const void *lhs, const void *rhs)
+{
+ u64 a = *(const u64 *)lhs;
+ u64 b = *(const u64 *)rhs;
+
+ return (a > b) - (a < b);
+}
+
+static bool
+mshv_host_pages_are_valid_2m(const u64 *gpfns,
+ const struct mshv_host_access_page *host_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(host_pages[i].page);
+
+ if (!IS_ALIGNED(gpfns[i], pages_per_large_page) ||
+ !IS_ALIGNED(base_pfn, pages_per_large_page))
+ return false;
+ for (j = 1; j < pages_per_large_page; j++)
+ if (gpfns[i + j] != gpfns[i] + j ||
+ page_to_pfn(host_pages[i + j].page) != base_pfn + j)
+ return false;
+ }
+ return true;
+}
+
+bool mshv_u64_array_has_duplicates(u64 *values, u64 count)
+{
+ u64 i;
+
+ sort(values, count, sizeof(*values), mshv_u64_cmp, NULL);
+ for (i = 1; i < count; i++)
+ if (values[i - 1] == values[i])
+ return true;
+ return false;
+}
+
+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 mshv_host_access_page *host_pages = NULL;
+ struct page **pages = NULL;
+ u64 __user *guest_gpas;
+ u64 *duplicates = NULL;
+ u64 *gpfns = NULL;
+ u64 input_completed;
+ u64 offset = 0;
+ u32 host_access = 0;
+ u32 flags = 0;
+ bool acquire;
+ long ret = 0;
+ u64 i;
+
+ 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;
+ }
+
+ guest_gpas = u64_to_user_ptr(args.guest_gpas);
+ gpfns = vmemdup_array_user(guest_gpas, args.page_count,
+ sizeof(*gpfns));
+ if (IS_ERR(gpfns)) {
+ ret = PTR_ERR(gpfns);
+ gpfns = NULL;
+ goto out;
+ }
+ host_pages = kvmalloc_array(args.page_count, sizeof(*host_pages),
+ GFP_KERNEL);
+ duplicates = kvmalloc_array(args.page_count, sizeof(*duplicates),
+ GFP_KERNEL);
+ pages = kvmalloc_array(MSHV_USER_PAGE_BATCH_SIZE, sizeof(*pages),
+ GFP_KERNEL);
+ if (!host_pages || !duplicates || !pages) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < args.page_count; i++) {
+ if (!IS_ALIGNED(gpfns[i], MSHV_HV_PAGE_SIZE)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ gpfns[i] = HVPFN_DOWN(gpfns[i]);
+ duplicates[i] = gpfns[i];
+ }
+ if (mshv_u64_array_has_duplicates(duplicates, args.page_count)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = mshv_gpfns_to_host_pages(partition, gpfns, args.page_count,
+ host_pages);
+ if (ret)
+ goto out;
+
+ for (i = 0; i < args.page_count; i++)
+ duplicates[i] = page_to_pfn(host_pages[i].page);
+ if (mshv_u64_array_has_duplicates(duplicates, args.page_count)) {
+ ret = -EINVAL;
+ goto out_put_pages;
+ }
+
+ if (args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_LARGE_PAGE)) {
+ if (!mshv_host_pages_are_valid_2m(gpfns, host_pages,
+ args.page_count)) {
+ ret = -EINVAL;
+ goto out_put_pages;
+ }
+ flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
+ }
+ 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;
+ acquire = args.flags & BIT(MSHV_GPA_HOST_ACCESS_BIT_ACQUIRE);
+
+ while (offset < args.page_count) {
+ struct mshv_host_access_result result = {};
+ u64 count = min_t(u64, args.page_count - offset,
+ MSHV_USER_PAGE_BATCH_SIZE);
+
+ for (i = 0; i < count; i++)
+ pages[i] = host_pages[offset + i].page;
+
+ mshv_modify_host_access(partition->pt_id, pages, count,
+ host_access, flags, acquire, &result);
+ ret = mshv_apply_host_access(host_pages + offset, count,
+ host_access, flags, &result);
+ if (result.uncertain)
+ mshv_quarantine_partition(partition);
+ offset += result.completed;
+ if (ret)
+ break;
+ cond_resched();
+ }
+
+out_put_pages:
+ mshv_host_pages_put(host_pages, args.page_count);
+out:
+ args.completed = offset;
+ if (copy_to_user(user_args, &args, sizeof(args))) {
+ if (offset)
+ mshv_quarantine_partition(partition);
+ ret = -EFAULT;
+ }
+ kvfree(pages);
+ kvfree(duplicates);
+ kvfree(host_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 u32 mshv_host_access_to_hv(u8 state)
+{
+ u32 access = 0;
+
+ if (state & MSHV_HOST_ACCESS_READ)
+ access |= HV_MAP_GPA_READABLE;
+ if (state & MSHV_HOST_ACCESS_WRITE)
+ access |= HV_MAP_GPA_WRITABLE;
+ return access;
+}
+
+static int
+mshv_restore_psp_pages(struct mshv_partition *partition,
+ struct mshv_host_access_page *host_pages,
+ const u8 *original, const u8 *transitioned,
+ u64 transitioned_count)
+{
+ int first_error = 0;
+ u64 i;
+
+ for (i = 0; i < transitioned_count; i++) {
+ struct mshv_host_access_page *host_page =
+ &host_pages[transitioned[i]];
+ struct mshv_host_access_result result = {};
+ struct page *page = host_page->page;
+ u32 current_access =
+ mshv_host_access_to_hv(mshv_host_page_state(host_page));
+ u32 original_access =
+ mshv_host_access_to_hv(original[transitioned[i]]);
+ bool acquire;
+ int ret;
+
+ if (current_access == original_access)
+ continue;
+ /* Restore permissions without changing the saved ownership. */
+ acquire = !!(original_access & ~current_access);
+
+ mshv_modify_host_access(partition->pt_id, &page, 1,
+ original_access, 0, acquire, &result);
+ ret = mshv_apply_host_access(host_page, 1, original_access, 0,
+ &result);
+ if (ret && !first_error)
+ first_error = ret;
+ }
+ return first_error;
+}
+
+static long
+mshv_partition_ioctl_issue_psp_guest_request(struct mshv_partition *partition,
+ void __user *user_args)
+{
+ struct mshv_issue_psp_guest_request req;
+ struct mshv_host_access_page host_pages[2];
+ struct page *transition_pages[2];
+ u8 transitioned[2];
+ u8 original[2];
+ struct mshv_host_access_result result = {};
+ u64 transitioned_count = 0;
+ u64 gpfns[2];
+ long ret;
+ u64 i;
+
+ 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) ||
+ req.req_gpa == req.rsp_gpa) {
+ pt_err_ratelimited(partition,
+ "Invalid 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_host_pages(partition, gpfns,
+ ARRAY_SIZE(gpfns), host_pages);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(host_pages); i++) {
+ struct mshv_host_access_page *host_page = &host_pages[i];
+
+ original[i] = mshv_host_page_state(host_page);
+ if (original[i] & MSHV_HOST_ACCESS_UNCERTAIN) {
+ ret = -EIO;
+ mshv_quarantine_partition(partition);
+ goto out_put_pages;
+ }
+ if (!(original[i] &
+ (MSHV_HOST_ACCESS_READ | MSHV_HOST_ACCESS_WRITE)))
+ continue;
+ transitioned[transitioned_count] = i;
+ transition_pages[transitioned_count] = host_pages[i].page;
+ transitioned_count++;
+ }
+
+ if (transitioned_count) {
+ struct mshv_host_access_page transition_host_pages[2];
+
+ for (i = 0; i < transitioned_count; i++)
+ transition_host_pages[i] = host_pages[transitioned[i]];
+
+ mshv_modify_host_access(partition->pt_id, transition_pages,
+ transitioned_count, 0, 0, false, &result);
+ ret = mshv_apply_host_access(transition_host_pages,
+ transitioned_count, 0, 0, &result);
+ if (result.uncertain)
+ mshv_quarantine_partition(partition);
+ transitioned_count = result.completed;
+ 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, gpfns[0],
+ gpfns[1],
+ mshv_async_hvcall_handler,
+ partition);
+ if (!ret)
+ goto out_put_pages;
+
+restore_host_access:
+ {
+ int restore_ret;
+
+ restore_ret = mshv_restore_psp_pages(partition, host_pages,
+ original, transitioned,
+ transitioned_count);
+ if (restore_ret) {
+ pt_err(partition,
+ "Failed to restore exact PSP page access: %d\n",
+ restore_ret);
+ mshv_quarantine_partition(partition);
+ if (!ret)
+ ret = restore_ret;
+ }
+ }
+out_put_pages:
+ mshv_host_pages_put(host_pages, ARRAY_SIZE(host_pages));
+ 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)
{
@@ -1733,6 +2413,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;
}
@@ -1845,38 +2534,106 @@ 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;
}
-/*
- * Tear down a partition and remove it from the list.
- * Partition's refcount must be 0
- */
-static void destroy_partition(struct mshv_partition *partition)
+int mshv_run_snp_teardown(const struct mshv_snp_teardown_ops *ops, void *data,
+ enum mshv_snp_teardown_phase *failed_phase)
{
- struct mshv_vp *vp;
- struct mshv_mem_region *region;
- struct hlist_node *n;
- int i;
+ int ret;
- if (refcount_read(&partition->pt_ref_count)) {
- pt_err(partition,
- "Attempt to destroy partition but refcount > 0\n");
- return;
- }
+ *failed_phase = MSHV_SNP_TEARDOWN_UNMAP;
+ ret = ops->unmap(data);
+ if (ret)
+ return ret;
- trace_mshv_destroy_partition(partition->pt_id);
+ *failed_phase = MSHV_SNP_TEARDOWN_ISOLATION;
+ ret = ops->isolation(data);
+ if (ret)
+ return ret;
+
+ *failed_phase = MSHV_SNP_TEARDOWN_RESTORE;
+ return ops->restore(data);
+}
+
+static int mshv_partition_unmap_regions(void *data)
+{
+ struct mshv_partition *partition = data;
+ struct mshv_mem_region *region;
hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) {
bool was_blocked;
- int unmap_ret;
+ int ret;
/* Stop new lookup users before draining in-flight remappers. */
spin_lock(&partition->pt_mem_regions_lock);
@@ -1886,19 +2643,17 @@ static void destroy_partition(struct mshv_partition *partition)
down_write(®ion->mreg_fault_lock);
mutex_lock(®ion->mreg_mutex);
- unmap_ret = mshv_region_unmap(region);
- if (unmap_ret) {
+ ret = mshv_region_unmap(region);
+ if (ret) {
spin_lock(&partition->pt_mem_regions_lock);
WRITE_ONCE(region->faults_blocked, was_blocked);
spin_unlock(&partition->pt_mem_regions_lock);
mutex_unlock(®ion->mreg_mutex);
up_write(®ion->mreg_fault_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;
+ "Failed to unmap region at GFN %#llx: %d\n",
+ region->start_gfn, ret);
+ return ret;
}
mutex_unlock(®ion->mreg_mutex);
up_write(®ion->mreg_fault_lock);
@@ -1907,6 +2662,92 @@ static void destroy_partition(struct mshv_partition *partition)
mshv_region_movable_fini(region);
}
+ return 0;
+}
+
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+static int mshv_partition_destroy_isolation(void *data)
+{
+ struct mshv_partition *partition = data;
+
+ if (!partition->pt_initialized)
+ return 0;
+ return destroy_snp_partition_state(partition);
+}
+
+static int mshv_partition_restore_host_access(void *data)
+{
+ struct mshv_partition *partition = data;
+ struct mshv_mem_region *region;
+ int ret;
+
+ hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) {
+ if (mshv_region_host_access_is_baseline(region))
+ continue;
+ ret = mshv_region_share(region);
+ if (ret || !mshv_region_host_access_is_baseline(region)) {
+ pt_err(partition,
+ "Failed to restore host access at GFN %#llx: %d\n",
+ region->start_gfn, ret);
+ return ret ?: -EUCLEAN;
+ }
+ }
+ return 0;
+}
+
+static const struct mshv_snp_teardown_ops mshv_snp_teardown_ops = {
+ .unmap = mshv_partition_unmap_regions,
+ .isolation = mshv_partition_destroy_isolation,
+ .restore = mshv_partition_restore_host_access,
+};
+#endif
+
+/*
+ * Tear down a partition and remove it from the list.
+ * Partition's refcount must be 0
+ */
+static void destroy_partition(struct mshv_partition *partition)
+{
+ struct mshv_vp *vp;
+ struct mshv_mem_region *region;
+ struct hlist_node *n;
+ bool memory_torn_down = false;
+ int ret;
+ int i;
+
+ if (refcount_read(&partition->pt_ref_count)) {
+ pt_err(partition,
+ "Attempt to destroy partition but refcount > 0\n");
+ return;
+ }
+
+ trace_mshv_destroy_partition(partition->pt_id);
+
+#ifdef HV_SUPPORTS_SEV_SNP_GUESTS
+ if (mshv_partition_encrypted(partition)) {
+ enum mshv_snp_teardown_phase failed_phase;
+
+ ret = mshv_run_snp_teardown(&mshv_snp_teardown_ops, partition,
+ &failed_phase);
+ if (ret) {
+ if (failed_phase == MSHV_SNP_TEARDOWN_ISOLATION)
+ pt_err(partition,
+ "Failed to destroy SNP isolation: %d\n",
+ ret);
+ mshv_quarantine_partition(partition);
+ return;
+ }
+ memory_torn_down = true;
+ }
+#endif
+ if (!memory_torn_down) {
+ ret = mshv_partition_unmap_regions(partition);
+ if (ret) {
+ mshv_quarantine_partition(partition);
+ return;
+ }
+ }
+
if (partition->pt_initialized) {
/*
* We only need to drain signals for root scheduler. This should be
@@ -1915,6 +2756,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];
@@ -1961,10 +2804,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);
@@ -2057,7 +2900,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;
@@ -2070,7 +2913,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 ||
@@ -2134,6 +2977,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;
@@ -2169,7 +3015,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] 40+ messages in thread
* [PATCH v5 7/9] mshv: detect and report SEV-SNP support at init
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (5 preceding siblings ...)
2026-09-08 12:13 ` [PATCH v5 6/9] mshv: wire SEV-SNP partition ioctls Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:28 ` sashiko-bot
2026-09-08 12:13 ` [PATCH v5 8/9] mshv: use safe partition CPU feature defaults Wei Hu
2026-09-08 12:13 ` [PATCH v5 9/9] mshv: set up own SynIC registers on a nested root partition Wei Hu
8 siblings, 1 reply; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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 42eb81b25709..bf43f1f1e6da 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -101,6 +101,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",
@@ -3086,7 +3088,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
@@ -3232,6 +3235,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)
{
@@ -3335,6 +3436,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;
@@ -3351,6 +3454,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;
@@ -3367,6 +3472,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] 40+ messages in thread
* [PATCH v5 8/9] mshv: use safe partition CPU feature defaults
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (6 preceding siblings ...)
2026-09-08 12:13 ` [PATCH v5 7/9] mshv: detect and report SEV-SNP support at init Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
2026-09-08 12:13 ` [PATCH v5 9/9] mshv: set up own SynIC registers on a nested root partition Wei Hu
8 siblings, 0 replies; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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 bf43f1f1e6da..03ac4c86f343 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2905,33 +2905,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)
@@ -2946,16 +3037,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] 40+ messages in thread
* [PATCH v5 9/9] mshv: set up own SynIC registers on a nested root partition
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
` (7 preceding siblings ...)
2026-09-08 12:13 ` [PATCH v5 8/9] mshv: use safe partition CPU feature defaults Wei Hu
@ 2026-09-08 12:13 ` Wei Hu
8 siblings, 0 replies; 40+ messages in thread
From: Wei Hu @ 2026-09-08 12:13 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] 40+ messages in thread
* Re: [PATCH v5 7/9] mshv: detect and report SEV-SNP support at init
2026-09-08 12:13 ` [PATCH v5 7/9] mshv: detect and report SEV-SNP support at init Wei Hu
@ 2026-09-08 12:28 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-09-08 12:28 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The patch uses the compiler-defined `__x86_64__` macro instead of the kernel standard `CONFIG_X86_64`.
--
commit 25a294afba0df1fc3823f90fe382591fefe4ae1f
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 ensures the misc device rejects opens
until the partition hash table and interrupt handling are fully ready.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index 42eb81b257092..bf43f1f1e6da5 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> @@ -3232,6 +3235,104 @@ static int mshv_root_scheduler_cleanup(unsigned int cpu)
> }
>
> /* Must be called after retrieving the scheduler type */
> +#if defined(__x86_64__)
[Severity: Low]
Should this use the kernel standard CONFIG_X86_64 instead of the
compiler-defined __x86_64__ macro? Using the compiler macro circumvents
kernel configuration management and creates an inconsistency with the rest
of the file which relies on standard Kconfig macros.
> +static const char *hv_snp_status_to_string(enum hv_snp_status status)
> +{
> + switch (status) {
> + case HV_SNP_STATUS_NONE:
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908121403.1160280-1-weh@linux.microsoft.com?part=7
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v5 6/9] mshv: wire SEV-SNP partition ioctls
2026-09-08 12:13 ` [PATCH v5 6/9] mshv: wire SEV-SNP partition ioctls Wei Hu
@ 2026-09-08 12:29 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-09-08 12:29 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] Missing host access restoration on successful PSP guest request permanently revokes host permissions.
- [High] Unbounded user-controlled array allocations and non-preemptible loops cause CPU soft lockups.
--
commit ff3cccc86d268ae551d99e559a8b63d138066868
Author: Wei Liu <wei.liu@kernel.org>
Subject: mshv: wire SEV-SNP partition ioctls
This commit wires the SEV-SNP UAPI into partition creation, memory ownership,
and isolated-page import. It adds tracking for host access permissions and
handles transitions to release/acquire host access.
> 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
> @@ -643,6 +643,68 @@ 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_host_pages(struct mshv_partition *partition, const u64 *gpfns,
> + u64 page_count,
> + struct mshv_host_access_page *host_pages)
> +{
> + struct mshv_mem_region *region;
> + u64 i;
> + int ret = 0;
> +
> + for (i = 0; i < page_count; i++) {
> + u64 offset;
> +
> + region = mshv_partition_region_by_gfn_get(partition, gpfns[i]);
[Severity: High]
Does this code allow an unbounded, user-controlled loop without yielding?
If a user provides an extremely large args.page_count in
mshv_partition_ioctl_modify_gpa_host_access(), this loop will repeatedly
acquire and release region mutexes for the entire array. Without a
cond_resched(), this could cause a CPU soft lockup.
[ ... ]
> +bool mshv_u64_array_has_duplicates(u64 *values, u64 count)
> +{
> + u64 i;
> +
> + sort(values, count, sizeof(*values), mshv_u64_cmp, NULL);
[Severity: High]
Can this sort() call cause a CPU soft lockup?
Since count originates from the user-controlled args.page_count without
an upper bound, sort() (which is non-preemptible) might block the CPU
for an O(N log N) duration on a massive array.
> + for (i = 1; i < count; i++)
> + if (values[i - 1] == values[i])
> + return true;
> + return false;
> +}
[ ... ]
> +static long
> +mshv_partition_ioctl_modify_gpa_host_access(struct mshv_partition *partition,
> + void __user *user_args)
> +{
[ ... ]
> + guest_gpas = u64_to_user_ptr(args.guest_gpas);
> + gpfns = vmemdup_array_user(guest_gpas, args.page_count,
> + sizeof(*gpfns));
> + if (IS_ERR(gpfns)) {
> + ret = PTR_ERR(gpfns);
> + gpfns = NULL;
> + goto out;
> + }
> + host_pages = kvmalloc_array(args.page_count, sizeof(*host_pages),
> + GFP_KERNEL);
> + duplicates = kvmalloc_array(args.page_count, sizeof(*duplicates),
> + GFP_KERNEL);
[Severity: High]
Are these allocations sufficiently bounded to prevent kernel memory exhaustion?
Since args.page_count is controlled by userspace, allocating multiple arrays
that scale with this count could lead to severe memory pressure or a denial of
service.
[ ... ]
> +static long
> +mshv_partition_ioctl_issue_psp_guest_request(struct mshv_partition *partition,
> + void __user *user_args)
> +{
[ ... ]
> + ret = hv_call_issue_psp_guest_request(partition->pt_id, gpfns[0],
> + gpfns[1],
> + mshv_async_hvcall_handler,
> + partition);
> + if (!ret)
> + goto out_put_pages;
> +
> +restore_host_access:
[Severity: High]
Does a successful hypercall skip host access restoration?
If hv_call_issue_psp_guest_request() succeeds and ret is 0, the code
jumps to out_put_pages, bypassing the restore_host_access block.
This leaves the host permissions permanently revoked for the request and
response pages, which could break functionality or leak memory.
> + {
> + int restore_ret;
> +
> + restore_ret = mshv_restore_psp_pages(partition, host_pages,
> + original, transitioned,
> + transitioned_count);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908121403.1160280-1-weh@linux.microsoft.com?part=6
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH v5 1/9] mshv: retain memory regions until unmap succeeds
2026-09-08 12:13 ` [PATCH v5 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
@ 2026-09-08 12:33 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-09-08 12:33 UTC (permalink / raw)
To: Wei Hu; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [High] Partial mapping failure for pinned memory causes permanent VM quarantine and memory leak without attempting rollback.
- [Medium] Data race on `region->mapping_may_exist` between the initialization mapping hypercall and the MMU interval notifier.
Pre-existing issues:
- [Critical] Missing verification of contiguous physical pages when mapping 2M chunks to the hypervisor, allowing guests to bypass COW and modify shared huge folios.
- [High] The mmu_interval_notifier invalidate callback returns false on a blockable failure, violating the API contract and causing host use-after-free.
- [High] GFP_KERNEL allocation inside MMU interval notifier invalidate callback causes reclaim deadlocks and atomic context sleep violations.
- [High] mshv_region_destroy calls unpin_user_pages on a NULL array after error paths have already invalidated the region, causing a kernel panic.
- [High] mshv_map_user_memory computes mmio_pfn incorrectly when userspace_addr is not at the VMA start, mapping the wrong physical memory into the guest.
--
commit ae5e74dcd02edb43f5a42fb59ce2354d9c3d2b17
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..7b1860d3233d3 100644
> --- a/drivers/hv/mshv_regions.c
> +++ b/drivers/hv/mshv_regions.c
[ ... ]
> @@ -245,13 +247,21 @@ static int mshv_region_chunk_remap(struct mshv_mem_region *region,
> u64 page_offset, u64 page_count,
> bool huge_page)
> {
[Severity: Critical]
This is a pre-existing issue, but I have a question about the huge page path
here. When mapping 2M chunks to the hypervisor, is there any verification
that the intermediate physical pages are contiguous and belong to the same
folio?
In mshv_chunk_stride() and mshv_region_process_chunk(), the code seems to
check only the first page and increments by a stride of 512. If a huge folio
mapped into the host has pages broken by COW (replaced by private anonymous
pages), the hypervisor might still be instructed to map the entire 2M
contiguous physical page. Could this allow a guest to bypass COW and overwrite
shared huge folio memory belonging to other processes?
> + 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;
> }
[ ... ]
> @@ -337,37 +347,39 @@ static int mshv_region_chunk_unmap(struct mshv_mem_region *region,
[ ... ]
> 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);
[Severity: High]
This is a pre-existing issue, but I am concerned about calling
mshv_region_invalidate() here unconditionally.
On error during mshv_prepare_pinned_region(), the code calls
mshv_region_invalidate_pages(), which unpins the pages and zero-initializes
the mreg_pages array. The error then propagates to mshv_map_user_memory()
which drops the refcount and triggers this mshv_region_destroy().
Because mshv_region_destroy() calls mshv_region_invalidate() again, it will
pass the now-NULL mreg_pages array elements to unpin_user_pages().
Will this result in a kernel panic when unpin_user_pages() dereferences the
NULL page pointers?
> -
> vfree(region);
> }
[ ... ]
> @@ -531,6 +532,12 @@ static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
> page_offset = HVPFN_DOWN(mstart - region->start_uaddr);
> page_count = HVPFN_DOWN(mend - mstart);
>
> + /*
> + * mmap_lock is already held by the notifier core. Take only the region
> + * mutex here; taking the fair fault rwsem would deadlock behind an unmap
> + * writer that is waiting for this mutex. Publish the sequence after the
> + * mutex is acquired even when a checked unmap proved no mapping remains.
> + */
> if (mmu_notifier_range_blockable(range))
> mutex_lock(®ion->mreg_mutex);
> else if (!mutex_trylock(®ion->mreg_mutex))
> @@ -538,15 +545,15 @@ static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
>
> mmu_interval_set_seq(mni, cur_seq);
>
> - ret = mshv_region_remap_pages(region, HV_MAP_GPA_NO_ACCESS,
> - page_offset, page_count);
> - if (ret)
> - goto out_unlock;
> + if (READ_ONCE(region->mapping_may_exist)) {
> + ret = mshv_region_remap_pages(region, HV_MAP_GPA_NO_ACCESS,
> + page_offset, page_count);
> + if (ret)
> + goto out_unlock;
> + }
[Severity: High]
This is a pre-existing issue, but I am worried about memory allocations here.
mshv_region_interval_invalidate() is invoked by memory reclaim or atomic
contexts. It calls mshv_region_remap_pages() which triggers a hypercall.
If the hypervisor requires memory, it calls hv_call_deposit_pages(), which
allocates memory using alloc_page(GFP_KERNEL).
Does using sleepable allocations (GFP_KERNEL) from reclaim context cause
deadlocks, or from atomic context trigger a sleeping-in-atomic bug?
[Severity: Medium]
Also, is it safe to use READ_ONCE(region->mapping_may_exist) here when
the writer in hv_do_map_gpa_hcall() uses a plain assignment
(*mapping_may_exist = true)?
Since mmu_interval_notifier_insert() could be called before the hypercall,
mshv_region_interval_invalidate() might run concurrently. Under the C memory
model, this data race could lead to missed invalidations if the compiler
reorders or tears the write.
>
> mshv_region_invalidate_pages(region, page_offset, page_count);
> -
> mutex_unlock(®ion->mreg_mutex);
> -
> return true;
>
> out_unlock:
[Severity: High]
This isn't a bug introduced by this patch, but returning false on a
blockable failure here violates the mmu_interval_notifier API contract.
The core MM subsystem strictly forbids returning false when
mmu_notifier_range_blockable(range) is true. Doing so causes a WARN_ON and
ignores the failure, which means the host proceeds to free or reuse the page
while the hypervisor retains guest access.
Could this lead to a cross-process use-after-free reachable under memory
pressure or untrusted guest action?
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index cc2cfce2aefdb..bb6f16fac4de1 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);
[Severity: High]
This is a pre-existing issue, but I wanted to ask about the mmio_pfn
computation earlier in this function:
mmio_pfn = is_mmio ? vma->vm_pgoff : 0;
Does this completely ignore the page offset of the requested userspace_addr
within the VMA? If a user requests a mapping for a sub-region of the VMA,
would the guest incorrectly be mapped to the very beginning of the VMA's
physical address space instead of the requested offset?
> 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);
> + }
[Severity: High]
If a later chunk fails to map during mshv_prepare_pinned_region(), the
earlier chunks remain mapped, leaving region->mapping_may_exist as true.
When the error propagates back here and mapping_may_exist is true, the
driver immediately calls mshv_quarantine_partition(), permanently leaking
the partition and module.
Should this code attempt to call mshv_region_unmap() on the error path to
clean up the partially mapped chunks before resorting to quarantine, to
prevent a permanent DoS via resource exhaustion?
> return ret;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908121403.1160280-1-weh@linux.microsoft.com?part=1
^ permalink raw reply [flat|nested] 40+ messages in thread
end of thread, other threads:[~2026-09-08 12:33 UTC | newest]
Thread overview: 40+ 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
2026-09-08 12:13 ` [PATCH v5 0/9] mshv: add SEV-SNP support for MSHV root partitions Wei Hu
2026-09-08 12:13 ` [PATCH v5 1/9] mshv: retain memory regions until unmap succeeds Wei Hu
2026-09-08 12:33 ` sashiko-bot
2026-09-08 12:13 ` [PATCH v5 2/9] mshv: clear SynIC mappings before freeing them Wei Hu
2026-09-08 12:13 ` [PATCH v5 3/9] mshv: add SEV-SNP UAPI definitions Wei Hu
2026-09-08 12:13 ` [PATCH v5 4/9] mshv: add SEV-SNP PSP request hypercall Wei Hu
2026-09-08 12:13 ` [PATCH v5 5/9] mshv: add SEV-SNP isolated page hypercalls Wei Hu
2026-09-08 12:13 ` [PATCH v5 6/9] mshv: wire SEV-SNP partition ioctls Wei Hu
2026-09-08 12:29 ` sashiko-bot
2026-09-08 12:13 ` [PATCH v5 7/9] mshv: detect and report SEV-SNP support at init Wei Hu
2026-09-08 12:28 ` sashiko-bot
2026-09-08 12:13 ` [PATCH v5 8/9] mshv: use safe partition CPU feature defaults Wei Hu
2026-09-08 12:13 ` [PATCH v5 9/9] mshv: set up own SynIC registers on a nested root partition Wei Hu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox