diff mbox series

[RFC,v7,25/64] crypto: ccp: Add support to initialize the AMD-SP for SEV-SNP

Message ID 20221214194056.161492-26-michael.roth@amd.com
State New
Headers show
Series Add AMD Secure Nested Paging (SEV-SNP) Hypervisor Support | expand

Commit Message

Michael Roth Dec. 14, 2022, 7:40 p.m. UTC
From: Brijesh Singh <brijesh.singh@amd.com>

Before SNP VMs can be launched, the platform must be appropriately
configured and initialized. Platform initialization is accomplished via
the SNP_INIT command. Make sure to do a WBINVD and issue DF_FLUSH
command to prepare for the first SNP guest launch after INIT.

During the execution of SNP_INIT command, the firmware configures
and enables SNP security policy enforcement in many system components.
Some system components write to regions of memory reserved by early
x86 firmware (e.g. UEFI). Other system components write to regions
provided by the operation system, hypervisor, or x86 firmware.
Such system components can only write to HV-fixed pages or Default
pages. They will error when attempting to write to other page states
after SNP_INIT enables their SNP enforcement.

Starting in SNP firmware v1.52, the SNP_INIT_EX command takes a list of
system physical address ranges to convert into the HV-fixed page states
during the RMP initialization. If INIT_RMP is 1, hypervisors should
provide all system physical address ranges that the hypervisor will
never assign to a guest until the next RMP re-initialization.
For instance, the memory that UEFI reserves should be included in the
range list. This allows system components that occasionally write to
memory (e.g. logging to UEFI reserved regions) to not fail due to
RMP initialization and SNP enablement.

Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
Signed-off-by: Michael Roth <michael.roth@amd.com>
---
 drivers/crypto/ccp/sev-dev.c | 225 +++++++++++++++++++++++++++++++++++
 drivers/crypto/ccp/sev-dev.h |   2 +
 include/linux/psp-sev.h      |  17 +++
 3 files changed, 244 insertions(+)

Comments

