diff mbox series

[v6,6/9] KVM: selftests: Add library support for interacting with SNP

Message ID 20250203223205.36121-7-prsampat@amd.com (mailing list archive)
State New
Headers show
Series Basic SEV-SNP Selftests | expand

Commit Message

Pratik Rajesh Sampat Feb. 3, 2025, 10:32 p.m. UTC
Extend the SEV library to include support for SNP ioctl() wrappers,
which aid in launching and interacting with a SEV-SNP guest.

Tested-by: Srikanth Aithal <sraithal@amd.com>
Signed-off-by: Pratik R. Sampat <prsampat@amd.com>
---
v5..v6:

* Collected tags from Srikanth.
---
 tools/testing/selftests/kvm/include/x86/sev.h | 49 ++++++++++-
 tools/testing/selftests/kvm/lib/x86/sev.c     | 82 +++++++++++++++++--
 2 files changed, 125 insertions(+), 6 deletions(-)

Comments

Sean Christopherson Feb. 12, 2025, 2:12 a.m. UTC | #1
On Mon, Feb 03, 2025, Pratik R. Sampat wrote:
> Extend the SEV library to include support for SNP ioctl() wrappers,
> which aid in launching and interacting with a SEV-SNP guest.
> 
> Tested-by: Srikanth Aithal <sraithal@amd.com>
> Signed-off-by: Pratik R. Sampat <prsampat@amd.com>
> ---
> v5..v6:
> 
> * Collected tags from Srikanth.
> ---
>  tools/testing/selftests/kvm/include/x86/sev.h | 49 ++++++++++-
>  tools/testing/selftests/kvm/lib/x86/sev.c     | 82 +++++++++++++++++--
>  2 files changed, 125 insertions(+), 6 deletions(-)
> 
> diff --git a/tools/testing/selftests/kvm/include/x86/sev.h b/tools/testing/selftests/kvm/include/x86/sev.h
> index faed91435963..fd5d5261e10e 100644
> --- a/tools/testing/selftests/kvm/include/x86/sev.h
> +++ b/tools/testing/selftests/kvm/include/x86/sev.h
> @@ -22,9 +22,20 @@ enum sev_guest_state {
>  	SEV_GUEST_STATE_RUNNING,
>  };
>  
> +/* Minimum firmware version required for the SEV-SNP support */
> +#define SNP_MIN_API_MAJOR	1
> +#define SNP_MIN_API_MINOR	51

Dead code.  Selftests don't care about this.

>  #define SEV_POLICY_NO_DBG	(1UL << 0)
>  #define SEV_POLICY_ES		(1UL << 2)
>  
> +#define SNP_POLICY_SMT		(1ULL << 16)
> +#define SNP_POLICY_RSVD_MBO	(1ULL << 17)
> +#define SNP_POLICY_DBG		(1ULL << 19)
> +
> +#define SNP_FW_VER_MINOR(min)	((uint8_t)(min) << 0)
> +#define SNP_FW_VER_MAJOR(maj)	((uint8_t)(maj) << 8)

Also dead code.

