* [PATCH RFC v2 01/50] KVM: guest_memfd: Split folio allocation and insertion in kvm_gmem_get_folio
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 02/50] KVM: guest_memfd: Route folio allocation through native provider operations Ackerley Tng via B4 Relay
` (49 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Separate __filemap_get_folio_mpol() call in kvm_gmem_get_folio() into two
distinct steps:
1. Allocating a folio via filemap_alloc_folio() with the NUMA policy.
2. Inserting the allocated folio into the inode filemap.
Handle -EEXIST from filemap_add_folio() by retrying filemap_lock_folio()
safely to manage concurrent insertions.
Decouple folio allocation from filemap insertion to prepare for supporting
other memory providers.
No functional change intended.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 93f3d29d5751a..f865fa0e46840 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -131,7 +131,9 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
/* TODO: Support huge pages. */
struct mempolicy *policy;
struct folio *folio;
+ int r;
+retry:
/*
* Fast-path: See if folio is already present in mapping to avoid
* policy_lookup.
@@ -141,10 +143,19 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
return folio;
policy = mpol_shared_policy_lookup(&GMEM_I(inode)->policy, index);
- folio = __filemap_get_folio_mpol(inode->i_mapping, index,
- FGP_LOCK | FGP_CREAT,
- mapping_gfp_mask(inode->i_mapping), policy);
+ folio = filemap_alloc_folio(mapping_gfp_mask(inode->i_mapping), 0,
+ policy);
mpol_cond_put(policy);
+ if (!folio)
+ return ERR_PTR(-ENOMEM);
+
+ r = filemap_add_folio(inode->i_mapping, folio, index, GFP_KERNEL);
+ if (r) {
+ folio_put(folio);
+ if (r == -EEXIST)
+ goto retry;
+ return ERR_PTR(r);
+ }
/*
* External interfaces like kvm_gmem_get_pfn() support dealing
@@ -153,7 +164,7 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
* to be updated for anything otherwise (e.g. page-clearing
* operations).
*/
- WARN_ON_ONCE(!IS_ERR(folio) && folio_order(folio));
+ WARN_ON_ONCE(folio_order(folio));
return folio;
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 02/50] KVM: guest_memfd: Route folio allocation through native provider operations
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 01/50] KVM: guest_memfd: Split folio allocation and insertion in kvm_gmem_get_folio Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 03/50] mm: shmem: Implement guest_memfd provider operations for tmpfs Ackerley Tng via B4 Relay
` (48 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Conceptually refactor the buddy allocator providing guest_memfd with
order-0 folios as the native provider.
Define operations with an alloc_folio callback for the native provider,
which still uses filemap_alloc_folio to allocate folios as before. Also
define the attach callback to establish provider context. Route folio
allocation in the get-folio path through provider ops, and set up the
native provider during creation using a sentinel fd and attach.
Attach the provider only after the entire inode and pseudo-file have been
initialized. Exposing the inode to external providers before allocating the
file or populating the file list risks race conditions and use-after-free
bugs if an external provider immediately triggers operations on the
guest_memfd inode.
Now that the inode is exposed for other providers, add a write barrier to
commit everything in the inode before the provider (and other racing
provider threads) gets to use the gmem inode.
Expose provider operations so external providers across the kernel can
implement them.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
include/linux/guest_memfd.h | 28 +++++++++++++++
virt/kvm/guest_memfd.c | 86 ++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
new file mode 100644
index 0000000000000..218de509769c1
--- /dev/null
+++ b/include/linux/guest_memfd.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _LINUX_GUEST_MEMFD_H
+#define _LINUX_GUEST_MEMFD_H
+
+#include <linux/types.h>
+
+struct file;
+struct folio;
+struct inode;
+struct mempolicy;
+
+/**
+ * struct guest_memfd_provider_operations - Operations for external memory providers
+ * @attach: Attach to a resource file, perform filesystem-specific validation,
+ * and return an opaque provider context.
+ * @alloc_folio: Allocate an uninserted folio for a given index and NUMA policy.
+ * Must return a pointer to a struct folio on success, or an
+ * ERR_PTR() error code on failure. Must never return NULL.
+ *
+ * Used by filesystems and device drivers that provide memory for guest_memfd.
+ */
+struct guest_memfd_provider_operations {
+ void *(*attach)(struct inode *gmem_inode, struct file *resource_file);
+ struct folio *(*alloc_folio)(void *provider, pgoff_t index,
+ struct mempolicy *mpol);
+};
+
+#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index f865fa0e46840..545c3e646e9c1 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -3,6 +3,7 @@
#include <linux/backing-dev.h>
#include <linux/falloc.h>
#include <linux/fs.h>
+#include <linux/guest_memfd.h>
#include <linux/kvm_host.h>
#include <linux/maple_tree.h>
#include <linux/mempolicy.h>
@@ -35,6 +36,9 @@ struct gmem_inode {
struct inode vfs_inode;
struct list_head gmem_file_list;
+ void *provider;
+ const struct guest_memfd_provider_operations *provider_ops;
+
u64 flags;
/*
* Every index in this inode, whether memory is populated or
@@ -50,6 +54,65 @@ static __always_inline struct gmem_inode *GMEM_I(struct inode *inode)
return container_of(inode, struct gmem_inode, vfs_inode);
}
+static void *gmem_native_attach(struct inode *gmem_inode,
+ struct file *resource_file)
+{
+ return GMEM_I(gmem_inode);
+}
+
+static struct folio *gmem_native_alloc_folio(void *provider, pgoff_t index,
+ struct mempolicy *mpol)
+{
+ struct gmem_inode *gi = provider;
+ struct folio *folio;
+ gfp_t gfp;
+
+ gfp = mapping_gfp_mask(gi->vfs_inode.i_mapping);
+ folio = filemap_alloc_folio(gfp, 0, mpol);
+ if (!folio)
+ return ERR_PTR(-ENOMEM);
+
+ return folio;
+}
+
+static const struct guest_memfd_provider_operations gmem_native_provider_ops = {
+ .attach = gmem_native_attach,
+ .alloc_folio = gmem_native_alloc_folio,
+};
+
+#define GMEM_RESOURCE_FD_NATIVE (-1)
+
+static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)
+{
+ const struct guest_memfd_provider_operations *ops = NULL;
+ struct file *resource_file = NULL;
+ void *provider;
+
+ if (resource_fd == GMEM_RESOURCE_FD_NATIVE)
+ ops = &gmem_native_provider_ops;
+ else
+ return -EOPNOTSUPP;
+
+ provider = ops->attach(inode, resource_file);
+ if (IS_ERR(provider))
+ return PTR_ERR(provider);
+
+ GMEM_I(inode)->provider = provider;
+ GMEM_I(inode)->provider_ops = ops;
+
+ return 0;
+}
+
+static struct folio *gmem_provider_alloc_folio(struct gmem_inode *gi,
+ pgoff_t index,
+ struct mempolicy *mpol)
+{
+ if (gi->provider_ops && gi->provider_ops->alloc_folio)
+ return gi->provider_ops->alloc_folio(gi->provider, index, mpol);
+
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
#define kvm_gmem_for_each_file(f, inode) \
list_for_each_entry(f, &GMEM_I(inode)->gmem_file_list, entry)
@@ -143,11 +206,10 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
return folio;
policy = mpol_shared_policy_lookup(&GMEM_I(inode)->policy, index);
- folio = filemap_alloc_folio(mapping_gfp_mask(inode->i_mapping), 0,
- policy);
+ folio = gmem_provider_alloc_folio(GMEM_I(inode), index, policy);
mpol_cond_put(policy);
- if (!folio)
- return ERR_PTR(-ENOMEM);
+ if (IS_ERR(folio))
+ return folio;
r = filemap_add_folio(inode->i_mapping, folio, index, GFP_KERNEL);
if (r) {
@@ -996,6 +1058,20 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
xa_init(&f->bindings);
list_add(&f->entry, &GMEM_I(inode)->gmem_file_list);
+ /*
+ * Ensure all inode and file initialization (such as gmem_file_list and
+ * bindings) is globally visible before exposing the inode to the
+ * provider in ->attach().
+ */
+ smp_wmb();
+
+ err = kvm_gmem_attach_resource(inode, GMEM_RESOURCE_FD_NATIVE);
+ if (err) {
+ fput(file);
+ put_unused_fd(fd);
+ return err;
+ }
+
fd_install(fd, file);
return fd;
@@ -1355,6 +1431,8 @@ static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)
mt_init_flags(&gi->attributes, MT_FLAGS_LOCK_EXTERN | MT_FLAGS_USE_RCU);
gi->flags = 0;
+ gi->provider = NULL;
+ gi->provider_ops = NULL;
INIT_LIST_HEAD(&gi->gmem_file_list);
return &gi->vfs_inode;
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 03/50] mm: shmem: Implement guest_memfd provider operations for tmpfs
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 01/50] KVM: guest_memfd: Split folio allocation and insertion in kvm_gmem_get_folio Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 02/50] KVM: guest_memfd: Route folio allocation through native provider operations Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-23 23:31 ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 04/50] KVM: guest_memfd: Support provider folio invalidation Ackerley Tng via B4 Relay
` (47 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Implement guest_memfd provider operations to declare that tmpfs supports
providing memory to guest_memfd.
I put guest_memfd_provider_operations as a pointer in struct
file_operations. Being in file_operations generalizes well to other kinds
of providers, like devices, which don't have their own super_operations.
Setting it in shmem_dir_operations allows directory file descriptors
opened on a tmpfs mount to provide memory to guest_memfd.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
fs/libfs.c | 4 +-
include/linux/fs.h | 6 +++
include/linux/guest_memfd.h | 4 ++
mm/shmem.c | 126 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 138 insertions(+), 2 deletions(-)
diff --git a/fs/libfs.c b/fs/libfs.c
index 27d7dc16fcb0a..77d3a4fcb5c13 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -429,7 +429,7 @@ void simple_offset_destroy(struct offset_ctx *octx)
* Returns the updated read position if successful; otherwise a
* negative errno is returned and the read position remains unchanged.
*/
-static loff_t offset_dir_llseek(struct file *file, loff_t offset, int whence)
+loff_t offset_dir_llseek(struct file *file, loff_t offset, int whence)
{
switch (whence) {
case SEEK_CUR:
@@ -551,7 +551,7 @@ static void offset_iterate_dir(struct file *file, struct dir_context *ctx)
* Return values:
* %0 - Complete
*/
-static int offset_readdir(struct file *file, struct dir_context *ctx)
+int offset_readdir(struct file *file, struct dir_context *ctx)
{
struct dentry *dir = file->f_path.dentry;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index deb411e866617..4f1494c4fbfde 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1915,6 +1915,7 @@ struct dir_context {
struct io_uring_cmd;
struct offset_ctx;
+struct guest_memfd_provider_operations;
struct file_operations {
struct module *owner;
@@ -1960,6 +1961,9 @@ struct file_operations {
int (*uring_cmd_iopoll)(struct io_uring_cmd *, struct io_comp_batch *,
unsigned int poll_flags);
int (*mmap_prepare)(struct vm_area_desc *);
+#ifdef CONFIG_KVM_GUEST_MEMFD
+ const struct guest_memfd_provider_operations *gmem_provider_ops;
+#endif
} __randomize_layout;
/* Supports async buffered reads */
@@ -3337,6 +3341,8 @@ int simple_offset_rename_exchange(struct inode *old_dir,
struct dentry *new_dentry);
void simple_offset_destroy(struct offset_ctx *octx);
+loff_t offset_dir_llseek(struct file *file, loff_t offset, int whence);
+int offset_readdir(struct file *file, struct dir_context *ctx);
extern const struct file_operations simple_offset_dir_operations;
extern int simple_fsync_noflush(struct file *, loff_t, loff_t, int);
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index 218de509769c1..f3e6077c39ac3 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -13,16 +13,20 @@ struct mempolicy;
* struct guest_memfd_provider_operations - Operations for external memory providers
* @attach: Attach to a resource file, perform filesystem-specific validation,
* and return an opaque provider context.
+ * @release: Release provider context and unpin any resources.
* @alloc_folio: Allocate an uninserted folio for a given index and NUMA policy.
* Must return a pointer to a struct folio on success, or an
* ERR_PTR() error code on failure. Must never return NULL.
+ * @invalidate_folio: Notify provider that a folio has been invalidated.
*
* Used by filesystems and device drivers that provide memory for guest_memfd.
*/
struct guest_memfd_provider_operations {
void *(*attach)(struct inode *gmem_inode, struct file *resource_file);
+ void (*release)(void *provider);
struct folio *(*alloc_folio)(void *provider, pgoff_t index,
struct mempolicy *mpol);
+ void (*invalidate_folio)(void *provider, struct folio *folio);
};
#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/mm/shmem.c b/mm/shmem.c
index 848316eaa7f4f..2a9faf8be7382 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -38,6 +38,7 @@
#include <linux/uio.h>
#include <linux/hugetlb.h>
#include <linux/fs_parser.h>
+#include <linux/guest_memfd.h>
#include <linux/swapfile.h>
#include <linux/iversion.h>
#include <linux/unicode.h>
@@ -268,6 +269,9 @@ static void shmem_inode_unacct_blocks(struct inode *inode, long pages)
static const struct super_operations shmem_ops;
static const struct address_space_operations shmem_aops;
static const struct file_operations shmem_file_operations;
+#ifdef CONFIG_KVM_GUEST_MEMFD
+static const struct file_operations shmem_dir_operations;
+#endif
static const struct inode_operations shmem_inode_operations;
static const struct inode_operations shmem_dir_inode_operations;
static const struct inode_operations shmem_special_inode_operations;
@@ -3088,7 +3092,11 @@ static struct inode *__shmem_get_inode(struct mnt_idmap *idmap,
/* Some things misbehave if size == 0 on a directory */
inode->i_size = 2 * BOGO_DIRENT_SIZE;
inode->i_op = &shmem_dir_inode_operations;
+#ifdef CONFIG_KVM_GUEST_MEMFD
+ inode->i_fop = &shmem_dir_operations;
+#else
inode->i_fop = &simple_offset_dir_operations;
+#endif
simple_offset_init(shmem_get_offset_ctx(inode));
break;
case S_IFLNK:
@@ -5237,6 +5245,124 @@ static const struct inode_operations shmem_special_inode_operations = {
#endif
};
+#ifdef CONFIG_KVM_GUEST_MEMFD
+static void *shmem_gmem_attach(struct inode *gmem_inode,
+ struct file *resource_file)
+{
+ struct inode *inode = file_inode(resource_file);
+ struct shmem_sb_info *sbinfo;
+
+ if (!S_ISDIR(inode->i_mode))
+ return ERR_PTR(-ENOTDIR);
+
+ /*
+ * Would like comments: Requiring the root directory of the mount
+ * provides a less confusing interface, although this is not strictly
+ * necessary.
+ */
+ if (resource_file->f_path.dentry != resource_file->f_path.mnt->mnt_root)
+ return ERR_PTR(-EINVAL);
+
+ if (__mnt_is_readonly(resource_file->f_path.mnt))
+ return ERR_PTR(-EROFS);
+
+ if (inode_permission(file_mnt_idmap(resource_file), inode,
+ MAY_WRITE | MAY_EXEC))
+ return ERR_PTR(-EACCES);
+
+ /* Enforce noswap because guest_memfd does not support swapping. */
+ sbinfo = SHMEM_SB(inode->i_sb);
+ if (!sbinfo->noswap)
+ return ERR_PTR(-EINVAL);
+
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+ /*
+ * guest_memfd does not support huge pages yet; this restriction can be
+ * relaxed in the future.
+ *
+ * TODO: Block rebinding with huge page support.
+ */
+ if (sbinfo->huge != SHMEM_HUGE_NEVER)
+ return ERR_PTR(-EINVAL);
+#endif
+
+ return (void *)mntget(resource_file->f_path.mnt);
+}
+
+static void shmem_gmem_release(void *provider)
+{
+ struct vfsmount *mnt = provider;
+
+ mntput(mnt);
+}
+
+/*
+ * TODO: Consider refactoring shmem allocation helpers to share code between
+ * internal tmpfs folio allocation and guest_memfd provider allocation.
+ */
+static struct folio *shmem_gmem_alloc_folio(void *provider, pgoff_t index,
+ struct mempolicy *mpol)
+{
+ struct mempolicy *sb_mpol = NULL;
+ struct vfsmount *mnt = provider;
+ struct shmem_sb_info *sbinfo;
+ struct folio *folio;
+
+ sbinfo = SHMEM_SB(mnt->mnt_sb);
+ if (sbinfo->max_blocks &&
+ !percpu_counter_limited_add(&sbinfo->used_blocks,
+ sbinfo->max_blocks, 1))
+ return ERR_PTR(-ENOSPC);
+
+ if (!mpol) {
+ sb_mpol = shmem_get_sbmpol(sbinfo);
+ mpol = sb_mpol;
+ }
+
+ if (mpol)
+ folio = folio_alloc_mpol(GFP_HIGHUSER_MOVABLE, 0, mpol, index,
+ numa_node_id());
+ else
+ folio = folio_alloc(GFP_HIGHUSER_MOVABLE, 0);
+
+ mpol_cond_put(sb_mpol);
+
+ if (!folio) {
+ if (sbinfo->max_blocks)
+ percpu_counter_sub(&sbinfo->used_blocks, 1);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return folio;
+}
+
+static void shmem_gmem_invalidate_folio(void *provider, struct folio *folio)
+{
+ struct vfsmount *mnt = provider;
+ struct shmem_sb_info *sbinfo;
+
+ sbinfo = SHMEM_SB(mnt->mnt_sb);
+ if (sbinfo->max_blocks)
+ percpu_counter_sub(&sbinfo->used_blocks, folio_nr_pages(folio));
+}
+
+static const struct guest_memfd_provider_operations shmem_gmem_provider_ops = {
+ .attach = shmem_gmem_attach,
+ .release = shmem_gmem_release,
+ .alloc_folio = shmem_gmem_alloc_folio,
+ .invalidate_folio = shmem_gmem_invalidate_folio,
+};
+
+static const struct file_operations shmem_dir_operations = {
+ .llseek = offset_dir_llseek,
+ .iterate_shared = offset_readdir,
+ .read = generic_read_dir,
+ .fsync = noop_fsync,
+ .setlease = generic_setlease,
+ .gmem_provider_ops = &shmem_gmem_provider_ops,
+};
+#endif /* CONFIG_KVM_GUEST_MEMFD */
+
static const struct super_operations shmem_ops = {
.alloc_inode = shmem_alloc_inode,
.free_inode = shmem_free_in_core_inode,
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 03/50] mm: shmem: Implement guest_memfd provider operations for tmpfs
2026-10-22 23:27 ` [PATCH RFC v2 03/50] mm: shmem: Implement guest_memfd provider operations for tmpfs Ackerley Tng via B4 Relay
@ 2026-10-23 23:31 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:31 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mm: shmem: reference count leak on sbinfo->mpol in shmem_gmem_alloc_folio()
- [High] mm: shmem: shmem_gmem_alloc_folio() allocates unmovable folios as movable
--
commit 88b5d5c54fc1c479ad407c9b1ae1787c3dad600e
Author: Ackerley Tng <ackerleytng@google.com>
mm: shmem: Implement guest_memfd provider operations for tmpfs
This commit adds guest_memfd_provider_operations to file_operations and
implements tmpfs memory provider callbacks on shmem_dir_operations so that a
tmpfs root directory file descriptor can provide backing memory to
guest_memfd.
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 848316eaa7f4f..2a9faf8be7382 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
[ ... ]
> @@ -5237,6 +5245,124 @@ static const struct inode_operations shmem_special_inode_operations = {
> #endif
> };
>
> +#ifdef CONFIG_KVM_GUEST_MEMFD
[ ... ]
> +static struct folio *shmem_gmem_alloc_folio(void *provider, pgoff_t index,
> + struct mempolicy *mpol)
> +{
> + struct mempolicy *sb_mpol = NULL;
> + struct vfsmount *mnt = provider;
> + struct shmem_sb_info *sbinfo;
> + struct folio *folio;
> +
> + sbinfo = SHMEM_SB(mnt->mnt_sb);
> + if (sbinfo->max_blocks &&
> + !percpu_counter_limited_add(&sbinfo->used_blocks,
> + sbinfo->max_blocks, 1))
> + return ERR_PTR(-ENOSPC);
> +
> + if (!mpol) {
> + sb_mpol = shmem_get_sbmpol(sbinfo);
> + mpol = sb_mpol;
> + }
> +
> + if (mpol)
> + folio = folio_alloc_mpol(GFP_HIGHUSER_MOVABLE, 0, mpol, index,
> + numa_node_id());
> + else
> + folio = folio_alloc(GFP_HIGHUSER_MOVABLE, 0);
[Severity: High]
Should shmem_gmem_alloc_folio() allocate folios with GFP_HIGHUSER instead of
GFP_HIGHUSER_MOVABLE?
In virt/kvm/guest_memfd.c, kvm_gmem_get_folio() inserts the folio returned by
gmem_provider_alloc_folio() -> shmem_gmem_alloc_folio() into the guest_memfd
inode->i_mapping via filemap_add_folio(). However, kvm_gmem_init_inode()
configures that mapping as inaccessible and unevictable:
virt/kvm/guest_memfd.c:kvm_gmem_init_inode() {
...
mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
/*
* guest_memfd memory is neither migratable nor swappable: set
* inaccessible to gate off both.
*/
mapping_set_inaccessible(inode->i_mapping);
WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
...
}
Because mapping_inaccessible() is true, move_to_new_folio() in mm/migrate.c
and isolate_migratepages_block() in mm/compaction.c reject migration with
-EOPNOTSUPP, kvm_gmem_migrate_folio() triggers WARN_ON_ONCE(1) and returns
-EINVAL, and shmem_gmem_attach() requires sbinfo->noswap. Allocating these
folios with __GFP_MOVABLE allows unmovable and unreclaimable guest_memfd
pages to be placed in ZONE_MOVABLE, MIGRATE_CMA, and MIGRATE_MOVABLE
pageblocks, which can cause CMA allocations, compaction, and memory
hot-unplug to fail.
> +
> + mpol_cond_put(sb_mpol);
[Severity: High]
Does mpol_cond_put(sb_mpol) leak a reference on sbinfo->mpol when
shmem_gmem_alloc_folio() is called with a NULL mpol on a tmpfs mount
configured with an mpol= mount option?
When mpol is NULL, shmem_get_sbmpol() in mm/shmem.c unconditionally takes a
reference on sbinfo->mpol via mpol_get():
mm/shmem.c:shmem_get_sbmpol() {
struct mempolicy *mpol = NULL;
if (sbinfo->mpol) {
raw_spin_lock(&sbinfo->stat_lock);
mpol = sbinfo->mpol;
mpol_get(mpol);
raw_spin_unlock(&sbinfo->stat_lock);
}
return mpol;
}
However, mpol_cond_put() in include/linux/mempolicy.h only drops the
reference if MPOL_F_SHARED is set:
include/linux/mempolicy.h:mpol_cond_put() {
if (mpol_needs_cond_ref(pol))
__mpol_put(pol);
}
Since sbinfo->mpol is allocated via mpol_parse_str() -> mpol_new() without
MPOL_F_SHARED, mpol_cond_put(sb_mpol) is a no-op and leaks a reference on
every allocation, preventing sbinfo->mpol from being freed on remount or
unmount (and eventually wrapping atomic_t refcnt). Should this call
mpol_put(sb_mpol) instead, matching shmem_show_options() and
mpol_shared_policy_init()?
> +
> + if (!folio) {
> + if (sbinfo->max_blocks)
> + percpu_counter_sub(&sbinfo->used_blocks, 1);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + return folio;
> +}
[ ... ]
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=3
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 04/50] KVM: guest_memfd: Support provider folio invalidation
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (2 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 03/50] mm: shmem: Implement guest_memfd provider operations for tmpfs Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-23 23:31 ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 05/50] KVM: guest_memfd: Support using resource provider Ackerley Tng via B4 Relay
` (46 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
tmpfs increments the total number of used pages on the mount at allocation
time, so that number has to be decremented when the folio is removed from
tmpfs' ownership.
In this model where guest_memfd allocates from a tmpfs mount, I believe we
should respect the limits set in the mount, hence I require .alloc_folio()
to increment.
.invalidate_folio() is then the counterpart to .alloc_folio(), for undoing
any provider-specific per-folio work during allocations.
.invalidate_folio() requires mapping_set_release_always(). Not sure how odd
it is to use that mapping flag.
Using .free_folio() is hard because folio->mapping is NULL by then, so I
can't reach provider information. The provider information could also be
stuffed in folio->private? What do people think of that?
Another alternative would be to use a custom truncation function in
guest_memfd, just like shmem_truncate_range() or shmem_undo_range(). The
con is that guest_memfd might at some point support more generic mm stuff
like swap/reclaim, of some form? Or maybe never? Shall we go custom now and
unify later? Or just not think too far ahead?
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 545c3e646e9c1..ee823f2ef0368 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -113,6 +113,13 @@ static struct folio *gmem_provider_alloc_folio(struct gmem_inode *gi,
return ERR_PTR(-EOPNOTSUPP);
}
+static void gmem_provider_invalidate_folio(struct gmem_inode *gi,
+ struct folio *folio)
+{
+ if (gi->provider_ops && gi->provider_ops->invalidate_folio)
+ gi->provider_ops->invalidate_folio(gi->provider, folio);
+}
+
#define kvm_gmem_for_each_file(f, inode) \
list_for_each_entry(f, &GMEM_I(inode)->gmem_file_list, entry)
@@ -213,6 +220,7 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
r = filemap_add_folio(inode->i_mapping, folio, index, GFP_KERNEL);
if (r) {
+ gmem_provider_invalidate_folio(GMEM_I(inode), folio);
folio_put(folio);
if (r == -EEXIST)
goto retry;
@@ -942,10 +950,24 @@ static void kvm_gmem_free_folio(struct folio *folio)
}
#endif
+static void kvm_gmem_invalidate_folio(struct folio *folio, size_t offset,
+ size_t len)
+{
+ struct inode *inode = folio->mapping->host;
+ struct gmem_inode *gi = GMEM_I(inode);
+
+ /* guest_memfd only allows truncating full folios. */
+ if (WARN_ON_ONCE(offset != 0 || len != folio_size(folio)))
+ return;
+
+ gmem_provider_invalidate_folio(gi, folio);
+}
+
static const struct address_space_operations kvm_gmem_aops = {
.dirty_folio = noop_dirty_folio,
.migrate_folio = kvm_gmem_migrate_folio,
.error_remove_folio = kvm_gmem_error_folio,
+ .invalidate_folio = kvm_gmem_invalidate_folio,
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
.free_folio = kvm_gmem_free_folio,
#endif
@@ -984,6 +1006,7 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
*/
mapping_set_inaccessible(inode->i_mapping);
WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
+ mapping_set_release_always(inode->i_mapping);
gi->flags = flags;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 04/50] KVM: guest_memfd: Support provider folio invalidation
2026-10-22 23:27 ` [PATCH RFC v2 04/50] KVM: guest_memfd: Support provider folio invalidation Ackerley Tng via B4 Relay
@ 2026-10-23 23:31 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:31 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] KVM: guest_memfd: AS_RELEASE_ALWAYS without release_folio() in kvm_gmem_aops
--
commit 761ef1ff98198357ffc1c8f5492a708cb9b2d9ab
Author: Ackerley Tng <ackerleytng@google.com>
KVM: guest_memfd: Support provider folio invalidation
This commit adds a provider invalidate_folio callback to guest_memfd and wires
it up to address_space_operations.invalidate_folio and the filemap_add_folio()
error path in kvm_gmem_get_folio(). It also sets AS_RELEASE_ALWAYS on the
inode mapping in kvm_gmem_init_inode() so truncate_cleanup_folio() invokes
.invalidate_folio.
> diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
> index 545c3e646e9c1..ee823f2ef0368 100644
> --- a/virt/kvm/guest_memfd.c
> +++ b/virt/kvm/guest_memfd.c
[ ... ]
> @@ -942,10 +950,24 @@ static void kvm_gmem_free_folio(struct folio *folio)
> }
> #endif
>
> +static void kvm_gmem_invalidate_folio(struct folio *folio, size_t offset,
> + size_t len)
> +{
> + struct inode *inode = folio->mapping->host;
> + struct gmem_inode *gi = GMEM_I(inode);
> +
> + /* guest_memfd only allows truncating full folios. */
> + if (WARN_ON_ONCE(offset != 0 || len != folio_size(folio)))
> + return;
> +
> + gmem_provider_invalidate_folio(gi, folio);
> +}
> +
> static const struct address_space_operations kvm_gmem_aops = {
> .dirty_folio = noop_dirty_folio,
> .migrate_folio = kvm_gmem_migrate_folio,
> .error_remove_folio = kvm_gmem_error_folio,
> + .invalidate_folio = kvm_gmem_invalidate_folio,
> #ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
> .free_folio = kvm_gmem_free_folio,
> #endif
> @@ -984,6 +1006,7 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
> */
> mapping_set_inaccessible(inode->i_mapping);
> WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
> + mapping_set_release_always(inode->i_mapping);
>
> gi->flags = flags;
>
[Severity: High]
Does setting mapping_set_release_always() in kvm_gmem_init_inode() without
providing a .release_folio callback in kvm_gmem_aops cause filemap_release_folio()
to fall back to try_to_free_buffers() and allow mapping_evict_folio() to evict
guest_memfd folios?
When AS_RELEASE_ALWAYS is set, folio_needs_release(folio) returns true. If
mapping->a_ops->release_folio is NULL, filemap_release_folio() falls through
to try_to_free_buffers():
mm/filemap.c:filemap_release_folio() {
if (!folio_needs_release(folio))
return true;
if (folio_test_writeback(folio))
return false;
if (mapping && mapping->a_ops->release_folio)
return mapping->a_ops->release_folio(folio, gfp);
return try_to_free_buffers(folio);
}
fs/buffer.c:try_to_free_buffers() {
/* Misconfigured folio check */
if (WARN_ON_ONCE(!folio_buffers(folio)))
return true;
}
When a folio is allocated in kvm_gmem_get_folio(), filemap_add_folio() places
it on the unevictable LRU with PG_lru set. If the folio is not mapped into
userspace page tables (such as private guest_memfd folios or unmapped shared
folios, even while mapped in KVM Stage-2/TDP page tables), its refcount is 1
and it is clean.
If soft_offline_page() in mm/memory-failure.c runs on that PFN (or if
__do_fault() in mm/memory.c encounters a PageHWPoison guest_memfd folio), it
calls mapping_evict_folio() in mm/truncate.c:
soft_offline_page()
-> get_hwpoison_page()
-> soft_offline_in_use_page()
-> mapping_evict_folio()
-> filemap_release_folio()
-> try_to_free_buffers()
-> remove_mapping()
Because mapping_evict_folio() does not check mapping_unevictable(), it calls
filemap_release_folio(), triggers WARN_ON_ONCE(!folio_buffers(folio)) in
try_to_free_buffers(), and receives true. It then proceeds to remove_mapping(),
which removes the folio from inode->i_mapping without holding
filemap_invalidate_lock, without zapping KVM Stage-2 mappings, and without
invoking kvm_gmem_invalidate_folio(), leaking the provider's allocation
accounting (such as tmpfs sbinfo->used_blocks).
Should kvm_gmem_aops also implement .release_folio (or avoid
mapping_set_release_always()) so that filemap_release_folio() does not fall
through to try_to_free_buffers() and permit eviction via mapping_evict_folio()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=4
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 05/50] KVM: guest_memfd: Support using resource provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (3 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 04/50] KVM: guest_memfd: Support provider folio invalidation Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-23 23:31 ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 06/50] KVM: selftests: Add helper to create guest_memfd with a resource file Ackerley Tng via B4 Relay
` (45 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
A refcount on the resource_fd is taken only to keep the reference stable
for guest_memfd to figure out the existence of a provider. The provider is
responsible for pinning anything required to keep the provider's ops and
the actual provided resource alive.
.release() is called when this guest_memfd inode is destroyed to allow the
provider to clean up.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
Documentation/virt/kvm/api.rst | 28 +++++++++++++++++----------
include/linux/kvm_host.h | 2 +-
include/uapi/linux/kvm.h | 5 ++++-
virt/kvm/guest_memfd.c | 44 ++++++++++++++++++++++++++++++++++--------
4 files changed, 59 insertions(+), 20 deletions(-)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index e95c9cda085d9..9f5313d405d9c 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6486,7 +6486,9 @@ and cannot be resized (guest_memfd files do however support PUNCH_HOLE).
struct kvm_create_guest_memfd {
__u64 size;
__u64 flags;
- __u64 reserved[6];
+ __s32 resource_fd;
+ __u32 pad;
+ __u64 reserved[5];
};
Conceptually, the inode backing a guest_memfd file represents physical memory,
@@ -6508,15 +6510,21 @@ a single guest_memfd file, but the bound ranges must not overlap).
The capability KVM_CAP_GUEST_MEMFD_FLAGS enumerates the `flags` that can be
specified via KVM_CREATE_GUEST_MEMFD. Currently defined flags:
- ============================ ================================================
- GUEST_MEMFD_FLAG_MMAP Enable using mmap() on the guest_memfd file
- descriptor.
- GUEST_MEMFD_FLAG_INIT_SHARED Make all memory in the file shared during
- KVM_CREATE_GUEST_MEMFD (memory files created
- without INIT_SHARED will be marked private).
- Shared memory can be faulted into host userspace
- page tables. Private memory cannot.
- ============================ ================================================
+ ============================= ================================================
+ GUEST_MEMFD_FLAG_MMAP Enable using mmap() on the guest_memfd file
+ descriptor.
+ GUEST_MEMFD_FLAG_INIT_SHARED Make all memory in the file shared during
+ KVM_CREATE_GUEST_MEMFD (memory files created
+ without INIT_SHARED will be marked private).
+ Shared memory can be faulted into host userspace
+ page tables. Private memory cannot.
+ GUEST_MEMFD_FLAG_USE_RESOURCE Consume resource_fd as an external memory
+ provider or resource pool. When not set,
+ resource_fd must be '0'. When set, the provider
+ must be a supported resource (e.g. tmpfs mount
+ root directory). Currently, tmpfs resource pools
+ require noswap and huge=never mount options.
+ ============================= ================================================
When the KVM MMU performs a PFN lookup to service a guest fault, the fault will
always be consumed from guest_memfd, regardless of whether it is a shared or a
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index e7fc94e2910a8..d820afac49252 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2618,7 +2618,7 @@ bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
{
- u64 flags = GUEST_MEMFD_FLAG_MMAP;
+ u64 flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_USE_RESOURCE;
if (!kvm || kvm_arch_supports_gmem_init_shared(kvm))
flags |= GUEST_MEMFD_FLAG_INIT_SHARED;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 8dff2fc1972e9..f4108bf7e3192 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1674,11 +1674,14 @@ struct kvm_memory_attributes2 {
#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd)
#define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
#define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1)
+#define GUEST_MEMFD_FLAG_USE_RESOURCE (1ULL << 2)
struct kvm_create_guest_memfd {
__u64 size;
__u64 flags;
- __u64 reserved[6];
+ __s32 resource_fd;
+ __u32 pad;
+ __u64 reserved[5];
};
#define KVM_PRE_FAULT_MEMORY _IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index ee823f2ef0368..84877beed6701 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -85,15 +85,26 @@ static const struct guest_memfd_provider_operations gmem_native_provider_ops = {
static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)
{
const struct guest_memfd_provider_operations *ops = NULL;
+ void *provider = ERR_PTR(-EOPNOTSUPP);
struct file *resource_file = NULL;
- void *provider;
- if (resource_fd == GMEM_RESOURCE_FD_NATIVE)
+ if (resource_fd == GMEM_RESOURCE_FD_NATIVE) {
ops = &gmem_native_provider_ops;
- else
- return -EOPNOTSUPP;
+ } else {
+ resource_file = fget_raw(resource_fd);
+ if (!resource_file)
+ return -EBADF;
+
+ if (resource_file->f_op)
+ ops = resource_file->f_op->gmem_provider_ops;
+ }
+
+ if (ops && ops->attach && ops->alloc_folio)
+ provider = ops->attach(inode, resource_file);
+
+ if (resource_file)
+ fput(resource_file);
- provider = ops->attach(inode, resource_file);
if (IS_ERR(provider))
return PTR_ERR(provider);
@@ -120,6 +131,12 @@ static void gmem_provider_invalidate_folio(struct gmem_inode *gi,
gi->provider_ops->invalidate_folio(gi->provider, folio);
}
+static void gmem_provider_release(struct gmem_inode *gi)
+{
+ if (gi->provider_ops && gi->provider_ops->release)
+ gi->provider_ops->release(gi->provider);
+}
+
#define kvm_gmem_for_each_file(f, inode) \
list_for_each_entry(f, &GMEM_I(inode)->gmem_file_list, entry)
@@ -1033,7 +1050,7 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
return r;
}
-static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
+static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags, int resource_fd)
{
static const char *name = "[kvm-gmem]";
struct gmem_file *f;
@@ -1088,7 +1105,7 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
*/
smp_wmb();
- err = kvm_gmem_attach_resource(inode, GMEM_RESOURCE_FD_NATIVE);
+ err = kvm_gmem_attach_resource(inode, resource_fd);
if (err) {
fput(file);
put_unused_fd(fd);
@@ -1111,6 +1128,7 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
{
+ int resource_fd = GMEM_RESOURCE_FD_NATIVE;
loff_t size = args->size;
u64 flags = args->flags;
@@ -1120,7 +1138,15 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
if (size <= 0 || !PAGE_ALIGNED(size))
return -EINVAL;
- return __kvm_gmem_create(kvm, size, flags);
+ if (flags & GUEST_MEMFD_FLAG_USE_RESOURCE) {
+ if (args->resource_fd < 0)
+ return -EBADF;
+ resource_fd = args->resource_fd;
+ } else if (args->resource_fd) {
+ return -EINVAL;
+ }
+
+ return __kvm_gmem_create(kvm, size, flags, resource_fd);
}
int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,
@@ -1464,6 +1490,8 @@ static void kvm_gmem_destroy_inode(struct inode *inode)
{
struct gmem_inode *gi = GMEM_I(inode);
+ gmem_provider_release(gi);
+
mpol_free_shared_policy(&gi->policy);
/*
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 05/50] KVM: guest_memfd: Support using resource provider
2026-10-22 23:27 ` [PATCH RFC v2 05/50] KVM: guest_memfd: Support using resource provider Ackerley Tng via B4 Relay
@ 2026-10-23 23:31 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:31 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] kvm: guest_memfd: kvm_gmem_create() checks resource_fd when flag is not set
- [High] KVM: guest_memfd: movable allocation of unmovable tmpfs-provided folios
- [Medium] KVM: guest_memfd: fget_raw() mismatch with f_op in kvm_gmem_attach_resource()
--
commit 82719b2c47dfb94fadcdf6f025718ccc05e83332
Author: Ackerley Tng <ackerleytng@google.com>
KVM: guest_memfd: Support using resource provider
This commit adds the GUEST_MEMFD_FLAG_USE_RESOURCE flag and resource_fd
parameter to KVM_CREATE_GUEST_MEMFD so that guest_memfd inodes can attach
to an external resource provider via resource_file->f_op->gmem_provider_ops
and release the provider on inode destruction.
> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> index 8dff2fc1972e9..f4108bf7e3192 100644
> --- a/include/uapi/linux/kvm.h
> +++ b/include/uapi/linux/kvm.h
> @@ -1674,11 +1674,14 @@ struct kvm_memory_attributes2 {
> #define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd)
> #define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
> #define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1)
> +#define GUEST_MEMFD_FLAG_USE_RESOURCE (1ULL << 2)
>
> struct kvm_create_guest_memfd {
> __u64 size;
> __u64 flags;
> - __u64 reserved[6];
> + __s32 resource_fd;
> + __u32 pad;
> + __u64 reserved[5];
> };
>
> #define KVM_PRE_FAULT_MEMORY _IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)
> diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
> index ee823f2ef0368..84877beed6701 100644
> --- a/virt/kvm/guest_memfd.c
> +++ b/virt/kvm/guest_memfd.c
> @@ -85,15 +85,26 @@ static const struct guest_memfd_provider_operations gmem_native_provider_ops = {
> static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)
> {
> const struct guest_memfd_provider_operations *ops = NULL;
> + void *provider = ERR_PTR(-EOPNOTSUPP);
> struct file *resource_file = NULL;
> - void *provider;
>
> - if (resource_fd == GMEM_RESOURCE_FD_NATIVE)
> + if (resource_fd == GMEM_RESOURCE_FD_NATIVE) {
> ops = &gmem_native_provider_ops;
> - else
> - return -EOPNOTSUPP;
> + } else {
> + resource_file = fget_raw(resource_fd);
> + if (!resource_file)
> + return -EBADF;
> +
> + if (resource_file->f_op)
> + ops = resource_file->f_op->gmem_provider_ops;
> + }
[Severity: Medium]
Why does kvm_gmem_attach_resource() use fget_raw() here when looking up
resource_file, and then read ops from resource_file->f_op?
In fs/file.c, the only difference between fget() and fget_raw() is that
fget_raw() accepts O_PATH (FMODE_PATH) file descriptors:
fs/file.c:
struct file *fget(unsigned int fd)
{
return __fget(fd, FMODE_PATH);
}
struct file *fget_raw(unsigned int fd)
{
return __fget(fd, 0);
}
However, in fs/open.c:do_dentry_open(), any file opened with O_PATH (such
as a mount fd from fsmount() or open_tree(), or a directory opened with
O_PATH) has f->f_op set to &empty_fops rather than inode->i_fop:
fs/open.c:do_dentry_open() {
...
if (unlikely(f->f_flags & O_PATH)) {
f->f_mode = FMODE_PATH | FMODE_OPENED;
file_set_fsnotify_mode(f, FMODE_NONOTIFY);
f->f_op = &empty_fops;
return 0;
}
...
}
As a result, when userspace passes an O_PATH fd as resource_fd to
ioctl(KVM_CREATE_GUEST_MEMFD) with GUEST_MEMFD_FLAG_USE_RESOURCE,
resource_file->f_op->gmem_provider_ops is always NULL and attaching fails
with -EOPNOTSUPP.
If O_PATH descriptors are meant to be supported, should this read
gmem_provider_ops from file_inode(resource_file)->i_fop instead of
resource_file->f_op? Conversely, if O_PATH descriptors are not intended to
be supported, should this use fget(resource_fd) so O_PATH descriptors fail
with -EBADF (and drop the NULL check on resource_file->f_op)?
> +
> + if (ops && ops->attach && ops->alloc_folio)
> + provider = ops->attach(inode, resource_file);
[Severity: High]
When attaching a tmpfs resource provider via kvm_gmem_attach_resource(),
can allocating folios through ops->alloc_folio() place unmovable
guest_memfd pages into ZONE_MOVABLE or MIGRATE_CMA/MIGRATE_MOVABLE
pageblocks?
In virt/kvm/guest_memfd.c:kvm_gmem_init_inode(), guest_memfd inodes
configure GFP_HIGHUSER (without __GFP_MOVABLE) and mark the mapping as
inaccessible because guest_memfd folios are neither migratable nor
swappable:
virt/kvm/guest_memfd.c:kvm_gmem_init_inode() {
...
mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
/*
* guest_memfd memory is neither migratable nor swappable: set
* inaccessible to gate off both.
*/
mapping_set_inaccessible(inode->i_mapping);
...
}
Native allocation in gmem_native_alloc_folio() uses
mapping_gfp_mask(gi->vfs_inode.i_mapping) (GFP_HIGHUSER). However, when a
tmpfs resource provider is attached and populated via
kvm_gmem_get_folio() -> gmem_provider_alloc_folio() ->
shmem_gmem_alloc_folio(), mm/shmem.c allocates the folio with
GFP_HIGHUSER_MOVABLE before kvm_gmem_get_folio() inserts it into the
AS_INACCESSIBLE guest_memfd mapping:
mm/shmem.c:shmem_gmem_alloc_folio() {
...
if (mpol)
folio = folio_alloc_mpol(GFP_HIGHUSER_MOVABLE, 0, mpol, index,
numa_node_id());
else
folio = folio_alloc(GFP_HIGHUSER_MOVABLE, 0);
...
}
Because kvm_gmem_migrate_folio() rejects migration with -EINVAL and
isolate_migratepages_block() / move_to_new_folio() refuse to migrate
mapping_inaccessible() folios, allocating these folios with __GFP_MOVABLE
pins movable and CMA pageblocks and can cause memory hot-unplug
(offline_pages()) to fail or loop when attempting to migrate them.
Should gmem_provider_alloc_folio() pass mapping_gfp_mask(inode->i_mapping)
(or should shmem_gmem_alloc_folio() use GFP_HIGHUSER instead of
GFP_HIGHUSER_MOVABLE)?
[ ... ]
> @@ -1111,6 +1128,7 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
>
> int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
> {
> + int resource_fd = GMEM_RESOURCE_FD_NATIVE;
> loff_t size = args->size;
> u64 flags = args->flags;
>
> @@ -1120,7 +1138,15 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
> if (size <= 0 || !PAGE_ALIGNED(size))
> return -EINVAL;
>
> - return __kvm_gmem_create(kvm, size, flags);
> + if (flags & GUEST_MEMFD_FLAG_USE_RESOURCE) {
> + if (args->resource_fd < 0)
> + return -EBADF;
> + resource_fd = args->resource_fd;
> + } else if (args->resource_fd) {
> + return -EINVAL;
> + }
[Severity: High]
Could checking args->resource_fd when GUEST_MEMFD_FLAG_USE_RESOURCE is not
set cause a userspace ABI regression for existing KVM_CREATE_GUEST_MEMFD
callers?
Prior to this change, struct kvm_create_guest_memfd in
include/uapi/linux/kvm.h had __u64 reserved[6] after flags, and neither
kvm_vm_ioctl() nor kvm_gmem_create() verified that reserved[] was zeroed
(and Documentation/virt/kvm/api.rst did not require reserved[] to be zero).
Because resource_fd is carved out of reserved[0] and gated by
GUEST_MEMFD_FLAG_USE_RESOURCE, any existing userspace caller that allocates
struct kvm_create_guest_memfd on the stack and only initializes .size and
.flags (or initializes unused fd fields to -1) without setting
GUEST_MEMFD_FLAG_USE_RESOURCE will now fail with -EINVAL in
kvm_gmem_create(). Meanwhile, args->pad and args->reserved[0..4] remain
unchecked.
Should args->resource_fd only be inspected when
GUEST_MEMFD_FLAG_USE_RESOURCE is set in flags (similar to how
KVM_SET_USER_MEMORY_REGION2 only inspects guest_memfd and
guest_memfd_offset when KVM_MEM_GUEST_MEMFD is set)?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=5
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 06/50] KVM: selftests: Add helper to create guest_memfd with a resource file
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (4 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 05/50] KVM: guest_memfd: Support using resource provider Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-23 23:31 ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 07/50] KVM: selftests: Test negative validation of resource_fd argument Ackerley Tng via B4 Relay
` (44 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add a helper to create guest_memfd instances with a resource file
descriptor, and update the existing creation helper to pass a zero
resource file descriptor.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/include/uapi/linux/kvm.h | 5 ++++-
tools/testing/selftests/kvm/include/kvm_util.h | 23 ++++++++++++++++++-----
2 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index 419011097fa8e..66c1f40b50c29 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1654,11 +1654,14 @@ struct kvm_memory_attributes {
#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd)
#define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
#define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1)
+#define GUEST_MEMFD_FLAG_USE_RESOURCE (1ULL << 2)
struct kvm_create_guest_memfd {
__u64 size;
__u64 flags;
- __u64 reserved[6];
+ __s32 resource_fd;
+ __u32 pad;
+ __u64 reserved[5];
};
#define KVM_PRE_FAULT_MEMORY _IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index e2618877619e1..eca296539db79 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -809,26 +809,39 @@ static inline bool is_smt_on(void)
void vm_create_irqchip(struct kvm_vm *vm);
-static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
- u64 flags)
+static inline int __vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,
+ u64 flags, int resource_fd)
{
struct kvm_create_guest_memfd guest_memfd = {
.size = size,
.flags = flags,
+ .resource_fd = resource_fd,
};
return __vm_ioctl(vm, KVM_CREATE_GUEST_MEMFD, &guest_memfd);
}
-static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
- u64 flags)
+static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
+ u64 flags)
+{
+ return __vm_create_guest_memfd_resource(vm, size, flags, 0);
+}
+
+static inline int vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,
+ u64 flags, int resource_fd)
{
- int fd = __vm_create_guest_memfd(vm, size, flags);
+ int fd = __vm_create_guest_memfd_resource(vm, size, flags, resource_fd);
TEST_ASSERT(fd >= 0, KVM_IOCTL_ERROR(KVM_CREATE_GUEST_MEMFD, fd));
return fd;
}
+static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
+ u64 flags)
+{
+ return vm_create_guest_memfd_resource(vm, size, flags, 0);
+}
+
void vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,
gpa_t gpa, u64 size, void *hva);
int __vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 06/50] KVM: selftests: Add helper to create guest_memfd with a resource file
2026-10-22 23:27 ` [PATCH RFC v2 06/50] KVM: selftests: Add helper to create guest_memfd with a resource file Ackerley Tng via B4 Relay
@ 2026-10-23 23:31 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:31 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] KVM: guest_memfd: missing release_folio() with mapping_set_release_always()
- [Medium] mm: shmem: unmovable guest_memfd folios allocated with GFP_HIGHUSER_MOVABLE
--
commit 27616bb0c537e1022b72cd23715bdb3c99505f19
Author: Ackerley Tng <ackerleytng@google.com>
KVM: selftests: Add helper to create guest_memfd with a resource file
This commit updates the tools UAPI header for KVM_CREATE_GUEST_MEMFD to
define GUEST_MEMFD_FLAG_USE_RESOURCE and resource_fd, and adds selftest
helpers to create a guest_memfd backed by a resource file descriptor.
> diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
> index 419011097fa8e..66c1f40b50c29 100644
> --- a/tools/include/uapi/linux/kvm.h
> +++ b/tools/include/uapi/linux/kvm.h
> @@ -1654,11 +1654,14 @@ struct kvm_memory_attributes {
> #define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd)
> #define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
> #define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1)
> +#define GUEST_MEMFD_FLAG_USE_RESOURCE (1ULL << 2)
>
> struct kvm_create_guest_memfd {
> __u64 size;
> __u64 flags;
> - __u64 reserved[6];
> + __s32 resource_fd;
> + __u32 pad;
> + __u64 reserved[5];
> };
>
> #define KVM_PRE_FAULT_MEMORY _IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)
[Severity: High]
To support provider folio invalidation for guest_memfd in this series,
virt/kvm/guest_memfd.c:kvm_gmem_init_inode() enables AS_RELEASE_ALWAYS so
that truncate_cleanup_folio() sees folio_needs_release(folio) as true and
invokes kvm_gmem_invalidate_folio():
virt/kvm/guest_memfd.c:kvm_gmem_init_inode() {
...
mapping_set_inaccessible(inode->i_mapping);
WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
mapping_set_release_always(inode->i_mapping);
}
However, kvm_gmem_aops in virt/kvm/guest_memfd.c does not define a
.release_folio callback:
static const struct address_space_operations kvm_gmem_aops = {
.dirty_folio = noop_dirty_folio,
.migrate_folio = kvm_gmem_migrate_folio,
.error_remove_folio = kvm_gmem_error_folio,
.invalidate_folio = kvm_gmem_invalidate_folio,
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
.free_folio = kvm_gmem_free_folio,
#endif
};
When folio_needs_release(folio) is true because of AS_RELEASE_ALWAYS and
mapping->a_ops->release_folio is NULL, mm/filemap.c:filemap_release_folio()
falls back to fs/buffer.c:try_to_free_buffers(), which hits a warning on
guest_memfd folios and returns true:
mm/filemap.c:filemap_release_folio() {
if (mapping && mapping->a_ops->release_folio)
return mapping->a_ops->release_folio(folio, gfp);
return try_to_free_buffers(folio);
}
fs/buffer.c:try_to_free_buffers() {
/* Misconfigured folio check */
if (WARN_ON_ONCE(!folio_buffers(folio)))
return true;
...
}
If soft_offline_page() -> mm/memory-failure.c:soft_offline_in_use_page()
(via MADV_SOFT_OFFLINE, sysfs soft_offline_page, or corrected memory error
handling) runs on a clean, unmapped guest_memfd folio (which has PG_lru set
on LRU_UNEVICTABLE by filemap_add_folio() -> folio_add_lru()), or if
mm/memory.c:__do_fault() handles a PageHWPoison fault, it calls
mapping_evict_folio():
mm/memory-failure.c:soft_offline_in_use_page() {
...
if (!huge && folio_test_lru(folio) && !folio_test_swapcache(folio))
/*
* Try to invalidate first. This should work for
* non dirty unmapped page cache pages.
*/
ret = mapping_evict_folio(folio_mapping(folio), folio);
}
Can this cause mapping_evict_folio() -> filemap_release_folio() to trigger
WARN_ON_ONCE(!folio_buffers(folio)) and then call remove_mapping(), evicting
and freeing the folio without calling kvm_gmem_invalidate_folio() or
kvm_gmem_invalidate_start() / kvm_gmem_invalidate_end(), leaking the tmpfs
provider sbinfo->used_blocks accounting and leaving stale stage-2 mappings
pointing to freed pages?
Should kvm_gmem_aops define a .release_folio callback to prevent
filemap_release_folio() from falling back to try_to_free_buffers() and
evicting folios without invalidation?
> diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
> index e2618877619e1..eca296539db79 100644
> --- a/tools/testing/selftests/kvm/include/kvm_util.h
> +++ b/tools/testing/selftests/kvm/include/kvm_util.h
> @@ -809,26 +809,39 @@ static inline bool is_smt_on(void)
>
> void vm_create_irqchip(struct kvm_vm *vm);
>
> -static inline int __vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
> - u64 flags)
> +static inline int __vm_create_guest_memfd_resource(struct kvm_vm *vm, u64 size,
> + u64 flags, int resource_fd)
> {
> struct kvm_create_guest_memfd guest_memfd = {
> .size = size,
> .flags = flags,
> + .resource_fd = resource_fd,
> };
>
> return __vm_ioctl(vm, KVM_CREATE_GUEST_MEMFD, &guest_memfd);
> }
[Severity: Medium]
When creating a guest_memfd with GUEST_MEMFD_FLAG_USE_RESOURCE and a tmpfs
resource_fd via __vm_create_guest_memfd_resource(), faulting or fallocating
pages calls kvm_gmem_get_folio() -> gmem_provider_alloc_folio() ->
mm/shmem.c:shmem_gmem_alloc_folio():
mm/shmem.c:shmem_gmem_alloc_folio() {
...
if (mpol)
folio = folio_alloc_mpol(GFP_HIGHUSER_MOVABLE, 0, mpol, index,
numa_node_id());
else
folio = folio_alloc(GFP_HIGHUSER_MOVABLE, 0);
...
}
In virt/kvm/guest_memfd.c:kvm_gmem_init_inode(), the guest_memfd mapping is
configured with GFP_HIGHUSER (without __GFP_MOVABLE) and marked
AS_INACCESSIBLE because guest_memfd folios cannot be migrated:
virt/kvm/guest_memfd.c:kvm_gmem_init_inode() {
...
mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
/*
* guest_memfd memory is neither migratable nor swappable: set
* inaccessible to gate off both.
*/
mapping_set_inaccessible(inode->i_mapping);
...
}
Unlike gmem_native_alloc_folio(), which uses
mapping_gfp_mask(gi->vfs_inode.i_mapping), shmem_gmem_alloc_folio() passes
GFP_HIGHUSER_MOVABLE. Once kvm_gmem_get_folio() inserts the folio into
inode->i_mapping via filemap_add_folio(), isolate_migratepages_block(),
move_to_new_folio(), and kvm_gmem_migrate_folio() all reject migration on
the AS_INACCESSIBLE mapping.
Could allocating these unmovable folios with GFP_HIGHUSER_MOVABLE place them
in ZONE_MOVABLE or MIGRATE_CMA pageblocks and permanently pin those blocks
against contiguous allocations and memory hot-unplug?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=6
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 07/50] KVM: selftests: Test negative validation of resource_fd argument
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (5 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 06/50] KVM: selftests: Add helper to create guest_memfd with a resource file Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 08/50] KVM: selftests: Test rejection of unsupported filesystem for resource_fd Ackerley Tng via B4 Relay
` (43 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add negative test coverage verifying that creating a guest_memfd
instance with a non-zero resource_fd when GUEST_MEMFD_FLAG_USE_RESOURCE
is clear fails with -EINVAL, and creating with an invalid resource_fd
fails with -EBADF.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index cd5df88bc6427..3313d306c0bdc 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -404,6 +404,8 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
int fd;
for (flag = BIT(0); flag; flag <<= 1) {
+ if (flag == GUEST_MEMFD_FLAG_USE_RESOURCE)
+ continue;
fd = __vm_create_guest_memfd(vm, page_size, flag);
if (flag & valid_flags) {
TEST_ASSERT(fd >= 0,
@@ -418,6 +420,22 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
}
}
+static void test_resource_fd_invalid(struct kvm_vm *vm)
+{
+ int fd;
+
+ /* Non-zero resource_fd without GUEST_MEMFD_FLAG_USE_RESOURCE fails */
+ fd = __vm_create_guest_memfd_resource(vm, page_size, 0, 1);
+ TEST_ASSERT(fd < 0, "guest_memfd with resource_fd but without flag should fail");
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ /* Bad file descriptor with GUEST_MEMFD_FLAG_USE_RESOURCE fails */
+ fd = __vm_create_guest_memfd_resource(vm, page_size,
+ GUEST_MEMFD_FLAG_USE_RESOURCE, -1);
+ TEST_ASSERT(fd < 0, "guest_memfd with -1 resource_fd should fail");
+ TEST_ASSERT_EQ(errno, EBADF);
+}
+
#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
do { \
int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
@@ -476,6 +494,8 @@ static void test_guest_memfd(unsigned long vm_type)
test_guest_memfd_flags(vm);
+ test_resource_fd_invalid(vm);
+
__test_guest_memfd(vm, 0);
flags = vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 08/50] KVM: selftests: Test rejection of unsupported filesystem for resource_fd
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (6 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 07/50] KVM: selftests: Test negative validation of resource_fd argument Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 09/50] KVM: selftests: Test rejection of memfd " Ackerley Tng via B4 Relay
` (42 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add test coverage verifying that passing a directory file descriptor
from an unsupported filesystem without provider operations as a resource
file fails with EOPNOTSUPP.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 3313d306c0bdc..445396860d819 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -436,6 +436,27 @@ static void test_resource_fd_invalid(struct kvm_vm *vm)
TEST_ASSERT_EQ(errno, EBADF);
}
+static void test_resource_fd_unsupported_fs(struct kvm_vm *vm)
+{
+ int fd, file_fd;
+
+ /*
+ * Passing an FD from an unsupported filesystem without provider ops
+ * (e.g. the /proc mount root directory) is rejected by KVM with
+ * EOPNOTSUPP.
+ */
+ file_fd = open("/proc", O_DIRECTORY | O_RDONLY);
+ TEST_ASSERT(file_fd >= 0, "open /proc failed");
+
+ fd = __vm_create_guest_memfd_resource(vm, page_size,
+ GUEST_MEMFD_FLAG_USE_RESOURCE,
+ file_fd);
+ TEST_ASSERT(fd < 0, "guest_memfd with /proc fd should fail");
+ TEST_ASSERT_EQ(errno, EOPNOTSUPP);
+
+ close(file_fd);
+}
+
#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
do { \
int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
@@ -495,6 +516,7 @@ static void test_guest_memfd(unsigned long vm_type)
test_guest_memfd_flags(vm);
test_resource_fd_invalid(vm);
+ test_resource_fd_unsupported_fs(vm);
__test_guest_memfd(vm, 0);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 09/50] KVM: selftests: Test rejection of memfd for resource_fd
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (7 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 08/50] KVM: selftests: Test rejection of unsupported filesystem for resource_fd Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 10/50] KVM: selftests: Test rejection of swap tmpfs mounts Ackerley Tng via B4 Relay
` (41 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add test coverage verifying that passing an fd created via memfd_create
as a resource file fails with EOPNOTSUPP, because regular files do not
implement gmem_provider_ops. Only directory file descriptors representing
the mount root are supported as resource pools.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 445396860d819..d62ae23256083 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -457,6 +457,27 @@ static void test_resource_fd_unsupported_fs(struct kvm_vm *vm)
close(file_fd);
}
+static void test_resource_fd_memfd(struct kvm_vm *vm)
+{
+ int fd, file_fd;
+
+ /*
+ * Passing an fd returned by memfd_create is rejected with EOPNOTSUPP
+ * because regular files do not have gmem_provider_ops defined on their
+ * f_op. Only directory FDs representing the mount root are supported
+ * resource pools.
+ */
+ file_fd = memfd_create("test_memfd", 0);
+ TEST_ASSERT(file_fd >= 0, "memfd_create failed");
+
+ fd = __vm_create_guest_memfd_resource(vm, page_size,
+ GUEST_MEMFD_FLAG_USE_RESOURCE,
+ file_fd);
+ TEST_ASSERT(fd < 0, "guest_memfd with memfd should fail");
+ TEST_ASSERT_EQ(errno, EOPNOTSUPP);
+ close(file_fd);
+}
+
#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
do { \
int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
@@ -517,6 +538,7 @@ static void test_guest_memfd(unsigned long vm_type)
test_resource_fd_invalid(vm);
test_resource_fd_unsupported_fs(vm);
+ test_resource_fd_memfd(vm);
__test_guest_memfd(vm, 0);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 10/50] KVM: selftests: Test rejection of swap tmpfs mounts
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (8 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 09/50] KVM: selftests: Test rejection of memfd " Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-23 23:31 ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 11/50] KVM: selftests: Test rejection of hugepage " Ackerley Tng via B4 Relay
` (40 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Test that guest_memfd creation fails with -EINVAL when passed a resource
pool fd for a mount that has swap enabled.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 70 ++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index d62ae23256083..205f822e10c6c 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -13,7 +13,9 @@
#include <linux/bitmap.h>
#include <linux/falloc.h>
+#include <linux/mount.h>
#include <linux/sizes.h>
+#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -478,6 +480,73 @@ static void test_resource_fd_memfd(struct kvm_vm *vm)
close(file_fd);
}
+static int create_tmpfs_pool_fd(const char *huge, bool noswap, size_t size)
+{
+ char size_str[32];
+ char template[] = "/tmp/gmem_test_dir_XXXXXX";
+ char *mount_dir;
+ int fs_fd, mnt_fd, dir_fd;
+
+ TEST_ASSERT(size && IS_ALIGNED(size, page_size),
+ "Pool size '0x%zx' must be positive and page-aligned", size);
+
+ fs_fd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC);
+ TEST_ASSERT(fs_fd >= 0, "fsopen failed");
+
+ if (noswap)
+ TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_FLAG,
+ "noswap", NULL, 0), "fsconfig noswap failed");
+
+ if (huge)
+ TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
+ "huge", huge, 0), "fsconfig huge failed");
+
+ snprintf(size_str, sizeof(size_str), "%zu", size);
+ TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
+ "size", size_str, 0), "fsconfig size failed");
+
+ TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE,
+ NULL, NULL, 0), "fsconfig create failed");
+
+ mnt_fd = syscall(__NR_fsmount, fs_fd, FSMOUNT_CLOEXEC, 0);
+ TEST_ASSERT(mnt_fd > 0, "fsmount failed");
+ close(fs_fd);
+
+ mount_dir = mkdtemp(template);
+ TEST_ASSERT(mount_dir, "mkdtemp failed");
+
+ TEST_ASSERT(!syscall(__NR_move_mount, mnt_fd, "", AT_FDCWD, mount_dir,
+ MOVE_MOUNT_F_EMPTY_PATH), "move_mount failed");
+ close(mnt_fd);
+
+ dir_fd = open(mount_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ TEST_ASSERT(dir_fd >= 0, "open tmpfs mount directory failed");
+
+ /*
+ * Detach mount from namespace so removing mount_dir upon exit or
+ * unmount doesn't conflict, while dir_fd retains the active mount.
+ */
+ TEST_ASSERT(!umount2(mount_dir, MNT_DETACH), "umount2 failed");
+ TEST_ASSERT(!rmdir(mount_dir), "rmdir failed");
+
+ return dir_fd;
+}
+
+static void test_resource_fd_tmpfs_swap(struct kvm_vm *vm)
+{
+ int pool_fd, fd;
+
+ pool_fd = create_tmpfs_pool_fd("never", false, page_size);
+
+ fd = __vm_create_guest_memfd_resource(vm, page_size,
+ GUEST_MEMFD_FLAG_USE_RESOURCE,
+ pool_fd);
+ TEST_ASSERT(fd < 0, "guest_memfd with swap-enabled tmpfs should fail");
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ close(pool_fd);
+}
+
#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
do { \
int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
@@ -539,6 +608,7 @@ static void test_guest_memfd(unsigned long vm_type)
test_resource_fd_invalid(vm);
test_resource_fd_unsupported_fs(vm);
test_resource_fd_memfd(vm);
+ test_resource_fd_tmpfs_swap(vm);
__test_guest_memfd(vm, 0);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 10/50] KVM: selftests: Test rejection of swap tmpfs mounts
2026-10-22 23:27 ` [PATCH RFC v2 10/50] KVM: selftests: Test rejection of swap tmpfs mounts Ackerley Tng via B4 Relay
@ 2026-10-23 23:31 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:31 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] KVM: selftests: missing CAP_SYS_ADMIN check in create_tmpfs_pool_fd()
--
commit 81fb5dc552f2fa0364e094d89b7aaec3df677efb
Author: Ackerley Tng <ackerleytng@google.com>
KVM: selftests: Test rejection of swap tmpfs mounts
This commit adds a helper to mount a tmpfs instance as a resource pool fd and
tests that creating a guest_memfd backed by a swap-enabled tmpfs mount is
rejected with -EINVAL.
> diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
> index d62ae23256083..205f822e10c6c 100644
> --- a/tools/testing/selftests/kvm/guest_memfd_test.c
> +++ b/tools/testing/selftests/kvm/guest_memfd_test.c
[ ... ]
> @@ -478,6 +480,73 @@ static void test_resource_fd_memfd(struct kvm_vm *vm)
> close(file_fd);
> }
>
> +static int create_tmpfs_pool_fd(const char *huge, bool noswap, size_t size)
> +{
> + char size_str[32];
> + char template[] = "/tmp/gmem_test_dir_XXXXXX";
> + char *mount_dir;
> + int fs_fd, mnt_fd, dir_fd;
> +
> + TEST_ASSERT(size && IS_ALIGNED(size, page_size),
> + "Pool size '0x%zx' must be positive and page-aligned", size);
> +
> + fs_fd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC);
> + TEST_ASSERT(fs_fd >= 0, "fsopen failed");
> +
> + if (noswap)
> + TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_FLAG,
> + "noswap", NULL, 0), "fsconfig noswap failed");
[Severity: Medium]
What happens in tools/testing/selftests/kvm/guest_memfd_test.c when
guest_memfd_test is run by an unprivileged user who has access to /dev/kvm
but lacks CAP_SYS_ADMIN?
Through the call chain:
main()
test_guest_memfd()
test_resource_fd_tmpfs_swap()
create_tmpfs_pool_fd("never", false, page_size)
syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC) checks may_mount() in the
kernel and fails with -EPERM without CAP_SYS_ADMIN (and when noswap is true,
shmem_parse_one() for "noswap" also requires capable(CAP_SYS_ADMIN) and fails
with -EINVAL).
Because test_assert() in tools/testing/selftests/kvm/lib/assert.c only skips
on EACCES and exits with code 254 on EPERM, doesn't TEST_ASSERT(fs_fd >= 0,
...) cause the entire test binary to fail and abort in test_guest_memfd()
before running __test_guest_memfd() and the non-tmpfs guest_memfd tests?
Also, a later patch in this series ("KVM: selftests: Test guest execution
with tmpfs-backed guest_memfd") checks:
if (pool_fd < 0)
TEST_REQUIRE(false);
after calling create_tmpfs_pool_fd(), which appears to be unreachable as long
as create_tmpfs_pool_fd() aborts via TEST_ASSERT() instead of returning an
error or skipping when CAP_SYS_ADMIN is missing. Later in the series ("KVM:
selftests: Test guest_memfd conversions with tmpfs backing"),
guest_memfd_conversions_test will also hit the same failure when run without
CAP_SYS_ADMIN.
Should create_tmpfs_pool_fd() and its callers skip the tmpfs resource pool
tests when the process lacks CAP_SYS_ADMIN, similar to how test_mbind() and
test_numa_allocation() skip on EPERM?
[ ... ]
> @@ -539,6 +608,7 @@ static void test_guest_memfd(unsigned long vm_type)
> test_resource_fd_invalid(vm);
> test_resource_fd_unsupported_fs(vm);
> test_resource_fd_memfd(vm);
> + test_resource_fd_tmpfs_swap(vm);
>
> __test_guest_memfd(vm, 0);
>
титься
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=10
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 11/50] KVM: selftests: Test rejection of hugepage tmpfs mounts
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (9 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 10/50] KVM: selftests: Test rejection of swap tmpfs mounts Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-23 23:32 ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 12/50] KVM: selftests: Test rejection of regular file in tmpfs mount Ackerley Tng via B4 Relay
` (39 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Ensure that guest_memfd creation fails with -EINVAL when passed a tmpfs
resource pool mount descriptor that has huge pages enabled, as required
by the tmpfs guest_memfd provider implementation.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 205f822e10c6c..541d8cdd0bbf7 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -547,6 +547,21 @@ static void test_resource_fd_tmpfs_swap(struct kvm_vm *vm)
close(pool_fd);
}
+static void test_resource_fd_tmpfs_huge(struct kvm_vm *vm)
+{
+ int pool_fd, fd;
+
+ pool_fd = create_tmpfs_pool_fd("always", true, page_size);
+
+ fd = __vm_create_guest_memfd_resource(vm, page_size,
+ GUEST_MEMFD_FLAG_USE_RESOURCE,
+ pool_fd);
+ TEST_ASSERT(fd < 0, "guest_memfd with huge-enabled tmpfs should fail");
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ close(pool_fd);
+}
+
#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
do { \
int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
@@ -609,6 +624,7 @@ static void test_guest_memfd(unsigned long vm_type)
test_resource_fd_unsupported_fs(vm);
test_resource_fd_memfd(vm);
test_resource_fd_tmpfs_swap(vm);
+ test_resource_fd_tmpfs_huge(vm);
__test_guest_memfd(vm, 0);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 11/50] KVM: selftests: Test rejection of hugepage tmpfs mounts
2026-10-22 23:27 ` [PATCH RFC v2 11/50] KVM: selftests: Test rejection of hugepage " Ackerley Tng via B4 Relay
@ 2026-10-23 23:32 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:32 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] KVM: selftests: missing THP check in test_resource_fd_tmpfs_huge()
--
commit fa5c797829415f0417ade9cfcef613bbfe69de07
Author: Ackerley Tng <ackerleytng@google.com>
KVM: selftests: Test rejection of hugepage tmpfs mounts
This commit adds test_resource_fd_tmpfs_huge() to guest_memfd_test to verify
that creating a guest_memfd backed by a tmpfs mount with hugepages enabled
is rejected.
> diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
> index 205f822e10c6c..541d8cdd0bbf7 100644
> --- a/tools/testing/selftests/kvm/guest_memfd_test.c
> +++ b/tools/testing/selftests/kvm/guest_memfd_test.c
> @@ -547,6 +547,21 @@ static void test_resource_fd_tmpfs_swap(struct kvm_vm *vm)
> close(pool_fd);
> }
>
> +static void test_resource_fd_tmpfs_huge(struct kvm_vm *vm)
> +{
> + int pool_fd, fd;
> +
> + pool_fd = create_tmpfs_pool_fd("always", true, page_size);
[Severity: Medium]
Will test_guest_memfd() -> test_resource_fd_tmpfs_huge() ->
create_tmpfs_pool_fd() fail the test on kernels built without transparent
hugepage support or on hardware where transparent hugepages are disabled?
In tools/testing/selftests/kvm/guest_memfd_test.c, create_tmpfs_pool_fd()
asserts that setting the huge mount option succeeds:
if (huge)
TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
"huge", huge, 0), "fsconfig huge failed");
However, in mm/shmem.c, shmem_parse_one() rejects huge=always when
transparent hugepages are not configured or supported:
case Opt_huge:
ctx->huge = result.uint_32;
if (ctx->huge != SHMEM_HUGE_NEVER &&
!(IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) &&
has_transparent_hugepage()))
goto unsupported_parameter;
And the check under test in shmem_gmem_attach() in mm/shmem.c is also
compiled out in that configuration:
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
if (sbinfo->huge != SHMEM_HUGE_NEVER)
return ERR_PTR(-EINVAL);
#endif
Should test_resource_fd_tmpfs_huge() check thp_configured() before calling
create_tmpfs_pool_fd("always", true, page_size) and return early if
transparent hugepages are not available?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=11
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 12/50] KVM: selftests: Test rejection of regular file in tmpfs mount
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (10 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 11/50] KVM: selftests: Test rejection of hugepage " Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 13/50] KVM: selftests: Test guest_memfd with tmpfs resource pool Ackerley Tng via B4 Relay
` (38 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add test coverage verifying that creating a regular file inside an
actual tmpfs mount and passing that file descriptor as resource_fd
fails with EOPNOTSUPP, because tmpfs regular files do not have
gmem_provider_ops defined on their file_operations.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 541d8cdd0bbf7..bef1acead4a6d 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -613,6 +613,25 @@ static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
gmem_test_vm(invalid_binding, vm, flags);
}
+static void test_resource_fd_tmpfs_regular_file(struct kvm_vm *vm)
+{
+ int pool_fd, file_fd, fd;
+
+ pool_fd = create_tmpfs_pool_fd("never", true, page_size);
+
+ file_fd = openat(pool_fd, "regular_file", O_CREAT | O_RDWR | O_CLOEXEC, 0644);
+ TEST_ASSERT(file_fd >= 0, "openat regular_file in tmpfs pool failed");
+
+ fd = __vm_create_guest_memfd_resource(vm, page_size,
+ GUEST_MEMFD_FLAG_USE_RESOURCE,
+ file_fd);
+ TEST_ASSERT(fd < 0, "guest_memfd with tmpfs regular file should fail");
+ TEST_ASSERT_EQ(errno, EOPNOTSUPP);
+
+ close(file_fd);
+ close(pool_fd);
+}
+
static void test_guest_memfd(unsigned long vm_type)
{
struct kvm_vm *vm = vm_create_barebones_type(vm_type);
@@ -625,6 +644,7 @@ static void test_guest_memfd(unsigned long vm_type)
test_resource_fd_memfd(vm);
test_resource_fd_tmpfs_swap(vm);
test_resource_fd_tmpfs_huge(vm);
+ test_resource_fd_tmpfs_regular_file(vm);
__test_guest_memfd(vm, 0);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 13/50] KVM: selftests: Test guest_memfd with tmpfs resource pool
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (11 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 12/50] KVM: selftests: Test rejection of regular file in tmpfs mount Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 14/50] KVM: selftests: Test guest_memfd resource pool sharing across instances Ackerley Tng via B4 Relay
` (37 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add test coverage verifying that creating a guest_memfd backed by a
tmpfs resource pool directory succeeds across all guest_memfd subtests
and flag combinations, and reports the requested file size.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 35 ++++++++++++++++++++------
1 file changed, 27 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index bef1acead4a6d..2250c3e9a583f 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -562,12 +562,23 @@ static void test_resource_fd_tmpfs_huge(struct kvm_vm *vm)
close(pool_fd);
}
-#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
-do { \
- int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
- \
- test_##__test(args); \
- close(fd); \
+#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
+do { \
+ int pool_fd = -1; \
+ int fd; \
+ \
+ if ((__flags) & GUEST_MEMFD_FLAG_USE_RESOURCE) { \
+ pool_fd = create_tmpfs_pool_fd("never", true, __gmem_size); \
+ fd = vm_create_guest_memfd_resource(__vm, __gmem_size, \
+ __flags, pool_fd); \
+ } else { \
+ fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
+ } \
+ \
+ test_##__test(args); \
+ close(fd); \
+ if (pool_fd >= 0) \
+ close(pool_fd); \
} while (0)
#define __gmem_test(__test, __vm, __flags, __gmem_size) \
@@ -647,15 +658,23 @@ static void test_guest_memfd(unsigned long vm_type)
test_resource_fd_tmpfs_regular_file(vm);
__test_guest_memfd(vm, 0);
+ __test_guest_memfd(vm, GUEST_MEMFD_FLAG_USE_RESOURCE);
flags = vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS);
- if (flags & GUEST_MEMFD_FLAG_MMAP)
+ if (flags & GUEST_MEMFD_FLAG_MMAP) {
__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP);
+ __test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+ GUEST_MEMFD_FLAG_USE_RESOURCE);
+ }
/* MMAP should always be supported if INIT_SHARED is supported. */
- if (flags & GUEST_MEMFD_FLAG_INIT_SHARED)
+ if (flags & GUEST_MEMFD_FLAG_INIT_SHARED) {
__test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
GUEST_MEMFD_FLAG_INIT_SHARED);
+ __test_guest_memfd(vm, GUEST_MEMFD_FLAG_MMAP |
+ GUEST_MEMFD_FLAG_INIT_SHARED |
+ GUEST_MEMFD_FLAG_USE_RESOURCE);
+ }
kvm_vm_free(vm);
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 14/50] KVM: selftests: Test guest_memfd resource pool sharing across instances
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (12 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 13/50] KVM: selftests: Test guest_memfd with tmpfs resource pool Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 15/50] KVM: selftests: Test memory allocation against shared tmpfs resource pool Ackerley Tng via B4 Relay
` (36 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Test that multiple guest_memfd instances can be created backed by the
same tmpfs resource pool descriptor, that each file descriptor reflects
its requested size, and that they have distinct inodes.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 34 ++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 2250c3e9a583f..310a45b6656aa 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -562,6 +562,39 @@ static void test_resource_fd_tmpfs_huge(struct kvm_vm *vm)
close(pool_fd);
}
+static void test_resource_fd_tmpfs_shared_pool(struct kvm_vm *vm)
+{
+ int pool_fd, fd1, fd2;
+ struct stat st1, st2;
+
+ pool_fd = create_tmpfs_pool_fd("never", true, page_size * 2);
+
+ fd1 = vm_create_guest_memfd_resource(vm, page_size,
+ GUEST_MEMFD_FLAG_USE_RESOURCE,
+ pool_fd);
+
+ fd2 = vm_create_guest_memfd_resource(vm, page_size * 2,
+ GUEST_MEMFD_FLAG_USE_RESOURCE,
+ pool_fd);
+
+ TEST_ASSERT(!fstat(fd1, &st1), "fstat on fd1 should succeed");
+ TEST_ASSERT(st1.st_size == page_size,
+ "fd1 st_size (%lu) should match requested size (%lu)",
+ st1.st_size, page_size);
+
+ TEST_ASSERT(!fstat(fd2, &st2), "fstat on fd2 should succeed");
+ TEST_ASSERT(st2.st_size == page_size * 2,
+ "fd2 st_size (%lu) should match requested size (%lu)",
+ st2.st_size, page_size * 2);
+
+ TEST_ASSERT(st1.st_ino != st2.st_ino,
+ "different guest_memfd instances should have distinct inodes");
+
+ close(fd2);
+ close(fd1);
+ close(pool_fd);
+}
+
#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
do { \
int pool_fd = -1; \
@@ -656,6 +689,7 @@ static void test_guest_memfd(unsigned long vm_type)
test_resource_fd_tmpfs_swap(vm);
test_resource_fd_tmpfs_huge(vm);
test_resource_fd_tmpfs_regular_file(vm);
+ test_resource_fd_tmpfs_shared_pool(vm);
__test_guest_memfd(vm, 0);
__test_guest_memfd(vm, GUEST_MEMFD_FLAG_USE_RESOURCE);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 15/50] KVM: selftests: Test memory allocation against shared tmpfs resource pool
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (13 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 14/50] KVM: selftests: Test guest_memfd resource pool sharing across instances Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 16/50] KVM: selftests: Test that shared tmpfs resource pool size limit is respected Ackerley Tng via B4 Relay
` (35 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Verify that allocating folios in multiple guest_memfd instances sharing
the same tmpfs resource pool consumes blocks from the underlying pool as
reported by filesystem statistics.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 310a45b6656aa..639b1b0c204ed 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -18,6 +18,7 @@
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <sys/statvfs.h>
#include "kvm_syscalls.h"
#include "kvm_util.h"
@@ -564,7 +565,8 @@ static void test_resource_fd_tmpfs_huge(struct kvm_vm *vm)
static void test_resource_fd_tmpfs_shared_pool(struct kvm_vm *vm)
{
- int pool_fd, fd1, fd2;
+ int pool_fd, fd1, fd2, ret;
+ struct statvfs svfs;
struct stat st1, st2;
pool_fd = create_tmpfs_pool_fd("never", true, page_size * 2);
@@ -590,6 +592,18 @@ static void test_resource_fd_tmpfs_shared_pool(struct kvm_vm *vm)
TEST_ASSERT(st1.st_ino != st2.st_ino,
"different guest_memfd instances should have distinct inodes");
+ ret = fallocate(fd1, FALLOC_FL_KEEP_SIZE, 0, page_size);
+ TEST_ASSERT(!ret, "fallocate on fd1 should succeed");
+
+ TEST_ASSERT(!fstatvfs(pool_fd, &svfs), "fstatvfs on pool_fd should succeed");
+ TEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 1);
+
+ ret = fallocate(fd2, FALLOC_FL_KEEP_SIZE, 0, page_size);
+ TEST_ASSERT(!ret, "fallocate on fd2 should succeed");
+
+ TEST_ASSERT(!fstatvfs(pool_fd, &svfs), "fstatvfs on pool_fd should succeed");
+ TEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 2);
+
close(fd2);
close(fd1);
close(pool_fd);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 16/50] KVM: selftests: Test that shared tmpfs resource pool size limit is respected
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (14 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 15/50] KVM: selftests: Test memory allocation against shared tmpfs resource pool Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 17/50] KVM: selftests: Test guest execution with tmpfs-backed guest_memfd Ackerley Tng via B4 Relay
` (34 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Verify that allocating memory across guest_memfd instances sharing a
tmpfs resource pool fails with ENOSPC when the total allocation exceeds
the pool capacity.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 639b1b0c204ed..20de24ad535f1 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -604,6 +604,10 @@ static void test_resource_fd_tmpfs_shared_pool(struct kvm_vm *vm)
TEST_ASSERT(!fstatvfs(pool_fd, &svfs), "fstatvfs on pool_fd should succeed");
TEST_ASSERT_EQ(svfs.f_blocks - svfs.f_bfree, 2);
+ ret = fallocate(fd2, FALLOC_FL_KEEP_SIZE, page_size, page_size);
+ TEST_ASSERT(ret < 0, "fallocate exceeding pool capacity should fail");
+ TEST_ASSERT_EQ(errno, ENOSPC);
+
close(fd2);
close(fd1);
close(pool_fd);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 17/50] KVM: selftests: Test guest execution with tmpfs-backed guest_memfd
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (15 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 16/50] KVM: selftests: Test that shared tmpfs resource pool size limit is respected Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-23 23:32 ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 18/50] KVM: selftests: Document testing TODOs Ackerley Tng via B4 Relay
` (33 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Test that a guest vCPU can execute instructions and read/write memory
mapped into a guest_memfd instance that is backed by a tmpfs resource
pool descriptor.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 56 ++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 20de24ad535f1..8e9261db23354 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -799,6 +799,61 @@ static void test_guest_memfd_guest(void)
kvm_vm_free(vm);
}
+static void test_guest_memfd_guest_resource(void)
+{
+ const gpa_t gpa = SZ_4G;
+ const int slot = 1;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ int pool_fd, fd, i;
+ size_t size;
+ u8 *mem;
+
+ if (!kvm_check_cap(KVM_CAP_GUEST_MEMFD_FLAGS))
+ return;
+
+ pool_fd = create_tmpfs_pool_fd("never", true, page_size);
+ if (pool_fd < 0)
+ TEST_REQUIRE(false);
+
+ vm = __vm_create_shape_with_one_vcpu(VM_SHAPE_DEFAULT, &vcpu, 1, guest_code);
+
+ TEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) & GUEST_MEMFD_FLAG_MMAP,
+ "Default VM type should support MMAP, supported flags = 0x%x",
+ vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));
+ TEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) & GUEST_MEMFD_FLAG_INIT_SHARED,
+ "Default VM type should support INIT_SHARED, supported flags = 0x%x",
+ vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));
+
+ size = max_t(size_t, vm->page_size, page_size);
+ fd = __vm_create_guest_memfd_resource(vm, size,
+ GUEST_MEMFD_FLAG_MMAP |
+ GUEST_MEMFD_FLAG_INIT_SHARED |
+ GUEST_MEMFD_FLAG_USE_RESOURCE,
+ pool_fd);
+ TEST_ASSERT(fd >= 0, "guest_memfd with tmpfs pool should succeed");
+
+ vm_set_user_memory_region2(vm, slot, KVM_MEM_GUEST_MEMFD, gpa, size, NULL, fd, 0);
+
+ mem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
+ memset(mem, 0xaa, size);
+ kvm_munmap(mem, size);
+
+ virt_map(vm, gpa, gpa, size / vm->page_size);
+ vcpu_args_set(vcpu, 2, gpa, size);
+ vcpu_run(vcpu);
+
+ TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
+
+ mem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
+ for (i = 0; i < size; i++)
+ TEST_ASSERT_EQ(mem[i], 0xff);
+
+ close(fd);
+ close(pool_fd);
+ kvm_vm_free(vm);
+}
+
int main(int argc, char *argv[])
{
unsigned long vm_types, vm_type;
@@ -819,4 +874,5 @@ int main(int argc, char *argv[])
test_guest_memfd(vm_type);
test_guest_memfd_guest();
+ test_guest_memfd_guest_resource();
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 17/50] KVM: selftests: Test guest execution with tmpfs-backed guest_memfd
2026-10-22 23:27 ` [PATCH RFC v2 17/50] KVM: selftests: Test guest execution with tmpfs-backed guest_memfd Ackerley Tng via B4 Relay
@ 2026-10-23 23:32 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:32 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] KVM: selftests: undersized tmpfs pool in test_guest_memfd_guest_resource()
- [Low] KVM: selftests: dead pool_fd check and duplication in test_guest_memfd_guest_resource()
--
commit a03f3023933c2e4a7447fa35e4f2470380c225bd
Author: Ackerley Tng <ackerleytng@google.com>
KVM: selftests: Test guest execution with tmpfs-backed guest_memfd
This patch adds a selftest in guest_memfd_test.c to verify that a guest
vCPU can execute instructions and read/write memory backed by a
tmpfs-backed guest_memfd resource pool.
> diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
> index 20de24ad535f1..8e9261db23354 100644
> --- a/tools/testing/selftests/kvm/guest_memfd_test.c
> +++ b/tools/testing/selftests/kvm/guest_memfd_test.c
> @@ -799,6 +799,61 @@ static void test_guest_memfd_guest(void)
> kvm_vm_free(vm);
> }
>
> +static void test_guest_memfd_guest_resource(void)
> +{
> + const gpa_t gpa = SZ_4G;
> + const int slot = 1;
> + struct kvm_vcpu *vcpu;
> + struct kvm_vm *vm;
> + int pool_fd, fd, i;
> + size_t size;
> + u8 *mem;
> +
> + if (!kvm_check_cap(KVM_CAP_GUEST_MEMFD_FLAGS))
> + return;
> +
> + pool_fd = create_tmpfs_pool_fd("never", true, page_size);
> + if (pool_fd < 0)
> + TEST_REQUIRE(false);
[Severity: Low]
In test_guest_memfd_guest_resource() in
tools/testing/selftests/kvm/guest_memfd_test.c, can pool_fd < 0 ever be
true here?
Looking at create_tmpfs_pool_fd(), every failure path already asserts via
TEST_ASSERT() before returning dir_fd:
dir_fd = open(mount_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
TEST_ASSERT(dir_fd >= 0, "open tmpfs mount directory failed");
...
return dir_fd;
Also, since test_guest_memfd_guest_resource() duplicates the VM setup,
memslot configuration, guest execution, and memory verification of
test_guest_memfd_guest(), could test_guest_memfd_guest() be parameterized
(and use vm_create_guest_memfd_resource()) instead of duplicating the test
function?
> +
> + vm = __vm_create_shape_with_one_vcpu(VM_SHAPE_DEFAULT, &vcpu, 1, guest_code);
> +
> + TEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) & GUEST_MEMFD_FLAG_MMAP,
> + "Default VM type should support MMAP, supported flags = 0x%x",
> + vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));
> + TEST_ASSERT(vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) & GUEST_MEMFD_FLAG_INIT_SHARED,
> + "Default VM type should support INIT_SHARED, supported flags = 0x%x",
> + vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS));
> +
> + size = max_t(size_t, vm->page_size, page_size);
> + fd = __vm_create_guest_memfd_resource(vm, size,
> + GUEST_MEMFD_FLAG_MMAP |
> + GUEST_MEMFD_FLAG_INIT_SHARED |
> + GUEST_MEMFD_FLAG_USE_RESOURCE,
> + pool_fd);
> + TEST_ASSERT(fd >= 0, "guest_memfd with tmpfs pool should succeed");
> +
> + vm_set_user_memory_region2(vm, slot, KVM_MEM_GUEST_MEMFD, gpa, size, NULL, fd, 0);
> +
> + mem = kvm_mmap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
> + memset(mem, 0xaa, size);
[Severity: Medium]
In test_guest_memfd_guest_resource() in
tools/testing/selftests/kvm/guest_memfd_test.c, what happens when
vm->page_size is larger than the host page_size?
pool_fd is created earlier via create_tmpfs_pool_fd("never", true,
page_size), which limits the backing tmpfs mount capacity to a single host
page. When vm->page_size > page_size, size is computed as
max_t(size_t, vm->page_size, page_size) and exceeds the pool capacity.
When memset(mem, 0xaa, size) faults in pages beyond the first host page,
shmem_gmem_alloc_folio() will fail with -ENOSPC and
kvm_gmem_fault_user_mapping() will raise SIGBUS.
Should pool_fd be created after size is computed, passing size instead of
page_size to create_tmpfs_pool_fd()?
[ ... ]
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=17
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 18/50] KVM: selftests: Document testing TODOs
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (16 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 17/50] KVM: selftests: Test guest execution with tmpfs-backed guest_memfd Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 19/50] KVM: selftests: Test guest_memfd conversions with tmpfs backing Ackerley Tng via B4 Relay
` (32 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Remind myself about all the tests (A)I did not write.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 8e9261db23354..fd249445ce3cc 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -701,6 +701,16 @@ static void test_guest_memfd(unsigned long vm_type)
test_guest_memfd_flags(vm);
+ /*
+ * TODO: Test that guest_memfd creation rejects a read-only tmpfs mount
+ * (e.g. MOUNT_ATTR_RDONLY) with EROFS.
+ *
+ * TODO: Test that guest_memfd creation rejects an ID-mapped tmpfs mount
+ * (e.g. MOUNT_ATTR_IDMAP) lacking write or execute/search permissions
+ * with EACCES.
+ *
+ * TODO: Test mpol fallback to mount config.
+ */
test_resource_fd_invalid(vm);
test_resource_fd_unsupported_fs(vm);
test_resource_fd_memfd(vm);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 19/50] KVM: selftests: Test guest_memfd conversions with tmpfs backing
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (17 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 18/50] KVM: selftests: Document testing TODOs Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 20/50] samples/kvm: Add a memory provider sample Ackerley Tng via B4 Relay
` (31 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Move create_tmpfs_pool_fd() into lib/test_util.c and declare it in
include/test_util.h so it can be reused across selftests.
Extend guest_memfd_conversions_test using fixture variants to run all
conversion test cases against both native guest_memfd and tmpfs-backed
guest_memfd instances.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 52 --------------------
tools/testing/selftests/kvm/include/test_util.h | 2 +
tools/testing/selftests/kvm/lib/test_util.c | 56 ++++++++++++++++++++++
.../kvm/x86/guest_memfd_conversions_test.c | 49 +++++++++++++++++--
4 files changed, 102 insertions(+), 57 deletions(-)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index fd249445ce3cc..cbc2f309664b2 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -481,58 +481,6 @@ static void test_resource_fd_memfd(struct kvm_vm *vm)
close(file_fd);
}
-static int create_tmpfs_pool_fd(const char *huge, bool noswap, size_t size)
-{
- char size_str[32];
- char template[] = "/tmp/gmem_test_dir_XXXXXX";
- char *mount_dir;
- int fs_fd, mnt_fd, dir_fd;
-
- TEST_ASSERT(size && IS_ALIGNED(size, page_size),
- "Pool size '0x%zx' must be positive and page-aligned", size);
-
- fs_fd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC);
- TEST_ASSERT(fs_fd >= 0, "fsopen failed");
-
- if (noswap)
- TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_FLAG,
- "noswap", NULL, 0), "fsconfig noswap failed");
-
- if (huge)
- TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
- "huge", huge, 0), "fsconfig huge failed");
-
- snprintf(size_str, sizeof(size_str), "%zu", size);
- TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
- "size", size_str, 0), "fsconfig size failed");
-
- TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE,
- NULL, NULL, 0), "fsconfig create failed");
-
- mnt_fd = syscall(__NR_fsmount, fs_fd, FSMOUNT_CLOEXEC, 0);
- TEST_ASSERT(mnt_fd > 0, "fsmount failed");
- close(fs_fd);
-
- mount_dir = mkdtemp(template);
- TEST_ASSERT(mount_dir, "mkdtemp failed");
-
- TEST_ASSERT(!syscall(__NR_move_mount, mnt_fd, "", AT_FDCWD, mount_dir,
- MOVE_MOUNT_F_EMPTY_PATH), "move_mount failed");
- close(mnt_fd);
-
- dir_fd = open(mount_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
- TEST_ASSERT(dir_fd >= 0, "open tmpfs mount directory failed");
-
- /*
- * Detach mount from namespace so removing mount_dir upon exit or
- * unmount doesn't conflict, while dir_fd retains the active mount.
- */
- TEST_ASSERT(!umount2(mount_dir, MNT_DETACH), "umount2 failed");
- TEST_ASSERT(!rmdir(mount_dir), "rmdir failed");
-
- return dir_fd;
-}
-
static void test_resource_fd_tmpfs_swap(struct kvm_vm *vm)
{
int pool_fd, fd;
diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h
index c80019e24b2d0..e6707534f1951 100644
--- a/tools/testing/selftests/kvm/include/test_util.h
+++ b/tools/testing/selftests/kvm/include/test_util.h
@@ -244,4 +244,6 @@ char *strdup_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2), n
char *sys_get_cur_clocksource(void);
+int create_tmpfs_pool_fd(const char *huge, bool noswap, size_t size);
+
#endif /* SELFTEST_KVM_TEST_UTIL_H */
diff --git a/tools/testing/selftests/kvm/lib/test_util.c b/tools/testing/selftests/kvm/lib/test_util.c
index f5f7189f96a84..c5b264f533ad0 100644
--- a/tools/testing/selftests/kvm/lib/test_util.c
+++ b/tools/testing/selftests/kvm/lib/test_util.c
@@ -10,10 +10,14 @@
#include <ctype.h>
#include <limits.h>
#include <stdlib.h>
+#include <fcntl.h>
#include <time.h>
+#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
+#include <linux/align.h>
#include <linux/mman.h>
+#include <linux/mount.h>
#include "linux/kernel.h"
#include "test_util.h"
@@ -483,3 +487,55 @@ char *sys_get_cur_clocksource(void)
return clk_name;
}
+
+int create_tmpfs_pool_fd(const char *huge, bool noswap, size_t size)
+{
+ char size_str[32];
+ char template[] = "/tmp/gmem_test_dir_XXXXXX";
+ char *mount_dir;
+ int fs_fd, mnt_fd, dir_fd;
+
+ TEST_ASSERT(size && IS_ALIGNED(size, getpagesize()),
+ "Pool size '0x%zx' must be positive and page-aligned", size);
+
+ fs_fd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC);
+ TEST_ASSERT(fs_fd >= 0, "fsopen failed");
+
+ if (noswap)
+ TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_FLAG,
+ "noswap", NULL, 0), "fsconfig noswap failed");
+
+ if (huge)
+ TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
+ "huge", huge, 0), "fsconfig huge failed");
+
+ snprintf(size_str, sizeof(size_str), "%zu", size);
+ TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_SET_STRING,
+ "size", size_str, 0), "fsconfig size failed");
+
+ TEST_ASSERT(!syscall(__NR_fsconfig, fs_fd, FSCONFIG_CMD_CREATE,
+ NULL, NULL, 0), "fsconfig create failed");
+
+ mnt_fd = syscall(__NR_fsmount, fs_fd, FSMOUNT_CLOEXEC, 0);
+ TEST_ASSERT(mnt_fd > 0, "fsmount failed");
+ close(fs_fd);
+
+ mount_dir = mkdtemp(template);
+ TEST_ASSERT(mount_dir, "mkdtemp failed");
+
+ TEST_ASSERT(!syscall(__NR_move_mount, mnt_fd, "", AT_FDCWD, mount_dir,
+ MOVE_MOUNT_F_EMPTY_PATH), "move_mount failed");
+ close(mnt_fd);
+
+ dir_fd = open(mount_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ TEST_ASSERT(dir_fd >= 0, "open tmpfs mount directory failed");
+
+ /*
+ * Detach mount from namespace so removing mount_dir upon exit or
+ * unmount doesn't conflict, while dir_fd retains the active mount.
+ */
+ TEST_ASSERT(!umount2(mount_dir, MNT_DETACH), "umount2 failed");
+ TEST_ASSERT(!rmdir(mount_dir), "rmdir failed");
+
+ return dir_fd;
+}
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index e1c84df0f0596..023df9cb9d4a6 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -20,12 +20,30 @@
FIXTURE(gmem_conversions) {
struct kvm_vcpu *vcpu;
int gmem_fd;
+ int resource_fd;
/* HVA of the first byte of the memory mmap()-ed from gmem_fd. */
char *mem;
};
typedef FIXTURE_DATA(gmem_conversions) test_data_t;
+FIXTURE_VARIANT(gmem_conversions) {
+ int (*create_resource)(size_t size);
+};
+
+FIXTURE_VARIANT_ADD(gmem_conversions, native) {
+ .create_resource = NULL,
+};
+
+static int create_tmpfs_resource(size_t size)
+{
+ return create_tmpfs_pool_fd("never", true, size);
+}
+
+FIXTURE_VARIANT_ADD(gmem_conversions, tmpfs) {
+ .create_resource = create_tmpfs_resource,
+};
+
FIXTURE_SETUP(gmem_conversions) { }
static size_t page_size;
@@ -37,8 +55,9 @@ static void guest_do_rmw(void);
* Defer setup until the individual test is invoked so that tests can specify
* the number of pages and flags for the guest_memfd instance.
*/
-static void gmem_conversions_do_setup(test_data_t *t, int nr_pages,
- int gmem_flags)
+static void gmem_conversions_do_setup(test_data_t *t,
+ const FIXTURE_VARIANT(gmem_conversions) * variant,
+ int nr_pages, int gmem_flags)
{
const struct vm_shape shape = {
.mode = VM_MODE_DEFAULT,
@@ -50,12 +69,27 @@ static void gmem_conversions_do_setup(test_data_t *t, int nr_pages,
*/
const gpa_t gpa = SZ_4G;
const u32 slot = 1;
+ size_t size = nr_pages * page_size;
struct kvm_vm *vm;
+ int gmem_fd = -1;
vm = __vm_create_shape_with_one_vcpu(shape, &t->vcpu, nr_pages, guest_do_rmw);
+ t->resource_fd = -1;
+
+ if (variant->create_resource) {
+ uint64_t flags = gmem_flags | GUEST_MEMFD_FLAG_USE_RESOURCE;
+
+ t->resource_fd = variant->create_resource(size);
+ TEST_ASSERT(t->resource_fd >= 0, "Failed to create backing resource");
+
+ gmem_fd = __vm_create_guest_memfd_resource(vm, size, flags,
+ t->resource_fd);
+ TEST_ASSERT(gmem_fd >= 0, "Failed to create guest_memfd with resource");
+ }
+
vm_mem_add(vm, VM_MEM_SRC_SHMEM, gpa, slot, nr_pages,
- KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags);
+ KVM_MEM_GUEST_MEMFD, gmem_fd, 0, gmem_flags);
t->gmem_fd = kvm_slot_to_fd(vm, slot);
t->mem = addr_gpa2hva(vm, gpa);
@@ -68,6 +102,11 @@ static void gmem_conversions_do_teardown(test_data_t *t)
if (!t->vcpu)
return;
+ if (t->resource_fd >= 0) {
+ close(t->resource_fd);
+ t->resource_fd = -1;
+ }
+
/* No need to close gmem_fd, it's owned by the VM structure. */
kvm_vm_free(t->vcpu->vm);
t->vcpu = NULL;
@@ -91,7 +130,7 @@ TEST_F(gmem_conversions, test) \
{ \
const int nr = (__nr_pages); \
\
- gmem_conversions_do_setup(self, nr, flags); \
+ gmem_conversions_do_setup(self, variant, nr, flags); \
__gmem_conversions_##test(self, nr); \
} \
static void __gmem_conversions_##test(test_data_t *t, int nr_pages) \
@@ -127,7 +166,7 @@ TEST_F(gmem_conversions, test) \
int i; \
\
for (i = 0; i < nr; ++i) { \
- gmem_conversions_do_setup(self, nr, flags); \
+ gmem_conversions_do_setup(self, variant, nr, flags); \
__gmem_conversions_multipage_##test(self, nr, i); \
gmem_conversions_do_teardown(self); \
} \
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 20/50] samples/kvm: Add a memory provider sample
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (18 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 19/50] KVM: selftests: Test guest_memfd conversions with tmpfs backing Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 21/50] samples/kvm: Remove donate, reclaim, and move operations from sample provider Ackerley Tng via B4 Relay
` (30 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Fred Griffoul <fgriffo@amazon.co.uk>
The memory provider interface has no in-tree provider that a VMM can
use.
Add mem_provider_sample. It lends a region, either a fixed range given
with addr= and len= or memory from alloc_contig_pages(), as child files
that a VMM passes to both guest_memfd and iommufd. A control device
moves pages between children, takes them back and makes them read
only, and every change revokes the range. It uses no KVM, dma-buf or
iommufd symbol.
The fixed range is not System RAM, so the sample keeps a write-back
memremap() of it while a file over it may be mapped into userspace.
Without it, PAT would make the range uncached on x86, and the VMM's
mapping would be uncached while the guest's is write-back. The sample
is x86 only, as guest_memfd accepts providers only there.
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/Kconfig | 17 +
samples/Makefile | 1 +
samples/kvm/Makefile | 2 +
samples/kvm/mem_provider_sample.c | 855 ++++++++++++++++++++++++++++++++++++++
samples/kvm/mem_provider_sample.h | 135 ++++++
5 files changed, 1010 insertions(+)
diff --git a/samples/Kconfig b/samples/Kconfig
index a75e8e78330da..3c870bca076af 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -326,6 +326,23 @@ source "samples/rust/Kconfig"
source "samples/damon/Kconfig"
+config SAMPLE_KVM_MEM_PROVIDER
+ tristate "Build sample memory provider -- loadable module only"
+ depends on MEM_PROVIDER && CONTIG_ALLOC && X86_64 && m
+ help
+ This builds a sample memory provider (include/linux/mem_provider.h).
+ Its files can back a guest_memfd and be mapped by iommufd. Loaded
+ with addr= and len=, it lends a fixed physical range that has no
+ struct page, for example memory hidden with memmap= on the command
+ line. Without them, it allocates a contiguous region with
+ alloc_contig_pages().
+
+ It shows how a memory owner moves pages between VMs, takes them
+ back or makes them read only, and how guest_memfd and iommufd
+ follow each change.
+
+ If unsure, say N.
+
endif # SAMPLES
config HAVE_SAMPLE_FTRACE_DIRECT
diff --git a/samples/Makefile b/samples/Makefile
index 07641e177bd8b..e7dfb0bb15a5b 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_SAMPLE_TPS6594_PFSM) += pfsm/
subdir-$(CONFIG_SAMPLE_WATCHDOG) += watchdog
subdir-$(CONFIG_SAMPLE_WATCH_QUEUE) += watch_queue
obj-$(CONFIG_SAMPLE_KMEMLEAK) += kmemleak/
+obj-$(CONFIG_SAMPLE_KVM_MEM_PROVIDER) += kvm/
obj-$(CONFIG_SAMPLE_CORESIGHT_SYSCFG) += coresight/
obj-$(CONFIG_SAMPLE_FPROBE) += fprobe/
obj-$(CONFIG_SAMPLES_RUST) += rust/
diff --git a/samples/kvm/Makefile b/samples/kvm/Makefile
new file mode 100644
index 0000000000000..2ce44fc0aa8e3
--- /dev/null
+++ b/samples/kvm/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_SAMPLE_KVM_MEM_PROVIDER) += mem_provider_sample.o
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
new file mode 100644
index 0000000000000..8180959ab78ff
--- /dev/null
+++ b/samples/kvm/mem_provider_sample.c
@@ -0,0 +1,855 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mem_provider_sample - a sample memory provider.
+ *
+ * The module owns a region of physical memory and lends it through provider
+ * files (include/linux/mem_provider.h). A VMM passes the same file to
+ * KVM_CREATE_GUEST_MEMFD, for the guest, and to IOMMU_IOAS_MAP_FILE, for the
+ * guest's devices. The module calls neither KVM nor iommufd: it answers
+ * "what is page N" and revokes a range when the answer changes.
+ *
+ * The region is either:
+ *
+ * - a fixed range given with addr= and len=, which has no struct page, for
+ * example memory hidden from the kernel with memmap= on the command line;
+ * - or, without those parameters, a range from alloc_contig_pages().
+ *
+ * The control device /dev/mem_provider_sample creates provider files.
+ * SETUP makes one file over the whole region. NEW_CHILD carves part of the
+ * region into a child file, and MOVE, DONATE and RECLAIM change which child
+ * has which page. A page can also be taken away or made read only. See
+ * mem_provider_sample.h.
+ *
+ * Confidential VMs are not supported.
+ */
+
+#include <linux/anon_inodes.h>
+#include <linux/bitmap.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/gfp.h>
+#include <linux/io.h>
+#include <linux/mem_provider.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include "mem_provider_sample.h"
+
+static unsigned long long addr;
+module_param(addr, ullong, 0444);
+MODULE_PARM_DESC(addr,
+ "Physical base of a region with no struct page (optional)");
+
+static unsigned long long len;
+module_param(len, ullong, 0444);
+MODULE_PARM_DESC(len, "Size in bytes of that region (optional)");
+
+/*
+ * One provider file.
+ *
+ * Lock order: mps_root.lock, then mps_info.lock, then the consumers' locks
+ * (taken by their revoke callbacks). get_page() takes no lock, so a
+ * consumer can call it from its fault paths and its revoke callback.
+ */
+struct mps_info {
+ struct mem_provider_file mpf;
+ struct mutex lock; /* the bitmaps */
+
+ unsigned long base_pfn;
+ unsigned long npages;
+ struct page *cma_pages; /* from alloc_contig_pages(), or NULL */
+ bool fixed; /* a SETUP file over addr=/len= */
+ bool mmap_capable;
+ bool fixed_wb; /* holds a ref on the fixed-region WB alias */
+
+ unsigned long *absent; /* taken away by CTL_SET_PRESENT */
+ unsigned long *readonly;
+
+ /*
+ * A child of the root: its first page in the root, and the pages it
+ * has. NULL @owned for a file made by SETUP, which has all its pages.
+ */
+ unsigned long root_index;
+ unsigned long *owned;
+ struct list_head root_link; /* mps_root.children, mps_root.lock */
+};
+
+/*
+ * The root region, owned by the control device. Changes of ownership run
+ * under @lock, which is taken before any child's lock.
+ */
+static struct mps_root {
+ struct mutex lock; /* everything below */
+ unsigned long base_pfn;
+ unsigned long npages;
+ struct page *cma_pages;
+ unsigned long *owned; /* pages that a child has */
+ unsigned long *donated; /* pages kept at the root */
+ struct list_head children;
+ /*
+ * The addr=/len= region is used either by one SETUP file or by the
+ * root, never by both, so that its frames have one owner.
+ */
+ bool fixed_setup;
+ /* Files over the fixed region that may be mapped into userspace. */
+ unsigned int fixed_wb_users;
+} mps_root;
+
+/*
+ * A write-back mapping of the addr=/len= region, held while any file over it
+ * may be mapped into userspace (counted by mps_root.fixed_wb_users). Without
+ * it, PAT makes the range uncached on x86, and the VMM's mapping would be
+ * uncached while the guest's is write-back. PAT may still give a weaker type
+ * if the MTRRs do not mark the range write-back.
+ */
+static void *mps_fixed_wb;
+
+/* A file over the fixed region whose pages the host may map. */
+static bool mps_over_fixed_mappable(struct mps_info *info)
+{
+ return addr && len && !info->cma_pages && info->mmap_capable;
+}
+
+/*
+ * Hold a write-back mapping of the fixed region while any file over it may be
+ * mapped into userspace. Called under mps_root.lock.
+ */
+static int mps_fixed_wb_get(void)
+{
+ if (mps_root.fixed_wb_users == 0) {
+ mps_fixed_wb = memremap(addr, len, MEMREMAP_WB);
+ if (!mps_fixed_wb)
+ return -ENOMEM;
+ }
+ mps_root.fixed_wb_users++;
+ return 0;
+}
+
+static void mps_fixed_wb_put(void)
+{
+ if (--mps_root.fixed_wb_users == 0) {
+ memunmap(mps_fixed_wb);
+ mps_fixed_wb = NULL;
+ }
+}
+
+/* Does @info have page @index now? Called with or without info->lock. */
+static bool mps_page_present(struct mps_info *info, unsigned long index)
+{
+ if (info->owned && !test_bit(index, info->owned))
+ return false;
+ return !test_bit(index, info->absent);
+}
+
+/*
+ * The length of the run from @index of pages with the same state as @index:
+ * present, and the same read-only bit.
+ */
+static unsigned long mps_run(struct mps_info *info, unsigned long index)
+{
+ unsigned long end = info->npages;
+
+ end = min(end, find_next_bit(info->absent, info->npages, index + 1));
+ if (info->owned)
+ end = min(end, find_next_zero_bit(info->owned, info->npages,
+ index + 1));
+ if (test_bit(index, info->readonly))
+ end = min(end, find_next_zero_bit(info->readonly, info->npages,
+ index + 1));
+ else
+ end = min(end, find_next_bit(info->readonly, info->npages,
+ index + 1));
+ return end - index;
+}
+
+/* ---- Provider operations ------------------------------------------------ */
+
+static struct mem_provider_file *
+mps_mp_attach(struct file *file, loff_t size)
+{
+ struct mps_info *info = file->private_data;
+
+ if (size > (loff_t)info->npages << PAGE_SHIFT)
+ return ERR_PTR(-EINVAL);
+ return &info->mpf;
+}
+
+static void mps_mp_detach(struct mem_provider_file *mpf)
+{
+}
+
+/*
+ * Reads the bitmaps without info->lock. A change runs under the lock and
+ * revokes the range afterwards, so an answer that a change makes out of date
+ * is dropped by the consumer.
+ */
+static int mps_mp_get_page(struct mem_provider_file *mpf, pgoff_t index,
+ unsigned long *pfn, int *max_order, u32 *attrs)
+{
+ struct mps_info *info = container_of(mpf, struct mps_info, mpf);
+ unsigned long run;
+
+ if (index >= info->npages)
+ return -EINVAL;
+
+ if (!mps_page_present(info, index))
+ return -EFAULT;
+
+ /* The aligned block around @index must be one run. */
+ run = mps_run(info, index);
+ while (*max_order) {
+ unsigned long first = ALIGN_DOWN(index, 1UL << *max_order);
+
+ if (first + (1UL << *max_order) <= index + run &&
+ (first == index || mps_page_present(info, first)) &&
+ mps_run(info, first) >= 1UL << *max_order)
+ break;
+ (*max_order)--;
+ }
+
+ *pfn = info->base_pfn + index;
+ if (test_bit(index, info->readonly))
+ *attrs |= MEM_PROVIDER_ATTR_READONLY;
+ /* The creator of the file decides whether the host may map it. */
+ if (!info->mmap_capable)
+ *attrs |= MEM_PROVIDER_ATTR_NO_USER_MAP;
+ return 0;
+}
+
+static const struct mem_provider_ops mps_mp_ops = {
+ .attach = mps_mp_attach,
+ .detach = mps_mp_detach,
+ .get_page = mps_mp_get_page,
+};
+
+/* Tell the consumers that pages [start, end) of @info changed. */
+static void mps_revoke(struct mps_info *info, unsigned long start,
+ unsigned long end)
+{
+ if (end > start)
+ mem_provider_revoke(&info->mpf, (loff_t)start << PAGE_SHIFT,
+ (loff_t)(end - start) << PAGE_SHIFT);
+}
+
+/* ---- Provider file ------------------------------------------------------ */
+
+/* Byte range [offset, offset + len) of @info as page indices. */
+static int mps_range(struct mps_info *info, u64 offset, u64 len,
+ unsigned long *start, unsigned long *end)
+{
+ if (!len || !PAGE_ALIGNED(offset) || !PAGE_ALIGNED(len))
+ return -EINVAL;
+ *start = offset >> PAGE_SHIFT;
+ *end = *start + (len >> PAGE_SHIFT);
+ if (*end > info->npages || *end < *start)
+ return -EINVAL;
+ return 0;
+}
+
+static void mps_set_readonly(struct mps_info *info, unsigned long start,
+ unsigned long end, bool readonly)
+{
+ mutex_lock(&info->lock);
+ if (readonly)
+ bitmap_set(info->readonly, start, end - start);
+ else
+ bitmap_clear(info->readonly, start, end - start);
+ mps_revoke(info, start, end);
+ mutex_unlock(&info->lock);
+}
+
+static void mps_set_present(struct mps_info *info, unsigned long start,
+ unsigned long end, bool present)
+{
+ mutex_lock(&info->lock);
+ if (present)
+ bitmap_clear(info->absent, start, end - start);
+ else
+ bitmap_set(info->absent, start, end - start);
+ mps_revoke(info, start, end);
+ mutex_unlock(&info->lock);
+}
+
+static long mps_get_stats(struct mps_info *info, void __user *uarg)
+{
+ struct mps_stats st = {};
+
+ mutex_lock(&info->lock);
+ st.region_offset = (u64)info->root_index << PAGE_SHIFT;
+ st.region_len = (u64)info->npages << PAGE_SHIFT;
+ st.owned_pages = info->owned ?
+ bitmap_weight(info->owned, info->npages) : info->npages;
+ st.absent_pages = bitmap_weight(info->absent, info->npages);
+ st.readonly_pages = bitmap_weight(info->readonly, info->npages);
+ mutex_unlock(&info->lock);
+
+ return copy_to_user(uarg, &st, sizeof(st)) ? -EFAULT : 0;
+}
+
+/*
+ * A child fd is read-only to its holder: only GET_STATS. The owner changes a
+ * child's pages through the control device (MPS_CTL_SET_PRESENT and
+ * MPS_CTL_SET_READONLY), so a VMM that holds a child cannot.
+ */
+static long mps_file_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mps_info *info = file->private_data;
+ void __user *uarg = (void __user *)arg;
+
+ if (cmd == MPS_GET_STATS)
+ return mps_get_stats(info, uarg);
+ return -ENOTTY;
+}
+
+/* Every consumer has detached: each one holds a reference to the file. */
+static int mps_file_release(struct inode *inode, struct file *file)
+{
+ struct mps_info *info = file->private_data;
+ unsigned long i;
+
+ /*
+ * A child gives the pages it has back to the root. Pages it donated
+ * stay at the root, and pages moved out belong to another child.
+ */
+ if (info->owned) {
+ mutex_lock(&mps_root.lock);
+ list_del(&info->root_link);
+ for_each_set_bit(i, info->owned, info->npages)
+ __clear_bit(info->root_index + i, mps_root.owned);
+ mutex_unlock(&mps_root.lock);
+ bitmap_free(info->owned);
+ }
+ if (addr && len && !info->cma_pages) {
+ mutex_lock(&mps_root.lock);
+ if (info->fixed)
+ mps_root.fixed_setup = false;
+ if (info->fixed_wb)
+ mps_fixed_wb_put();
+ mutex_unlock(&mps_root.lock);
+ }
+
+ if (info->cma_pages)
+ free_contig_range(info->base_pfn, info->npages);
+ bitmap_free(info->absent);
+ bitmap_free(info->readonly);
+ kfree(info);
+ return 0;
+}
+
+static const struct mem_provider_fops mps_file_fops = {
+ .fops = {
+ .owner = THIS_MODULE,
+ .fop_flags = FOP_MEM_PROVIDER,
+ .release = mps_file_release,
+ .unlocked_ioctl = mps_file_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ },
+ .ops = &mps_mp_ops,
+};
+
+/*
+ * Make a provider file over [base_pfn, base_pfn + npages). The fd is
+ * reserved but not installed, so that the caller can finish setting up
+ * @info before another thread can reach it.
+ */
+static int mps_new_file(unsigned long base_pfn, unsigned long npages,
+ u32 flags, struct mps_info **infop,
+ struct file **filep)
+{
+ struct mps_info *info;
+ struct file *file;
+ int fd, ret;
+
+ info = kzalloc_obj(*info);
+ if (!info)
+ return -ENOMEM;
+ mem_provider_file_init(&info->mpf);
+ info->base_pfn = base_pfn;
+ info->npages = npages;
+ info->mmap_capable = flags & MPS_FLAG_MMAP_CAPABLE;
+ info->absent = bitmap_zalloc(npages, GFP_KERNEL);
+ info->readonly = bitmap_zalloc(npages, GFP_KERNEL);
+ if (!info->absent || !info->readonly) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+ mutex_init(&info->lock);
+ INIT_LIST_HEAD(&info->root_link);
+
+ fd = get_unused_fd_flags(O_CLOEXEC);
+ if (fd < 0) {
+ ret = fd;
+ goto err_free;
+ }
+ file = anon_inode_getfile("[mem-provider-sample]", &mps_file_fops.fops,
+ info, O_RDWR);
+ if (IS_ERR(file)) {
+ put_unused_fd(fd);
+ ret = PTR_ERR(file);
+ goto err_free;
+ }
+ *infop = info;
+ *filep = file;
+ return fd;
+
+err_free:
+ bitmap_free(info->absent);
+ bitmap_free(info->readonly);
+ kfree(info);
+ return ret;
+}
+
+static struct page *mps_alloc_region(unsigned long npages)
+{
+ struct page *pages;
+
+ pages = alloc_contig_pages(npages, GFP_KERNEL | __GFP_ZERO,
+ numa_node_id(), NULL);
+ return pages;
+}
+
+static long mps_ctl_setup(void __user *uarg)
+{
+ struct mps_setup setup;
+ unsigned long base_pfn, npages;
+ struct page *pages = NULL;
+ struct mps_info *info;
+ struct file *file;
+ int fd;
+
+ if (copy_from_user(&setup, uarg, sizeof(setup)))
+ return -EFAULT;
+ if ((setup.flags & ~MPS_FLAG_MMAP_CAPABLE) || setup.pad)
+ return -EINVAL;
+
+ if (addr && len) {
+ /* The fixed region has one owner: this file or the root. */
+ mutex_lock(&mps_root.lock);
+ if (mps_root.fixed_setup || mps_root.npages) {
+ mutex_unlock(&mps_root.lock);
+ return -EBUSY;
+ }
+ fd = mps_new_file(addr >> PAGE_SHIFT, len >> PAGE_SHIFT,
+ setup.flags, &info, &file);
+ if (fd >= 0 && mps_over_fixed_mappable(info)) {
+ if (mps_fixed_wb_get()) {
+ put_unused_fd(fd);
+ fput(file);
+ fd = -ENOMEM;
+ } else {
+ info->fixed_wb = true;
+ }
+ }
+ if (fd >= 0) {
+ info->fixed = true;
+ mps_root.fixed_setup = true;
+ }
+ mutex_unlock(&mps_root.lock);
+ if (fd >= 0)
+ fd_install(fd, file);
+ return fd;
+ }
+
+ if (!setup.size || !PAGE_ALIGNED(setup.size))
+ return -EINVAL;
+ npages = setup.size >> PAGE_SHIFT;
+ pages = mps_alloc_region(npages);
+ if (!pages)
+ return -ENOMEM;
+ base_pfn = page_to_pfn(pages);
+
+ fd = mps_new_file(base_pfn, npages, setup.flags, &info, &file);
+ if (fd < 0) {
+ free_contig_range(base_pfn, npages);
+ return fd;
+ }
+ info->cma_pages = pages;
+ fd_install(fd, file);
+ return fd;
+}
+
+/* ---- Root and children -------------------------------------------------- */
+
+/* Create the root on the first NEW_CHILD, from addr=/len= or of @size. */
+static int mps_root_ensure(u64 size)
+{
+ struct page *pages = NULL;
+ unsigned long npages;
+
+ lockdep_assert_held(&mps_root.lock);
+ if (mps_root.npages)
+ return 0;
+
+ if (addr && len) {
+ if (mps_root.fixed_setup)
+ return -EBUSY;
+ mps_root.base_pfn = addr >> PAGE_SHIFT;
+ npages = len >> PAGE_SHIFT;
+ } else {
+ if (!size || !PAGE_ALIGNED(size))
+ return -EINVAL;
+ npages = size >> PAGE_SHIFT;
+ pages = mps_alloc_region(npages);
+ if (!pages)
+ return -ENOMEM;
+ mps_root.base_pfn = page_to_pfn(pages);
+ }
+ mps_root.owned = bitmap_zalloc(npages, GFP_KERNEL);
+ mps_root.donated = bitmap_zalloc(npages, GFP_KERNEL);
+ if (!mps_root.owned || !mps_root.donated) {
+ bitmap_free(mps_root.owned);
+ bitmap_free(mps_root.donated);
+ mps_root.owned = NULL;
+ mps_root.donated = NULL;
+ if (pages)
+ free_contig_range(page_to_pfn(pages), npages);
+ return -ENOMEM;
+ }
+ mps_root.cma_pages = pages;
+ mps_root.npages = npages;
+ return 0;
+}
+
+static void mps_root_teardown(void)
+{
+ if (!mps_root.npages)
+ return;
+ WARN_ON(!list_empty(&mps_root.children));
+ if (mps_root.cma_pages)
+ free_contig_range(mps_root.base_pfn, mps_root.npages);
+ bitmap_free(mps_root.owned);
+ bitmap_free(mps_root.donated);
+}
+
+/* A page-aligned byte range of the root as page indices [first, last). */
+static int mps_root_range(u64 offset, u64 length, unsigned long *first,
+ unsigned long *last)
+{
+ if (!length || !PAGE_ALIGNED(offset) || !PAGE_ALIGNED(length))
+ return -EINVAL;
+ *first = offset >> PAGE_SHIFT;
+ *last = *first + (length >> PAGE_SHIFT);
+ if (*last <= *first || *last > mps_root.npages)
+ return -EINVAL;
+ return 0;
+}
+
+/* Return a referenced child file made by NEW_CHILD. */
+static struct file *mps_get_child(int fd, struct mps_info **infop)
+{
+ struct file *file = fget(fd);
+
+ if (!file)
+ return ERR_PTR(-EBADF);
+ if (file->f_op != &mps_file_fops.fops ||
+ !((struct mps_info *)file->private_data)->owned) {
+ fput(file);
+ return ERR_PTR(-EINVAL);
+ }
+ *infop = file->private_data;
+ return file;
+}
+
+static long mps_ctl_new_child(void __user *uarg)
+{
+ struct mps_new_child nc;
+ unsigned long first, last, i, grant = 0;
+ struct mps_info *info;
+ unsigned long *owned;
+ struct file *file;
+ int fd, ret;
+
+ if (copy_from_user(&nc, uarg, sizeof(nc)))
+ return -EFAULT;
+ if ((nc.flags & ~MPS_FLAG_MMAP_CAPABLE) || nc.pad)
+ return -EINVAL;
+
+ mutex_lock(&mps_root.lock);
+ ret = mps_root_ensure(nc.offset + nc.len);
+ if (ret)
+ goto out_unlock;
+ ret = mps_root_range(nc.offset, nc.len, &first, &last);
+ if (ret)
+ goto out_unlock;
+
+ /* A child that would get no page is likely a mistake. */
+ for (i = first; i < last; i++)
+ if (!test_bit(i, mps_root.owned) &&
+ !test_bit(i, mps_root.donated))
+ grant++;
+ if (!grant) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ owned = bitmap_zalloc(last - first, GFP_KERNEL);
+ if (!owned) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+ fd = mps_new_file(mps_root.base_pfn + first, last - first, nc.flags,
+ &info, &file);
+ if (fd < 0) {
+ bitmap_free(owned);
+ ret = fd;
+ goto out_unlock;
+ }
+ if (mps_over_fixed_mappable(info)) {
+ if (mps_fixed_wb_get()) {
+ put_unused_fd(fd);
+ fput(file);
+ bitmap_free(owned);
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+ info->fixed_wb = true;
+ }
+
+ /* No other thread can reach the child until fd_install(). */
+ for (i = first; i < last; i++) {
+ if (test_bit(i, mps_root.owned) ||
+ test_bit(i, mps_root.donated))
+ continue;
+ __set_bit(i - first, owned);
+ __set_bit(i, mps_root.owned);
+ }
+ info->owned = owned;
+ info->root_index = first;
+ list_add(&info->root_link, &mps_root.children);
+ mutex_unlock(&mps_root.lock);
+ fd_install(fd, file);
+ return fd;
+
+out_unlock:
+ mutex_unlock(&mps_root.lock);
+ return ret;
+}
+
+/* Take [first, last) of the root from @info. Called under mps_root.lock. */
+static void mps_child_lose(struct mps_info *info, unsigned long first,
+ unsigned long last)
+{
+ unsigned long s = first - info->root_index;
+ unsigned long e = last - info->root_index;
+
+ mutex_lock(&info->lock);
+ bitmap_clear(info->owned, s, e - s);
+ mps_revoke(info, s, e);
+ mutex_unlock(&info->lock);
+ bitmap_clear(mps_root.owned, first, last - first);
+}
+
+/* Give [first, last) of the root to @info. Called under mps_root.lock. */
+static void mps_child_gain(struct mps_info *info, unsigned long first,
+ unsigned long last)
+{
+ unsigned long s = first - info->root_index;
+ unsigned long e = last - info->root_index;
+
+ mutex_lock(&info->lock);
+ bitmap_set(info->owned, s, e - s);
+ mps_revoke(info, s, e);
+ mutex_unlock(&info->lock);
+ bitmap_set(mps_root.owned, first, last - first);
+}
+
+static bool mps_child_covers(struct mps_info *info, unsigned long first,
+ unsigned long last)
+{
+ return first >= info->root_index &&
+ last <= info->root_index + info->npages;
+}
+
+static bool mps_child_owns(struct mps_info *info, unsigned long first,
+ unsigned long last)
+{
+ unsigned long s = first - info->root_index;
+ unsigned long e = last - info->root_index;
+
+ return mps_child_covers(info, first, last) &&
+ find_next_zero_bit(info->owned, e, s) >= e;
+}
+
+static long mps_ctl_move(void __user *uarg)
+{
+ struct mps_move mv;
+ struct mps_info *src, *dst;
+ unsigned long first, last;
+ struct file *sf, *df;
+ long ret;
+
+ if (copy_from_user(&mv, uarg, sizeof(mv)))
+ return -EFAULT;
+ sf = mps_get_child(mv.src_fd, &src);
+ if (IS_ERR(sf))
+ return PTR_ERR(sf);
+ df = mps_get_child(mv.dst_fd, &dst);
+ if (IS_ERR(df)) {
+ fput(sf);
+ return PTR_ERR(df);
+ }
+
+ mutex_lock(&mps_root.lock);
+ ret = mps_root_range(mv.offset, mv.len, &first, &last);
+ if (ret)
+ goto out;
+ if (src == dst || !mps_child_owns(src, first, last) ||
+ !mps_child_covers(dst, first, last)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ /* Revoke from the source before the destination gets the pages. */
+ mps_child_lose(src, first, last);
+ mps_child_gain(dst, first, last);
+out:
+ mutex_unlock(&mps_root.lock);
+ fput(df);
+ fput(sf);
+ return ret;
+}
+
+static long mps_ctl_donate(void __user *uarg, bool reclaim)
+{
+ struct mps_donate d;
+ unsigned long first, last;
+ struct mps_info *info;
+ struct file *file;
+ long ret;
+
+ if (copy_from_user(&d, uarg, sizeof(d)))
+ return -EFAULT;
+ if (d.pad)
+ return -EINVAL;
+ file = mps_get_child(d.fd, &info);
+ if (IS_ERR(file))
+ return PTR_ERR(file);
+
+ mutex_lock(&mps_root.lock);
+ ret = mps_root_range(d.offset, d.len, &first, &last);
+ if (ret)
+ goto out;
+ if (!reclaim) {
+ if (!mps_child_owns(info, first, last)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ mps_child_lose(info, first, last);
+ bitmap_set(mps_root.donated, first, last - first);
+ } else {
+ if (!mps_child_covers(info, first, last) ||
+ find_next_zero_bit(mps_root.donated, last, first) < last) {
+ ret = -EINVAL;
+ goto out;
+ }
+ bitmap_clear(mps_root.donated, first, last - first);
+ mps_child_gain(info, first, last);
+ }
+out:
+ mutex_unlock(&mps_root.lock);
+ fput(file);
+ return ret;
+}
+
+/*
+ * CTL_SET_READONLY and CTL_SET_PRESENT change a child's pages, by the owner on
+ * the control device. A VMM that holds the child fd cannot, so it cannot undo
+ * them.
+ */
+static long mps_ctl_set_range(void __user *uarg, bool readonly)
+{
+ struct mps_ctl_range cr;
+ unsigned long start, end;
+ struct mps_info *info;
+ struct file *file;
+ int ret;
+
+ if (copy_from_user(&cr, uarg, sizeof(cr)))
+ return -EFAULT;
+ if (cr.value > 1)
+ return -EINVAL;
+ file = mps_get_child(cr.fd, &info);
+ if (IS_ERR(file))
+ return PTR_ERR(file);
+ ret = mps_range(info, cr.offset, cr.len, &start, &end);
+ if (!ret) {
+ if (readonly)
+ mps_set_readonly(info, start, end, cr.value);
+ else
+ mps_set_present(info, start, end, cr.value);
+ }
+ fput(file);
+ return ret;
+}
+
+static long mps_ctl_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *uarg = (void __user *)arg;
+
+ switch (cmd) {
+ case MPS_SETUP:
+ return mps_ctl_setup(uarg);
+ case MPS_NEW_CHILD:
+ return mps_ctl_new_child(uarg);
+ case MPS_MOVE:
+ return mps_ctl_move(uarg);
+ case MPS_DONATE:
+ return mps_ctl_donate(uarg, false);
+ case MPS_RECLAIM:
+ return mps_ctl_donate(uarg, true);
+ case MPS_CTL_SET_READONLY:
+ return mps_ctl_set_range(uarg, true);
+ case MPS_CTL_SET_PRESENT:
+ return mps_ctl_set_range(uarg, false);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations mps_ctl_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = mps_ctl_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+};
+
+static struct miscdevice mps_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "mem_provider_sample",
+ .fops = &mps_ctl_fops,
+};
+
+static int __init mps_init(void)
+{
+ int ret;
+
+ if ((addr || len) &&
+ (!addr || !len || !PAGE_ALIGNED(addr) || !PAGE_ALIGNED(len))) {
+ pr_err("mem_provider_sample: addr= and len= must both be set and page aligned\n");
+ return -EINVAL;
+ }
+
+ mutex_init(&mps_root.lock);
+ INIT_LIST_HEAD(&mps_root.children);
+
+ ret = misc_register(&mps_dev);
+ if (ret)
+ return ret;
+ return 0;
+}
+module_init(mps_init);
+
+static void __exit mps_exit(void)
+{
+ misc_deregister(&mps_dev);
+ mps_root_teardown();
+ if (mps_fixed_wb)
+ memunmap(mps_fixed_wb);
+}
+module_exit(mps_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Sample memory provider for guest_memfd and iommufd");
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
new file mode 100644
index 0000000000000..0e036d1c76a45
--- /dev/null
+++ b/samples/kvm/mem_provider_sample.h
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SAMPLES_KVM_MEM_PROVIDER_SAMPLE_H
+#define _SAMPLES_KVM_MEM_PROVIDER_SAMPLE_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/*
+ * A sample memory provider (include/linux/mem_provider.h).
+ *
+ * Every fd that this module returns is a provider file. Pass it as
+ * resource_fd to KVM_CREATE_GUEST_MEMFD with GUEST_MEMFD_FLAG_USE_RESOURCE,
+ * and as fd to IOMMU_IOAS_MAP_FILE. Both consumers follow every change that
+ * the ioctls below make.
+ */
+
+/* Flags for struct mps_setup and struct mps_new_child */
+/* The host may map the pages; otherwise they are NO_USER_MAP */
+#define MPS_FLAG_MMAP_CAPABLE (1u << 0)
+
+/*
+ * ioctl on /dev/mem_provider_sample: create a provider fd.
+ *
+ * If the module was loaded with addr= and len=, the fd covers that fixed
+ * range, which has no struct page, and @size is ignored. The fixed range
+ * then has one owner. SETUP fails with -EBUSY while a SETUP file uses it,
+ * or once the first MPS_NEW_CHILD has made it the root of the children, which
+ * lasts until the module is unloaded. MPS_NEW_CHILD fails with -EBUSY while
+ * a SETUP file uses it. Otherwise the fd covers @size bytes from
+ * alloc_contig_pages().
+ */
+struct mps_setup {
+ __u32 flags; /* MPS_FLAG_* */
+ __u32 pad;
+ __u64 size; /* bytes, page aligned */
+};
+
+#define MPS_IOCTL_BASE 'P'
+#define MPS_SETUP \
+ _IOW(MPS_IOCTL_BASE, 1, struct mps_setup)
+
+/*
+ * ---- A tree of owners --------------------------------------------------
+ *
+ * The control device owns one region, the root. NEW_CHILD carves part of
+ * it into a new provider fd for one VM. The owner can MOVE pages between
+ * children, DONATE pages to the root, so that no child has them, and
+ * RECLAIM them. Each change revokes the range, so KVM and iommufd follow.
+ *
+ * The owner changes a child's pages through the control device
+ * (CTL_SET_READONLY, CTL_SET_PRESENT). A child fd is read-only to its
+ * holder: a VMM can only GET_STATS, never change the memory.
+ */
+
+/*
+ * ioctl on /dev/mem_provider_sample: carve [@offset, @offset + @len) of the
+ * root into a new child fd. The child gets every page of the range that no
+ * other child has and that is not donated. Ranges of children may overlap,
+ * which is how a page can MOVE between them. Returns the child fd.
+ */
+struct mps_new_child {
+ __u32 flags; /* MPS_FLAG_* */
+ __u32 pad;
+ __u64 offset; /* bytes into the root, page aligned */
+ __u64 len; /* bytes, page aligned */
+};
+
+#define MPS_NEW_CHILD \
+ _IOW(MPS_IOCTL_BASE, 5, struct mps_new_child)
+
+/*
+ * ioctl on /dev/mem_provider_sample: move [@offset, @offset + @len) of the root
+ * from child @src_fd to child @dst_fd. @src_fd must have every page of the
+ * range, and the range must be inside @dst_fd's. The pages are revoked from
+ * the source before the destination gets them, so the consumers of the two
+ * children never map them at the same time.
+ */
+struct mps_move {
+ __s32 src_fd;
+ __s32 dst_fd;
+ __u64 offset; /* bytes into the root, page aligned */
+ __u64 len; /* bytes, page aligned */
+};
+
+#define MPS_MOVE \
+ _IOW(MPS_IOCTL_BASE, 6, struct mps_move)
+
+/*
+ * ioctls on /dev/mem_provider_sample: DONATE takes [@offset, @offset + @len) of
+ * the root from child @fd, which must have all of it, and keeps it at the
+ * root. RECLAIM gives a donated range to child @fd, whose range must
+ * contain it. The sample does not scrub the pages.
+ */
+struct mps_donate {
+ __s32 fd;
+ __u32 pad;
+ __u64 offset; /* bytes into the root, page aligned */
+ __u64 len; /* bytes, page aligned */
+};
+
+#define MPS_DONATE \
+ _IOW(MPS_IOCTL_BASE, 7, struct mps_donate)
+#define MPS_RECLAIM \
+ _IOW(MPS_IOCTL_BASE, 8, struct mps_donate)
+
+/* ioctl on a child fd: read the child's state, for tests. */
+struct mps_stats {
+ __u64 region_offset; /* the child's range in the root */
+ __u64 region_len;
+ __u64 owned_pages; /* pages the child has */
+ __u64 absent_pages; /* pages taken away by CTL_SET_PRESENT */
+ __u64 readonly_pages;
+};
+
+#define MPS_GET_STATS \
+ _IOR(MPS_IOCTL_BASE, 10, struct mps_stats)
+
+/*
+ * ioctls on /dev/mem_provider_sample: CTL_SET_READONLY or CTL_SET_PRESENT on
+ * child @fd, by the owner. @value is the readonly or present value, and
+ * @offset and @len are a byte range within the child, page aligned.
+ */
+struct mps_ctl_range {
+ __s32 fd;
+ __u32 value;
+ __u64 offset; /* bytes into the child, page aligned */
+ __u64 len; /* bytes, page aligned */
+};
+
+#define MPS_CTL_SET_READONLY \
+ _IOW(MPS_IOCTL_BASE, 11, struct mps_ctl_range)
+#define MPS_CTL_SET_PRESENT \
+ _IOW(MPS_IOCTL_BASE, 12, struct mps_ctl_range)
+
+#endif /* _SAMPLES_KVM_MEM_PROVIDER_SAMPLE_H */
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 21/50] samples/kvm: Remove donate, reclaim, and move operations from sample provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (19 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 20/50] samples/kvm: Add a memory provider sample Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 22/50] samples/kvm: Remove readonly support " Ackerley Tng via B4 Relay
` (29 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Remove the MPS_MOVE, MPS_DONATE, and MPS_RECLAIM ioctls from the sample
memory provider.
These operations manage dynamic ownership transfers and returning memory
to the root pool, which adds complexity that is not needed for the core
interface design.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 86 ---------------------------------------
samples/kvm/mem_provider_sample.h | 35 ----------------
2 files changed, 121 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 8180959ab78ff..5afc433dce6e8 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -674,86 +674,6 @@ static bool mps_child_owns(struct mps_info *info, unsigned long first,
find_next_zero_bit(info->owned, e, s) >= e;
}
-static long mps_ctl_move(void __user *uarg)
-{
- struct mps_move mv;
- struct mps_info *src, *dst;
- unsigned long first, last;
- struct file *sf, *df;
- long ret;
-
- if (copy_from_user(&mv, uarg, sizeof(mv)))
- return -EFAULT;
- sf = mps_get_child(mv.src_fd, &src);
- if (IS_ERR(sf))
- return PTR_ERR(sf);
- df = mps_get_child(mv.dst_fd, &dst);
- if (IS_ERR(df)) {
- fput(sf);
- return PTR_ERR(df);
- }
-
- mutex_lock(&mps_root.lock);
- ret = mps_root_range(mv.offset, mv.len, &first, &last);
- if (ret)
- goto out;
- if (src == dst || !mps_child_owns(src, first, last) ||
- !mps_child_covers(dst, first, last)) {
- ret = -EINVAL;
- goto out;
- }
- /* Revoke from the source before the destination gets the pages. */
- mps_child_lose(src, first, last);
- mps_child_gain(dst, first, last);
-out:
- mutex_unlock(&mps_root.lock);
- fput(df);
- fput(sf);
- return ret;
-}
-
-static long mps_ctl_donate(void __user *uarg, bool reclaim)
-{
- struct mps_donate d;
- unsigned long first, last;
- struct mps_info *info;
- struct file *file;
- long ret;
-
- if (copy_from_user(&d, uarg, sizeof(d)))
- return -EFAULT;
- if (d.pad)
- return -EINVAL;
- file = mps_get_child(d.fd, &info);
- if (IS_ERR(file))
- return PTR_ERR(file);
-
- mutex_lock(&mps_root.lock);
- ret = mps_root_range(d.offset, d.len, &first, &last);
- if (ret)
- goto out;
- if (!reclaim) {
- if (!mps_child_owns(info, first, last)) {
- ret = -EINVAL;
- goto out;
- }
- mps_child_lose(info, first, last);
- bitmap_set(mps_root.donated, first, last - first);
- } else {
- if (!mps_child_covers(info, first, last) ||
- find_next_zero_bit(mps_root.donated, last, first) < last) {
- ret = -EINVAL;
- goto out;
- }
- bitmap_clear(mps_root.donated, first, last - first);
- mps_child_gain(info, first, last);
- }
-out:
- mutex_unlock(&mps_root.lock);
- fput(file);
- return ret;
-}
-
/*
* CTL_SET_READONLY and CTL_SET_PRESENT change a child's pages, by the owner on
* the control device. A VMM that holds the child fd cannot, so it cannot undo
@@ -795,12 +715,6 @@ static long mps_ctl_ioctl(struct file *file, unsigned int cmd,
return mps_ctl_setup(uarg);
case MPS_NEW_CHILD:
return mps_ctl_new_child(uarg);
- case MPS_MOVE:
- return mps_ctl_move(uarg);
- case MPS_DONATE:
- return mps_ctl_donate(uarg, false);
- case MPS_RECLAIM:
- return mps_ctl_donate(uarg, true);
case MPS_CTL_SET_READONLY:
return mps_ctl_set_range(uarg, true);
case MPS_CTL_SET_PRESENT:
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index 0e036d1c76a45..acff5785099b5 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -68,41 +68,6 @@ struct mps_new_child {
#define MPS_NEW_CHILD \
_IOW(MPS_IOCTL_BASE, 5, struct mps_new_child)
-/*
- * ioctl on /dev/mem_provider_sample: move [@offset, @offset + @len) of the root
- * from child @src_fd to child @dst_fd. @src_fd must have every page of the
- * range, and the range must be inside @dst_fd's. The pages are revoked from
- * the source before the destination gets them, so the consumers of the two
- * children never map them at the same time.
- */
-struct mps_move {
- __s32 src_fd;
- __s32 dst_fd;
- __u64 offset; /* bytes into the root, page aligned */
- __u64 len; /* bytes, page aligned */
-};
-
-#define MPS_MOVE \
- _IOW(MPS_IOCTL_BASE, 6, struct mps_move)
-
-/*
- * ioctls on /dev/mem_provider_sample: DONATE takes [@offset, @offset + @len) of
- * the root from child @fd, which must have all of it, and keeps it at the
- * root. RECLAIM gives a donated range to child @fd, whose range must
- * contain it. The sample does not scrub the pages.
- */
-struct mps_donate {
- __s32 fd;
- __u32 pad;
- __u64 offset; /* bytes into the root, page aligned */
- __u64 len; /* bytes, page aligned */
-};
-
-#define MPS_DONATE \
- _IOW(MPS_IOCTL_BASE, 7, struct mps_donate)
-#define MPS_RECLAIM \
- _IOW(MPS_IOCTL_BASE, 8, struct mps_donate)
-
/* ioctl on a child fd: read the child's state, for tests. */
struct mps_stats {
__u64 region_offset; /* the child's range in the root */
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 22/50] samples/kvm: Remove readonly support from sample provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (20 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 21/50] samples/kvm: Remove donate, reclaim, and move operations from sample provider Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 23/50] samples/kvm: Remove child ownership tracking " Ackerley Tng via B4 Relay
` (28 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Remove the MPS_CTL_SET_READONLY ioctl, MEM_PROVIDER_ATTR_READONLY
attribute, and readonly tracking bitmap from the sample memory provider.
Keeping only the present versus absent control interface focuses
attention on the essential interface design for page backing and
revocation.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 52 ++++++++-------------------------------
samples/kvm/mem_provider_sample.h | 13 ++++------
2 files changed, 15 insertions(+), 50 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 5afc433dce6e8..37538dce40cf8 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -66,7 +66,6 @@ struct mps_info {
bool fixed_wb; /* holds a ref on the fixed-region WB alias */
unsigned long *absent; /* taken away by CTL_SET_PRESENT */
- unsigned long *readonly;
/*
* A child of the root: its first page in the root, and the pages it
@@ -146,7 +145,7 @@ static bool mps_page_present(struct mps_info *info, unsigned long index)
/*
* The length of the run from @index of pages with the same state as @index:
- * present, and the same read-only bit.
+ * present.
*/
static unsigned long mps_run(struct mps_info *info, unsigned long index)
{
@@ -156,12 +155,6 @@ static unsigned long mps_run(struct mps_info *info, unsigned long index)
if (info->owned)
end = min(end, find_next_zero_bit(info->owned, info->npages,
index + 1));
- if (test_bit(index, info->readonly))
- end = min(end, find_next_zero_bit(info->readonly, info->npages,
- index + 1));
- else
- end = min(end, find_next_bit(info->readonly, info->npages,
- index + 1));
return end - index;
}
@@ -211,8 +204,6 @@ static int mps_mp_get_page(struct mem_provider_file *mpf, pgoff_t index,
}
*pfn = info->base_pfn + index;
- if (test_bit(index, info->readonly))
- *attrs |= MEM_PROVIDER_ATTR_READONLY;
/* The creator of the file decides whether the host may map it. */
if (!info->mmap_capable)
*attrs |= MEM_PROVIDER_ATTR_NO_USER_MAP;
@@ -249,18 +240,6 @@ static int mps_range(struct mps_info *info, u64 offset, u64 len,
return 0;
}
-static void mps_set_readonly(struct mps_info *info, unsigned long start,
- unsigned long end, bool readonly)
-{
- mutex_lock(&info->lock);
- if (readonly)
- bitmap_set(info->readonly, start, end - start);
- else
- bitmap_clear(info->readonly, start, end - start);
- mps_revoke(info, start, end);
- mutex_unlock(&info->lock);
-}
-
static void mps_set_present(struct mps_info *info, unsigned long start,
unsigned long end, bool present)
{
@@ -283,7 +262,6 @@ static long mps_get_stats(struct mps_info *info, void __user *uarg)
st.owned_pages = info->owned ?
bitmap_weight(info->owned, info->npages) : info->npages;
st.absent_pages = bitmap_weight(info->absent, info->npages);
- st.readonly_pages = bitmap_weight(info->readonly, info->npages);
mutex_unlock(&info->lock);
return copy_to_user(uarg, &st, sizeof(st)) ? -EFAULT : 0;
@@ -291,8 +269,8 @@ static long mps_get_stats(struct mps_info *info, void __user *uarg)
/*
* A child fd is read-only to its holder: only GET_STATS. The owner changes a
- * child's pages through the control device (MPS_CTL_SET_PRESENT and
- * MPS_CTL_SET_READONLY), so a VMM that holds a child cannot.
+ * child's pages through the control device (MPS_CTL_SET_PRESENT), so a VMM
+ * that holds a child cannot.
*/
static long mps_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
@@ -335,7 +313,6 @@ static int mps_file_release(struct inode *inode, struct file *file)
if (info->cma_pages)
free_contig_range(info->base_pfn, info->npages);
bitmap_free(info->absent);
- bitmap_free(info->readonly);
kfree(info);
return 0;
}
@@ -372,8 +349,7 @@ static int mps_new_file(unsigned long base_pfn, unsigned long npages,
info->npages = npages;
info->mmap_capable = flags & MPS_FLAG_MMAP_CAPABLE;
info->absent = bitmap_zalloc(npages, GFP_KERNEL);
- info->readonly = bitmap_zalloc(npages, GFP_KERNEL);
- if (!info->absent || !info->readonly) {
+ if (!info->absent) {
ret = -ENOMEM;
goto err_free;
}
@@ -398,7 +374,6 @@ static int mps_new_file(unsigned long base_pfn, unsigned long npages,
err_free:
bitmap_free(info->absent);
- bitmap_free(info->readonly);
kfree(info);
return ret;
}
@@ -675,11 +650,10 @@ static bool mps_child_owns(struct mps_info *info, unsigned long first,
}
/*
- * CTL_SET_READONLY and CTL_SET_PRESENT change a child's pages, by the owner on
- * the control device. A VMM that holds the child fd cannot, so it cannot undo
- * them.
+ * CTL_SET_PRESENT changes a child's pages, by the owner on the control device.
+ * A VMM that holds the child fd cannot, so it cannot undo them.
*/
-static long mps_ctl_set_range(void __user *uarg, bool readonly)
+static long mps_ctl_set_present(void __user *uarg)
{
struct mps_ctl_range cr;
unsigned long start, end;
@@ -695,12 +669,8 @@ static long mps_ctl_set_range(void __user *uarg, bool readonly)
if (IS_ERR(file))
return PTR_ERR(file);
ret = mps_range(info, cr.offset, cr.len, &start, &end);
- if (!ret) {
- if (readonly)
- mps_set_readonly(info, start, end, cr.value);
- else
- mps_set_present(info, start, end, cr.value);
- }
+ if (!ret)
+ mps_set_present(info, start, end, cr.value);
fput(file);
return ret;
}
@@ -715,10 +685,8 @@ static long mps_ctl_ioctl(struct file *file, unsigned int cmd,
return mps_ctl_setup(uarg);
case MPS_NEW_CHILD:
return mps_ctl_new_child(uarg);
- case MPS_CTL_SET_READONLY:
- return mps_ctl_set_range(uarg, true);
case MPS_CTL_SET_PRESENT:
- return mps_ctl_set_range(uarg, false);
+ return mps_ctl_set_present(uarg);
default:
return -ENOTTY;
}
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index acff5785099b5..5955c1c0fbf80 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -48,8 +48,8 @@ struct mps_setup {
* RECLAIM them. Each change revokes the range, so KVM and iommufd follow.
*
* The owner changes a child's pages through the control device
- * (CTL_SET_READONLY, CTL_SET_PRESENT). A child fd is read-only to its
- * holder: a VMM can only GET_STATS, never change the memory.
+ * (CTL_SET_PRESENT). A child fd is read-only to its holder: a VMM can
+ * only GET_STATS, never change the memory.
*/
/*
@@ -74,16 +74,15 @@ struct mps_stats {
__u64 region_len;
__u64 owned_pages; /* pages the child has */
__u64 absent_pages; /* pages taken away by CTL_SET_PRESENT */
- __u64 readonly_pages;
};
#define MPS_GET_STATS \
_IOR(MPS_IOCTL_BASE, 10, struct mps_stats)
/*
- * ioctls on /dev/mem_provider_sample: CTL_SET_READONLY or CTL_SET_PRESENT on
- * child @fd, by the owner. @value is the readonly or present value, and
- * @offset and @len are a byte range within the child, page aligned.
+ * ioctl on /dev/mem_provider_sample: CTL_SET_PRESENT on child @fd, by the
+ * owner. @value is the present value (1 = present, 0 = absent), and @offset
+ * and @len are a byte range within the child, page aligned.
*/
struct mps_ctl_range {
__s32 fd;
@@ -92,8 +91,6 @@ struct mps_ctl_range {
__u64 len; /* bytes, page aligned */
};
-#define MPS_CTL_SET_READONLY \
- _IOW(MPS_IOCTL_BASE, 11, struct mps_ctl_range)
#define MPS_CTL_SET_PRESENT \
_IOW(MPS_IOCTL_BASE, 12, struct mps_ctl_range)
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 23/50] samples/kvm: Remove child ownership tracking from sample provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (21 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 22/50] samples/kvm: Remove readonly support " Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 24/50] samples/kvm: Remove MPS_SETUP support " Ackerley Tng via B4 Relay
` (27 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Remove per-child owned bitmaps and overlapping child range tracking from
the sample memory provider.
Each child directly carves out a non-overlapping slice from the root pool
tracking allocated pages in mps_root.allocated.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 115 +++++---------------------------------
samples/kvm/mem_provider_sample.h | 1 -
2 files changed, 15 insertions(+), 101 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 37538dce40cf8..4e8f306ba1154 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -68,11 +68,11 @@ struct mps_info {
unsigned long *absent; /* taken away by CTL_SET_PRESENT */
/*
- * A child of the root: its first page in the root, and the pages it
- * has. NULL @owned for a file made by SETUP, which has all its pages.
+ * A child of the root: its first page in the root, and whether it
+ * was carved out from the root (is_child).
*/
unsigned long root_index;
- unsigned long *owned;
+ bool is_child;
struct list_head root_link; /* mps_root.children, mps_root.lock */
};
@@ -85,8 +85,7 @@ static struct mps_root {
unsigned long base_pfn;
unsigned long npages;
struct page *cma_pages;
- unsigned long *owned; /* pages that a child has */
- unsigned long *donated; /* pages kept at the root */
+ unsigned long *allocated; /* pages allocated to a child */
struct list_head children;
/*
* The addr=/len= region is used either by one SETUP file or by the
@@ -138,8 +137,6 @@ static void mps_fixed_wb_put(void)
/* Does @info have page @index now? Called with or without info->lock. */
static bool mps_page_present(struct mps_info *info, unsigned long index)
{
- if (info->owned && !test_bit(index, info->owned))
- return false;
return !test_bit(index, info->absent);
}
@@ -152,9 +149,6 @@ static unsigned long mps_run(struct mps_info *info, unsigned long index)
unsigned long end = info->npages;
end = min(end, find_next_bit(info->absent, info->npages, index + 1));
- if (info->owned)
- end = min(end, find_next_zero_bit(info->owned, info->npages,
- index + 1));
return end - index;
}
@@ -259,8 +253,6 @@ static long mps_get_stats(struct mps_info *info, void __user *uarg)
mutex_lock(&info->lock);
st.region_offset = (u64)info->root_index << PAGE_SHIFT;
st.region_len = (u64)info->npages << PAGE_SHIFT;
- st.owned_pages = info->owned ?
- bitmap_weight(info->owned, info->npages) : info->npages;
st.absent_pages = bitmap_weight(info->absent, info->npages);
mutex_unlock(&info->lock);
@@ -287,19 +279,13 @@ static long mps_file_ioctl(struct file *file, unsigned int cmd,
static int mps_file_release(struct inode *inode, struct file *file)
{
struct mps_info *info = file->private_data;
- unsigned long i;
- /*
- * A child gives the pages it has back to the root. Pages it donated
- * stay at the root, and pages moved out belong to another child.
- */
- if (info->owned) {
+ /* A child gives the pages it has back to the root. */
+ if (info->is_child) {
mutex_lock(&mps_root.lock);
list_del(&info->root_link);
- for_each_set_bit(i, info->owned, info->npages)
- __clear_bit(info->root_index + i, mps_root.owned);
+ bitmap_clear(mps_root.allocated, info->root_index, info->npages);
mutex_unlock(&mps_root.lock);
- bitmap_free(info->owned);
}
if (addr && len && !info->cma_pages) {
mutex_lock(&mps_root.lock);
@@ -473,13 +459,8 @@ static int mps_root_ensure(u64 size)
return -ENOMEM;
mps_root.base_pfn = page_to_pfn(pages);
}
- mps_root.owned = bitmap_zalloc(npages, GFP_KERNEL);
- mps_root.donated = bitmap_zalloc(npages, GFP_KERNEL);
- if (!mps_root.owned || !mps_root.donated) {
- bitmap_free(mps_root.owned);
- bitmap_free(mps_root.donated);
- mps_root.owned = NULL;
- mps_root.donated = NULL;
+ mps_root.allocated = bitmap_zalloc(npages, GFP_KERNEL);
+ if (!mps_root.allocated) {
if (pages)
free_contig_range(page_to_pfn(pages), npages);
return -ENOMEM;
@@ -496,8 +477,7 @@ static void mps_root_teardown(void)
WARN_ON(!list_empty(&mps_root.children));
if (mps_root.cma_pages)
free_contig_range(mps_root.base_pfn, mps_root.npages);
- bitmap_free(mps_root.owned);
- bitmap_free(mps_root.donated);
+ bitmap_free(mps_root.allocated);
}
/* A page-aligned byte range of the root as page indices [first, last). */
@@ -521,7 +501,7 @@ static struct file *mps_get_child(int fd, struct mps_info **infop)
if (!file)
return ERR_PTR(-EBADF);
if (file->f_op != &mps_file_fops.fops ||
- !((struct mps_info *)file->private_data)->owned) {
+ !((struct mps_info *)file->private_data)->is_child) {
fput(file);
return ERR_PTR(-EINVAL);
}
@@ -532,9 +512,8 @@ static struct file *mps_get_child(int fd, struct mps_info **infop)
static long mps_ctl_new_child(void __user *uarg)
{
struct mps_new_child nc;
- unsigned long first, last, i, grant = 0;
+ unsigned long first, last;
struct mps_info *info;
- unsigned long *owned;
struct file *file;
int fd, ret;
@@ -551,25 +530,14 @@ static long mps_ctl_new_child(void __user *uarg)
if (ret)
goto out_unlock;
- /* A child that would get no page is likely a mistake. */
- for (i = first; i < last; i++)
- if (!test_bit(i, mps_root.owned) &&
- !test_bit(i, mps_root.donated))
- grant++;
- if (!grant) {
+ if (find_next_bit(mps_root.allocated, last, first) < last) {
ret = -EBUSY;
goto out_unlock;
}
- owned = bitmap_zalloc(last - first, GFP_KERNEL);
- if (!owned) {
- ret = -ENOMEM;
- goto out_unlock;
- }
fd = mps_new_file(mps_root.base_pfn + first, last - first, nc.flags,
&info, &file);
if (fd < 0) {
- bitmap_free(owned);
ret = fd;
goto out_unlock;
}
@@ -577,22 +545,14 @@ static long mps_ctl_new_child(void __user *uarg)
if (mps_fixed_wb_get()) {
put_unused_fd(fd);
fput(file);
- bitmap_free(owned);
ret = -ENOMEM;
goto out_unlock;
}
info->fixed_wb = true;
}
- /* No other thread can reach the child until fd_install(). */
- for (i = first; i < last; i++) {
- if (test_bit(i, mps_root.owned) ||
- test_bit(i, mps_root.donated))
- continue;
- __set_bit(i - first, owned);
- __set_bit(i, mps_root.owned);
- }
- info->owned = owned;
+ bitmap_set(mps_root.allocated, first, last - first);
+ info->is_child = true;
info->root_index = first;
list_add(&info->root_link, &mps_root.children);
mutex_unlock(&mps_root.lock);
@@ -604,51 +564,6 @@ static long mps_ctl_new_child(void __user *uarg)
return ret;
}
-/* Take [first, last) of the root from @info. Called under mps_root.lock. */
-static void mps_child_lose(struct mps_info *info, unsigned long first,
- unsigned long last)
-{
- unsigned long s = first - info->root_index;
- unsigned long e = last - info->root_index;
-
- mutex_lock(&info->lock);
- bitmap_clear(info->owned, s, e - s);
- mps_revoke(info, s, e);
- mutex_unlock(&info->lock);
- bitmap_clear(mps_root.owned, first, last - first);
-}
-
-/* Give [first, last) of the root to @info. Called under mps_root.lock. */
-static void mps_child_gain(struct mps_info *info, unsigned long first,
- unsigned long last)
-{
- unsigned long s = first - info->root_index;
- unsigned long e = last - info->root_index;
-
- mutex_lock(&info->lock);
- bitmap_set(info->owned, s, e - s);
- mps_revoke(info, s, e);
- mutex_unlock(&info->lock);
- bitmap_set(mps_root.owned, first, last - first);
-}
-
-static bool mps_child_covers(struct mps_info *info, unsigned long first,
- unsigned long last)
-{
- return first >= info->root_index &&
- last <= info->root_index + info->npages;
-}
-
-static bool mps_child_owns(struct mps_info *info, unsigned long first,
- unsigned long last)
-{
- unsigned long s = first - info->root_index;
- unsigned long e = last - info->root_index;
-
- return mps_child_covers(info, first, last) &&
- find_next_zero_bit(info->owned, e, s) >= e;
-}
-
/*
* CTL_SET_PRESENT changes a child's pages, by the owner on the control device.
* A VMM that holds the child fd cannot, so it cannot undo them.
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index 5955c1c0fbf80..1afea136328f5 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -72,7 +72,6 @@ struct mps_new_child {
struct mps_stats {
__u64 region_offset; /* the child's range in the root */
__u64 region_len;
- __u64 owned_pages; /* pages the child has */
__u64 absent_pages; /* pages taken away by CTL_SET_PRESENT */
};
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 24/50] samples/kvm: Remove MPS_SETUP support from sample provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (22 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 23/50] samples/kvm: Remove child ownership tracking " Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 25/50] samples/kvm: Remove MMAP_CAPABLE flag " Ackerley Tng via B4 Relay
` (26 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
MPS_SETUP is a subset of the functionality provided by MPS_NEW_CHILD.
A single file covering the entire region can be created by carving out
the full range with MPS_NEW_CHILD.
Retain MPS_NEW_CHILD as it enables testing alternative provider
functionality when there are separate sub-pools of memory carved out
from the root.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 72 ---------------------------------------
samples/kvm/mem_provider_sample.h | 29 ++--------------
2 files changed, 3 insertions(+), 98 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 4e8f306ba1154..abb8e35cd5357 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -61,7 +61,6 @@ struct mps_info {
unsigned long base_pfn;
unsigned long npages;
struct page *cma_pages; /* from alloc_contig_pages(), or NULL */
- bool fixed; /* a SETUP file over addr=/len= */
bool mmap_capable;
bool fixed_wb; /* holds a ref on the fixed-region WB alias */
@@ -87,11 +86,6 @@ static struct mps_root {
struct page *cma_pages;
unsigned long *allocated; /* pages allocated to a child */
struct list_head children;
- /*
- * The addr=/len= region is used either by one SETUP file or by the
- * root, never by both, so that its frames have one owner.
- */
- bool fixed_setup;
/* Files over the fixed region that may be mapped into userspace. */
unsigned int fixed_wb_users;
} mps_root;
@@ -289,8 +283,6 @@ static int mps_file_release(struct inode *inode, struct file *file)
}
if (addr && len && !info->cma_pages) {
mutex_lock(&mps_root.lock);
- if (info->fixed)
- mps_root.fixed_setup = false;
if (info->fixed_wb)
mps_fixed_wb_put();
mutex_unlock(&mps_root.lock);
@@ -373,66 +365,6 @@ static struct page *mps_alloc_region(unsigned long npages)
return pages;
}
-static long mps_ctl_setup(void __user *uarg)
-{
- struct mps_setup setup;
- unsigned long base_pfn, npages;
- struct page *pages = NULL;
- struct mps_info *info;
- struct file *file;
- int fd;
-
- if (copy_from_user(&setup, uarg, sizeof(setup)))
- return -EFAULT;
- if ((setup.flags & ~MPS_FLAG_MMAP_CAPABLE) || setup.pad)
- return -EINVAL;
-
- if (addr && len) {
- /* The fixed region has one owner: this file or the root. */
- mutex_lock(&mps_root.lock);
- if (mps_root.fixed_setup || mps_root.npages) {
- mutex_unlock(&mps_root.lock);
- return -EBUSY;
- }
- fd = mps_new_file(addr >> PAGE_SHIFT, len >> PAGE_SHIFT,
- setup.flags, &info, &file);
- if (fd >= 0 && mps_over_fixed_mappable(info)) {
- if (mps_fixed_wb_get()) {
- put_unused_fd(fd);
- fput(file);
- fd = -ENOMEM;
- } else {
- info->fixed_wb = true;
- }
- }
- if (fd >= 0) {
- info->fixed = true;
- mps_root.fixed_setup = true;
- }
- mutex_unlock(&mps_root.lock);
- if (fd >= 0)
- fd_install(fd, file);
- return fd;
- }
-
- if (!setup.size || !PAGE_ALIGNED(setup.size))
- return -EINVAL;
- npages = setup.size >> PAGE_SHIFT;
- pages = mps_alloc_region(npages);
- if (!pages)
- return -ENOMEM;
- base_pfn = page_to_pfn(pages);
-
- fd = mps_new_file(base_pfn, npages, setup.flags, &info, &file);
- if (fd < 0) {
- free_contig_range(base_pfn, npages);
- return fd;
- }
- info->cma_pages = pages;
- fd_install(fd, file);
- return fd;
-}
-
/* ---- Root and children -------------------------------------------------- */
/* Create the root on the first NEW_CHILD, from addr=/len= or of @size. */
@@ -446,8 +378,6 @@ static int mps_root_ensure(u64 size)
return 0;
if (addr && len) {
- if (mps_root.fixed_setup)
- return -EBUSY;
mps_root.base_pfn = addr >> PAGE_SHIFT;
npages = len >> PAGE_SHIFT;
} else {
@@ -596,8 +526,6 @@ static long mps_ctl_ioctl(struct file *file, unsigned int cmd,
void __user *uarg = (void __user *)arg;
switch (cmd) {
- case MPS_SETUP:
- return mps_ctl_setup(uarg);
case MPS_NEW_CHILD:
return mps_ctl_new_child(uarg);
case MPS_CTL_SET_PRESENT:
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index 1afea136328f5..3b0928b6478ca 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -14,38 +14,17 @@
* the ioctls below make.
*/
-/* Flags for struct mps_setup and struct mps_new_child */
+/* Flags for struct mps_new_child */
/* The host may map the pages; otherwise they are NO_USER_MAP */
#define MPS_FLAG_MMAP_CAPABLE (1u << 0)
-/*
- * ioctl on /dev/mem_provider_sample: create a provider fd.
- *
- * If the module was loaded with addr= and len=, the fd covers that fixed
- * range, which has no struct page, and @size is ignored. The fixed range
- * then has one owner. SETUP fails with -EBUSY while a SETUP file uses it,
- * or once the first MPS_NEW_CHILD has made it the root of the children, which
- * lasts until the module is unloaded. MPS_NEW_CHILD fails with -EBUSY while
- * a SETUP file uses it. Otherwise the fd covers @size bytes from
- * alloc_contig_pages().
- */
-struct mps_setup {
- __u32 flags; /* MPS_FLAG_* */
- __u32 pad;
- __u64 size; /* bytes, page aligned */
-};
-
#define MPS_IOCTL_BASE 'P'
-#define MPS_SETUP \
- _IOW(MPS_IOCTL_BASE, 1, struct mps_setup)
/*
* ---- A tree of owners --------------------------------------------------
*
* The control device owns one region, the root. NEW_CHILD carves part of
- * it into a new provider fd for one VM. The owner can MOVE pages between
- * children, DONATE pages to the root, so that no child has them, and
- * RECLAIM them. Each change revokes the range, so KVM and iommufd follow.
+ * it into a new provider fd for one VM.
*
* The owner changes a child's pages through the control device
* (CTL_SET_PRESENT). A child fd is read-only to its holder: a VMM can
@@ -54,9 +33,7 @@ struct mps_setup {
/*
* ioctl on /dev/mem_provider_sample: carve [@offset, @offset + @len) of the
- * root into a new child fd. The child gets every page of the range that no
- * other child has and that is not donated. Ranges of children may overlap,
- * which is how a page can MOVE between them. Returns the child fd.
+ * root into a new child fd. Returns the child fd.
*/
struct mps_new_child {
__u32 flags; /* MPS_FLAG_* */
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 25/50] samples/kvm: Remove MMAP_CAPABLE flag from sample provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (23 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 24/50] samples/kvm: Remove MPS_SETUP support " Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 26/50] samples/kvm: Remove fixed address and length module parameters Ackerley Tng via B4 Relay
` (25 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Remove the MMAP_CAPABLE flag and associated write-back PAT mapping logic
from the sample memory provider.
This is removed to focus on the core concept of providing memory.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 66 +--------------------------------------
samples/kvm/mem_provider_sample.h | 6 +---
2 files changed, 2 insertions(+), 70 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index abb8e35cd5357..f7ecb2ea1d75d 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -61,8 +61,6 @@ struct mps_info {
unsigned long base_pfn;
unsigned long npages;
struct page *cma_pages; /* from alloc_contig_pages(), or NULL */
- bool mmap_capable;
- bool fixed_wb; /* holds a ref on the fixed-region WB alias */
unsigned long *absent; /* taken away by CTL_SET_PRESENT */
@@ -86,48 +84,8 @@ static struct mps_root {
struct page *cma_pages;
unsigned long *allocated; /* pages allocated to a child */
struct list_head children;
- /* Files over the fixed region that may be mapped into userspace. */
- unsigned int fixed_wb_users;
} mps_root;
-/*
- * A write-back mapping of the addr=/len= region, held while any file over it
- * may be mapped into userspace (counted by mps_root.fixed_wb_users). Without
- * it, PAT makes the range uncached on x86, and the VMM's mapping would be
- * uncached while the guest's is write-back. PAT may still give a weaker type
- * if the MTRRs do not mark the range write-back.
- */
-static void *mps_fixed_wb;
-
-/* A file over the fixed region whose pages the host may map. */
-static bool mps_over_fixed_mappable(struct mps_info *info)
-{
- return addr && len && !info->cma_pages && info->mmap_capable;
-}
-
-/*
- * Hold a write-back mapping of the fixed region while any file over it may be
- * mapped into userspace. Called under mps_root.lock.
- */
-static int mps_fixed_wb_get(void)
-{
- if (mps_root.fixed_wb_users == 0) {
- mps_fixed_wb = memremap(addr, len, MEMREMAP_WB);
- if (!mps_fixed_wb)
- return -ENOMEM;
- }
- mps_root.fixed_wb_users++;
- return 0;
-}
-
-static void mps_fixed_wb_put(void)
-{
- if (--mps_root.fixed_wb_users == 0) {
- memunmap(mps_fixed_wb);
- mps_fixed_wb = NULL;
- }
-}
-
/* Does @info have page @index now? Called with or without info->lock. */
static bool mps_page_present(struct mps_info *info, unsigned long index)
{
@@ -192,9 +150,6 @@ static int mps_mp_get_page(struct mem_provider_file *mpf, pgoff_t index,
}
*pfn = info->base_pfn + index;
- /* The creator of the file decides whether the host may map it. */
- if (!info->mmap_capable)
- *attrs |= MEM_PROVIDER_ATTR_NO_USER_MAP;
return 0;
}
@@ -281,13 +236,6 @@ static int mps_file_release(struct inode *inode, struct file *file)
bitmap_clear(mps_root.allocated, info->root_index, info->npages);
mutex_unlock(&mps_root.lock);
}
- if (addr && len && !info->cma_pages) {
- mutex_lock(&mps_root.lock);
- if (info->fixed_wb)
- mps_fixed_wb_put();
- mutex_unlock(&mps_root.lock);
- }
-
if (info->cma_pages)
free_contig_range(info->base_pfn, info->npages);
bitmap_free(info->absent);
@@ -325,7 +273,6 @@ static int mps_new_file(unsigned long base_pfn, unsigned long npages,
mem_provider_file_init(&info->mpf);
info->base_pfn = base_pfn;
info->npages = npages;
- info->mmap_capable = flags & MPS_FLAG_MMAP_CAPABLE;
info->absent = bitmap_zalloc(npages, GFP_KERNEL);
if (!info->absent) {
ret = -ENOMEM;
@@ -449,7 +396,7 @@ static long mps_ctl_new_child(void __user *uarg)
if (copy_from_user(&nc, uarg, sizeof(nc)))
return -EFAULT;
- if ((nc.flags & ~MPS_FLAG_MMAP_CAPABLE) || nc.pad)
+ if (nc.flags || nc.pad)
return -EINVAL;
mutex_lock(&mps_root.lock);
@@ -471,15 +418,6 @@ static long mps_ctl_new_child(void __user *uarg)
ret = fd;
goto out_unlock;
}
- if (mps_over_fixed_mappable(info)) {
- if (mps_fixed_wb_get()) {
- put_unused_fd(fd);
- fput(file);
- ret = -ENOMEM;
- goto out_unlock;
- }
- info->fixed_wb = true;
- }
bitmap_set(mps_root.allocated, first, last - first);
info->is_child = true;
@@ -571,8 +509,6 @@ static void __exit mps_exit(void)
{
misc_deregister(&mps_dev);
mps_root_teardown();
- if (mps_fixed_wb)
- memunmap(mps_fixed_wb);
}
module_exit(mps_exit);
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index 3b0928b6478ca..51eaf39acbad1 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -14,10 +14,6 @@
* the ioctls below make.
*/
-/* Flags for struct mps_new_child */
-/* The host may map the pages; otherwise they are NO_USER_MAP */
-#define MPS_FLAG_MMAP_CAPABLE (1u << 0)
-
#define MPS_IOCTL_BASE 'P'
/*
@@ -36,7 +32,7 @@
* root into a new child fd. Returns the child fd.
*/
struct mps_new_child {
- __u32 flags; /* MPS_FLAG_* */
+ __u32 flags; /* Reserved, must be 0 */
__u32 pad;
__u64 offset; /* bytes into the root, page aligned */
__u64 len; /* bytes, page aligned */
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 26/50] samples/kvm: Remove fixed address and length module parameters
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (24 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 25/50] samples/kvm: Remove MMAP_CAPABLE flag " Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 27/50] samples/kvm: Remove mem_provider_ops from sample provider Ackerley Tng via B4 Relay
` (24 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Remove the addr and len module parameters from the sample memory
provider.
The prototype doesn't require pre-carved out memory ranges, since
MPS_NEW_CHILD already allows carving out memory on the fly. Being able to
carve out memory on the fly makes testing easier.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/Kconfig | 12 +++------
samples/kvm/mem_provider_sample.c | 53 +++++++++------------------------------
2 files changed, 15 insertions(+), 50 deletions(-)
diff --git a/samples/Kconfig b/samples/Kconfig
index 3c870bca076af..a06eafb5d7ba4 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -331,15 +331,9 @@ config SAMPLE_KVM_MEM_PROVIDER
depends on MEM_PROVIDER && CONTIG_ALLOC && X86_64 && m
help
This builds a sample memory provider (include/linux/mem_provider.h).
- Its files can back a guest_memfd and be mapped by iommufd. Loaded
- with addr= and len=, it lends a fixed physical range that has no
- struct page, for example memory hidden with memmap= on the command
- line. Without them, it allocates a contiguous region with
- alloc_contig_pages().
-
- It shows how a memory owner moves pages between VMs, takes them
- back or makes them read only, and how guest_memfd and iommufd
- follow each change.
+ Its files can back a guest_memfd and be mapped by iommufd.
+ It allocates a contiguous region with alloc_contig_pages()
+ and carves out child provider files on the fly.
If unsure, say N.
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index f7ecb2ea1d75d..574c712a11389 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -8,17 +8,9 @@
* guest's devices. The module calls neither KVM nor iommufd: it answers
* "what is page N" and revokes a range when the answer changes.
*
- * The region is either:
- *
- * - a fixed range given with addr= and len=, which has no struct page, for
- * example memory hidden from the kernel with memmap= on the command line;
- * - or, without those parameters, a range from alloc_contig_pages().
- *
* The control device /dev/mem_provider_sample creates provider files.
- * SETUP makes one file over the whole region. NEW_CHILD carves part of the
- * region into a child file, and MOVE, DONATE and RECLAIM change which child
- * has which page. A page can also be taken away or made read only. See
- * mem_provider_sample.h.
+ * NEW_CHILD carves part of the region into a child file. A page can also
+ * be taken away. See mem_provider_sample.h.
*
* Confidential VMs are not supported.
*/
@@ -38,15 +30,6 @@
#include "mem_provider_sample.h"
-static unsigned long long addr;
-module_param(addr, ullong, 0444);
-MODULE_PARM_DESC(addr,
- "Physical base of a region with no struct page (optional)");
-
-static unsigned long long len;
-module_param(len, ullong, 0444);
-MODULE_PARM_DESC(len, "Size in bytes of that region (optional)");
-
/*
* One provider file.
*
@@ -314,32 +297,26 @@ static struct page *mps_alloc_region(unsigned long npages)
/* ---- Root and children -------------------------------------------------- */
-/* Create the root on the first NEW_CHILD, from addr=/len= or of @size. */
+/* Create the root on the first NEW_CHILD of @size. */
static int mps_root_ensure(u64 size)
{
- struct page *pages = NULL;
+ struct page *pages;
unsigned long npages;
lockdep_assert_held(&mps_root.lock);
if (mps_root.npages)
return 0;
- if (addr && len) {
- mps_root.base_pfn = addr >> PAGE_SHIFT;
- npages = len >> PAGE_SHIFT;
- } else {
- if (!size || !PAGE_ALIGNED(size))
- return -EINVAL;
- npages = size >> PAGE_SHIFT;
- pages = mps_alloc_region(npages);
- if (!pages)
- return -ENOMEM;
- mps_root.base_pfn = page_to_pfn(pages);
- }
+ if (!size || !PAGE_ALIGNED(size))
+ return -EINVAL;
+ npages = size >> PAGE_SHIFT;
+ pages = mps_alloc_region(npages);
+ if (!pages)
+ return -ENOMEM;
+ mps_root.base_pfn = page_to_pfn(pages);
mps_root.allocated = bitmap_zalloc(npages, GFP_KERNEL);
if (!mps_root.allocated) {
- if (pages)
- free_contig_range(page_to_pfn(pages), npages);
+ free_contig_range(page_to_pfn(pages), npages);
return -ENOMEM;
}
mps_root.cma_pages = pages;
@@ -489,12 +466,6 @@ static int __init mps_init(void)
{
int ret;
- if ((addr || len) &&
- (!addr || !len || !PAGE_ALIGNED(addr) || !PAGE_ALIGNED(len))) {
- pr_err("mem_provider_sample: addr= and len= must both be set and page aligned\n");
- return -EINVAL;
- }
-
mutex_init(&mps_root.lock);
INIT_LIST_HEAD(&mps_root.children);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 27/50] samples/kvm: Remove mem_provider_ops from sample provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (25 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 26/50] samples/kvm: Remove fixed address and length module parameters Ackerley Tng via B4 Relay
@ 2026-10-22 23:27 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 28/50] samples/kvm: Make provider files independent of a root pool Ackerley Tng via B4 Relay
` (23 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:27 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Trim this memory provider down to just the memory provider parts first,
removing mem_provider_ops and related callbacks.
Leave the integration with guest_memfd alternative providers to future
patches.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/Kconfig | 4 +-
samples/kvm/mem_provider_sample.c | 121 ++++----------------------------------
samples/kvm/mem_provider_sample.h | 8 +--
3 files changed, 18 insertions(+), 115 deletions(-)
diff --git a/samples/Kconfig b/samples/Kconfig
index a06eafb5d7ba4..29a0efe49ac3a 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -328,9 +328,9 @@ source "samples/damon/Kconfig"
config SAMPLE_KVM_MEM_PROVIDER
tristate "Build sample memory provider -- loadable module only"
- depends on MEM_PROVIDER && CONTIG_ALLOC && X86_64 && m
+ depends on CONTIG_ALLOC && X86_64 && m
help
- This builds a sample memory provider (include/linux/mem_provider.h).
+ This builds a sample memory provider.
Its files can back a guest_memfd and be mapped by iommufd.
It allocates a contiguous region with alloc_contig_pages()
and carves out child provider files on the fly.
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 574c712a11389..294b2d65fc29d 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -2,13 +2,10 @@
/*
* mem_provider_sample - a sample memory provider.
*
- * The module owns a region of physical memory and lends it through provider
- * files (include/linux/mem_provider.h). A VMM passes the same file to
- * KVM_CREATE_GUEST_MEMFD, for the guest, and to IOMMU_IOAS_MAP_FILE, for the
- * guest's devices. The module calls neither KVM nor iommufd: it answers
- * "what is page N" and revokes a range when the answer changes.
+ * The module owns a region of physical memory and lends it through child
+ * files.
*
- * The control device /dev/mem_provider_sample creates provider files.
+ * The control device /dev/mem_provider_sample creates child files.
* NEW_CHILD carves part of the region into a child file. A page can also
* be taken away. See mem_provider_sample.h.
*
@@ -21,7 +18,6 @@
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/io.h>
-#include <linux/mem_provider.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/module.h>
@@ -31,14 +27,11 @@
#include "mem_provider_sample.h"
/*
- * One provider file.
+ * One child file.
*
- * Lock order: mps_root.lock, then mps_info.lock, then the consumers' locks
- * (taken by their revoke callbacks). get_page() takes no lock, so a
- * consumer can call it from its fault paths and its revoke callback.
+ * Lock order: mps_root.lock, then mps_info.lock.
*/
struct mps_info {
- struct mem_provider_file mpf;
struct mutex lock; /* the bitmaps */
unsigned long base_pfn;
@@ -69,89 +62,7 @@ static struct mps_root {
struct list_head children;
} mps_root;
-/* Does @info have page @index now? Called with or without info->lock. */
-static bool mps_page_present(struct mps_info *info, unsigned long index)
-{
- return !test_bit(index, info->absent);
-}
-
-/*
- * The length of the run from @index of pages with the same state as @index:
- * present.
- */
-static unsigned long mps_run(struct mps_info *info, unsigned long index)
-{
- unsigned long end = info->npages;
-
- end = min(end, find_next_bit(info->absent, info->npages, index + 1));
- return end - index;
-}
-
-/* ---- Provider operations ------------------------------------------------ */
-
-static struct mem_provider_file *
-mps_mp_attach(struct file *file, loff_t size)
-{
- struct mps_info *info = file->private_data;
-
- if (size > (loff_t)info->npages << PAGE_SHIFT)
- return ERR_PTR(-EINVAL);
- return &info->mpf;
-}
-
-static void mps_mp_detach(struct mem_provider_file *mpf)
-{
-}
-
-/*
- * Reads the bitmaps without info->lock. A change runs under the lock and
- * revokes the range afterwards, so an answer that a change makes out of date
- * is dropped by the consumer.
- */
-static int mps_mp_get_page(struct mem_provider_file *mpf, pgoff_t index,
- unsigned long *pfn, int *max_order, u32 *attrs)
-{
- struct mps_info *info = container_of(mpf, struct mps_info, mpf);
- unsigned long run;
-
- if (index >= info->npages)
- return -EINVAL;
-
- if (!mps_page_present(info, index))
- return -EFAULT;
-
- /* The aligned block around @index must be one run. */
- run = mps_run(info, index);
- while (*max_order) {
- unsigned long first = ALIGN_DOWN(index, 1UL << *max_order);
-
- if (first + (1UL << *max_order) <= index + run &&
- (first == index || mps_page_present(info, first)) &&
- mps_run(info, first) >= 1UL << *max_order)
- break;
- (*max_order)--;
- }
-
- *pfn = info->base_pfn + index;
- return 0;
-}
-
-static const struct mem_provider_ops mps_mp_ops = {
- .attach = mps_mp_attach,
- .detach = mps_mp_detach,
- .get_page = mps_mp_get_page,
-};
-
-/* Tell the consumers that pages [start, end) of @info changed. */
-static void mps_revoke(struct mps_info *info, unsigned long start,
- unsigned long end)
-{
- if (end > start)
- mem_provider_revoke(&info->mpf, (loff_t)start << PAGE_SHIFT,
- (loff_t)(end - start) << PAGE_SHIFT);
-}
-
-/* ---- Provider file ------------------------------------------------------ */
+/* ---- Child file --------------------------------------------------------- */
/* Byte range [offset, offset + len) of @info as page indices. */
static int mps_range(struct mps_info *info, u64 offset, u64 len,
@@ -174,7 +85,6 @@ static void mps_set_present(struct mps_info *info, unsigned long start,
bitmap_clear(info->absent, start, end - start);
else
bitmap_set(info->absent, start, end - start);
- mps_revoke(info, start, end);
mutex_unlock(&info->lock);
}
@@ -226,15 +136,11 @@ static int mps_file_release(struct inode *inode, struct file *file)
return 0;
}
-static const struct mem_provider_fops mps_file_fops = {
- .fops = {
- .owner = THIS_MODULE,
- .fop_flags = FOP_MEM_PROVIDER,
- .release = mps_file_release,
- .unlocked_ioctl = mps_file_ioctl,
- .compat_ioctl = compat_ptr_ioctl,
- },
- .ops = &mps_mp_ops,
+static const struct file_operations mps_file_fops = {
+ .owner = THIS_MODULE,
+ .release = mps_file_release,
+ .unlocked_ioctl = mps_file_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
};
/*
@@ -253,7 +159,6 @@ static int mps_new_file(unsigned long base_pfn, unsigned long npages,
info = kzalloc_obj(*info);
if (!info)
return -ENOMEM;
- mem_provider_file_init(&info->mpf);
info->base_pfn = base_pfn;
info->npages = npages;
info->absent = bitmap_zalloc(npages, GFP_KERNEL);
@@ -269,7 +174,7 @@ static int mps_new_file(unsigned long base_pfn, unsigned long npages,
ret = fd;
goto err_free;
}
- file = anon_inode_getfile("[mem-provider-sample]", &mps_file_fops.fops,
+ file = anon_inode_getfile("[mem-provider-sample]", &mps_file_fops,
info, O_RDWR);
if (IS_ERR(file)) {
put_unused_fd(fd);
@@ -354,7 +259,7 @@ static struct file *mps_get_child(int fd, struct mps_info **infop)
if (!file)
return ERR_PTR(-EBADF);
- if (file->f_op != &mps_file_fops.fops ||
+ if (file->f_op != &mps_file_fops ||
!((struct mps_info *)file->private_data)->is_child) {
fput(file);
return ERR_PTR(-EINVAL);
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index 51eaf39acbad1..b520b49e56f60 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -6,12 +6,10 @@
#include <linux/types.h>
/*
- * A sample memory provider (include/linux/mem_provider.h).
+ * A sample memory provider.
*
- * Every fd that this module returns is a provider file. Pass it as
- * resource_fd to KVM_CREATE_GUEST_MEMFD with GUEST_MEMFD_FLAG_USE_RESOURCE,
- * and as fd to IOMMU_IOAS_MAP_FILE. Both consumers follow every change that
- * the ioctls below make.
+ * Every fd that this module returns is a child file. Pass it to
+ * KVM_CREATE_GUEST_MEMFD.
*/
#define MPS_IOCTL_BASE 'P'
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 28/50] samples/kvm: Make provider files independent of a root pool
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (26 preceding siblings ...)
2026-10-22 23:27 ` [PATCH RFC v2 27/50] samples/kvm: Remove mem_provider_ops from sample provider Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 29/50] samples/kvm: Move set_present ioctl from device to provider file Ackerley Tng via B4 Relay
` (22 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Remove the root pool concept (mps_root) and allow each provider file to
directly allocate its own contiguous memory range from alloc_contig_pages.
Because children are effectively independent and no longer share or
transfer ownership of memory regions, managing a global root pool is
unnecessary and adds artificial restrictions.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 264 ++++++++++----------------------------
samples/kvm/mem_provider_sample.h | 37 ++----
2 files changed, 77 insertions(+), 224 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 294b2d65fc29d..a8d40f3c631ed 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -2,12 +2,12 @@
/*
* mem_provider_sample - a sample memory provider.
*
- * The module owns a region of physical memory and lends it through child
- * files.
+ * The module provides physical memory through provider files.
*
- * The control device /dev/mem_provider_sample creates child files.
- * NEW_CHILD carves part of the region into a child file. A page can also
- * be taken away. See mem_provider_sample.h.
+ * The control device /dev/mem_provider_sample creates provider files.
+ * Each file allocates its own contiguous memory from alloc_contig_pages().
+ * Pages can be dynamically marked present or absent.
+ * See mem_provider_sample.h.
*
* Confidential VMs are not supported.
*/
@@ -27,43 +27,18 @@
#include "mem_provider_sample.h"
/*
- * One child file.
- *
- * Lock order: mps_root.lock, then mps_info.lock.
+ * One provider file.
*/
struct mps_info {
struct mutex lock; /* the bitmaps */
unsigned long base_pfn;
unsigned long npages;
- struct page *cma_pages; /* from alloc_contig_pages(), or NULL */
+ struct page *cma_pages; /* from alloc_contig_pages() */
unsigned long *absent; /* taken away by CTL_SET_PRESENT */
-
- /*
- * A child of the root: its first page in the root, and whether it
- * was carved out from the root (is_child).
- */
- unsigned long root_index;
- bool is_child;
- struct list_head root_link; /* mps_root.children, mps_root.lock */
};
-/*
- * The root region, owned by the control device. Changes of ownership run
- * under @lock, which is taken before any child's lock.
- */
-static struct mps_root {
- struct mutex lock; /* everything below */
- unsigned long base_pfn;
- unsigned long npages;
- struct page *cma_pages;
- unsigned long *allocated; /* pages allocated to a child */
- struct list_head children;
-} mps_root;
-
-/* ---- Child file --------------------------------------------------------- */
-
/* Byte range [offset, offset + len) of @info as page indices. */
static int mps_range(struct mps_info *info, u64 offset, u64 len,
unsigned long *start, unsigned long *end)
@@ -93,8 +68,7 @@ static long mps_get_stats(struct mps_info *info, void __user *uarg)
struct mps_stats st = {};
mutex_lock(&info->lock);
- st.region_offset = (u64)info->root_index << PAGE_SHIFT;
- st.region_len = (u64)info->npages << PAGE_SHIFT;
+ st.size = (u64)info->npages << PAGE_SHIFT;
st.absent_pages = bitmap_weight(info->absent, info->npages);
mutex_unlock(&info->lock);
@@ -102,9 +76,8 @@ static long mps_get_stats(struct mps_info *info, void __user *uarg)
}
/*
- * A child fd is read-only to its holder: only GET_STATS. The owner changes a
- * child's pages through the control device (MPS_CTL_SET_PRESENT), so a VMM
- * that holds a child cannot.
+ * A provider fd is read-only to its holder: only GET_STATS. The owner changes
+ * a file's pages through the control device (MPS_CTL_SET_PRESENT).
*/
static long mps_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
@@ -117,18 +90,10 @@ static long mps_file_ioctl(struct file *file, unsigned int cmd,
return -ENOTTY;
}
-/* Every consumer has detached: each one holds a reference to the file. */
static int mps_file_release(struct inode *inode, struct file *file)
{
struct mps_info *info = file->private_data;
- /* A child gives the pages it has back to the root. */
- if (info->is_child) {
- mutex_lock(&mps_root.lock);
- list_del(&info->root_link);
- bitmap_clear(mps_root.allocated, info->root_index, info->npages);
- mutex_unlock(&mps_root.lock);
- }
if (info->cma_pages)
free_contig_range(info->base_pfn, info->npages);
bitmap_free(info->absent);
@@ -143,124 +108,13 @@ static const struct file_operations mps_file_fops = {
.compat_ioctl = compat_ptr_ioctl,
};
-/*
- * Make a provider file over [base_pfn, base_pfn + npages). The fd is
- * reserved but not installed, so that the caller can finish setting up
- * @info before another thread can reach it.
- */
-static int mps_new_file(unsigned long base_pfn, unsigned long npages,
- u32 flags, struct mps_info **infop,
- struct file **filep)
-{
- struct mps_info *info;
- struct file *file;
- int fd, ret;
-
- info = kzalloc_obj(*info);
- if (!info)
- return -ENOMEM;
- info->base_pfn = base_pfn;
- info->npages = npages;
- info->absent = bitmap_zalloc(npages, GFP_KERNEL);
- if (!info->absent) {
- ret = -ENOMEM;
- goto err_free;
- }
- mutex_init(&info->lock);
- INIT_LIST_HEAD(&info->root_link);
-
- fd = get_unused_fd_flags(O_CLOEXEC);
- if (fd < 0) {
- ret = fd;
- goto err_free;
- }
- file = anon_inode_getfile("[mem-provider-sample]", &mps_file_fops,
- info, O_RDWR);
- if (IS_ERR(file)) {
- put_unused_fd(fd);
- ret = PTR_ERR(file);
- goto err_free;
- }
- *infop = info;
- *filep = file;
- return fd;
-
-err_free:
- bitmap_free(info->absent);
- kfree(info);
- return ret;
-}
-
-static struct page *mps_alloc_region(unsigned long npages)
-{
- struct page *pages;
-
- pages = alloc_contig_pages(npages, GFP_KERNEL | __GFP_ZERO,
- numa_node_id(), NULL);
- return pages;
-}
-
-/* ---- Root and children -------------------------------------------------- */
-
-/* Create the root on the first NEW_CHILD of @size. */
-static int mps_root_ensure(u64 size)
-{
- struct page *pages;
- unsigned long npages;
-
- lockdep_assert_held(&mps_root.lock);
- if (mps_root.npages)
- return 0;
-
- if (!size || !PAGE_ALIGNED(size))
- return -EINVAL;
- npages = size >> PAGE_SHIFT;
- pages = mps_alloc_region(npages);
- if (!pages)
- return -ENOMEM;
- mps_root.base_pfn = page_to_pfn(pages);
- mps_root.allocated = bitmap_zalloc(npages, GFP_KERNEL);
- if (!mps_root.allocated) {
- free_contig_range(page_to_pfn(pages), npages);
- return -ENOMEM;
- }
- mps_root.cma_pages = pages;
- mps_root.npages = npages;
- return 0;
-}
-
-static void mps_root_teardown(void)
-{
- if (!mps_root.npages)
- return;
- WARN_ON(!list_empty(&mps_root.children));
- if (mps_root.cma_pages)
- free_contig_range(mps_root.base_pfn, mps_root.npages);
- bitmap_free(mps_root.allocated);
-}
-
-/* A page-aligned byte range of the root as page indices [first, last). */
-static int mps_root_range(u64 offset, u64 length, unsigned long *first,
- unsigned long *last)
-{
- if (!length || !PAGE_ALIGNED(offset) || !PAGE_ALIGNED(length))
- return -EINVAL;
- *first = offset >> PAGE_SHIFT;
- *last = *first + (length >> PAGE_SHIFT);
- if (*last <= *first || *last > mps_root.npages)
- return -EINVAL;
- return 0;
-}
-
-/* Return a referenced child file made by NEW_CHILD. */
-static struct file *mps_get_child(int fd, struct mps_info **infop)
+static struct file *mps_get_file(int fd, struct mps_info **infop)
{
struct file *file = fget(fd);
if (!file)
return ERR_PTR(-EBADF);
- if (file->f_op != &mps_file_fops ||
- !((struct mps_info *)file->private_data)->is_child) {
+ if (file->f_op != &mps_file_fops) {
fput(file);
return ERR_PTR(-EINVAL);
}
@@ -268,55 +122,76 @@ static struct file *mps_get_child(int fd, struct mps_info **infop)
return file;
}
-static long mps_ctl_new_child(void __user *uarg)
+static long mps_ctl_new_file(void __user *uarg)
{
- struct mps_new_child nc;
- unsigned long first, last;
+ struct mps_new_file nf;
struct mps_info *info;
+ struct page *pages;
struct file *file;
+ unsigned long npages;
int fd, ret;
- if (copy_from_user(&nc, uarg, sizeof(nc)))
+ if (copy_from_user(&nf, uarg, sizeof(nf)))
return -EFAULT;
- if (nc.flags || nc.pad)
+ if (nf.flags || nf.pad)
+ return -EINVAL;
+ if (!nf.size || !PAGE_ALIGNED(nf.size))
+ return -EINVAL;
+
+ npages = nf.size >> PAGE_SHIFT;
+ if (npages > KMALLOC_MAX_SIZE * BITS_PER_BYTE)
return -EINVAL;
- mutex_lock(&mps_root.lock);
- ret = mps_root_ensure(nc.offset + nc.len);
- if (ret)
- goto out_unlock;
- ret = mps_root_range(nc.offset, nc.len, &first, &last);
- if (ret)
- goto out_unlock;
-
- if (find_next_bit(mps_root.allocated, last, first) < last) {
- ret = -EBUSY;
- goto out_unlock;
+ info = kzalloc_obj(*info);
+ if (!info)
+ return -ENOMEM;
+
+ info->absent = bitmap_zalloc(npages, GFP_KERNEL);
+ if (!info->absent) {
+ ret = -ENOMEM;
+ goto err_free_info;
}
- fd = mps_new_file(mps_root.base_pfn + first, last - first, nc.flags,
- &info, &file);
+ pages = alloc_contig_pages(npages, GFP_KERNEL | __GFP_ZERO,
+ numa_node_id(), NULL);
+ if (!pages) {
+ ret = -ENOMEM;
+ goto err_free_absent;
+ }
+
+ info->cma_pages = pages;
+ info->base_pfn = page_to_pfn(pages);
+ info->npages = npages;
+ mutex_init(&info->lock);
+
+ fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0) {
ret = fd;
- goto out_unlock;
+ goto err_free_pages;
+ }
+
+ file = anon_inode_getfile("[mem-provider-sample]", &mps_file_fops,
+ info, O_RDWR);
+ if (IS_ERR(file)) {
+ put_unused_fd(fd);
+ ret = PTR_ERR(file);
+ goto err_free_pages;
}
- bitmap_set(mps_root.allocated, first, last - first);
- info->is_child = true;
- info->root_index = first;
- list_add(&info->root_link, &mps_root.children);
- mutex_unlock(&mps_root.lock);
fd_install(fd, file);
return fd;
-out_unlock:
- mutex_unlock(&mps_root.lock);
+err_free_pages:
+ free_contig_range(page_to_pfn(pages), npages);
+err_free_absent:
+ bitmap_free(info->absent);
+err_free_info:
+ kfree(info);
return ret;
}
/*
- * CTL_SET_PRESENT changes a child's pages, by the owner on the control device.
- * A VMM that holds the child fd cannot, so it cannot undo them.
+ * CTL_SET_PRESENT changes a file's pages, by the owner on the control device.
*/
static long mps_ctl_set_present(void __user *uarg)
{
@@ -330,7 +205,7 @@ static long mps_ctl_set_present(void __user *uarg)
return -EFAULT;
if (cr.value > 1)
return -EINVAL;
- file = mps_get_child(cr.fd, &info);
+ file = mps_get_file(cr.fd, &info);
if (IS_ERR(file))
return PTR_ERR(file);
ret = mps_range(info, cr.offset, cr.len, &start, &end);
@@ -346,8 +221,8 @@ static long mps_ctl_ioctl(struct file *file, unsigned int cmd,
void __user *uarg = (void __user *)arg;
switch (cmd) {
- case MPS_NEW_CHILD:
- return mps_ctl_new_child(uarg);
+ case MPS_NEW_FILE:
+ return mps_ctl_new_file(uarg);
case MPS_CTL_SET_PRESENT:
return mps_ctl_set_present(uarg);
default:
@@ -369,22 +244,13 @@ static struct miscdevice mps_dev = {
static int __init mps_init(void)
{
- int ret;
-
- mutex_init(&mps_root.lock);
- INIT_LIST_HEAD(&mps_root.children);
-
- ret = misc_register(&mps_dev);
- if (ret)
- return ret;
- return 0;
+ return misc_register(&mps_dev);
}
module_init(mps_init);
static void __exit mps_exit(void)
{
misc_deregister(&mps_dev);
- mps_root_teardown();
}
module_exit(mps_exit);
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index b520b49e56f60..c4cb10b4aadeb 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -8,41 +8,28 @@
/*
* A sample memory provider.
*
- * Every fd that this module returns is a child file. Pass it to
+ * Every fd that this module returns is a provider file. Pass it to
* KVM_CREATE_GUEST_MEMFD.
*/
#define MPS_IOCTL_BASE 'P'
/*
- * ---- A tree of owners --------------------------------------------------
- *
- * The control device owns one region, the root. NEW_CHILD carves part of
- * it into a new provider fd for one VM.
- *
- * The owner changes a child's pages through the control device
- * (CTL_SET_PRESENT). A child fd is read-only to its holder: a VMM can
- * only GET_STATS, never change the memory.
- */
-
-/*
- * ioctl on /dev/mem_provider_sample: carve [@offset, @offset + @len) of the
- * root into a new child fd. Returns the child fd.
+ * ioctl on /dev/mem_provider_sample: create a new provider fd of @size.
+ * Returns the provider fd.
*/
-struct mps_new_child {
+struct mps_new_file {
__u32 flags; /* Reserved, must be 0 */
__u32 pad;
- __u64 offset; /* bytes into the root, page aligned */
- __u64 len; /* bytes, page aligned */
+ __u64 size; /* bytes, page aligned */
};
-#define MPS_NEW_CHILD \
- _IOW(MPS_IOCTL_BASE, 5, struct mps_new_child)
+#define MPS_NEW_FILE \
+ _IOW(MPS_IOCTL_BASE, 5, struct mps_new_file)
-/* ioctl on a child fd: read the child's state, for tests. */
+/* ioctl on a provider fd: read the file's state, for tests. */
struct mps_stats {
- __u64 region_offset; /* the child's range in the root */
- __u64 region_len;
+ __u64 size;
__u64 absent_pages; /* pages taken away by CTL_SET_PRESENT */
};
@@ -50,14 +37,14 @@ struct mps_stats {
_IOR(MPS_IOCTL_BASE, 10, struct mps_stats)
/*
- * ioctl on /dev/mem_provider_sample: CTL_SET_PRESENT on child @fd, by the
+ * ioctl on /dev/mem_provider_sample: CTL_SET_PRESENT on file @fd, by the
* owner. @value is the present value (1 = present, 0 = absent), and @offset
- * and @len are a byte range within the child, page aligned.
+ * and @len are a byte range within the file, page aligned.
*/
struct mps_ctl_range {
__s32 fd;
__u32 value;
- __u64 offset; /* bytes into the child, page aligned */
+ __u64 offset; /* bytes into the file, page aligned */
__u64 len; /* bytes, page aligned */
};
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 29/50] samples/kvm: Move set_present ioctl from device to provider file
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (27 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 28/50] samples/kvm: Make provider files independent of a root pool Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 30/50] KVM: selftests: Add basic test for sample memory provider Ackerley Tng via B4 Relay
` (21 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Move the MPS_CTL_SET_PRESENT ioctl from the /dev/mem_provider_sample
control device to become MPS_SET_PRESENT directly on the provider file
descriptor itself.
Calling MPS_SET_PRESENT directly on the provider file eliminates the
need to pass a file descriptor in the ioctl argument.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 90 ++++++++++++++-------------------------
samples/kvm/mem_provider_sample.h | 14 +++---
2 files changed, 40 insertions(+), 64 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index a8d40f3c631ed..397ebb3773f39 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -36,7 +36,7 @@ struct mps_info {
unsigned long npages;
struct page *cma_pages; /* from alloc_contig_pages() */
- unsigned long *absent; /* taken away by CTL_SET_PRESENT */
+ unsigned long *absent; /* taken away by MPS_SET_PRESENT */
};
/* Byte range [offset, offset + len) of @info as page indices. */
@@ -75,21 +75,6 @@ static long mps_get_stats(struct mps_info *info, void __user *uarg)
return copy_to_user(uarg, &st, sizeof(st)) ? -EFAULT : 0;
}
-/*
- * A provider fd is read-only to its holder: only GET_STATS. The owner changes
- * a file's pages through the control device (MPS_CTL_SET_PRESENT).
- */
-static long mps_file_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
-{
- struct mps_info *info = file->private_data;
- void __user *uarg = (void __user *)arg;
-
- if (cmd == MPS_GET_STATS)
- return mps_get_stats(info, uarg);
- return -ENOTTY;
-}
-
static int mps_file_release(struct inode *inode, struct file *file)
{
struct mps_info *info = file->private_data;
@@ -101,6 +86,38 @@ static int mps_file_release(struct inode *inode, struct file *file)
return 0;
}
+static long mps_file_set_present(struct mps_info *info, void __user *uarg)
+{
+ struct mps_set_present sp;
+ unsigned long start, end;
+ int ret;
+
+ if (copy_from_user(&sp, uarg, sizeof(sp)))
+ return -EFAULT;
+ if (sp.value > 1 || sp.pad)
+ return -EINVAL;
+ ret = mps_range(info, sp.offset, sp.len, &start, &end);
+ if (!ret)
+ mps_set_present(info, start, end, sp.value);
+ return ret;
+}
+
+static long mps_file_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mps_info *info = file->private_data;
+ void __user *uarg = (void __user *)arg;
+
+ switch (cmd) {
+ case MPS_GET_STATS:
+ return mps_get_stats(info, uarg);
+ case MPS_SET_PRESENT:
+ return mps_file_set_present(info, uarg);
+ default:
+ return -ENOTTY;
+ }
+}
+
static const struct file_operations mps_file_fops = {
.owner = THIS_MODULE,
.release = mps_file_release,
@@ -108,20 +125,6 @@ static const struct file_operations mps_file_fops = {
.compat_ioctl = compat_ptr_ioctl,
};
-static struct file *mps_get_file(int fd, struct mps_info **infop)
-{
- struct file *file = fget(fd);
-
- if (!file)
- return ERR_PTR(-EBADF);
- if (file->f_op != &mps_file_fops) {
- fput(file);
- return ERR_PTR(-EINVAL);
- }
- *infop = file->private_data;
- return file;
-}
-
static long mps_ctl_new_file(void __user *uarg)
{
struct mps_new_file nf;
@@ -190,31 +193,6 @@ static long mps_ctl_new_file(void __user *uarg)
return ret;
}
-/*
- * CTL_SET_PRESENT changes a file's pages, by the owner on the control device.
- */
-static long mps_ctl_set_present(void __user *uarg)
-{
- struct mps_ctl_range cr;
- unsigned long start, end;
- struct mps_info *info;
- struct file *file;
- int ret;
-
- if (copy_from_user(&cr, uarg, sizeof(cr)))
- return -EFAULT;
- if (cr.value > 1)
- return -EINVAL;
- file = mps_get_file(cr.fd, &info);
- if (IS_ERR(file))
- return PTR_ERR(file);
- ret = mps_range(info, cr.offset, cr.len, &start, &end);
- if (!ret)
- mps_set_present(info, start, end, cr.value);
- fput(file);
- return ret;
-}
-
static long mps_ctl_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
@@ -223,8 +201,6 @@ static long mps_ctl_ioctl(struct file *file, unsigned int cmd,
switch (cmd) {
case MPS_NEW_FILE:
return mps_ctl_new_file(uarg);
- case MPS_CTL_SET_PRESENT:
- return mps_ctl_set_present(uarg);
default:
return -ENOTTY;
}
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index c4cb10b4aadeb..b9f10286cb30e 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -30,25 +30,25 @@ struct mps_new_file {
/* ioctl on a provider fd: read the file's state, for tests. */
struct mps_stats {
__u64 size;
- __u64 absent_pages; /* pages taken away by CTL_SET_PRESENT */
+ __u64 absent_pages; /* pages taken away by MPS_SET_PRESENT */
};
#define MPS_GET_STATS \
_IOR(MPS_IOCTL_BASE, 10, struct mps_stats)
/*
- * ioctl on /dev/mem_provider_sample: CTL_SET_PRESENT on file @fd, by the
- * owner. @value is the present value (1 = present, 0 = absent), and @offset
+ * ioctl on a provider fd: SET_PRESENT on [@offset, @offset + @len).
+ * @value is the present value (1 = present, 0 = absent), and @offset
* and @len are a byte range within the file, page aligned.
*/
-struct mps_ctl_range {
- __s32 fd;
+struct mps_set_present {
__u32 value;
+ __u32 pad;
__u64 offset; /* bytes into the file, page aligned */
__u64 len; /* bytes, page aligned */
};
-#define MPS_CTL_SET_PRESENT \
- _IOW(MPS_IOCTL_BASE, 12, struct mps_ctl_range)
+#define MPS_SET_PRESENT \
+ _IOW(MPS_IOCTL_BASE, 12, struct mps_set_present)
#endif /* _SAMPLES_KVM_MEM_PROVIDER_SAMPLE_H */
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 30/50] KVM: selftests: Add basic test for sample memory provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (28 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 29/50] samples/kvm: Move set_present ioctl from device to provider file Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-23 23:32 ` sashiko-bot
2026-10-22 23:28 ` [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers Ackerley Tng via B4 Relay
` (20 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add a selftest for the sample memory provider (/dev/mem_provider_sample).
Test basic lifecycle creation across multiple sizes. Verify file capacity
and absent pages accounting using the MPS_GET_STATS ioctl. Also test
marking pages absent and present with MPS_SET_PRESENT, verifying that
absent pages are reflected in stats accordingly.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/mem_provider_sample_test.c | 147 +++++++++++++++++++++
2 files changed, 148 insertions(+)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 17a4256e90819..5f7118bddfc36 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -72,6 +72,7 @@ TEST_GEN_PROGS_COMMON += set_memory_region_test
TEST_GEN_PROGS_COMMON += memslot_modification_stress_test
TEST_GEN_PROGS_COMMON += memslot_perf_test
TEST_GEN_PROGS_COMMON += vm_types_test
+TEST_GEN_PROGS_COMMON += mem_provider_sample_test
# Compiled test targets
TEST_GEN_PROGS_x86 = $(TEST_GEN_PROGS_COMMON)
diff --git a/tools/testing/selftests/kvm/mem_provider_sample_test.c b/tools/testing/selftests/kvm/mem_provider_sample_test.c
new file mode 100644
index 0000000000000..932ea3f30f494
--- /dev/null
+++ b/tools/testing/selftests/kvm/mem_provider_sample_test.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests for the sample memory provider (/dev/mem_provider_sample).
+ */
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "test_util.h"
+#include "../../../../samples/kvm/mem_provider_sample.h"
+
+#define DEV_PATH "/dev/mem_provider_sample"
+
+static int open_provider_dev(void)
+{
+ int fd = open(DEV_PATH, O_RDWR);
+
+ if (fd < 0) {
+ TEST_REQUIRE(errno != ENOENT && errno != ENODEV);
+ TEST_FAIL("Failed to open %s: %d (%s)", DEV_PATH, errno,
+ strerror(errno));
+ }
+ return fd;
+}
+
+static int create_provider_file(int dev_fd, uint64_t size)
+{
+ struct mps_new_file nf = {
+ .size = size,
+ };
+ int fd;
+
+ fd = ioctl(dev_fd, MPS_NEW_FILE, &nf);
+ TEST_ASSERT(fd >= 0, "MPS_NEW_FILE failed for size 0x%lx: %d (%s)",
+ size, errno, strerror(errno));
+ return fd;
+}
+
+static void test_lifecycle_and_creation(int dev_fd)
+{
+ size_t page_size = getpagesize();
+ uint64_t sizes[] = {
+ page_size,
+ 2 * page_size,
+ 2 * 1024 * 1024,
+ };
+ struct mps_stats st;
+ int i, fd;
+
+ for (i = 0; i < ARRAY_SIZE(sizes); i++) {
+ fd = create_provider_file(dev_fd, sizes[i]);
+
+ memset(&st, 0, sizeof(st));
+ TEST_ASSERT(!ioctl(fd, MPS_GET_STATS, &st),
+ "MPS_GET_STATS failed: %d (%s)",
+ errno, strerror(errno));
+ TEST_ASSERT(st.size == sizes[i],
+ "size mismatch: expected 0x%lx, got 0x%llx",
+ sizes[i], st.size);
+ TEST_ASSERT(st.absent_pages == 0,
+ "absent_pages mismatch: expected 0, got %llu",
+ st.absent_pages);
+
+ close(fd);
+ }
+}
+
+static void test_set_present(int dev_fd)
+{
+ size_t page_size = getpagesize();
+ uint64_t nr_pages = 4;
+ uint64_t file_size = nr_pages * page_size;
+ struct mps_set_present sp;
+ struct mps_stats st;
+ int ret, fd;
+
+ fd = create_provider_file(dev_fd, file_size);
+
+ /* Initially all pages are present */
+ TEST_ASSERT(!ioctl(fd, MPS_GET_STATS, &st),
+ "MPS_GET_STATS failed: %d (%s)", errno, strerror(errno));
+ TEST_ASSERT(st.absent_pages == 0,
+ "Initial absent_pages mismatch: expected 0, got %llu",
+ st.absent_pages);
+
+ /* Mark page 1 absent */
+ memset(&sp, 0, sizeof(sp));
+ sp.value = 0;
+ sp.offset = page_size;
+ sp.len = page_size;
+ ret = ioctl(fd, MPS_SET_PRESENT, &sp);
+ TEST_ASSERT(!ret, "MPS_SET_PRESENT (absent) failed: %d (%s)",
+ errno, strerror(errno));
+
+ TEST_ASSERT(!ioctl(fd, MPS_GET_STATS, &st),
+ "MPS_GET_STATS failed: %d (%s)", errno, strerror(errno));
+ TEST_ASSERT(st.size == file_size,
+ "size changed unexpectedly: 0x%llx", st.size);
+ TEST_ASSERT(st.absent_pages == 1,
+ "absent_pages mismatch after absent: expected 1, got %llu",
+ st.absent_pages);
+
+ /* Mark page 1 back to present */
+ sp.value = 1;
+ ret = ioctl(fd, MPS_SET_PRESENT, &sp);
+ TEST_ASSERT(!ret, "MPS_SET_PRESENT (present) failed: %d (%s)",
+ errno, strerror(errno));
+
+ TEST_ASSERT(!ioctl(fd, MPS_GET_STATS, &st),
+ "MPS_GET_STATS failed: %d (%s)", errno, strerror(errno));
+ TEST_ASSERT(st.absent_pages == 0,
+ "absent_pages mismatch after restore: expected 0, got %llu",
+ st.absent_pages);
+
+ /* Mark all pages absent */
+ sp.value = 0;
+ sp.offset = 0;
+ sp.len = file_size;
+ ret = ioctl(fd, MPS_SET_PRESENT, &sp);
+ TEST_ASSERT(!ret, "MPS_SET_PRESENT (all absent) failed: %d (%s)",
+ errno, strerror(errno));
+
+ TEST_ASSERT(!ioctl(fd, MPS_GET_STATS, &st),
+ "MPS_GET_STATS failed: %d (%s)", errno, strerror(errno));
+ TEST_ASSERT(st.absent_pages == nr_pages,
+ "absent_pages mismatch after all absent: expected %lu, got %llu",
+ nr_pages, st.absent_pages);
+
+ close(fd);
+}
+
+int main(int argc, char *argv[])
+{
+ int dev_fd;
+
+ dev_fd = open_provider_dev();
+
+ test_lifecycle_and_creation(dev_fd);
+ test_set_present(dev_fd);
+
+ close(dev_fd);
+ return 0;
+}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 30/50] KVM: selftests: Add basic test for sample memory provider
2026-10-22 23:28 ` [PATCH RFC v2 30/50] KVM: selftests: Add basic test for sample memory provider Ackerley Tng via B4 Relay
@ 2026-10-23 23:32 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:32 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] KVM: selftests: open_provider_dev() reimplements __open_path_or_exit()
--
commit adc070cca1ed801047bd293a0094a045579d124f
Author: Ackerley Tng <ackerleytng@google.com>
KVM: selftests: Add basic test for sample memory provider
This commit adds a selftest for /dev/mem_provider_sample to verify basic file
creation across multiple sizes, stats reporting via MPS_GET_STATS, and page
presence toggling via MPS_SET_PRESENT.
> diff --git a/tools/testing/selftests/kvm/mem_provider_sample_test.c b/tools/testing/selftests/kvm/mem_provider_sample_test.c
> new file mode 100644
> index 0000000000000..932ea3f30f494
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/mem_provider_sample_test.c
> @@ -0,0 +1,147 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Tests for the sample memory provider (/dev/mem_provider_sample).
> + */
> +#include <fcntl.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/ioctl.h>
> +#include <sys/stat.h>
> +#include <unistd.h>
> +
> +#include "test_util.h"
> +#include "../../../../samples/kvm/mem_provider_sample.h"
> +
> +#define DEV_PATH "/dev/mem_provider_sample"
> +
> +static int open_provider_dev(void)
> +{
> + int fd = open(DEV_PATH, O_RDWR);
> +
> + if (fd < 0) {
> + TEST_REQUIRE(errno != ENOENT && errno != ENODEV);
> + TEST_FAIL("Failed to open %s: %d (%s)", DEV_PATH, errno,
> + strerror(errno));
> + }
> + return fd;
> +}
[Severity: Medium]
Could open_provider_dev() use __open_path_or_exit() from
tools/testing/selftests/kvm/lib/kvm_util.c instead of hand-rolling the
open() error handling?
tools/testing/selftests/kvm/lib/kvm_util.c:__open_path_or_exit() {
fd = open(path, flags);
if (fd < 0)
goto error;
return fd;
error:
if (errno == EACCES || errno == ENOENT)
ksft_exit_skip("- Cannot open '%s': %s. %s\n",
path, strerror(errno),
errno == EACCES ? "Root required?" : enoent_help);
TEST_FAIL("Failed to open '%s'", path);
}
Because samples/kvm/mem_provider_sample.c registers /dev/mem_provider_sample
via misc_register() with default 0600 permissions, running the test as an
unprivileged user causes open() in main()->open_provider_dev() to fail with
EACCES. That bypasses TEST_REQUIRE(errno != ENOENT && errno != ENODEV) and
calls TEST_FAIL() -> test_assert() in
tools/testing/selftests/kvm/lib/assert.c:
tools/testing/selftests/kvm/lib/assert.c:test_assert() {
if (!(exp)) {
va_start(ap, fmt);
fprintf(stderr, "\n==== Test Assertion Failure ====\n"
" %s:%u: %s\n"
" pid=%d tid=%d errno=%d - %s\n",
file, line, exp_str, getpid(), kvm_gettid(),
errno, strerror(errno));
test_dump_stack();
...
if (errno == EACCES) {
print_skip("Access denied - Exiting");
exit(KSFT_SKIP);
}
exit(254);
}
}
In test_assert(), fprintf() and test_dump_stack() run before the
errno == EACCES check, printing an assertion failure stack trace and
clobbering errno so the test exits with 254 instead of skipping cleanly. In
addition, when the sample module is not loaded,
TEST_REQUIRE(errno != ENOENT && errno != ENODEV) only prints the raw
expression without the missing device path or Kconfig hint.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=30
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (29 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 30/50] KVM: selftests: Add basic test for sample memory provider Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-23 17:23 ` Fred Griffoul
2026-10-23 17:42 ` Fred Griffoul
2026-10-22 23:28 ` [PATCH RFC v2 32/50] KVM: guest_memfd: Refactor error handling in kvm_gmem_fault_user_mapping Ackerley Tng via B4 Relay
` (19 subsequent siblings)
50 siblings, 2 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add .get_pfn callback to struct guest_memfd_provider_operations to
support physical memory providers (e.g. devices, DAX, or pre-allocated
ranges) that supply PFNs directly without folio allocation or page cache
insertion.
Define GUEST_MEMFD_FLAG_PFN_MODE in uapi/linux/kvm.h and define
kvm_gmem_is_pfn_mode() to check for this flag in gi->flags.
In __kvm_gmem_get_pfn(), query .get_pfn() directly when in PFN mode
and return NULL on success (instead of a struct folio *).
Note that .alloc_folio is still required by kvm_gmem_attach_resource() and
GUEST_MEMFD_FLAG_PFN_MODE is not yet enabled for userspace. Subsequent
patches will update the rest of guest_memfd (e.g. mmap, fallocate,
truncation, and attributes) to support PFN mode before enabling the flag
and allowing external PFN-backed providers to be attached.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
include/linux/guest_memfd.h | 8 ++++++++
include/uapi/linux/kvm.h | 1 +
virt/kvm/guest_memfd.c | 42 +++++++++++++++++++++++++++++++++++++-----
3 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index f3e6077c39ac3..10c48f41a6d7e 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -18,6 +18,12 @@ struct mempolicy;
* Must return a pointer to a struct folio on success, or an
* ERR_PTR() error code on failure. Must never return NULL.
* @invalidate_folio: Notify provider that a folio has been invalidated.
+ * @get_pfn: Obtain a PFN for a given index. The provider must ensure backing
+ * memory is present and is responsible for its contents, such as
+ * zeroing if necessary. On success, return 0 with the physical frame
+ * number in @pfn and, if @max_order is non-NULL, the maximum page
+ * order of contiguous backing memory containing @pfn. Returns a
+ * negative error code on failure.
*
* Used by filesystems and device drivers that provide memory for guest_memfd.
*/
@@ -27,6 +33,8 @@ struct guest_memfd_provider_operations {
struct folio *(*alloc_folio)(void *provider, pgoff_t index,
struct mempolicy *mpol);
void (*invalidate_folio)(void *provider, struct folio *folio);
+ int (*get_pfn)(void *provider, pgoff_t index, unsigned long *pfn,
+ int *max_order);
};
#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index f4108bf7e3192..6798056f50e32 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1675,6 +1675,7 @@ struct kvm_memory_attributes2 {
#define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
#define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1)
#define GUEST_MEMFD_FLAG_USE_RESOURCE (1ULL << 2)
+#define GUEST_MEMFD_FLAG_PFN_MODE (1ULL << 3)
struct kvm_create_guest_memfd {
__u64 size;
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 84877beed6701..3d89bc76f3867 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -54,6 +54,11 @@ static __always_inline struct gmem_inode *GMEM_I(struct inode *inode)
return container_of(inode, struct gmem_inode, vfs_inode);
}
+static bool kvm_gmem_is_pfn_mode(struct gmem_inode *gi)
+{
+ return gi->flags & GUEST_MEMFD_FLAG_PFN_MODE;
+}
+
static void *gmem_native_attach(struct inode *gmem_inode,
struct file *resource_file)
{
@@ -124,6 +129,20 @@ static struct folio *gmem_provider_alloc_folio(struct gmem_inode *gi,
return ERR_PTR(-EOPNOTSUPP);
}
+static int gmem_provider_get_pfn(struct gmem_inode *gi, pgoff_t index,
+ kvm_pfn_t *pfn, int *max_order)
+{
+ unsigned long raw_pfn;
+ int r;
+
+ r = gi->provider_ops->get_pfn(gi->provider, index, &raw_pfn, max_order);
+ if (r)
+ return r;
+
+ *pfn = raw_pfn;
+ return 0;
+}
+
static void gmem_provider_invalidate_folio(struct gmem_inode *gi,
struct folio *folio)
{
@@ -1270,15 +1289,17 @@ void kvm_gmem_unbind(struct kvm_memory_slot *slot)
filemap_invalidate_unlock(file->f_mapping);
}
-/* Returns a locked folio on success. */
+/* On success, return a locked folio in folio mode and NULL in PFN mode. */
static struct folio *__kvm_gmem_get_pfn(struct file *file,
struct kvm_memory_slot *slot,
pgoff_t index, kvm_pfn_t *pfn,
int *max_order)
{
struct file *slot_file = READ_ONCE(slot->gmem.file);
+ struct gmem_inode *gi = GMEM_I(file_inode(file));
struct gmem_file *f = file->private_data;
struct folio *folio;
+ int r;
if (file != slot_file) {
WARN_ON_ONCE(slot_file);
@@ -1290,6 +1311,13 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
return ERR_PTR(-EIO);
}
+ if (kvm_gmem_is_pfn_mode(gi)) {
+ r = gmem_provider_get_pfn(gi, index, pfn, max_order);
+ if (r)
+ return ERR_PTR(r);
+ return NULL;
+ }
+
folio = kvm_gmem_get_folio(file_inode(file), index);
if (IS_ERR(folio))
return folio;
@@ -1338,8 +1366,10 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
r = kvm_arch_gmem_make_private(kvm, gfn, *pfn,
(kvm_pfn_t)1 << *max_order);
- folio_unlock(folio);
- folio_put(folio);
+ if (folio) {
+ folio_unlock(folio);
+ folio_put(folio);
+ }
out:
filemap_invalidate_unlock_shared(file_inode(file)->i_mapping);
@@ -1366,7 +1396,8 @@ static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot,
goto out_unlock;
}
- folio_unlock(folio);
+ if (folio)
+ folio_unlock(folio);
if (!kvm_is_private_gfn(kvm, gfn)) {
ret = -EINVAL;
@@ -1376,7 +1407,8 @@ static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot,
ret = post_populate(kvm, gfn, pfn, src_page, opaque);
out_put_folio:
- folio_put(folio);
+ if (folio)
+ folio_put(folio);
out_unlock:
filemap_invalidate_unlock(file->f_mapping);
return ret;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers
2026-10-22 23:28 ` [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers Ackerley Tng via B4 Relay
@ 2026-10-23 17:23 ` Fred Griffoul
2026-10-23 17:42 ` Fred Griffoul
1 sibling, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-23 17:23 UTC (permalink / raw)
To: Ackerley Tng
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H. Peter Anvin, Hugh Dickins,
Ingo Molnar, Jacob Pan, James Houghton, Jan Kara, Jason Gunthorpe,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Kevin Tian, Lorenzo Stoakes,
Marc Zyngier, Michael Roth, Mike Rapoport, Nathan Chancellor,
Nick Desaulniers, Nicolin Chen, Oliver Upton, Paolo Bonzini,
Pratyush Yadav, Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba, kvm,
linux-doc, linux-fsdevel, linux-kernel, linux-kselftest, linux-mm,
llvm
> + int (*get_pfn)(void *provider, pgoff_t index, unsigned long *pfn,
> + int *max_order);
Should get_pfn() also return attributes of the frame? Only the provider
knows them, and every mapping of the frame has to follow them. Two
examples:
- Read only. A provider may lend a page that the guest can read but not
write. I know you dropped this from the sample to keep things simple,
but without it guest_memfd maps every frame writable, and there is no
other way to make a guest_memfd page read only.
- Aliased. The frame is also reachable somewhere else, e.g. a ring
shared with a device. Such a frame must not become private; see my
reply to 34/50.
A u32 of flags, with 0 for today's behaviour, would cover both and
leave room for more later.
Fred
^ permalink raw reply [flat|nested] 79+ messages in thread
* Re: [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers
2026-10-22 23:28 ` [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers Ackerley Tng via B4 Relay
2026-10-23 17:23 ` Fred Griffoul
@ 2026-10-23 17:42 ` Fred Griffoul
1 sibling, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-23 17:42 UTC (permalink / raw)
To: Ackerley Tng
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H. Peter Anvin, Hugh Dickins,
Ingo Molnar, Jacob Pan, James Houghton, Jan Kara, Jason Gunthorpe,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Kevin Tian, Lorenzo Stoakes,
Marc Zyngier, Michael Roth, Mike Rapoport, Nathan Chancellor,
Nick Desaulniers, Nicolin Chen, Oliver Upton, Paolo Bonzini,
Pratyush Yadav, Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba, kvm,
linux-doc, linux-fsdevel, linux-kernel, linux-kselftest, linux-mm,
llvm
> + if (kvm_gmem_is_pfn_mode(gi)) {
> + r = gmem_provider_get_pfn(gi, index, pfn, max_order);
> + if (r)
> + return ERR_PTR(r);
> + return NULL;
> + }
The order goes from the provider to the fault handler unchecked. MPS
reports 0, but a provider with contiguous memory won't, and then a
fault can map a block that the gfn and pfn aren't aligned to, one that
mixes shared and private pages, or one that covers a page being revoked
or converted at the same time: only that page's gfn is marked as being
invalidated, so the fault doesn't retry.
Clamping the order in kvm_gmem_get_pfn() to an aligned block of uniform
attributes, and widening invalidations to the surrounding blocks, would
cover it.
Fred
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 32/50] KVM: guest_memfd: Refactor error handling in kvm_gmem_fault_user_mapping
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (30 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 33/50] KVM: guest_memfd: Support mmap and user fault handling in PFN mode Ackerley Tng via B4 Relay
` (18 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Track the status of kvm_gmem_fault_user_mapping() using an explicit error
code rather than encoding errors into the folio pointer.
This moves away from relying on the folio pointer holding error states,
such as synthesizing ERR_PTR(-EACCES) when memory is not shared only to
extract PTR_ERR afterwards, and prepares kvm_gmem_fault_user_mapping() for
PFN mode in the next commit, where no folio is returned on success.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 3d89bc76f3867..8588912cb0b82 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -562,24 +562,28 @@ static bool kvm_gmem_supports_mmap(struct inode *inode)
static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
{
struct inode *inode = file_inode(vmf->vma->vm_file);
- struct folio *folio;
vm_fault_t ret = VM_FAULT_LOCKED;
+ struct folio *folio = NULL;
+ int r = 0;
if (((loff_t)vmf->pgoff << PAGE_SHIFT) >= i_size_read(inode))
return VM_FAULT_SIGBUS;
filemap_invalidate_lock_shared(inode->i_mapping);
- if (kvm_gmem_is_shared_mem(inode, vmf->pgoff))
+ if (!kvm_gmem_is_shared_mem(inode, vmf->pgoff)) {
+ r = -EACCES;
+ } else {
folio = kvm_gmem_get_folio(inode, vmf->pgoff);
- else
- folio = ERR_PTR(-EACCES);
+ if (IS_ERR(folio))
+ r = PTR_ERR(folio);
+ }
filemap_invalidate_unlock_shared(inode->i_mapping);
- if (IS_ERR(folio)) {
- if (PTR_ERR(folio) == -EAGAIN)
+ if (r) {
+ if (r == -EAGAIN)
return VM_FAULT_RETRY;
- return vmf_error(PTR_ERR(folio));
+ return vmf_error(r);
}
if (WARN_ON_ONCE(folio_test_large(folio))) {
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 33/50] KVM: guest_memfd: Support mmap and user fault handling in PFN mode
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (31 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 32/50] KVM: guest_memfd: Refactor error handling in kvm_gmem_fault_user_mapping Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 34/50] KVM: guest_memfd: Skip checking outstanding references " Ackerley Tng via B4 Relay
` (17 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
In PFN mode, guest_memfd memory is backed by raw PFNs rather than
page-cache folios.
Mark the VMA with VM_PFNMAP when setting up the VMA in PFN mode, and
insert the PFN into the user page tables during fault handling.
In folio mode, the fault handler obtains a locked folio under the
shared filemap invalidation lock and returns with the folio still
locked so the core fault handler installs the PTE before unlocking the
folio. Any concurrent truncation or invalidation must wait on the
folio lock before unmapping and removing the folio.
In PFN mode, there is no folio lock to bridge the gap after dropping
the shared filemap invalidation lock. If the lock were dropped before
inserting the PFN into the page tables, a concurrent hole punch or
provider-driven revocation could acquire the exclusive filemap
invalidation lock, unmap existing mappings (missing the not-yet-mapped
PTE), and reclaim the backing memory, only for the fault handler to
subsequently install a stale PTE pointing to the revoked PFN.
Keep the shared filemap invalidation lock held across PFN insertion so
that obtaining the PFN and installing the PTE are atomic with respect
to operations holding the exclusive filemap invalidation lock. For
provider-driven revocation to safely tear down mappings without races,
the provider must first mark the range unavailable so new faults fail,
and then invoke revocation, which acquires the exclusive filemap
invalidation lock to wait for any in-flight faults to finish installing
their PTEs before unmapping them. This also requires providers not to
hold any lock across revocation that is also acquired during PFN
lookup.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 8588912cb0b82..50031e191968b 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -562,8 +562,10 @@ static bool kvm_gmem_supports_mmap(struct inode *inode)
static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
{
struct inode *inode = file_inode(vmf->vma->vm_file);
+ struct gmem_inode *gi = GMEM_I(inode);
vm_fault_t ret = VM_FAULT_LOCKED;
struct folio *folio = NULL;
+ kvm_pfn_t pfn;
int r = 0;
if (((loff_t)vmf->pgoff << PAGE_SHIFT) >= i_size_read(inode))
@@ -572,6 +574,10 @@ static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
filemap_invalidate_lock_shared(inode->i_mapping);
if (!kvm_gmem_is_shared_mem(inode, vmf->pgoff)) {
r = -EACCES;
+ } else if (kvm_gmem_is_pfn_mode(gi)) {
+ r = gmem_provider_get_pfn(gi, vmf->pgoff, &pfn, NULL);
+ if (!r)
+ ret = vmf_insert_pfn(vmf->vma, vmf->address, pfn);
} else {
folio = kvm_gmem_get_folio(inode, vmf->pgoff);
if (IS_ERR(folio))
@@ -586,6 +592,9 @@ static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
return vmf_error(r);
}
+ if (kvm_gmem_is_pfn_mode(gi))
+ return ret;
+
if (WARN_ON_ONCE(folio_test_large(folio))) {
ret = VM_FAULT_SIGBUS;
goto out_folio;
@@ -646,6 +655,8 @@ static const struct vm_operations_struct kvm_gmem_vm_ops = {
static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma)
{
+ struct gmem_inode *gi = GMEM_I(file_inode(file));
+
if (!kvm_gmem_supports_mmap(file_inode(file)))
return -ENODEV;
@@ -654,6 +665,9 @@ static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma)
return -EINVAL;
}
+ if (kvm_gmem_is_pfn_mode(gi))
+ vm_flags_set(vma, VM_PFNMAP);
+
vma->vm_ops = &kvm_gmem_vm_ops;
return 0;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 34/50] KVM: guest_memfd: Skip checking outstanding references in PFN mode
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (32 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 33/50] KVM: guest_memfd: Support mmap and user fault handling in PFN mode Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-23 17:29 ` Fred Griffoul
2026-10-22 23:28 ` [PATCH RFC v2 35/50] KVM: guest_memfd: Support converting to shared " Ackerley Tng via B4 Relay
` (16 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
When converting memory attributes to private, guest_memfd verifies that
there are no outstanding references to folios in the page cache.
For PFN mode, retain the part where memory is unmapped from userspace,
since userspace should not be able to map private memory.
Since memory was not mapped into userspace using pages, there will not be
any other users that had GUP-ed the memory and there is no need to check
for outstanding references (that indicate other users of the memory).
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 50031e191968b..daf4541c3dec6 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -854,7 +854,8 @@ static int __kvm_gmem_set_attributes(struct inode *inode, pgoff_t start,
*/
unmap_mapping_pages(mapping, start, nr_pages, false);
- if (kvm_gmem_has_outstanding_references(inode, start, nr_pages,
+ if (!kvm_gmem_is_pfn_mode(gi) &&
+ kvm_gmem_has_outstanding_references(inode, start, nr_pages,
err_index)) {
mas_destroy(&mas);
r = -EAGAIN;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 34/50] KVM: guest_memfd: Skip checking outstanding references in PFN mode
2026-10-22 23:28 ` [PATCH RFC v2 34/50] KVM: guest_memfd: Skip checking outstanding references " Ackerley Tng via B4 Relay
@ 2026-10-23 17:29 ` Fred Griffoul
0 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-23 17:29 UTC (permalink / raw)
To: Ackerley Tng
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H. Peter Anvin, Hugh Dickins,
Ingo Molnar, Jacob Pan, James Houghton, Jan Kara, Jason Gunthorpe,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Kevin Tian, Lorenzo Stoakes,
Marc Zyngier, Michael Roth, Mike Rapoport, Nathan Chancellor,
Nick Desaulniers, Nicolin Chen, Oliver Upton, Paolo Bonzini,
Pratyush Yadav, Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba, kvm,
linux-doc, linux-fsdevel, linux-kernel, linux-kselftest, linux-mm,
llvm
> + if (!kvm_gmem_is_pfn_mode(gi) &&
> + kvm_gmem_has_outstanding_references(inode, start, nr_pages,
> err_index)) {
In folio mode this check is also what guarantees that a private page
belongs to the guest alone. In PFN mode there is nothing like it: if
the provider also exposes the frame somewhere else, say a ring shared
with a device, the VMM can still make it private.
And it is not only this guest that suffers then. Whoever else uses the
frame breaks: an RMP fault on SEV-SNP, a poisoned page or a machine
check on TDX, a granule protection fault on CCA, or, with
SW_PROTECTED, silent access to "private" memory.
Only the provider knows about such frames. With an attributes output
from get_pfn() (see my reply to 31/50), it could flag them as aliased,
and guest_memfd would keep them shared: refuse the conversion, treat a
private fault as a hole, and refuse populate.
Fred
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 35/50] KVM: guest_memfd: Support converting to shared in PFN mode
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (33 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 34/50] KVM: guest_memfd: Skip checking outstanding references " Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 36/50] KVM: guest_memfd: Support preallocating memory via fallocate " Ackerley Tng via B4 Relay
` (15 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
When converting memory attributes to shared, guest_memfd iterates
through folios in the page cache to notify architecture code.
In PFN mode, memory is not backed by page cache folios. To notify
architecture code without unintentionally triggering memory allocation
in dynamic providers for unpopulated ranges, introduce the
lookup_pfn_range provider operation. It returns the PFN and number of
pages of contiguous backing memory, or -ENOENT if not allocated.
Add support for PFN mode when converting to shared by querying
lookup_pfn_range and converting contiguous chunks.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
include/linux/guest_memfd.h | 13 ++++++++++++-
virt/kvm/guest_memfd.c | 41 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index 10c48f41a6d7e..2f1c679dd154c 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -23,7 +23,16 @@ struct mempolicy;
* zeroing if necessary. On success, return 0 with the physical frame
* number in @pfn and, if @max_order is non-NULL, the maximum page
* order of contiguous backing memory containing @pfn. Returns a
- * negative error code on failure.
+ * negative error code on failure. The provider must define
+ * @lookup_pfn_range when @get_pfn is defined.
+ * @lookup_pfn_range: Look up the PFN for a given index. Returns 0 on success
+ * with the physical frame number in @pfn and the number of
+ * contiguous pages the mapping is valid for in @nr_pages.
+ * The provider does not need to ensure that backing memory
+ * 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.
*
* Used by filesystems and device drivers that provide memory for guest_memfd.
*/
@@ -35,6 +44,8 @@ struct guest_memfd_provider_operations {
void (*invalidate_folio)(void *provider, struct folio *folio);
int (*get_pfn)(void *provider, pgoff_t index, unsigned long *pfn,
int *max_order);
+ int (*lookup_pfn_range)(void *provider, pgoff_t index,
+ unsigned long *pfn, size_t *nr_pages);
};
#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index daf4541c3dec6..734107dad63ee 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -143,6 +143,27 @@ static int gmem_provider_get_pfn(struct gmem_inode *gi, pgoff_t index,
return 0;
}
+#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT
+static int gmem_provider_lookup_pfn_range(struct gmem_inode *gi, pgoff_t index,
+ kvm_pfn_t *pfn, size_t *nr_pages)
+{
+ unsigned long raw_pfn;
+ int r;
+
+ r = gi->provider_ops->lookup_pfn_range(gi->provider, index, &raw_pfn,
+ nr_pages);
+ if (r)
+ return r;
+
+ /* *nr_pages must be > 0 if there is no error. */
+ if (WARN_ON_ONCE(!*nr_pages))
+ return -EINVAL;
+
+ *pfn = raw_pfn;
+ return 0;
+}
+#endif
+
static void gmem_provider_invalidate_folio(struct gmem_inode *gi,
struct folio *folio)
{
@@ -783,10 +804,30 @@ static bool kvm_gmem_has_outstanding_references(struct inode *inode,
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT
static void kvm_gmem_make_shared(struct inode *inode, pgoff_t start, pgoff_t end)
{
+ struct gmem_inode *gi = GMEM_I(inode);
struct folio_batch fbatch;
pgoff_t next = start;
int i;
+ if (kvm_gmem_is_pfn_mode(gi)) {
+ pgoff_t index;
+
+ for (index = start; index < end; ) {
+ size_t step = 1;
+ size_t nr_pages;
+ kvm_pfn_t pfn;
+
+ if (!gmem_provider_lookup_pfn_range(gi, index, &pfn, &nr_pages)) {
+ step = min_t(size_t, nr_pages, end - index);
+ kvm_arch_gmem_make_shared(pfn, step);
+ }
+
+ index += step;
+ cond_resched();
+ }
+ return;
+ }
+
folio_batch_init(&fbatch);
while (filemap_get_folios(inode->i_mapping, &next, end - 1, &fbatch)) {
for (i = 0; i < folio_batch_count(&fbatch); ++i) {
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 36/50] KVM: guest_memfd: Support preallocating memory via fallocate in PFN mode
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (34 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 35/50] KVM: guest_memfd: Support converting to shared " Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 37/50] KVM: guest_memfd: Clamp hole punch range to inode size Ackerley Tng via B4 Relay
` (14 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
In PFN mode, memory is not backed by page cache folios, so attempting
to allocate folios during fallocate fails.
Support preallocating backing memory in PFN mode by querying the
provider for PFNs across the requested range. Step forward by the
contiguous order reported by the provider so that large contiguous
allocations do not require redundant per-page calls.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 734107dad63ee..f0c5162cc7a7f 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -405,6 +405,7 @@ static long kvm_gmem_punch_hole(struct inode *inode, loff_t offset, loff_t len)
static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
{
struct address_space *mapping = inode->i_mapping;
+ struct gmem_inode *gi = GMEM_I(inode);
pgoff_t start, index, end;
int r;
@@ -418,6 +419,30 @@ static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
end = (offset + len) >> PAGE_SHIFT;
r = 0;
+ if (kvm_gmem_is_pfn_mode(gi)) {
+ for (index = start; index < end; ) {
+ pgoff_t next_boundary;
+ pgoff_t max_nr_pages;
+ int max_order = 0;
+ kvm_pfn_t pfn;
+
+ if (signal_pending(current)) {
+ r = -EINTR;
+ break;
+ }
+
+ r = gmem_provider_get_pfn(gi, index, &pfn, &max_order);
+ if (r)
+ break;
+
+ max_nr_pages = 1UL << max_order;
+ next_boundary = ALIGN_DOWN(index + max_nr_pages, max_nr_pages);
+ index += min_t(size_t, next_boundary - index, end - index);
+ cond_resched();
+ }
+ goto out_unlock;
+ }
+
for (index = start; index < end; ) {
struct folio *folio;
@@ -444,6 +469,7 @@ static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
cond_resched();
}
+out_unlock:
filemap_invalidate_unlock_shared(mapping);
return r;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 37/50] KVM: guest_memfd: Clamp hole punch range to inode size
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (35 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 36/50] KVM: guest_memfd: Support preallocating memory via fallocate " Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 38/50] KVM: guest_memfd: Support reclaim during hole punch in PFN mode Ackerley Tng via B4 Relay
` (13 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
When userspace punches a hole via fallocate, the VFS only validates that
the offset and length do not overflow the superblock maximum file size,
allowing ranges that extend past the end of the guest_memfd inode.
In folio mode, truncating page cache folios past the end of the inode is a
fast no-op because the page cache xarray has no entries beyond the inode
size.
Prepare for PFN mode by clamping the hole punch range to the inode size and
returning early if the starting offset is at or beyond the end of the file.
This avoids, in PFN mode, iterating across a potentially huge punched range
where the provider isn't even providing that memory to guest_memfd.
For folios, no functional change intended.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index f0c5162cc7a7f..206b90646c743 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -382,8 +382,15 @@ static void kvm_gmem_invalidate_end(struct inode *inode, pgoff_t start,
static long kvm_gmem_punch_hole(struct inode *inode, loff_t offset, loff_t len)
{
enum kvm_gfn_range_filter filter = kvm_gmem_get_all_gfns_filter(inode);
- pgoff_t start = offset >> PAGE_SHIFT;
- pgoff_t end = (offset + len) >> PAGE_SHIFT;
+ loff_t size = i_size_read(inode);
+ pgoff_t start, end;
+
+ if (offset >= size)
+ return 0;
+
+ len = min(len, size - offset);
+ start = offset >> PAGE_SHIFT;
+ end = (offset + len) >> PAGE_SHIFT;
/*
* Bindings must be stable across invalidation to ensure the start+end
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 38/50] KVM: guest_memfd: Support reclaim during hole punch in PFN mode
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (36 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 37/50] KVM: guest_memfd: Clamp hole punch range to inode size Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 39/50] KVM: guest_memfd: Support arch reclaim on inode destruction " Ackerley Tng via B4 Relay
` (12 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
In PFN mode, guest_memfd does not allocate or insert folios into the page
cache (inode->i_mapping). Calling truncate_inode_pages_range() is therefore
unnecessary.
Instead, call unmap_mapping_range() when in PFN mode to unmap any host
userspace PTEs over the punched range, while retaining
kvm_gmem_invalidate_start() and kvm_gmem_invalidate_end() to zap stage-2
KVM SPTEs.
Reclaim any confidential architecture state (e.g. SEV-SNP RMP) on provider
PFNs across the punched range via kvm_gmem_pfn_reclaim(), querying
.lookup_pfn_range() to skip unallocated holes without allocating memory.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 32 ++++++++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 206b90646c743..0819c3653e88a 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -143,7 +143,7 @@ static int gmem_provider_get_pfn(struct gmem_inode *gi, pgoff_t index,
return 0;
}
-#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT
+#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)
{
@@ -379,6 +379,29 @@ static void kvm_gmem_invalidate_end(struct inode *inode, pgoff_t start,
__kvm_gmem_invalidate_end(f, start, end);
}
+#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
+static void kvm_gmem_pfn_reclaim(struct gmem_inode *gi, pgoff_t start, pgoff_t end)
+{
+ pgoff_t index;
+
+ for (index = start; index < end; ) {
+ size_t step = 1;
+ size_t nr_pages;
+ kvm_pfn_t pfn;
+
+ if (!gmem_provider_lookup_pfn_range(gi, index, &pfn, &nr_pages)) {
+ step = min_t(size_t, nr_pages, end - index);
+ kvm_arch_gmem_reclaim(pfn, step);
+ }
+
+ index += step;
+ cond_resched();
+ }
+}
+#else
+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)
{
enum kvm_gfn_range_filter filter = kvm_gmem_get_all_gfns_filter(inode);
@@ -400,7 +423,12 @@ static long kvm_gmem_punch_hole(struct inode *inode, loff_t offset, loff_t len)
kvm_gmem_invalidate_start(inode, start, end, filter);
- truncate_inode_pages_range(inode->i_mapping, offset, offset + len - 1);
+ 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);
+ } else {
+ truncate_inode_pages_range(inode->i_mapping, offset, offset + len - 1);
+ }
kvm_gmem_invalidate_end(inode, start, end);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 39/50] KVM: guest_memfd: Support arch reclaim on inode destruction in PFN mode
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (37 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 38/50] KVM: guest_memfd: Support reclaim during hole punch in PFN mode Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 40/50] KVM: guest_memfd: Support put_pfn_range provider op " Ackerley Tng via B4 Relay
` (11 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
In folio mode, folios truncated on inode destruction are reclaimed via
aops->free_folio (kvm_gmem_free_folio), calling kvm_arch_gmem_reclaim()
to reset hardware confidential VM state (e.g. SEV-SNP RMP entries).
In PFN mode, memory is not backed by page cache folios, so
kvm_gmem_free_folio() is never invoked.
Do the reclaim during kvm_gmem_destroy_inode() when in PFN mode across the
entire file range, ensuring that any confidential architecture state on
provider PFNs is reclaimed before releasing the provider.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 0819c3653e88a..88eae9d6d249f 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -1643,6 +1643,13 @@ static void kvm_gmem_destroy_inode(struct inode *inode)
{
struct gmem_inode *gi = GMEM_I(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);
+
gmem_provider_release(gi);
mpol_free_shared_policy(&gi->policy);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 40/50] KVM: guest_memfd: Support put_pfn_range provider op in PFN mode
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (38 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 39/50] KVM: guest_memfd: Support arch reclaim on inode destruction " Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 41/50] KVM: guest_memfd: Add TODO for memory failure handling " Ackerley Tng via B4 Relay
` (10 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
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@google.com>
---
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
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 41/50] KVM: guest_memfd: Add TODO for memory failure handling in PFN mode
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (39 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 40/50] KVM: guest_memfd: Support put_pfn_range provider op " Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 42/50] KVM: guest_memfd: Allow attaching PFN-backed providers Ackerley Tng via B4 Relay
` (9 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add a TODO comment noting that PFN mode does not use page-cache folios, so
->error_remove_folio() will not be called by the core memory failure
infrastructure when physical memory errors occur.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
virt/kvm/guest_memfd.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 5741ff6ab94c6..c89df77df7146 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -1068,6 +1068,11 @@ static int kvm_gmem_migrate_folio(struct address_space *mapping,
return -EINVAL;
}
+/*
+ * TODO: Support memory failure notification for PFN mode. In PFN mode, memory
+ * is not backed by page cache folios, so ->error_remove_folio() will not be
+ * invoked by the core memory failure handler.
+ */
static int kvm_gmem_error_folio(struct address_space *mapping, struct folio *folio)
{
struct inode *inode = mapping->host;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 42/50] KVM: guest_memfd: Allow attaching PFN-backed providers
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (40 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 41/50] KVM: guest_memfd: Add TODO for memory failure handling " Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 43/50] KVM: guest_memfd: Export kvm_gmem_revoke for provider-driven revocation Ackerley Tng via B4 Relay
` (8 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Expose GUEST_MEMFD_FLAG_PFN_MODE in supported flags to allow userspace
to explicitly request PFN mode when creating a guest_memfd instance with
GUEST_MEMFD_FLAG_USE_RESOURCE.
PFN mode is only valid when backed by an external resource provider, as
native guest_memfd relies on the page cache and manages memory as folios.
Reject creation if PFN mode is requested without an external resource.
While guest_memfd could try to infer PFN mode from whether PFN lookup
callbacks exist, an explicit creation flag ensures userspace selects the
operating mode directly, allowing providers to support both modes.
Validate the requested operating mode against the provider operations
when attaching a resource:
- In PFN mode, the provider must implement both .get_pfn and
.lookup_pfn_range.
- In folio mode, the provider must implement .alloc_folio.
Document the new flag and its requirements in the KVM API documentation.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
Documentation/virt/kvm/api.rst | 6 ++++++
include/linux/kvm_host.h | 3 ++-
virt/kvm/guest_memfd.c | 18 +++++++++++++++++-
3 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 9f5313d405d9c..bb550e4f11cd8 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6524,6 +6524,12 @@ specified via KVM_CREATE_GUEST_MEMFD. Currently defined flags:
must be a supported resource (e.g. tmpfs mount
root directory). Currently, tmpfs resource pools
require noswap and huge=never mount options.
+ GUEST_MEMFD_FLAG_PFN_MODE Request PFN mode for guest_memfd backed
+ by an external resource provider. When set,
+ GUEST_MEMFD_FLAG_USE_RESOURCE must also be set,
+ and the provider must support PFN lookup
+ (.get_pfn and .lookup_pfn_range) rather than
+ folio allocation (.alloc_folio).
============================= ================================================
When the KVM MMU performs a PFN lookup to service a guest fault, the fault will
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index d820afac49252..345f97b110fa6 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2618,7 +2618,8 @@ bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
{
- u64 flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_USE_RESOURCE;
+ u64 flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_USE_RESOURCE |
+ GUEST_MEMFD_FLAG_PFN_MODE;
if (!kvm || kvm_arch_supports_gmem_init_shared(kvm))
flags |= GUEST_MEMFD_FLAG_INIT_SHARED;
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index c89df77df7146..a4519f3b9eadb 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -85,6 +85,18 @@ static const struct guest_memfd_provider_operations gmem_native_provider_ops = {
.alloc_folio = gmem_native_alloc_folio,
};
+static bool gmem_provider_ops_valid(struct gmem_inode *gi,
+ const struct guest_memfd_provider_operations *ops)
+{
+ if (!ops || !ops->attach)
+ return false;
+
+ if (kvm_gmem_is_pfn_mode(gi))
+ return ops->get_pfn && ops->lookup_pfn_range;
+
+ return ops->alloc_folio != NULL;
+}
+
#define GMEM_RESOURCE_FD_NATIVE (-1)
static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)
@@ -104,7 +116,7 @@ static int kvm_gmem_attach_resource(struct inode *inode, int resource_fd)
ops = resource_file->f_op->gmem_provider_ops;
}
- if (ops && ops->attach && ops->alloc_folio)
+ if (gmem_provider_ops_valid(GMEM_I(inode), ops))
provider = ops->attach(inode, resource_file);
if (resource_file)
@@ -1291,6 +1303,10 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
if (size <= 0 || !PAGE_ALIGNED(size))
return -EINVAL;
+ if (flags & GUEST_MEMFD_FLAG_PFN_MODE &&
+ !(flags & GUEST_MEMFD_FLAG_USE_RESOURCE))
+ return -EINVAL;
+
if (flags & GUEST_MEMFD_FLAG_USE_RESOURCE) {
if (args->resource_fd < 0)
return -EBADF;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 43/50] KVM: guest_memfd: Export kvm_gmem_revoke for provider-driven revocation
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (41 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 42/50] KVM: guest_memfd: Allow attaching PFN-backed providers Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 44/50] KVM: selftests: Test flag dependency of PFN_MODE on USE_RESOURCE Ackerley Tng via B4 Relay
` (7 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
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@google.com>
---
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
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 44/50] KVM: selftests: Test flag dependency of PFN_MODE on USE_RESOURCE
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (42 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 43/50] KVM: guest_memfd: Export kvm_gmem_revoke for provider-driven revocation Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 45/50] samples/kvm: Implement attach and release provider ops in sample provider Ackerley Tng via B4 Relay
` (6 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Creating a guest_memfd with GUEST_MEMFD_FLAG_PFN_MODE requires
GUEST_MEMFD_FLAG_USE_RESOURCE.
Skip testing GUEST_MEMFD_FLAG_PFN_MODE as an isolated flag in
test_guest_memfd_flags() because creating a guest_memfd with PFN_MODE
alone without USE_RESOURCE is rejected by KVM with EINVAL.
In test_resource_fd_invalid(), verify that attempting to create a
guest_memfd with GUEST_MEMFD_FLAG_PFN_MODE without
GUEST_MEMFD_FLAG_USE_RESOURCE fails with EINVAL.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/include/uapi/linux/kvm.h | 1 +
tools/testing/selftests/kvm/guest_memfd_test.c | 8 +++++++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index 66c1f40b50c29..3ef7bc37a7070 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1655,6 +1655,7 @@ struct kvm_memory_attributes {
#define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
#define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1)
#define GUEST_MEMFD_FLAG_USE_RESOURCE (1ULL << 2)
+#define GUEST_MEMFD_FLAG_PFN_MODE (1ULL << 3)
struct kvm_create_guest_memfd {
__u64 size;
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index cbc2f309664b2..fb9d50be2e3b6 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -407,7 +407,8 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
int fd;
for (flag = BIT(0); flag; flag <<= 1) {
- if (flag == GUEST_MEMFD_FLAG_USE_RESOURCE)
+ if (flag == GUEST_MEMFD_FLAG_USE_RESOURCE ||
+ flag == GUEST_MEMFD_FLAG_PFN_MODE)
continue;
fd = __vm_create_guest_memfd(vm, page_size, flag);
if (flag & valid_flags) {
@@ -432,6 +433,11 @@ static void test_resource_fd_invalid(struct kvm_vm *vm)
TEST_ASSERT(fd < 0, "guest_memfd with resource_fd but without flag should fail");
TEST_ASSERT_EQ(errno, EINVAL);
+ /* PFN_MODE without GUEST_MEMFD_FLAG_USE_RESOURCE fails */
+ fd = __vm_create_guest_memfd(vm, page_size, GUEST_MEMFD_FLAG_PFN_MODE);
+ TEST_ASSERT(fd < 0, "guest_memfd with PFN_MODE without USE_RESOURCE should fail");
+ TEST_ASSERT_EQ(errno, EINVAL);
+
/* Bad file descriptor with GUEST_MEMFD_FLAG_USE_RESOURCE fails */
fd = __vm_create_guest_memfd_resource(vm, page_size,
GUEST_MEMFD_FLAG_USE_RESOURCE, -1);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 45/50] samples/kvm: Implement attach and release provider ops in sample provider
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (43 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 44/50] KVM: selftests: Test flag dependency of PFN_MODE on USE_RESOURCE Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-23 23:32 ` sashiko-bot
2026-10-22 23:28 ` [PATCH RFC v2 46/50] samples/kvm: Implement get_pfn provider operation Ackerley Tng via B4 Relay
` (5 subsequent siblings)
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Implement the guest_memfd provider operations in the sample memory
provider module, providing .attach and .release callbacks.
In .attach, take a reference to the provider file (get_file) to keep the
provider context alive while attached. In .release, release the file
reference via fput.
Attach the provider operations to mps_file_fops.gmem_provider_ops so that
provider file descriptors created by the sample module can be passed as
resource_fd to guest_memfd.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 397ebb3773f39..d9e57484c3933 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -17,6 +17,7 @@
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/gfp.h>
+#include <linux/guest_memfd.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
@@ -118,11 +119,30 @@ static long mps_file_ioctl(struct file *file, unsigned int cmd,
}
}
+static void *mps_gmem_attach(struct inode *gmem_inode,
+ struct file *resource_file)
+{
+ return get_file(resource_file);
+}
+
+static void mps_gmem_release(void *provider)
+{
+ struct file *resource_file = provider;
+
+ fput(resource_file);
+}
+
+static const struct guest_memfd_provider_operations mps_gmem_provider_ops = {
+ .attach = mps_gmem_attach,
+ .release = mps_gmem_release,
+};
+
static const struct file_operations mps_file_fops = {
.owner = THIS_MODULE,
.release = mps_file_release,
.unlocked_ioctl = mps_file_ioctl,
.compat_ioctl = compat_ptr_ioctl,
+ .gmem_provider_ops = &mps_gmem_provider_ops,
};
static long mps_ctl_new_file(void __user *uarg)
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 45/50] samples/kvm: Implement attach and release provider ops in sample provider
2026-10-22 23:28 ` [PATCH RFC v2 45/50] samples/kvm: Implement attach and release provider ops in sample provider Ackerley Tng via B4 Relay
@ 2026-10-23 23:32 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:32 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] samples/kvm: build failure when CONFIG_KVM_GUEST_MEMFD is disabled
--
commit da89517936ff62d4ed67bf323c9cade71c875336
Author: Ackerley Tng <ackerleytng@google.com>
samples/kvm: Implement attach and release provider ops in sample provider
This commit implements the .attach and .release guest_memfd provider
operations in the sample memory provider module and wires them up to
mps_file_fops.gmem_provider_ops.
> diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
> index 397ebb3773f39..d9e57484c3933 100644
> --- a/samples/kvm/mem_provider_sample.c
> +++ b/samples/kvm/mem_provider_sample.c
[ ... ]
> @@ -118,11 +119,30 @@ static long mps_file_ioctl(struct file *file, unsigned int cmd,
> }
> }
>
> +static void *mps_gmem_attach(struct inode *gmem_inode,
> + struct file *resource_file)
> +{
> + return get_file(resource_file);
> +}
> +
> +static void mps_gmem_release(void *provider)
> +{
> + struct file *resource_file = provider;
> +
> + fput(resource_file);
> +}
> +
> +static const struct guest_memfd_provider_operations mps_gmem_provider_ops = {
> + .attach = mps_gmem_attach,
> + .release = mps_gmem_release,
> +};
> +
> static const struct file_operations mps_file_fops = {
> .owner = THIS_MODULE,
> .release = mps_file_release,
> .unlocked_ioctl = mps_file_ioctl,
> .compat_ioctl = compat_ptr_ioctl,
> + .gmem_provider_ops = &mps_gmem_provider_ops,
> };
[Severity: Low]
Does initializing .gmem_provider_ops in mps_file_fops in
samples/kvm/mem_provider_sample.c cause a build regression when
CONFIG_SAMPLE_KVM_MEM_PROVIDER=m is enabled while CONFIG_KVM_GUEST_MEMFD is
disabled?
In include/linux/fs.h, gmem_provider_ops in struct file_operations is guarded
by CONFIG_KVM_GUEST_MEMFD:
#ifdef CONFIG_KVM_GUEST_MEMFD
const struct guest_memfd_provider_operations *gmem_provider_ops;
#endif
However, at this point in the series, SAMPLE_KVM_MEM_PROVIDER in
samples/Kconfig only depends on CONTIG_ALLOC && X86_64 && m:
config SAMPLE_KVM_MEM_PROVIDER
tristate "Build sample memory provider -- loadable module only"
depends on CONTIG_ALLOC && X86_64 && m
A later commit in this series, 279ad9d61dfc ("samples/kvm: Support dynamic
revocation via kvm_gmem_revoke"), adds && KVM_GUEST_MEMFD to
SAMPLE_KVM_MEM_PROVIDER in samples/Kconfig. Should that Kconfig dependency be
moved to this commit to avoid breaking bisection when CONFIG_KVM_GUEST_MEMFD
is not enabled?
>
> static long mps_ctl_new_file(void __user *uarg)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=45
^ permalink raw reply [flat|nested] 79+ messages in thread
* [PATCH RFC v2 46/50] samples/kvm: Implement get_pfn provider operation
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (44 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 45/50] samples/kvm: Implement attach and release provider ops in sample provider Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 47/50] samples/kvm: Implement put_pfn_range " Ackerley Tng via B4 Relay
` (4 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Implement the .get_pfn callback in mps_gmem_provider_ops to operate as a
direct PFN provider for guest_memfd.
Implementing .get_pfn satisfies guest_memfd's provider operations
requirement, enabling mem_provider_sample files to be attached as
resource_fd memory providers for guest_memfd.
In mps_gmem_get_pfn(), return the physical frame number corresponding to
the requested index from the CMA reservation, returning -EFAULT if the
page was marked absent via MPS_SET_PRESENT.
Checking test_bit(index, info->absent) is done locklessly without
holding info->lock:
- test_bit() is an atomic memory read.
- Locklessness prevents lock inversion and deadlocks. In guest_memfd,
the guest page fault path (kvm_gmem_get_pfn()) holds
filemap_invalidate_lock_shared while calling .get_pfn(). Avoiding
info->lock in .get_pfn() ensures no lock inversions exist between
filemap_invalidate_lock and info->lock.
- Similarly, while kvm_gmem_revoke() now takes an explicit pfn and avoids
calling back into .get_pfn(), avoiding info->lock in .get_pfn() keeps
the lookup path completely lock-free and robust.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index d9e57484c3933..a43c76d2259e7 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -132,9 +132,29 @@ static void mps_gmem_release(void *provider)
fput(resource_file);
}
+static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
+ int *max_order)
+{
+ struct file *resource_file = provider;
+ struct mps_info *info = resource_file->private_data;
+
+ if (index >= info->npages)
+ return -EINVAL;
+
+ if (test_bit(index, info->absent))
+ return -EFAULT;
+
+ *pfn = info->base_pfn + index;
+ if (max_order)
+ *max_order = 0;
+
+ return 0;
+}
+
static const struct guest_memfd_provider_operations mps_gmem_provider_ops = {
.attach = mps_gmem_attach,
.release = mps_gmem_release,
+ .get_pfn = mps_gmem_get_pfn,
};
static const struct file_operations mps_file_fops = {
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 47/50] samples/kvm: Implement put_pfn_range provider operation
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (45 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 46/50] samples/kvm: Implement get_pfn provider operation Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 48/50] samples/kvm: Support dynamic revocation via kvm_gmem_revoke Ackerley Tng via B4 Relay
` (3 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
When guest_memfd hole punches or destroys an instance backed by a PFN
provider, it invokes .put_pfn_range to release the physical memory back
to the provider.
Implement the .put_pfn_range callback in mps_gmem_provider_ops to clear
the CMA pages across the released range via clear_highpage(). This
ensures that subsequent accesses to punched ranges observe zeroed memory,
matching POSIX fallocate hole-punching semantics and preventing stale data
from being observed when pages are reused.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index a43c76d2259e7..dee15179959b2 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -17,6 +17,7 @@
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/gfp.h>
+#include <linux/highmem.h>
#include <linux/guest_memfd.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
@@ -40,6 +41,21 @@ struct mps_info {
unsigned long *absent; /* taken away by MPS_SET_PRESENT */
};
+static void mps_gmem_put_pfn_range(void *provider, pgoff_t index,
+ size_t nr_pages)
+{
+ struct file *resource_file = provider;
+ struct mps_info *info = resource_file->private_data;
+ size_t i;
+
+ if (index >= info->npages)
+ return;
+
+ nr_pages = min_t(size_t, nr_pages, info->npages - index);
+ for (i = 0; i < nr_pages; i++)
+ clear_highpage(info->cma_pages + index + i);
+}
+
/* Byte range [offset, offset + len) of @info as page indices. */
static int mps_range(struct mps_info *info, u64 offset, u64 len,
unsigned long *start, unsigned long *end)
@@ -155,6 +171,7 @@ static const struct guest_memfd_provider_operations mps_gmem_provider_ops = {
.attach = mps_gmem_attach,
.release = mps_gmem_release,
.get_pfn = mps_gmem_get_pfn,
+ .put_pfn_range = mps_gmem_put_pfn_range,
};
static const struct file_operations mps_file_fops = {
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 48/50] samples/kvm: Support dynamic revocation via kvm_gmem_revoke
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (46 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 47/50] samples/kvm: Implement put_pfn_range " Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 49/50] samples/kvm: Implement lookup_pfn_range provider operation Ackerley Tng via B4 Relay
` (2 subsequent siblings)
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Support dynamic revocation in the sample memory provider module by
calling kvm_gmem_revoke() when pages are marked absent via
MPS_SET_PRESENT.
Note down the attaching gmem inode during .attach() and disallow double
attachment to the same provider file.
Clear the revoked pages after kvm_gmem_revoke() using mps_zero_range()
to ensure that stale data is wiped and cannot be observed when pages are
later marked present or reused.
Regarding locking and ordering:
- In mps_set_present(), mark the absent bitmap first before revoking so
any concurrent page fault immediately sees the page as absent and
rejects the lookup with -EFAULT.
- kvm_gmem_revoke() is called under info->lock to fully serialize
MPS_SET_PRESENT operations against each other and prevent races
between marking pages absent and present.
- The PFN passed to kvm_gmem_revoke() is derived from info->base_pfn +
start. The mapping between page index and physical PFN is static and
immutable for the lifetime of the provider file, ensuring that the
pfn mapping remains completely stable across the revoke call.
- Passing pfn directly to kvm_gmem_revoke() allows guest_memfd to
reclaim the physical range directly without calling back into the
provider. This keeps the lock hierarchy strictly top-down (info->lock
-> filemap_invalidate_lock -> mmu_lock) with no callbacks or deadlocks.
For now, nothing needs to be done when setting a page present, since users
should (re-)fault and (re-)obtain the PFN via .get_pfn(), to be added in a
later patch.
For users that don't support faulting, like the IOMMU, the counterpart to
kvm_gmem_revoke() will be made available for the first user to indicate
that the memory is now available. guest_memfd should then do the page table
mappings as necessary, based on the shared/private status.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/Kconfig | 2 +-
samples/kvm/mem_provider_sample.c | 42 +++++++++++++++++++++++++++++++++------
2 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/samples/Kconfig b/samples/Kconfig
index 29a0efe49ac3a..1b227eec8bb6d 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -328,7 +328,7 @@ source "samples/damon/Kconfig"
config SAMPLE_KVM_MEM_PROVIDER
tristate "Build sample memory provider -- loadable module only"
- depends on CONTIG_ALLOC && X86_64 && m
+ depends on CONTIG_ALLOC && X86_64 && m && KVM_GUEST_MEMFD
help
This builds a sample memory provider.
Its files can back a guest_memfd and be mapped by iommufd.
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index dee15179959b2..3fa0d1a4335b5 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -39,13 +39,13 @@ struct mps_info {
struct page *cma_pages; /* from alloc_contig_pages() */
unsigned long *absent; /* taken away by MPS_SET_PRESENT */
+
+ struct inode *gmem_inode;
};
-static void mps_gmem_put_pfn_range(void *provider, pgoff_t index,
- size_t nr_pages)
+static void mps_zero_range(struct mps_info *info, pgoff_t index,
+ size_t nr_pages)
{
- struct file *resource_file = provider;
- struct mps_info *info = resource_file->private_data;
size_t i;
if (index >= info->npages)
@@ -56,6 +56,14 @@ static void mps_gmem_put_pfn_range(void *provider, pgoff_t index,
clear_highpage(info->cma_pages + index + i);
}
+static void mps_gmem_put_pfn_range(void *provider, pgoff_t index,
+ size_t nr_pages)
+{
+ struct file *resource_file = provider;
+
+ mps_zero_range(resource_file->private_data, index, nr_pages);
+}
+
/* Byte range [offset, offset + len) of @info as page indices. */
static int mps_range(struct mps_info *info, u64 offset, u64 len,
unsigned long *start, unsigned long *end)
@@ -73,10 +81,17 @@ static void mps_set_present(struct mps_info *info, unsigned long start,
unsigned long end, bool present)
{
mutex_lock(&info->lock);
- if (present)
+ if (present) {
bitmap_clear(info->absent, start, end - start);
- else
+ } else {
bitmap_set(info->absent, start, end - start);
+ if (info->gmem_inode) {
+ unsigned long pfn = info->base_pfn + start;
+
+ kvm_gmem_revoke(info->gmem_inode, start, end - start, &pfn);
+ mps_zero_range(info, start, end - start);
+ }
+ }
mutex_unlock(&info->lock);
}
@@ -138,12 +153,27 @@ static long mps_file_ioctl(struct file *file, unsigned int cmd,
static void *mps_gmem_attach(struct inode *gmem_inode,
struct file *resource_file)
{
+ struct mps_info *info = resource_file->private_data;
+
+ mutex_lock(&info->lock);
+ if (info->gmem_inode) {
+ mutex_unlock(&info->lock);
+ return ERR_PTR(-EBUSY);
+ }
+ info->gmem_inode = gmem_inode;
+ mutex_unlock(&info->lock);
+
return get_file(resource_file);
}
static void mps_gmem_release(void *provider)
{
struct file *resource_file = provider;
+ struct mps_info *info = resource_file->private_data;
+
+ mutex_lock(&info->lock);
+ info->gmem_inode = NULL;
+ mutex_unlock(&info->lock);
fput(resource_file);
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 49/50] samples/kvm: Implement lookup_pfn_range provider operation
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (47 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 48/50] samples/kvm: Support dynamic revocation via kvm_gmem_revoke Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 50/50] KVM: selftests: Test guest_memfd conversions with sample memory provider backing Ackerley Tng via B4 Relay
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
50 siblings, 0 replies; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Implement .lookup_pfn_range to allow looking up index -> pfn mappings to
satisfy guest_memfd requirements and make this sample memory provider a
valid PFN mode provider.
This sample memory provider performs allocations upfront, so
.lookup_pfn_range doesn't do any allocations anyway and is only slightly
different from .get_pfn.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
samples/kvm/mem_provider_sample.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 3fa0d1a4335b5..5496e0c7864b9 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -197,10 +197,28 @@ static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
return 0;
}
+static int mps_gmem_lookup_pfn_range(void *provider, pgoff_t index,
+ unsigned long *pfn, size_t *nr_pages)
+{
+ struct file *resource_file = provider;
+ struct mps_info *info = resource_file->private_data;
+
+ if (index >= info->npages)
+ return -EINVAL;
+
+ if (test_bit(index, info->absent))
+ return -ENOENT;
+
+ *pfn = info->base_pfn + index;
+ *nr_pages = 1;
+ return 0;
+}
+
static const struct guest_memfd_provider_operations mps_gmem_provider_ops = {
.attach = mps_gmem_attach,
.release = mps_gmem_release,
.get_pfn = mps_gmem_get_pfn,
+ .lookup_pfn_range = mps_gmem_lookup_pfn_range,
.put_pfn_range = mps_gmem_put_pfn_range,
};
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* [PATCH RFC v2 50/50] KVM: selftests: Test guest_memfd conversions with sample memory provider backing
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (48 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 49/50] samples/kvm: Implement lookup_pfn_range provider operation Ackerley Tng via B4 Relay
@ 2026-10-22 23:28 ` Ackerley Tng via B4 Relay
2026-10-23 23:32 ` sashiko-bot
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
50 siblings, 1 reply; 79+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-10-22 23:28 UTC (permalink / raw)
To: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-doc, linux-fsdevel, linux-kernel, linux-kselftest,
linux-mm, llvm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
Add fixture variant for the sample memory provider.
If /dev/mem_provider_sample cannot be opened because the module is not
loaded, the mps variant is skipped gracefully via TEST_REQUIRE().
Because PFN-backed memory cannot be pinned via GUP, expect pinning to fail
for the mps variant in the elevated_refcount test.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/include/kvm_util.h | 1 +
tools/testing/selftests/kvm/lib/kvm_util.c | 9 ++++-
.../kvm/x86/guest_memfd_conversions_test.c | 44 +++++++++++++++++++++-
3 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index eca296539db79..1a1387f36a52c 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -1350,6 +1350,7 @@ static inline int pin_self_to_any_cpu(void)
return pin_task_to_any_cpu(pthread_self());
}
+int __pin_pages(void *vaddr, uint64_t size);
void pin_pages(void *vaddr, uint64_t size);
void unpin_pages(void);
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index d42e201eba763..9793818f54d23 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -674,7 +674,7 @@ int __pin_task_to_cpu(pthread_t task, int cpu)
static int gup_test_fd = -1;
-void pin_pages(void *vaddr, uint64_t size)
+int __pin_pages(void *vaddr, uint64_t size)
{
const struct pin_longterm_test args = {
.addr = (uint64_t)vaddr,
@@ -685,7 +685,12 @@ void pin_pages(void *vaddr, uint64_t size)
gup_test_fd = __open_path_or_exit("/sys/kernel/debug/gup_test", O_RDWR,
"Is CONFIG_GUP_TEST enabled?");
- TEST_ASSERT_EQ(ioctl(gup_test_fd, PIN_LONGTERM_TEST_START, &args), 0);
+ return ioctl(gup_test_fd, PIN_LONGTERM_TEST_START, &args);
+}
+
+void pin_pages(void *vaddr, uint64_t size)
+{
+ TEST_ASSERT_EQ(__pin_pages(vaddr, size), 0);
}
void unpin_pages(void)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index 023df9cb9d4a6..b99e4cdbab8e6 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -16,11 +16,13 @@
#include "kselftest_harness.h"
#include "test_util.h"
#include "ucall_common.h"
+#include "../../../../samples/kvm/mem_provider_sample.h"
FIXTURE(gmem_conversions) {
struct kvm_vcpu *vcpu;
int gmem_fd;
int resource_fd;
+ bool expect_pin_failure;
/* HVA of the first byte of the memory mmap()-ed from gmem_fd. */
char *mem;
};
@@ -29,10 +31,14 @@ typedef FIXTURE_DATA(gmem_conversions) test_data_t;
FIXTURE_VARIANT(gmem_conversions) {
int (*create_resource)(size_t size);
+ uint64_t extra_flags;
+ bool expect_pin_failure;
};
FIXTURE_VARIANT_ADD(gmem_conversions, native) {
.create_resource = NULL,
+ .extra_flags = 0,
+ .expect_pin_failure = false,
};
static int create_tmpfs_resource(size_t size)
@@ -42,6 +48,34 @@ static int create_tmpfs_resource(size_t size)
FIXTURE_VARIANT_ADD(gmem_conversions, tmpfs) {
.create_resource = create_tmpfs_resource,
+ .extra_flags = 0,
+ .expect_pin_failure = false,
+};
+
+static int create_mps_resource(size_t size)
+{
+ struct mps_new_file nf = {
+ .size = size,
+ };
+ int dev_fd, fd;
+
+ dev_fd = open("/dev/mem_provider_sample", O_RDWR);
+ if (dev_fd < 0) {
+ TEST_REQUIRE(errno != ENOENT && errno != ENODEV);
+ TEST_FAIL("Failed to open /dev/mem_provider_sample: %d (%s)",
+ errno, strerror(errno));
+ }
+
+ fd = ioctl(dev_fd, MPS_NEW_FILE, &nf);
+ TEST_ASSERT(fd >= 0, "MPS_NEW_FILE failed: %d (%s)", errno, strerror(errno));
+ close(dev_fd);
+ return fd;
+}
+
+FIXTURE_VARIANT_ADD(gmem_conversions, mps) {
+ .create_resource = create_mps_resource,
+ .extra_flags = GUEST_MEMFD_FLAG_PFN_MODE,
+ .expect_pin_failure = true,
};
FIXTURE_SETUP(gmem_conversions) { }
@@ -75,10 +109,12 @@ static void gmem_conversions_do_setup(test_data_t *t,
vm = __vm_create_shape_with_one_vcpu(shape, &t->vcpu, nr_pages, guest_do_rmw);
+ t->expect_pin_failure = variant->expect_pin_failure;
t->resource_fd = -1;
if (variant->create_resource) {
- uint64_t flags = gmem_flags | GUEST_MEMFD_FLAG_USE_RESOURCE;
+ uint64_t flags = gmem_flags | GUEST_MEMFD_FLAG_USE_RESOURCE |
+ variant->extra_flags;
t->resource_fd = variant->create_resource(size);
TEST_ASSERT(t->resource_fd >= 0, "Failed to create backing resource");
@@ -506,6 +542,12 @@ GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED(elevated_refcount, 4)
{
int i;
+ if (t->expect_pin_failure) {
+ TEST_ASSERT(__pin_pages(t->mem + test_page * page_size, page_size) < 0,
+ "Pinning PFN-backed memory should fail");
+ return;
+ }
+
pin_pages(t->mem + test_page * page_size, page_size);
for (i = 0; i < nr_pages; i++)
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 79+ messages in thread* Re: [PATCH RFC v2 50/50] KVM: selftests: Test guest_memfd conversions with sample memory provider backing
2026-10-22 23:28 ` [PATCH RFC v2 50/50] KVM: selftests: Test guest_memfd conversions with sample memory provider backing Ackerley Tng via B4 Relay
@ 2026-10-23 23:32 ` sashiko-bot
0 siblings, 0 replies; 79+ messages in thread
From: sashiko-bot @ 2026-10-23 23:32 UTC (permalink / raw)
To: Ackerley Tng, Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K.V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Fred Griffoul, Fred Griffoul, Gregory Price,
H. Peter Anvin, Hugh Dickins, Ingo Molnar, Jacob Pan,
James Houghton, Jan Kara, Jason Gunthorpe, Jianxiong Gao,
Joerg Roedel, Joey Gouly, John Starks, Jonathan Corbet,
Justin Stitt, Kevin Tian, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Paolo Bonzini, Pratyush Yadav,
Randy Dunlap, Robin Murphy, Samiullah Khawaja,
Sean Christopherson, Shuah Khan, Steffen Eiden, Steven Price,
Suzuki K Poulose, Tarun Sahu, Thomas Gleixner, Tom Lendacky,
Tycho Andersen, Vishal Annapurve, Vlastimil Babka, Will Deacon,
Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan, Fuad Tabba
Cc: kvm, linux-mm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] KVM: selftests: wrong relative path to mem_provider_sample.h
--
commit b071597d5a3f5abd9db2726fb150510172e23946
Author: Ackerley Tng <ackerleytng@google.com>
KVM: selftests: Test guest_memfd conversions with sample memory provider backing
This patch adds a fixture variant to guest_memfd_conversions_test.c to test
guest_memfd conversions backed by the sample memory provider module, skipping
the variant if /dev/mem_provider_sample is unavailable and asserting that
page pinning fails on pfn-backed memory.
> diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
> index 023df9cb9d4a6..b99e4cdbab8e6 100644
> --- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
> +++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
> @@ -16,11 +16,13 @@
> #include "kselftest_harness.h"
> #include "test_util.h"
> #include "ucall_common.h"
> +#include "../../../../samples/kvm/mem_provider_sample.h"
[Severity: Low]
Since tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c is five
directory levels below the kernel tree root, four ../ traversals resolve
relative to the source file as tools/samples/kvm/mem_provider_sample.h, and
relative to the tools/include search path as two levels above the kernel tree
root, before happening to match via the four-level-deep
tools/arch/<arch>/include search path.
Should this use five ../ traversals to match other five-level-deep files such
as tools/testing/selftests/kvm/lib/kvm_util.c:
#include "../../../../../samples/kvm/mem_provider_sample.h"
>
> FIXTURE(gmem_conversions) {
> struct kvm_vcpu *vcpu;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com?part=50
^ permalink raw reply [flat|nested] 79+ messages in thread
* [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
` (49 preceding siblings ...)
2026-10-22 23:28 ` [PATCH RFC v2 50/50] KVM: selftests: Test guest_memfd conversions with sample memory provider backing Ackerley Tng via B4 Relay
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 01/14] KVM: guest_memfd: Clamp the order of PFN mode blocks to what KVM can map Fred Griffoul
` (13 more replies)
50 siblings, 14 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
This series follows Ackerley's RFC v2, "guest_memfd alternative
providers" [1], and applies on top of it (kvm/next 7e60162b16f9 plus
his 50 patches). It answers one question his RFC leaves open: how the
IOMMU mappings of a device assigned to the guest follow guest_memfd.
A device cannot fault, and iommufd cannot pin guest_memfd pages: a
conversion to private, a punch hole, memory failure or a provider's
revoke must take a page away from the device too, and in PFN mode
there is no page to pin. So guest_memfd has to drive the device
mapping. This series proposes an interface for that, uses it in
iommufd as a demonstration, and adds the two PFN mode pieces it relies
on.
The interface
=============
A subsystem that maps guest_memfd pages, such as iommufd, uses three
functions and one callback, all in include/linux/guest_memfd.h:
kvm_gmem_export_attach() start following a guest_memfd file
kvm_gmem_export_lookup_pfn() which frame backs this page? A frame for
a shared page with memory, nothing for a
private page or a page without memory
kvm_gmem_export_detach() stop following it
update(start, nr_pages) the subsystem's callback: the answer for
this range may have changed, look it up
again and fix the mappings
No reference is taken on the frames. Instead, guest_memfd calls
update() on every change: conversions, punch holes, memory failure,
pages getting memory, and a PFN mode provider taking frames back or
giving them. The subsystem can rely on this:
- the old frames stay valid until update() returns, so a device that
never faults can switch each mapping in one step, to the new frame
or to a scratch page; after it returns, the old frames are no
longer used;
- update() cannot fail (see the open questions);
- update() may be called for pages that did not change;
- it runs under guest_memfd's invalidate lock.
For the update to come before a frame is freed, a punch hole keeps the
frames until then: folios are held until update() returns, and in PFN
mode the range reads as holes until the update has run, before the
provider gets its frames back.
A PFN mode provider takes frames back with kvm_gmem_revoke(), from
Ackerley's RFC. When it gives frames to pages that had none, or makes a
read-only frame writable, it calls the new kvm_gmem_restore(), the
"counterpart to kvm_gmem_revoke()" that his sample leaves for users that
cannot fault (48/50). Nothing is taken away then, so restore only calls
update(); KVM and the VMM fault the pages in themselves.
Why not dma-buf: our RFC that backed guest_memfd with an imported
dma-buf [3] needed a dma-buf call for importers to build their own page
tables, and ranged invalidation, and Christian rejected both: importers
filling page tables is a no-go for dma-buf. A dma-buf also knows
nothing of shared and private pages, and folio mode guest_memfd needs
the same tracking as PFN mode. So the interface lives in guest_memfd.
iommufd, as a demonstration
===========================
IOMMU_IOAS_MAP_FILE accepts a guest_memfd. Its pages are not pinned:
each update() unmaps the range and maps what a lookup returns now, with
PAGE_SIZE IOPTEs, so a device that touches the range during an update
faults. Dirty tracking, the no-IOMMU mode and kernel access are
refused. This tests the interface and is not meant to be merged: real
support needs large IOPTEs cut and merged in place, IOPTEs replaced in
one step, and a scratch page for holes, all generic_pt work (Jason, on
our v2), and will be a separate iommufd series.
PFN mode pieces
===============
Two PFN mode changes come first, both raised in replies to Ackerley's
series:
- get_pfn()'s order now means the naturally aligned block around the
index, and KVM clamps it to a block it can map, aligned and of one
attribute; today it can map a block over the wrong frames, or over
shared and private pages. Invalidations of a PFN mode file cover
the 1G-aligned blocks around their range, so that a fault that
sized a block before the change retries.
- get_pfn() gains an attributes output, with read only as its first
bit, which the guest's, the VMM's and the IOMMU's mappings follow.
Open questions
==============
- Private pages. The export gives shared pages only. That suits
devices that are not trusted, and TDX, where a shared IOPTE on a
private page is fatal. A trusted device (TDISP) needs private pages
too: on AMD the IOMMU maps them, with IOPTE sizes that follow the
RMP. The choice depends on the architecture and the device, so
userspace would make it through iommufd, which would pass an attach
flag asking lookups for private frames too, refused where the
architecture cannot take it. Is an attach flag the right place for
it?
- update() cannot fail, but a consumer may need memory to apply one:
iommufd allocates page tables to map a range again, and will need
one to cut a large IOPTE. The demonstration only warns when that
fails. Either the consumer reserves page tables when a range is
mapped, or guest_memfd adds a prepare step before a conversion,
which could fail and abort it. A provider's revoke cannot be
aborted, so it would need the reservation either way. Which would
you prefer?
Patches
=======
1-2 PFN mode: clamp the order KVM maps, read-only frames.
3-7 the export interface: folio mode, truncation, PFN mode, hole
sizes, and updates when pages get memory.
8-10 iommufd: track revocable domains, map guest_memfd files, and
mock-domain IOVA queries for the test.
11 kvm_gmem_restore().
12-13 the sample provider: read-only pages, large blocks, and backing
pages on demand and freeing them on a punch hole.
14 gmem_conversion_iommufd_test.
Patch 2 also puts back the return after a failed kvm_gmem_get_pfn() in
arm64's gmem_abort(), which a kvm/next merge dropped; the fix alone
will be sent separately.
Testing
=======
On x86_64 in QEMU, with KASAN and lockdep:
- gmem_conversion_iommufd_test maps the guest_memfd of a
KVM_X86_SW_PROTECTED_VM into an iommufd mock domain, backed by
folios, a tmpfs resource or the sample provider in PFN mode, and
checks that the guest, the VMM and the device all see each change:
conversions, allocations, punch holes, memory failure, and the
provider's revokes and restores;
- Ackerley's tests pass as on his RFC alone;
- the iommufd selftests pass, except 12 dmabuf and
access_domain_destory tests that also fail on his RFC alone here;
- each patch builds with KVM and iommufd built in, with KVM as a
module, with both as modules, and with SEV.
arm64 is neither built nor run.
History
=======
Our v2 [2] added an interface in mm for drivers that lend memory, with
guest_memfd and iommufd as two consumers of a provider file. Ackerley's
RFC now gives guest_memfd PFN mode providers itself and takes v2's
sample provider, so this series drops the mm interface: iommufd maps
the guest_memfd, not the provider file, and follows conversions.
[1] https://lore.kernel.org/all/20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com/
[2] https://lore.kernel.org/all/20261014093733.90312-1-griffoul@gmail.com/
[3] https://lore.kernel.org/all/20261005095329.49764-1-griffoul@gmail.com/
prerequisite-message-id: <20261022-gmem-alternative-providers-v2-0-5dd1dc6c660a@google.com>
Fred Griffoul (14):
KVM: guest_memfd: Clamp the order of PFN mode blocks to what KVM can
map
KVM: guest_memfd: Support read-only frames from PFN mode providers
KVM: guest_memfd: Allow other subsystems to map guest_memfd files
KVM: guest_memfd: Keep truncated folios until the exports are updated
KVM: guest_memfd: Export PFN mode files
KVM: guest_memfd: Report the size of holes in export lookups
KVM: guest_memfd: Update exports when pages are allocated
iommufd: Track the domains of revocable pages in struct iopt_pages
iommufd: Map guest_memfd files
iommufd/selftest: Add mock-domain IOVA queries
KVM: guest_memfd: Add kvm_gmem_restore() for PFN mode providers
samples/kvm: Add read-only pages and large blocks to the sample
provider
samples/kvm: Back absent pages on demand in the sample provider
KVM: selftests: Test guest_memfd mapped by iommufd
arch/arm64/kvm/mmu.c | 10 +-
arch/arm64/kvm/nested.c | 9 +-
arch/x86/kvm/Makefile | 4 +-
arch/x86/kvm/mmu/mmu.c | 15 +-
arch/x86/kvm/svm/sev.c | 4 +-
drivers/iommu/iommufd/io_pagetable.c | 38 +-
drivers/iommu/iommufd/io_pagetable.h | 61 +-
drivers/iommu/iommufd/iommufd_test.h | 18 +
drivers/iommu/iommufd/pages.c | 366 ++++++-
drivers/iommu/iommufd/selftest.c | 125 +++
include/linux/guest_memfd.h | 93 +-
include/linux/kvm_host.h | 6 +-
include/uapi/linux/iommufd.h | 11 +-
samples/kvm/mem_provider_sample.c | 118 ++-
samples/kvm/mem_provider_sample.h | 18 +-
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../kvm/x86/gmem_conversion_iommufd_test.c | 956 ++++++++++++++++++
virt/kvm/guest_memfd.c | 601 ++++++++++-
18 files changed, 2329 insertions(+), 125 deletions(-)
create mode 100644 tools/testing/selftests/kvm/x86/gmem_conversion_iommufd_test.c
base-commit: 7e60162b16f9da0d2fa83da5c1723b6853a0ac1d
prerequisite-patch-id: d0932c65d2a453e84f7917da27f9f6ae1da95856
prerequisite-patch-id: 7be8f39fdcfe7e504a9ac85e835a5dfb2ee414ba
prerequisite-patch-id: c6d0df8d4fdd910000ed9e35f514db6ac32e6e11
prerequisite-patch-id: 45cd3951b114a26eac94390727287c1221862b5c
prerequisite-patch-id: 6d978b6f0561697e5b145e4b82d79ac8c89f9f31
prerequisite-patch-id: 9bd6979aa47dd00c87a3aa8feb1798486107ac4b
prerequisite-patch-id: 98438bd35d53340738a20fd8724b038c9de1207e
prerequisite-patch-id: 146dfaa64e29a4930adb60e9524c7613f888a241
prerequisite-patch-id: 170e27bc053fede8d24accca69809d3687faf668
prerequisite-patch-id: 789fe5bfb1183f6b9a9b0d817e5d1050a7999e1a
prerequisite-patch-id: 5dd6878d0476a9dd925aa8d7ef1cf18900cbcd82
prerequisite-patch-id: 1f0d1dca2a51df2283f1d83ed57dd9ce4e11b025
prerequisite-patch-id: 503eb8fb0bf9af163f344c2693d374b6a3093fd1
prerequisite-patch-id: 94e4777a58b357458bdce680bbb41c04c0c661f4
prerequisite-patch-id: 4c1f6b548994d239e5093ee1d268ef5a12a76873
prerequisite-patch-id: f6d13ac583d54fe1ae22adde87f5c68d8e8e41ce
prerequisite-patch-id: 7846b44d34507a9af6ac8a3fb263ee446ea56a1d
prerequisite-patch-id: 2d309198e27a3922b5cf5805ac5660ea92029219
prerequisite-patch-id: e1f75965298956ad87bb78969461eff31419b6eb
prerequisite-patch-id: 40ef669df2a64e5b28127059e4cea288d426f6f6
prerequisite-patch-id: d8adb8f100cb13423db02e7e7a834fa241ff04fe
prerequisite-patch-id: 75591ca138e7de26db6cd3e242a7f0ce56c4752c
prerequisite-patch-id: b1e2c46b473783f37490c29140498a0bb35ec7d3
prerequisite-patch-id: bc8b607986a132eef4daa5e20979d3f038d046ba
prerequisite-patch-id: c9de5cd8a95f8102597b49dcf70b8ec5825b9ec7
prerequisite-patch-id: 954606132449080c4e173eb06d114504f87d0322
prerequisite-patch-id: 82a44a46cf9d4cc3fcdc03c362fc4e86d1fc533a
prerequisite-patch-id: 23dbc9968c0b2570f40cd6c3b13337078334aa8d
prerequisite-patch-id: 6c6a5ca6d0a1b60158a47ed21f25889dd71dd9b7
prerequisite-patch-id: e4d1ba4119277d7772dbf6e81f748beab4e62856
prerequisite-patch-id: a32d69bdd1fb21b85547fdc026475e2d94ffbc28
prerequisite-patch-id: 2704fe13a294e1f33cfbee6cd27c11ee392a8b78
prerequisite-patch-id: ebb701b7e050327e798c172d897021806f7e6c35
prerequisite-patch-id: 03db7e48c976a019ce478883002670c4b1a33cd9
prerequisite-patch-id: a18608129e63bbbc6e89a4ca237863ba4c506a63
prerequisite-patch-id: a35607a73bd0329fd91a39bd7b9c855edb4b8798
prerequisite-patch-id: 6d57cd16d6c6e71c3da069c1d9eee1c86bdeee8d
prerequisite-patch-id: b64a5a718f540e7daf2dc4f0703422ad2cc52ebe
prerequisite-patch-id: eb9a9e4d7f4b7e439c5687786628f07c406864a4
prerequisite-patch-id: 2fe22fda60b4e4678bcf62d87bfd82181556af98
prerequisite-patch-id: 26211cbacdbc31a62524aa5ad402b0f3933d433e
prerequisite-patch-id: 02455f9ee70487d8b30b6aada5f5b62ff4e5ebe2
prerequisite-patch-id: 113da62f7845c2a9f86d24ed178cf52756917cc0
prerequisite-patch-id: 9ef3e8b12bc70690470035f0c6f351fa49562659
prerequisite-patch-id: 0953a92b86c0790554b64ebda5d6aed038a6cf0e
prerequisite-patch-id: 877e1c79e389936278122aa1efbdf99053b2d6c7
prerequisite-patch-id: 0b04c869d49a4c1ad96ff7b5d8e3b11c4069f23f
prerequisite-patch-id: 942b7480ddb281b9df74705f1a3a6e23a2440a43
prerequisite-patch-id: 98c3864233a7ba6d789ad260f4af170a020d2929
prerequisite-patch-id: 40dc0f2b1ab5e59f1f5de0be5134101ca896d13d
--
2.47.3
^ permalink raw reply [flat|nested] 79+ messages in thread* [RFC PATCH 01/14] KVM: guest_memfd: Clamp the order of PFN mode blocks to what KVM can map
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 02/14] KVM: guest_memfd: Support read-only frames from PFN mode providers Fred Griffoul
` (12 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
KVM maps guest memory in 4K, 2M or 1G blocks, and uses guest_memfd's
max_order to choose. A folio of order N is physically aligned and never
mixes shared and private pages, so the order needs no check. A PFN mode
provider's frames need not be aligned and the provider does not know
which pages are private, yet kvm_gmem_get_pfn() passes its order to KVM
unchanged. KVM can then map a block over the wrong frames, or with one
attribute over shared and private pages.
Define the provider's order as that of the naturally aligned block
around the index, preset to 0. Clamp it to a block size KVM supports,
in which the gfn, the index and the pfn are aligned alike and every
page has the same attribute.
A fault that sized its block just before a revoke or conversion of
another page in the block would not retry, since KVM only retries
faults inside the invalidated range. Make every invalidation of a PFN
mode file cover the 1G-aligned blocks around its range.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
include/linux/guest_memfd.h | 5 +--
virt/kvm/guest_memfd.c | 63 +++++++++++++++++++++++++++++++++++--
2 files changed, 63 insertions(+), 5 deletions(-)
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index 3e2c0714ca33..a38a81693422 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -21,8 +21,9 @@ struct mempolicy;
* @get_pfn: Obtain a PFN for a given index. The provider must ensure backing
* memory is present and is responsible for its contents, such as
* zeroing if necessary. On success, return 0 with the physical frame
- * number in @pfn and, if @max_order is non-NULL, the maximum page
- * order of contiguous backing memory containing @pfn. Returns a
+ * number in @pfn and, if @max_order is non-NULL, the largest order
+ * for which the naturally aligned block of 2^order pages that
+ * contains @index is backed by contiguous frames. Returns a
* negative error code on failure. The provider must define
* @lookup_pfn_range when @get_pfn is defined.
* @lookup_pfn_range: Look up the PFN for a given index. Returns 0 on success
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 3c4bedceccfc..be26bd16d420 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -147,6 +147,8 @@ static int gmem_provider_get_pfn(struct gmem_inode *gi, pgoff_t index,
unsigned long raw_pfn;
int r;
+ if (max_order)
+ *max_order = 0;
r = gi->provider_ops->get_pfn(gi->provider, index, &raw_pfn, max_order);
if (r)
return r;
@@ -326,10 +328,13 @@ static enum kvm_gfn_range_filter kvm_gmem_get_all_gfns_filter(struct inode *inod
return KVM_FILTER_PRIVATE;
}
-static void __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
+static void __kvm_gmem_invalidate_start(struct inode *inode,
+ struct gmem_file *f, pgoff_t start,
pgoff_t end,
enum kvm_gfn_range_filter attr_filter)
{
+ const gfn_t blk = kvm_gmem_is_pfn_mode(GMEM_I(inode)) ?
+ 1UL << PUD_ORDER : 1;
bool flush = false, found_memslot = false;
struct kvm_memory_slot *slot;
struct kvm *kvm = f->kvm;
@@ -355,6 +360,16 @@ static void __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
flush |= kvm_mmu_unmap_gfn_range(kvm, &gfn_range);
+ /*
+ * In PFN mode, a fault may map a block of up to PUD_ORDER
+ * pages, sized before this invalidation (see
+ * kvm_gmem_pfn_map_order()): make faults anywhere in the
+ * blocks around the range retry.
+ */
+ kvm_mmu_invalidate_range_add(kvm,
+ round_down(gfn_range.start, blk),
+ round_up(gfn_range.end, blk));
+
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_INVALIDATE
kvm_arch_gmem_invalidate_range(kvm, &gfn_range);
#endif
@@ -374,7 +389,7 @@ static void kvm_gmem_invalidate_start(struct inode *inode, pgoff_t start,
struct gmem_file *f;
kvm_gmem_for_each_file(f, inode)
- __kvm_gmem_invalidate_start(f, start, end, filter);
+ __kvm_gmem_invalidate_start(inode, f, start, end, filter);
}
static void __kvm_gmem_invalidate_end(struct gmem_file *f, pgoff_t start,
@@ -670,7 +685,7 @@ static int kvm_gmem_release(struct inode *inode, struct file *file)
* Zap all SPTEs pointed at by this file. Do not free the backing
* memory, as its lifetime is associated with the inode, not the file.
*/
- __kvm_gmem_invalidate_start(f, 0, -1ul,
+ __kvm_gmem_invalidate_start(inode, f, 0, -1ul,
kvm_gmem_get_all_gfns_filter(inode));
__kvm_gmem_invalidate_end(f, 0, -1ul);
@@ -1535,6 +1550,44 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
return folio;
}
+/*
+ * The order a PFN mode block can be mapped at: the provider's order, no
+ * larger than the alignment of @gfn and @pfn to @index allows, rounded down to
+ * a page-table size, and never over pages that are not all shared or all
+ * private, since KVM maps a block with a single attribute. Called with the
+ * invalidate lock held; an invalidation of any page of the block makes the
+ * fault retry (see __kvm_gmem_invalidate_start()).
+ */
+static int kvm_gmem_pfn_map_order(struct inode *inode, pgoff_t index,
+ gfn_t gfn, kvm_pfn_t pfn, int order)
+{
+ u64 misalign = (gfn ^ index) | (pfn ^ index);
+ u64 attrs;
+
+ if (WARN_ON_ONCE(order < 0))
+ return 0;
+
+ order = min(order, PUD_ORDER);
+ if (misalign)
+ order = min_t(int, order, __ffs64(misalign));
+
+ if (order >= PUD_ORDER)
+ order = PUD_ORDER;
+ else if (order >= PMD_ORDER)
+ order = PMD_ORDER;
+ else
+ return 0;
+
+ attrs = kvm_gmem_get_attributes(inode,
+ mtree_load(&GMEM_I(inode)->attributes, index));
+ while (order &&
+ !kvm_gmem_range_has_attributes(inode,
+ ALIGN_DOWN(index, 1UL << order),
+ 1UL << order, attrs))
+ order = order == PUD_ORDER ? PMD_ORDER : 0;
+ return order;
+}
+
int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
gfn_t gfn, kvm_pfn_t *pfn, int *max_order)
{
@@ -1556,6 +1609,10 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
goto out;
}
+ if (kvm_gmem_is_pfn_mode(GMEM_I(file_inode(file))))
+ *max_order = kvm_gmem_pfn_map_order(file_inode(file), index, gfn,
+ *pfn, *max_order);
+
if (kvm_arch_has_gmem_convert() &&
kvm_gmem_is_private_mem(file_inode(file), index))
r = kvm_arch_gmem_make_private(kvm, gfn, *pfn,
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 02/14] KVM: guest_memfd: Support read-only frames from PFN mode providers
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 01/14] KVM: guest_memfd: Clamp the order of PFN mode blocks to what KVM can map Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 03/14] KVM: guest_memfd: Allow other subsystems to map guest_memfd files Fred Griffoul
` (11 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
A PFN mode provider may lend a frame that the guest must not write,
e.g. one shared by several guests, but guest_memfd maps every frame
writable.
Add an attributes output to get_pfn(), with GUEST_MEMFD_PFN_READONLY as
its first bit. KVM then maps such a frame read only and exits to
userspace on a guest write, and the VMM's mapping raises SIGBUS on a
write. A read-only frame cannot back a private page or be populated.
On arm64, the new exit in gmem_abort() also restores the return after a
failed kvm_gmem_get_pfn() that a kvm/next merge dropped.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
arch/arm64/kvm/mmu.c | 10 ++-
arch/arm64/kvm/nested.c | 9 ++-
arch/x86/kvm/mmu/mmu.c | 15 +++-
arch/x86/kvm/svm/sev.c | 4 +-
include/linux/guest_memfd.h | 24 +++++-
include/linux/kvm_host.h | 6 +-
samples/kvm/mem_provider_sample.c | 2 +-
virt/kvm/guest_memfd.c | 128 ++++++++++++++++++++++++++----
8 files changed, 168 insertions(+), 30 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 745cb86f9e04..cdf6d671158a 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1736,7 +1736,7 @@ static gfn_t get_canonical_gfn(const struct kvm_s2_fault_desc *s2fd,
static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
struct kvm_s2_fault_result *result)
{
- bool write_fault, exec_fault;
+ bool write_fault, exec_fault, writable;
bool perm_fault = kvm_s2_fault_is_perm(s2fd);
enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
@@ -1773,17 +1773,19 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
/* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
smp_rmb();
- ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, canonical_gfn, &s2vi.pfn, NULL);
- if (ret) {
+ ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, canonical_gfn, &s2vi.pfn, NULL,
+ &writable);
+ if (ret || (write_fault && !writable)) {
/* If result is non-NULL this is a synthetic fault. */
if (!result)
kvm_prepare_memory_fault_exit(s2fd->vcpu, gfn_to_gpa(canonical_gfn),
s2vi.vma_pagesize, write_fault, exec_fault,
false);
kfree(mapping);
+ return ret ?: -EFAULT;
}
- if (!(s2fd->memslot->flags & KVM_MEM_READONLY))
+ if (!(s2fd->memslot->flags & KVM_MEM_READONLY) && writable)
prot |= KVM_PGTABLE_PROT_W;
if (s2fd->nested)
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index f1c414f1c3fa..ec3bcdc230cf 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1615,12 +1615,19 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem)
return -EFAULT;
}
} else {
- ret = kvm_gmem_get_pfn(vcpu->kvm, memslot, gfn, &pfn, NULL);
+ ret = kvm_gmem_get_pfn(vcpu->kvm, memslot, gfn, &pfn, NULL,
+ &writable);
if (ret) {
kvm_prepare_memory_fault_exit(vcpu, vt->wr.pa, PAGE_SIZE,
write_fault, false, false);
return ret;
}
+ /* The CPU writes the VNCR page: a read-only frame cannot back it. */
+ if (!writable) {
+ kvm_prepare_memory_fault_exit(vcpu, vt->wr.pa, PAGE_SIZE,
+ true, false, false);
+ return -EFAULT;
+ }
writable = !(memslot->flags & KVM_MEM_READONLY);
}
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 5a2cc6a17a9b..1c50955e80f9 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4675,6 +4675,7 @@ static int kvm_mmu_faultin_pfn_gmem(struct kvm_vcpu *vcpu,
struct kvm_page_fault *fault)
{
int max_order, r;
+ bool writable;
if (!kvm_slot_has_gmem(fault->slot)) {
kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
@@ -4682,13 +4683,23 @@ static int kvm_mmu_faultin_pfn_gmem(struct kvm_vcpu *vcpu,
}
r = kvm_gmem_get_pfn(vcpu->kvm, fault->slot, fault->gfn, &fault->pfn,
- &max_order);
+ &max_order, &writable);
if (r) {
kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
return r;
}
- fault->map_writable = !(fault->slot->flags & KVM_MEM_READONLY);
+ /*
+ * guest_memfd may report a read-only frame: map it without write
+ * permission, and exit to userspace on a guest write rather than
+ * install it.
+ */
+ fault->map_writable = !(fault->slot->flags & KVM_MEM_READONLY) &&
+ writable;
+ if (fault->write && !fault->map_writable) {
+ kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
+ return -EFAULT;
+ }
fault->max_level = kvm_max_level_for_order(max_order);
return RET_PF_CONTINUE;
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 6427db390b1c..4125b4fd30da 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -4096,7 +4096,7 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
* The new VMSA will be private memory guest memory, so retrieve the
* PFN from the gmem backend.
*/
- if (kvm_gmem_get_pfn(vcpu->kvm, slot, gfn, &pfn, NULL))
+ if (kvm_gmem_get_pfn(vcpu->kvm, slot, gfn, &pfn, NULL, NULL))
return;
read_lock(&kvm->mmu_lock);
@@ -5092,7 +5092,7 @@ void sev_handle_rmp_fault(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code)
mmu_seq = kvm->mmu_invalidate_seq;
smp_rmb();
- ret = kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &order);
+ ret = kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &order, NULL);
if (ret) {
pr_warn_ratelimited("SEV: Unexpected RMP fault, no backing page for private GPA 0x%llx\n",
gpa);
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index a38a81693422..9d8764b93af2 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -2,6 +2,7 @@
#ifndef _LINUX_GUEST_MEMFD_H
#define _LINUX_GUEST_MEMFD_H
+#include <linux/bits.h>
#include <linux/types.h>
struct file;
@@ -23,9 +24,12 @@ struct mempolicy;
* zeroing if necessary. On success, return 0 with the physical frame
* number in @pfn and, if @max_order is non-NULL, the largest order
* for which the naturally aligned block of 2^order pages that
- * contains @index is backed by contiguous frames. Returns a
- * negative error code on failure. The provider must define
- * @lookup_pfn_range when @get_pfn is defined.
+ * contains @index is backed by contiguous frames. @attrs, preset
+ * to 0 by guest_memfd, receives the GUEST_MEMFD_PFN_* attributes
+ * of the whole block. A provider that changes them must then
+ * kvm_gmem_revoke() the range. Returns a negative error code on
+ * failure. The provider must define @lookup_pfn_range when
+ * @get_pfn is defined.
* @lookup_pfn_range: Look up the PFN for a given index. Returns 0 on success
* with the physical frame number in @pfn and the number of
* contiguous pages the mapping is valid for in @nr_pages.
@@ -49,12 +53,24 @@ struct guest_memfd_provider_operations {
struct mempolicy *mpol);
void (*invalidate_folio)(void *provider, struct folio *folio);
int (*get_pfn)(void *provider, pgoff_t index, unsigned long *pfn,
- int *max_order);
+ int *max_order, u32 *attrs);
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);
};
+/*
+ * Attributes of a PFN mode block, reported by get_pfn(). Only the provider
+ * knows them, and every mapping of the block must follow them.
+ *
+ * READONLY: the guest may read the block but not write it, e.g. a page that
+ * the provider lends to several guests. guest_memfd maps it read only for
+ * the guest and the VMM, and never backs a private page with it.
+ */
+#define GUEST_MEMFD_PFN_READONLY BIT(0)
+
+#define GUEST_MEMFD_PFN_ATTRS GUEST_MEMFD_PFN_READONLY
+
#ifdef CONFIG_KVM_GUEST_MEMFD
int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
const unsigned long *pfn);
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 345f97b110fa..3d0db49ad08b 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2628,11 +2628,13 @@ static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
}
int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
- gfn_t gfn, kvm_pfn_t *pfn, int *max_order);
+ gfn_t gfn, kvm_pfn_t *pfn, int *max_order,
+ bool *writable);
#else
static inline int kvm_gmem_get_pfn(struct kvm *kvm,
struct kvm_memory_slot *slot, gfn_t gfn,
- kvm_pfn_t *pfn, int *max_order)
+ kvm_pfn_t *pfn, int *max_order,
+ bool *writable)
{
KVM_BUG_ON(1, kvm);
return -EIO;
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 5496e0c7864b..f30654a60dd8 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -179,7 +179,7 @@ static void mps_gmem_release(void *provider)
}
static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
- int *max_order)
+ int *max_order, u32 *attrs)
{
struct file *resource_file = provider;
struct mps_info *info = resource_file->private_data;
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index be26bd16d420..1554a17906da 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -142,18 +142,25 @@ static struct folio *gmem_provider_alloc_folio(struct gmem_inode *gi,
}
static int gmem_provider_get_pfn(struct gmem_inode *gi, pgoff_t index,
- kvm_pfn_t *pfn, int *max_order)
+ kvm_pfn_t *pfn, int *max_order, u32 *attrs)
{
unsigned long raw_pfn;
+ u32 __attrs = 0;
int r;
if (max_order)
*max_order = 0;
- r = gi->provider_ops->get_pfn(gi->provider, index, &raw_pfn, max_order);
+ r = gi->provider_ops->get_pfn(gi->provider, index, &raw_pfn, max_order,
+ &__attrs);
if (r)
return r;
+ if (WARN_ON_ONCE(__attrs & ~GUEST_MEMFD_PFN_ATTRS))
+ return -EIO;
+
*pfn = raw_pfn;
+ if (attrs)
+ *attrs = __attrs;
return 0;
}
@@ -546,7 +553,7 @@ static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
break;
}
- r = gmem_provider_get_pfn(gi, index, &pfn, &max_order);
+ r = gmem_provider_get_pfn(gi, index, &pfn, &max_order, NULL);
if (r)
break;
@@ -721,13 +728,71 @@ static bool kvm_gmem_supports_mmap(struct inode *inode)
return GMEM_I(inode)->flags & GUEST_MEMFD_FLAG_MMAP;
}
+/* The protection for a host mapping of a PFN mode frame with @attrs. */
+static pgprot_t kvm_gmem_pfn_prot(struct vm_area_struct *vma, u32 attrs)
+{
+ vm_flags_t flags = vma->vm_flags;
+
+ if (attrs & GUEST_MEMFD_PFN_READONLY)
+ flags &= ~VM_WRITE;
+ return vm_get_page_prot(flags);
+}
+
+/*
+ * Map a PFN mode frame for the host. Called with the invalidate lock held
+ * shared, so that a frame is never mapped after the revoke that removes it.
+ * A read-only frame is mapped without write permission, and a write to it
+ * raises SIGBUS.
+ */
+static vm_fault_t kvm_gmem_pfn_fault(struct vm_fault *vmf)
+{
+ struct inode *inode = file_inode(vmf->vma->vm_file);
+ struct vm_area_struct *vma = vmf->vma;
+ bool write = vmf->flags & FAULT_FLAG_WRITE;
+ kvm_pfn_t pfn;
+ u32 attrs;
+ int r;
+
+ r = gmem_provider_get_pfn(GMEM_I(inode), vmf->pgoff, &pfn, NULL, &attrs);
+ if (r)
+ return vmf_error(r);
+
+ if (write && (attrs & GUEST_MEMFD_PFN_READONLY))
+ return VM_FAULT_SIGBUS;
+
+ return vmf_insert_pfn_prot(vma, vmf->address, pfn,
+ kvm_gmem_pfn_prot(vma, attrs));
+}
+
+/*
+ * A write to a read-only PTE, e.g. after mprotect(): allow it unless the frame
+ * is read only. The core upgrades the PTE under its lock only if it did not
+ * change, and a provider that makes the frame read only revokes it first.
+ */
+static vm_fault_t kvm_gmem_pfn_mkwrite(struct vm_fault *vmf)
+{
+ struct inode *inode = file_inode(vmf->vma->vm_file);
+ vm_fault_t ret = 0;
+ kvm_pfn_t pfn;
+ u32 attrs;
+ int r;
+
+ filemap_invalidate_lock_shared(inode->i_mapping);
+ r = gmem_provider_get_pfn(GMEM_I(inode), vmf->pgoff, &pfn, NULL, &attrs);
+ if (r)
+ ret = vmf_error(r);
+ else if (attrs & GUEST_MEMFD_PFN_READONLY)
+ ret = VM_FAULT_SIGBUS;
+ filemap_invalidate_unlock_shared(inode->i_mapping);
+ return ret;
+}
+
static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
{
struct inode *inode = file_inode(vmf->vma->vm_file);
struct gmem_inode *gi = GMEM_I(inode);
vm_fault_t ret = VM_FAULT_LOCKED;
struct folio *folio = NULL;
- kvm_pfn_t pfn;
int r = 0;
if (((loff_t)vmf->pgoff << PAGE_SHIFT) >= i_size_read(inode))
@@ -737,9 +802,7 @@ static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
if (!kvm_gmem_is_shared_mem(inode, vmf->pgoff)) {
r = -EACCES;
} else if (kvm_gmem_is_pfn_mode(gi)) {
- r = gmem_provider_get_pfn(gi, vmf->pgoff, &pfn, NULL);
- if (!r)
- ret = vmf_insert_pfn(vmf->vma, vmf->address, pfn);
+ ret = kvm_gmem_pfn_fault(vmf);
} else {
folio = kvm_gmem_get_folio(inode, vmf->pgoff);
if (IS_ERR(folio))
@@ -815,6 +878,12 @@ static const struct vm_operations_struct kvm_gmem_vm_ops = {
#endif
};
+/* PFN mode: a write to a page mapped read only comes back to guest_memfd. */
+static const struct vm_operations_struct kvm_gmem_pfn_vm_ops = {
+ .fault = kvm_gmem_fault_user_mapping,
+ .pfn_mkwrite = kvm_gmem_pfn_mkwrite,
+};
+
static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma)
{
struct gmem_inode *gi = GMEM_I(file_inode(file));
@@ -827,8 +896,11 @@ static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma)
return -EINVAL;
}
- if (kvm_gmem_is_pfn_mode(gi))
+ if (kvm_gmem_is_pfn_mode(gi)) {
vm_flags_set(vma, VM_PFNMAP);
+ vma->vm_ops = &kvm_gmem_pfn_vm_ops;
+ return 0;
+ }
vma->vm_ops = &kvm_gmem_vm_ops;
@@ -1503,7 +1575,7 @@ void kvm_gmem_unbind(struct kvm_memory_slot *slot)
static struct folio *__kvm_gmem_get_pfn(struct file *file,
struct kvm_memory_slot *slot,
pgoff_t index, kvm_pfn_t *pfn,
- int *max_order)
+ int *max_order, u32 *attrs)
{
struct file *slot_file = READ_ONCE(slot->gmem.file);
struct gmem_inode *gi = GMEM_I(file_inode(file));
@@ -1522,7 +1594,7 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
}
if (kvm_gmem_is_pfn_mode(gi)) {
- r = gmem_provider_get_pfn(gi, index, pfn, max_order);
+ r = gmem_provider_get_pfn(gi, index, pfn, max_order, attrs);
if (r)
return ERR_PTR(r);
return NULL;
@@ -1588,14 +1660,22 @@ static int kvm_gmem_pfn_map_order(struct inode *inode, pgoff_t index,
return order;
}
+/*
+ * @writable, if not NULL, is set to whether the guest may write the page. It
+ * may be NULL only for private pages, which are always writable.
+ */
int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
- gfn_t gfn, kvm_pfn_t *pfn, int *max_order)
+ gfn_t gfn, kvm_pfn_t *pfn, int *max_order,
+ bool *writable)
{
pgoff_t index = kvm_gmem_get_index(slot, gfn);
struct folio *folio;
int r = 0, __order;
+ u32 attrs = 0;
max_order = max_order ?: &__order;
+ if (writable)
+ *writable = true;
CLASS(gmem_get_file, file)(slot);
if (!file)
@@ -1603,15 +1683,28 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
filemap_invalidate_lock_shared(file_inode(file)->i_mapping);
- folio = __kvm_gmem_get_pfn(file, slot, index, pfn, max_order);
+ folio = __kvm_gmem_get_pfn(file, slot, index, pfn, max_order, &attrs);
if (IS_ERR(folio)) {
r = PTR_ERR(folio);
goto out;
}
- if (kvm_gmem_is_pfn_mode(GMEM_I(file_inode(file))))
+ if (kvm_gmem_is_pfn_mode(GMEM_I(file_inode(file)))) {
+ /*
+ * A read-only frame is not the guest's alone, so it never
+ * backs a private page: a private fault on it is a hole.
+ */
+ if (attrs & GUEST_MEMFD_PFN_READONLY) {
+ if (kvm_gmem_is_private_mem(file_inode(file), index) ||
+ WARN_ON_ONCE(!writable)) {
+ r = -EFAULT;
+ goto out;
+ }
+ *writable = false;
+ }
*max_order = kvm_gmem_pfn_map_order(file_inode(file), index, gfn,
*pfn, *max_order);
+ }
if (kvm_arch_has_gmem_convert() &&
kvm_gmem_is_private_mem(file_inode(file), index))
@@ -1637,12 +1730,13 @@ static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot,
{
pgoff_t index = kvm_gmem_get_index(slot, gfn);
struct folio *folio;
+ u32 attrs = 0;
kvm_pfn_t pfn;
int ret;
filemap_invalidate_lock(file->f_mapping);
- folio = __kvm_gmem_get_pfn(file, slot, index, &pfn, NULL);
+ folio = __kvm_gmem_get_pfn(file, slot, index, &pfn, NULL, &attrs);
if (IS_ERR(folio)) {
ret = PTR_ERR(folio);
goto out_unlock;
@@ -1651,6 +1745,12 @@ static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot,
if (folio)
folio_unlock(folio);
+ /* Populated pages are private, and are written: not a read-only frame. */
+ if (attrs & GUEST_MEMFD_PFN_READONLY) {
+ ret = -EPERM;
+ goto out_put_folio;
+ }
+
if (!kvm_is_private_gfn(kvm, gfn)) {
ret = -EINVAL;
goto out_put_folio;
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 03/14] KVM: guest_memfd: Allow other subsystems to map guest_memfd files
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 01/14] KVM: guest_memfd: Clamp the order of PFN mode blocks to what KVM can map Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 02/14] KVM: guest_memfd: Support read-only frames from PFN mode providers Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 04/14] KVM: guest_memfd: Keep truncated folios until the exports are updated Fred Griffoul
` (10 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
A device assigned to a guest must map the guest's shared memory and
lose a page whenever guest_memfd takes it away. iommufd cannot do that:
it pins what it maps, which guest_memfd does not allow, and a device
cannot fault.
Add an export interface. A subsystem attaches to a guest_memfd file,
looks up the frame behind each shared page without taking a reference,
and gets an update() callback when what a lookup returns for a range
may have changed. The old frames stay valid until update() returns, so
a device that never faults can switch each mapping in one step.
update() is called on conversions and memory failure; punch holes, PFN
mode and pages that get memory follow. The functions are exported for
symbol_get(), since KVM may be a module while the consumer is built in.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
arch/x86/kvm/Makefile | 3 +-
include/linux/guest_memfd.h | 55 +++++++++++++
virt/kvm/guest_memfd.c | 149 ++++++++++++++++++++++++++++++++++++
3 files changed, 206 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile
index 5fd01bd19d1c..25fb3d6e2032 100644
--- a/arch/x86/kvm/Makefile
+++ b/arch/x86/kvm/Makefile
@@ -62,7 +62,8 @@ exports_grep_trailer := --include='*.[ch]' -nrw $(srctree)/virt/kvm $(srctree)/a
-e kvm_write_track_add_gfn \
-e kvm_write_track_remove_gfn \
-e kvm_file_to_kvm_fn \
- -e kvm_gmem_revoke
+ -e kvm_gmem_revoke \
+ -e kvm_gmem_export
# 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 9d8764b93af2..fdf7f8404d6c 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -3,6 +3,7 @@
#define _LINUX_GUEST_MEMFD_H
#include <linux/bits.h>
+#include <linux/list.h>
#include <linux/types.h>
struct file;
@@ -82,4 +83,58 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
}
#endif
+/*
+ * Mapping guest_memfd files in other subsystems.
+ *
+ * A subsystem that maps guest_memfd pages, such as iommufd, can neither pin
+ * nor fault them. It attaches to the file, looks up the frame behind each
+ * shared page without taking a reference, and guest_memfd calls its update()
+ * whenever what a lookup returns for a range may have changed: conversions and
+ * memory failure. update() makes the consumer's mappings of the range match
+ * what a lookup returns now: a frame, or a hole for a private page or a page
+ * without a frame. It may be called for pages that did not change, and for
+ * ranges that do not follow the blocks that lookups report: the consumer may
+ * then remap whole blocks, also outside the range, e.g. to merge them into
+ * larger mappings. A consumer that can fault may instead only remove its
+ * mappings of the range.
+ *
+ * Until update() returns, the frames it replaces stay valid, so a consumer
+ * may switch each mapping to its new target in one step; once it returns,
+ * the consumer no longer accesses them. update() cannot fail: a consumer
+ * that needs memory to apply one, e.g. to split a large mapping, reserves it
+ * beforehand.
+ *
+ * guest_memfd calls update() with its invalidate lock held, exclusive when
+ * pages convert and shared on memory failure, possibly with a folio of the
+ * file locked; so update() may only call kvm_gmem_export_lookup_pfn(). Under
+ * the shared lock, updates may run at the same time, also for overlapping
+ * ranges. The consumer holds a lock across a lookup and the mapping it
+ * installs, which update() also takes. Lock order:
+ *
+ * guest_memfd invalidate lock
+ * consumer lock
+ *
+ * A consumer attaches and detaches outside its lock. KVM may be a module
+ * while the consumer is built in: consumers reach these with symbol_get().
+ */
+struct guest_memfd_export;
+
+struct guest_memfd_export_ops {
+ void (*update)(struct guest_memfd_export *e, pgoff_t start,
+ pgoff_t nr_pages);
+};
+
+struct guest_memfd_export {
+ const struct guest_memfd_export_ops *ops;
+
+ /* Private to guest_memfd. */
+ struct list_head node;
+ struct file *file;
+};
+
+int kvm_gmem_export_attach(struct file *file, struct guest_memfd_export *e);
+void kvm_gmem_export_detach(struct guest_memfd_export *e);
+int kvm_gmem_export_lookup_pfn(struct guest_memfd_export *e, pgoff_t index,
+ unsigned long *pfn, int *max_order, u32 *attrs);
+
#endif /* _LINUX_GUEST_MEMFD_H */
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 1554a17906da..81031483a1f1 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -38,6 +38,8 @@ struct gmem_inode {
void *provider;
const struct guest_memfd_provider_operations *provider_ops;
+ /* Subsystems that map the file, see include/linux/guest_memfd.h. */
+ struct list_head exports;
u64 flags;
/*
@@ -420,6 +422,21 @@ static void kvm_gmem_invalidate_end(struct inode *inode, pgoff_t start,
__kvm_gmem_invalidate_end(f, start, end);
}
+/*
+ * What a lookup returns for [start, end) may have changed: exclusive lock when
+ * pages converted, shared on memory failure.
+ */
+static void kvm_gmem_update_exports(struct inode *inode, pgoff_t start,
+ pgoff_t end)
+{
+ struct guest_memfd_export *e;
+
+ lockdep_assert_held(&inode->i_mapping->invalidate_lock);
+
+ list_for_each_entry(e, &GMEM_I(inode)->exports, node)
+ e->ops->update(e, start, end - start);
+}
+
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
static void kvm_gmem_pfn_reclaim(struct gmem_inode *gi, pgoff_t start, pgoff_t end)
{
@@ -1130,6 +1147,9 @@ static int __kvm_gmem_set_attributes(struct inode *inode, pgoff_t start,
mas_store_prealloc(&mas, xa_mk_value(attrs));
+ /* After the store, before a vCPU can fault a private page in. */
+ kvm_gmem_update_exports(inode, start, end);
+
kvm_gmem_invalidate_end(inode, start, end);
out:
filemap_invalidate_unlock(mapping);
@@ -1204,6 +1224,131 @@ static struct file_operations kvm_gmem_fops = {
.unlocked_ioctl = kvm_gmem_ioctl,
};
+/**
+ * kvm_gmem_export_attach - Map a guest_memfd file in another subsystem.
+ * @file: The guest_memfd file.
+ * @e: The export, with @e->ops set.
+ *
+ * guest_memfd calls @e->ops->update() from now on, and holds a reference to
+ * @file until kvm_gmem_export_detach(). May sleep.
+ *
+ * Return: 0, -ENODEV if @file is not a guest_memfd file, or -EOPNOTSUPP in
+ * PFN mode.
+ */
+int kvm_gmem_export_attach(struct file *file, struct guest_memfd_export *e)
+{
+ struct inode *inode = file_inode(file);
+
+ if (file->f_op != &kvm_gmem_fops)
+ return -ENODEV;
+ if (kvm_gmem_is_pfn_mode(GMEM_I(inode)))
+ return -EOPNOTSUPP;
+
+ e->file = get_file(file);
+ filemap_invalidate_lock(inode->i_mapping);
+ list_add_tail(&e->node, &GMEM_I(inode)->exports);
+ filemap_invalidate_unlock(inode->i_mapping);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(kvm_gmem_export_attach);
+
+/**
+ * kvm_gmem_export_detach - Stop mapping a guest_memfd file.
+ * @e: The export.
+ *
+ * Waits for an update() in progress. May sleep.
+ */
+void kvm_gmem_export_detach(struct guest_memfd_export *e)
+{
+ struct inode *inode = file_inode(e->file);
+
+ filemap_invalidate_lock(inode->i_mapping);
+ list_del(&e->node);
+ filemap_invalidate_unlock(inode->i_mapping);
+ fput(e->file);
+}
+EXPORT_SYMBOL_GPL(kvm_gmem_export_detach);
+
+/* Are [index, index + nr) all shared? Without the invalidate lock. */
+static bool kvm_gmem_range_is_shared(struct inode *inode, pgoff_t index,
+ unsigned long nr)
+{
+ unsigned long first = index;
+ void *entry;
+
+ mt_for_each(&GMEM_I(inode)->attributes, entry, first, index + nr - 1) {
+ if (kvm_gmem_get_attributes(inode, entry) &
+ KVM_MEMORY_ATTRIBUTE_PRIVATE)
+ return false;
+ }
+ return true;
+}
+
+/* What kvm_gmem_export_lookup_pfn() returns for a page inside the file. */
+static int __kvm_gmem_export_lookup(struct inode *inode, pgoff_t index,
+ unsigned long *pfn, int *max_order,
+ u32 *attrs)
+{
+ struct folio *folio;
+
+ if (!kvm_gmem_range_is_shared(inode, index, 1))
+ return -ENOENT;
+
+ /* A folio that was never zeroed is a hole. */
+ folio = filemap_get_folio(inode->i_mapping, index);
+ if (IS_ERR(folio))
+ return -ENOENT;
+ if (folio_test_hwpoison(folio) || !folio_test_uptodate(folio)) {
+ folio_put(folio);
+ return -ENOENT;
+ }
+ *pfn = folio_file_pfn(folio, index);
+ *max_order = 0;
+ *attrs = 0;
+ folio_put(folio);
+
+ /* Nor may the block hold a private page. */
+ while (*max_order &&
+ !kvm_gmem_range_is_shared(inode,
+ round_down(index, 1UL << *max_order),
+ 1UL << *max_order))
+ (*max_order)--;
+ return 0;
+}
+
+/**
+ * kvm_gmem_export_lookup_pfn - The frame behind a shared page.
+ * @e: The export.
+ * @index: The page index in the file.
+ * @pfn: The frame.
+ * @max_order: In: the largest order the caller can map. Out: the order of
+ * the naturally aligned block around @index that is contiguous,
+ * shared and of the same @attrs; 0 for -ENOENT.
+ * @attrs: The GUEST_MEMFD_PFN_* attributes of the block.
+ *
+ * Takes no reference and allocates nothing, and may be called from the
+ * export's update(): the answer holds until the update() that changes it.
+ *
+ * Context: May sleep.
+ * Return: 0, -ENOENT for a private page or a page without a frame, -EINVAL
+ * beyond the end of the file, or another error from the provider.
+ */
+int kvm_gmem_export_lookup_pfn(struct guest_memfd_export *e, pgoff_t index,
+ unsigned long *pfn, int *max_order, u32 *attrs)
+{
+ struct inode *inode = file_inode(e->file);
+ int r;
+
+ if (index >= i_size_read(inode) >> PAGE_SHIFT)
+ return -EINVAL;
+
+ r = __kvm_gmem_export_lookup(inode, index, pfn, max_order, attrs);
+ if (r == -ENOENT)
+ *max_order = 0;
+ return r;
+}
+EXPORT_SYMBOL_GPL(kvm_gmem_export_lookup_pfn);
+
static int kvm_gmem_migrate_folio(struct address_space *mapping,
struct folio *dst, struct folio *src,
enum migrate_mode mode)
@@ -1241,6 +1386,9 @@ static int kvm_gmem_error_folio(struct address_space *mapping, struct folio *fol
filter = kvm_gmem_get_all_gfns_filter(inode);
kvm_gmem_invalidate_start(inode, start, end, filter);
+ /* Lookups see the poisoned folio as a hole: take it from the exports. */
+ kvm_gmem_update_exports(inode, start, end);
+
/*
* Do not truncate the range, what action is taken in response to the
* error is userspace's decision (assuming the architecture supports
@@ -1867,6 +2015,7 @@ static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)
gi->provider = NULL;
gi->provider_ops = NULL;
INIT_LIST_HEAD(&gi->gmem_file_list);
+ INIT_LIST_HEAD(&gi->exports);
return &gi->vfs_inode;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 04/14] KVM: guest_memfd: Keep truncated folios until the exports are updated
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (2 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 03/14] KVM: guest_memfd: Allow other subsystems to map guest_memfd files Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 05/14] KVM: guest_memfd: Export PFN mode files Fred Griffoul
` (9 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
A punch hole frees the folios it truncates at once, but an export may
still map them, since it holds no reference: a device would then write
into freed memory.
Truncate a folio mode range batch by batch, holding a reference on each
folio until the exports are updated, so that a folio goes back to the
allocator only once no device maps it.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
include/linux/guest_memfd.h | 28 ++++++++++++++--------------
virt/kvm/guest_memfd.c | 30 ++++++++++++++++++++++++++++--
2 files changed, 42 insertions(+), 16 deletions(-)
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index fdf7f8404d6c..ba3eebb1ed1a 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -89,14 +89,14 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
* A subsystem that maps guest_memfd pages, such as iommufd, can neither pin
* nor fault them. It attaches to the file, looks up the frame behind each
* shared page without taking a reference, and guest_memfd calls its update()
- * whenever what a lookup returns for a range may have changed: conversions and
- * memory failure. update() makes the consumer's mappings of the range match
- * what a lookup returns now: a frame, or a hole for a private page or a page
- * without a frame. It may be called for pages that did not change, and for
- * ranges that do not follow the blocks that lookups report: the consumer may
- * then remap whole blocks, also outside the range, e.g. to merge them into
- * larger mappings. A consumer that can fault may instead only remove its
- * mappings of the range.
+ * whenever what a lookup returns for a range may have changed: conversions,
+ * punch holes and memory failure. update() makes the consumer's mappings of
+ * the range match what a lookup returns now: a frame, or a hole for a private
+ * page or a page without a frame. It may be called for pages that did not
+ * change, and for ranges that do not follow the blocks that lookups report:
+ * the consumer may then remap whole blocks, also outside the range, e.g. to
+ * merge them into larger mappings. A consumer that can fault may instead only
+ * remove its mappings of the range.
*
* Until update() returns, the frames it replaces stay valid, so a consumer
* may switch each mapping to its new target in one step; once it returns,
@@ -104,12 +104,12 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
* that needs memory to apply one, e.g. to split a large mapping, reserves it
* beforehand.
*
- * guest_memfd calls update() with its invalidate lock held, exclusive when
- * pages convert and shared on memory failure, possibly with a folio of the
- * file locked; so update() may only call kvm_gmem_export_lookup_pfn(). Under
- * the shared lock, updates may run at the same time, also for overlapping
- * ranges. The consumer holds a lock across a lookup and the mapping it
- * installs, which update() also takes. Lock order:
+ * guest_memfd calls update() with its invalidate lock held, exclusive when it
+ * takes frames away or pages convert and shared on memory failure, possibly
+ * with a folio of the file locked; so update() may only call
+ * kvm_gmem_export_lookup_pfn(). Under the shared lock, updates may run at the
+ * same time, also for overlapping ranges. The consumer holds a lock across a
+ * lookup and the mapping it installs, which update() also takes. Lock order:
*
* guest_memfd invalidate lock
* consumer lock
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 81031483a1f1..65a951fa49c2 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -424,7 +424,7 @@ static void kvm_gmem_invalidate_end(struct inode *inode, pgoff_t start,
/*
* What a lookup returns for [start, end) may have changed: exclusive lock when
- * pages converted, shared on memory failure.
+ * frames were taken away or pages converted, at least shared on memory failure.
*/
static void kvm_gmem_update_exports(struct inode *inode, pgoff_t start,
pgoff_t end)
@@ -437,6 +437,32 @@ static void kvm_gmem_update_exports(struct inode *inode, pgoff_t start,
e->ops->update(e, start, end - start);
}
+/*
+ * Truncate [start, end) of a folio mode file. The exports map the folios
+ * without references, so hold the folios until the exports are updated.
+ */
+static void kvm_gmem_truncate(struct inode *inode, pgoff_t start, pgoff_t end)
+{
+ struct folio_batch fbatch;
+ pgoff_t next = start;
+ unsigned int i;
+
+ folio_batch_init(&fbatch);
+ while (filemap_get_folios(inode->i_mapping, &next, end - 1, &fbatch)) {
+ pgoff_t first = max(fbatch.folios[0]->index, start);
+ pgoff_t last = min(next, end);
+
+ truncate_inode_pages_range(inode->i_mapping,
+ (loff_t)first << PAGE_SHIFT,
+ ((loff_t)last << PAGE_SHIFT) - 1);
+ kvm_gmem_update_exports(inode, first, last);
+ for (i = 0; i < folio_batch_count(&fbatch); i++)
+ folio_put(fbatch.folios[i]);
+ folio_batch_reinit(&fbatch);
+ cond_resched();
+ }
+}
+
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
static void kvm_gmem_pfn_reclaim(struct gmem_inode *gi, pgoff_t start, pgoff_t end)
{
@@ -499,7 +525,7 @@ static int __kvm_gmem_revoke(struct inode *inode, pgoff_t start,
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);
+ kvm_gmem_truncate(inode, start, end);
}
kvm_gmem_invalidate_end(inode, start, end);
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 05/14] KVM: guest_memfd: Export PFN mode files
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (3 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 04/14] KVM: guest_memfd: Keep truncated folios until the exports are updated Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 06/14] KVM: guest_memfd: Report the size of holes in export lookups Fred Griffoul
` (8 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
PFN mode files cannot be attached yet, though a provider's frames are
the memory that most needs a device mapping that follows its revokes.
Allow them. A lookup asks the provider with lookup_pfn_range(), which
allocates nothing, then get_pfn() for the block and its attributes. A
revoke updates the exports before the frames are reclaimed. A punch hole
must remove the frames from the exports before the provider gets them
back, while the provider still backs them: lookups report the range as
holes until then, and the exports are updated again afterwards.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
include/linux/guest_memfd.h | 20 ++++---
virt/kvm/guest_memfd.c | 111 ++++++++++++++++++++++++++++++------
2 files changed, 105 insertions(+), 26 deletions(-)
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index ba3eebb1ed1a..937693ae9573 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -90,13 +90,13 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
* nor fault them. It attaches to the file, looks up the frame behind each
* shared page without taking a reference, and guest_memfd calls its update()
* whenever what a lookup returns for a range may have changed: conversions,
- * punch holes and memory failure. update() makes the consumer's mappings of
- * the range match what a lookup returns now: a frame, or a hole for a private
- * page or a page without a frame. It may be called for pages that did not
- * change, and for ranges that do not follow the blocks that lookups report:
- * the consumer may then remap whole blocks, also outside the range, e.g. to
- * merge them into larger mappings. A consumer that can fault may instead only
- * remove its mappings of the range.
+ * punch holes, memory failure and a provider's kvm_gmem_revoke(). update()
+ * makes the consumer's mappings of the range match what a lookup returns now:
+ * a frame, or a hole for a private page or a page without a frame. It may be
+ * called for pages that did not change, and for ranges that do not follow the
+ * blocks that lookups report: the consumer may then remap whole blocks, also
+ * outside the range, e.g. to merge them into larger mappings. A consumer that
+ * can fault may instead only remove its mappings of the range.
*
* Until update() returns, the frames it replaces stay valid, so a consumer
* may switch each mapping to its new target in one step; once it returns,
@@ -111,8 +111,10 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
* same time, also for overlapping ranges. The consumer holds a lock across a
* lookup and the mapping it installs, which update() also takes. Lock order:
*
- * guest_memfd invalidate lock
- * consumer lock
+ * PFN mode provider lock (across kvm_gmem_revoke())
+ * guest_memfd invalidate lock
+ * consumer lock
+ * provider leaf lock (get_pfn(), lookup_pfn_range())
*
* A consumer attaches and detaches outside its lock. KVM may be a module
* while the consumer is built in: consumers reach these with symbol_get().
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 65a951fa49c2..015f6657c386 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -40,6 +40,12 @@ struct gmem_inode {
const struct guest_memfd_provider_operations *provider_ops;
/* Subsystems that map the file, see include/linux/guest_memfd.h. */
struct list_head exports;
+ /*
+ * A PFN mode punch hole in progress: lookups find holes in
+ * [punch_start, punch_end) until the provider has the frames back.
+ * Written under the invalidate lock, read without it.
+ */
+ pgoff_t punch_start, punch_end;
u64 flags;
/*
@@ -515,15 +521,32 @@ static int __kvm_gmem_revoke(struct inode *inode, pgoff_t start,
kvm_gmem_invalidate_start(inode, start, end, filter);
if (kvm_gmem_is_pfn_mode(GMEM_I(inode))) {
+ struct gmem_inode *gi = GMEM_I(inode);
+
unmap_mapping_range(inode->i_mapping, offset, len, 0);
+ /*
+ * A punch hole gives the frames back only after the reclaim:
+ * until then, lookups find holes. Afterwards, the exports map
+ * what the provider backs the range with.
+ */
+ if (put_pfn_range) {
+ WRITE_ONCE(gi->punch_start, start);
+ /* Pairs with the smp_rmb() in kvm_gmem_export_pfn(). */
+ smp_wmb();
+ WRITE_ONCE(gi->punch_end, end);
+ }
+ kvm_gmem_update_exports(inode, start, end);
#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);
+ kvm_gmem_pfn_reclaim(gi, start, end);
#endif
- if (put_pfn_range)
- gmem_provider_put_pfn_range(GMEM_I(inode), start, nr_pages);
+ if (put_pfn_range) {
+ gmem_provider_put_pfn_range(gi, start, nr_pages);
+ WRITE_ONCE(gi->punch_end, 0);
+ kvm_gmem_update_exports(inode, start, end);
+ }
} else {
kvm_gmem_truncate(inode, start, end);
}
@@ -1258,8 +1281,7 @@ static struct file_operations kvm_gmem_fops = {
* guest_memfd calls @e->ops->update() from now on, and holds a reference to
* @file until kvm_gmem_export_detach(). May sleep.
*
- * Return: 0, -ENODEV if @file is not a guest_memfd file, or -EOPNOTSUPP in
- * PFN mode.
+ * Return: 0, or -ENODEV if @file is not a guest_memfd file.
*/
int kvm_gmem_export_attach(struct file *file, struct guest_memfd_export *e)
{
@@ -1267,8 +1289,6 @@ int kvm_gmem_export_attach(struct file *file, struct guest_memfd_export *e)
if (file->f_op != &kvm_gmem_fops)
return -ENODEV;
- if (kvm_gmem_is_pfn_mode(GMEM_I(inode)))
- return -EOPNOTSUPP;
e->file = get_file(file);
filemap_invalidate_lock(inode->i_mapping);
@@ -1310,28 +1330,83 @@ static bool kvm_gmem_range_is_shared(struct inode *inode, pgoff_t index,
return true;
}
+/*
+ * PFN mode: lookup_pfn_range() first, so that nothing is allocated, then
+ * get_pfn() for the order and attributes.
+ */
+static int kvm_gmem_export_pfn(struct gmem_inode *gi, pgoff_t index,
+ unsigned long *pfn, int *max_order, u32 *attrs)
+{
+ pgoff_t punch_start, punch_end;
+ int order = *max_order, block;
+ unsigned long raw_pfn;
+ size_t nr_pages;
+ kvm_pfn_t frame;
+ int r;
+
+ /*
+ * The end first: a new end comes with its start, and an old end of 0
+ * makes the window empty.
+ */
+ punch_end = READ_ONCE(gi->punch_end);
+ /* Pairs with the smp_wmb() in __kvm_gmem_revoke(). */
+ smp_rmb();
+ punch_start = READ_ONCE(gi->punch_start);
+ if (index >= punch_start && index < punch_end)
+ return -ENOENT;
+
+ r = gi->provider_ops->lookup_pfn_range(gi->provider, index, &raw_pfn,
+ &nr_pages);
+ if (r)
+ return r;
+
+ r = gmem_provider_get_pfn(gi, index, &frame, &block, attrs);
+ if (r)
+ return r == -EFAULT ? -ENOENT : r;
+
+ order = min(order, block);
+ if (frame ^ index)
+ order = min_t(int, order, __ffs64(frame ^ index));
+ /* Nor may the block reach into a punch hole. */
+ while (order &&
+ round_down(index, 1UL << order) < punch_end &&
+ round_up(index + 1, 1UL << order) > punch_start)
+ order--;
+ *pfn = frame;
+ *max_order = order;
+ return 0;
+}
+
/* What kvm_gmem_export_lookup_pfn() returns for a page inside the file. */
static int __kvm_gmem_export_lookup(struct inode *inode, pgoff_t index,
unsigned long *pfn, int *max_order,
u32 *attrs)
{
+ struct gmem_inode *gi = GMEM_I(inode);
struct folio *folio;
+ int r;
if (!kvm_gmem_range_is_shared(inode, index, 1))
return -ENOENT;
- /* A folio that was never zeroed is a hole. */
- folio = filemap_get_folio(inode->i_mapping, index);
- if (IS_ERR(folio))
- return -ENOENT;
- if (folio_test_hwpoison(folio) || !folio_test_uptodate(folio)) {
+ if (kvm_gmem_is_pfn_mode(gi)) {
+ r = kvm_gmem_export_pfn(gi, index, pfn, max_order, attrs);
+ if (r)
+ return r;
+ } else {
+ /* A folio that was never zeroed is a hole. */
+ folio = filemap_get_folio(inode->i_mapping, index);
+ if (IS_ERR(folio))
+ return -ENOENT;
+ if (folio_test_hwpoison(folio) || !folio_test_uptodate(folio)) {
+ folio_put(folio);
+ return -ENOENT;
+ }
+ *pfn = folio_file_pfn(folio, index);
+ *max_order = 0;
+ *attrs = 0;
folio_put(folio);
- return -ENOENT;
}
- *pfn = folio_file_pfn(folio, index);
- *max_order = 0;
- *attrs = 0;
- folio_put(folio);
/* Nor may the block hold a private page. */
while (*max_order &&
@@ -2042,6 +2117,8 @@ static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)
gi->provider_ops = NULL;
INIT_LIST_HEAD(&gi->gmem_file_list);
INIT_LIST_HEAD(&gi->exports);
+ gi->punch_start = 0;
+ gi->punch_end = 0;
return &gi->vfs_inode;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 06/14] KVM: guest_memfd: Report the size of holes in export lookups
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (4 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 05/14] KVM: guest_memfd: Export PFN mode files Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 07/14] KVM: guest_memfd: Update exports when pages are allocated Fred Griffoul
` (7 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
A lookup reports a hole as a single page, so a consumer walks a large
private or unbacked range one page at a time, and cannot map a large
hole with one entry, e.g. to a scratch page.
Report the largest aligned hole around the page, up to the order the
caller asks for. The search checks one half-block at a time, so it
costs about one lookup per page of the hole it finds.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
virt/kvm/guest_memfd.c | 48 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 46 insertions(+), 2 deletions(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 015f6657c386..1c5826c5384e 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -1417,6 +1417,48 @@ static int __kvm_gmem_export_lookup(struct inode *inode, pgoff_t index,
return 0;
}
+/* Does no page of [start, start + nr) have a frame to map? */
+static bool kvm_gmem_export_is_hole(struct inode *inode, pgoff_t start,
+ pgoff_t nr)
+{
+ unsigned long pfn;
+ pgoff_t index;
+ u32 attrs;
+ int order;
+
+ for (index = start; index < start + nr; index++) {
+ order = 0;
+ if (__kvm_gmem_export_lookup(inode, index, &pfn, &order,
+ &attrs) != -ENOENT)
+ return false;
+ cond_resched();
+ }
+ return true;
+}
+
+/*
+ * The order, up to @max_order, of the largest naturally aligned block around
+ * @index that lies inside the file and has no frame to map. Each step checks
+ * only the half of the larger block that does not hold @index.
+ */
+static int kvm_gmem_export_hole_order(struct inode *inode, pgoff_t index,
+ int max_order)
+{
+ pgoff_t npages = i_size_read(inode) >> PAGE_SHIFT;
+ int order;
+
+ for (order = 0; order < max_order; order++) {
+ pgoff_t n = 1UL << order;
+ pgoff_t first = round_down(index, 2 * n);
+
+ if (first + 2 * n > npages ||
+ !kvm_gmem_export_is_hole(inode, index & n ? first : first + n,
+ n))
+ break;
+ }
+ return order;
+}
+
/**
* kvm_gmem_export_lookup_pfn - The frame behind a shared page.
* @e: The export.
@@ -1424,7 +1466,8 @@ static int __kvm_gmem_export_lookup(struct inode *inode, pgoff_t index,
* @pfn: The frame.
* @max_order: In: the largest order the caller can map. Out: the order of
* the naturally aligned block around @index that is contiguous,
- * shared and of the same @attrs; 0 for -ENOENT.
+ * shared and of the same @attrs or, for -ENOENT, that has no
+ * frame to map at all.
* @attrs: The GUEST_MEMFD_PFN_* attributes of the block.
*
* Takes no reference and allocates nothing, and may be called from the
@@ -1438,6 +1481,7 @@ int kvm_gmem_export_lookup_pfn(struct guest_memfd_export *e, pgoff_t index,
unsigned long *pfn, int *max_order, u32 *attrs)
{
struct inode *inode = file_inode(e->file);
+ int order = *max_order;
int r;
if (index >= i_size_read(inode) >> PAGE_SHIFT)
@@ -1445,7 +1489,7 @@ int kvm_gmem_export_lookup_pfn(struct guest_memfd_export *e, pgoff_t index,
r = __kvm_gmem_export_lookup(inode, index, pfn, max_order, attrs);
if (r == -ENOENT)
- *max_order = 0;
+ *max_order = kvm_gmem_export_hole_order(inode, index, order);
return r;
}
EXPORT_SYMBOL_GPL(kvm_gmem_export_lookup_pfn);
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 07/14] KVM: guest_memfd: Update exports when pages are allocated
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (5 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 06/14] KVM: guest_memfd: Report the size of holes in export lookups Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 08/14] iommufd: Track the domains of revocable pages in struct iopt_pages Fred Griffoul
` (6 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
An export maps only the pages that have memory when it looks them up,
and a device cannot fault in the others, so guest_memfd must call
update() when a page gets memory.
Lookups treat a folio that was never zeroed as a hole. Zero folios in
one helper, under the invalidate lock, from the host fault, KVM's
get_pfn() and fallocate(), which did not zero at all, and update the
exports for each folio zeroed. Mark zeroed folios dirty: lookups hold no
reference, and memory-failure handling evicts clean folios that nothing
references.
In PFN mode, update the block that get_pfn() reports when it fills a
hole, since the provider may back all of it.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
include/linux/guest_memfd.h | 26 ++++++------
virt/kvm/guest_memfd.c | 85 +++++++++++++++++++++++++++++++------
2 files changed, 85 insertions(+), 26 deletions(-)
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index 937693ae9573..f8e89723dd68 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -90,13 +90,14 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
* nor fault them. It attaches to the file, looks up the frame behind each
* shared page without taking a reference, and guest_memfd calls its update()
* whenever what a lookup returns for a range may have changed: conversions,
- * punch holes, memory failure and a provider's kvm_gmem_revoke(). update()
- * makes the consumer's mappings of the range match what a lookup returns now:
- * a frame, or a hole for a private page or a page without a frame. It may be
- * called for pages that did not change, and for ranges that do not follow the
- * blocks that lookups report: the consumer may then remap whole blocks, also
- * outside the range, e.g. to merge them into larger mappings. A consumer that
- * can fault may instead only remove its mappings of the range.
+ * punch holes, memory failure, a provider's kvm_gmem_revoke(), fallocate() and
+ * pages getting their first frame. update() makes the consumer's mappings of
+ * the range match what a lookup returns now: a frame, or a hole for a private
+ * page or a page without a frame. It may be called for pages that did not
+ * change, and for ranges that do not follow the blocks that lookups report:
+ * the consumer may then remap whole blocks, also outside the range, e.g. to
+ * merge them into larger mappings. A consumer that can fault may instead only
+ * remove its mappings of the range.
*
* Until update() returns, the frames it replaces stay valid, so a consumer
* may switch each mapping to its new target in one step; once it returns,
@@ -105,11 +106,12 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
* beforehand.
*
* guest_memfd calls update() with its invalidate lock held, exclusive when it
- * takes frames away or pages convert and shared on memory failure, possibly
- * with a folio of the file locked; so update() may only call
- * kvm_gmem_export_lookup_pfn(). Under the shared lock, updates may run at the
- * same time, also for overlapping ranges. The consumer holds a lock across a
- * lookup and the mapping it installs, which update() also takes. Lock order:
+ * takes frames away or pages convert, and shared when pages only gain frames
+ * and on memory failure, possibly with a folio of the file locked; so update()
+ * may only call kvm_gmem_export_lookup_pfn(). Under the shared lock, updates
+ * may run at the same time, also for overlapping ranges. The consumer holds a
+ * lock across a lookup and the mapping it installs, which update() also takes.
+ * Lock order:
*
* PFN mode provider lock (across kvm_gmem_revoke())
* guest_memfd invalidate lock
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 1c5826c5384e..42bb5f263e23 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -286,7 +286,8 @@ static bool kvm_gmem_range_has_attributes(struct inode *inode,
* There is no backing storage for the memory, so the folio will remain
* up-to-date until it's removed.
*
- * Ignore accessed, referenced, and dirty flags. The memory is
+ * Ignore accessed and referenced flags; the dirty flag only keeps a zeroed
+ * folio in the page cache, see kvm_gmem_folio_zero(). The memory is
* unevictable and there is no storage to write back to.
*/
static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
@@ -430,7 +431,8 @@ static void kvm_gmem_invalidate_end(struct inode *inode, pgoff_t start,
/*
* What a lookup returns for [start, end) may have changed: exclusive lock when
- * frames were taken away or pages converted, at least shared on memory failure.
+ * frames were taken away or pages converted, at least shared when pages only
+ * gained frames and on memory failure.
*/
static void kvm_gmem_update_exports(struct inode *inode, pgoff_t start,
pgoff_t end)
@@ -590,6 +592,64 @@ int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
}
EXPORT_SYMBOL_GPL(kvm_gmem_revoke);
+/*
+ * Zero a folio on its first use. Lookups find it from then on, with no
+ * reference held, so mark it dirty: memory-failure handling drops clean
+ * folios that nothing references from the page cache (mapping_evict_folio()).
+ * Called with the folio locked. Returns true if the folio was zeroed.
+ */
+static bool kvm_gmem_folio_zero(struct folio *folio)
+{
+ if (folio_test_uptodate(folio))
+ return false;
+
+ folio_zero_range(folio, 0, folio_size(folio));
+ folio_mark_uptodate(folio);
+ folio_mark_dirty(folio);
+ return true;
+}
+
+/* Zero a folio on its first use, and tell the exports. Under the lock. */
+static void kvm_gmem_folio_clear(struct inode *inode, struct folio *folio)
+{
+ if (kvm_gmem_folio_zero(folio))
+ kvm_gmem_update_exports(inode, folio->index, folio_next_index(folio));
+}
+
+/*
+ * get_pfn() in PFN mode, which may back a hole: then tell the exports about
+ * the whole block that get_pfn() reports, as the provider may have backed all
+ * of it. Called with the invalidate lock held.
+ */
+static int kvm_gmem_pfn_get(struct inode *inode, pgoff_t index,
+ kvm_pfn_t *pfn, int *max_order, u32 *attrs)
+{
+ pgoff_t npages = i_size_read(inode) >> PAGE_SHIFT;
+ struct gmem_inode *gi = GMEM_I(inode);
+ bool hole = false;
+ unsigned long raw;
+ pgoff_t first;
+ int order, r;
+ size_t nr;
+
+ if (!list_empty(&gi->exports))
+ hole = gi->provider_ops->lookup_pfn_range(gi->provider, index,
+ &raw, &nr) == -ENOENT;
+
+ r = gmem_provider_get_pfn(gi, index, pfn, &order, attrs);
+ if (r)
+ return r;
+
+ if (hole) {
+ first = round_down(index, 1UL << order);
+ kvm_gmem_update_exports(inode, first,
+ min(first + (1UL << order), npages));
+ }
+ if (max_order)
+ *max_order = order;
+ return 0;
+}
+
static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
{
struct address_space *mapping = inode->i_mapping;
@@ -619,7 +679,7 @@ static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
break;
}
- r = gmem_provider_get_pfn(gi, index, &pfn, &max_order, NULL);
+ r = kvm_gmem_pfn_get(inode, index, &pfn, &max_order, NULL);
if (r)
break;
@@ -647,6 +707,8 @@ static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
index = folio_next_index(folio);
+ /* Zeroed now, so that a device can map it before first use. */
+ kvm_gmem_folio_clear(inode, folio);
folio_unlock(folio);
folio_put(folio);
@@ -819,7 +881,7 @@ static vm_fault_t kvm_gmem_pfn_fault(struct vm_fault *vmf)
u32 attrs;
int r;
- r = gmem_provider_get_pfn(GMEM_I(inode), vmf->pgoff, &pfn, NULL, &attrs);
+ r = kvm_gmem_pfn_get(inode, vmf->pgoff, &pfn, NULL, &attrs);
if (r)
return vmf_error(r);
@@ -873,6 +935,8 @@ static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
folio = kvm_gmem_get_folio(inode, vmf->pgoff);
if (IS_ERR(folio))
r = PTR_ERR(folio);
+ else if (!folio_test_large(folio))
+ kvm_gmem_folio_clear(inode, folio);
}
filemap_invalidate_unlock_shared(inode->i_mapping);
@@ -891,11 +955,6 @@ static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
goto out_folio;
}
- if (!folio_test_uptodate(folio)) {
- clear_highpage(folio_page(folio, 0));
- folio_mark_uptodate(folio);
- }
-
vmf->page = folio_file_page(folio, vmf->pgoff);
out_folio:
@@ -1887,7 +1946,8 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
}
if (kvm_gmem_is_pfn_mode(gi)) {
- r = gmem_provider_get_pfn(gi, index, pfn, max_order, attrs);
+ r = kvm_gmem_pfn_get(file_inode(file), index, pfn, max_order,
+ attrs);
if (r)
return ERR_PTR(r);
return NULL;
@@ -1903,10 +1963,7 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
return ERR_PTR(-EHWPOISON);
}
- if (!folio_test_uptodate(folio)) {
- clear_highpage(folio_page(folio, 0));
- folio_mark_uptodate(folio);
- }
+ kvm_gmem_folio_clear(file_inode(file), folio);
*pfn = folio_file_pfn(folio, index);
if (max_order)
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 08/14] iommufd: Track the domains of revocable pages in struct iopt_pages
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (6 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 07/14] KVM: guest_memfd: Update exports when pages are allocated Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 09/14] iommufd: Map guest_memfd files Fred Griffoul
` (5 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
guest_memfd pages, like dma-buf pages, can be taken away from iommufd
whatever references it holds, but the list of (area, domain) pairs to
unmap on a revoke lives in struct iopt_pages_dmabuf.
Move it to struct iopt_pages, rename the iopt_dmabuf_*track* helpers to
iopt_pages_*, and add iopt_pages_tracked() for the page types that need
it, so that other page types can use it. No functional change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
drivers/iommu/iommufd/io_pagetable.c | 24 +++++-----
drivers/iommu/iommufd/io_pagetable.h | 33 +++++++++-----
drivers/iommu/iommufd/pages.c | 67 ++++++++++++++--------------
3 files changed, 67 insertions(+), 57 deletions(-)
diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c
index 4e447ce74cf6..43d518e4ce58 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -1086,14 +1086,14 @@ static void iopt_unfill_domain(struct io_pagetable *iopt,
WARN_ON(!area->storage_domain);
if (area->storage_domain == domain)
area->storage_domain = storage_domain;
- if (iopt_is_dmabuf(pages)) {
+ if (iopt_pages_tracked(pages)) {
if (!iopt_dmabuf_revoked(pages))
iopt_area_unmap_domain(area, domain);
- iopt_dmabuf_untrack_domain(pages, area, domain);
+ iopt_pages_untrack_domain(pages, area, domain);
}
mutex_unlock(&pages->mutex);
- if (!iopt_is_dmabuf(pages))
+ if (!iopt_pages_tracked(pages))
iopt_area_unmap_domain(area, domain);
}
return;
@@ -1111,8 +1111,8 @@ static void iopt_unfill_domain(struct io_pagetable *iopt,
WARN_ON(area->storage_domain != domain);
area->storage_domain = NULL;
iopt_area_unfill_domain(area, pages, domain);
- if (iopt_is_dmabuf(pages))
- iopt_dmabuf_untrack_domain(pages, area, domain);
+ if (iopt_pages_tracked(pages))
+ iopt_pages_untrack_domain(pages, area, domain);
mutex_unlock(&pages->mutex);
}
}
@@ -1143,15 +1143,15 @@ static int iopt_fill_domain(struct io_pagetable *iopt,
continue;
guard(mutex)(&pages->mutex);
- if (iopt_is_dmabuf(pages)) {
- rc = iopt_dmabuf_track_domain(pages, area, domain);
+ if (iopt_pages_tracked(pages)) {
+ rc = iopt_pages_track_domain(pages, area, domain);
if (rc)
goto out_unfill;
}
rc = iopt_area_fill_domain(area, domain);
if (rc) {
- if (iopt_is_dmabuf(pages))
- iopt_dmabuf_untrack_domain(pages, area, domain);
+ if (iopt_pages_tracked(pages))
+ iopt_pages_untrack_domain(pages, area, domain);
goto out_unfill;
}
if (!area->storage_domain) {
@@ -1180,8 +1180,8 @@ static int iopt_fill_domain(struct io_pagetable *iopt,
area->storage_domain = NULL;
}
iopt_area_unfill_domain(area, pages, domain);
- if (iopt_is_dmabuf(pages))
- iopt_dmabuf_untrack_domain(pages, area, domain);
+ if (iopt_pages_tracked(pages))
+ iopt_pages_untrack_domain(pages, area, domain);
mutex_unlock(&pages->mutex);
}
return rc;
@@ -1393,7 +1393,7 @@ static int iopt_area_split(struct iopt_area *area, unsigned long iova)
return -EBUSY;
/* Maintaining the domains_itree below is a bit complicated */
- if (iopt_is_dmabuf(pages))
+ if (iopt_pages_tracked(pages))
return -EOPNOTSUPP;
if (new_start & (alignment - 1) ||
diff --git a/drivers/iommu/iommufd/io_pagetable.h b/drivers/iommu/iommufd/io_pagetable.h
index 27e3e311d395..0dddfb5db5fd 100644
--- a/drivers/iommu/iommufd/io_pagetable.h
+++ b/drivers/iommu/iommufd/io_pagetable.h
@@ -70,15 +70,14 @@ void iopt_area_unfill_domain(struct iopt_area *area, struct iopt_pages *pages,
void iopt_area_unmap_domain(struct iopt_area *area,
struct iommu_domain *domain);
-int iopt_dmabuf_track_domain(struct iopt_pages *pages, struct iopt_area *area,
- struct iommu_domain *domain);
-void iopt_dmabuf_untrack_domain(struct iopt_pages *pages,
- struct iopt_area *area,
- struct iommu_domain *domain);
-int iopt_dmabuf_track_all_domains(struct iopt_area *area,
- struct iopt_pages *pages);
-void iopt_dmabuf_untrack_all_domains(struct iopt_area *area,
- struct iopt_pages *pages);
+int iopt_pages_track_domain(struct iopt_pages *pages, struct iopt_area *area,
+ struct iommu_domain *domain);
+void iopt_pages_untrack_domain(struct iopt_pages *pages, struct iopt_area *area,
+ struct iommu_domain *domain);
+int iopt_pages_track_all_domains(struct iopt_area *area,
+ struct iopt_pages *pages);
+void iopt_pages_untrack_all_domains(struct iopt_area *area,
+ struct iopt_pages *pages);
static inline unsigned long iopt_area_index(struct iopt_area *area)
{
@@ -194,7 +193,8 @@ enum iopt_address_type {
IOPT_ADDRESS_DMABUF,
};
-struct iopt_pages_dmabuf_track {
+/* One (area, domain) mapping of revocable pages, on iopt_pages::tracker. */
+struct iopt_pages_track {
struct iommu_domain *domain;
struct iopt_area *area;
struct list_head elm;
@@ -205,7 +205,6 @@ struct iopt_pages_dmabuf {
struct phys_vec phys;
/* Always PAGE_SIZE aligned */
unsigned long start;
- struct list_head tracker;
};
/*
@@ -246,6 +245,12 @@ struct iopt_pages {
struct rb_root_cached access_itree;
/* Of iopt_area::pages_node */
struct rb_root_cached domains_itree;
+ /*
+ * Of iopt_pages_track::elm. The owner of these pages can revoke them
+ * whatever references iommufd holds, so every (area, domain) that
+ * maps them is listed here for the revoke. See iopt_pages_tracked().
+ */
+ struct list_head tracker;
};
static inline bool iopt_is_dmabuf(struct iopt_pages *pages)
@@ -255,6 +260,12 @@ static inline bool iopt_is_dmabuf(struct iopt_pages *pages)
return pages->type == IOPT_ADDRESS_DMABUF;
}
+/* The owner of the pages can revoke them, so their domains are tracked. */
+static inline bool iopt_pages_tracked(struct iopt_pages *pages)
+{
+ return iopt_is_dmabuf(pages);
+}
+
static inline bool iopt_dmabuf_revoked(struct iopt_pages *pages)
{
lockdep_assert_held(&pages->mutex);
diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index 404f31d8f729..cf0f08c4e9a6 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -1384,6 +1384,7 @@ static struct iopt_pages *iopt_alloc_pages(unsigned long start_byte,
pages->npages = DIV_ROUND_UP(length + start_byte, PAGE_SIZE);
pages->access_itree = RB_ROOT_CACHED;
pages->domains_itree = RB_ROOT_CACHED;
+ INIT_LIST_HEAD(&pages->tracker);
pages->writable = writable;
if (capable(CAP_IPC_LOCK))
pages->account_mode = IOPT_PAGES_ACCOUNT_NONE;
@@ -1434,13 +1435,13 @@ struct iopt_pages *iopt_alloc_file_pages(struct file *file,
static void iopt_revoke_notify(struct dma_buf_attachment *attach)
{
struct iopt_pages *pages = attach->importer_priv;
- struct iopt_pages_dmabuf_track *track;
+ struct iopt_pages_track *track;
guard(mutex)(&pages->mutex);
if (iopt_dmabuf_revoked(pages))
return;
- list_for_each_entry(track, &pages->dmabuf.tracker, elm) {
+ list_for_each_entry(track, &pages->tracker, elm) {
struct iopt_area *area = track->area;
iopt_area_unmap_domain_range(area, track->domain,
@@ -1557,7 +1558,6 @@ struct iopt_pages *iopt_alloc_dmabuf_pages(struct iommufd_ctx *ictx,
pages->account_mode = IOPT_PAGES_ACCOUNT_NONE;
pages->type = IOPT_ADDRESS_DMABUF;
pages->dmabuf.start = start - start_byte;
- INIT_LIST_HEAD(&pages->dmabuf.tracker);
rc = iopt_map_dmabuf(ictx, pages, dmabuf);
if (rc) {
@@ -1568,16 +1568,16 @@ struct iopt_pages *iopt_alloc_dmabuf_pages(struct iommufd_ctx *ictx,
return pages;
}
-int iopt_dmabuf_track_domain(struct iopt_pages *pages, struct iopt_area *area,
- struct iommu_domain *domain)
+int iopt_pages_track_domain(struct iopt_pages *pages, struct iopt_area *area,
+ struct iommu_domain *domain)
{
- struct iopt_pages_dmabuf_track *track;
+ struct iopt_pages_track *track;
lockdep_assert_held(&pages->mutex);
- if (WARN_ON(!iopt_is_dmabuf(pages)))
+ if (WARN_ON(!iopt_pages_tracked(pages)))
return -EINVAL;
- list_for_each_entry(track, &pages->dmabuf.tracker, elm)
+ list_for_each_entry(track, &pages->tracker, elm)
if (WARN_ON(track->domain == domain && track->area == area))
return -EINVAL;
@@ -1586,21 +1586,20 @@ int iopt_dmabuf_track_domain(struct iopt_pages *pages, struct iopt_area *area,
return -ENOMEM;
track->domain = domain;
track->area = area;
- list_add_tail(&track->elm, &pages->dmabuf.tracker);
+ list_add_tail(&track->elm, &pages->tracker);
return 0;
}
-void iopt_dmabuf_untrack_domain(struct iopt_pages *pages,
- struct iopt_area *area,
- struct iommu_domain *domain)
+void iopt_pages_untrack_domain(struct iopt_pages *pages, struct iopt_area *area,
+ struct iommu_domain *domain)
{
- struct iopt_pages_dmabuf_track *track;
+ struct iopt_pages_track *track;
lockdep_assert_held(&pages->mutex);
- WARN_ON(!iopt_is_dmabuf(pages));
+ WARN_ON(!iopt_pages_tracked(pages));
- list_for_each_entry(track, &pages->dmabuf.tracker, elm) {
+ list_for_each_entry(track, &pages->tracker, elm) {
if (track->domain == domain && track->area == area) {
list_del(&track->elm);
kfree(track);
@@ -1610,36 +1609,36 @@ void iopt_dmabuf_untrack_domain(struct iopt_pages *pages,
WARN_ON(true);
}
-int iopt_dmabuf_track_all_domains(struct iopt_area *area,
- struct iopt_pages *pages)
+int iopt_pages_track_all_domains(struct iopt_area *area,
+ struct iopt_pages *pages)
{
- struct iopt_pages_dmabuf_track *track;
+ struct iopt_pages_track *track;
struct iommu_domain *domain;
unsigned long index;
int rc;
- list_for_each_entry(track, &pages->dmabuf.tracker, elm)
+ list_for_each_entry(track, &pages->tracker, elm)
if (WARN_ON(track->area == area))
return -EINVAL;
xa_for_each(&area->iopt->domains, index, domain) {
- rc = iopt_dmabuf_track_domain(pages, area, domain);
+ rc = iopt_pages_track_domain(pages, area, domain);
if (rc)
goto err_untrack;
}
return 0;
err_untrack:
- iopt_dmabuf_untrack_all_domains(area, pages);
+ iopt_pages_untrack_all_domains(area, pages);
return rc;
}
-void iopt_dmabuf_untrack_all_domains(struct iopt_area *area,
- struct iopt_pages *pages)
+void iopt_pages_untrack_all_domains(struct iopt_area *area,
+ struct iopt_pages *pages)
{
- struct iopt_pages_dmabuf_track *track;
- struct iopt_pages_dmabuf_track *tmp;
+ struct iopt_pages_track *track;
+ struct iopt_pages_track *tmp;
- list_for_each_entry_safe(track, tmp, &pages->dmabuf.tracker,
+ list_for_each_entry_safe(track, tmp, &pages->tracker,
elm) {
if (track->area == area) {
list_del(&track->elm);
@@ -1656,6 +1655,7 @@ void iopt_release_pages(struct kref *kref)
WARN_ON(!RB_EMPTY_ROOT(&pages->domains_itree.rb_root));
WARN_ON(pages->npinned);
WARN_ON(!xa_empty(&pages->pinned_pfns));
+ WARN_ON(!list_empty(&pages->tracker));
if (iopt_is_dmabuf(pages) && pages->dmabuf.attach) {
struct dma_buf *dmabuf = pages->dmabuf.attach->dmabuf;
@@ -1664,7 +1664,6 @@ void iopt_release_pages(struct kref *kref)
dma_resv_unlock(dmabuf->resv);
dma_buf_detach(dmabuf, pages->dmabuf.attach);
dma_buf_put(dmabuf);
- WARN_ON(!list_empty(&pages->dmabuf.tracker));
} else if (pages->type == IOPT_ADDRESS_FILE) {
fput(pages->file);
}
@@ -1749,7 +1748,7 @@ static void __iopt_area_unfill_domain(struct iopt_area *area,
lockdep_assert_held(&pages->mutex);
- if (iopt_is_dmabuf(pages)) {
+ if (iopt_pages_tracked(pages)) {
if (WARN_ON(iopt_dmabuf_revoked(pages)))
return;
iopt_area_unmap_domain_range(area, domain, start_index,
@@ -1917,8 +1916,8 @@ int iopt_area_fill_domains(struct iopt_area *area, struct iopt_pages *pages)
return 0;
mutex_lock(&pages->mutex);
- if (iopt_is_dmabuf(pages)) {
- rc = iopt_dmabuf_track_all_domains(area, pages);
+ if (iopt_pages_tracked(pages)) {
+ rc = iopt_pages_track_all_domains(area, pages);
if (rc)
goto out_unlock;
}
@@ -1983,8 +1982,8 @@ int iopt_area_fill_domains(struct iopt_area *area, struct iopt_pages *pages)
}
pfn_reader_destroy(&pfns);
out_untrack:
- if (iopt_is_dmabuf(pages))
- iopt_dmabuf_untrack_all_domains(area, pages);
+ if (iopt_pages_tracked(pages))
+ iopt_pages_untrack_all_domains(area, pages);
out_unlock:
mutex_unlock(&pages->mutex);
return rc;
@@ -2024,8 +2023,8 @@ void iopt_area_unfill_domains(struct iopt_area *area, struct iopt_pages *pages)
WARN_ON(RB_EMPTY_NODE(&area->pages_node.rb));
interval_tree_remove(&area->pages_node, &pages->domains_itree);
iopt_area_unfill_domain(area, pages, area->storage_domain);
- if (iopt_is_dmabuf(pages))
- iopt_dmabuf_untrack_all_domains(area, pages);
+ if (iopt_pages_tracked(pages))
+ iopt_pages_untrack_all_domains(area, pages);
area->storage_domain = NULL;
out_unlock:
mutex_unlock(&pages->mutex);
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 09/14] iommufd: Map guest_memfd files
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (7 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 08/14] iommufd: Track the domains of revocable pages in struct iopt_pages Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 10/14] iommufd/selftest: Add mock-domain IOVA queries Fred Griffoul
` (4 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
A VMM that assigns a device to a guest backed by guest_memfd needs the
IOMMU mappings to follow the guest's shared memory. IOMMU_IOAS_MAP_FILE
pins what it maps, which guest_memfd does not allow.
Accept a guest_memfd in IOMMU_IOAS_MAP_FILE through guest_memfd's export
interface: its pages are not pinned, and each update() unmaps the range
and maps what a lookup returns now, read-only frames without write
permission. This is a demonstration: entries are PAGE_SIZE, a device
that touches the range during an update faults, and a range that fails
to map again stays unmapped, with a warning.
Dirty tracking, the no-IOMMU mode, kernel access, splitting an area and
a frame at address 0 are refused, since iommufd could not follow them.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
drivers/iommu/iommufd/io_pagetable.c | 14 +-
drivers/iommu/iommufd/io_pagetable.h | 30 ++-
drivers/iommu/iommufd/pages.c | 299 ++++++++++++++++++++++++++-
include/uapi/linux/iommufd.h | 11 +-
4 files changed, 349 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c
index 43d518e4ce58..62fa553cdffc 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -217,9 +217,13 @@ static int iopt_insert_area(struct io_pagetable *iopt, struct iopt_area *area,
return -EPERM;
area->iommu_prot = iommu_prot;
+ area->gmem = iopt_is_gmem(pages);
area->page_offset = start_byte % PAGE_SIZE;
if (area->page_offset & (iopt->iova_alignment - 1))
return -EINVAL;
+ /* guest_memfd frames are mapped whole, see iopt_gmem_map(). */
+ if (area->gmem && (area->page_offset || !PAGE_ALIGNED(length)))
+ return -EINVAL;
area->node.start = iova;
if (check_add_overflow(iova, length - 1, &area->node.last))
@@ -289,6 +293,9 @@ static int iopt_alloc_area_pages(struct io_pagetable *iopt,
case IOPT_ADDRESS_DMABUF:
start = elm->start_byte + elm->pages->dmabuf.start;
break;
+ case IOPT_ADDRESS_GMEM:
+ start = elm->start_byte + elm->pages->gmem.start;
+ break;
}
rc = iopt_alloc_iova(iopt, dst_iova, start, length);
if (rc)
@@ -515,8 +522,13 @@ int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
if (!file)
return -EBADF;
- pages = iopt_alloc_file_pages(file, start_byte, start, length,
+ /* -ENODEV: not a guest_memfd, so map it as a memfd. */
+ pages = iopt_alloc_gmem_pages(file, start, length,
iommu_prot & IOMMU_WRITE);
+ if (PTR_ERR(pages) == -ENODEV)
+ pages = iopt_alloc_file_pages(file, start_byte, start,
+ length,
+ iommu_prot & IOMMU_WRITE);
fput(file);
if (IS_ERR(pages))
return PTR_ERR(pages);
diff --git a/drivers/iommu/iommufd/io_pagetable.h b/drivers/iommu/iommufd/io_pagetable.h
index 0dddfb5db5fd..11e16d22436a 100644
--- a/drivers/iommu/iommufd/io_pagetable.h
+++ b/drivers/iommu/iommufd/io_pagetable.h
@@ -6,6 +6,7 @@
#define __IO_PAGETABLE_H
#include <linux/dma-buf.h>
+#include <linux/guest_memfd.h>
#include <linux/interval_tree.h>
#include <linux/kref.h>
#include <linux/mutex.h>
@@ -13,6 +14,8 @@
#include "iommufd_private.h"
+struct mem_cgroup;
+
struct iommu_domain;
/*
@@ -48,6 +51,8 @@ struct iopt_area {
/* IOMMU_READ, IOMMU_WRITE, etc */
int iommu_prot;
bool prevent_access : 1;
+ /* The pages come from a guest_memfd and may have holes */
+ bool gmem : 1;
unsigned int num_accesses;
unsigned int num_locks;
};
@@ -191,6 +196,7 @@ enum iopt_address_type {
IOPT_ADDRESS_USER = 0,
IOPT_ADDRESS_FILE,
IOPT_ADDRESS_DMABUF,
+ IOPT_ADDRESS_GMEM,
};
/* One (area, domain) mapping of revocable pages, on iopt_pages::tracker. */
@@ -207,6 +213,17 @@ struct iopt_pages_dmabuf {
unsigned long start;
};
+struct iopt_pages_gmem {
+ struct guest_memfd_export export;
+ /* From symbol_get(): KVM may be a module */
+ typeof(kvm_gmem_export_lookup_pfn) *lookup_pfn;
+ typeof(kvm_gmem_export_detach) *detach;
+ /* Byte offset in the guest_memfd, always PAGE_SIZE aligned */
+ unsigned long start;
+ /* Charged for the page tables that a fill allocates */
+ struct mem_cgroup *memcg;
+};
+
/*
* This holds a pinned page list for multiple areas of IO address space. The
* pages always originate from a linear chunk of userspace VA. Multiple
@@ -236,6 +253,8 @@ struct iopt_pages {
};
/* IOPT_ADDRESS_DMABUF */
struct iopt_pages_dmabuf dmabuf;
+ /* IOPT_ADDRESS_GMEM */
+ struct iopt_pages_gmem gmem;
};
bool writable:1;
u8 account_mode;
@@ -260,10 +279,15 @@ static inline bool iopt_is_dmabuf(struct iopt_pages *pages)
return pages->type == IOPT_ADDRESS_DMABUF;
}
+static inline bool iopt_is_gmem(struct iopt_pages *pages)
+{
+ return pages->type == IOPT_ADDRESS_GMEM;
+}
+
/* The owner of the pages can revoke them, so their domains are tracked. */
static inline bool iopt_pages_tracked(struct iopt_pages *pages)
{
- return iopt_is_dmabuf(pages);
+ return iopt_is_dmabuf(pages) || iopt_is_gmem(pages);
}
static inline bool iopt_dmabuf_revoked(struct iopt_pages *pages)
@@ -285,6 +309,10 @@ struct iopt_pages *iopt_alloc_dmabuf_pages(struct iommufd_ctx *ictx,
unsigned long start_byte,
unsigned long start,
unsigned long length, bool writable);
+struct iopt_pages *iopt_alloc_gmem_pages(struct file *file,
+ unsigned long start,
+ unsigned long length,
+ bool writable);
void iopt_release_pages(struct kref *kref);
static inline void iopt_put_pages(struct iopt_pages *pages)
{
diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index cf0f08c4e9a6..88354aa554cf 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -52,6 +52,7 @@
#include <linux/iommu.h>
#include <linux/iommufd.h>
#include <linux/kthread.h>
+#include <linux/memcontrol.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/sched/mm.h>
@@ -238,6 +239,40 @@ static void iommu_unmap_nofail(struct iommu_domain *domain, unsigned long iova,
WARN_ON(ret != size);
}
+/*
+ * guest_memfd pages are mapped with PAGE_SIZE entries, and may have holes.
+ * iommu_unmap() stops at the first IOVA that is not mapped, which
+ * iommu_unmap_nofail() warns about, so find the runs that are mapped and
+ * unmap each one, without touching the holes.
+ * iopt_gmem_map() refuses frame 0, which iova_to_phys() reports for
+ * an IOVA that is not mapped.
+ */
+static void iopt_gmem_unmap(struct iopt_area *area,
+ struct iommu_domain *domain,
+ unsigned long start_index,
+ unsigned long last_index)
+{
+ unsigned long iova = iopt_area_index_to_iova(area, start_index);
+ size_t left = (last_index - start_index + 1) * PAGE_SIZE;
+
+ /* A guest_memfd can be large: yield, once per page scanned. */
+ while (left) {
+ size_t len = 0;
+
+ while (len < left && iommu_iova_to_phys(domain, iova + len)) {
+ len += PAGE_SIZE;
+ cond_resched();
+ }
+ if (len)
+ iommu_unmap_nofail(domain, iova, len);
+ /* Step over the run and the hole page that ended it. */
+ len = min(len + PAGE_SIZE, left);
+ iova += len;
+ left -= len;
+ cond_resched();
+ }
+}
+
static void iopt_area_unmap_domain_range(struct iopt_area *area,
struct iommu_domain *domain,
unsigned long start_index,
@@ -245,6 +280,11 @@ static void iopt_area_unmap_domain_range(struct iopt_area *area,
{
unsigned long start_iova = iopt_area_index_to_iova(area, start_index);
+ if (area->gmem) {
+ iopt_gmem_unmap(area, domain, start_index, last_index);
+ return;
+ }
+
iommu_unmap_nofail(domain, start_iova,
iopt_area_index_to_iova_last(area, last_index) -
start_iova + 1);
@@ -1349,6 +1389,10 @@ static int pfn_reader_first(struct pfn_reader *pfns, struct iopt_pages *pages,
WARN_ON(last_index < start_index))
return -EINVAL;
+ /* guest_memfd pages are read from guest_memfd, see iopt_gmem_map() */
+ if (WARN_ON(iopt_is_gmem(pages)))
+ return -EINVAL;
+
rc = pfn_reader_init(pfns, pages, start_index, last_index);
if (rc)
return rc;
@@ -1647,6 +1691,230 @@ void iopt_pages_untrack_all_domains(struct iopt_area *area,
}
}
+static int iopt_gmem_prot(struct iopt_area *area, u32 attrs)
+{
+ int prot = area->iommu_prot;
+
+ if (attrs & GUEST_MEMFD_PFN_READONLY)
+ prot &= ~IOMMU_WRITE;
+ return prot;
+}
+
+/*
+ * Map what guest_memfd backs in [start_index, last_index] of the area into
+ * @domain, with PAGE_SIZE entries, so that updating part of a large block
+ * never has to cut a larger IOMMU page. On failure, nothing in the range is
+ * mapped.
+ */
+static int iopt_gmem_map(struct iopt_area *area, struct iopt_pages *pages,
+ struct iommu_domain *domain, unsigned long start_index,
+ unsigned long last_index)
+{
+ struct guest_memfd_export *e = &pages->gmem.export;
+ unsigned long base = pages->gmem.start >> PAGE_SHIFT;
+ unsigned long index = start_index;
+ int rc = 0;
+
+ lockdep_assert_held(&pages->mutex);
+
+ /* A revoke would race a dirty bitmap read and lose its dirty bits. */
+ if (domain->dirty_ops)
+ return -EOPNOTSUPP;
+
+ /* The device of a no-IOMMU domain keeps the addresses after a revoke. */
+ if (IS_ENABLED(CONFIG_IOMMUFD_NOIOMMU) &&
+ domain->owner == &iommufd_noiommu_ops)
+ return -EOPNOTSUPP;
+
+ while (index <= last_index) {
+ unsigned long pfn, iova, end, nr;
+ /* No larger than the rest of the range: holes cost per page. */
+ int order = min_t(int, PUD_ORDER, fls_long(last_index - index));
+ u32 attrs;
+ int prot;
+
+ cond_resched();
+
+ rc = pages->gmem.lookup_pfn(e, base + index, &pfn, &order,
+ &attrs);
+ /* The aligned block of frames, or of holes, around index. */
+ end = ALIGN(base + index + 1, 1UL << order) - base;
+ nr = min(end, last_index + 1) - index;
+ if (rc == -ENOENT) {
+ index += nr;
+ continue;
+ }
+ /* Frame 0 would look unmapped to iopt_gmem_unmap(). */
+ if (!rc && !pfn)
+ rc = -EINVAL;
+ if (rc)
+ goto err_unmap;
+
+ iova = iopt_area_index_to_iova(area, index);
+ prot = iopt_gmem_prot(area, attrs);
+
+ /* A read-only frame in an area that is not readable: a hole. */
+ if (!(prot & (IOMMU_READ | IOMMU_WRITE))) {
+ index += nr;
+ continue;
+ }
+
+ rc = batch_iommu_map_small(domain, iova, PFN_PHYS(pfn),
+ nr * PAGE_SIZE, prot);
+ if (rc)
+ goto err_unmap;
+ index += nr;
+ }
+ return 0;
+
+err_unmap:
+ if (index > start_index)
+ iopt_gmem_unmap(area, domain, start_index, index - 1);
+ return rc;
+}
+
+/* Map the whole area into every domain of its io_pagetable. */
+static int iopt_gmem_fill_domains(struct iopt_area *area,
+ struct iopt_pages *pages)
+{
+ struct iommu_domain *domain, *undo;
+ unsigned long index, undo_index;
+ int rc;
+
+ xa_for_each(&area->iopt->domains, index, domain) {
+ rc = iopt_gmem_map(area, pages, domain, iopt_area_index(area),
+ iopt_area_last_index(area));
+ if (rc)
+ goto err_unmap;
+ }
+ return 0;
+
+err_unmap:
+ xa_for_each(&area->iopt->domains, undo_index, undo) {
+ if (undo_index >= index)
+ break;
+ iopt_gmem_unmap(area, undo, iopt_area_index(area),
+ iopt_area_last_index(area));
+ }
+ return rc;
+}
+
+/*
+ * guest_memfd says that what [start, start + nr_pages) of the file holds may
+ * have changed: in every (area, domain) over part of it, unmap that part and
+ * map what guest_memfd backs now. A device that accesses it in between, or
+ * after a failure to map, faults.
+ */
+static void iopt_gmem_update(struct guest_memfd_export *e, pgoff_t start,
+ pgoff_t nr_pages)
+{
+ struct iopt_pages *pages =
+ container_of(e, struct iopt_pages, gmem.export);
+ pgoff_t base = pages->gmem.start >> PAGE_SHIFT;
+ struct iopt_pages_track *track;
+ struct mem_cgroup *old_memcg;
+ unsigned long first, last;
+
+ if (start + nr_pages <= base || start >= base + pages->npages)
+ return;
+ first = max(start, base) - base;
+ last = min(start + nr_pages, base + pages->npages) - base - 1;
+
+ guard(mutex)(&pages->mutex);
+ /* The page tables belong to whoever mapped the file, not to this task. */
+ old_memcg = set_active_memcg(pages->gmem.memcg);
+ list_for_each_entry(track, &pages->tracker, elm) {
+ struct iopt_area *area = track->area;
+ unsigned long s = max(first, iopt_area_index(area));
+ unsigned long l = min(last, iopt_area_last_index(area));
+
+ if (s > l)
+ continue;
+ iopt_gmem_unmap(area, track->domain, s, l);
+ if (iopt_gmem_map(area, pages, track->domain, s, l))
+ pr_warn_ratelimited("iommufd: guest_memfd pages left unmapped after an update\n");
+ }
+ set_active_memcg(old_memcg);
+}
+
+static const struct guest_memfd_export_ops iopt_gmem_export_ops = {
+ .update = iopt_gmem_update,
+};
+
+/**
+ * iopt_alloc_gmem_pages() - Pages backed by a guest_memfd file
+ * @file: The file
+ * @start: Byte offset in the file
+ * @length: Number of bytes
+ * @writable: The pages may be mapped writable
+ *
+ * The pages are not pinned, and every domain that maps them follows
+ * guest_memfd's changes.
+ *
+ * Return: the pages, ERR_PTR(-ENODEV) if @file is not a guest_memfd file, or
+ * another ERR_PTR().
+ */
+struct iopt_pages *iopt_alloc_gmem_pages(struct file *file,
+ unsigned long start,
+ unsigned long length,
+ bool writable)
+{
+ static struct lock_class_key pages_gmem_mutex_key;
+ int (*attach)(struct file *file, struct guest_memfd_export *e);
+ struct iopt_pages *pages;
+ unsigned long end;
+ int rc;
+
+ /* KVM may be a module, or not loaded: then this is no guest_memfd. */
+ attach = symbol_get(kvm_gmem_export_attach);
+ if (!attach)
+ return ERR_PTR(-ENODEV);
+
+ pages = iopt_alloc_pages(0, length, writable);
+ if (IS_ERR(pages))
+ goto out_put;
+
+ /*
+ * Pinned pages take the mmap_lock under their mutex. The mutex of
+ * guest_memfd pages is taken under guest_memfd's invalidate lock, which
+ * nests in the mmap_lock of a host fault, so give it its own class.
+ */
+ lockdep_set_class(&pages->mutex, &pages_gmem_mutex_key);
+
+ /* guest_memfd pages are not pinned, so they are not accounted. */
+ pages->account_mode = IOPT_PAGES_ACCOUNT_NONE;
+ pages->type = IOPT_ADDRESS_GMEM;
+ pages->gmem.start = start;
+ pages->gmem.export.ops = &iopt_gmem_export_ops;
+
+ rc = attach(file, &pages->gmem.export);
+ if (rc) {
+ iopt_put_pages(pages);
+ pages = ERR_PTR(rc);
+ goto out_put;
+ }
+
+ if (check_add_overflow(start, length, &end) ||
+ end > i_size_read(file_inode(file))) {
+ rc = -EINVAL;
+ goto err_detach;
+ }
+
+ /* The file holds the KVM module from here on. */
+ pages->gmem.lookup_pfn = symbol_get(kvm_gmem_export_lookup_pfn);
+ pages->gmem.detach = symbol_get(kvm_gmem_export_detach);
+ pages->gmem.memcg = get_mem_cgroup_from_mm(current->mm);
+ goto out_put;
+
+err_detach:
+ pages->gmem.detach = symbol_get(kvm_gmem_export_detach);
+ iopt_put_pages(pages);
+ pages = ERR_PTR(rc);
+out_put:
+ symbol_put(kvm_gmem_export_attach);
+ return pages;
+}
+
void iopt_release_pages(struct kref *kref)
{
struct iopt_pages *pages = container_of(kref, struct iopt_pages, kref);
@@ -1664,6 +1932,14 @@ void iopt_release_pages(struct kref *kref)
dma_resv_unlock(dmabuf->resv);
dma_buf_detach(dmabuf, pages->dmabuf.attach);
dma_buf_put(dmabuf);
+ } else if (iopt_is_gmem(pages)) {
+ if (pages->gmem.detach) {
+ pages->gmem.detach(&pages->gmem.export);
+ symbol_put(kvm_gmem_export_detach);
+ }
+ if (pages->gmem.lookup_pfn)
+ symbol_put(kvm_gmem_export_lookup_pfn);
+ mem_cgroup_put(pages->gmem.memcg);
} else if (pages->type == IOPT_ADDRESS_FILE) {
fput(pages->file);
}
@@ -1814,6 +2090,12 @@ static void iopt_area_unfill_partial_domain(struct iopt_area *area,
*/
void iopt_area_unmap_domain(struct iopt_area *area, struct iommu_domain *domain)
{
+ if (area->gmem) {
+ iopt_gmem_unmap(area, domain, iopt_area_index(area),
+ iopt_area_last_index(area));
+ return;
+ }
+
iommu_unmap_nofail(domain, iopt_area_iova(area),
iopt_area_length(area));
}
@@ -1857,6 +2139,11 @@ int iopt_area_fill_domain(struct iopt_area *area, struct iommu_domain *domain)
if (iopt_dmabuf_revoked(area->pages))
return 0;
+ if (iopt_is_gmem(area->pages))
+ return iopt_gmem_map(area, area->pages, domain,
+ iopt_area_index(area),
+ iopt_area_last_index(area));
+
rc = pfn_reader_first(&pfns, area->pages, iopt_area_index(area),
iopt_area_last_index(area));
if (rc)
@@ -1922,7 +2209,11 @@ int iopt_area_fill_domains(struct iopt_area *area, struct iopt_pages *pages)
goto out_unlock;
}
- if (!iopt_dmabuf_revoked(pages)) {
+ if (iopt_is_gmem(pages)) {
+ rc = iopt_gmem_fill_domains(area, pages);
+ if (rc)
+ goto out_untrack;
+ } else if (!iopt_dmabuf_revoked(pages)) {
rc = pfn_reader_first(&pfns, pages, iopt_area_index(area),
iopt_area_last_index(area));
if (rc)
@@ -2361,7 +2652,7 @@ int iopt_pages_rw_access(struct iopt_pages *pages, unsigned long start_byte,
if ((flags & IOMMUFD_ACCESS_RW_WRITE) && !pages->writable)
return -EPERM;
- if (iopt_is_dmabuf(pages))
+ if (iopt_is_dmabuf(pages) || iopt_is_gmem(pages))
return -EINVAL;
if (pages->type != IOPT_ADDRESS_USER)
@@ -2453,6 +2744,10 @@ int iopt_area_add_access(struct iopt_area *area, unsigned long start_index,
if (iopt_is_dmabuf(pages))
return -EINVAL;
+ /* guest_memfd frames may have no struct page, and are not pinned. */
+ if (iopt_is_gmem(pages))
+ return -EINVAL;
+
mutex_lock(&pages->mutex);
access = iopt_pages_get_exact_access(pages, start_index, last_index);
if (access) {
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 206fa667c782..db5f09c21d1f 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -252,7 +252,7 @@ struct iommu_ioas_noiommu_get_pa {
* @size: sizeof(struct iommu_ioas_map_file)
* @flags: same as for iommu_ioas_map
* @ioas_id: same as for iommu_ioas_map
- * @fd: the memfd or supported dma-buf file to map
+ * @fd: the memfd, guest_memfd or supported dma-buf file to map
* @start: byte offset from start of the file to map from
* @length: same as for iommu_ioas_map
* @iova: same as for iommu_ioas_map
@@ -263,6 +263,15 @@ struct iommu_ioas_noiommu_get_pa {
* VFIO PCI dma-bufs exported through VFIO_DEVICE_FEATURE_DMA_BUF, and
* other dma-bufs may be rejected. All other arguments and semantics match
* those of IOMMU_IOAS_MAP.
+ *
+ * A guest_memfd is also accepted, with @start and @length page aligned. Its
+ * pages are not pinned: only its shared pages that have memory are mapped, a
+ * page as soon as it gets memory, and the mapping follows conversions, punch
+ * holes and its provider's changes. Read-only pages are mapped without write
+ * permission, or not at all without IOMMU_IOAS_MAP_READABLE. Such a mapping
+ * cannot be split by a partial unmap, accessed by the kernel, moved by
+ * IOMMU_IOAS_CHANGE_PROCESS, or used with dirty tracking or the no-IOMMU mode,
+ * and a page at physical address 0 cannot be mapped.
*/
struct iommu_ioas_map_file {
__u32 size;
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 10/14] iommufd/selftest: Add mock-domain IOVA queries
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (8 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 09/14] iommufd: Map guest_memfd files Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 11/14] KVM: guest_memfd: Add kvm_gmem_restore() for PFN mode providers Fred Griffoul
` (3 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
Tests of guest_memfd mappings cannot know in advance which frame an
IOVA reaches, since a revoke can unmap or replace it, and
IOMMU_TEST_OP_MD_CHECK_MAP only compares a range against a user buffer.
Add IOMMU_TEST_OP_MD_CHECK_MAPPED, which checks that a range of a mock
domain is all mapped or all unmapped, and IOMMU_TEST_OP_MD_IOVA_TO_PHYS,
which returns the physical address behind an IOVA. They hold
domains_rwsem for writing, so that a userspace unmap cannot free a
page-table level during the walk; they are not safe against a
concurrent guest_memfd update or dma-buf revoke, which the selftests do
not do.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
drivers/iommu/iommufd/iommufd_test.h | 18 ++++
drivers/iommu/iommufd/selftest.c | 125 +++++++++++++++++++++++++++
2 files changed, 143 insertions(+)
diff --git a/drivers/iommu/iommufd/iommufd_test.h b/drivers/iommu/iommufd/iommufd_test.h
index 52b78cbcc920..3fffcb2b20d7 100644
--- a/drivers/iommu/iommufd/iommufd_test.h
+++ b/drivers/iommu/iommufd/iommufd_test.h
@@ -31,6 +31,8 @@ enum {
IOMMU_TEST_OP_PASID_CHECK_HWPT,
IOMMU_TEST_OP_DMABUF_GET,
IOMMU_TEST_OP_DMABUF_REVOKE,
+ IOMMU_TEST_OP_MD_CHECK_MAPPED,
+ IOMMU_TEST_OP_MD_IOVA_TO_PHYS,
};
enum {
@@ -193,6 +195,22 @@ struct iommu_test_cmd {
__s32 dmabuf_fd;
__u32 revoked;
} dmabuf_revoke;
+ struct {
+ /*
+ * 1: every page in [iova, iova+length) must be mapped;
+ * 0: none of them may be. Any page in the other state
+ * fails with -ENOENT. Any other value, or a non-zero
+ * __reserved, is -EINVAL.
+ */
+ __u32 mapped;
+ __u32 __reserved;
+ __aligned_u64 iova;
+ __aligned_u64 length;
+ } check_mapped;
+ struct {
+ __aligned_u64 iova;
+ __aligned_u64 out_phys; /* 0 if unmapped */
+ } iova_to_phys;
};
__u32 last;
};
diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index ee706f18f7e9..378d7c46327e 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -2068,6 +2068,122 @@ static int iommufd_test_dmabuf_get(struct iommufd_ucmd *ucmd,
return rc;
}
+/*
+ * True if [iova, iova + length) lies inside the domain's aperture. An IOVA
+ * that the domain cannot map would read as unmapped, so the queries refuse
+ * it rather than report it as such.
+ */
+static bool mock_domain_covers(struct mock_iommu_domain *mock,
+ unsigned long iova, size_t length)
+{
+ struct iommu_domain_geometry *geo = &mock->domain.geometry;
+ unsigned long last;
+
+ if (check_add_overflow(iova, length - 1, &last))
+ return false;
+ return iova >= geo->aperture_start && last <= geo->aperture_end;
+}
+
+/*
+ * iova_to_phys() walks the page table, so it must not run while an unmap
+ * frees a level of it. A userspace unmap holds the IOAS domains_rwsem for
+ * reading while it unmaps the domains, so holding it for writing keeps such
+ * unmaps away. A guest_memfd update, e.g. on a conversion or a punch hole,
+ * and a dma-buf revoke unmap under their pages mutex only, so tests must not
+ * run these queries while one is in progress. A range can cross several
+ * areas and holes, so the queries take one IOAS-wide lock rather than the
+ * pages mutex of each area.
+ */
+static struct rw_semaphore *
+mock_domain_unmap_lock(struct iommufd_hw_pagetable *hwpt)
+{
+ return &to_hwpt_paging(hwpt)->ioas->iopt.domains_rwsem;
+}
+
+/*
+ * Report the physical address the mock domain resolves @iova to, or 0 if
+ * it is unmapped. Lets a test check that two IOVAs share one frame, or that
+ * an IOVA moved to another frame, without knowing the frames in advance.
+ * Not safe against a concurrent guest_memfd update or dma-buf revoke; see
+ * mock_domain_unmap_lock().
+ */
+static int iommufd_test_md_iova_to_phys(struct iommufd_ucmd *ucmd,
+ unsigned int mockpt_id,
+ unsigned long iova)
+{
+ struct iommu_test_cmd *cmd = ucmd->cmd;
+ struct iommufd_hw_pagetable *hwpt;
+ struct mock_iommu_domain *mock;
+ unsigned int page_size;
+ int rc;
+
+ hwpt = get_md_pagetable(ucmd, mockpt_id, &mock);
+ if (IS_ERR(hwpt))
+ return PTR_ERR(hwpt);
+
+ page_size = 1 << __ffs(mock->domain.pgsize_bitmap);
+ if (iova % page_size || !mock_domain_covers(mock, iova, page_size)) {
+ rc = -EINVAL;
+ goto out_put;
+ }
+ down_write(mock_domain_unmap_lock(hwpt));
+ cmd->iova_to_phys.out_phys =
+ mock->domain.ops->iova_to_phys(&mock->domain, iova);
+ up_write(mock_domain_unmap_lock(hwpt));
+ rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+out_put:
+ iommufd_put_object(ucmd->ictx, &hwpt->obj);
+ return rc;
+}
+
+/*
+ * Check that [@iova, @iova + @length) is fully mapped or fully unmapped. Not
+ * safe against a concurrent guest_memfd update or dma-buf revoke; see
+ * mock_domain_unmap_lock().
+ */
+static int iommufd_test_md_check_mapped(struct iommufd_ucmd *ucmd,
+ unsigned int mockpt_id,
+ unsigned long iova, size_t length,
+ u32 mapped, u32 reserved)
+{
+ struct iommufd_hw_pagetable *hwpt;
+ struct mock_iommu_domain *mock;
+ unsigned int page_size;
+ int rc = 0;
+
+ if (mapped > 1 || reserved)
+ return -EINVAL;
+
+ hwpt = get_md_pagetable(ucmd, mockpt_id, &mock);
+ if (IS_ERR(hwpt))
+ return PTR_ERR(hwpt);
+
+ page_size = 1 << __ffs(mock->domain.pgsize_bitmap);
+ if (iova % page_size || length % page_size || !length ||
+ !mock_domain_covers(mock, iova, length)) {
+ rc = -EINVAL;
+ goto out_put;
+ }
+
+ down_write(mock_domain_unmap_lock(hwpt));
+ for (; length; length -= page_size, iova += page_size) {
+ bool is_mapped =
+ mock->domain.ops->iova_to_phys(&mock->domain, iova) != 0;
+
+ if (is_mapped != mapped) {
+ rc = -ENOENT;
+ break;
+ }
+ /* A huge-page mock domain spans 2^38 bytes. */
+ cond_resched();
+ }
+ up_write(mock_domain_unmap_lock(hwpt));
+
+out_put:
+ iommufd_put_object(ucmd->ictx, &hwpt->obj);
+ return rc;
+}
+
static int iommufd_test_dmabuf_revoke(struct iommufd_ucmd *ucmd, int fd,
bool revoked)
{
@@ -2180,6 +2296,15 @@ int iommufd_test(struct iommufd_ucmd *ucmd)
return iommufd_test_dmabuf_revoke(ucmd,
cmd->dmabuf_revoke.dmabuf_fd,
cmd->dmabuf_revoke.revoked);
+ case IOMMU_TEST_OP_MD_CHECK_MAPPED:
+ return iommufd_test_md_check_mapped(ucmd, cmd->id,
+ cmd->check_mapped.iova,
+ cmd->check_mapped.length,
+ cmd->check_mapped.mapped,
+ cmd->check_mapped.__reserved);
+ case IOMMU_TEST_OP_MD_IOVA_TO_PHYS:
+ return iommufd_test_md_iova_to_phys(ucmd, cmd->id,
+ cmd->iova_to_phys.iova);
default:
return -EOPNOTSUPP;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 11/14] KVM: guest_memfd: Add kvm_gmem_restore() for PFN mode providers
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (9 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 10/14] iommufd/selftest: Add mock-domain IOVA queries Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 12/14] samples/kvm: Add read-only pages and large blocks to the sample provider Fred Griffoul
` (2 subsequent siblings)
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
A device cannot fault, so when a PFN mode provider gives frames to pages
that had none, or makes a read-only frame writable, a subsystem that
maps the file must be told. kvm_gmem_revoke() does not fit: it would
zap the guest's and the VMM's mappings and reclaim frames that a guest
may already use.
Add kvm_gmem_restore(), the counterpart to kvm_gmem_revoke() that the
sample provider leaves for users that cannot fault. It only updates the
exports, under the shared invalidate lock.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
arch/x86/kvm/Makefile | 1 +
include/linux/guest_memfd.h | 37 ++++++++++++++++++++++---------------
virt/kvm/guest_memfd.c | 35 +++++++++++++++++++++++++++++++++--
3 files changed, 56 insertions(+), 17 deletions(-)
diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile
index 25fb3d6e2032..3255280884b5 100644
--- a/arch/x86/kvm/Makefile
+++ b/arch/x86/kvm/Makefile
@@ -63,6 +63,7 @@ exports_grep_trailer := --include='*.[ch]' -nrw $(srctree)/virt/kvm $(srctree)/a
-e kvm_write_track_remove_gfn \
-e kvm_file_to_kvm_fn \
-e kvm_gmem_revoke \
+ -e kvm_gmem_restore \
-e kvm_gmem_export
# Force grep to emit a goofy group separator that can in turn be replaced with
diff --git a/include/linux/guest_memfd.h b/include/linux/guest_memfd.h
index f8e89723dd68..45572d929bd7 100644
--- a/include/linux/guest_memfd.h
+++ b/include/linux/guest_memfd.h
@@ -75,12 +75,18 @@ struct guest_memfd_provider_operations {
#ifdef CONFIG_KVM_GUEST_MEMFD
int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
const unsigned long *pfn);
+void kvm_gmem_restore(struct inode *inode, pgoff_t start, size_t nr_pages);
#else
static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
const unsigned long *pfn)
{
return -EOPNOTSUPP;
}
+
+static inline void kvm_gmem_restore(struct inode *inode, pgoff_t start,
+ size_t nr_pages)
+{
+}
#endif
/*
@@ -90,14 +96,15 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
* nor fault them. It attaches to the file, looks up the frame behind each
* shared page without taking a reference, and guest_memfd calls its update()
* whenever what a lookup returns for a range may have changed: conversions,
- * punch holes, memory failure, a provider's kvm_gmem_revoke(), fallocate() and
- * pages getting their first frame. update() makes the consumer's mappings of
- * the range match what a lookup returns now: a frame, or a hole for a private
- * page or a page without a frame. It may be called for pages that did not
- * change, and for ranges that do not follow the blocks that lookups report:
- * the consumer may then remap whole blocks, also outside the range, e.g. to
- * merge them into larger mappings. A consumer that can fault may instead only
- * remove its mappings of the range.
+ * punch holes, memory failure, a provider's kvm_gmem_revoke() or
+ * kvm_gmem_restore(), fallocate() and pages getting their first frame.
+ * update() makes the consumer's mappings of the range match what a lookup
+ * returns now: a frame, or a hole for a private page or a page without a
+ * frame. It may be called for pages that did not change, and for ranges that
+ * do not follow the blocks that lookups report: the consumer may then remap
+ * whole blocks, also outside the range, e.g. to merge them into larger
+ * mappings. A consumer that can fault may instead only remove its mappings of
+ * the range.
*
* Until update() returns, the frames it replaces stay valid, so a consumer
* may switch each mapping to its new target in one step; once it returns,
@@ -106,14 +113,14 @@ static inline int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_
* beforehand.
*
* guest_memfd calls update() with its invalidate lock held, exclusive when it
- * takes frames away or pages convert, and shared when pages only gain frames
- * and on memory failure, possibly with a folio of the file locked; so update()
- * may only call kvm_gmem_export_lookup_pfn(). Under the shared lock, updates
- * may run at the same time, also for overlapping ranges. The consumer holds a
- * lock across a lookup and the mapping it installs, which update() also takes.
- * Lock order:
+ * takes frames or write access away or pages convert, and shared when pages
+ * only gain frames or write access and on memory failure, possibly with a
+ * folio of the file locked; so update() may only call
+ * kvm_gmem_export_lookup_pfn(). Under the shared lock, updates may run at the
+ * same time, also for overlapping ranges. The consumer holds a lock across a
+ * lookup and the mapping it installs, which update() also takes. Lock order:
*
- * PFN mode provider lock (across kvm_gmem_revoke())
+ * PFN mode provider lock (across kvm_gmem_revoke() and kvm_gmem_restore())
* guest_memfd invalidate lock
* consumer lock
* provider leaf lock (get_pfn(), lookup_pfn_range())
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 42bb5f263e23..37ff2fae89ae 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -431,8 +431,8 @@ static void kvm_gmem_invalidate_end(struct inode *inode, pgoff_t start,
/*
* What a lookup returns for [start, end) may have changed: exclusive lock when
- * frames were taken away or pages converted, at least shared when pages only
- * gained frames and on memory failure.
+ * frames or write access were taken away or pages converted, at least shared
+ * when pages only gained frames or write access and on memory failure.
*/
static void kvm_gmem_update_exports(struct inode *inode, pgoff_t start,
pgoff_t end)
@@ -592,6 +592,37 @@ int kvm_gmem_revoke(struct inode *inode, pgoff_t start, size_t nr_pages,
}
EXPORT_SYMBOL_GPL(kvm_gmem_revoke);
+/**
+ * kvm_gmem_restore - Tell guest_memfd that memory of a range is now available.
+ * @inode: The guest_memfd inode.
+ * @start: Starting page index within the file.
+ * @nr_pages: Number of pages.
+ *
+ * A PFN mode provider calls this when it gives frames to pages that had none,
+ * unless get_pfn() does so for the block it reports, and when it makes a
+ * read-only frame writable. Nothing is taken away, so the guest and the VMM
+ * need nothing: they fault the pages in, or get write access on their next
+ * write. The subsystems that map the file cannot, and are told to map the
+ * range again. A change that takes something away, a frame or write access,
+ * needs kvm_gmem_revoke() instead, which updates them too. Does nothing past
+ * the end of the file.
+ *
+ * Context: May sleep. Must not be called with the provider's leaf lock held.
+ */
+void kvm_gmem_restore(struct inode *inode, pgoff_t start, size_t nr_pages)
+{
+ pgoff_t max_pages = i_size_read(inode) >> PAGE_SHIFT;
+
+ if (start >= max_pages)
+ return;
+ nr_pages = min_t(size_t, nr_pages, max_pages - start);
+
+ filemap_invalidate_lock_shared(inode->i_mapping);
+ kvm_gmem_update_exports(inode, start, start + nr_pages);
+ filemap_invalidate_unlock_shared(inode->i_mapping);
+}
+EXPORT_SYMBOL_GPL(kvm_gmem_restore);
+
/*
* Zero a folio on its first use. Lookups find it from then on, with no
* reference held, so mark it dirty: memory-failure handling drops clean
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 12/14] samples/kvm: Add read-only pages and large blocks to the sample provider
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (10 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 11/14] KVM: guest_memfd: Add kvm_gmem_restore() for PFN mode providers Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 13/14] samples/kvm: Back absent pages on demand in " Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 14/14] KVM: selftests: Test guest_memfd mapped by iommufd Fred Griffoul
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
The sample provider reports every page as writable and of order 0, and
does nothing when a page comes back, so tests cannot exercise read-only
frames, large mappings, or a device mapping that follows a page made
present again.
Add MPS_SET_READONLY, report the largest aligned block of pages in the
same state, and call kvm_gmem_restore() when pages become present or
writable again; making pages read only revokes them.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
samples/kvm/mem_provider_sample.c | 89 +++++++++++++++++++++++++++----
samples/kvm/mem_provider_sample.h | 9 ++++
2 files changed, 87 insertions(+), 11 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index f30654a60dd8..670e36207e59 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -6,7 +6,8 @@
*
* The control device /dev/mem_provider_sample creates provider files.
* Each file allocates its own contiguous memory from alloc_contig_pages().
- * Pages can be dynamically marked present or absent.
+ * Pages can be dynamically marked present or absent, and read only (for
+ * tests).
* See mem_provider_sample.h.
*
* Confidential VMs are not supported.
@@ -39,6 +40,7 @@ struct mps_info {
struct page *cma_pages; /* from alloc_contig_pages() */
unsigned long *absent; /* taken away by MPS_SET_PRESENT */
+ unsigned long *readonly; /* set by MPS_SET_READONLY */
struct inode *gmem_inode;
};
@@ -83,6 +85,9 @@ static void mps_set_present(struct mps_info *info, unsigned long start,
mutex_lock(&info->lock);
if (present) {
bitmap_clear(info->absent, start, end - start);
+ /* Tell the users of guest_memfd that cannot fault. */
+ if (info->gmem_inode)
+ kvm_gmem_restore(info->gmem_inode, start, end - start);
} else {
bitmap_set(info->absent, start, end - start);
if (info->gmem_inode) {
@@ -95,6 +100,31 @@ static void mps_set_present(struct mps_info *info, unsigned long start,
mutex_unlock(&info->lock);
}
+/*
+ * Change what get_pfn() reports for [start, end). Making it read only takes
+ * write access away, so revoke it: the users of the guest_memfd map it again
+ * read only. Making it writable takes nothing away, so only tell the users
+ * that cannot fault. The pages keep their contents.
+ */
+static void mps_set_readonly(struct mps_info *info, unsigned long start,
+ unsigned long end, bool value)
+{
+ unsigned long pfn = info->base_pfn + start;
+
+ mutex_lock(&info->lock);
+ if (value) {
+ bitmap_set(info->readonly, start, end - start);
+ if (info->gmem_inode)
+ kvm_gmem_revoke(info->gmem_inode, start, end - start,
+ &pfn);
+ } else {
+ bitmap_clear(info->readonly, start, end - start);
+ if (info->gmem_inode)
+ kvm_gmem_restore(info->gmem_inode, start, end - start);
+ }
+ mutex_unlock(&info->lock);
+}
+
static long mps_get_stats(struct mps_info *info, void __user *uarg)
{
struct mps_stats st = {};
@@ -114,11 +144,13 @@ static int mps_file_release(struct inode *inode, struct file *file)
if (info->cma_pages)
free_contig_range(info->base_pfn, info->npages);
bitmap_free(info->absent);
+ bitmap_free(info->readonly);
kfree(info);
return 0;
}
-static long mps_file_set_present(struct mps_info *info, void __user *uarg)
+static long mps_file_set_range(struct mps_info *info, void __user *uarg,
+ unsigned int cmd)
{
struct mps_set_present sp;
unsigned long start, end;
@@ -129,9 +161,13 @@ static long mps_file_set_present(struct mps_info *info, void __user *uarg)
if (sp.value > 1 || sp.pad)
return -EINVAL;
ret = mps_range(info, sp.offset, sp.len, &start, &end);
- if (!ret)
+ if (ret)
+ return ret;
+ if (cmd == MPS_SET_PRESENT)
mps_set_present(info, start, end, sp.value);
- return ret;
+ else
+ mps_set_readonly(info, start, end, sp.value);
+ return 0;
}
static long mps_file_ioctl(struct file *file, unsigned int cmd,
@@ -144,7 +180,8 @@ static long mps_file_ioctl(struct file *file, unsigned int cmd,
case MPS_GET_STATS:
return mps_get_stats(info, uarg);
case MPS_SET_PRESENT:
- return mps_file_set_present(info, uarg);
+ case MPS_SET_READONLY:
+ return mps_file_set_range(info, uarg, cmd);
default:
return -ENOTTY;
}
@@ -178,11 +215,28 @@ static void mps_gmem_release(void *provider)
fput(resource_file);
}
+/* Is every bit of @bitmap in [first, first + n) equal to @value? */
+static bool mps_bits_are(unsigned long *bitmap, unsigned long first,
+ unsigned long n, bool value)
+{
+ unsigned long end = first + n;
+
+ return (value ? find_next_zero_bit(bitmap, end, first) :
+ find_next_bit(bitmap, end, first)) >= end;
+}
+
+/*
+ * The frame behind @index, and the largest aligned block around it that is
+ * present and has the same attributes: the file's frames are contiguous.
+ * Lockless: a change is followed by kvm_gmem_revoke() or kvm_gmem_restore().
+ */
static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
int *max_order, u32 *attrs)
{
struct file *resource_file = provider;
struct mps_info *info = resource_file->private_data;
+ bool ro;
+ int order;
if (index >= info->npages)
return -EINVAL;
@@ -190,10 +244,22 @@ static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
if (test_bit(index, info->absent))
return -EFAULT;
+ ro = test_bit(index, info->readonly);
*pfn = info->base_pfn + index;
- if (max_order)
- *max_order = 0;
-
+ *attrs = ro ? GUEST_MEMFD_PFN_READONLY : 0;
+
+ if (!max_order)
+ return 0;
+ for (order = PUD_ORDER; order; order--) {
+ unsigned long first = ALIGN_DOWN(index, 1UL << order);
+ unsigned long n = 1UL << order;
+
+ if (first + n <= info->npages &&
+ mps_bits_are(info->absent, first, n, false) &&
+ mps_bits_are(info->readonly, first, n, ro))
+ break;
+ }
+ *max_order = order;
return 0;
}
@@ -255,9 +321,10 @@ static long mps_ctl_new_file(void __user *uarg)
return -ENOMEM;
info->absent = bitmap_zalloc(npages, GFP_KERNEL);
- if (!info->absent) {
+ info->readonly = bitmap_zalloc(npages, GFP_KERNEL);
+ if (!info->absent || !info->readonly) {
ret = -ENOMEM;
- goto err_free_info;
+ goto err_free_absent;
}
pages = alloc_contig_pages(npages, GFP_KERNEL | __GFP_ZERO,
@@ -293,7 +360,7 @@ static long mps_ctl_new_file(void __user *uarg)
free_contig_range(page_to_pfn(pages), npages);
err_free_absent:
bitmap_free(info->absent);
-err_free_info:
+ bitmap_free(info->readonly);
kfree(info);
return ret;
}
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index b9f10286cb30..57f494dc46fa 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -51,4 +51,13 @@ struct mps_set_present {
#define MPS_SET_PRESENT \
_IOW(MPS_IOCTL_BASE, 12, struct mps_set_present)
+/*
+ * ioctl on a provider fd, with the same argument as MPS_SET_PRESENT: report
+ * [@offset, @offset + @len) as read only (GUEST_MEMFD_PFN_READONLY, @value 1)
+ * or writable again (@value 0). Every mapping follows. For tests: the
+ * sample does not really share the pages.
+ */
+#define MPS_SET_READONLY \
+ _IOW(MPS_IOCTL_BASE, 13, struct mps_set_present)
+
#endif /* _SAMPLES_KVM_MEM_PROVIDER_SAMPLE_H */
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 13/14] samples/kvm: Back absent pages on demand in the sample provider
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (11 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 12/14] samples/kvm: Add read-only pages and large blocks to the sample provider Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
2026-10-27 15:46 ` [RFC PATCH 14/14] KVM: selftests: Test guest_memfd mapped by iommufd Fred Griffoul
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
The sample provider fails get_pfn() for an absent page and keeps its
frames on a punch hole, so tests cannot exercise a provider that backs
a hole when it is asked for a frame, one that backs more than the page
asked for, or one that frees the frames that a punch hole gives back.
Add MPS_NEW_FILE_FILL: get_pfn() then backs an absent page with the
whole 2 MiB block around it, and put_pfn_range() makes the pages absent
again.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
samples/kvm/mem_provider_sample.c | 29 ++++++++++++++++++++++++-----
samples/kvm/mem_provider_sample.h | 9 ++++++++-
2 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 670e36207e59..41e0ce5b6bc6 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -41,6 +41,7 @@ struct mps_info {
unsigned long *absent; /* taken away by MPS_SET_PRESENT */
unsigned long *readonly; /* set by MPS_SET_READONLY */
+ bool fill; /* MPS_NEW_FILE_FILL */
struct inode *gmem_inode;
};
@@ -62,8 +63,15 @@ static void mps_gmem_put_pfn_range(void *provider, pgoff_t index,
size_t nr_pages)
{
struct file *resource_file = provider;
+ struct mps_info *info = resource_file->private_data;
+ size_t i;
- mps_zero_range(resource_file->private_data, index, nr_pages);
+ mps_zero_range(info, index, nr_pages);
+ if (!info->fill || index >= info->npages)
+ return;
+ /* Lockless, like get_pfn(): guest_memfd holds its invalidate lock. */
+ for (i = index; i < min_t(size_t, index + nr_pages, info->npages); i++)
+ set_bit(i, info->absent);
}
/* Byte range [offset, offset + len) of @info as page indices. */
@@ -228,7 +236,9 @@ static bool mps_bits_are(unsigned long *bitmap, unsigned long first,
/*
* The frame behind @index, and the largest aligned block around it that is
* present and has the same attributes: the file's frames are contiguous.
- * Lockless: a change is followed by kvm_gmem_revoke() or kvm_gmem_restore().
+ * Lockless: a change is followed by kvm_gmem_revoke() or kvm_gmem_restore(),
+ * except a fill, which only gives frames to holes; guest_memfd tells its users
+ * about the block.
*/
static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
int *max_order, u32 *attrs)
@@ -241,8 +251,16 @@ static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
if (index >= info->npages)
return -EINVAL;
- if (test_bit(index, info->absent))
- return -EFAULT;
+ if (test_bit(index, info->absent)) {
+ unsigned long first = ALIGN_DOWN(index, PTRS_PER_PMD);
+ unsigned long i;
+
+ if (!info->fill)
+ return -EFAULT;
+ /* Absent pages are zeroed already. */
+ for (i = first; i < min(first + PTRS_PER_PMD, info->npages); i++)
+ clear_bit(i, info->absent);
+ }
ro = test_bit(index, info->readonly);
*pfn = info->base_pfn + index;
@@ -307,7 +325,7 @@ static long mps_ctl_new_file(void __user *uarg)
if (copy_from_user(&nf, uarg, sizeof(nf)))
return -EFAULT;
- if (nf.flags || nf.pad)
+ if ((nf.flags & ~MPS_NEW_FILE_FILL) || nf.pad)
return -EINVAL;
if (!nf.size || !PAGE_ALIGNED(nf.size))
return -EINVAL;
@@ -337,6 +355,7 @@ static long mps_ctl_new_file(void __user *uarg)
info->cma_pages = pages;
info->base_pfn = page_to_pfn(pages);
info->npages = npages;
+ info->fill = nf.flags & MPS_NEW_FILE_FILL;
mutex_init(&info->lock);
fd = get_unused_fd_flags(O_CLOEXEC);
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index 57f494dc46fa..45ec9605b593 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -19,11 +19,18 @@
* Returns the provider fd.
*/
struct mps_new_file {
- __u32 flags; /* Reserved, must be 0 */
+ __u32 flags; /* MPS_NEW_FILE_* */
__u32 pad;
__u64 size; /* bytes, page aligned */
};
+/*
+ * get_pfn() backs an absent page, with every page of the 2 MiB block around
+ * it, instead of failing, and put_pfn_range() takes the pages back: a
+ * provider that allocates on demand and frees on a punch hole, for tests.
+ */
+#define MPS_NEW_FILE_FILL (1U << 0)
+
#define MPS_NEW_FILE \
_IOW(MPS_IOCTL_BASE, 5, struct mps_new_file)
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread* [RFC PATCH 14/14] KVM: selftests: Test guest_memfd mapped by iommufd
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
` (12 preceding siblings ...)
2026-10-27 15:46 ` [RFC PATCH 13/14] samples/kvm: Back absent pages on demand in " Fred Griffoul
@ 2026-10-27 15:46 ` Fred Griffoul
13 siblings, 0 replies; 79+ messages in thread
From: Fred Griffoul @ 2026-10-27 15:46 UTC (permalink / raw)
To: Ackerley Tng, Paolo Bonzini, Sean Christopherson, Jason Gunthorpe,
Kevin Tian
Cc: Alex Williamson, Alexander Graf, Alexander Viro,
Alexey Kardashevskiy, Andrew Morton, Aneesh Kumar K . V,
Ashish Kalra, Baolin Wang, Baolu Lu, Bill Wendling,
Borislav Petkov, Catalin Marinas, Christian Brauner,
Christian König, Connor Williamson, Dave Hansen, Dave Jiang,
David Hildenbrand, David Rientjes, David Woodhouse, Erdem Aktas,
Frank van der Linden, Gregory Price, H . Peter Anvin,
Hugh Dickins, Ingo Molnar, Jacob Pan, James Houghton, Jan Kara,
Jianxiong Gao, Joerg Roedel, Joey Gouly, John Starks,
Jonathan Corbet, Justin Stitt, Lorenzo Stoakes, Marc Zyngier,
Michael Roth, Mike Rapoport, Nathan Chancellor, Nick Desaulniers,
Nicolin Chen, Oliver Upton, Pratyush Yadav, Randy Dunlap,
Robin Murphy, Samiullah Khawaja, Shuah Khan, Steffen Eiden,
Steven Price, Suzuki K Poulose, Tarun Sahu, Thomas Gleixner,
Tom Lendacky, Tycho Andersen, Vishal Annapurve, Vlastimil Babka,
Will Deacon, Xu Yilun, Yan Zhao, Zenghui Yu, x86, Shuah Khan,
Fuad Tabba, kvm, linux-doc, linux-fsdevel, linux-kernel,
linux-kselftest, linux-mm, llvm, iommu
From: Fred Griffoul <fgriffo@amazon.co.uk>
Nothing tests that a device mapping of a guest_memfd follows the file.
Add gmem_conversion_iommufd_test. A KVM_X86_SW_PROTECTED_VM maps its
guest_memfd into an iommufd mock domain, on native folios, a tmpfs
resource and the sample provider in PFN mode, and the test checks what
the guest, the VMM and the device see after each change: conversions,
allocations, punch holes, memory failure, and the provider's revokes and
restores. It is skipped without CONFIG_IOMMUFD_TEST, the PFN mode cases
without the sample, and memory failure without CONFIG_MEMORY_FAILURE.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../kvm/x86/gmem_conversion_iommufd_test.c | 956 ++++++++++++++++++
2 files changed, 957 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86/gmem_conversion_iommufd_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 5f7118bddfc3..4520c102b695 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -162,6 +162,7 @@ TEST_GEN_PROGS_x86 += x86/triple_fault_event_test
TEST_GEN_PROGS_x86 += x86/recalc_apic_map_test
TEST_GEN_PROGS_x86 += x86/aperfmperf_test
TEST_GEN_PROGS_x86 += x86/guest_memfd_conversions_test
+TEST_GEN_PROGS_x86 += x86/gmem_conversion_iommufd_test
TEST_GEN_PROGS_x86 += access_tracking_perf_test
TEST_GEN_PROGS_x86 += coalesced_io_test
TEST_GEN_PROGS_x86 += dirty_log_perf_test
diff --git a/tools/testing/selftests/kvm/x86/gmem_conversion_iommufd_test.c b/tools/testing/selftests/kvm/x86/gmem_conversion_iommufd_test.c
new file mode 100644
index 000000000000..5d17b0e2dd43
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/gmem_conversion_iommufd_test.c
@@ -0,0 +1,956 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * gmem_conversion_iommufd_test - guest_memfd mapped by iommufd.
+ *
+ * A software protected VM has its guest_memfd mapped into an iommufd mock
+ * domain, and converts pages between shared and private. A conversion to
+ * private must take the page away from the device before the guest can use
+ * it as private; a conversion to shared gives the device the frame again.
+ * The same scenario runs on each backing of a guest_memfd:
+ *
+ * - native folios;
+ * - folios from a tmpfs resource (GUEST_MEMFD_FLAG_USE_RESOURCE on a mount);
+ * - frames of a PFN mode provider (a file of the sample provider).
+ *
+ * The folio backings also check that the device gets the pages that
+ * fallocate() or a first host access allocates, whether the guest_memfd is
+ * mapped before or after, that it loses a page that memory failure poisons,
+ * and that two IOAS mapping one guest_memfd both follow it. The PFN mode
+ * provider then gets its own checks: read-only frames, which the guest and the
+ * VMM cannot write and the device keeps mapped, private frames, read-only
+ * frames as private holes, absent frames made private, the provider taking
+ * back a private page and giving it back, a punch hole, a 2 MiB private
+ * mapping, and a provider that backs a whole 2 MiB block when one page of it
+ * is asked for and frees it on a punch hole.
+ *
+ * Requires CONFIG_IOMMUFD_TEST; the PFN mode scenarios also require
+ * samples/kvm/mem_provider_sample.ko.
+ */
+#include <fcntl.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <linux/iommufd.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+
+/*
+ * The iommufd mock domain test interface. The iommufd selftest helpers
+ * bring their own harness, which does not mix with the KVM selftest library.
+ */
+#include "../../../../../drivers/iommu/iommufd/iommufd_test.h"
+#include "../../../../../samples/kvm/mem_provider_sample.h"
+
+#define PAGE 0x1000ULL
+#define HPAGE 0x200000ULL /* 2 MiB */
+#define FILE_SIZE 0x400000ULL /* 4 MiB per guest_memfd */
+#define GPA (1ULL << 32) /* the VM maps its file here */
+#define IOVA (1ULL << 28) /* inside the mock aperture */
+
+struct guest_args {
+ uint64_t write_gpa; /* 0 to skip */
+ uint64_t write_val;
+ uint64_t read_gpa; /* 0 to skip; the value goes to GUEST_SYNC */
+};
+
+static __maybe_unused void guest_code(struct guest_args *a)
+{
+ if (a->write_gpa)
+ *(volatile uint64_t *)a->write_gpa = a->write_val;
+ if (a->read_gpa)
+ GUEST_SYNC(*(volatile uint64_t *)a->read_gpa);
+ GUEST_DONE();
+}
+
+static int ctl = -1; /* /dev/mem_provider_sample */
+
+static int mps_new_file(uint64_t size)
+{
+ struct mps_new_file nf = { .size = size };
+ int fd = ioctl(ctl, MPS_NEW_FILE, &nf);
+
+ TEST_ASSERT(fd >= 0, "MPS_NEW_FILE(%#llx) errno=%d",
+ (unsigned long long)size, errno);
+ return fd;
+}
+
+/* MPS_SET_PRESENT or MPS_SET_READONLY on a range. */
+static void mps_set(int fd, unsigned long cmd, uint64_t off, uint64_t len,
+ bool value)
+{
+ struct mps_set_present sp = { .value = value, .offset = off,
+ .len = len };
+
+ TEST_ASSERT(!ioctl(fd, cmd, &sp), "MPS ioctl %#lx errno=%d", cmd,
+ errno);
+}
+
+struct vmm {
+ const char *name;
+ int child; /* the resource file, or -1 */
+ bool pfn; /* the resource is a provider file (PFN mode) */
+ int gmem; /* the guest_memfd backed by it */
+ int iommufd;
+ uint32_t ioas, stdev, hwpt;
+ struct kvm_vm *vm;
+ struct kvm_vcpu *vcpu;
+ void *hva; /* host mapping of the whole guest_memfd */
+ gva_t args_gva;
+};
+
+static void vmm_create_type(struct vmm *v, unsigned long type)
+{
+ const struct vm_shape shape = {
+ .mode = VM_MODE_DEFAULT,
+ .type = type,
+ };
+
+ v->vm = __vm_create_shape_with_one_vcpu(shape, &v->vcpu, 0, guest_code);
+ v->args_gva = vm_alloc_page(v->vm);
+}
+
+static bool vmm_iova_mapped(struct vmm *v, uint64_t iova, uint64_t len,
+ bool mapped)
+{
+ struct iommu_test_cmd cmd = {
+ .size = sizeof(cmd), .op = IOMMU_TEST_OP_MD_CHECK_MAPPED,
+ .id = v->hwpt,
+ .check_mapped = { .mapped = mapped, .iova = iova,
+ .length = len },
+ };
+
+ return !ioctl(v->iommufd, IOMMU_TEST_CMD, &cmd);
+}
+
+static uint64_t vmm_iova_phys(struct vmm *v, uint64_t iova)
+{
+ struct iommu_test_cmd cmd = {
+ .size = sizeof(cmd), .op = IOMMU_TEST_OP_MD_IOVA_TO_PHYS,
+ .id = v->hwpt,
+ .iova_to_phys = { .iova = iova },
+ };
+
+ if (ioctl(v->iommufd, IOMMU_TEST_CMD, &cmd))
+ return 0;
+ return cmd.iova_to_phys.out_phys;
+}
+
+/*
+ * Map the guest_memfd on a 2 MiB boundary: KVM uses large pages only if the
+ * memslot's guest and host addresses are aligned alike.
+ */
+static void *vmm_map_gmem(struct vmm *v)
+{
+ uint8_t *area, *aligned;
+
+ area = mmap(NULL, FILE_SIZE + HPAGE, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ TEST_ASSERT(area != MAP_FAILED, "%s: mmap(reserve) errno=%d", v->name,
+ errno);
+ aligned = (uint8_t *)(((uintptr_t)area + HPAGE - 1) & ~(HPAGE - 1));
+ if (aligned > area)
+ munmap(area, aligned - area);
+ munmap(aligned + FILE_SIZE, area + HPAGE - aligned);
+
+ return mmap(aligned, FILE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_FIXED, v->gmem, 0);
+}
+
+/*
+ * Give @resource_fd (a provider file of the sample, a tmpfs mount, or -1 for
+ * native folios) to KVM as the guest_memfd's resource, and the guest_memfd to
+ * iommufd.
+ */
+static void vmm_attach(struct vmm *v, int resource_fd)
+{
+ struct iommu_ioas_alloc alloc = { .size = sizeof(alloc) };
+ struct iommu_test_cmd mock = {
+ .size = sizeof(mock), .op = IOMMU_TEST_OP_MOCK_DOMAIN,
+ };
+ struct iommu_ioas_map_file map = {
+ .size = sizeof(map),
+ .flags = IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_READABLE |
+ IOMMU_IOAS_MAP_WRITEABLE,
+ .start = 0, .length = FILE_SIZE, .iova = IOVA,
+ };
+ int r;
+
+ uint64_t flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_INIT_SHARED;
+
+ v->child = resource_fd;
+ if (v->pfn)
+ flags |= GUEST_MEMFD_FLAG_PFN_MODE;
+
+ /* KVM: a guest_memfd on the resource, behind one memslot. */
+ if (resource_fd >= 0)
+ v->gmem = vm_create_guest_memfd_resource(v->vm, FILE_SIZE,
+ flags | GUEST_MEMFD_FLAG_USE_RESOURCE,
+ resource_fd);
+ else
+ v->gmem = vm_create_guest_memfd(v->vm, FILE_SIZE, flags);
+ v->hva = vmm_map_gmem(v);
+ TEST_ASSERT(v->hva != MAP_FAILED, "%s: mmap(guest_memfd) errno=%d",
+ v->name, errno);
+ r = __vm_set_user_memory_region2(v->vm, 10, KVM_MEM_GUEST_MEMFD, GPA,
+ FILE_SIZE, v->hva, v->gmem, 0);
+ TEST_ASSERT(!r, "%s: SET_USER_MEMORY_REGION2 errno=%d", v->name,
+ errno);
+ virt_map(v->vm, GPA, GPA, FILE_SIZE / PAGE);
+
+ /* iommufd: the guest_memfd, in an IOAS on a mock domain. */
+ v->iommufd = open("/dev/iommu", O_RDWR);
+ __TEST_REQUIRE(v->iommufd >= 0, "iommufd unavailable");
+ TEST_ASSERT(!ioctl(v->iommufd, IOMMU_IOAS_ALLOC, &alloc),
+ "IOAS_ALLOC errno=%d", errno);
+ v->ioas = alloc.out_ioas_id;
+
+ mock.id = v->ioas;
+ __TEST_REQUIRE(!ioctl(v->iommufd, IOMMU_TEST_CMD, &mock),
+ "no iommufd mock domain (CONFIG_IOMMUFD_TEST?)");
+ v->stdev = mock.mock_domain.out_stdev_id;
+ v->hwpt = mock.mock_domain.out_hwpt_id;
+
+ map.ioas_id = v->ioas;
+ map.fd = v->gmem;
+ TEST_ASSERT(!ioctl(v->iommufd, IOMMU_IOAS_MAP_FILE, &map),
+ "%s: IOAS_MAP_FILE(guest_memfd) errno=%d", v->name, errno);
+}
+
+/*
+ * Continue the guest. KVM_RUN fails with EFAULT for KVM_EXIT_MEMORY_FAULT,
+ * which several scenarios expect; the expect_*() helpers check the exit.
+ */
+static void vmm_resume(struct vmm *v)
+{
+ int r = _vcpu_run(v->vcpu);
+
+ TEST_ASSERT(!r || (errno == EFAULT &&
+ v->vcpu->run->exit_reason == KVM_EXIT_MEMORY_FAULT),
+ "%s: KVM_RUN r=%d errno=%d exit=%s", v->name, r, errno,
+ exit_reason_str(v->vcpu->run->exit_reason));
+}
+
+static void vmm_run(struct vmm *v, uint64_t write_gpa, uint64_t write_val,
+ uint64_t read_gpa)
+{
+ struct guest_args *a = addr_gva2hva(v->vm, v->args_gva);
+
+ a->write_gpa = write_gpa;
+ a->write_val = write_val;
+ a->read_gpa = read_gpa;
+ vcpu_arch_set_entry_point(v->vcpu, guest_code);
+ vcpu_args_set(v->vcpu, 1, v->args_gva);
+ vmm_resume(v);
+}
+
+static void expect_done(struct vmm *v)
+{
+ struct kvm_run *run = v->vcpu->run;
+ struct ucall uc;
+
+ TEST_ASSERT(run->exit_reason != KVM_EXIT_MEMORY_FAULT,
+ "%s: unexpected memory fault at gpa %#llx", v->name,
+ (unsigned long long)run->memory_fault.gpa);
+ switch (get_ucall(v->vcpu, &uc)) {
+ case UCALL_DONE:
+ return;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ default:
+ TEST_FAIL("%s: guest exit %s, ucall %lu", v->name,
+ exit_reason_str(run->exit_reason), uc.cmd);
+ }
+}
+
+static uint64_t expect_sync_then_done(struct vmm *v)
+{
+ struct ucall uc;
+ uint64_t val;
+
+ TEST_ASSERT(get_ucall(v->vcpu, &uc) == UCALL_SYNC, "%s: guest exit %s",
+ v->name, exit_reason_str(v->vcpu->run->exit_reason));
+ val = uc.args[1];
+ vmm_resume(v);
+ expect_done(v);
+ return val;
+}
+
+static void expect_memory_fault(struct vmm *v, uint64_t gpa)
+{
+ struct kvm_run *run = v->vcpu->run;
+
+ TEST_ASSERT(run->exit_reason == KVM_EXIT_MEMORY_FAULT,
+ "%s: want KVM_EXIT_MEMORY_FAULT, got %s", v->name,
+ exit_reason_str(run->exit_reason));
+ TEST_ASSERT(run->memory_fault.gpa == gpa,
+ "%s: fault at gpa %#llx, want %#llx", v->name,
+ (unsigned long long)run->memory_fault.gpa,
+ (unsigned long long)gpa);
+}
+
+static void vmm_destroy(struct vmm *v)
+{
+ close(v->iommufd);
+ kvm_vm_free(v->vm);
+ munmap(v->hva, FILE_SIZE);
+ close(v->gmem);
+ if (v->child >= 0)
+ close(v->child);
+}
+
+static sigjmp_buf host_jmp;
+
+static void host_sig(int sig)
+{
+ siglongjmp(host_jmp, sig);
+}
+
+/*
+ * Read @addr, or write @val to it, through a host mapping. Return the
+ * signal that the access raised, or 0.
+ */
+static int host_access(volatile uint64_t *addr, bool write, uint64_t val)
+{
+ struct sigaction sa = { .sa_handler = host_sig }, old_bus, old_segv;
+ int sig;
+
+ sigaction(SIGBUS, &sa, &old_bus);
+ sigaction(SIGSEGV, &sa, &old_segv);
+ sig = sigsetjmp(host_jmp, 1);
+ if (!sig) {
+ if (write)
+ *addr = val;
+ else
+ (void)*addr;
+ }
+ sigaction(SIGBUS, &old_bus, NULL);
+ sigaction(SIGSEGV, &old_segv, NULL);
+ return sig;
+}
+
+/*
+ * Poison the page at @addr with MADV_HWPOISON. Memory failure kills the task
+ * that maps a dirty page with SIGBUS, which counts as done. Returns 0, or
+ * the errno of madvise().
+ */
+static int host_poison(void *addr)
+{
+ struct sigaction sa = { .sa_handler = host_sig }, old_bus;
+ int r = 0;
+
+ sigaction(SIGBUS, &sa, &old_bus);
+ if (!sigsetjmp(host_jmp, 1) && madvise(addr, PAGE, MADV_HWPOISON))
+ r = errno;
+ sigaction(SIGBUS, &old_bus, NULL);
+ return r;
+}
+
+#define host_read_faults(p) (host_access(p, false, 0) == SIGBUS)
+#define host_write_faults(p, v) (host_access(p, true, v) == SIGBUS)
+
+#define MAGIC 0xc0c0a000c0c0a000ULL
+
+/* The page that is converted, inside the file. */
+#define CONV_OFF (16 * PAGE)
+#define CONV_GPA (GPA + CONV_OFF)
+#define CONV_IOVA (IOVA + CONV_OFF)
+
+/* Pages that only fallocate() or the host touch first. */
+#define FALLOC_OFF (32 * PAGE)
+#define FALLOC2_OFF (33 * PAGE)
+#define HOST_OFF (34 * PAGE)
+
+/* A page that memory failure poisons, and its neighbour. */
+#define POISON_OFF (80 * PAGE)
+#define POISON_IOVA (IOVA + POISON_OFF)
+
+/* Pages of the file that the PFN mode checks use. */
+#define RO_OFF (48 * PAGE)
+#define RO_GPA (GPA + RO_OFF)
+#define PFN_OFF (64 * PAGE)
+#define PFN_GPA (GPA + PFN_OFF)
+#define PFN_IOVA (IOVA + PFN_OFF)
+
+/* The second 2 MiB block of the file, for the large mapping. */
+#define LARGE_OFF HPAGE
+#define LARGE_GPA (GPA + LARGE_OFF)
+#define LARGE_IOVA (IOVA + LARGE_OFF)
+
+enum backing { BACKING_FOLIO, BACKING_TMPFS, BACKING_PFN, NR_BACKINGS };
+
+static const char *const backing_names[] = { "folio", "tmpfs", "pfn" };
+
+/* A software protected VM on @backing, mapped into a mock domain. */
+static void vmm_setup(struct vmm *v, enum backing backing)
+{
+ int resource;
+
+ vmm_create_type(v, KVM_X86_SW_PROTECTED_VM);
+ switch (backing) {
+ case BACKING_FOLIO:
+ resource = -1;
+ break;
+ case BACKING_TMPFS:
+ resource = create_tmpfs_pool_fd("never", true, FILE_SIZE);
+ break;
+ default:
+ resource = mps_new_file(FILE_SIZE);
+ v->pfn = true;
+ break;
+ }
+ vmm_attach(v, resource);
+}
+
+/*
+ * Convert a page to private and back, and check the device, the host and
+ * the guest at each step. The host and the device reach the page first, so
+ * that a host mapping or an IOPTE left behind would show.
+ */
+static void scenario_convert(enum backing backing)
+{
+ struct vmm v = { .name = backing_names[backing] };
+ volatile uint64_t *host;
+ uint64_t frame, err = 0;
+
+ pr_info("%d. conversions on %s\n", backing + 1, backing_names[backing]);
+ vmm_setup(&v, backing);
+ host = (volatile uint64_t *)(v.hva + CONV_OFF);
+
+ /* Shared: the guest writes, the host and the device see the page. */
+ vmm_run(&v, CONV_GPA, MAGIC, 0);
+ expect_done(&v);
+ TEST_ASSERT(*host == MAGIC, "%s: host does not see the guest's write",
+ v.name);
+ frame = vmm_iova_phys(&v, CONV_IOVA);
+ TEST_ASSERT(frame, "%s: device does not map the page", v.name);
+
+ /* Private: the device and the host lose it; the guest keeps it. */
+ TEST_ASSERT(!__gmem_set_private(v.gmem, CONV_OFF, PAGE, &err),
+ "%s: conversion to private errno=%d", v.name, errno);
+ TEST_ASSERT(!vmm_iova_phys(&v, CONV_IOVA),
+ "%s: device still maps the private page", v.name);
+ TEST_ASSERT(host_read_faults(host),
+ "%s: host mapping of a private page readable", v.name);
+ vmm_run(&v, 0, 0, CONV_GPA);
+ TEST_ASSERT(expect_sync_then_done(&v) == MAGIC,
+ "%s: the private page lost the guest's write", v.name);
+ TEST_ASSERT(!vmm_iova_phys(&v, CONV_IOVA),
+ "%s: device maps the page the guest uses as private",
+ v.name);
+
+ /* Shared again: the same frame for everyone, contents kept. */
+ TEST_ASSERT(!__gmem_set_shared(v.gmem, CONV_OFF, PAGE, &err),
+ "%s: conversion to shared errno=%d", v.name, errno);
+ TEST_ASSERT(vmm_iova_phys(&v, CONV_IOVA) == frame,
+ "%s: device does not map the frame again", v.name);
+ TEST_ASSERT(*host == MAGIC, "%s: host does not see the page again",
+ v.name);
+ vmm_run(&v, 0, 0, CONV_GPA);
+ TEST_ASSERT(expect_sync_then_done(&v) == MAGIC,
+ "%s: the guest lost the page on the way back", v.name);
+
+ /* Folios: a punch hole takes the page from the device too. */
+ if (backing != BACKING_PFN) {
+ TEST_ASSERT(!fallocate(v.gmem,
+ FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE,
+ CONV_OFF, PAGE),
+ "%s: punch hole errno=%d", v.name, errno);
+ TEST_ASSERT(!vmm_iova_phys(&v, CONV_IOVA),
+ "%s: device maps a punched page", v.name);
+ /* Touching it allocates a new page, which the device gets. */
+ vmm_run(&v, CONV_GPA, MAGIC, 0);
+ expect_done(&v);
+ TEST_ASSERT(vmm_iova_phys(&v, CONV_IOVA),
+ "%s: device does not map the new page", v.name);
+ }
+
+ vmm_destroy(&v);
+}
+
+static void ioas_map_gmem(struct vmm *v)
+{
+ struct iommu_ioas_map_file map = {
+ .size = sizeof(map),
+ .flags = IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_READABLE |
+ IOMMU_IOAS_MAP_WRITEABLE,
+ .ioas_id = v->ioas, .fd = v->gmem,
+ .start = 0, .length = FILE_SIZE, .iova = IOVA,
+ };
+
+ TEST_ASSERT(!ioctl(v->iommufd, IOMMU_IOAS_MAP_FILE, &map),
+ "%s: IOAS_MAP_FILE errno=%d", v->name, errno);
+}
+
+static void ioas_unmap_gmem(struct vmm *v)
+{
+ struct iommu_ioas_unmap unmap = {
+ .size = sizeof(unmap), .ioas_id = v->ioas,
+ .iova = IOVA, .length = FILE_SIZE,
+ };
+
+ TEST_ASSERT(!ioctl(v->iommufd, IOMMU_IOAS_UNMAP, &unmap),
+ "%s: IOAS_UNMAP errno=%d", v->name, errno);
+}
+
+/*
+ * Folios: the device gets a page as soon as it exists, whichever path
+ * allocates it, and whether the guest_memfd was mapped before or after.
+ */
+static void scenario_alloc(enum backing backing)
+{
+ struct vmm v = { .name = backing_names[backing] };
+ volatile uint64_t *host;
+
+ pr_info(" allocations on %s\n", backing_names[backing]);
+ vmm_setup(&v, backing);
+
+ /* Mapped first: fallocate() allocates the page. */
+ TEST_ASSERT(!vmm_iova_phys(&v, IOVA + FALLOC_OFF),
+ "%s: device maps a page that does not exist", v.name);
+ TEST_ASSERT(!fallocate(v.gmem, FALLOC_FL_KEEP_SIZE, FALLOC_OFF, PAGE),
+ "%s: fallocate errno=%d", v.name, errno);
+ TEST_ASSERT(vmm_iova_phys(&v, IOVA + FALLOC_OFF),
+ "%s: device does not map a page fallocate() allocated",
+ v.name);
+
+ /* Mapped first: the host's first access allocates the page. */
+ host = (volatile uint64_t *)(v.hva + HOST_OFF);
+ TEST_ASSERT(!vmm_iova_phys(&v, IOVA + HOST_OFF),
+ "%s: device maps a page the host never touched", v.name);
+ *host = MAGIC;
+ TEST_ASSERT(vmm_iova_phys(&v, IOVA + HOST_OFF),
+ "%s: device does not map a page the host allocated", v.name);
+ vmm_run(&v, 0, 0, GPA + HOST_OFF);
+ TEST_ASSERT(expect_sync_then_done(&v) == MAGIC,
+ "%s: guest does not see the host's write", v.name);
+
+ /* Allocated first, while nothing maps the file, then mapped. */
+ ioas_unmap_gmem(&v);
+ TEST_ASSERT(!fallocate(v.gmem, FALLOC_FL_KEEP_SIZE, FALLOC2_OFF, PAGE),
+ "%s: fallocate errno=%d", v.name, errno);
+ ioas_map_gmem(&v);
+ TEST_ASSERT(vmm_iova_phys(&v, IOVA + FALLOC2_OFF),
+ "%s: device does not map a page allocated before the map",
+ v.name);
+ TEST_ASSERT(vmm_iova_phys(&v, IOVA + HOST_OFF),
+ "%s: device lost a page across the remap", v.name);
+
+ vmm_destroy(&v);
+}
+
+/* An IOAS without a domain: nothing maps an area until a device attaches. */
+static void ioas_check_map_overflow(void)
+{
+ struct vmm v = { .name = "folio" };
+ struct iommu_ioas_alloc alloc = { .size = sizeof(alloc) };
+ struct iommu_ioas_map_file map = {
+ .size = sizeof(map),
+ .flags = IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_READABLE,
+ .start = -PAGE, .length = PAGE, .iova = IOVA,
+ };
+
+ pr_info(" a guest_memfd range that wraps\n");
+ vmm_setup(&v, BACKING_FOLIO);
+ TEST_ASSERT(!ioctl(v.iommufd, IOMMU_IOAS_ALLOC, &alloc),
+ "IOAS_ALLOC errno=%d", errno);
+ map.ioas_id = alloc.out_ioas_id;
+ map.fd = v.gmem;
+ TEST_ASSERT(ioctl(v.iommufd, IOMMU_IOAS_MAP_FILE, &map) && errno == EINVAL,
+ "IOAS_MAP_FILE of a wrapping range: errno=%d, want EINVAL",
+ errno);
+ vmm_destroy(&v);
+}
+
+/*
+ * Memory failure on a page the device maps: the device loses it, and the
+ * poisoned folio stays in the file, so the host cannot fault in a new one.
+ */
+static void scenario_poison(enum backing backing)
+{
+ struct vmm v = { .name = backing_names[backing] };
+ volatile uint64_t *host, *next;
+ int r;
+
+ pr_info(" memory failure on %s\n", backing_names[backing]);
+ vmm_setup(&v, backing);
+ host = (volatile uint64_t *)(v.hva + POISON_OFF);
+ next = (volatile uint64_t *)(v.hva + POISON_OFF + PAGE);
+ *host = MAGIC;
+ *next = MAGIC;
+ TEST_ASSERT(vmm_iova_phys(&v, POISON_IOVA),
+ "%s: device does not map the page", v.name);
+
+ r = host_poison((void *)host);
+ if (r == EINVAL || r == EPERM) {
+ pr_info(" skipped: no MADV_HWPOISON (errno %d)\n", r);
+ vmm_destroy(&v);
+ return;
+ }
+ TEST_ASSERT(!r, "%s: MADV_HWPOISON errno=%d", v.name, r);
+
+ TEST_ASSERT(!vmm_iova_phys(&v, POISON_IOVA),
+ "%s: device maps a poisoned page", v.name);
+ TEST_ASSERT(host_read_faults(host),
+ "%s: host faulted in a new page over a poisoned one",
+ v.name);
+ TEST_ASSERT(!vmm_iova_phys(&v, POISON_IOVA),
+ "%s: device maps a poisoned page after a host fault",
+ v.name);
+ TEST_ASSERT(vmm_iova_phys(&v, POISON_IOVA + PAGE) && *next == MAGIC,
+ "%s: the next page was lost", v.name);
+
+ vmm_destroy(&v);
+}
+
+/* A second IOAS on the same guest_memfd, with its own mock domain. */
+static void ioas_attach_second(struct vmm *v, struct vmm *w)
+{
+ struct iommu_ioas_alloc alloc = { .size = sizeof(alloc) };
+ struct iommu_test_cmd mock = {
+ .size = sizeof(mock), .op = IOMMU_TEST_OP_MOCK_DOMAIN,
+ };
+
+ *w = *v;
+ w->name = "second IOAS";
+ TEST_ASSERT(!ioctl(v->iommufd, IOMMU_IOAS_ALLOC, &alloc),
+ "IOAS_ALLOC errno=%d", errno);
+ w->ioas = alloc.out_ioas_id;
+ mock.id = w->ioas;
+ TEST_ASSERT(!ioctl(v->iommufd, IOMMU_TEST_CMD, &mock),
+ "MOCK_DOMAIN errno=%d", errno);
+ w->stdev = mock.mock_domain.out_stdev_id;
+ w->hwpt = mock.mock_domain.out_hwpt_id;
+ ioas_map_gmem(w);
+}
+
+/*
+ * Two IOAS map one guest_memfd: both follow its conversions, and either can
+ * go away while the other keeps following.
+ */
+static void scenario_two_ioas(void)
+{
+ struct vmm v = { .name = "folio" }, w;
+ volatile uint64_t *host;
+ uint64_t frame, err = 0;
+
+ pr_info(" two IOAS mapping one guest_memfd\n");
+ vmm_setup(&v, BACKING_FOLIO);
+ ioas_attach_second(&v, &w);
+ host = (volatile uint64_t *)(v.hva + CONV_OFF);
+ *host = MAGIC;
+ frame = vmm_iova_phys(&v, CONV_IOVA);
+ TEST_ASSERT(frame && vmm_iova_phys(&w, CONV_IOVA) == frame,
+ "the two IOAS do not map the same frame");
+
+ TEST_ASSERT(!__gmem_set_private(v.gmem, CONV_OFF, PAGE, &err),
+ "conversion to private errno=%d", errno);
+ TEST_ASSERT(!vmm_iova_phys(&v, CONV_IOVA) && !vmm_iova_phys(&w, CONV_IOVA),
+ "an IOAS maps a private page");
+ TEST_ASSERT(!__gmem_set_shared(v.gmem, CONV_OFF, PAGE, &err),
+ "conversion to shared errno=%d", errno);
+ TEST_ASSERT(vmm_iova_phys(&v, CONV_IOVA) == frame &&
+ vmm_iova_phys(&w, CONV_IOVA) == frame,
+ "an IOAS does not map the page shared again");
+
+ /* The second goes away; the first keeps following. */
+ ioas_unmap_gmem(&w);
+ TEST_ASSERT(!__gmem_set_private(v.gmem, CONV_OFF, PAGE, &err),
+ "conversion to private errno=%d", errno);
+ TEST_ASSERT(!vmm_iova_phys(&v, CONV_IOVA),
+ "the first IOAS maps a private page");
+ TEST_ASSERT(!__gmem_set_shared(v.gmem, CONV_OFF, PAGE, &err),
+ "conversion to shared errno=%d", errno);
+ TEST_ASSERT(vmm_iova_phys(&v, CONV_IOVA) == frame,
+ "the first IOAS does not map the page shared again");
+
+ vmm_destroy(&v);
+}
+
+/*
+ * PFN mode: a read-only frame is mapped read only for the guest and the VMM,
+ * and stays mapped for the device. The guest maps the page first, so that
+ * marking it read only must zap it.
+ */
+static void scenario_readonly(void)
+{
+ struct vmm v = { .name = "pfn" };
+ volatile uint64_t *page;
+
+ pr_info(" read-only frames on a PFN mode provider\n");
+ vmm_setup(&v, BACKING_PFN);
+ page = (volatile uint64_t *)(v.hva + RO_OFF);
+
+ *page = MAGIC;
+ vmm_run(&v, 0, 0, RO_GPA);
+ TEST_ASSERT(expect_sync_then_done(&v) == MAGIC,
+ "guest does not read the host's write");
+
+ mps_set(v.child, MPS_SET_READONLY, RO_OFF, PAGE, true);
+ TEST_ASSERT(vmm_iova_mapped(&v, IOVA + RO_OFF, PAGE, true),
+ "read-only page not mapped for the device");
+ vmm_run(&v, 0, 0, RO_GPA);
+ TEST_ASSERT(expect_sync_then_done(&v) == MAGIC,
+ "guest cannot read a read-only page");
+ vmm_run(&v, RO_GPA, ~MAGIC, 0);
+ expect_memory_fault(&v, RO_GPA);
+ TEST_ASSERT(host_write_faults(page, ~MAGIC),
+ "host write to a read-only page did not fault");
+ TEST_ASSERT(*page == MAGIC, "write to a read-only page landed");
+
+ /* Writable again: the guest retries the same write. */
+ mps_set(v.child, MPS_SET_READONLY, RO_OFF, PAGE, false);
+ vmm_resume(&v);
+ expect_done(&v);
+ TEST_ASSERT(*page == ~MAGIC, "write after clearing read only lost");
+ TEST_ASSERT(!host_write_faults(page, MAGIC),
+ "host write after clearing read only faulted");
+
+ vmm_destroy(&v);
+}
+
+/* PFN mode: private frames, and the provider's changes. */
+static void scenario_pfn(void)
+{
+ struct vmm v = { .name = "pfn" };
+ volatile uint64_t *host;
+ uint64_t err = 0, frame;
+
+ pr_info("4. private pages on a PFN mode provider\n");
+ vmm_setup(&v, BACKING_PFN);
+ host = (volatile uint64_t *)(v.hva + PFN_OFF);
+
+ vmm_run(&v, PFN_GPA, MAGIC, 0);
+ expect_done(&v);
+
+ /* Private: the device and the host lose it, the guest keeps it. */
+ TEST_ASSERT(!__gmem_set_private(v.gmem, PFN_OFF, PAGE, &err),
+ "conversion to private errno=%d", errno);
+ TEST_ASSERT(!vmm_iova_phys(&v, PFN_IOVA), "device maps a private page");
+ TEST_ASSERT(host_read_faults(host),
+ "host mapping of a private page readable");
+ vmm_run(&v, 0, 0, PFN_GPA);
+ TEST_ASSERT(expect_sync_then_done(&v) == MAGIC,
+ "the private page lost the guest's write");
+
+ /*
+ * The provider takes the private page back: a hole for the guest until
+ * it comes back, zeroed by the sample.
+ */
+ mps_set(v.child, MPS_SET_PRESENT, PFN_OFF, PAGE, false);
+ vmm_run(&v, 0, 0, PFN_GPA);
+ expect_memory_fault(&v, PFN_GPA);
+ mps_set(v.child, MPS_SET_PRESENT, PFN_OFF, PAGE, true);
+ vmm_run(&v, 0, 0, PFN_GPA);
+ TEST_ASSERT(!expect_sync_then_done(&v),
+ "the page came back with old contents");
+ TEST_ASSERT(!vmm_iova_phys(&v, PFN_IOVA),
+ "device maps a private page given back");
+
+ /* A page that is not backed may become private; the guest faults. */
+ TEST_ASSERT(!__gmem_set_shared(v.gmem, PFN_OFF, PAGE, &err),
+ "conversion to shared errno=%d", errno);
+ mps_set(v.child, MPS_SET_PRESENT, PFN_OFF, PAGE, false);
+ TEST_ASSERT(!__gmem_set_private(v.gmem, PFN_OFF, PAGE, &err),
+ "conversion of an absent page errno=%d", errno);
+ vmm_run(&v, 0, 0, PFN_GPA);
+ expect_memory_fault(&v, PFN_GPA);
+ mps_set(v.child, MPS_SET_PRESENT, PFN_OFF, PAGE, true);
+ vmm_run(&v, PFN_GPA, MAGIC, 0);
+ expect_done(&v);
+
+ /* A read-only frame never backs a private page: a hole until writable. */
+ mps_set(v.child, MPS_SET_READONLY, PFN_OFF, PAGE, true);
+ vmm_run(&v, 0, 0, PFN_GPA);
+ expect_memory_fault(&v, PFN_GPA);
+ mps_set(v.child, MPS_SET_READONLY, PFN_OFF, PAGE, false);
+ vmm_run(&v, 0, 0, PFN_GPA);
+ TEST_ASSERT(expect_sync_then_done(&v) == MAGIC,
+ "the private page lost its contents while read only");
+
+ /* Back to shared: the device maps it again, with the contents. */
+ TEST_ASSERT(!__gmem_set_shared(v.gmem, PFN_OFF, PAGE, &err),
+ "conversion to shared errno=%d", errno);
+ TEST_ASSERT(vmm_iova_phys(&v, PFN_IOVA), "device does not map the page");
+ TEST_ASSERT(*host == MAGIC, "host does not see the page");
+
+ /* A shared page taken back and given back: the device follows. */
+ mps_set(v.child, MPS_SET_PRESENT, PFN_OFF, PAGE, false);
+ TEST_ASSERT(!vmm_iova_phys(&v, PFN_IOVA), "device maps an absent page");
+ TEST_ASSERT(host_read_faults(host), "host maps an absent page");
+ mps_set(v.child, MPS_SET_PRESENT, PFN_OFF, PAGE, true);
+ TEST_ASSERT(vmm_iova_phys(&v, PFN_IOVA),
+ "device does not map a page given back");
+
+ /*
+ * A punch hole gives the frame back to the provider, which zeroes it
+ * and keeps backing the page: the device maps the same frame
+ * afterwards, and the host reads it zeroed.
+ */
+ frame = vmm_iova_phys(&v, PFN_IOVA);
+ *host = MAGIC;
+ TEST_ASSERT(!fallocate(v.gmem, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE,
+ PFN_OFF, PAGE),
+ "punch hole errno=%d", errno);
+ TEST_ASSERT(vmm_iova_phys(&v, PFN_IOVA) == frame,
+ "device does not map the page after a punch hole");
+ TEST_ASSERT(!*host, "a punched page kept its contents");
+
+ vmm_destroy(&v);
+}
+
+/*
+ * A private 2 MiB block of a PFN mode provider gets one stage-2 mapping, which a
+ * change inside the block takes away; the rest of the block stays readable.
+ */
+static void scenario_large(void)
+{
+ struct vmm v = { .name = "pfn" };
+ uint64_t before, after, err = 0;
+
+ pr_info("5. a 2 MiB private mapping on a PFN mode provider\n");
+ vmm_setup(&v, BACKING_PFN);
+
+ if (vmm_iova_phys(&v, IOVA) & (HPAGE - 1)) {
+ pr_info(" skipped: the file's frames are not 2 MiB aligned\n");
+ vmm_destroy(&v);
+ return;
+ }
+
+ /* Fault in the guest's code and stack first, then count. */
+ vmm_run(&v, 0, 0, 0);
+ expect_done(&v);
+ TEST_ASSERT(!__gmem_set_private(v.gmem, LARGE_OFF, HPAGE, &err),
+ "conversion of a 2 MiB block errno=%d", errno);
+ TEST_ASSERT(vmm_iova_mapped(&v, LARGE_IOVA, HPAGE, false),
+ "device maps part of a private block");
+ before = vm_get_stat(v.vm, pages_2m);
+
+ vmm_run(&v, LARGE_GPA, MAGIC, 0);
+ expect_done(&v);
+ after = vm_get_stat(v.vm, pages_2m);
+ TEST_ASSERT(after == before + 1, "%llu 2 MiB mappings, want %llu",
+ (unsigned long long)after, (unsigned long long)before + 1);
+
+ /*
+ * A page taken out of the block removes the 2 MiB mapping; the other
+ * pages still work.
+ */
+ mps_set(v.child, MPS_SET_PRESENT, LARGE_OFF + PAGE, PAGE, false);
+ TEST_ASSERT(vm_get_stat(v.vm, pages_2m) == before,
+ "the 2 MiB mapping survived a change inside it");
+ vmm_run(&v, 0, 0, LARGE_GPA);
+ TEST_ASSERT(expect_sync_then_done(&v) == MAGIC,
+ "the rest of the block lost the guest's write");
+ TEST_ASSERT(vm_get_stat(v.vm, pages_2m) == before,
+ "a 2 MiB mapping over a block with a hole");
+ vmm_run(&v, 0, 0, LARGE_GPA + PAGE);
+ expect_memory_fault(&v, LARGE_GPA + PAGE);
+ mps_set(v.child, MPS_SET_PRESENT, LARGE_OFF + PAGE, PAGE, true);
+ vmm_run(&v, 0, 0, LARGE_GPA + PAGE);
+ TEST_ASSERT(!expect_sync_then_done(&v),
+ "a page given back kept its contents");
+
+ /* Shared again: the device maps the whole block. */
+ TEST_ASSERT(!__gmem_set_shared(v.gmem, LARGE_OFF, HPAGE, &err),
+ "conversion to shared errno=%d", errno);
+ TEST_ASSERT(vmm_iova_mapped(&v, LARGE_IOVA, HPAGE, true),
+ "device does not map the shared block");
+
+ vmm_destroy(&v);
+}
+
+/*
+ * A provider that backs a hole with the whole 2 MiB block around it: the
+ * device maps the block, whether a host fault or fallocate() asks for one
+ * page of it.
+ */
+static void scenario_fill(void)
+{
+ struct mps_new_file nf = { .flags = MPS_NEW_FILE_FILL,
+ .size = FILE_SIZE };
+ struct vmm v = { .name = "pfn" };
+ volatile uint64_t *host;
+ int fd;
+
+ pr_info("6. a provider that backs 2 MiB blocks on demand\n");
+ fd = ioctl(ctl, MPS_NEW_FILE, &nf);
+ TEST_ASSERT(fd >= 0, "MPS_NEW_FILE(FILL) errno=%d", errno);
+ vmm_create_type(&v, KVM_X86_SW_PROTECTED_VM);
+ v.pfn = true;
+ vmm_attach(&v, fd);
+
+ mps_set(v.child, MPS_SET_PRESENT, 0, FILE_SIZE, false);
+ TEST_ASSERT(vmm_iova_mapped(&v, IOVA, FILE_SIZE, false),
+ "device maps absent pages");
+
+ host = (volatile uint64_t *)(v.hva + LARGE_OFF + 5 * PAGE);
+ TEST_ASSERT(!*host, "a page backed on demand is not zeroed");
+ TEST_ASSERT(vmm_iova_mapped(&v, LARGE_IOVA, HPAGE, true),
+ "device does not map the block backed for a host fault");
+
+ TEST_ASSERT(!fallocate(v.gmem, FALLOC_FL_KEEP_SIZE, 3 * PAGE, PAGE),
+ "fallocate errno=%d", errno);
+ TEST_ASSERT(vmm_iova_mapped(&v, IOVA, HPAGE, true),
+ "device does not map the block backed for fallocate()");
+
+ /*
+ * A punch hole gives the block back and the provider frees it: the
+ * device sees a hole until the next access backs the block again.
+ */
+ *host = MAGIC;
+ TEST_ASSERT(!fallocate(v.gmem, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE,
+ LARGE_OFF, HPAGE),
+ "punch hole errno=%d", errno);
+ TEST_ASSERT(vmm_iova_mapped(&v, LARGE_IOVA, HPAGE, false),
+ "device maps a block the provider freed");
+ TEST_ASSERT(vmm_iova_mapped(&v, IOVA, HPAGE, true),
+ "device lost the block next to the punch hole");
+ TEST_ASSERT(!*host, "a block backed again is not zeroed");
+ TEST_ASSERT(vmm_iova_mapped(&v, LARGE_IOVA, HPAGE, true),
+ "device does not map the block backed again");
+
+ vmm_destroy(&v);
+}
+
+int main(void)
+{
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) &
+ BIT(KVM_X86_SW_PROTECTED_VM));
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES));
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_GUEST_MEMFD_FLAGS) &
+ GUEST_MEMFD_FLAG_USE_RESOURCE);
+
+ scenario_convert(BACKING_FOLIO);
+ scenario_alloc(BACKING_FOLIO);
+ scenario_convert(BACKING_TMPFS);
+ scenario_alloc(BACKING_TMPFS);
+ ioas_check_map_overflow();
+ scenario_poison(BACKING_FOLIO);
+ scenario_poison(BACKING_TMPFS);
+ scenario_two_ioas();
+
+ ctl = open("/dev/mem_provider_sample", O_RDWR);
+ if (ctl < 0) {
+ pr_info("PFN mode scenarios skipped: mem_provider_sample not loaded\n");
+ return 0;
+ }
+
+ scenario_convert(BACKING_PFN);
+ scenario_readonly();
+ scenario_pfn();
+ scenario_large();
+ scenario_fill();
+
+ close(ctl);
+ pr_info("gmem_conversion_iommufd: all scenarios passed\n");
+ return 0;
+}
--
2.47.3
^ permalink raw reply related [flat|nested] 79+ messages in thread