Provide nonallocating folio range lookup and carry PFN attributes separately from requested KVM memory attributes Signed-off-by: Aneesh Kumar K.V (Arm) --- include/linux/guest_memfd.h | 10 ++++-- samples/kvm/mem_provider_sample.c | 6 ++-- virt/kvm/guest_memfd.c | 60 ++++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h index b63ca59773f9..d3b153a3d891 100644 --- a/include/linux/guest_memfd.h +++ b/include/linux/guest_memfd.h @@ -60,7 +60,10 @@ struct guest_memfd_handle { * is present. If backing memory is not available, return * -ENOENT. Return a negative error code on other failure. * The provider must define @get_pfn when @lookup_pfn_range - * is defined. + * is defined, or omit both to use the folio helper. + * Lookup must not allocate memory or prepare it for access. + * PFNs remain stable under the inode invalidate lock and + * any provider lock held by conversion/reclaim callers. * @put_pfn_range: Release a range of PFNs previously obtained for a given * index. The provider does not need to ensure that backing * memory is present. If backing memory is not available, the @@ -79,8 +82,9 @@ struct guest_memfd_provider_operations { void (*invalidate_folio)(void *provider, struct folio *folio); int (*get_pfn)(struct inode *inode, void *provider, pgoff_t index, struct guest_memfd_handle *handle); - int (*lookup_pfn_range)(void *provider, pgoff_t index, - unsigned long *pfn, size_t *nr_pages); + int (*lookup_pfn_range)(struct inode *inode, void *provider, pgoff_t index, + unsigned long *pfn, size_t *nr_pages, + u64 *pfn_attrs); void (*put_pfn_range)(void *provider, pgoff_t index, size_t nr_pages); }; diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c index f1b0698268c0..8eca914c49ef 100644 --- a/samples/kvm/mem_provider_sample.c +++ b/samples/kvm/mem_provider_sample.c @@ -197,8 +197,9 @@ static int mps_gmem_get_pfn(struct inode *inode, void *provider, return 0; } -static int mps_gmem_lookup_pfn_range(void *provider, pgoff_t index, - unsigned long *pfn, size_t *nr_pages) +static int mps_gmem_lookup_pfn_range(struct inode *inode, void *provider, + pgoff_t index, unsigned long *pfn, + size_t *nr_pages, u64 *pfn_attrs) { struct file *resource_file = provider; struct mps_info *info = resource_file->private_data; @@ -211,6 +212,7 @@ static int mps_gmem_lookup_pfn_range(void *provider, pgoff_t index, *pfn = info->base_pfn + index; *nr_pages = 1; + *pfn_attrs = 0; return 0; } diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c index 5fda14a53488..ad9cc1bbe5d7 100644 --- a/virt/kvm/guest_memfd.c +++ b/virt/kvm/guest_memfd.c @@ -52,6 +52,8 @@ struct gmem_inode { static int gmem_folio_get_pfn(struct inode *inode, void *provider, pgoff_t index, struct guest_memfd_handle *handle); +static int gmem_folio_lookup_pfn_range(struct inode *inode, void *provider, + pgoff_t index, unsigned long *pfn, size_t *nr_pages, u64 *pfn_attrs); static __always_inline struct gmem_inode *GMEM_I(struct inode *inode) { @@ -88,6 +90,7 @@ static const struct guest_memfd_provider_operations gmem_native_provider_ops = { .attach = gmem_native_attach, .alloc_folio = gmem_native_alloc_folio, .get_pfn = gmem_folio_get_pfn, + .lookup_pfn_range = gmem_folio_lookup_pfn_range, }; static bool gmem_provider_ops_valid(struct gmem_inode *gi, @@ -129,8 +132,10 @@ static void gmem_provider_init_ops(struct gmem_inode *gi, { gi->provider_operations = *ops; if (ops->alloc_folio) { - if (!ops->get_pfn) + if (!ops->get_pfn) { gi->provider_operations.get_pfn = gmem_folio_get_pfn; + gi->provider_operations.lookup_pfn_range = gmem_folio_lookup_pfn_range; + } } gi->provider_ops = &gi->provider_operations; } @@ -239,13 +244,15 @@ static void gmem_put_handle(struct guest_memfd_handle *handle) #if defined(CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT) || defined(CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM) static int gmem_provider_lookup_pfn_range(struct gmem_inode *gi, pgoff_t index, - kvm_pfn_t *pfn, size_t *nr_pages) + kvm_pfn_t *pfn, size_t *nr_pages, u64 *pfn_attrs) { unsigned long raw_pfn; int r; - r = gi->provider_ops->lookup_pfn_range(gi->provider, index, &raw_pfn, - nr_pages); + /* Initialize the length in case an error leaves it unset by the provider. */ + *nr_pages = 0; + r = gi->provider_ops->lookup_pfn_range(&gi->vfs_inode, gi->provider, index, &raw_pfn, + nr_pages, pfn_attrs); if (r) return r; @@ -433,6 +440,38 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index) return folio; } +/* Conversion holds invalidate_lock for write, excluding truncation/allocation. */ +static int gmem_folio_lookup_pfn_range(struct inode *inode, void *provider, + pgoff_t index, unsigned long *pfn, size_t *nr_pages, u64 *pfn_attrs) +{ + struct folio *folio; + + lockdep_assert_held_write(&inode->i_mapping->invalidate_lock); + folio = filemap_get_folio(inode->i_mapping, index); + if (IS_ERR(folio)) { + struct folio_batch fbatch; + pgoff_t end = i_size_read(inode) >> PAGE_SHIFT; + pgoff_t next = index; + + if (PTR_ERR(folio) != -ENOENT) + return PTR_ERR(folio); + + folio_batch_init(&fbatch); + if (filemap_get_folios(inode->i_mapping, &next, end - 1, &fbatch)) { + end = fbatch.folios[0]->index; + folio_batch_release(&fbatch); + } + *nr_pages = end - index; + return -ENOENT; + } + + *pfn = folio_file_pfn(folio, index); + *nr_pages = folio_next_index(folio) - index; + *pfn_attrs = 0; + folio_put(folio); + return 0; +} + static enum kvm_gfn_range_filter kvm_gmem_get_all_gfns_filter(struct inode *inode) { if (gmem_in_place_conversion) @@ -525,11 +564,14 @@ static void kvm_gmem_pfn_reclaim(struct gmem_inode *gi, pgoff_t start, pgoff_t e size_t step = 1; size_t nr_pages; kvm_pfn_t pfn; + u64 pfn_attrs; + int ret; - if (!gmem_provider_lookup_pfn_range(gi, index, &pfn, &nr_pages)) { - step = min_t(size_t, nr_pages, end - index); + ret = gmem_provider_lookup_pfn_range(gi, index, &pfn, &nr_pages, + &pfn_attrs); + step = min_t(size_t, max_t(size_t, nr_pages, 1), end - index); + if (!ret && !(pfn_attrs & GUEST_MEMFD_PFN_MMIO)) kvm_arch_gmem_reclaim(pfn, step); - } index += step; cond_resched(); @@ -1068,8 +1110,10 @@ static void kvm_gmem_make_shared(struct inode *inode, pgoff_t start, pgoff_t end size_t step = 1; size_t nr_pages; kvm_pfn_t pfn; + u64 pfn_attrs; - if (!gmem_provider_lookup_pfn_range(gi, index, &pfn, &nr_pages)) { + if (!gmem_provider_lookup_pfn_range(gi, index, &pfn, &nr_pages, + &pfn_attrs)) { step = min_t(size_t, nr_pages, end - index); kvm_arch_gmem_make_shared(pfn, step); } -- 2.43.0