* [PATCH 1/5] mm/mempolicy: add mempolicy_create()
2026-09-02 19:46 [PATCH 0/5] KVM: guest_memfd: bind backing memory to a NUMA node Gregory Price
@ 2026-09-02 19:46 ` Gregory Price
2026-09-02 19:46 ` [PATCH 2/5] mm/mempolicy: add mpol_set_shared_policy_range() Gregory Price
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Gregory Price @ 2026-09-02 19:46 UTC (permalink / raw)
To: linux-mm
Cc: kvm, linux-kselftest, linux-kernel, kernel-team, pbonzini, seanjc,
akpm, david, ziy, matthew.brost, joshua.hahnjy, rakie.kim,
byungchul, gourry, ying.huang, apopple, shuah
do_set_mempolicy() builds a validated policy and installs it into
the current running task.
An in-kernel user that wants to construct a mempolicy wants the same
validation without the install step.
Add mempolicy_create(mode, flags, nodes): it allocates the policy and
contextualises it against the calling task's cpuset, and returns the
mempolicy to the caller (or ERR_PTR on error).
do_set_mempolicy() is deliberately left alone rather than reimplemented
on top of it. For syscall users, mpol_set_nodemask() and the current
task policy swap must run under the task_lock(current) acquisition.
Export the symbol to kvm for use in guest_memfd integration.
Signed-off-by: Gregory Price <gourry@gourry.net>
---
include/linux/mempolicy.h | 3 +++
mm/mempolicy.c | 40 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/include/linux/mempolicy.h b/include/linux/mempolicy.h
index 65c732d440d2f..aef018ad92317 100644
--- a/include/linux/mempolicy.h
+++ b/include/linux/mempolicy.h
@@ -128,6 +128,9 @@ void mpol_free_shared_policy(struct shared_policy *sp);
struct mempolicy *mpol_shared_policy_lookup(struct shared_policy *sp,
pgoff_t idx);
+struct mempolicy *mempolicy_create(unsigned short mode, unsigned short flags,
+ nodemask_t *nodes);
+
struct mempolicy *get_task_policy(struct task_struct *p);
struct mempolicy *__get_vma_policy(struct vm_area_struct *vma,
unsigned long addr, pgoff_t *ilx);
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 2ad0a5f18280a..da133ffe0b1c8 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -1085,7 +1085,45 @@ static int mbind_range(struct vma_iterator *vmi, struct vm_area_struct *vma,
return vma_replace_policy(vma, new_pol);
}
-/* Set the process memory policy */
+/**
+ * mempolicy_create - build a validated, cpuset-contextualised mempolicy
+ * @mode: MPOL_* mode
+ * @flags: MPOL_F_* flags
+ * @nodes: target nodemask, or NULL (interpreted per @mode; see mpol_new())
+ *
+ * Creates a new policy and constrains it to the task's cpuset.
+ *
+ * The caller owns the returned reference and frees it with mpol_put().
+ *
+ * Return: the policy (NULL for a default policy), or an ERR_PTR on failure.
+ */
+struct mempolicy *mempolicy_create(unsigned short mode, unsigned short flags,
+ nodemask_t *nodes)
+{
+ struct mempolicy *pol;
+ NODEMASK_SCRATCH(scratch);
+ int err;
+
+ if (!scratch)
+ return ERR_PTR(-ENOMEM);
+
+ pol = mpol_new(mode, flags, nodes);
+ if (IS_ERR(pol))
+ goto out;
+
+ task_lock(current);
+ err = mpol_set_nodemask(pol, nodes, scratch);
+ task_unlock(current);
+ if (err) {
+ mpol_put(pol);
+ pol = ERR_PTR(err);
+ }
+out:
+ NODEMASK_SCRATCH_FREE(scratch);
+ return pol;
+}
+EXPORT_SYMBOL_FOR_MODULES(mempolicy_create, "kvm");
+
static long do_set_mempolicy(unsigned short mode, unsigned short flags,
nodemask_t *nodes)
{
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 2/5] mm/mempolicy: add mpol_set_shared_policy_range()
2026-09-02 19:46 [PATCH 0/5] KVM: guest_memfd: bind backing memory to a NUMA node Gregory Price
2026-09-02 19:46 ` [PATCH 1/5] mm/mempolicy: add mempolicy_create() Gregory Price
@ 2026-09-02 19:46 ` Gregory Price
2026-09-02 19:46 ` [PATCH 3/5] KVM: guest_memfd: bind backing memory to a NUMA node at creation Gregory Price
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Gregory Price @ 2026-09-02 19:46 UTC (permalink / raw)
To: linux-mm
Cc: kvm, linux-kselftest, linux-kernel, kernel-team, pbonzini, seanjc,
akpm, david, ziy, matthew.brost, joshua.hahnjy, rakie.kim,
byungchul, gourry, ying.huang, apopple, shuah, Dave Jiang
mpol_set_shared_policy() installs a policy over a VMA's page-offset
range, which is the only programmatic way to populate an inode's
shared policy after init.
Two limitations make it unusable for binding an entire backing inode
from in-kernel code:
- It requires a VMA, so it cannot cover unmapped offsets.
(e.g. unmapped file folios faulted by pagecache)
- mpol_shared_policy_init(), the only no-VMA installer, reconstructs
the policy from mpol->w.user_nodemask. That field is only populated
for static/relative or mount-string policies.
a policy built programmatically (e.g. by mempolicy_create()) leaves it
empty and stores w.cpuset_mems_allowed instead and _init mangles it.
Both matter to a caller that wants a whole inode bound at creation.
guest_memfd is one: it has no VMA at that point and may never gain one
for a given offset, and it builds its policy in-kernel rather than from
a mount string, so neither existing installer can express it.
Add mpol_set_shared_policy_range(), which installs an already-built,
fully contextualised policy verbatim over an arbitrary [start, end)
page range with no VMA.
Reimplement mpol_set_shared_policy() as a thin wrapper that derives
the range from the VMA, so both share a single underlying path.
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Co-developed-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Gregory Price <gourry@gourry.net>
Assisted-by: Claude:claude-opus-4-8
---
include/linux/mempolicy.h | 2 ++
mm/mempolicy.c | 35 +++++++++++++++++++++++++++++------
2 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/include/linux/mempolicy.h b/include/linux/mempolicy.h
index aef018ad92317..398318175cec7 100644
--- a/include/linux/mempolicy.h
+++ b/include/linux/mempolicy.h
@@ -124,6 +124,8 @@ int vma_dup_policy(struct vm_area_struct *src, struct vm_area_struct *dst);
void mpol_shared_policy_init(struct shared_policy *sp, struct mempolicy *mpol);
int mpol_set_shared_policy(struct shared_policy *sp,
struct vm_area_struct *vma, struct mempolicy *mpol);
+int mpol_set_shared_policy_range(struct shared_policy *sp, pgoff_t start,
+ pgoff_t end, struct mempolicy *mpol);
void mpol_free_shared_policy(struct shared_policy *sp);
struct mempolicy *mpol_shared_policy_lookup(struct shared_policy *sp,
pgoff_t idx);
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index da133ffe0b1c8..ce10ce4374643 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -3312,24 +3312,47 @@ void mpol_shared_policy_init(struct shared_policy *sp, struct mempolicy *mpol)
}
EXPORT_SYMBOL_FOR_MODULES(mpol_shared_policy_init, "kvm");
-int mpol_set_shared_policy(struct shared_policy *sp,
- struct vm_area_struct *vma, struct mempolicy *pol)
+/**
+ * mpol_set_shared_policy_range - install @pol over [@start, @end) of @sp
+ * @sp: the shared policy tree
+ * @start: first page offset (inclusive)
+ * @end: last page offset (exclusive)
+ * @pol: a fully-built, validated policy, or NULL to clear the range
+ *
+ * Installs @pol over the given range, replacing any overlapping policy.
+ * @sp takes its own reference, the caller retains its reference on @pol.
+ *
+ * The policy is not reconstructed, so the policy is preserved exactly.
+ *
+ * Unlike mpol_set_shared_policy(), no VMA is required, so a range that
+ * is never mapped into a VMA can be covered, including the whole file.
+ *
+ * Return: 0 on success, -ENOMEM on allocation failure.
+ */
+int mpol_set_shared_policy_range(struct shared_policy *sp, pgoff_t start,
+ pgoff_t end, struct mempolicy *pol)
{
- const pgoff_t pgoff = vma_start_pgoff(vma);
- const pgoff_t pgoff_end = vma_end_pgoff(vma);
struct sp_node *new = NULL;
int err;
if (pol) {
- new = sp_alloc(pgoff, pgoff_end, pol);
+ new = sp_alloc(start, end, pol);
if (!new)
return -ENOMEM;
}
- err = shared_policy_replace(sp, pgoff, pgoff_end, new);
+ err = shared_policy_replace(sp, start, end, new);
if (err && new)
sp_free(new);
return err;
}
+EXPORT_SYMBOL_FOR_MODULES(mpol_set_shared_policy_range, "kvm");
+
+int mpol_set_shared_policy(struct shared_policy *sp,
+ struct vm_area_struct *vma, struct mempolicy *pol)
+{
+ return mpol_set_shared_policy_range(sp, vma->vm_pgoff,
+ vma->vm_pgoff + vma_pages(vma), pol);
+}
EXPORT_SYMBOL_FOR_MODULES(mpol_set_shared_policy, "kvm");
/* Free a backing policy store on inode delete. */
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 3/5] KVM: guest_memfd: bind backing memory to a NUMA node at creation
2026-09-02 19:46 [PATCH 0/5] KVM: guest_memfd: bind backing memory to a NUMA node Gregory Price
2026-09-02 19:46 ` [PATCH 1/5] mm/mempolicy: add mempolicy_create() Gregory Price
2026-09-02 19:46 ` [PATCH 2/5] mm/mempolicy: add mpol_set_shared_policy_range() Gregory Price
@ 2026-09-02 19:46 ` Gregory Price
2026-09-02 19:46 ` [PATCH 4/5] selftests: KVM: guest_memfd: let the gmem_test() harness bind a node Gregory Price
2026-09-02 19:46 ` [PATCH 5/5] selftests: KVM: guest_memfd: test GUEST_MEMFD_FLAG_BIND_NODE Gregory Price
4 siblings, 0 replies; 6+ messages in thread
From: Gregory Price @ 2026-09-02 19:46 UTC (permalink / raw)
To: linux-mm
Cc: kvm, linux-kselftest, linux-kernel, kernel-team, pbonzini, seanjc,
akpm, david, ziy, matthew.brost, joshua.hahnjy, rakie.kim,
byungchul, gourry, ying.huang, apopple, shuah, Dave Jiang
guest_memfd presently allocates its page-cache folios through a
per-inode shared mempolicy (kvm_gmem_get_folio()).
Today that policy can only be set after the fact, via mbind() on
a host mmap of the fd. This requires the fd to be mmap-able and
cannot reach folios that are only ever guest-faulted (no host VMA).
Neither holds for a non-mappable (confidential) guest_memfd.
Add GUEST_MEMFD_FLAG_BIND_NODE.
When set, KVM builds an MPOL_BIND policy for the requested node and
installs it over the whole inode's shared policy, so every folio
is allocated on the requested node with no userspace mbind().
The flag is advertised through KVM_CAP_GUEST_MEMFD_FLAGS only when
CONFIG_NUMA is enabled.
Two user-visible behaviors worth noting:
- mempolicy_create() constrains the request against the calling task's
cpuset. Requesting a node outside the cpuset mems_allowed results
in the ioctl failing with -EINVAL. Binding is essentially subject
to the same cpuset constraint as mbind().
This behavior is correct - a task cannot grant a guest_memfd access
to a node it cannot access itself.
- The policy hangs off the inode, and nothing rebinds an inode's
shared policy on a later cpuset change.
mpol_rebind_task() walks tsk->mempolicy
mpol_rebind_mm() walks vma->vm_policy
Neither walker reaches a struct shared_policy.
The binding is therefore fixed for the life of the fd. That matches
shmem, whose inode policy behaves the same way, and is the intent
here: the node is a property of the guest's backing memory, not of
whoever happens to hold the fd.
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Co-developed-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Gregory Price <gourry@gourry.net>
Assisted-by: Claude:claude-opus-4-8
---
include/linux/kvm_host.h | 3 +++
include/uapi/linux/kvm.h | 5 ++++-
virt/kvm/guest_memfd.c | 44 ++++++++++++++++++++++++++++++++++++++--
3 files changed, 49 insertions(+), 3 deletions(-)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 03bfc92864b6e..738e276633c1e 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -739,6 +739,9 @@ static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
if (!kvm || kvm_arch_supports_gmem_init_shared(kvm))
flags |= GUEST_MEMFD_FLAG_INIT_SHARED;
+ if (IS_ENABLED(CONFIG_NUMA))
+ flags |= GUEST_MEMFD_FLAG_BIND_NODE;
+
return flags;
}
#endif
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index ac2d77d149635..8d3ae7e2ead8e 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1658,11 +1658,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_BIND_NODE (1ULL << 2)
struct kvm_create_guest_memfd {
__u64 size;
__u64 flags;
- __u64 reserved[6];
+ __u32 node;
+ __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 625e62e1a0318..dc9f071dd969b 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -423,6 +423,31 @@ static struct mempolicy *kvm_gmem_get_policy(struct vm_area_struct *vma,
*/
return mpol_shared_policy_lookup(&GMEM_I(inode)->policy, pgoff);
}
+
+static int kvm_gmem_bind_node(struct inode *inode, int node)
+{
+ struct mempolicy *pol;
+ nodemask_t nodes;
+ int err;
+
+ if ((unsigned int)node >= MAX_NUMNODES)
+ return -EINVAL;
+
+ init_nodemask_of_node(&nodes, node);
+ pol = mempolicy_create(MPOL_BIND, 0, &nodes);
+ if (IS_ERR(pol))
+ return PTR_ERR(pol);
+
+ err = mpol_set_shared_policy_range(&GMEM_I(inode)->policy, 0,
+ MAX_LFS_FILESIZE >> PAGE_SHIFT, pol);
+ mpol_put(pol);
+ return err;
+}
+#else
+static int kvm_gmem_bind_node(struct inode *inode, int node)
+{
+ return -EINVAL;
+}
#endif /* CONFIG_NUMA */
static const struct vm_operations_struct kvm_gmem_vm_ops = {
@@ -520,7 +545,7 @@ bool __weak kvm_arch_supports_gmem_init_shared(struct kvm *kvm)
return true;
}
-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 node)
{
static const char *name = "[kvm-gmem]";
struct gmem_file *f;
@@ -561,6 +586,12 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
GMEM_I(inode)->flags = flags;
+ if (flags & GUEST_MEMFD_FLAG_BIND_NODE) {
+ err = kvm_gmem_bind_node(inode, node);
+ if (err)
+ goto err_inode;
+ }
+
file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, &kvm_gmem_fops);
if (IS_ERR(file)) {
err = PTR_ERR(file);
@@ -593,6 +624,7 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
{
loff_t size = args->size;
u64 flags = args->flags;
+ int node = NUMA_NO_NODE;
if (flags & ~kvm_gmem_get_supported_flags(kvm))
return -EINVAL;
@@ -600,7 +632,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_BIND_NODE) {
+ if (args->pad || args->node >= MAX_NUMNODES)
+ return -EINVAL;
+ node = args->node;
+ } else if (args->node || args->pad) {
+ return -EINVAL;
+ }
+
+ return __kvm_gmem_create(kvm, size, flags, node);
}
int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 4/5] selftests: KVM: guest_memfd: let the gmem_test() harness bind a node
2026-09-02 19:46 [PATCH 0/5] KVM: guest_memfd: bind backing memory to a NUMA node Gregory Price
` (2 preceding siblings ...)
2026-09-02 19:46 ` [PATCH 3/5] KVM: guest_memfd: bind backing memory to a NUMA node at creation Gregory Price
@ 2026-09-02 19:46 ` Gregory Price
2026-09-02 19:46 ` [PATCH 5/5] selftests: KVM: guest_memfd: test GUEST_MEMFD_FLAG_BIND_NODE Gregory Price
4 siblings, 0 replies; 6+ messages in thread
From: Gregory Price @ 2026-09-02 19:46 UTC (permalink / raw)
To: linux-mm
Cc: kvm, linux-kselftest, linux-kernel, kernel-team, pbonzini, seanjc,
akpm, david, ziy, matthew.brost, joshua.hahnjy, rakie.kim,
byungchul, gourry, ying.huang, apopple, shuah
To test GUEST_MEMFD_FLAG_BIND_NODE - extend create_guest_memfd() to
accept a node.
Ignore the node argument if the BIND_NODE flag is not provided.
Signed-off-by: Gregory Price <gourry@gourry.net>
Assisted-by: Claude:claude-opus-5
---
.../testing/selftests/kvm/guest_memfd_test.c | 50 ++++++++++++++++---
1 file changed, 42 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 2233d871a38f4..1818e0fea5690 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -25,6 +25,31 @@
static size_t page_size;
+static int __create_guest_memfd_node(struct kvm_vm *vm, u64 size, u64 flags,
+ u32 node, u32 pad)
+{
+ struct kvm_create_guest_memfd guest_memfd = {
+ .size = size,
+ .flags = flags,
+ .node = node,
+ .pad = pad,
+ };
+
+ return __vm_ioctl(vm, KVM_CREATE_GUEST_MEMFD, &guest_memfd);
+}
+
+static int create_guest_memfd(struct kvm_vm *vm, u64 size, u64 flags, u32 node)
+{
+ int fd;
+
+ if (!(flags & GUEST_MEMFD_FLAG_BIND_NODE))
+ return vm_create_guest_memfd(vm, size, flags);
+
+ fd = __create_guest_memfd_node(vm, size, flags, node, 0);
+ TEST_ASSERT(fd >= 0, KVM_IOCTL_ERROR(KVM_CREATE_GUEST_MEMFD, fd));
+ return fd;
+}
+
static void test_file_read_write(int fd, size_t total_size)
{
char buf[64];
@@ -418,26 +443,35 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
}
}
-#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, __node, args...) \
+do { \
+ int fd = create_guest_memfd(__vm, __gmem_size, __flags, __node); \
+ \
+ test_##__test(args); \
+ close(fd); \
} while (0)
#define __gmem_test(__test, __vm, __flags, __gmem_size) \
- ____gmem_test(__test, __vm, __flags, __gmem_size, fd, __gmem_size)
+ ____gmem_test(__test, __vm, __flags, __gmem_size, 0, fd, __gmem_size)
#define gmem_test(__test, __vm, __flags) \
__gmem_test(__test, __vm, __flags, page_size * 4)
#define __gmem_test_vm(__test, __vm, __flags, __gmem_size) \
- ____gmem_test(__test, __vm, __flags, __gmem_size, __vm, fd, __gmem_size)
+ ____gmem_test(__test, __vm, __flags, __gmem_size, 0, \
+ __vm, fd, __gmem_size)
#define gmem_test_vm(__test, __vm, __flags) \
__gmem_test_vm(__test, __vm, __flags, page_size * 4)
+#define __gmem_test_node(__test, __vm, __flags, __gmem_size, __node) \
+ ____gmem_test(__test, __vm, \
+ (__flags) | GUEST_MEMFD_FLAG_BIND_NODE, \
+ __gmem_size, __node, fd, __gmem_size, __node)
+
+#define gmem_test_node(__test, __vm, __flags, __node) \
+ __gmem_test_node(__test, __vm, __flags, page_size * 4, __node)
+
static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
{
test_create_guest_memfd_multiple(vm);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 5/5] selftests: KVM: guest_memfd: test GUEST_MEMFD_FLAG_BIND_NODE
2026-09-02 19:46 [PATCH 0/5] KVM: guest_memfd: bind backing memory to a NUMA node Gregory Price
` (3 preceding siblings ...)
2026-09-02 19:46 ` [PATCH 4/5] selftests: KVM: guest_memfd: let the gmem_test() harness bind a node Gregory Price
@ 2026-09-02 19:46 ` Gregory Price
4 siblings, 0 replies; 6+ messages in thread
From: Gregory Price @ 2026-09-02 19:46 UTC (permalink / raw)
To: linux-mm
Cc: kvm, linux-kselftest, linux-kernel, kernel-team, pbonzini, seanjc,
akpm, david, ziy, matthew.brost, joshua.hahnjy, rakie.kim,
byungchul, gourry, ying.huang, apopple, shuah
Add tests for an umapped guest_memfd mempolicy configured at
creation via GUEST_MEMFD_FLAG_BIND_NODE.
Handles:
- !CONFIG_NUMA (skips)
- single node system
- multi-node system (faults onto remote node)
- invalid arguments (non-zero pad, bad node, node w/o flag).
BIND_NODE is skipped in test_guest_memfd_flags() and testing later
because that loop asserts every advertised flag succeeds on its own,
but BIND_NODE is the only flag that depends on a second field.
Signed-off-by: Gregory Price <gourry@gourry.net>
Assisted-by: Claude:claude-opus-5
---
.../testing/selftests/kvm/guest_memfd_test.c | 84 +++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 1818e0fea5690..b333cb42fab29 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -196,6 +196,83 @@ static void test_numa_allocation(int fd, size_t total_size)
kvm_munmap(mem, total_size);
}
+static bool has_bind_node(struct kvm_vm *vm)
+{
+ return vm_check_cap(vm, KVM_CAP_GUEST_MEMFD_FLAGS) &
+ GUEST_MEMFD_FLAG_BIND_NODE;
+}
+
+static void test_bind_node_invalid(struct kvm_vm *vm, u64 flags)
+{
+ int fd;
+
+ if (!has_bind_node(vm))
+ return;
+
+ fd = __create_guest_memfd_node(vm, page_size,
+ flags | GUEST_MEMFD_FLAG_BIND_NODE, 0, 1);
+ TEST_ASSERT(fd < 0 && errno == EINVAL,
+ "guest_memfd() with non-zero pad should fail with EINVAL");
+
+ fd = __create_guest_memfd_node(vm, page_size,
+ flags | GUEST_MEMFD_FLAG_BIND_NODE,
+ 1 << 20, 0);
+ TEST_ASSERT(fd < 0 && errno == EINVAL,
+ "guest_memfd() with out-of-range node should fail with EINVAL");
+
+ fd = __create_guest_memfd_node(vm, page_size, flags, 1, 0);
+ TEST_ASSERT(fd < 0 && errno == EINVAL,
+ "guest_memfd() with a node but no BIND_NODE flag should fail with EINVAL");
+}
+
+static void test_bind_node(int fd, size_t total_size, int node)
+{
+ const unsigned long other_mask = 1UL << (node ? 0 : 1);
+ const unsigned long maxnode = BITS_PER_TYPE(other_mask);
+ bool steer_away = is_multi_numa_node_system();
+ void *pages[4];
+ int status[4];
+ char *mem;
+ int i;
+
+ mem = kvm_mmap(total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
+ for (i = 0; i < 4; i++)
+ pages[i] = mem + page_size * i;
+
+ /*
+ * Bind on a different node if possible order to check whether faulting
+ * happens as desired. Without a second node use the local node and
+ * just get coverage of create/mmap/fault paths.
+ */
+ if (steer_away)
+ kvm_set_mempolicy(MPOL_BIND, &other_mask, maxnode);
+
+ /* Deliberately no mbind() on this mapping. */
+ memset(mem, 0xaa, total_size);
+
+ kvm_move_pages(0, 4, pages, NULL, status, 0);
+ for (i = 0; i < 4; i++)
+ TEST_ASSERT(status[i] == node,
+ "Expected page %d on node %d, got it on node %d",
+ i, node, status[i]);
+
+ /* Dropped memory should fault back onto the same node */
+ kvm_fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0,
+ total_size);
+ memset(mem, 0xaa, total_size);
+
+ kvm_move_pages(0, 4, pages, NULL, status, 0);
+ for (i = 0; i < 4; i++)
+ TEST_ASSERT(status[i] == node,
+ "Expected page %d back on node %d, got it on node %d",
+ i, node, status[i]);
+
+ if (steer_away)
+ kvm_set_mempolicy(MPOL_DEFAULT, NULL, 0);
+
+ kvm_munmap(mem, total_size);
+}
+
static void test_collapse(int fd, u64 flags)
{
const size_t pmd_size = get_trans_hugepagesz();
@@ -429,6 +506,10 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
int fd;
for (flag = BIT(0); flag; flag <<= 1) {
+ /* BIND_NODE depends on a valid node field, test separately */
+ if (flag == GUEST_MEMFD_FLAG_BIND_NODE)
+ continue;
+
fd = __vm_create_guest_memfd(vm, page_size, flag);
if (flag & valid_flags) {
TEST_ASSERT(fd >= 0,
@@ -476,6 +557,7 @@ static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
{
test_create_guest_memfd_multiple(vm);
test_create_guest_memfd_invalid_sizes(vm, flags);
+ test_bind_node_invalid(vm, flags);
gmem_test(file_read_write, vm, flags);
@@ -486,6 +568,8 @@ static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
gmem_test(mmap_supported, vm, flags);
gmem_test(fault_overflow, vm, flags);
gmem_test(numa_allocation, vm, flags);
+ if (has_bind_node(vm))
+ gmem_test_node(bind_node, vm, flags, 0);
__gmem_test(collapse, vm, flags, pmd_size);
} else {
gmem_test(fault_private, vm, flags);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 6+ messages in thread