>  #define GHCB_MSR_TERM_REQ	0x100
>  
>  #define VMGEXIT()		{ __asm__ __volatile__("rep; vmmcall"); }
> @@ -36,13 +47,35 @@ bool is_sev_snp_vm(struct kvm_vm *vm);
>  void sev_vm_launch(struct kvm_vm *vm, uint32_t policy);
>  void sev_vm_launch_measure(struct kvm_vm *vm, uint8_t *measurement);
>  void sev_vm_launch_finish(struct kvm_vm *vm);
> +void snp_vm_launch_start(struct kvm_vm *vm, uint64_t policy);
> +void snp_vm_launch_update(struct kvm_vm *vm);
> +void snp_vm_launch_finish(struct kvm_vm *vm);
>  
>  struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, void *guest_code,
>  					   struct kvm_vcpu **cpu);
> -void vm_sev_launch(struct kvm_vm *vm, uint32_t policy, uint8_t *measurement);
> +void vm_sev_launch(struct kvm_vm *vm, uint64_t policy, uint8_t *measurement);
>  
>  kvm_static_assert(SEV_RET_SUCCESS == 0);
>  
> +/*
> + * A SEV-SNP VM requires the policy reserved bit to always be set.
> + * The SMT policy bit is also required to be set based on SMT being
> + * available and active on the system.
> + */
> +static inline u64 snp_default_policy(void)
> +{
> +	bool smt_active = false;
> +	FILE *f;
> +
> +	f = fopen("/sys/devices/system/cpu/smt/active", "r");

Please add a helper to query if SMT is enabled.  I doubt there will ever be many
users of this, but it doesn't seem like something that should buried in SNP code.

Ha!  smt_possible() in tools/testing/selftests/kvm/x86/hyperv_cpuid.c is already
guilty of burying a related helper, and it looks like it's a more robust version.

> +	if (f) {
> +		smt_active = fgetc(f) - '0';
> +		fclose(f);
> +	}
> +
> +	return SNP_POLICY_RSVD_MBO | (smt_active ? SNP_POLICY_SMT : 0);
> +}
> +
>  /*
>   * The KVM_MEMORY_ENCRYPT_OP uAPI is utter garbage and takes an "unsigned long"
>   * instead of a proper struct.  The size of the parameter is embedded in the
> @@ -76,6 +109,7 @@ kvm_static_assert(SEV_RET_SUCCESS == 0);
>  
>  void sev_vm_init(struct kvm_vm *vm);
>  void sev_es_vm_init(struct kvm_vm *vm);
> +void snp_vm_init(struct kvm_vm *vm);
>  
>  static inline void sev_register_encrypted_memory(struct kvm_vm *vm,
>  						 struct userspace_mem_region *region)
> @@ -99,4 +133,17 @@ static inline void sev_launch_update_data(struct kvm_vm *vm, vm_paddr_t gpa,
>  	vm_sev_ioctl(vm, KVM_SEV_LAUNCH_UPDATE_DATA, &update_data);
>  }
>  
> +static inline void snp_launch_update_data(struct kvm_vm *vm, vm_paddr_t gpa,
> +					  uint64_t hva, uint64_t size, uint8_t type)
> +{
> +	struct kvm_sev_snp_launch_update update_data = {
> +		.uaddr = hva,
> +		.gfn_start = gpa >> PAGE_SHIFT,
> +		.len = size,
> +		.type = type,
> +	};
> +
> +	vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update_data);
> +}
> +
>  #endif /* SELFTEST_KVM_SEV_H */
> diff --git a/tools/testing/selftests/kvm/lib/x86/sev.c b/tools/testing/selftests/kvm/lib/x86/sev.c
> index 280ec42e281b..17d493e9907a 100644
> --- a/tools/testing/selftests/kvm/lib/x86/sev.c
> +++ b/tools/testing/selftests/kvm/lib/x86/sev.c
> @@ -31,7 +31,8 @@ bool is_sev_vm(struct kvm_vm *vm)
>   * and find the first range, but that's correct because the condition
>   * expression would cause us to quit the loop.
>   */
> -static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *region)
> +static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *region,
> +			   uint8_t page_type)
>  {
>  	const struct sparsebit *protected_phy_pages = region->protected_phy_pages;
>  	const vm_paddr_t gpa_base = region->region.guest_phys_addr;
> @@ -41,13 +42,35 @@ static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *regio
>  	if (!sparsebit_any_set(protected_phy_pages))
>  		return;
>  
> -	sev_register_encrypted_memory(vm, region);
> +	if (!is_sev_snp_vm(vm))
> +		sev_register_encrypted_memory(vm, region);
>  
>  	sparsebit_for_each_set_range(protected_phy_pages, i, j) {
>  		const uint64_t size = (j - i + 1) * vm->page_size;
>  		const uint64_t offset = (i - lowest_page_in_region) * vm->page_size;
>  
> -		sev_launch_update_data(vm, gpa_base + offset, size);
> +		if (is_sev_snp_vm(vm)) {

Curly braces are unnecessary.

> +			snp_launch_update_data(vm, gpa_base + offset,
> +					       (uint64_t)addr_gpa2hva(vm, gpa_base + offset),
> +					       size, page_type);
> +		} else {
> +			sev_launch_update_data(vm, gpa_base + offset, size);
> +		}
> +	}
> +}
> +
> +static void privatize_region(struct kvm_vm *vm, struct userspace_mem_region *region)

