[RFC Part2 PATCH 17/30] KVM: SVM: add KVM_SEV_SNP_LAUNCH_START command
From: Brijesh Singh
Date: Wed Mar 24 2021 - 13:06:56 EST
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 spec section 4.5 and 8.11.
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: Ingo Molnar <mingo@xxxxxxxxxx>
Cc: Borislav Petkov <bp@xxxxxxxxx>
Cc: Joerg Roedel <jroedel@xxxxxxx>
Cc: "H. Peter Anvin" <hpa@xxxxxxxxx>
Cc: Tony Luck <tony.luck@xxxxxxxxx>
Cc: Dave Hansen <dave.hansen@xxxxxxxxx>
Cc: "Peter Zijlstra (Intel)" <peterz@xxxxxxxxxxxxx>
Cc: Paolo Bonzini <pbonzini@xxxxxxxxxx>
Cc: Tom Lendacky <thomas.lendacky@xxxxxxx>
Cc: David Rientjes <rientjes@xxxxxxxxxx>
Cc: Sean Christopherson <seanjc@xxxxxxxxxx>
Cc: Vitaly Kuznetsov <vkuznets@xxxxxxxxxx>
Cc: Wanpeng Li <wanpengli@xxxxxxxxxxx>
Cc: Jim Mattson <jmattson@xxxxxxxxxx>
Cc: x86@xxxxxxxxxx
Cc: kvm@xxxxxxxxxxxxxxx
Signed-off-by: Brijesh Singh <brijesh.singh@xxxxxxx>
---
arch/x86/kvm/svm/sev.c | 221 ++++++++++++++++++++++++++++++++++++++-
arch/x86/kvm/svm/svm.h | 1 +
include/uapi/linux/kvm.h | 8 ++
3 files changed, 229 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 36042a2b19b3..7652e57f7e01 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -37,6 +37,9 @@ static unsigned int min_sev_asid;
static unsigned long *sev_asid_bitmap;
static unsigned long *sev_reclaim_asid_bitmap;
+static void snp_free_context_page(struct page *page);
+static int snp_decommission_context(struct kvm *kvm);
+
struct enc_region {
struct list_head list;
unsigned long npages;
@@ -1069,6 +1072,181 @@ static int sev_snp_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp)
return 0;
}
+static int snp_page_reclaim(struct page *page, int rmppage_size)
+{
+ struct sev_data_snp_page_reclaim *data;
+ struct rmpupdate e = {};
+ int rc, error;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL_ACCOUNT);
+ if (!data)
+ return -ENOMEM;
+
+ data->paddr = __sme_page_pa(page) | rmppage_size;
+ rc = sev_snp_reclaim(data, &error);
+ if (rc)
+ goto e_free;
+
+ rc = rmptable_rmpupdate(page, &e);
+
+e_free:
+ kfree(data);
+
+ return rc;
+}
+
+static void snp_free_context_page(struct page *page)
+{
+ /* Reclaim the page before changing the attribute */
+ if (snp_page_reclaim(page, RMP_PG_SIZE_4K)) {
+ pr_info("SEV-SNP: failed to reclaim page, leaking it.\n");
+ return;
+ }
+
+ __free_page(page);
+}
+
+static struct page *snp_alloc_context_page(void)
+{
+ struct rmpupdate val = {};
+ struct page *page = NULL;
+ int rc;
+
+ page = alloc_page(GFP_KERNEL);
+ if (!page)
+ return NULL;
+
+ /* Transition the context page to the firmware state.*/
+ val.immutable = 1;
+ val.assigned = 1;
+ val.pagesize = RMP_PG_SIZE_4K;
+ rc = rmptable_rmpupdate(page, &val);
+ if (rc)
+ goto e_free;
+
+ return page;
+
+e_free:
+ __free_page(page);
+
+ return NULL;
+}
+
+static struct page *snp_context_create(struct kvm *kvm, struct kvm_sev_cmd *argp)
+{
+ struct sev_data_snp_gctx_create *data;
+ struct page *context = NULL;
+ int rc;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL_ACCOUNT);
+ if (!data)
+ return NULL;
+
+ /* Allocate memory for context page */
+ context = snp_alloc_context_page();
+ if (!context)
+ goto e_free;
+
+ data->gctx_paddr = __sme_page_pa(context);
+ rc = __sev_issue_cmd(argp->sev_fd, SEV_CMD_SNP_GCTX_CREATE, data, &argp->error);
+ if (rc) {
+ snp_free_context_page(context);
+ context = NULL;
+ }
+
+e_free:
+ kfree(data);
+
+ 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_activate *data;
+ int asid = sev_get_asid(kvm);
+ int ret, retry_count = 0;
+
+ data = kmalloc(sizeof(*data), GFP_KERNEL_ACCOUNT);
+ if (!data)
+ return -ENOMEM;
+
+ /* Activate ASID on the given context */
+ data->gctx_paddr = __sme_page_pa(sev->snp_context);
+ 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 = sev_guest_snp_df_flush(error);
+ up_read(&sev_deactivate_lock);
+
+ if (ret)
+ goto e_free;
+
+ /* only one retry */
+ retry_count = 1;
+
+ goto again;
+ }
+
+e_free:
+ kfree(data);
+
+ 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_start *start;
+ struct kvm_sev_snp_launch_start params;
+ int rc;
+
+ if (!sev_snp_guest(kvm))
+ return -ENOTTY;
+
+ if (copy_from_user(¶ms, (void __user *)(uintptr_t)argp->data, sizeof(params)))
+ return -EFAULT;
+
+ /* Initialize the guest context */
+ sev->snp_context = snp_context_create(kvm, argp);
+ if (!sev->snp_context)
+ return -ENOTTY;
+
+ rc = -ENOMEM;
+ start = kzalloc(sizeof(*start), GFP_KERNEL_ACCOUNT);
+ if (!start)
+ goto e_free_context;
+
+ /* Issue the LAUNCH_START command */
+ start->gctx_paddr = __sme_page_pa(sev->snp_context);
+ start->policy = params.policy;
+ 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;
+
+ goto e_free_start;
+
+e_free_context:
+ snp_decommission_context(kvm);
+
+e_free_start:
+ kfree(start);
+
+ return rc;
+}
+
int svm_mem_enc_op(struct kvm *kvm, void __user *argp)
{
struct kvm_sev_cmd sev_cmd;
@@ -1122,6 +1300,9 @@ int svm_mem_enc_op(struct kvm *kvm, void __user *argp)
case KVM_SEV_SNP_INIT:
r = sev_snp_guest_init(kvm, &sev_cmd);
break;
+ case KVM_SEV_SNP_LAUNCH_START:
+ r = snp_launch_start(kvm, &sev_cmd);
+ break;
default:
r = -EINVAL;
goto out;
@@ -1241,6 +1422,36 @@ int svm_unregister_enc_region(struct kvm *kvm,
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 = kzalloc(sizeof(*data), GFP_KERNEL_ACCOUNT);
+ if (!data)
+ return -ENOMEM;
+
+ data->gctx_paddr = __sme_page_pa(sev->snp_context);
+ ret = sev_guest_snp_decommission(data, NULL);
+ if (ret) {
+ pr_err("SEV-SNP: failed to decommission context, leaking the context page\n");
+ return ret;
+ }
+
+ /* free the context page now */
+ snp_free_context_page(sev->snp_context);
+ sev->snp_context = NULL;
+
+ kfree(data);
+
+ return 0;
+}
+
void sev_vm_destroy(struct kvm *kvm)
{
struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
@@ -1273,7 +1484,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("SEV-SNP: failed to free guest context, leaking asid!\n");
+ return;
+ }
+ } else {
+ sev_unbind_asid(kvm, sev->handle);
+ }
+
sev_asid_free(sev->asid);
}
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 9d41735699c6..97efdca498ed 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -80,6 +80,7 @@ struct kvm_sev_info {
unsigned long pages_locked; /* Number of pages locked */
struct list_head regions_list; /* List of registered regions */
u64 ap_jump_table; /* SEV-ES AP Jump Table address */
+ struct page *snp_context; /* SNP guest context page */
};
struct kvm_svm {
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index e0e7dd71a863..84a242597d81 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1596,6 +1596,7 @@ enum sev_cmd_id {
/* SNP specific commands */
KVM_SEV_SNP_INIT,
+ KVM_SEV_SNP_LAUNCH_START,
KVM_SEV_NR_MAX,
};
@@ -1648,6 +1649,13 @@ struct kvm_sev_dbg {
__u32 len;
};
+struct kvm_sev_snp_launch_start {
+ __u64 policy;
+ __u64 ma_uaddr;
+ __u8 ma_en;
+ __u8 imi_en;
+};
+
#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