[PATCH RFC v2 40/50] KVM: guest_memfd: Support put_pfn_range provider op in PFN mode

From: Ackerley Tng via B4 Relay

Date: Thu Oct 22 2026 - 19:39:26 EST


From: Ackerley Tng <ackerleytng@xxxxxxxxxx>

When punching holes or destroying guest_memfd files in folio mode,
folios are truncated and freed, allowing the backing filesystem to free
or reset memory.

In PFN mode, memory is not backed by page cache folios. To inform the
provider that a physical range has been released by guest_memfd,
introduce the .put_pfn_range callback to struct
guest_memfd_provider_operations.

Invoke .put_pfn_range() after kvm_gmem_pfn_reclaim() during both hole
punching and inode destruction, passing the file index and page count.

Signed-off-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
---
include/linux/guest_memfd.h | 5 +++++
virt/kvm/guest_memfd.c | 16 ++++++++++++++--
2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index 2f1c679dd154c..3c0cc35f748bc 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -33,6 +33,10 @@ struct mempolicy;
* -ENOENT. Return a negative error code on other failure.
* The provider must define @get_pfn when @lookup_pfn_range
* is defined.
+ * @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
+ * provider should skip the unbacked range.
*
* Used by filesystems and device drivers that provide memory for guest_memfd.
*/
@@ -46,6 +50,7 @@ struct guest_memfd_provider_operations {
int *max_order);
int (*lookup_pfn_range)(void *provider, pgoff_t index,
unsigned long *pfn, size_t *nr_pages);
+ void (*put_pfn_range)(void *provider, pgoff_t index, size_t nr_pages);
};

#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 88eae9d6d249f..5741ff6ab94c6 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -171,6 +171,13 @@ static void gmem_provider_invalidate_folio(struct gmem_inode *gi,
gi->provider_ops->invalidate_folio(gi->provider, folio);
}

+static void gmem_provider_put_pfn_range(struct gmem_inode *gi, pgoff_t index,
+ size_t nr_pages)
+{
+ if (gi->provider_ops && gi->provider_ops->put_pfn_range)
+ gi->provider_ops->put_pfn_range(gi->provider, index, nr_pages);
+}
+
static void gmem_provider_release(struct gmem_inode *gi)
{
if (gi->provider_ops && gi->provider_ops->release)
@@ -426,6 +433,7 @@ static long kvm_gmem_punch_hole(struct inode *inode, loff_t offset, loff_t len)
if (kvm_gmem_is_pfn_mode(GMEM_I(inode))) {
unmap_mapping_range(inode->i_mapping, offset, len, 0);
kvm_gmem_pfn_reclaim(GMEM_I(inode), start, end);
+ gmem_provider_put_pfn_range(GMEM_I(inode), start, end - start);
} else {
truncate_inode_pages_range(inode->i_mapping, offset, offset + len - 1);
}
@@ -1647,8 +1655,12 @@ static void kvm_gmem_destroy_inode(struct inode *inode)
* Skip if the provider was not yet set up, as iput() can reach this on
* error paths before provider_ops is initialized.
*/
- if (gi->provider_ops && kvm_gmem_is_pfn_mode(gi))
- kvm_gmem_pfn_reclaim(gi, 0, i_size_read(inode) >> PAGE_SHIFT);
+ if (gi->provider_ops && kvm_gmem_is_pfn_mode(gi)) {
+ pgoff_t nr_pages = i_size_read(inode) >> PAGE_SHIFT;
+
+ kvm_gmem_pfn_reclaim(gi, 0, nr_pages);
+ gmem_provider_put_pfn_range(gi, 0, nr_pages);
+ }

gmem_provider_release(gi);


--
2.56.0.257.gf7497c7d27-goog