--- v3
+++ v4
@@ -1,198 +1,271 @@
-The KVM_SEV_SNP_LAUNCH_FINISH finalize the cryptographic digest and stores
-it as the measurement of the guest at launch.
-
-While finalizing the launch flow, it also issues the LAUNCH_UPDATE command
-to encrypt the VMSA pages.
+KVM_SEV_SNP_LAUNCH_START begins the launch process for an SEV-SNP guest.
+The command initializes a cryptographic digest context used to construct
+the measurement of the guest. If the guest is expected to be migrated,
+the command also binds a migration agent (MA) to the guest.
+
+For more information see the SEV-SNP specification.
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
- arch/x86/kvm/svm/sev.c | 125 +++++++++++++++++++++++++++++++++++++++
- include/uapi/linux/kvm.h | 13 ++++
- 2 files changed, 138 insertions(+)
-
+ .../virt/kvm/amd-memory-encryption.rst | 25 ++++
+ arch/x86/kvm/svm/sev.c | 132 +++++++++++++++++-
+ arch/x86/kvm/svm/svm.h | 1 +
+ include/uapi/linux/kvm.h | 9 ++
+ 4 files changed, 166 insertions(+), 1 deletion(-)
+
+diff --git a/Documentation/virt/kvm/amd-memory-encryption.rst b/Documentation/virt/kvm/amd-memory-encryption.rst
+index 75ca60b6d40a..8620383d405a 100644
+--- a/Documentation/virt/kvm/amd-memory-encryption.rst
++++ b/Documentation/virt/kvm/amd-memory-encryption.rst
+@@ -443,6 +443,31 @@ Returns: 0 on success, -negative on error
+ __u64 flags; /* must be zero */
+ };
+
++
++19. KVM_SNP_LAUNCH_START
++------------------------
++
++The KVM_SNP_LAUNCH_START command is used for creating the memory encryption
++context for the SEV-SNP guest. To create the encryption context, user must
++provide a guest policy, migration agent (if any) and guest OS visible
++workarounds value as defined SEV-SNP specification.
++
++Parameters (in): struct kvm_snp_launch_start
++
++Returns: 0 on success, -negative on error
++
++::
++
++ struct kvm_sev_snp_launch_start {
++ __u64 policy; /* Guest policy to use. */
++ __u64 ma_uaddr; /* userspace address of migration agent */
++ __u8 ma_en; /* 1 if the migtation agent is enabled */
++ __u8 imi_en; /* set IMI to 1. */
++ __u8 gosvw[16]; /* guest OS visible workarounds */
++ };
++
++See the SEV-SNP specification for further detail on the launch input.
++
+ References
+ ==========
+
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
-index 6b7c8287eada..856a6cf99a61 100644
+index be31221f0a47..f44a657e8912 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
-@@ -1741,6 +1741,111 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
- return ret;
+@@ -20,6 +20,7 @@
+ #include <asm/fpu/internal.h>
+
+ #include <asm/trapnr.h>
++#include <asm/sev.h>
+
+ #include "x86.h"
+ #include "svm.h"
+@@ -75,6 +76,8 @@ static unsigned long sev_me_mask;
+ static unsigned long *sev_asid_bitmap;
+ static unsigned long *sev_reclaim_asid_bitmap;
+
++static int snp_decommission_context(struct kvm *kvm);
++
+ struct enc_region {
+ struct list_head list;
+ unsigned long npages;
+@@ -1527,6 +1530,100 @@ static int sev_receive_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)
+ return sev_issue_cmd(kvm, SEV_CMD_RECEIVE_FINISH, &data, &argp->error);
}
-+static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
++static void *snp_context_create(struct kvm *kvm, struct kvm_sev_cmd *argp)
++{
++ struct sev_data_snp_gctx_create data = {};
++ void *context;
++ int rc;
++
++ /* Allocate memory for context page */
++ context = snp_alloc_firmware_page(GFP_KERNEL_ACCOUNT);
++ if (!context)
++ return NULL;
++
++ data.gctx_paddr = __psp_pa(context);
++ rc = __sev_issue_cmd(argp->sev_fd, SEV_CMD_SNP_GCTX_CREATE, &data, &argp->error);
++ if (rc) {
++ snp_free_firmware_page(context);
++ return NULL;
++ }
++
++ return context;
++}
++
++static int snp_bind_asid(struct kvm *kvm, int *error)
+{
+ struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
-+ struct sev_data_snp_launch_update data = {};
-+ int i, ret;
-+
++ struct sev_data_snp_activate data = {};
++ int asid = sev_get_asid(kvm);
++ int ret, retry_count = 0;
++
++ /* Activate ASID on the given context */
+ data.gctx_paddr = __psp_pa(sev->snp_context);
-+ data.page_type = SNP_PAGE_TYPE_VMSA;
-+
-+ for (i = 0; i < kvm->created_vcpus; i++) {
-+ struct vcpu_svm *svm = to_svm(kvm->vcpus[i]);
-+ struct rmpupdate e = {};
-+
-+ /* Perform some pre-encryption checks against the VMSA */
-+ ret = sev_es_sync_vmsa(svm);
++ data.asid = asid;
++again:
++ ret = sev_issue_cmd(kvm, SEV_CMD_SNP_ACTIVATE, &data, error);
++
++ /* Check if the DF_FLUSH is required, and try again */
++ if (ret && (*error == SEV_RET_DFFLUSH_REQUIRED) && (!retry_count)) {
++ /* Guard DEACTIVATE against WBINVD/DF_FLUSH used in ASID recycling */
++ down_read(&sev_deactivate_lock);
++ wbinvd_on_all_cpus();
++ ret = snp_guest_df_flush(error);
++ up_read(&sev_deactivate_lock);
++
+ if (ret)
+ return ret;
+
-+ /* Transition the VMSA page to a firmware state. */
-+ e.assigned = 1;
-+ e.immutable = 1;
-+ e.asid = sev->asid;
-+ e.gpa = -1;
-+ e.pagesize = RMP_PG_SIZE_4K;
-+ ret = rmpupdate(virt_to_page(svm->vmsa), &e);
-+ if (ret)
-+ return ret;
-+
-+ /* Issue the SNP command to encrypt the VMSA */
-+ data.address = __sme_pa(svm->vmsa);
-+ ret = __sev_issue_cmd(argp->sev_fd, SEV_CMD_SNP_LAUNCH_UPDATE,
-+ &data, &argp->error);
-+ if (ret) {
-+ snp_page_reclaim(virt_to_page(svm->vmsa), RMP_PG_SIZE_4K);
-+ return ret;
-+ }
-+
-+ svm->vcpu.arch.guest_state_protected = true;
++ /* only one retry */
++ retry_count = 1;
++
++ goto again;
+ }
+
-+ return 0;
-+}
-+
-+static int snp_launch_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)
++ return ret;
++}
++
++static int snp_launch_start(struct kvm *kvm, struct kvm_sev_cmd *argp)
+{
+ struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
-+ struct sev_data_snp_launch_finish *data;
-+ void *id_block = NULL, *id_auth = NULL;
-+ struct kvm_sev_snp_launch_finish params;
-+ int ret;
++ struct sev_data_snp_launch_start start = {};
++ struct kvm_sev_snp_launch_start params;
++ int rc;
+
+ if (!sev_snp_guest(kvm))
+ return -ENOTTY;
+
-+ if (!sev->snp_context)
-+ return -EINVAL;
-+
+ if (copy_from_user(¶ms, (void __user *)(uintptr_t)argp->data, sizeof(params)))
+ return -EFAULT;
+
-+ /* Measure all vCPUs using LAUNCH_UPDATE before we finalize the launch flow. */
-+ ret = snp_launch_update_vmsa(kvm, argp);
-+ if (ret)
-+ return ret;
-+
-+ data = kzalloc(sizeof(*data), GFP_KERNEL_ACCOUNT);
-+ if (!data)
-+ return -ENOMEM;
-+
-+ if (params.id_block_en) {
-+ id_block = psp_copy_user_blob(params.id_block_uaddr, KVM_SEV_SNP_ID_BLOCK_SIZE);
-+ if (IS_ERR(id_block)) {
-+ ret = PTR_ERR(id_block);
-+ goto e_free;
-+ }
-+
-+ data->id_block_en = 1;
-+ data->id_block_paddr = __sme_pa(id_block);
-+ }
-+
-+ if (params.auth_key_en) {
-+ id_auth = psp_copy_user_blob(params.id_auth_uaddr, KVM_SEV_SNP_ID_AUTH_SIZE);
-+ if (IS_ERR(id_auth)) {
-+ ret = PTR_ERR(id_auth);
-+ goto e_free_id_block;
-+ }
-+
-+ data->auth_key_en = 1;
-+ data->id_auth_paddr = __sme_pa(id_auth);
-+ }
-+
-+ data->gctx_paddr = __psp_pa(sev->snp_context);
-+ ret = sev_issue_cmd(kvm, SEV_CMD_SNP_LAUNCH_FINISH, data, &argp->error);
-+
-+ kfree(id_auth);
-+
-+e_free_id_block:
-+ kfree(id_block);
-+
-+e_free:
-+ kfree(data);
-+
-+ return ret;
++ /* Initialize the guest context */
++ sev->snp_context = snp_context_create(kvm, argp);
++ if (!sev->snp_context)
++ return -ENOTTY;
++
++ /* Issue the LAUNCH_START command */
++ start.gctx_paddr = __psp_pa(sev->snp_context);
++ start.policy = params.policy;
++ memcpy(start.gosvw, params.gosvw, sizeof(params.gosvw));
++ rc = __sev_issue_cmd(argp->sev_fd, SEV_CMD_SNP_LAUNCH_START, &start, &argp->error);
++ if (rc)
++ goto e_free_context;
++
++ /* Bind ASID to this guest */
++ sev->fd = argp->sev_fd;
++ rc = snp_bind_asid(kvm, &argp->error);
++ if (rc)
++ goto e_free_context;
++
++ return 0;
++
++e_free_context:
++ snp_decommission_context(kvm);
++
++ return rc;
+}
+
int svm_mem_enc_op(struct kvm *kvm, void __user *argp)
{
struct kvm_sev_cmd sev_cmd;
-@@ -1836,6 +1941,9 @@ int svm_mem_enc_op(struct kvm *kvm, void __user *argp)
- case KVM_SEV_SNP_LAUNCH_UPDATE:
- r = snp_launch_update(kvm, &sev_cmd);
+@@ -1616,6 +1713,9 @@ int svm_mem_enc_op(struct kvm *kvm, void __user *argp)
+ case KVM_SEV_RECEIVE_FINISH:
+ r = sev_receive_finish(kvm, &sev_cmd);
break;
-+ case KVM_SEV_SNP_LAUNCH_FINISH:
-+ r = snp_launch_finish(kvm, &sev_cmd);
++ case KVM_SEV_SNP_LAUNCH_START:
++ r = snp_launch_start(kvm, &sev_cmd);
+ break;
default:
r = -EINVAL;
goto out;
-@@ -2324,8 +2432,25 @@ void sev_free_vcpu(struct kvm_vcpu *vcpu)
-
- if (vcpu->arch.guest_state_protected)
- sev_flush_guest_memory(svm, svm->vmsa, PAGE_SIZE);
-+
-+ /*
-+ * If its an SNP guest, then VMSA was added in the RMP entry as a guest owned page.
-+ * Transition the page to hyperivosr state before releasing it back to the system.
-+ */
-+ if (sev_snp_guest(vcpu->kvm)) {
-+ struct rmpupdate e = {};
-+ int rc;
-+
-+ rc = rmpupdate(virt_to_page(svm->vmsa), &e);
-+ if (rc) {
-+ pr_err("Failed to release SNP guest VMSA page (rc %d), leaking it\n", rc);
-+ goto skip_vmsa_free;
+@@ -1809,6 +1909,28 @@ int svm_vm_copy_asid_from(struct kvm *kvm, unsigned int source_fd)
+ return ret;
+ }
+
++static int snp_decommission_context(struct kvm *kvm)
++{
++ struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
++ struct sev_data_snp_decommission data = {};
++ int ret;
++
++ /* If context is not created then do nothing */
++ if (!sev->snp_context)
++ return 0;
++
++ data.gctx_paddr = __sme_pa(sev->snp_context);
++ ret = snp_guest_decommission(&data, NULL);
++ if (ret)
++ return ret;
++
++ /* free the context page now */
++ snp_free_firmware_page(sev->snp_context);
++ sev->snp_context = NULL;
++
++ return 0;
++}
++
+ void sev_vm_destroy(struct kvm *kvm)
+ {
+ struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
+@@ -1847,7 +1969,15 @@ void sev_vm_destroy(struct kvm *kvm)
+
+ mutex_unlock(&kvm->lock);
+
+- sev_unbind_asid(kvm, sev->handle);
++ if (sev_snp_guest(kvm)) {
++ if (snp_decommission_context(kvm)) {
++ pr_err("Failed to free SNP guest context, leaking asid!\n");
++ return;
+ }
++ } else {
++ sev_unbind_asid(kvm, sev->handle);
+ }
+
- __free_page(virt_to_page(svm->vmsa));
-
-+skip_vmsa_free:
- if (svm->ghcb_sa_free)
- kfree(svm->ghcb_sa);
+ sev_asid_free(sev);
}
+
+diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
+index b9ea99f8579e..bc5582b44356 100644
+--- a/arch/x86/kvm/svm/svm.h
++++ b/arch/x86/kvm/svm/svm.h
+@@ -67,6 +67,7 @@ struct kvm_sev_info {
+ u64 ap_jump_table; /* SEV-ES AP Jump Table address */
+ struct kvm *enc_context_owner; /* Owner of copied encryption context */
+ struct misc_cg *misc_cg; /* For misc cgroup accounting */
++ void *snp_context; /* SNP guest context page */
+ };
+
+ struct kvm_svm {
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
-index 8890d5a340be..8db12055b8b9 100644
+index 989a64aa1ae5..dbd05179d8fa 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
-@@ -1682,6 +1682,7 @@ enum sev_cmd_id {
- KVM_SEV_SNP_INIT = 255,
- KVM_SEV_SNP_LAUNCH_START,
- KVM_SEV_SNP_LAUNCH_UPDATE,
-+ KVM_SEV_SNP_LAUNCH_FINISH,
+@@ -1680,6 +1680,7 @@ enum sev_cmd_id {
+
+ /* SNP specific commands */
+ KVM_SEV_SNP_INIT = 256,
++ KVM_SEV_SNP_LAUNCH_START,
KVM_SEV_NR_MAX,
};
-@@ -1693,6 +1694,18 @@ struct kvm_sev_cmd {
- __u32 sev_fd;
+@@ -1781,6 +1782,14 @@ struct kvm_snp_init {
+ __u64 flags;
};
-+#define KVM_SEV_SNP_ID_BLOCK_SIZE 96
-+#define KVM_SEV_SNP_ID_AUTH_SIZE 4096
-+#define KVM_SEV_SNP_FINISH_DATA_SIZE 32
-+
-+struct kvm_sev_snp_launch_finish {
-+ __u64 id_block_uaddr;
-+ __u64 id_auth_uaddr;
-+ __u8 id_block_en;
-+ __u8 auth_key_en;
-+ __u8 host_data[KVM_SEV_SNP_FINISH_DATA_SIZE];
++struct kvm_sev_snp_launch_start {
++ __u64 policy;
++ __u64 ma_uaddr;
++ __u8 ma_en;
++ __u8 imi_en;
++ __u8 gosvw[16];
+};
+
- struct kvm_sev_launch_start {
- __u32 handle;
- __u32 policy;
+ #define KVM_DEV_ASSIGN_ENABLE_IOMMU (1 << 0)
+ #define KVM_DEV_ASSIGN_PCI_2_3 (1 << 1)
+ #define KVM_DEV_ASSIGN_MASK_INTX (1 << 2)
--
2.17.1