Thread (90 messages) 90 messages, 8 authors, 2025-08-16

Re: [PATCH v9 19/43] arm64: RME: Allow populating initial contents

From: Steven Price <steven.price@arm.com>
Date: 2025-08-13 09:30:08
Also in: kvm, kvmarm, linux-coco, lkml

On 01/08/2025 02:56, Vishal Annapurve wrote:
On Wed, Jun 11, 2025 at 3:59 AM Steven Price [off-list ref] wrote:
quoted

+static int realm_create_protected_data_page(struct realm *realm,
+                                           unsigned long ipa,
+                                           kvm_pfn_t dst_pfn,
+                                           kvm_pfn_t src_pfn,
+                                           unsigned long flags)
+{
+       unsigned long rd = virt_to_phys(realm->rd);
+       phys_addr_t dst_phys, src_phys;
+       bool undelegate_failed = false;
+       int ret, offset;
+
+       dst_phys = __pfn_to_phys(dst_pfn);
+       src_phys = __pfn_to_phys(src_pfn);
+
+       for (offset = 0; offset < PAGE_SIZE; offset += RMM_PAGE_SIZE) {
+               ret = realm_create_protected_data_granule(realm,
+                                                         ipa,
+                                                         dst_phys,
+                                                         src_phys,
+                                                         flags);
+               if (ret)
+                       goto err;
+
+               ipa += RMM_PAGE_SIZE;
+               dst_phys += RMM_PAGE_SIZE;
+               src_phys += RMM_PAGE_SIZE;
+       }
+
+       return 0;
+
+err:
+       if (ret == -EIO) {
+               /* current offset needs undelegating */
+               if (WARN_ON(rmi_granule_undelegate(dst_phys)))
+                       undelegate_failed = true;
+       }
+       while (offset > 0) {
+               ipa -= RMM_PAGE_SIZE;
+               offset -= RMM_PAGE_SIZE;
+               dst_phys -= RMM_PAGE_SIZE;
+
+               rmi_data_destroy(rd, ipa, NULL, NULL);
+
+               if (WARN_ON(rmi_granule_undelegate(dst_phys)))
+                       undelegate_failed = true;
+       }
+
+       if (undelegate_failed) {
+               /*
+                * A granule could not be undelegated,
+                * so the page has to be leaked
+                */
+               get_page(pfn_to_page(dst_pfn));
I would like to point out that the support for in-place conversion
with guest_memfd using hugetlb pages [1] is under discussion.

As part of the in-place conversion, the policy we are routing for is
to avoid any "refcounts" from KVM on folios supplied by guest_memfd as
in-place conversion works by splitting and merging folios during
memory conversion as per discussion at LPC [2].
CCA doesn't really support "in-place" conversions (see more detail
below). But here the issue is that something has gone wrong and the RMM
is refusing to give us a page back.
The best way to avoid further use of this page with huge page support
around would be either:
1) Explicitly Inform guest_memfd of a particular pfn being in use by
KVM without relying on page refcounts or
This might work, but note that the page is unavailable even after user
space has freed the guest_memfd. So at some point the page needs to be
marked so that it cannot be reallocated by the kernel. Holding a
refcount isn't ideal but I haven't come up with a better idea.

Note that this is a "should never happen" situation - the code will have
WARN()ed already - so this is just a best effort to allow the system to
limp on.
2) Set the page as hwpoisoned. (Needs further discussion)
This certainly sounds like a closer fit - but I'm not very familiar with
hwpoison so I don't know how easy it would be to integrate with this.
This page refcounting strategy will have to be revisited depending on
which series lands first. That being said, it would be great if ARM
could review/verify if the series [1] works for backing CCA VMs with
huge pages.

