* [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
` (48 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (47 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:27 ` [PATCH RFC v2 04/50] KVM: guest_memfd: Support provider folio invalidation Ackerley Tng via B4 Relay
` (46 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:27 ` [PATCH RFC v2 05/50] KVM: guest_memfd: Support using resource provider Ackerley Tng via B4 Relay
` (45 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-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
` (44 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:27 ` [PATCH RFC v2 07/50] KVM: selftests: Test negative validation of resource_fd argument Ackerley Tng via B4 Relay
` (43 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (42 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (41 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (40 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:27 ` [PATCH RFC v2 11/50] KVM: selftests: Test rejection of hugepage " Ackerley Tng via B4 Relay
` (39 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:27 ` [PATCH RFC v2 12/50] KVM: selftests: Test rejection of regular file in tmpfs mount Ackerley Tng via B4 Relay
` (38 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (37 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (36 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (35 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (34 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (33 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:27 ` [PATCH RFC v2 18/50] KVM: selftests: Document testing TODOs Ackerley Tng via B4 Relay
` (32 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (31 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (30 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (29 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (28 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (27 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (26 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (25 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (24 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (23 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (22 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (21 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (20 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:28 ` [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers Ackerley Tng via B4 Relay
` (19 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-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
` (18 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (17 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (16 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:28 ` [PATCH RFC v2 35/50] KVM: guest_memfd: Support converting to shared " Ackerley Tng via B4 Relay
` (15 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (14 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (13 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (12 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (11 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (10 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (9 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (8 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (7 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (6 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (5 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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-22 23:28 ` [PATCH RFC v2 46/50] samples/kvm: Implement get_pfn provider operation Ackerley Tng via B4 Relay
` (4 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (3 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
` (2 subsequent siblings)
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
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
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ 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
49 siblings, 0 replies; 51+ 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 [flat|nested] 51+ messages in thread