[RFC,v1,3/6] x86/sev: Maintain shadow rmptable on Hyper-V

Message ID 20230123165128.28185-4-jpiotrowski@linux.microsoft.com
State New
Headers
Series Support nested SNP KVM guests on Hyper-V |

Commit Message

Jeremi Piotrowski Jan. 23, 2023, 4:51 p.m. UTC
  Hyper-V can expose the SEV-SNP feature to guests, and manages the
system-wide RMP (Reverse Map) table. The SNP implementation in the
kernel needs access to the rmptable for tracking pages and deciding
when/how to issue rmpupdate/psmash.  When running as a Hyper-V guest
with SNP support, an rmptable is allocated by the kernel during boot for
this purpose. Keep the table in sync with issued rmpupdate/psmash
instructions.

The logic for how to update the rmptable comes from "AMD64 Architecture
Programmer’s Manual, Volume 3" which describes the psmash and rmpupdate
instructions. To ensure correctness of the SNP host code, the most
important fields are "assigned" and "page size".

Signed-off-by: Jeremi Piotrowski <jpiotrowski@linux.microsoft.com>
---
 arch/x86/kernel/sev.c | 59 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)
  

Comments

Michael Kelley (LINUX) Jan. 29, 2023, 4:37 a.m. UTC | #1
From: Jeremi Piotrowski <jpiotrowski@linux.microsoft.com> Sent: Monday, January 23, 2023 8:51 AM
> 
> Hyper-V can expose the SEV-SNP feature to guests, and manages the
> system-wide RMP (Reverse Map) table. The SNP implementation in the
> kernel needs access to the rmptable for tracking pages and deciding
> when/how to issue rmpupdate/psmash.  When running as a Hyper-V guest
> with SNP support, an rmptable is allocated by the kernel during boot for
> this purpose. Keep the table in sync with issued rmpupdate/psmash
> instructions.
> 
> The logic for how to update the rmptable comes from "AMD64 Architecture
> Programmer’s Manual, Volume 3" which describes the psmash and rmpupdate
> instructions. To ensure correctness of the SNP host code, the most
> important fields are "assigned" and "page size".
> 
> Signed-off-by: Jeremi Piotrowski <jpiotrowski@linux.microsoft.com>
> ---
>  arch/x86/kernel/sev.c | 59 +++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 59 insertions(+)
> 
> diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c
> index 95404c7e5150..edec1ccb80b1 100644
> --- a/arch/x86/kernel/sev.c
> +++ b/arch/x86/kernel/sev.c
> @@ -26,6 +26,7 @@
>  #include <linux/iommu.h>
>  #include <linux/amd-iommu.h>
> 
> +#include <asm/mshyperv.h>

The code in sev.c should be generic and appropriate for use with any
hypervisor.  As such, I think we want to avoid sev.c having a dependency
on Hyper-V specific code, such as the <asm/mshyperv.h> include file
and the ms_hyperv.nested_features variable as used below.

Instead, create a boolean static variable in the sev.c module along with
a wrapper function to set it.  The logic that tests hv_no_rmp_table()
should test this static variable instead, which would default to "false".
Hypervisor-specific initialization code can call the wrapper function
to set the variable to "true" based on whatever logic is appropriate for
the hypervisor.  This approach reverses the dependency and hopefully
is usable by other hypervisors that want to offer nested SNP support.