Can't this just be a param to encrypt_region() that also says "make it private"?

> +{
> +	const struct sparsebit *protected_phy_pages = region->protected_phy_pages;
> +	const vm_paddr_t gpa_base = region->region.guest_phys_addr;
> +	const sparsebit_idx_t lowest_page_in_region = gpa_base >> vm->page_shift;
> +	sparsebit_idx_t i, j;
> +
> +	sparsebit_for_each_set_range(protected_phy_pages, i, j) {
> +		const uint64_t size = (j - i + 1) * vm->page_size;
> +		const uint64_t offset = (i - lowest_page_in_region) * vm->page_size;
> +
> +		vm_mem_set_private(vm, gpa_base + offset, size);
>  	}
>  }
>  
> @@ -77,6 +100,14 @@ void sev_es_vm_init(struct kvm_vm *vm)
>  	}
>  }
>  
> +void snp_vm_init(struct kvm_vm *vm)
> +{
> +	struct kvm_sev_init init = { 0 };
> +
> +	assert(vm->type == KVM_X86_SNP_VM);

Use TEST_ASSERT(), or do nothing, don't use assert().

> +	vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
> +}
> +
>  void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
>  {
>  	struct kvm_sev_launch_start launch_start = {
> @@ -93,7 +124,7 @@ void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
>  	TEST_ASSERT_EQ(status.state, SEV_GUEST_STATE_LAUNCH_UPDATE);
>  
>  	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node)
> -		encrypt_region(vm, region);
> +		encrypt_region(vm, region, 0);

Please add an enum/macro instead of open coding a literal '0'.  I gotta assume
there's an appropriate name for page type '0'.

>  
>  	if (policy & SEV_POLICY_ES)
>  		vm_sev_ioctl(vm, KVM_SEV_LAUNCH_UPDATE_VMSA, NULL);
Pratik Rajesh Sampat Feb. 14, 2025, 6:12 p.m. UTC | #2
On 2/11/25 8:12 PM, Sean Christopherson wrote:
> On Mon, Feb 03, 2025, Pratik R. Sampat wrote:
>> Extend the SEV library to include support for SNP ioctl() wrappers,
>> which aid in launching and interacting with a SEV-SNP guest.
>>
>> Tested-by: Srikanth Aithal <sraithal@amd.com>
>> Signed-off-by: Pratik R. Sampat <prsampat@amd.com>

[..snip..]

>> +/*
>> + * A SEV-SNP VM requires the policy reserved bit to always be set.
>> + * The SMT policy bit is also required to be set based on SMT being
>> + * available and active on the system.
>> + */
>> +static inline u64 snp_default_policy(void)
>> +{
>> +	bool smt_active = false;
>> +	FILE *f;
>> +
>> +	f = fopen("/sys/devices/system/cpu/smt/active", "r");
> 
> Please add a helper to query if SMT is enabled.  I doubt there will ever be many
> users of this, but it doesn't seem like something that should buried in SNP code.
> 
> Ha!  smt_possible() in tools/testing/selftests/kvm/x86/hyperv_cpuid.c is already
> guilty of burying a related helper, and it looks like it's a more robust version.
> 

You're right, a more general helper is in order here.

Since the hyperv_cpuid selftest only seems to care about whether SMT is
possible (i.e., it may or may not be enabled) and we care about it being
enabled as well, for the flag to be set. I should make a more generic
variant(s) that can be accessible to both. Maybe I can implement it
within testing/selftests/kvm/include/x86_processor.h?

>> +	if (f) {
>> +		smt_active = fgetc(f) - '0';
>> +		fclose(f);
>> +	}
>> +
>> +	return SNP_POLICY_RSVD_MBO | (smt_active ? SNP_POLICY_SMT : 0);
>> +}
>> +
>>  /*
>>   * The KVM_MEMORY_ENCRYPT_OP uAPI is utter garbage and takes an "unsigned long"
>>   * instead of a proper struct.  The size of the parameter is embedded in the
>> @@ -76,6 +109,7 @@ kvm_static_assert(SEV_RET_SUCCESS == 0);
>>  
>>  void sev_vm_init(struct kvm_vm *vm);
>>  void sev_es_vm_init(struct kvm_vm *vm);
>> +void snp_vm_init(struct kvm_vm *vm);
>>  
>>  static inline void sev_register_encrypted_memory(struct kvm_vm *vm,
>>  						 struct userspace_mem_region *region)
>> @@ -99,4 +133,17 @@ static inline void sev_launch_update_data(struct kvm_vm *vm, vm_paddr_t gpa,
>>  	vm_sev_ioctl(vm, KVM_SEV_LAUNCH_UPDATE_DATA, &update_data);
>>  }
>>  
>> +static inline void snp_launch_update_data(struct kvm_vm *vm, vm_paddr_t gpa,
>> +					  uint64_t hva, uint64_t size, uint8_t type)
>> +{
>> +	struct kvm_sev_snp_launch_update update_data = {
>> +		.uaddr = hva,
>> +		.gfn_start = gpa >> PAGE_SHIFT,
>> +		.len = size,
>> +		.type = type,
>> +	};
>> +
>> +	vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update_data);
>> +}
>> +
>>  #endif /* SELFTEST_KVM_SEV_H */
>> diff --git a/tools/testing/selftests/kvm/lib/x86/sev.c b/tools/testing/selftests/kvm/lib/x86/sev.c
>> index 280ec42e281b..17d493e9907a 100644
>> --- a/tools/testing/selftests/kvm/lib/x86/sev.c
>> +++ b/tools/testing/selftests/kvm/lib/x86/sev.c
>> @@ -31,7 +31,8 @@ bool is_sev_vm(struct kvm_vm *vm)
>>   * and find the first range, but that's correct because the condition
>>   * expression would cause us to quit the loop.
>>   */
>> -static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *region)
>> +static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *region,
>> +			   uint8_t page_type)
>>  {
>>  	const struct sparsebit *protected_phy_pages = region->protected_phy_pages;
>>  	const vm_paddr_t gpa_base = region->region.guest_phys_addr;
>> @@ -41,13 +42,35 @@ static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *regio
>>  	if (!sparsebit_any_set(protected_phy_pages))
>>  		return;
>>  
>> -	sev_register_encrypted_memory(vm, region);
>> +	if (!is_sev_snp_vm(vm))
>> +		sev_register_encrypted_memory(vm, region);
>>  
>>  	sparsebit_for_each_set_range(protected_phy_pages, i, j) {
>>  		const uint64_t size = (j - i + 1) * vm->page_size;
>>  		const uint64_t offset = (i - lowest_page_in_region) * vm->page_size;
>>  
>> -		sev_launch_update_data(vm, gpa_base + offset, size);
>> +		if (is_sev_snp_vm(vm)) {
> 
> Curly braces are unnecessary.

Ack. Will be remove them.

> 
>> +			snp_launch_update_data(vm, gpa_base + offset,
>> +					       (uint64_t)addr_gpa2hva(vm, gpa_base + offset),
>> +					       size, page_type);
>> +		} else {
>> +			sev_launch_update_data(vm, gpa_base + offset, size);
>> +		}
>> +	}
>> +}
>> +
>> +static void privatize_region(struct kvm_vm *vm, struct userspace_mem_region *region)
> 
> Can't this just be a param to encrypt_region() that also says "make it private"?
> 

