[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251125175753.1428857-4-prsampat@amd.com>
Date: Tue, 25 Nov 2025 11:57:52 -0600
From: "Pratik R. Sampat" <prsampat@....com>
To: <linux-mm@...ck.org>, <linux-coco@...ts.linux.dev>,
<linux-efi@...r.kernel.org>, <x86@...nel.org>, <linux-kernel@...r.kernel.org>
CC: <tglx@...utronix.de>, <mingo@...hat.com>, <bp@...en8.de>,
<dave.hansen@...ux.intel.com>, <kas@...nel.org>, <ardb@...nel.org>,
<akpm@...ux-foundation.org>, <david@...hat.com>, <osalvador@...e.de>,
<thomas.lendacky@....com>, <michael.roth@....com>, <prsampat@....com>
Subject: [RFC PATCH 3/4] x86/sev: Introduce hotplug-aware SNP page state validation
When hot-removing memory in a SEV-SNP environment, pages must be set to
shared state so they can be reused by the hypervisor. This also applies
when memory is intended to be hotplugged back in later, as those pages
will need to be re-accepted after crossing the trust boundary.
However, memory can already be set to shared state externally. In such
cases, the pvalidate rescind operation will not change the validated bit
in the RMP table, setting the carry flag and causing the guest to
terminate.
Since memory hotplug is arguably unique, introduce a guest-maintained
memory state tracking structure that maintains a bitmap to track the
state (private vs shared) of all hotplugged memory supplemented with a
flag to indicate intent. This allows for memory that is already marked
as shared in the hotplug bitmap to avoid performing the pvalidate
rescind operation. Additionally, tracking page state changes from the
guest's perspective, enables the detection of inconsistencies if the
hypervisor changes states unexpectedly. For example, if the guest bitmap
reports memory as private but the hypervisor has already changed the RMP
state to shared, the guest detects this inconsistency when attempting to
share the memory and terminate rather than skipping over the pvalidate
rescind operation.
Signed-off-by: Pratik R. Sampat <prsampat@....com>
---
arch/x86/coco/sev/core.c | 104 +++++++++++++++++++++--
arch/x86/include/asm/sev.h | 32 +++++++
arch/x86/include/asm/unaccepted_memory.h | 13 +++
drivers/firmware/efi/unaccepted_memory.c | 2 +-
4 files changed, 143 insertions(+), 8 deletions(-)
diff --git a/arch/x86/coco/sev/core.c b/arch/x86/coco/sev/core.c
index 14ef5908fb27..a5c9615a6e0c 100644
--- a/arch/x86/coco/sev/core.c
+++ b/arch/x86/coco/sev/core.c
@@ -46,6 +46,8 @@
#include <asm/cmdline.h>
#include <asm/msr.h>
+struct snp_hotplug_memory *snp_hp_mem;
+
/* AP INIT values as documented in the APM2 section "Processor Initialization State" */
#define AP_INIT_CS_LIMIT 0xffff
#define AP_INIT_DS_LIMIT 0xffff
@@ -453,9 +455,54 @@ static int vmgexit_psc(struct ghcb *ghcb, struct snp_psc_desc *desc)
return ret;
}
+static bool snp_hotplug_state_shared(unsigned long vaddr)
+{
+ phys_addr_t paddr = __pa(vaddr);
+ u64 hotplug_bit;
+
+ if (!snp_is_hotplug_memory(paddr))
+ return false;
+
+ hotplug_bit = (paddr - snp_hp_mem->phys_base) / snp_hp_mem->unit_size;
+
+ return !test_bit(hotplug_bit, snp_hp_mem->bitmap);
+}
+
+static void snp_set_hotplug_bit(unsigned long vaddr, bool private)
+{
+ phys_addr_t paddr = __pa(vaddr);
+ u64 hotplug_bit;
+
+ if (!snp_is_hotplug_memory(paddr))
+ return;
+
+ hotplug_bit = (paddr - snp_hp_mem->phys_base) / snp_hp_mem->unit_size;
+ if (private)
+ set_bit(hotplug_bit, snp_hp_mem->bitmap);
+ else
+ clear_bit(hotplug_bit, snp_hp_mem->bitmap);
+}
+
+static void set_hotplug_pages_state(struct snp_psc_desc *desc)
+{
+ struct psc_entry *e;
+ unsigned long vaddr;
+ bool op;
+ int i;
+
+ for (i = 0; i <= desc->hdr.end_entry; i++) {
+ e = &desc->entries[i];
+ vaddr = (unsigned long)pfn_to_kaddr(e->gfn);
+ op = e->operation == SNP_PAGE_STATE_PRIVATE;
+
+ snp_set_hotplug_bit(vaddr, op);
+ }
+}
+
static unsigned long __set_pages_state(struct snp_psc_desc *data, unsigned long vaddr,
- unsigned long vaddr_end, int op)
+ unsigned long vaddr_end, int op, u8 psc_flags)
{
+ unsigned long vaddr_base;
struct ghcb_state state;
bool use_large_entry;
struct psc_hdr *hdr;
@@ -465,6 +512,7 @@ static unsigned long __set_pages_state(struct snp_psc_desc *data, unsigned long
struct ghcb *ghcb;
int i;
+ vaddr_base = vaddr;
hdr = &data->hdr;
e = data->entries;
@@ -499,7 +547,8 @@ static unsigned long __set_pages_state(struct snp_psc_desc *data, unsigned long
}
/* Page validation must be rescinded before changing to shared */
- if (op == SNP_PAGE_STATE_SHARED)
+ if (op == SNP_PAGE_STATE_SHARED &&
+ !(snp_hotplug_state_shared(vaddr_base) && (psc_flags & SNP_PSC_SHARED_TO_SHARED)))
pvalidate_pages(data);
local_irq_save(flags);
@@ -522,10 +571,12 @@ static unsigned long __set_pages_state(struct snp_psc_desc *data, unsigned long
if (op == SNP_PAGE_STATE_PRIVATE)
pvalidate_pages(data);
+ set_hotplug_pages_state(data);
+
return vaddr;
}
-static void set_pages_state(unsigned long vaddr, unsigned long npages, int op)
+static void set_pages_state(unsigned long vaddr, unsigned long npages, int op, u8 psc_flags)
{
struct snp_psc_desc desc;
unsigned long vaddr_end;
@@ -538,7 +589,7 @@ static void set_pages_state(unsigned long vaddr, unsigned long npages, int op)
vaddr_end = vaddr + (npages << PAGE_SHIFT);
while (vaddr < vaddr_end)
- vaddr = __set_pages_state(&desc, vaddr, vaddr_end, op);
+ vaddr = __set_pages_state(&desc, vaddr, vaddr_end, op, psc_flags);
}
void snp_set_memory_shared(unsigned long vaddr, unsigned long npages)
@@ -546,7 +597,7 @@ void snp_set_memory_shared(unsigned long vaddr, unsigned long npages)
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
return;
- set_pages_state(vaddr, npages, SNP_PAGE_STATE_SHARED);
+ set_pages_state(vaddr, npages, SNP_PAGE_STATE_SHARED, 0);
}
void snp_set_memory_private(unsigned long vaddr, unsigned long npages)
@@ -554,7 +605,7 @@ void snp_set_memory_private(unsigned long vaddr, unsigned long npages)
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
return;
- set_pages_state(vaddr, npages, SNP_PAGE_STATE_PRIVATE);
+ set_pages_state(vaddr, npages, SNP_PAGE_STATE_PRIVATE, 0);
}
void snp_accept_memory(phys_addr_t start, phys_addr_t end)
@@ -567,7 +618,46 @@ void snp_accept_memory(phys_addr_t start, phys_addr_t end)
vaddr = (unsigned long)__va(start);
npages = (end - start) >> PAGE_SHIFT;
- set_pages_state(vaddr, npages, SNP_PAGE_STATE_PRIVATE);
+ set_pages_state(vaddr, npages, SNP_PAGE_STATE_PRIVATE, 0);
+}
+
+int snp_extend_hotplug_memory_state_bitmap(phys_addr_t start,
+ unsigned long size,
+ uint64_t unit_size)
+{
+ u64 hp_mem_size = DIV_ROUND_UP(size, unit_size * BITS_PER_BYTE);
+
+ if (snp_hp_mem) {
+ u64 old_size = snp_hp_mem->size;
+ unsigned long *bitmap;
+
+ bitmap = krealloc(snp_hp_mem->bitmap, hp_mem_size, GFP_KERNEL);
+ if (!bitmap)
+ return -ENOMEM;
+
+ memset(bitmap + old_size, 0, hp_mem_size - old_size);
+ snp_hp_mem->size = hp_mem_size;
+ snp_hp_mem->bitmap = bitmap;
+
+ return 0;
+ }
+
+ snp_hp_mem = kzalloc(sizeof(*snp_hp_mem), GFP_KERNEL);
+ if (!snp_hp_mem)
+ return -ENOMEM;
+
+ snp_hp_mem->bitmap = kzalloc(hp_mem_size, GFP_KERNEL);
+ if (!snp_hp_mem->bitmap) {
+ kfree(snp_hp_mem);
+ return -ENOMEM;
+ }
+
+ snp_hp_mem->phys_base = start;
+ snp_hp_mem->phys_end = start + hp_mem_size;
+ snp_hp_mem->size = hp_mem_size;
+ snp_hp_mem->unit_size = unit_size;
+
+ return 0;
}
static int vmgexit_ap_control(u64 event, struct sev_es_save_area *vmsa, u32 apic_id)
diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h
index 465b19fd1a2d..eb605892645c 100644
--- a/arch/x86/include/asm/sev.h
+++ b/arch/x86/include/asm/sev.h
@@ -464,6 +464,38 @@ static __always_inline void sev_es_nmi_complete(void)
extern int __init sev_es_efi_map_ghcbs_cas(pgd_t *pgd);
extern void sev_enable(struct boot_params *bp);
+#define SNP_PSC_SHARED_TO_SHARED 0x1
+
+struct snp_hotplug_memory {
+ u64 phys_base;
+ u64 phys_end;
+ u32 unit_size;
+ u64 size;
+ /* bitmap bit unset: shared, set: private */
+ unsigned long *bitmap;
+};
+
+extern struct snp_hotplug_memory *snp_hp_mem;
+
+#ifdef CONFIG_UNACCEPTED_MEMORY
+int snp_extend_hotplug_memory_state_bitmap(phys_addr_t start,
+ unsigned long size,
+ uint64_t unit_size);
+static inline bool snp_is_hotplug_memory(phys_addr_t paddr)
+{
+ return snp_hp_mem && paddr >= snp_hp_mem->phys_base && paddr < snp_hp_mem->phys_end;
+}
+#else /* !CONFIG_UNACCEPTED_MEMORY */
+static inline int snp_extend_hotplug_memory_state_bitmap(phys_addr_t start,
+ unsigned long size,
+ uint64_t unit_size)
+{
+ return 0;
+}
+
+static inline bool snp_is_hotplug_memory(phys_addr_t paddr) { return false; }
+#endif
+
/*
* RMPADJUST modifies the RMP permissions of a page of a lesser-
* privileged (numerically higher) VMPL.
diff --git a/arch/x86/include/asm/unaccepted_memory.h b/arch/x86/include/asm/unaccepted_memory.h
index 5da80e68d718..abdf5472de9e 100644
--- a/arch/x86/include/asm/unaccepted_memory.h
+++ b/arch/x86/include/asm/unaccepted_memory.h
@@ -33,4 +33,17 @@ static inline unsigned long *efi_get_unaccepted_bitmap(void)
return NULL;
return __va(unaccepted->bitmap);
}
+
+static inline int arch_set_unaccepted_mem_state(phys_addr_t start, unsigned long size)
+{
+ struct efi_unaccepted_memory *unaccepted = efi_get_unaccepted_table();
+
+ if (!unaccepted)
+ return -EIO;
+
+ if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
+ return snp_extend_hotplug_memory_state_bitmap(start, size, unaccepted->unit_size);
+
+ return 0;
+}
#endif
diff --git a/drivers/firmware/efi/unaccepted_memory.c b/drivers/firmware/efi/unaccepted_memory.c
index 8537812346e2..6796042a64aa 100644
--- a/drivers/firmware/efi/unaccepted_memory.c
+++ b/drivers/firmware/efi/unaccepted_memory.c
@@ -281,7 +281,7 @@ static int extend_unaccepted_bitmap(phys_addr_t mem_range_start,
unacc_tbl->bitmap = (unsigned long *)__pa(new_bitmap);
spin_unlock_irqrestore(&unaccepted_memory_lock, flags);
- return 0;
+ return arch_set_unaccepted_mem_state(mem_range_start, mem_range_size);
}
int accept_hotplug_memory(phys_addr_t mem_range_start, unsigned long mem_range_size)
--
2.51.1
Powered by blists - more mailing lists