[RFC PATCH 13/14] samples/kvm: Back absent pages on demand in the sample provider
From: Fred Griffoul
Date: Tue Oct 27 2026 - 12:03:29 EST
From: Fred Griffoul <fgriffo@xxxxxxxxxxxx>
The sample provider fails get_pfn() for an absent page and keeps its
frames on a punch hole, so tests cannot exercise a provider that backs
a hole when it is asked for a frame, one that backs more than the page
asked for, or one that frees the frames that a punch hole gives back.
Add MPS_NEW_FILE_FILL: get_pfn() then backs an absent page with the
whole 2 MiB block around it, and put_pfn_range() makes the pages absent
again.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@xxxxxxxxxxxx>
---
samples/kvm/mem_provider_sample.c | 29 ++++++++++++++++++++++++-----
samples/kvm/mem_provider_sample.h | 9 ++++++++-
2 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/samples/kvm/mem_provider_sample.c b/samples/kvm/mem_provider_sample.c
index 670e36207e59..41e0ce5b6bc6 100644
--- a/samples/kvm/mem_provider_sample.c
+++ b/samples/kvm/mem_provider_sample.c
@@ -41,6 +41,7 @@ struct mps_info {
unsigned long *absent; /* taken away by MPS_SET_PRESENT */
unsigned long *readonly; /* set by MPS_SET_READONLY */
+ bool fill; /* MPS_NEW_FILE_FILL */
struct inode *gmem_inode;
};
@@ -62,8 +63,15 @@ static void mps_gmem_put_pfn_range(void *provider, pgoff_t index,
size_t nr_pages)
{
struct file *resource_file = provider;
+ struct mps_info *info = resource_file->private_data;
+ size_t i;
- mps_zero_range(resource_file->private_data, index, nr_pages);
+ mps_zero_range(info, index, nr_pages);
+ if (!info->fill || index >= info->npages)
+ return;
+ /* Lockless, like get_pfn(): guest_memfd holds its invalidate lock. */
+ for (i = index; i < min_t(size_t, index + nr_pages, info->npages); i++)
+ set_bit(i, info->absent);
}
/* Byte range [offset, offset + len) of @info as page indices. */
@@ -228,7 +236,9 @@ static bool mps_bits_are(unsigned long *bitmap, unsigned long first,
/*
* The frame behind @index, and the largest aligned block around it that is
* present and has the same attributes: the file's frames are contiguous.
- * Lockless: a change is followed by kvm_gmem_revoke() or kvm_gmem_restore().
+ * Lockless: a change is followed by kvm_gmem_revoke() or kvm_gmem_restore(),
+ * except a fill, which only gives frames to holes; guest_memfd tells its users
+ * about the block.
*/
static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
int *max_order, u32 *attrs)
@@ -241,8 +251,16 @@ static int mps_gmem_get_pfn(void *provider, pgoff_t index, unsigned long *pfn,
if (index >= info->npages)
return -EINVAL;
- if (test_bit(index, info->absent))
- return -EFAULT;
+ if (test_bit(index, info->absent)) {
+ unsigned long first = ALIGN_DOWN(index, PTRS_PER_PMD);
+ unsigned long i;
+
+ if (!info->fill)
+ return -EFAULT;
+ /* Absent pages are zeroed already. */
+ for (i = first; i < min(first + PTRS_PER_PMD, info->npages); i++)
+ clear_bit(i, info->absent);
+ }
ro = test_bit(index, info->readonly);
*pfn = info->base_pfn + index;
@@ -307,7 +325,7 @@ static long mps_ctl_new_file(void __user *uarg)
if (copy_from_user(&nf, uarg, sizeof(nf)))
return -EFAULT;
- if (nf.flags || nf.pad)
+ if ((nf.flags & ~MPS_NEW_FILE_FILL) || nf.pad)
return -EINVAL;
if (!nf.size || !PAGE_ALIGNED(nf.size))
return -EINVAL;
@@ -337,6 +355,7 @@ static long mps_ctl_new_file(void __user *uarg)
info->cma_pages = pages;
info->base_pfn = page_to_pfn(pages);
info->npages = npages;
+ info->fill = nf.flags & MPS_NEW_FILE_FILL;
mutex_init(&info->lock);
fd = get_unused_fd_flags(O_CLOEXEC);
diff --git a/samples/kvm/mem_provider_sample.h b/samples/kvm/mem_provider_sample.h
index 57f494dc46fa..45ec9605b593 100644
--- a/samples/kvm/mem_provider_sample.h
+++ b/samples/kvm/mem_provider_sample.h
@@ -19,11 +19,18 @@
* Returns the provider fd.
*/
struct mps_new_file {
- __u32 flags; /* Reserved, must be 0 */
+ __u32 flags; /* MPS_NEW_FILE_* */
__u32 pad;
__u64 size; /* bytes, page aligned */
};
+/*
+ * get_pfn() backs an absent page, with every page of the 2 MiB block around
+ * it, instead of failing, and put_pfn_range() takes the pages back: a
+ * provider that allocates on demand and frees on a punch hole, for tests.
+ */
+#define MPS_NEW_FILE_FILL (1U << 0)
+
#define MPS_NEW_FILE \
_IOW(MPS_IOCTL_BASE, 5, struct mps_new_file)
--
2.47.3