[1] https://lore.kernel.org/kvm/cover.1747264138.git.ackerleytng@google.com/ (local)
[2] https://lpc.events/event/18/contributions/1764/
quoted
+       }
+
+       return -ENXIO;
+}
+
+static int populate_region(struct kvm *kvm,
+                          phys_addr_t ipa_base,
+                          phys_addr_t ipa_end,
+                          unsigned long data_flags)
+{
+       struct realm *realm = &kvm->arch.realm;
+       struct kvm_memory_slot *memslot;
+       gfn_t base_gfn, end_gfn;
+       int idx;
+       phys_addr_t ipa = ipa_base;
+       int ret = 0;
+
+       base_gfn = gpa_to_gfn(ipa_base);
+       end_gfn = gpa_to_gfn(ipa_end);
+
+       idx = srcu_read_lock(&kvm->srcu);
+       memslot = gfn_to_memslot(kvm, base_gfn);
+       if (!memslot) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       /* We require the region to be contained within a single memslot */
+       if (memslot->base_gfn + memslot->npages < end_gfn) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (!kvm_slot_can_be_private(memslot)) {
+               ret = -EPERM;
+               goto out;
+       }
+
+       while (ipa < ipa_end) {
+               struct vm_area_struct *vma;
+               unsigned long hva;
+               struct page *page;
+               bool writeable;
+               kvm_pfn_t pfn;
+               kvm_pfn_t priv_pfn;
+               struct page *gmem_page;
+
+               hva = gfn_to_hva_memslot(memslot, gpa_to_gfn(ipa));
+               vma = vma_lookup(current->mm, hva);
+               if (!vma) {
+                       ret = -EFAULT;
+                       break;
+               }
+
+               pfn = __kvm_faultin_pfn(memslot, gpa_to_gfn(ipa), FOLL_WRITE,
+                                       &writeable, &page);
Is this assuming double backing of guest memory ranges? Is this logic
trying to simulate a shared fault?
Yes and yes...
Does memory population work with CCA if priv_pfn and pfn are the same?
I am curious how the memory population will work with in-place
conversion support available for guest_memfd files.
The RMM interface doesn't support an in-place conversion. The
RMI_DATA_CREATE operation takes the PA of the already delegated
granule[1] along with the PA of a non-delegated granule with the data.

So to mimic an in-place conversion requires copying the data from the
page, delegating the (original) page and then using RMI_DATA_CREATE
which copies the data back. Fundamentally because there may be memory
encryption involved there is going to be a requirement for this double
memcpy() approach. Note that this is only relevant during the initial
setup phase - CCA doesn't (at least currently) permit populated pages to
be provided to the guest when it is running.

The approach this series takes pre-dates the guest_memfd discussions and
so is assuming that the shared memory is not (directly) provided by the
guest_memfd but is using the user space pointer provided in the memslot.
It would be possible (with the patches proposed) for the VMM to mmap()
the guest_memfd when the memory is being shared so as to reuse the
physical pages.

I do also plan to look at supporting the use of the guest_memfd for the
shared memory directly. But I've been waiting for the discussions to
conclude before attempting to implement that.

[1] A 'granule' is the RMM's idea of a page size (RMM_PAGE_SIZE), which
is currently (RMM v1.0) always 4k. So may be different when Linux is
running with a larger page size.

Thanks,
Steve
quoted
+
+               if (is_error_pfn(pfn)) {
+                       ret = -EFAULT;
+                       break;
+               }
+
+               ret = kvm_gmem_get_pfn(kvm, memslot,
+                                      ipa >> PAGE_SHIFT,
+                                      &priv_pfn, &gmem_page, NULL);
+               if (ret)
+                       break;
+
+               ret = realm_create_protected_data_page(realm, ipa,
+                                                      priv_pfn,
+                                                      pfn,
+                                                      data_flags);
+
+               kvm_release_page_clean(page);
+
+               if (ret)
+                       break;
+
+               ipa += PAGE_SIZE;
+       }
+
+out:
+       srcu_read_unlock(&kvm->srcu, idx);
+       return ret;
+}
+
  
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help