[PATCH RFC v5 17/53] KVM: guest_memfd: Determine invalidation filter from memory attributes

Ackerley Tng via B4 Relay
2026-04-28T23:25:18Z

Before conversion, the range filter doesn't really matter:

+ For non-CoCo VMs that use guest_memfd, they have no mirrored tdp, so
KVM_DIRECT_ROOTS would have been invalidated anyway.
+ CoCo VMs could not use INIT_SHARED, and there's no conversion support, so
always using KVM_FILTER_PRIVATE would have worked.

Now with conversion support, update kvm_gmem_get_invalidate_filter to
inspect the memory attributes maple tree for a given range.

Instead of determining the invalidation filter based on static inode
flags, iterate through the attributes maple tree for the specific range
being invalidated. This allows KVM to identify if the range contains
private pages, shared pages, or both, and set the filter bits
accordingly.

Update kvm_gmem_invalidate_begin and kvm_gmem_release to pass the range
parameters to the filter helper to ensure invalidation accurately
targets the memory types present in the affected range.

Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index a8a5e37c982a4..85e8b3a981307 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -199,12 +199,24 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
return folio;
}

-static enum kvm_gfn_range_filter kvm_gmem_get_invalidate_filter(struct inode *inode)
+static enum kvm_gfn_range_filter kvm_gmem_get_invalidate_filter(
+ struct inode *inode, pgoff_t start, pgoff_t end)
{
- if (GMEM_I(inode)->flags & GUEST_MEMFD_FLAG_INIT_SHARED)
- return KVM_FILTER_SHARED;
+ struct gmem_inode *gi = GMEM_I(inode);
+ enum kvm_gfn_range_filter filter = 0;
+ void *entry;
+
+ lockdep_assert(mt_lock_is_held(&gi->attributes));
+
+ mt_for_each(&gi->attributes, entry, start, end - 1) {
+ filter |= (xa_to_value(entry) & KVM_MEMORY_ATTRIBUTE_PRIVATE) ?
+ KVM_FILTER_PRIVATE : KVM_FILTER_SHARED;
+
+ if (filter == (KVM_FILTER_PRIVATE | KVM_FILTER_SHARED))
+ break;
+ }

- return KVM_FILTER_PRIVATE;
+ return filter;
}

static void __kvm_gmem_invalidate_begin(struct gmem_file *f, pgoff_t start,
@@ -250,7 +262,7 @@ static void kvm_gmem_invalidate_begin(struct inode *inode, pgoff_t start,
enum kvm_gfn_range_filter attr_filter;
struct gmem_file *f;

- attr_filter = kvm_gmem_get_invalidate_filter(inode);
+ attr_filter = kvm_gmem_get_invalidate_filter(inode, start, end);

kvm_gmem_for_each_file(f, inode)
__kvm_gmem_invalidate_begin(f, start, end, attr_filter);
@@ -373,6 +385,7 @@ static long kvm_gmem_fallocate(struct file *file, int mode, loff_t offset,
static int kvm_gmem_release(struct inode *inode, struct file *file)
{
struct gmem_file *f = file->private_data;
+ enum kvm_gfn_range_filter filter;
struct kvm_memory_slot *slot;
struct kvm *kvm = f->kvm;
unsigned long index;
@@ -404,8 +417,8 @@ static int kvm_gmem_release(struct inode *inode, struct file *file)
* memory, as its lifetime is associated with the inode, not the file.
*/
end = i_size_read(inode) >> PAGE_SHIFT;
- __kvm_gmem_invalidate_begin(f, 0, end,
- kvm_gmem_get_invalidate_filter(inode));
+ filter = kvm_gmem_get_invalidate_filter(inode, 0, end);
+ __kvm_gmem_invalidate_begin(f, 0, end, filter);
__kvm_gmem_invalidate_end(f, 0, end);

list_del(&f->entry);

--
2.54.0.545.g6539524ca2-goog