>  #include <asm/cpu_entry_area.h>
>  #include <asm/stacktrace.h>
>  #include <asm/sev.h>
> @@ -2566,6 +2567,11 @@ int snp_lookup_rmpentry(u64 pfn, int *level)
>  }
>  EXPORT_SYMBOL_GPL(snp_lookup_rmpentry);
> 
> +static bool hv_no_rmp_table(void)
> +{
> +	return ms_hyperv.nested_features & HV_X64_NESTED_NO_RMP_TABLE;
> +}
> +
>  static bool virt_snp_msr(void)
>  {
>  	return boot_cpu_has(X86_FEATURE_NESTED_VIRT_SNP_MSR);
> @@ -2584,6 +2590,26 @@ static u64 virt_psmash(u64 paddr)
>  	return ret;
>  }
> 
> +static void snp_update_rmptable_psmash(u64 pfn)
> +{
> +	int level;
> +	struct rmpentry *entry = __snp_lookup_rmpentry(pfn, &level);
> +
> +	if (WARN_ON(IS_ERR_OR_NULL(entry)))
> +		return;
> +
> +	if (level == PG_LEVEL_2M) {
> +		int i;
> +
> +		entry->info.pagesize = RMP_PG_SIZE_4K;
> +		for (i = 1; i < PTRS_PER_PMD; i++) {
> +			struct rmpentry *it = &entry[i];
> +			*it = *entry;
> +			it->info.gpa = entry->info.gpa + i * PAGE_SIZE;
> +		}
> +	}
> +}
> +
>  /*
>   * psmash is used to smash a 2MB aligned page into 4K
>   * pages while preserving the Validated bit in the RMP.
> @@ -2601,6 +2627,8 @@ int psmash(u64 pfn)
> 
>  	if (virt_snp_msr()) {
>  		ret = virt_psmash(paddr);
> +		if (!ret && hv_no_rmp_table())
> +			snp_update_rmptable_psmash(pfn);
>  	} else {
>  		/* Binutils version 2.36 supports the PSMASH mnemonic. */
>  		asm volatile(".byte 0xF3, 0x0F, 0x01, 0xFF"
> @@ -2638,6 +2666,35 @@ static u64 virt_rmpupdate(unsigned long paddr, struct
> rmp_state *val)
>  	return ret;
>  }
> 
> +static void snp_update_rmptable_rmpupdate(u64 pfn, int level, struct rmp_state *val)
> +{
> +	int prev_level;
> +	struct rmpentry *entry = __snp_lookup_rmpentry(pfn, &prev_level);
> +
> +	if (WARN_ON(IS_ERR_OR_NULL(entry)))
> +		return;
> +
> +	if (level > PG_LEVEL_4K) {
> +		int i;
> +		struct rmpentry tmp_rmp = {
> +			.info = {
> +				.assigned = val->assigned,
> +			},
> +		};
> +		for (i = 1; i < PTRS_PER_PMD; i++)
> +			entry[i] = tmp_rmp;
> +	}
> +	if (!val->assigned) {
> +		memset(entry, 0, sizeof(*entry));
> +	} else {
> +		entry->info.assigned = val->assigned;
> +		entry->info.pagesize = val->pagesize;
> +		entry->info.immutable = val->immutable;
> +		entry->info.gpa = val->gpa;
> +		entry->info.asid = val->asid;
> +	}
> +}
> +
>  static int rmpupdate(u64 pfn, struct rmp_state *val)
>  {
>  	unsigned long paddr = pfn << PAGE_SHIFT;
> @@ -2666,6 +2723,8 @@ static int rmpupdate(u64 pfn, struct rmp_state *val)
> 
>  	if (virt_snp_msr()) {
>  		ret = virt_rmpupdate(paddr, val);
> +		if (!ret && hv_no_rmp_table())
> +			snp_update_rmptable_rmpupdate(pfn, level, val);
>  	} else {
>  		/* Binutils version 2.36 supports the RMPUPDATE mnemonic. */
>  		asm volatile(".byte 0xF2, 0x0F, 0x01, 0xFE"
> --
> 2.25.1
  
Jeremi Piotrowski Jan. 30, 2023, 4:51 p.m. UTC | #2
On Sun, Jan 29, 2023 at 04:37:24AM +0000, Michael Kelley (LINUX) wrote:
> From: Jeremi Piotrowski <jpiotrowski@linux.microsoft.com> Sent: Monday, January 23, 2023 8:51 AM
> > 
> > Hyper-V can expose the SEV-SNP feature to guests, and manages the
> > system-wide RMP (Reverse Map) table. The SNP implementation in the
> > kernel needs access to the rmptable for tracking pages and deciding
> > when/how to issue rmpupdate/psmash.  When running as a Hyper-V guest
> > with SNP support, an rmptable is allocated by the kernel during boot for
> > this purpose. Keep the table in sync with issued rmpupdate/psmash
> > instructions.
> > 
> > The logic for how to update the rmptable comes from "AMD64 Architecture
> > Programmer’s Manual, Volume 3" which describes the psmash and rmpupdate
> > instructions. To ensure correctness of the SNP host code, the most
> > important fields are "assigned" and "page size".
> > 
> > Signed-off-by: Jeremi Piotrowski <jpiotrowski@linux.microsoft.com>
> > ---
> >  arch/x86/kernel/sev.c | 59 +++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 59 insertions(+)
> > 
> > diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c
> > index 95404c7e5150..edec1ccb80b1 100644
> > --- a/arch/x86/kernel/sev.c
> > +++ b/arch/x86/kernel/sev.c
> > @@ -26,6 +26,7 @@
> >  #include <linux/iommu.h>
> >  #include <linux/amd-iommu.h>
> > 
> > +#include <asm/mshyperv.h>
> 
> The code in sev.c should be generic and appropriate for use with any
> hypervisor.  As such, I think we want to avoid sev.c having a dependency
> on Hyper-V specific code, such as the <asm/mshyperv.h> include file
> and the ms_hyperv.nested_features variable as used below.
> 
> Instead, create a boolean static variable in the sev.c module along with
> a wrapper function to set it.  The logic that tests hv_no_rmp_table()
> should test this static variable instead, which would default to "false".
> Hypervisor-specific initialization code can call the wrapper function
> to set the variable to "true" based on whatever logic is appropriate for
> the hypervisor.  This approach reverses the dependency and hopefully
> is usable by other hypervisors that want to offer nested SNP support.
> 

ok, this makes sense. I've added a call to the wrapper to the init_mem_mapping
callback added before so that it is enabled together with allocating the
rmptable.
  

Patch

diff --git a/arch/x86/kernel/sev.c b/arch/x86/kernel/sev.c
index 95404c7e5150..edec1ccb80b1 100644
--- a/arch/x86/kernel/sev.c
+++ b/arch/x86/kernel/sev.c
@@ -26,6 +26,7 @@ 
 #include <linux/iommu.h>
 #include <linux/amd-iommu.h>
 
+#include <asm/mshyperv.h>
 #include <asm/cpu_entry_area.h>
 #include <asm/stacktrace.h>
 #include <asm/sev.h>
@@ -2566,6 +2567,11 @@  int snp_lookup_rmpentry(u64 pfn, int *level)
 }
 EXPORT_SYMBOL_GPL(snp_lookup_rmpentry);
 
+static bool hv_no_rmp_table(void)
+{
+	return ms_hyperv.nested_features & HV_X64_NESTED_NO_RMP_TABLE;
+}
+
 static bool virt_snp_msr(void)
 {
 	return boot_cpu_has(X86_FEATURE_NESTED_VIRT_SNP_MSR);
@@ -2584,6 +2590,26 @@  static u64 virt_psmash(u64 paddr)
 	return ret;
 }
 
+static void snp_update_rmptable_psmash(u64 pfn)
+{
+	int level;
+	struct rmpentry *entry = __snp_lookup_rmpentry(pfn, &level);
+
+	if (WARN_ON(IS_ERR_OR_NULL(entry)))
+		return;
+
+	if (level == PG_LEVEL_2M) {
+		int i;
+
+		entry->info.pagesize = RMP_PG_SIZE_4K;
+		for (i = 1; i < PTRS_PER_PMD; i++) {
+			struct rmpentry *it = &entry[i];
+			*it = *entry;
+			it->info.gpa = entry->info.gpa + i * PAGE_SIZE;
+		}
+	}
+}
+
 /*
  * psmash is used to smash a 2MB aligned page into 4K
  * pages while preserving the Validated bit in the RMP.
@@ -2601,6 +2627,8 @@  int psmash(u64 pfn)
 
 	if (virt_snp_msr()) {
 		ret = virt_psmash(paddr);
+		if (!ret && hv_no_rmp_table())
+			snp_update_rmptable_psmash(pfn);
 	} else {
 		/* Binutils version 2.36 supports the PSMASH mnemonic. */
 		asm volatile(".byte 0xF3, 0x0F, 0x01, 0xFF"
@@ -2638,6 +2666,35 @@  static u64 virt_rmpupdate(unsigned long paddr, struct rmp_state *val)
 	return ret;
 }
 
+static void snp_update_rmptable_rmpupdate(u64 pfn, int level, struct rmp_state *val)
+{
+	int prev_level;
+	struct rmpentry *entry = __snp_lookup_rmpentry(pfn, &prev_level);
+
+	if (WARN_ON(IS_ERR_OR_NULL(entry)))
+		return;
+
+	if (level > PG_LEVEL_4K) {
+		int i;
+		struct rmpentry tmp_rmp = {
+			.info = {
+				.assigned = val->assigned,
+			},
+		};
+		for (i = 1; i < PTRS_PER_PMD; i++)
+			entry[i] = tmp_rmp;
+	}
+	if (!val->assigned) {
+		memset(entry, 0, sizeof(*entry));
+	} else {
+		entry->info.assigned = val->assigned;
+		entry->info.pagesize = val->pagesize;
+		entry->info.immutable = val->immutable;
+		entry->info.gpa = val->gpa;
+		entry->info.asid = val->asid;
+	}
+}
+
 static int rmpupdate(u64 pfn, struct rmp_state *val)
 {
 	unsigned long paddr = pfn << PAGE_SHIFT;
@@ -2666,6 +2723,8 @@  static int rmpupdate(u64 pfn, struct rmp_state *val)
 
 	if (virt_snp_msr()) {
 		ret = virt_rmpupdate(paddr, val);
+		if (!ret && hv_no_rmp_table())
+			snp_update_rmptable_rmpupdate(pfn, level, val);
 	} else {
 		/* Binutils version 2.36 supports the RMPUPDATE mnemonic. */
 		asm volatile(".byte 0xF2, 0x0F, 0x01, 0xFE"