From: Brijesh Singh <brijesh.singh@xxxxxxx> 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. Signed-off-by: Brijesh Singh <brijesh.singh@xxxxxxx> Signed-off by: Ashish Kalra <ashish.kalra@xxxxxxx> --- drivers/crypto/ccp/sev-dev.c | 121 +++++++++++++++++++++++++++++++++++ drivers/crypto/ccp/sev-dev.h | 2 + include/linux/psp-sev.h | 16 +++++ 3 files changed, 139 insertions(+) diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c index 9cb3265f3bef..f1173221d0b9 100644 --- a/drivers/crypto/ccp/sev-dev.c +++ b/drivers/crypto/ccp/sev-dev.c @@ -33,6 +33,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; @@ -775,6 +779,98 @@ 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 __sev_snp_init_locked(int *error) +{ + struct psp_device *psp = psp_master; + struct sev_device *sev; + int rc = 0; + + if (!psp || !psp->sev_data) + return -ENODEV; + + sev = psp->sev_data; + + if (sev->snp_inited) + 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); + + /* Issue the SNP_INIT firmware command. */ + 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_inited = true; + dev_dbg(sev->dev, "SEV-SNP firmware initialized\n"); + + return rc; +} + +int sev_snp_init(int *error) +{ + int rc; + + if (!cpu_feature_enabled(X86_FEATURE_SEV_SNP)) + return -ENODEV; + + 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; + int ret; + + if (!sev->snp_inited) + return 0; + + /* SHUTDOWN requires the DF_FLUSH */ + wbinvd_on_all_cpus(); + __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, NULL); + + ret = __sev_do_cmd_locked(SEV_CMD_SNP_SHUTDOWN, NULL, error); + if (ret) { + dev_err(sev->dev, "SEV-SNP firmware shutdown failed\n"); + return ret; + } + + sev->snp_inited = 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(NULL); + 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; @@ -1231,6 +1327,8 @@ static void sev_firmware_shutdown(struct sev_device *sev) get_order(NV_LENGTH)); sev_init_ex_buffer = NULL; } + + sev_snp_shutdown(NULL); } void sev_dev_destroy(struct psp_device *psp) @@ -1287,6 +1385,26 @@ void sev_pci_init(void) } } + /* + * If boot CPU supports the 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); + if (rc) { + /* + * If we failed to INIT SNP then don't abort the probe. + * 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) @@ -1302,6 +1420,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_inited ? + "-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..186ad20cbd24 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_inited; }; int sev_dev_init(struct psp_device *psp); diff --git a/include/linux/psp-sev.h b/include/linux/psp-sev.h index 01ba9dc46ca3..ef4d42e8c96e 100644 --- a/include/linux/psp-sev.h +++ b/include/linux/psp-sev.h @@ -769,6 +769,20 @@ struct sev_data_snp_init_ex { */ int sev_platform_init(int *error); +/** + * sev_snp_init - perform SEV SNP_INIT command + * + * @error: SEV command return code + * + * 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); + /** * sev_platform_status - perform SEV PLATFORM_STATUS command * @@ -876,6 +890,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) { return -ENODEV; } + static inline int sev_guest_deactivate(struct sev_data_deactivate *data, int *error) { return -ENODEV; } -- 2.25.1