Sure, I can parameterize encrypt_region() to have a private boolean.
The idea was essentially to ensure that privatizing and encryption can
be called separately if they wanted to. However, since this is
currently only used in tandem (by SNP) I can remove this to reduce
duplication.

>> +{
>> +	const struct sparsebit *protected_phy_pages = region->protected_phy_pages;
>> +	const vm_paddr_t gpa_base = region->region.guest_phys_addr;
>> +	const sparsebit_idx_t lowest_page_in_region = gpa_base >> vm->page_shift;
>> +	sparsebit_idx_t i, j;
>> +
>> +	sparsebit_for_each_set_range(protected_phy_pages, i, j) {
>> +		const uint64_t size = (j - i + 1) * vm->page_size;
>> +		const uint64_t offset = (i - lowest_page_in_region) * vm->page_size;
>> +
>> +		vm_mem_set_private(vm, gpa_base + offset, size);
>>  	}
>>  }
>>  
>> @@ -77,6 +100,14 @@ void sev_es_vm_init(struct kvm_vm *vm)
>>  	}
>>  }
>>  
>> +void snp_vm_init(struct kvm_vm *vm)
>> +{
>> +	struct kvm_sev_init init = { 0 };
>> +
>> +	assert(vm->type == KVM_X86_SNP_VM);
> 
> Use TEST_ASSERT(), or do nothing, don't use assert().
> 

Alright, I can do that.
I will also send another cleanups patch to remove asserts() in the
SEV, SEV-ES counterparts too in that case!

>> +	vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
>> +}
>> +
>>  void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
>>  {
>>  	struct kvm_sev_launch_start launch_start = {
>> @@ -93,7 +124,7 @@ void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
>>  	TEST_ASSERT_EQ(status.state, SEV_GUEST_STATE_LAUNCH_UPDATE);
>>  
>>  	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node)
>> -		encrypt_region(vm, region);
>> +		encrypt_region(vm, region, 0);
> 
> Please add an enum/macro instead of open coding a literal '0'.  I gotta assume
> there's an appropriate name for page type '0'.
> 

For SNP, we supply this parameter to determine the page type for SNP
launch update defined as KVM_SEV_SNP_PAGE_TYPE_*. For SEV/SEV-ES,
however, the page type doesn't really get factored in and falls through
unaccounted in that case, so I had passed a zero to it.

Having said that, having a literal here is quite unclean. Maybe I can
pass one of the existing page types to it or, better yet, define a new
KVM_SEV_PAGE_TYPE_[RESERVED|UNUSED] type instead for our selftest
header?

>>  
>>  	if (policy & SEV_POLICY_ES)
>>  		vm_sev_ioctl(vm, KVM_SEV_LAUNCH_UPDATE_VMSA, NULL);
Sean Christopherson Feb. 19, 2025, 4:55 p.m. UTC | #3
On Fri, Feb 14, 2025, Pratik Rajesh Sampat wrote:
> On 2/11/25 8:12 PM, Sean Christopherson wrote:
> > On Mon, Feb 03, 2025, Pratik R. Sampat wrote:
> >> Extend the SEV library to include support for SNP ioctl() wrappers,
> >> which aid in launching and interacting with a SEV-SNP guest.
> >>
> >> Tested-by: Srikanth Aithal <sraithal@amd.com>
> >> Signed-off-by: Pratik R. Sampat <prsampat@amd.com>
> 
> [..snip..]
> 
> >> +/*
> >> + * A SEV-SNP VM requires the policy reserved bit to always be set.
> >> + * The SMT policy bit is also required to be set based on SMT being
> >> + * available and active on the system.
> >> + */
> >> +static inline u64 snp_default_policy(void)
> >> +{
> >> +	bool smt_active = false;
> >> +	FILE *f;
> >> +
> >> +	f = fopen("/sys/devices/system/cpu/smt/active", "r");
> > 
> > Please add a helper to query if SMT is enabled.  I doubt there will ever be many
> > users of this, but it doesn't seem like something that should buried in SNP code.
> > 
> > Ha!  smt_possible() in tools/testing/selftests/kvm/x86/hyperv_cpuid.c is already
> > guilty of burying a related helper, and it looks like it's a more robust version.
> > 
> 
> You're right, a more general helper is in order here.
> 
> Since the hyperv_cpuid selftest only seems to care about whether SMT is
> possible (i.e., it may or may not be enabled) and we care about it being
> enabled as well, for the flag to be set. I should make a more generic
> variant(s) that can be accessible to both. Maybe I can implement it
> within testing/selftests/kvm/include/x86_processor.h?

It should go in kvm_util.h, /sys/devices/system/cpu/smt/active is a generic interface
provided by kernel/cpu.c.

> >> @@ -93,7 +124,7 @@ void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
> >>  	TEST_ASSERT_EQ(status.state, SEV_GUEST_STATE_LAUNCH_UPDATE);
> >>  
> >>  	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node)
> >> -		encrypt_region(vm, region);
> >> +		encrypt_region(vm, region, 0);
> > 
> > Please add an enum/macro instead of open coding a literal '0'.  I gotta assume
> > there's an appropriate name for page type '0'.
> > 
> 
> For SNP, we supply this parameter to determine the page type for SNP
> launch update defined as KVM_SEV_SNP_PAGE_TYPE_*. For SEV/SEV-ES,
> however, the page type doesn't really get factored in and falls through
> unaccounted in that case, so I had passed a zero to it.
> 
> Having said that, having a literal here is quite unclean. Maybe I can
> pass one of the existing page types to it or, better yet, define a new
> KVM_SEV_PAGE_TYPE_[RESERVED|UNUSED] type instead for our selftest
> header?

Ya, define something new and arbitrary.  I vote for either KVM_SEV_PAGE_TYPE_NONE
or KVM_SEV_PAGE_TYPE_INVALID.  RESERVED suggests the page is "valid" but reserved
for some entity.  Ditto for UNUSED; valid, but not yet claimed.
diff mbox series

Patch

diff --git a/tools/testing/selftests/kvm/include/x86/sev.h b/tools/testing/selftests/kvm/include/x86/sev.h
index faed91435963..fd5d5261e10e 100644
--- a/tools/testing/selftests/kvm/include/x86/sev.h
+++ b/tools/testing/selftests/kvm/include/x86/sev.h
@@ -22,9 +22,20 @@  enum sev_guest_state {
 	SEV_GUEST_STATE_RUNNING,
 };
 
+/* Minimum firmware version required for the SEV-SNP support */
+#define SNP_MIN_API_MAJOR	1
+#define SNP_MIN_API_MINOR	51
+
 #define SEV_POLICY_NO_DBG	(1UL << 0)
 #define SEV_POLICY_ES		(1UL << 2)
 
+#define SNP_POLICY_SMT		(1ULL << 16)
+#define SNP_POLICY_RSVD_MBO	(1ULL << 17)
+#define SNP_POLICY_DBG		(1ULL << 19)
+
+#define SNP_FW_VER_MINOR(min)	((uint8_t)(min) << 0)
+#define SNP_FW_VER_MAJOR(maj)	((uint8_t)(maj) << 8)
+
 #define GHCB_MSR_TERM_REQ	0x100
 
 #define VMGEXIT()		{ __asm__ __volatile__("rep; vmmcall"); }
@@ -36,13 +47,35 @@  bool is_sev_snp_vm(struct kvm_vm *vm);
 void sev_vm_launch(struct kvm_vm *vm, uint32_t policy);
 void sev_vm_launch_measure(struct kvm_vm *vm, uint8_t *measurement);
 void sev_vm_launch_finish(struct kvm_vm *vm);
+void snp_vm_launch_start(struct kvm_vm *vm, uint64_t policy);
+void snp_vm_launch_update(struct kvm_vm *vm);
+void snp_vm_launch_finish(struct kvm_vm *vm);
 
 struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, void *guest_code,
 					   struct kvm_vcpu **cpu);
