[PATCH RFC v2 43/50] KVM: guest_memfd: Export kvm_gmem_revoke for provider-driven revocation
From: Ackerley Tng via B4 Relay
Date: Thu Oct 22 2026 - 19:44:36 EST
From: Ackerley Tng <ackerleytng@xxxxxxxxxx>
Providers may dynamically revoke physical memory ranges (e.g., dynamic
page removal or device teardown) and need a mechanism to sever guest and
host mappings and reclaim confidential architecture state.
Introduce __kvm_gmem_revoke() to share the core invalidation and
reclamation path between kvm_gmem_punch_hole() and kvm_gmem_revoke().
Introduce boolean to control whether to call .put_pfn_range().
.put_pfn_range() must not be called when revoke is used by a provider,
because the provider already initiated the revocation and calling back into
the provider could lead to self-deadlock or re-entrancy on provider locks.
Export kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
const unsigned long *pfn) via EXPORT_SYMBOL_GPL. Providers must pass
both start and pfn.
Signed-off-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
---
arch/x86/kvm/Makefile | 3 ++-
include/linux/guest_memfd.h | 14 +++++++++-
virt/kvm/guest_memfd.c | 63 ++++++++++++++++++++++++++++++++++++++-------
3 files changed, 69 insertions(+), 11 deletions(-)
diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile
index c6bf463f50327..5fd01bd19d1c4 100644
--- a/arch/x86/kvm/Makefile
+++ b/arch/x86/kvm/Makefile
@@ -61,7 +61,8 @@ exports_grep_trailer := --include='*.[ch]' -nrw $(srctree)/virt/kvm $(srctree)/a
-e kvm_page_track_unregister_notifier \
-e kvm_write_track_add_gfn \
-e kvm_write_track_remove_gfn \
- -e kvm_file_to_kvm_fn
+ -e kvm_file_to_kvm_fn \
+ -e kvm_gmem_revoke
# Force grep to emit a goofy group separator that can in turn be replaced with
# the above newline macro (newlines in Make are a nightmare). Note, grep only
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index 3c0cc35f748bc..3e2c0714ca33e 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -36,7 +36,8 @@ struct mempolicy;
* @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.
+ * provider should skip the unbacked range. Not called on
+ * kvm_gmem_revoke().
*
* Used by filesystems and device drivers that provide memory for guest_memfd.
*/
@@ -53,4 +54,15 @@ struct guest_memfd_provider_operations {
void (*put_pfn_range)(void *provider, pgoff_t index, size_t nr_pages);
};
+#ifdef CONFIG_KVM_GUEST_MEMFD
+int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
+ const unsigned long *pfn);
+#else
+static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
+ const unsigned long *pfn)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index a4519f3b9eadb..3c4bedceccfc2 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -421,18 +421,25 @@ static void kvm_gmem_pfn_reclaim(struct gmem_inode *gi, pgoff_t start, pgoff_t e
static void kvm_gmem_pfn_reclaim(struct gmem_inode *gi, pgoff_t start, pgoff_t end) {}
#endif
-static long kvm_gmem_punch_hole(struct inode *inode, loff_t offset, loff_t len)
+static int __kvm_gmem_revoke(struct inode *inode, pgoff_t start,
+ size_t nr_pages, const unsigned long *pfn,
+ bool put_pfn_range)
{
enum kvm_gfn_range_filter filter = kvm_gmem_get_all_gfns_filter(inode);
- loff_t size = i_size_read(inode);
- pgoff_t start, end;
+ pgoff_t max_pages = i_size_read(inode) >> PAGE_SHIFT;
+ loff_t offset, len;
+ pgoff_t end;
- if (offset >= size)
+ if (start >= max_pages)
return 0;
- len = min(len, size - offset);
- start = offset >> PAGE_SHIFT;
- end = (offset + len) >> PAGE_SHIFT;
+ nr_pages = min_t(size_t, nr_pages, max_pages - start);
+ if (!nr_pages)
+ return 0;
+
+ offset = (loff_t)start << PAGE_SHIFT;
+ len = (loff_t)nr_pages << PAGE_SHIFT;
+ end = start + nr_pages;
/*
* Bindings must be stable across invalidation to ensure the start+end
@@ -444,8 +451,14 @@ 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);
+#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
+ if (pfn)
+ kvm_arch_gmem_reclaim(*pfn, nr_pages);
+ else
+ kvm_gmem_pfn_reclaim(GMEM_I(inode), start, end);
+#endif
+ if (put_pfn_range)
+ gmem_provider_put_pfn_range(GMEM_I(inode), start, nr_pages);
} else {
truncate_inode_pages_range(inode->i_mapping, offset, offset + len - 1);
}
@@ -457,6 +470,38 @@ static long kvm_gmem_punch_hole(struct inode *inode, loff_t offset, loff_t len)
return 0;
}
+static long kvm_gmem_punch_hole(struct inode *inode, loff_t offset, loff_t len)
+{
+ return __kvm_gmem_revoke(inode, offset >> PAGE_SHIFT, len >> PAGE_SHIFT,
+ NULL, true);
+}
+
+/**
+ * kvm_gmem_revoke - Revoke and reclaim a range of memory in guest_memfd.
+ * @inode: The guest_memfd inode.
+ * @start: Starting page index within the file.
+ * @nr_pages: Number of pages to revoke.
+ * @pfn: The physical address / frame number being revoked. Directly
+ * reclaims this physical range without querying the provider's
+ * .lookup_pfn_range().
+ *
+ * guest_memfd will do everything to make sure the range revoked is no longer in
+ * use across guest and host mappings, as well as reclaim any confidential
+ * architecture state.
+ *
+ * Must be called before accessing memory contents. Keeping the mapping from
+ * @start to @pfn constant is the responsibility of the caller.
+ * .put_pfn_range() will not be called.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
+ const unsigned long *pfn)
+{
+ return __kvm_gmem_revoke(inode, start, nr_pages, pfn, false);
+}
+EXPORT_SYMBOL_GPL(kvm_gmem_revoke);
+
static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
{
struct address_space *mapping = inode->i_mapping;
--
2.56.0.257.gf7497c7d27-goog