Jarkko Sakkinen Dec. 31, 2022, 3:32 p.m. UTC | #1
On Wed, Dec 14, 2022 at 01:40:17PM -0600, Michael Roth wrote:
> From: Brijesh Singh <brijesh.singh@amd.com>
> 
> Before SNP VMs can be launched, the platform must be appropriately
> configured and initialized. Platform initialization is accomplished via
> the SNP_INIT command. Make sure to do a WBINVD and issue DF_FLUSH
> command to prepare for the first SNP guest launch after INIT.
> 
> During the execution of SNP_INIT command, the firmware configures
> and enables SNP security policy enforcement in many system components.
> Some system components write to regions of memory reserved by early
> x86 firmware (e.g. UEFI). Other system components write to regions
> provided by the operation system, hypervisor, or x86 firmware.
> Such system components can only write to HV-fixed pages or Default
> pages. They will error when attempting to write to other page states
> after SNP_INIT enables their SNP enforcement.
> 
> Starting in SNP firmware v1.52, the SNP_INIT_EX command takes a list of
> system physical address ranges to convert into the HV-fixed page states
> during the RMP initialization. If INIT_RMP is 1, hypervisors should
> provide all system physical address ranges that the hypervisor will
> never assign to a guest until the next RMP re-initialization.
> For instance, the memory that UEFI reserves should be included in the
> range list. This allows system components that occasionally write to
> memory (e.g. logging to UEFI reserved regions) to not fail due to
> RMP initialization and SNP enablement.
> 
> Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
> Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
> Signed-off-by: Michael Roth <michael.roth@amd.com>
> ---
>  drivers/crypto/ccp/sev-dev.c | 225 +++++++++++++++++++++++++++++++++++
>  drivers/crypto/ccp/sev-dev.h |   2 +
>  include/linux/psp-sev.h      |  17 +++
>  3 files changed, 244 insertions(+)
> 
> diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
> index 9d84720a41d7..af20420bd6c2 100644
> --- a/drivers/crypto/ccp/sev-dev.c
> +++ b/drivers/crypto/ccp/sev-dev.c
> @@ -26,6 +26,7 @@
>  #include <linux/fs_struct.h>
>  
>  #include <asm/smp.h>
> +#include <asm/e820/types.h>
>  
>  #include "psp-dev.h"
>  #include "sev-dev.h"
> @@ -34,6 +35,10 @@
>  #define SEV_FW_FILE		"amd/sev.fw"
>  #define SEV_FW_NAME_SIZE	64
>  
> +/* Minimum firmware version required for the SEV-SNP support */
> +#define SNP_MIN_API_MAJOR	1
> +#define SNP_MIN_API_MINOR	51
> +
>  static DEFINE_MUTEX(sev_cmd_mutex);
>  static struct sev_misc_dev *misc_dev;
>  
> @@ -76,6 +81,13 @@ static void *sev_es_tmr;
>  #define NV_LENGTH (32 * 1024)
>  static void *sev_init_ex_buffer;
>  
> +/*
> + * SEV_DATA_RANGE_LIST:
> + *   Array containing range of pages that firmware transitions to HV-fixed
> + *   page state.
> + */
> +struct sev_data_range_list *snp_range_list;
> +
>  static inline bool sev_version_greater_or_equal(u8 maj, u8 min)
>  {
>  	struct sev_device *sev = psp_master->sev_data;
> @@ -830,6 +842,186 @@ static int sev_update_firmware(struct device *dev)
>  	return ret;
>  }
>  
> +static void snp_set_hsave_pa(void *arg)
> +{
> +	wrmsrl(MSR_VM_HSAVE_PA, 0);
> +}
> +
> +static int snp_filter_reserved_mem_regions(struct resource *rs, void *arg)
> +{
> +	struct sev_data_range_list *range_list = arg;
> +	struct sev_data_range *range = &range_list->ranges[range_list->num_elements];
> +	size_t size;
> +
> +	if ((range_list->num_elements * sizeof(struct sev_data_range) +
> +	     sizeof(struct sev_data_range_list)) > PAGE_SIZE)
> +		return -E2BIG;
> +
> +	switch (rs->desc) {
> +	case E820_TYPE_RESERVED:
> +	case E820_TYPE_PMEM:
> +	case E820_TYPE_ACPI:
> +		range->base = rs->start & PAGE_MASK;
> +		size = (rs->end + 1) - rs->start;
> +		range->page_count = size >> PAGE_SHIFT;
> +		range_list->num_elements++;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int __sev_snp_init_locked(int *error)
> +{
> +	struct psp_device *psp = psp_master;
> +	struct sev_data_snp_init_ex data;
> +	struct sev_device *sev;
> +	int rc = 0;
> +
> +	if (!psp || !psp->sev_data)
> +		return -ENODEV;
> +
> +	sev = psp->sev_data;
> +
> +	if (sev->snp_initialized)
> +		return 0;

Shouldn't this follow this check:

        if (sev->state == SEV_STATE_INIT) {
                /* debug printk about possible incorrect call order */
                return -ENODEV;
        }

It is game over for SNP, if SEV_CMD_INIT{_EX} got first, which means that
this should not proceed.

BR, Jarkko
Jarkko Sakkinen Jan. 4, 2023, 12:12 p.m. UTC | #2
On Wed, Dec 14, 2022 at 01:40:17PM -0600, Michael Roth wrote:
> +	/*
> +	 * If boot CPU supports SNP, then first attempt to initialize
> +	 * the SNP firmware.
> +	 */
> +	if (cpu_feature_enabled(X86_FEATURE_SEV_SNP)) {
> +		if (!sev_version_greater_or_equal(SNP_MIN_API_MAJOR, SNP_MIN_API_MINOR)) {
> +			dev_err(sev->dev, "SEV-SNP support requires firmware version >= %d:%d\n",
> +				SNP_MIN_API_MAJOR, SNP_MIN_API_MINOR);
> +		} else {
> +			rc = sev_snp_init(&error, true);
> +			if (rc) {
> +				/*
> +				 * Don't abort the probe if SNP INIT failed,
> +				 * continue to initialize the legacy SEV firmware.
> +				 */
> +				dev_err(sev->dev, "SEV-SNP: failed to INIT error %#x\n", error);
> +			}
> +		}
> +	}

I think this is not right as there is a dep between sev init and this,
and there is about a dozen of call sites already __sev_platform_init_locked().

Instead there should be __sev_snp_init_locked() that would be called as
part of __sev_platform_init_locked() flow.

Also TMR allocation should be moved inside __sev_platform_init_locked,
given that it needs to be marked into RMP after SNP init.

BR, Jarkko
Kalra, Ashish Jan. 5, 2023, 10:54 p.m. UTC | #3
Hello Jarkko,

On 1/4/2023 6:12 AM, Jarkko Sakkinen wrote:
> On Wed, Dec 14, 2022 at 01:40:17PM -0600, Michael Roth wrote:
>> +	/*
>> +	 * If boot CPU supports SNP, then first attempt to initialize
>> +	 * the SNP firmware.
>> +	 */
>> +	if (cpu_feature_enabled(X86_FEATURE_SEV_SNP)) {
>> +		if (!sev_version_greater_or_equal(SNP_MIN_API_MAJOR, SNP_MIN_API_MINOR)) {
>> +			dev_err(sev->dev, "SEV-SNP support requires firmware version >= %d:%d\n",
>> +				SNP_MIN_API_MAJOR, SNP_MIN_API_MINOR);
>> +		} else {
>> +			rc = sev_snp_init(&error, true);
>> +			if (rc) {
>> +				/*
>> +				 * Don't abort the probe if SNP INIT failed,
>> +				 * continue to initialize the legacy SEV firmware.
>> +				 */
>> +				dev_err(sev->dev, "SEV-SNP: failed to INIT error %#x\n", error);
>> +			}
>> +		}
>> +	}
> 
> I think this is not right as there is a dep between sev init and this,
> and there is about a dozen of call sites already __sev_platform_init_locked().
> 

sev_init ?

As this is invoked during CCP module load/initialization, shouldn't this 
get invoked before any other call sites invoking 
__sev_platform_init_locked() ?

Thanks,
Ashish

> Instead there should be __sev_snp_init_locked() that would be called as
> part of __sev_platform_init_locked() flow.
> 
> Also TMR allocation should be moved inside __sev_platform_init_locked,
> given that it needs to be marked into RMP after SNP init.
> 
> BR, Jarkko
>
Jarkko Sakkinen Jan. 20, 2023, 10:19 p.m. UTC | #4
On Thu, Jan 05, 2023 at 04:40:29PM -0600, Kalra, Ashish wrote:
> Hello Jarkko,
> 
> On 12/31/2022 9:32 AM, Jarkko Sakkinen wrote:
> > On Wed, Dec 14, 2022 at 01:40:17PM -0600, Michael Roth wrote:
> > > From: Brijesh Singh <brijesh.singh@amd.com>
> > > 
> > > Before SNP VMs can be launched, the platform must be appropriately
> > > configured and initialized. Platform initialization is accomplished via
> > > the SNP_INIT command. Make sure to do a WBINVD and issue DF_FLUSH
> > > command to prepare for the first SNP guest launch after INIT.
> > > 
> > > During the execution of SNP_INIT command, the firmware configures
> > > and enables SNP security policy enforcement in many system components.
> > > Some system components write to regions of memory reserved by early
> > > x86 firmware (e.g. UEFI). Other system components write to regions
> > > provided by the operation system, hypervisor, or x86 firmware.
> > > Such system components can only write to HV-fixed pages or Default
> > > pages. They will error when attempting to write to other page states
> > > after SNP_INIT enables their SNP enforcement.
> > > 
> > > Starting in SNP firmware v1.52, the SNP_INIT_EX command takes a list of
> > > system physical address ranges to convert into the HV-fixed page states
> > > during the RMP initialization. If INIT_RMP is 1, hypervisors should
> > > provide all system physical address ranges that the hypervisor will
> > > never assign to a guest until the next RMP re-initialization.
> > > For instance, the memory that UEFI reserves should be included in the
> > > range list. This allows system components that occasionally write to
> > > memory (e.g. logging to UEFI reserved regions) to not fail due to
> > > RMP initialization and SNP enablement.
> > > 
> > > Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
> > > Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
> > > Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
> > > Signed-off-by: Michael Roth <michael.roth@amd.com>
> > > ---
> > >   drivers/crypto/ccp/sev-dev.c | 225 +++++++++++++++++++++++++++++++++++
> > >   drivers/crypto/ccp/sev-dev.h |   2 +
> > >   include/linux/psp-sev.h      |  17 +++
> > >   3 files changed, 244 insertions(+)
> > > 
> > > diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
> > > index 9d84720a41d7..af20420bd6c2 100644
> > > --- a/drivers/crypto/ccp/sev-dev.c
> > > +++ b/drivers/crypto/ccp/sev-dev.c
> > > @@ -26,6 +26,7 @@
> > >   #include <linux/fs_struct.h>
> > >   #include <asm/smp.h>
> > > +#include <asm/e820/types.h>
> > >   #include "psp-dev.h"
> > >   #include "sev-dev.h"
> > > @@ -34,6 +35,10 @@
> > >   #define SEV_FW_FILE		"amd/sev.fw"
> > >   #define SEV_FW_NAME_SIZE	64
> > > +/* Minimum firmware version required for the SEV-SNP support */
> > > +#define SNP_MIN_API_MAJOR	1
> > > +#define SNP_MIN_API_MINOR	51
> > > +
> > >   static DEFINE_MUTEX(sev_cmd_mutex);
> > >   static struct sev_misc_dev *misc_dev;
> > > @@ -76,6 +81,13 @@ static void *sev_es_tmr;
> > >   #define NV_LENGTH (32 * 1024)
> > >   static void *sev_init_ex_buffer;
> > > +/*
> > > + * SEV_DATA_RANGE_LIST:
> > > + *   Array containing range of pages that firmware transitions to HV-fixed
> > > + *   page state.
> > > + */
> > > +struct sev_data_range_list *snp_range_list;
> > > +
> > >   static inline bool sev_version_greater_or_equal(u8 maj, u8 min)
> > >   {
> > >   	struct sev_device *sev = psp_master->sev_data;
> > > @@ -830,6 +842,186 @@ static int sev_update_firmware(struct device *dev)
> > >   	return ret;
> > >   }
> > > +static void snp_set_hsave_pa(void *arg)
> > > +{
> > > +	wrmsrl(MSR_VM_HSAVE_PA, 0);
> > > +}
> > > +
> > > +static int snp_filter_reserved_mem_regions(struct resource *rs, void *arg)
> > > +{
> > > +	struct sev_data_range_list *range_list = arg;
> > > +	struct sev_data_range *range = &range_list->ranges[range_list->num_elements];
> > > +	size_t size;
> > > +
> > > +	if ((range_list->num_elements * sizeof(struct sev_data_range) +
> > > +	     sizeof(struct sev_data_range_list)) > PAGE_SIZE)
> > > +		return -E2BIG;
> > > +
> > > +	switch (rs->desc) {
> > > +	case E820_TYPE_RESERVED:
> > > +	case E820_TYPE_PMEM:
> > > +	case E820_TYPE_ACPI:
> > > +		range->base = rs->start & PAGE_MASK;
> > > +		size = (rs->end + 1) - rs->start;
> > > +		range->page_count = size >> PAGE_SHIFT;
> > > +		range_list->num_elements++;
> > > +		break;
> > > +	default:
> > > +		break;
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int __sev_snp_init_locked(int *error)
> > > +{
> > > +	struct psp_device *psp = psp_master;
> > > +	struct sev_data_snp_init_ex data;
> > > +	struct sev_device *sev;
> > > +	int rc = 0;
> > > +
> > > +	if (!psp || !psp->sev_data)
> > > +		return -ENODEV;
> > > +
> > > +	sev = psp->sev_data;
> > > +
> > > +	if (sev->snp_initialized)
> > > +		return 0;
> > 
> > Shouldn't this follow this check:
> > 
> >          if (sev->state == SEV_STATE_INIT) {
> >                  /* debug printk about possible incorrect call order */
> >                  return -ENODEV;
> >          }
> > 
> > It is game over for SNP, if SEV_CMD_INIT{_EX} got first, which means that
> > this should not proceed.
> 
> 
> But, how will SEV_CMD_INIT_EX happen before as sev_pci_init() which is
> invoked during CCP module load/initialization, will first try to do
> sev_snp_init() if SNP is supported, before it invokes sev_platform_init() to
> do SEV firmware initialization ?

Because the symbol is exported outside the driver to be called by other
subsystems, you need to have a santiy check for the call order, as it
is a hardware constraint. Otherwise, any unconsidered change in either
side could unknowingily break the kernel.

Alternatively, you could choose not to export sev_snp_init(). It is
supported by the fact that the call in sev_guest_init() is does nothing
useful (for the reasons you already wrote).

BR, Jarkko
Jarkko Sakkinen Jan. 20, 2023, 10:56 p.m. UTC | #5
On Thu, Jan 05, 2023 at 04:54:23PM -0600, Kalra, Ashish wrote:
> Hello Jarkko,
> 
> On 1/4/2023 6:12 AM, Jarkko Sakkinen wrote:
> > On Wed, Dec 14, 2022 at 01:40:17PM -0600, Michael Roth wrote:
> > > +	/*
> > > +	 * If boot CPU supports SNP, then first attempt to initialize
> > > +	 * the SNP firmware.
> > > +	 */
> > > +	if (cpu_feature_enabled(X86_FEATURE_SEV_SNP)) {
> > > +		if (!sev_version_greater_or_equal(SNP_MIN_API_MAJOR, SNP_MIN_API_MINOR)) {
> > > +			dev_err(sev->dev, "SEV-SNP support requires firmware version >= %d:%d\n",
> > > +				SNP_MIN_API_MAJOR, SNP_MIN_API_MINOR);
> > > +		} else {
> > > +			rc = sev_snp_init(&error, true);
> > > +			if (rc) {
> > > +				/*
> > > +				 * Don't abort the probe if SNP INIT failed,
> > > +				 * continue to initialize the legacy SEV firmware.
> > > +				 */
> > > +				dev_err(sev->dev, "SEV-SNP: failed to INIT error %#x\n", error);
> > > +			}
> > > +		}
> > > +	}
> > 
> > I think this is not right as there is a dep between sev init and this,
> > and there is about a dozen of call sites already __sev_platform_init_locked().
> > 
> 
> sev_init ?
> 
> As this is invoked during CCP module load/initialization, shouldn't this get
> invoked before any other call sites invoking __sev_platform_init_locked() ?

Then it should not be exported because this the only working call site.

However, the benefit of __sev_platform_init_locked() addressing SNP init is
that psp_init_on_probe can also postpone SNP init without possibility to
any side effects (other call sites than sev_guest_init()).

BR, Jarkko
diff mbox series

Patch

diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
index 9d84720a41d7..af20420bd6c2 100644
--- a/drivers/crypto/ccp/sev-dev.c
+++ b/drivers/crypto/ccp/sev-dev.c
@@ -26,6 +26,7 @@ 
 #include <linux/fs_struct.h>
 
 #include <asm/smp.h>
+#include <asm/e820/types.h>
 
 #include "psp-dev.h"
 #include "sev-dev.h"
@@ -34,6 +35,10 @@ 
 #define SEV_FW_FILE		"amd/sev.fw"
 #define SEV_FW_NAME_SIZE	64
 
+/* Minimum firmware version required for the SEV-SNP support */
+#define SNP_MIN_API_MAJOR	1
+#define SNP_MIN_API_MINOR	51
+
 static DEFINE_MUTEX(sev_cmd_mutex);
 static struct sev_misc_dev *misc_dev;
 
@@ -76,6 +81,13 @@  static void *sev_es_tmr;
 #define NV_LENGTH (32 * 1024)
 static void *sev_init_ex_buffer;
 
+/*
+ * SEV_DATA_RANGE_LIST:
+ *   Array containing range of pages that firmware transitions to HV-fixed
+ *   page state.
+ */
+struct sev_data_range_list *snp_range_list;
+
 static inline bool sev_version_greater_or_equal(u8 maj, u8 min)
 {
 	struct sev_device *sev = psp_master->sev_data;
@@ -830,6 +842,186 @@  static int sev_update_firmware(struct device *dev)
 	return ret;
 }
 
+static void snp_set_hsave_pa(void *arg)
+{
+	wrmsrl(MSR_VM_HSAVE_PA, 0);
+}
+
+static int snp_filter_reserved_mem_regions(struct resource *rs, void *arg)
+{
+	struct sev_data_range_list *range_list = arg;
+	struct sev_data_range *range = &range_list->ranges[range_list->num_elements];
+	size_t size;
+
+	if ((range_list->num_elements * sizeof(struct sev_data_range) +
+	     sizeof(struct sev_data_range_list)) > PAGE_SIZE)
+		return -E2BIG;
+
+	switch (rs->desc) {
+	case E820_TYPE_RESERVED:
+	case E820_TYPE_PMEM:
+	case E820_TYPE_ACPI:
+		range->base = rs->start & PAGE_MASK;
+		size = (rs->end + 1) - rs->start;
+		range->page_count = size >> PAGE_SHIFT;
+		range_list->num_elements++;
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int __sev_snp_init_locked(int *error)
+{
+	struct psp_device *psp = psp_master;
+	struct sev_data_snp_init_ex data;
+	struct sev_device *sev;
+	int rc = 0;
+
+	if (!psp || !psp->sev_data)
+		return -ENODEV;
+
+	sev = psp->sev_data;
+
+	if (sev->snp_initialized)
+		return 0;
+
+	/*
+	 * The SNP_INIT requires the MSR_VM_HSAVE_PA must be set to 0h
+	 * across all cores.
+	 */
+	on_each_cpu(snp_set_hsave_pa, NULL, 1);
+
+	/*
+	 * Starting in SNP firmware v1.52, the SNP_INIT_EX command takes a list of
+	 * system physical address ranges to convert into the HV-fixed page states
+	 * during the RMP initialization.  For instance, the memory that UEFI
+	 * reserves should be included in the range list. This allows system
+	 * components that occasionally write to memory (e.g. logging to UEFI
+	 * reserved regions) to not fail due to RMP initialization and SNP enablement.
+	 */
+	if (sev_version_greater_or_equal(SNP_MIN_API_MAJOR, 52)) {
+		/*
+		 * Firmware checks that the pages containing the ranges enumerated
+		 * in the RANGES structure are either in the Default page state or in the
+		 * firmware page state.
+		 */
+		snp_range_list = sev_fw_alloc(PAGE_SIZE);
+		if (!snp_range_list) {
+			dev_err(sev->dev,
+				"SEV: SNP_INIT_EX range list memory allocation failed\n");
+			return -ENOMEM;
+		}
+
+		memset(snp_range_list, 0, PAGE_SIZE);
+
+		/*
+		 * Retrieve all reserved memory regions setup by UEFI from the e820 memory map
+		 * to be setup as HV-fixed pages.
+		 */
+
+		rc = walk_iomem_res_desc(IORES_DESC_NONE, IORESOURCE_MEM, 0, ~0,
+					 snp_range_list, snp_filter_reserved_mem_regions);
+		if (rc) {
+			dev_err(sev->dev,
+				"SEV: SNP_INIT_EX walk_iomem_res_desc failed rc = %d\n", rc);
+			return rc;
+		}
+
+		memset(&data, 0, sizeof(data));
+		data.init_rmp = 1;
+		data.list_paddr_en = 1;
+		data.list_paddr = __pa(snp_range_list);
+
+		rc = __sev_do_cmd_locked(SEV_CMD_SNP_INIT_EX, &data, error);
+		if (rc)
+			return rc;
+	} else {
+		rc = __sev_do_cmd_locked(SEV_CMD_SNP_INIT, NULL, error);
+		if (rc)
+			return rc;
+	}
+
+	/* Prepare for first SNP guest launch after INIT */
+	wbinvd_on_all_cpus();
+	rc = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, error);
+	if (rc)
+		return rc;
+
+	sev->snp_initialized = true;
+	dev_dbg(sev->dev, "SEV-SNP firmware initialized\n");
+
+	return rc;
+}
+
+int sev_snp_init(int *error, bool init_on_probe)
+{
+	int rc;
+
+	if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP))
+		return -ENODEV;
+
+	if (init_on_probe && !psp_init_on_probe)
+		return 0;
+
+	mutex_lock(&sev_cmd_mutex);
+	rc = __sev_snp_init_locked(error);
+	mutex_unlock(&sev_cmd_mutex);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(sev_snp_init);
+
+static int __sev_snp_shutdown_locked(int *error)
+{
+	struct sev_device *sev = psp_master->sev_data;
+	struct sev_data_snp_shutdown_ex data;
+	int ret;
+
+	if (!sev->snp_initialized)
+		return 0;
+
+	memset(&data, 0, sizeof(data));
+	data.length = sizeof(data);
+	data.iommu_snp_shutdown = 1;
+
+	wbinvd_on_all_cpus();
+
+retry:
+	ret = __sev_do_cmd_locked(SEV_CMD_SNP_SHUTDOWN_EX, &data, error);
+	/* SHUTDOWN may require DF_FLUSH */
+	if (*error == SEV_RET_DFFLUSH_REQUIRED) {
+		ret = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, NULL);
+		if (ret) {
+			dev_err(sev->dev, "SEV-SNP DF_FLUSH failed\n");
+			return ret;
+		}
+		goto retry;
+	}
+	if (ret) {
+		dev_err(sev->dev, "SEV-SNP firmware shutdown failed\n");
+		return ret;
+	}
+
+	sev->snp_initialized = false;
+	dev_dbg(sev->dev, "SEV-SNP firmware shutdown\n");
+
+	return ret;
+}
+
+static int sev_snp_shutdown(int *error)
+{
+	int rc;
+
+	mutex_lock(&sev_cmd_mutex);
+	rc = __sev_snp_shutdown_locked(error);
+	mutex_unlock(&sev_cmd_mutex);
+
+	return rc;
+}
+
 static int sev_ioctl_do_pek_import(struct sev_issue_cmd *argp, bool writable)
 {
 	struct sev_device *sev = psp_master->sev_data;
@@ -1270,6 +1462,8 @@  int sev_dev_init(struct psp_device *psp)
 
 static void sev_firmware_shutdown(struct sev_device *sev)
 {
+	int error;
+
 	sev_platform_shutdown(NULL);
 
 	if (sev_es_tmr) {
@@ -1286,6 +1480,14 @@  static void sev_firmware_shutdown(struct sev_device *sev)
 			   get_order(NV_LENGTH));
 		sev_init_ex_buffer = NULL;
 	}
+
+	if (snp_range_list) {
+		free_pages((unsigned long)snp_range_list,
+			   get_order(PAGE_SIZE));
+		snp_range_list = NULL;
+	}
+
+	sev_snp_shutdown(&error);
 }
 
 void sev_dev_destroy(struct psp_device *psp)
@@ -1341,6 +1543,26 @@  void sev_pci_init(void)
 		}
 	}
 
+	/*
+	 * If boot CPU supports SNP, then first attempt to initialize
+	 * the SNP firmware.
+	 */
+	if (cpu_feature_enabled(X86_FEATURE_SEV_SNP)) {
+		if (!sev_version_greater_or_equal(SNP_MIN_API_MAJOR, SNP_MIN_API_MINOR)) {
+			dev_err(sev->dev, "SEV-SNP support requires firmware version >= %d:%d\n",
+				SNP_MIN_API_MAJOR, SNP_MIN_API_MINOR);
+		} else {
+			rc = sev_snp_init(&error, true);
+			if (rc) {
+				/*
+				 * Don't abort the probe if SNP INIT failed,
+				 * continue to initialize the legacy SEV firmware.
+				 */
+				dev_err(sev->dev, "SEV-SNP: failed to INIT error %#x\n", error);
+			}
+		}
+	}
+
 	/* Obtain the TMR memory area for SEV-ES use */
 	sev_es_tmr = sev_fw_alloc(SEV_ES_TMR_SIZE);
 	if (!sev_es_tmr)
@@ -1356,6 +1578,9 @@  void sev_pci_init(void)
 		dev_err(sev->dev, "SEV: failed to INIT error %#x, rc %d\n",
 			error, rc);
 
+	dev_info(sev->dev, "SEV%s API:%d.%d build:%d\n", sev->snp_initialized ?
+		"-SNP" : "", sev->api_major, sev->api_minor, sev->build);
+
 	return;
 
 err:
diff --git a/drivers/crypto/ccp/sev-dev.h b/drivers/crypto/ccp/sev-dev.h
index 666c21eb81ab..34767657beb5 100644
--- a/drivers/crypto/ccp/sev-dev.h
+++ b/drivers/crypto/ccp/sev-dev.h
@@ -52,6 +52,8 @@  struct sev_device {
 	u8 build;
 
 	void *cmd_buf;
+
+	bool snp_initialized;
 };
 
 int sev_dev_init(struct psp_device *psp);
diff --git a/include/linux/psp-sev.h b/include/linux/psp-sev.h
index 31b045e1926f..8cfe92e82743 100644
--- a/include/linux/psp-sev.h
+++ b/include/linux/psp-sev.h
@@ -794,6 +794,21 @@  struct sev_data_snp_shutdown_ex {
  */
 int sev_platform_init(int *error);
 
+/**
+ * sev_snp_init - perform SEV SNP_INIT command
+ *
+ * @error: SEV command return code
+ * @init_on_probe: indicates if called during module probe/init
+ *
+ * Returns:
+ * 0 if the SEV successfully processed the command
+ * -%ENODEV    if the SEV device is not available
+ * -%ENOTSUPP  if the SEV does not support SEV
+ * -%ETIMEDOUT if the SEV command timed out
+ * -%EIO       if the SEV returned a non-zero return code
+ */
+int sev_snp_init(int *error, bool init_on_probe);
+
 /**
  * sev_platform_status - perform SEV PLATFORM_STATUS command
  *
@@ -901,6 +916,8 @@  sev_platform_status(struct sev_user_data_status *status, int *error) { return -E
 
 static inline int sev_platform_init(int *error) { return -ENODEV; }
 
+static inline int sev_snp_init(int *error, bool init_on_probe) { return -ENODEV; }
+
 static inline int
 sev_guest_deactivate(struct sev_data_deactivate *data, int *error) { return -ENODEV; }