-void vm_sev_launch(struct kvm_vm *vm, uint32_t policy, uint8_t *measurement);
+void vm_sev_launch(struct kvm_vm *vm, uint64_t policy, uint8_t *measurement);
 
 kvm_static_assert(SEV_RET_SUCCESS == 0);
 
+/*
+ * A SEV-SNP VM requires the policy reserved bit to always be set.
+ * The SMT policy bit is also required to be set based on SMT being
+ * available and active on the system.
+ */
+static inline u64 snp_default_policy(void)
+{
+	bool smt_active = false;
+	FILE *f;
+
+	f = fopen("/sys/devices/system/cpu/smt/active", "r");
+	if (f) {
+		smt_active = fgetc(f) - '0';
+		fclose(f);
+	}
+
+	return SNP_POLICY_RSVD_MBO | (smt_active ? SNP_POLICY_SMT : 0);
+}
+
 /*
  * The KVM_MEMORY_ENCRYPT_OP uAPI is utter garbage and takes an "unsigned long"
  * instead of a proper struct.  The size of the parameter is embedded in the
@@ -76,6 +109,7 @@  kvm_static_assert(SEV_RET_SUCCESS == 0);
 
 void sev_vm_init(struct kvm_vm *vm);
 void sev_es_vm_init(struct kvm_vm *vm);
+void snp_vm_init(struct kvm_vm *vm);
 
 static inline void sev_register_encrypted_memory(struct kvm_vm *vm,
 						 struct userspace_mem_region *region)
@@ -99,4 +133,17 @@  static inline void sev_launch_update_data(struct kvm_vm *vm, vm_paddr_t gpa,
 	vm_sev_ioctl(vm, KVM_SEV_LAUNCH_UPDATE_DATA, &update_data);
 }
 
+static inline void snp_launch_update_data(struct kvm_vm *vm, vm_paddr_t gpa,
+					  uint64_t hva, uint64_t size, uint8_t type)
+{
+	struct kvm_sev_snp_launch_update update_data = {
+		.uaddr = hva,
+		.gfn_start = gpa >> PAGE_SHIFT,
+		.len = size,
+		.type = type,
+	};
+
+	vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update_data);
+}
+
 #endif /* SELFTEST_KVM_SEV_H */
