On 3/29/24 10:24 PM, Michael Roth wrote: > During guest run-time, kvm_arch_gmem_prepare() is issued as needed to > prepare newly-allocated gmem pages prior to mapping them into the guest. > In the case of SEV-SNP, this mainly involves setting the pages to > private in the RMP table. > > However, for the GPA ranges comprising the initial guest payload, which > are encrypted/measured prior to starting the guest, the gmem pages need > to be accessed prior to setting them to private in the RMP table so they > can be initialized with the userspace-provided data. Additionally, an > SNP firmware call is needed afterward to encrypt them in-place and > measure the contents into the guest's launch digest. > > While it is possible to bypass the kvm_arch_gmem_prepare() hooks so that > this handling can be done in an open-coded/vendor-specific manner, this > may expose more gmem-internal state/dependencies to external callers > than necessary. Try to avoid this by implementing an interface that > tries to handle as much of the common functionality inside gmem as > possible, while also making it generic enough to potentially be > usable/extensible for use-cases beyond just SEV-SNP. > > Suggested-by: Sean Christopherson <seanjc@xxxxxxxxxx> > Signed-off-by: Michael Roth <michael.roth@xxxxxxx> > --- > include/linux/kvm_host.h | 40 ++++++++++++++++++++++++++++++++++++++++ > virt/kvm/guest_memfd.c | 40 ++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 80 insertions(+) > > diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h > index 5b8308b5e4af..8a75787090f3 100644 > --- a/include/linux/kvm_host.h > +++ b/include/linux/kvm_host.h > @@ -2473,4 +2473,44 @@ bool kvm_arch_gmem_prepare_needed(struct kvm *kvm); > void kvm_arch_gmem_invalidate(kvm_pfn_t start, kvm_pfn_t end); > #endif > > +/** > + * kvm_gmem_populate_args - kvm_gmem_populate() argument structure > + * > + * @gfn: starting GFN to be populated > + * @src: userspace-provided buffer containing data to copy into GFN range > + * @npages: number of pages to copy from userspace-buffer > + * @do_memcpy: whether to do a direct memcpy of the data prior to issuing > + * the post-populate callback > + * @post_populate: callback to issue for each gmem page that backs the GPA > + * range (which will be filled with corresponding contents from > + * @src if @do_memcpy was set) > + * @opaque: opaque data to pass to @post_populate callback > + */ > +struct kvm_gmem_populate_args { > + gfn_t gfn; > + void __user *src; > + int npages; > + bool do_memcpy; > + int (*post_populate)(struct kvm *kvm, struct kvm_memory_slot *slot, > + gfn_t gfn, kvm_pfn_t pfn, void __user *src, int order, > + void *opaque); > + void *opaque; > +}; > + > +/** > + * kvm_gmem_populate() - Populate/prepare a GPA range with guest data > + * > + * @kvm: KVM instance > + * @slot: slot containing the GPA range being prepared > + * @args: argument structure > + * > + * This is primarily intended for cases where a gmem-backed GPA range needs > + * to be initialized with userspace-provided data prior to being mapped into > + * the guest as a private page. This should be called with the slots->lock > + * held so that caller-enforced invariants regarding the expected memory > + * attributes of the GPA range do not race with KVM_SET_MEMORY_ATTRIBUTES. > + */ > +int kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot, > + struct kvm_gmem_populate_args *args); > + > #endif > diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c > index 3668a5f1d82b..3e3c4b7fff3b 100644 > --- a/virt/kvm/guest_memfd.c > +++ b/virt/kvm/guest_memfd.c > @@ -643,3 +643,43 @@ int kvm_gmem_undo_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, > return r; > } > EXPORT_SYMBOL_GPL(kvm_gmem_undo_get_pfn); > + > +int kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot, > + struct kvm_gmem_populate_args *args) > +{ > + int ret, max_order, i; > + > + for (i = 0; i < args->npages; i += (1 << max_order)) { > + void __user *src = args->src + i * PAGE_SIZE; > + gfn_t gfn = args->gfn + i; > + kvm_pfn_t pfn; > + > + ret = __kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &max_order, false); > + if (ret) > + break; > + > + if (!IS_ALIGNED(gfn, (1 << max_order)) || > + (args->npages - i) < (1 << max_order)) > + max_order = 0; > + > + if (args->do_memcpy && args->src) { > + ret = copy_from_user(pfn_to_kaddr(pfn), src, (1 << max_order) * PAGE_SIZE); > + if (ret) > + goto e_release;> + } > + > + if (args->post_populate) { > + ret = args->post_populate(kvm, slot, gfn, pfn, src, max_order, > + args->opaque); > + if (ret) > + goto e_release; This if (ret) goto seems unnecessary, was there more code before the label in a previous version? With that we could also change this block to "if (!ret && args->post_populate)" and remove the first goto and the label. > + } > +e_release: > + put_page(pfn_to_page(pfn)); > + if (ret) > + break; > + } > + > + return ret; > +} > +EXPORT_SYMBOL_GPL(kvm_gmem_populate);