diff --git a/tools/testing/selftests/kvm/lib/x86/sev.c b/tools/testing/selftests/kvm/lib/x86/sev.c
index 280ec42e281b..17d493e9907a 100644
--- a/tools/testing/selftests/kvm/lib/x86/sev.c
+++ b/tools/testing/selftests/kvm/lib/x86/sev.c
@@ -31,7 +31,8 @@  bool is_sev_vm(struct kvm_vm *vm)
  * and find the first range, but that's correct because the condition
  * expression would cause us to quit the loop.
  */
-static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *region)
+static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *region,
+			   uint8_t page_type)
 {
 	const struct sparsebit *protected_phy_pages = region->protected_phy_pages;
 	const vm_paddr_t gpa_base = region->region.guest_phys_addr;
@@ -41,13 +42,35 @@  static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *regio
 	if (!sparsebit_any_set(protected_phy_pages))
 		return;
 
-	sev_register_encrypted_memory(vm, region);
+	if (!is_sev_snp_vm(vm))
+		sev_register_encrypted_memory(vm, region);
 
 	sparsebit_for_each_set_range(protected_phy_pages, i, j) {
 		const uint64_t size = (j - i + 1) * vm->page_size;
 		const uint64_t offset = (i - lowest_page_in_region) * vm->page_size;
 
-		sev_launch_update_data(vm, gpa_base + offset, size);
+		if (is_sev_snp_vm(vm)) {
+			snp_launch_update_data(vm, gpa_base + offset,
+					       (uint64_t)addr_gpa2hva(vm, gpa_base + offset),
+					       size, page_type);
+		} else {
+			sev_launch_update_data(vm, gpa_base + offset, size);
+		}
+	}
+}
+
+static void privatize_region(struct kvm_vm *vm, struct userspace_mem_region *region)
+{
+	const struct sparsebit *protected_phy_pages = region->protected_phy_pages;
+	const vm_paddr_t gpa_base = region->region.guest_phys_addr;
+	const sparsebit_idx_t lowest_page_in_region = gpa_base >> vm->page_shift;
+	sparsebit_idx_t i, j;
+
+	sparsebit_for_each_set_range(protected_phy_pages, i, j) {
+		const uint64_t size = (j - i + 1) * vm->page_size;
+		const uint64_t offset = (i - lowest_page_in_region) * vm->page_size;
+
+		vm_mem_set_private(vm, gpa_base + offset, size);
 	}
 }
 
@@ -77,6 +100,14 @@  void sev_es_vm_init(struct kvm_vm *vm)
 	}
 }
 
+void snp_vm_init(struct kvm_vm *vm)
+{
+	struct kvm_sev_init init = { 0 };
+
+	assert(vm->type == KVM_X86_SNP_VM);
+	vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
+}
+
 void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
 {
 	struct kvm_sev_launch_start launch_start = {
@@ -93,7 +124,7 @@  void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
 	TEST_ASSERT_EQ(status.state, SEV_GUEST_STATE_LAUNCH_UPDATE);
 
 	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node)
-		encrypt_region(vm, region);
+		encrypt_region(vm, region, 0);
 
 	if (policy & SEV_POLICY_ES)
 		vm_sev_ioctl(vm, KVM_SEV_LAUNCH_UPDATE_VMSA, NULL);
@@ -129,6 +160,35 @@  void sev_vm_launch_finish(struct kvm_vm *vm)
 	TEST_ASSERT_EQ(status.state, SEV_GUEST_STATE_RUNNING);
 }
 
+void snp_vm_launch_start(struct kvm_vm *vm, uint64_t policy)
+{
+	struct kvm_sev_snp_launch_start launch_start = {
+		.policy = policy,
+	};
+
+	vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_START, &launch_start);
+}
+
+void snp_vm_launch_update(struct kvm_vm *vm)
+{
+	struct userspace_mem_region *region;
+	int ctr;
+
+	hash_for_each(vm->regions.slot_hash, ctr, region, slot_node) {
+		privatize_region(vm, region);
+		encrypt_region(vm, region, KVM_SEV_SNP_PAGE_TYPE_NORMAL);
+	}
+
+	vm->arch.is_pt_protected = true;
+}
+
+void snp_vm_launch_finish(struct kvm_vm *vm)
+{
+	struct kvm_sev_snp_launch_finish launch_finish = { 0 };
+
+	vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_FINISH, &launch_finish);
+}
+
 struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, void *guest_code,
 					   struct kvm_vcpu **cpu)
 {
@@ -145,8 +205,20 @@  struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, void *guest_code,
 	return vm;
 }
 
-void vm_sev_launch(struct kvm_vm *vm, uint32_t policy, uint8_t *measurement)
+void vm_sev_launch(struct kvm_vm *vm, uint64_t policy, uint8_t *measurement)
 {
+	if (is_sev_snp_vm(vm)) {
+		vm_enable_cap(vm, KVM_CAP_EXIT_HYPERCALL, (1 << KVM_HC_MAP_GPA_RANGE));
+
+		snp_vm_launch_start(vm, policy);
+
+		snp_vm_launch_update(vm);
+
+		snp_vm_launch_finish(vm);
+
+		return;
+	}
+
 	sev_vm_launch(vm, policy);
 
 	if (!measurement)