* [PATCH v2 0/4] KVM: guest_memfd: fix NUMA selftests
@ 2026-09-01 8:22 Shivank Garg
2026-09-01 8:22 ` [PATCH v2 1/4] KVM: selftests: fix maxnode arguments in xapic_ipi_test Shivank Garg
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Shivank Garg @ 2026-09-01 8:22 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, David Hildenbrand, Shuah Khan,
Jim Mattson, Peter Shier, Ricardo Koller, Ackerley Tng
Cc: kvm, linux-kselftest, linux-kernel, Shivank Garg
NUMA selftests in guest_memfd_test assumes that node 0 and 1 exist and
have memory. However, nodes can be sparse or memoryless which would
break this selftest.
xapic_ipi_test pass the nodemask size in bytes as maxnode to
migrate_pages() but the syscall expect the number of bits plus one,
so the kernel was only looking at node IDs 0-6.
Tested on a 7.3-rc1, AMD EPYC 7713.
Changes in V2:
- Split from unbind race fix. (Sean)
- Include <linux/bits.h> that defines BITS_PER_TYPE for self-containment. (Sashiko)
- Link to V1: https://lore.kernel.org/kvm/20260823-shivank-gmem-fix-split-v1-0-512a29fb8e86@amd.com
Signed-off-by: Shivank Garg <shivankg@amd.com>
---
Shivank Garg (4):
KVM: selftests: fix maxnode arguments in xapic_ipi_test
KVM: selftests: use BITS_PER_TYPE() for NUMA masks
KVM: selftests: add get_numa_mem_nodes()
KVM: selftests: use allowed NUMA nodes in guest_memfd_test
tools/testing/selftests/kvm/guest_memfd_test.c | 86 ++++++++++++++++--------
tools/testing/selftests/kvm/include/numaif.h | 53 ++-------------
tools/testing/selftests/kvm/x86/xapic_ipi_test.c | 14 ++--
3 files changed, 71 insertions(+), 82 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260901-gmem-selftests-fix-2a5e94192f79
Best regards,
--
Shivank Garg <shivankg@amd.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 1/4] KVM: selftests: fix maxnode arguments in xapic_ipi_test
2026-09-01 8:22 [PATCH v2 0/4] KVM: guest_memfd: fix NUMA selftests Shivank Garg
@ 2026-09-01 8:22 ` Shivank Garg
2026-09-01 15:04 ` Sean Christopherson
2026-09-01 8:22 ` [PATCH v2 2/4] KVM: selftests: use BITS_PER_TYPE() for NUMA masks Shivank Garg
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Shivank Garg @ 2026-09-01 8:22 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, David Hildenbrand, Shuah Khan,
Jim Mattson, Peter Shier, Ricardo Koller, Ackerley Tng
Cc: kvm, linux-kselftest, linux-kernel, Shivank Garg
migrate_pages() syscall expect maxnode to be one greater than the
number of bits in the nodemask. do_migrations() passes the size of
nodemask in bytes to migrate_pages(). This sets the maxnode to 8,
so kernel only checks node IDs 0-6 even though the nodemask covers
node IDs 0-63.
Pass the nodemask size in bits plus one because get_nodes() in
mempolicy does --maxnode.
Fixes: 678e90a349a4 ("KVM: selftests: Test IPI to halted vCPU in xAPIC while backing page moves")
Signed-off-by: Shivank Garg <shivankg@amd.com>
---
tools/testing/selftests/kvm/x86/xapic_ipi_test.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
index 469e3ab16460..0f11f4d7cc3d 100644
--- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
+++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
@@ -291,7 +291,7 @@ void do_migrations(struct test_data_page *data, int run_secs, int delay_usecs,
* KVM_CREATE_VCPU ioctl. If that assumption ever changes this
* test may break or give a false positive signal.
*/
- pages_not_moved = migrate_pages(0, sizeof(nodemasks[from]),
+ pages_not_moved = migrate_pages(0, sizeof(nodemasks[from]) * 8 + 1,
&nodemasks[from],
&nodemasks[to]);
if (pages_not_moved < 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 2/4] KVM: selftests: use BITS_PER_TYPE() for NUMA masks
2026-09-01 8:22 [PATCH v2 0/4] KVM: guest_memfd: fix NUMA selftests Shivank Garg
2026-09-01 8:22 ` [PATCH v2 1/4] KVM: selftests: fix maxnode arguments in xapic_ipi_test Shivank Garg
@ 2026-09-01 8:22 ` Shivank Garg
2026-09-01 8:22 ` [PATCH v2 3/4] KVM: selftests: add get_numa_mem_nodes() Shivank Garg
2026-09-01 8:22 ` [PATCH v2 4/4] KVM: selftests: use allowed NUMA nodes in guest_memfd_test Shivank Garg
3 siblings, 0 replies; 8+ messages in thread
From: Shivank Garg @ 2026-09-01 8:22 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, David Hildenbrand, Shuah Khan,
Jim Mattson, Peter Shier, Ricardo Koller, Ackerley Tng
Cc: kvm, linux-kselftest, linux-kernel, Shivank Garg
Replace the open-coded sizeof() * 8 calculations in do_migrations() with
BITS_PER_TYPE().
No functional change intended.
Signed-off-by: Shivank Garg <shivankg@amd.com>
---
tools/testing/selftests/kvm/x86/xapic_ipi_test.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
index 0f11f4d7cc3d..e5706dd62d3b 100644
--- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
+++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
@@ -233,7 +233,7 @@ void do_migrations(struct test_data_page *data, int run_secs, int delay_usecs,
{
long pages_not_moved;
unsigned long nodemask = 0;
- unsigned long nodemasks[sizeof(nodemask) * 8];
+ unsigned long nodemasks[BITS_PER_TYPE(nodemask)];
int nodes = 0;
time_t start_time, last_update, now;
time_t interval_secs = 1;
@@ -248,18 +248,18 @@ void do_migrations(struct test_data_page *data, int run_secs, int delay_usecs,
delay_usecs);
/* Get set of first 64 numa nodes available */
- kvm_get_mempolicy(NULL, &nodemask, sizeof(nodemask) * 8,
+ kvm_get_mempolicy(NULL, &nodemask, BITS_PER_TYPE(nodemask),
0, MPOL_F_MEMS_ALLOWED);
fprintf(stderr, "Numa nodes found amongst first %lu possible nodes "
"(each 1-bit indicates node is present): %#lx\n",
- sizeof(nodemask) * 8, nodemask);
+ BITS_PER_TYPE(nodemask), nodemask);
/* Init array of masks containing a single-bit in each, one for each
* available node. migrate_pages called below requires specifying nodes
* as bit masks.
*/
- for (i = 0, bit = 1; i < sizeof(nodemask) * 8; i++, bit <<= 1) {
+ for (i = 0, bit = 1; i < BITS_PER_TYPE(nodemask); i++, bit <<= 1) {
if (nodemask & bit) {
nodemasks[nodes] = nodemask & bit;
nodes++;
@@ -291,7 +291,7 @@ void do_migrations(struct test_data_page *data, int run_secs, int delay_usecs,
* KVM_CREATE_VCPU ioctl. If that assumption ever changes this
* test may break or give a false positive signal.
*/
- pages_not_moved = migrate_pages(0, sizeof(nodemasks[from]) * 8 + 1,
+ pages_not_moved = migrate_pages(0, BITS_PER_TYPE(nodemasks[from]) + 1,
&nodemasks[from],
&nodemasks[to]);
if (pages_not_moved < 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 3/4] KVM: selftests: add get_numa_mem_nodes()
2026-09-01 8:22 [PATCH v2 0/4] KVM: guest_memfd: fix NUMA selftests Shivank Garg
2026-09-01 8:22 ` [PATCH v2 1/4] KVM: selftests: fix maxnode arguments in xapic_ipi_test Shivank Garg
2026-09-01 8:22 ` [PATCH v2 2/4] KVM: selftests: use BITS_PER_TYPE() for NUMA masks Shivank Garg
@ 2026-09-01 8:22 ` Shivank Garg
2026-09-01 15:06 ` Sean Christopherson
2026-09-01 8:22 ` [PATCH v2 4/4] KVM: selftests: use allowed NUMA nodes in guest_memfd_test Shivank Garg
3 siblings, 1 reply; 8+ messages in thread
From: Shivank Garg @ 2026-09-01 8:22 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, David Hildenbrand, Shuah Khan,
Jim Mattson, Peter Shier, Ricardo Koller, Ackerley Tng
Cc: kvm, linux-kselftest, linux-kernel, Shivank Garg
The xAPIC IPI test uses MPOL_F_MEMS_ALLOWED to get the memory nodes
available to the current process. Move the query to numaif.h as
get_numa_mem_nodes() so other KVM selftests can use it.
Call get_mempolicy() directly instead of using the assert-on-failure
kvm_get_mempolicy() wrapper. The xAPIC test already asserts its two
node requirement.
Signed-off-by: Shivank Garg <shivankg@amd.com>
---
tools/testing/selftests/kvm/include/numaif.h | 13 +++++++++++++
tools/testing/selftests/kvm/x86/xapic_ipi_test.c | 6 ++----
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/numaif.h b/tools/testing/selftests/kvm/include/numaif.h
index 29572a6d789c..98cf77cfc481 100644
--- a/tools/testing/selftests/kvm/include/numaif.h
+++ b/tools/testing/selftests/kvm/include/numaif.h
@@ -6,6 +6,7 @@
#include <dirent.h>
+#include <linux/bits.h>
#include <linux/mempolicy.h>
#include "kvm_syscalls.h"
@@ -75,6 +76,18 @@ static bool is_numa_available(void)
(errno != ENOSYS && errno != EPERM);
}
+static inline unsigned long get_numa_mem_nodes(void)
+{
+ unsigned long nodemask = 0;
+
+ /* Get set of first 64 numa nodes available */
+ if (get_mempolicy(NULL, &nodemask, BITS_PER_TYPE(nodemask), NULL,
+ MPOL_F_MEMS_ALLOWED))
+ return 0;
+
+ return nodemask;
+}
+
static inline bool is_multi_numa_node_system(void)
{
return is_numa_available() && get_max_numa_node() >= 1;
diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
index e5706dd62d3b..b5f4ed671218 100644
--- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
+++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
@@ -232,7 +232,7 @@ void do_migrations(struct test_data_page *data, int run_secs, int delay_usecs,
u64 *pipis_rcvd)
{
long pages_not_moved;
- unsigned long nodemask = 0;
+ unsigned long nodemask;
unsigned long nodemasks[BITS_PER_TYPE(nodemask)];
int nodes = 0;
time_t start_time, last_update, now;
@@ -247,9 +247,7 @@ void do_migrations(struct test_data_page *data, int run_secs, int delay_usecs,
fprintf(stderr, "Calling migrate_pages every %d microseconds\n",
delay_usecs);
- /* Get set of first 64 numa nodes available */
- kvm_get_mempolicy(NULL, &nodemask, BITS_PER_TYPE(nodemask),
- 0, MPOL_F_MEMS_ALLOWED);
+ nodemask = get_numa_mem_nodes();
fprintf(stderr, "Numa nodes found amongst first %lu possible nodes "
"(each 1-bit indicates node is present): %#lx\n",
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 4/4] KVM: selftests: use allowed NUMA nodes in guest_memfd_test
2026-09-01 8:22 [PATCH v2 0/4] KVM: guest_memfd: fix NUMA selftests Shivank Garg
` (2 preceding siblings ...)
2026-09-01 8:22 ` [PATCH v2 3/4] KVM: selftests: add get_numa_mem_nodes() Shivank Garg
@ 2026-09-01 8:22 ` Shivank Garg
2026-09-01 17:28 ` Sean Christopherson
3 siblings, 1 reply; 8+ messages in thread
From: Shivank Garg @ 2026-09-01 8:22 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, David Hildenbrand, Shuah Khan,
Jim Mattson, Peter Shier, Ricardo Koller, Ackerley Tng
Cc: kvm, linux-kselftest, linux-kernel, Shivank Garg
guest_memfd_test assumes that nodes 0 and 1 exist and have memory.
is_multi_numa_node_system() only checks that the maximum node ID is
nonzero, which is not enough for sparse or memoryless nodes.
Select the required nodes from MPOL_F_MEMS_ALLOWED instead. Use the full
nodemask width plus one to mbind(), and let test_mbind() run when only
one memory node is available.
The sysfs helpers for finding maxnode are no longer needed.
Signed-off-by: Shivank Garg <shivankg@amd.com>
---
tools/testing/selftests/kvm/guest_memfd_test.c | 86 +++++++++++++++++---------
tools/testing/selftests/kvm/include/numaif.h | 52 ----------------
2 files changed, 58 insertions(+), 80 deletions(-)
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 2233d871a38f..aee80dda6229 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -76,33 +76,53 @@ static void test_mmap_supported(int fd, size_t total_size)
kvm_munmap(mem, total_size);
}
+/*
+ * Fill @nids with the first @nr_nids nodes in the allowed mask.
+ * Return false if the mask contains fewer than @nr_nids nodes.
+ */
+static bool get_numa_node_ids(int *nids, int nr_nids)
+{
+ unsigned long nodemask = get_numa_mem_nodes();
+ unsigned long nid;
+ int nr_found = 0;
+
+ for_each_set_bit(nid, &nodemask, BITS_PER_TYPE(nodemask)) {
+ nids[nr_found++] = nid;
+ if (nr_found == nr_nids)
+ return true;
+ }
+
+ return false;
+}
+
static void test_mbind(int fd, size_t total_size)
{
- const unsigned long nodemask_0 = 1; /* nid: 0 */
- unsigned long nodemask = 0;
- unsigned long maxnode = BITS_PER_TYPE(nodemask);
+ unsigned long nodemask, bind_nodemask;
+ unsigned long maxnode = BITS_PER_TYPE(nodemask) + 1;
int policy;
char *mem;
+ int nid;
int ret;
- if (!is_multi_numa_node_system())
+ if (!get_numa_node_ids(&nid, 1))
return;
+ bind_nodemask = 1UL << nid;
mem = kvm_mmap(total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
/* Test MPOL_INTERLEAVE policy */
- kvm_mbind(mem, page_size * 2, MPOL_INTERLEAVE, &nodemask_0, maxnode, 0);
+ kvm_mbind(mem, page_size * 2, MPOL_INTERLEAVE, &bind_nodemask, maxnode, 0);
kvm_get_mempolicy(&policy, &nodemask, maxnode, mem, MPOL_F_ADDR);
- TEST_ASSERT(policy == MPOL_INTERLEAVE && nodemask == nodemask_0,
+ TEST_ASSERT(policy == MPOL_INTERLEAVE && nodemask == bind_nodemask,
"Wanted MPOL_INTERLEAVE (%u) and nodemask 0x%lx, got %u and 0x%lx",
- MPOL_INTERLEAVE, nodemask_0, policy, nodemask);
+ MPOL_INTERLEAVE, bind_nodemask, policy, nodemask);
/* Test basic MPOL_BIND policy */
- kvm_mbind(mem + page_size * 2, page_size * 2, MPOL_BIND, &nodemask_0, maxnode, 0);
+ kvm_mbind(mem + page_size * 2, page_size * 2, MPOL_BIND, &bind_nodemask, maxnode, 0);
kvm_get_mempolicy(&policy, &nodemask, maxnode, mem + page_size * 2, MPOL_F_ADDR);
- TEST_ASSERT(policy == MPOL_BIND && nodemask == nodemask_0,
+ TEST_ASSERT(policy == MPOL_BIND && nodemask == bind_nodemask,
"Wanted MPOL_BIND (%u) and nodemask 0x%lx, got %u and 0x%lx",
- MPOL_BIND, nodemask_0, policy, nodemask);
+ MPOL_BIND, bind_nodemask, policy, nodemask);
/* Test MPOL_DEFAULT policy */
kvm_mbind(mem, total_size, MPOL_DEFAULT, NULL, 0, 0);
@@ -112,7 +132,7 @@ static void test_mbind(int fd, size_t total_size)
MPOL_DEFAULT, policy, nodemask);
/* Test with invalid policy */
- ret = mbind(mem, page_size, 999, &nodemask_0, maxnode, 0);
+ ret = mbind(mem, page_size, 999, &bind_nodemask, maxnode, 0);
TEST_ASSERT(ret == -1 && errno == EINVAL,
"mbind with invalid policy should fail with EINVAL");
@@ -121,17 +141,19 @@ static void test_mbind(int fd, size_t total_size)
static void test_numa_allocation(int fd, size_t total_size)
{
- unsigned long node0_mask = 1; /* Node 0 */
- unsigned long node1_mask = 2; /* Node 1 */
- unsigned long maxnode = 8;
+ unsigned long bind_nodemasks[2];
+ unsigned long maxnode = BITS_PER_TYPE(bind_nodemasks[0]) + 1;
void *pages[4];
+ int nids[2];
int status[4];
char *mem;
int i;
- if (!is_multi_numa_node_system())
+ if (!get_numa_node_ids(nids, ARRAY_SIZE(nids)))
return;
+ bind_nodemasks[0] = 1UL << nids[0];
+ bind_nodemasks[1] = 1UL << nids[1];
mem = kvm_mmap(total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd);
for (i = 0; i < 4; i++)
@@ -139,34 +161,42 @@ static void test_numa_allocation(int fd, size_t total_size)
/* Set NUMA policy after allocation */
memset(mem, 0xaa, page_size);
- kvm_mbind(pages[0], page_size, MPOL_BIND, &node0_mask, maxnode, 0);
+ kvm_mbind(pages[0], page_size, MPOL_BIND, &bind_nodemasks[0], maxnode, 0);
kvm_fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, page_size);
/* Set NUMA policy before allocation */
- kvm_mbind(pages[0], page_size * 2, MPOL_BIND, &node1_mask, maxnode, 0);
- kvm_mbind(pages[2], page_size * 2, MPOL_BIND, &node0_mask, maxnode, 0);
+ kvm_mbind(pages[0], page_size * 2, MPOL_BIND, &bind_nodemasks[1], maxnode, 0);
+ kvm_mbind(pages[2], page_size * 2, MPOL_BIND, &bind_nodemasks[0], maxnode, 0);
memset(mem, 0xaa, total_size);
/* Validate if pages are allocated on specified NUMA nodes */
kvm_move_pages(0, 4, pages, NULL, status, 0);
- TEST_ASSERT(status[0] == 1, "Expected page 0 on node 1, got it on node %d", status[0]);
- TEST_ASSERT(status[1] == 1, "Expected page 1 on node 1, got it on node %d", status[1]);
- TEST_ASSERT(status[2] == 0, "Expected page 2 on node 0, got it on node %d", status[2]);
- TEST_ASSERT(status[3] == 0, "Expected page 3 on node 0, got it on node %d", status[3]);
+ TEST_ASSERT(status[0] == nids[1],
+ "Expected page 0 on node %d, got it on node %d", nids[1], status[0]);
+ TEST_ASSERT(status[1] == nids[1],
+ "Expected page 1 on node %d, got it on node %d", nids[1], status[1]);
+ TEST_ASSERT(status[2] == nids[0],
+ "Expected page 2 on node %d, got it on node %d", nids[0], status[2]);
+ TEST_ASSERT(status[3] == nids[0],
+ "Expected page 3 on node %d, got it on node %d", nids[0], status[3]);
/* Punch hole for all pages */
kvm_fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, total_size);
/* Change NUMA policy nodes and reallocate */
- kvm_mbind(pages[0], page_size * 2, MPOL_BIND, &node0_mask, maxnode, 0);
- kvm_mbind(pages[2], page_size * 2, MPOL_BIND, &node1_mask, maxnode, 0);
+ kvm_mbind(pages[0], page_size * 2, MPOL_BIND, &bind_nodemasks[0], maxnode, 0);
+ kvm_mbind(pages[2], page_size * 2, MPOL_BIND, &bind_nodemasks[1], maxnode, 0);
memset(mem, 0xaa, total_size);
kvm_move_pages(0, 4, pages, NULL, status, 0);
- TEST_ASSERT(status[0] == 0, "Expected page 0 on node 0, got it on node %d", status[0]);
- TEST_ASSERT(status[1] == 0, "Expected page 1 on node 0, got it on node %d", status[1]);
- TEST_ASSERT(status[2] == 1, "Expected page 2 on node 1, got it on node %d", status[2]);
- TEST_ASSERT(status[3] == 1, "Expected page 3 on node 1, got it on node %d", status[3]);
+ TEST_ASSERT(status[0] == nids[0],
+ "Expected page 0 on node %d, got it on node %d", nids[0], status[0]);
+ TEST_ASSERT(status[1] == nids[0],
+ "Expected page 1 on node %d, got it on node %d", nids[0], status[1]);
+ TEST_ASSERT(status[2] == nids[1],
+ "Expected page 2 on node %d, got it on node %d", nids[1], status[2]);
+ TEST_ASSERT(status[3] == nids[1],
+ "Expected page 3 on node %d, got it on node %d", nids[1], status[3]);
kvm_munmap(mem, total_size);
}
diff --git a/tools/testing/selftests/kvm/include/numaif.h b/tools/testing/selftests/kvm/include/numaif.h
index 98cf77cfc481..b89559b6ea3c 100644
--- a/tools/testing/selftests/kvm/include/numaif.h
+++ b/tools/testing/selftests/kvm/include/numaif.h
@@ -4,8 +4,6 @@
#ifndef SELFTEST_KVM_NUMAIF_H
#define SELFTEST_KVM_NUMAIF_H
-#include <dirent.h>
-
#include <linux/bits.h>
#include <linux/mempolicy.h>
@@ -31,51 +29,6 @@ KVM_SYSCALL_DEFINE(mbind, 6, void *, addr, unsigned long, size, int, mode,
const unsigned long *, nodemask, unsigned long, maxnode,
unsigned int, flags);
-static inline int get_max_numa_node(void)
-{
- struct dirent *de;
- int max_node = 0;
- DIR *d;
-
- /*
- * Assume there's a single node if the kernel doesn't support NUMA,
- * or if no nodes are found.
- */
- d = opendir("/sys/devices/system/node");
- if (!d)
- return 0;
-
- while ((de = readdir(d)) != NULL) {
- int node_id;
- char *endptr;
-
- if (strncmp(de->d_name, "node", 4) != 0)
- continue;
-
- node_id = strtol(de->d_name + 4, &endptr, 10);
- if (*endptr != '\0')
- continue;
-
- if (node_id > max_node)
- max_node = node_id;
- }
- closedir(d);
-
- return max_node;
-}
-
-static bool is_numa_available(void)
-{
- /*
- * Probe for NUMA by doing a dummy get_mempolicy(). If the syscall
- * fails with ENOSYS, then the kernel was built without NUMA support.
- * if the syscall fails with EPERM, then the process/user lacks the
- * necessary capabilities (CAP_SYS_NICE).
- */
- return !get_mempolicy(NULL, NULL, 0, NULL, 0) ||
- (errno != ENOSYS && errno != EPERM);
-}
-
static inline unsigned long get_numa_mem_nodes(void)
{
unsigned long nodemask = 0;
@@ -88,9 +41,4 @@ static inline unsigned long get_numa_mem_nodes(void)
return nodemask;
}
-static inline bool is_multi_numa_node_system(void)
-{
- return is_numa_available() && get_max_numa_node() >= 1;
-}
-
#endif /* SELFTEST_KVM_NUMAIF_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2 1/4] KVM: selftests: fix maxnode arguments in xapic_ipi_test
2026-09-01 8:22 ` [PATCH v2 1/4] KVM: selftests: fix maxnode arguments in xapic_ipi_test Shivank Garg
@ 2026-09-01 15:04 ` Sean Christopherson
0 siblings, 0 replies; 8+ messages in thread
From: Sean Christopherson @ 2026-09-01 15:04 UTC (permalink / raw)
To: Shivank Garg
Cc: Paolo Bonzini, David Hildenbrand, Shuah Khan, Jim Mattson,
Peter Shier, Ricardo Koller, Ackerley Tng, kvm, linux-kselftest,
linux-kernel
On Tue, Sep 01, 2026, Shivank Garg wrote:
> migrate_pages() syscall expect maxnode to be one greater than the
> number of bits in the nodemask. do_migrations() passes the size of
> nodemask in bytes to migrate_pages(). This sets the maxnode to 8,
> so kernel only checks node IDs 0-6 even though the nodemask covers
> node IDs 0-63.
>
> Pass the nodemask size in bits plus one because get_nodes() in
> mempolicy does --maxnode.
Ok, I'm not crazy. I read all of this multiple times and the manpages seemed
completely nonsensical. Looking at QEMU's use of mbind(), it's the kernel that
sucks:
/*
* We can have up to MAX_NODES nodes, but we need to pass maxnode+1
* as argument to mbind() due to an old Linux bug (feature?) which
* cuts off the last specified node. This means backend->host_nodes
* must have MAX_NODES+1 bits available.
*/
assert(sizeof(backend->host_nodes) >=
BITS_TO_LONGS(MAX_NODES + 1) * sizeof(unsigned long));
And from https://lore.kernel.org/all/63ccc890-fd57-118b-5997-e0259f507d28@suse.cz:
: And this is I think the reason why we can't change this now. I assume
: numactl allocates 1024 bits (0 to 1023) and passes 1025 to make sure all
: 1024 bits are processed. If we change it now, kernel will process 1025
: bits (0 to 1024) and overflow the allocated bitmask. If it happens to be
: at the border of mmaped vma, it's a segfault...
This is quite possibly the most confusing syscall interface ever, and IMO the
manpage is still straight up wrong (well, the kernel is the one that's buggy,
but the manpage doesn't reflect the kernel's behavior):
: The maxnode argument is the maximum node number in the bit mask plus one
Beacuse it's not the maximum node number plus one, it's the number of nodes in
the bitask plus one.
What a confusing mess.
> Fixes: 678e90a349a4 ("KVM: selftests: Test IPI to halted vCPU in xAPIC while backing page moves")
> Signed-off-by: Shivank Garg <shivankg@amd.com>
> ---
> tools/testing/selftests/kvm/x86/xapic_ipi_test.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> index 469e3ab16460..0f11f4d7cc3d 100644
> --- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> +++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
> @@ -291,7 +291,7 @@ void do_migrations(struct test_data_page *data, int run_secs, int delay_usecs,
> * KVM_CREATE_VCPU ioctl. If that assumption ever changes this
> * test may break or give a false positive signal.
> */
> - pages_not_moved = migrate_pages(0, sizeof(nodemasks[from]),
> + pages_not_moved = migrate_pages(0, sizeof(nodemasks[from]) * 8 + 1,
BITS_PER_TYPE()
However, given that it's basically impossible for developers to get this right,
we should add "#define MAXNODE_FOR_MASK(mask) (BITS_PER_TYPE(mask) + 1)" with a
big comment explain why the code looks wrong.
E.g. patch 3 gets it wrong in get_numa_mem_nodes():
static inline unsigned long get_numa_mem_nodes(void)
{
unsigned long nodemask = 0;
/* Get set of first 64 numa nodes available */
if (get_mempolicy(NULL, &nodemask, BITS_PER_TYPE(nodemask), NULL,
MPOL_F_MEMS_ALLOWED))
return 0;
return nodemask;
}
because that will only get the mask for bits 62:0. Even Sashiko got confused in
patch 4:
When maxnode is passed to kvm_get_mempolicy() here, the kernel must write at
least 5 bytes to return 33 bits of node status. This rounds up to 8 bytes (two
32-bit words).
since the disaster of a syscall that is get_mempolicy() and friends will only
provide 32 bits of node status.
Looking at the rest of the patches in this series, the main goal of fixing the
extremely-unlikely-to-happen-in-practice bug in patch 4 needs a lot of work. To
move along the other cleanups, I'll send the below plus rebased versions of
patches 1 and 2.
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 2233d871a38f..cd5df88bc642 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -80,7 +80,7 @@ static void test_mbind(int fd, size_t total_size)
{
const unsigned long nodemask_0 = 1; /* nid: 0 */
unsigned long nodemask = 0;
- unsigned long maxnode = BITS_PER_TYPE(nodemask);
+ unsigned long maxnode = MAXNODE_FOR_MASK(nodemask);
int policy;
char *mem;
int ret;
diff --git a/tools/testing/selftests/kvm/include/numaif.h b/tools/testing/selftests/kvm/include/numaif.h
index 29572a6d789c..84e82857f1c5 100644
--- a/tools/testing/selftests/kvm/include/numaif.h
+++ b/tools/testing/selftests/kvm/include/numaif.h
@@ -30,6 +30,16 @@ KVM_SYSCALL_DEFINE(mbind, 6, void *, addr, unsigned long, size, int, mode,
const unsigned long *, nodemask, unsigned long, maxnode,
unsigned int, flags);
+/*
+ * Caclucate the @maxnode param for the above syscalls given the mask that will
+ * be passed to the kernel, to account for a longstanding off-by-one bug in the
+ * kernel that isn't properly documented in the manpages. The manpages say
+ * that @maxnode is "the maximum node ID plus one", but the kernel's actual
+ * behavior is "the number of bits in the mask plus one", i.e. "the maximum
+ * node ID plus two".
+ */
+#define MAXNODE_FOR_MASK(mask) (BITS_PER_TYPE(mask) + 1)
+
static inline int get_max_numa_node(void)
{
struct dirent *de;
diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
index 469e3ab16460..1ddcf95d7fe4 100644
--- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
+++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
@@ -248,7 +248,7 @@ void do_migrations(struct test_data_page *data, int run_secs, int delay_usecs,
delay_usecs);
/* Get set of first 64 numa nodes available */
- kvm_get_mempolicy(NULL, &nodemask, sizeof(nodemask) * 8,
+ kvm_get_mempolicy(NULL, &nodemask, MAXNODE_FOR_MASK(nodemask),
0, MPOL_F_MEMS_ALLOWED);
fprintf(stderr, "Numa nodes found amongst first %lu possible nodes "
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2 3/4] KVM: selftests: add get_numa_mem_nodes()
2026-09-01 8:22 ` [PATCH v2 3/4] KVM: selftests: add get_numa_mem_nodes() Shivank Garg
@ 2026-09-01 15:06 ` Sean Christopherson
0 siblings, 0 replies; 8+ messages in thread
From: Sean Christopherson @ 2026-09-01 15:06 UTC (permalink / raw)
To: Shivank Garg
Cc: Paolo Bonzini, David Hildenbrand, Shuah Khan, Jim Mattson,
Peter Shier, Ricardo Koller, Ackerley Tng, kvm, linux-kselftest,
linux-kernel
On Tue, Sep 01, 2026, Shivank Garg wrote:
> The xAPIC IPI test uses MPOL_F_MEMS_ALLOWED to get the memory nodes
> available to the current process. Move the query to numaif.h as
> get_numa_mem_nodes() so other KVM selftests can use it.
>
> Call get_mempolicy() directly instead of using the assert-on-failure
> kvm_get_mempolicy() wrapper. The xAPIC test already asserts its two
> node requirement.
NAK. This is not an improvement. get_mempolicy() straight up should not fail
for this usage.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 4/4] KVM: selftests: use allowed NUMA nodes in guest_memfd_test
2026-09-01 8:22 ` [PATCH v2 4/4] KVM: selftests: use allowed NUMA nodes in guest_memfd_test Shivank Garg
@ 2026-09-01 17:28 ` Sean Christopherson
0 siblings, 0 replies; 8+ messages in thread
From: Sean Christopherson @ 2026-09-01 17:28 UTC (permalink / raw)
To: Shivank Garg
Cc: Paolo Bonzini, David Hildenbrand, Shuah Khan, Jim Mattson,
Peter Shier, Ricardo Koller, Ackerley Tng, kvm, linux-kselftest,
linux-kernel
On Tue, Sep 01, 2026, Shivank Garg wrote:
> guest_memfd_test assumes that nodes 0 and 1 exist and have memory.
> is_multi_numa_node_system() only checks that the maximum node ID is
> nonzero, which is not enough for sparse or memoryless nodes.
>
> Select the required nodes from MPOL_F_MEMS_ALLOWED instead. Use the full
> nodemask width plus one to mbind(), and let test_mbind() run when only
> one memory node is available.
Please split this into at least three patches.
1. Refactor guest_memfd_test.c to prepare for using nodes other than 0 and 1.
2. Fix test_mbind().
3. Fix test_numa_allocation().
4. If necessary, do additional cleanups in numaif.h
As is, this is extremely difficult to review, e.g. without staring intently, I
can't tell what's refactoring and what's actually a functional change.
Actually, looking at the xAPIC IPI test more, what you proposed in patch 3 is in
the general direction of what we want, but needs to be more than just a wrapper
for get_mempolicy() to be useful. Specificaly, if it fills the mask *and* returns
the number of nodes found, then it's more generically useful. And if we also add
an API to get the next (exclusive) node, then we can cut down on the amount of
copy+paste without forcing tests to use the "array of one-bit nodemasks" approach
that the xAPIC test uses.
E.g. the below get_numa_node_ids() is basically just copy+paste from the xAPIC
test, except that it returns an array of nodes instead of an array of nodemasks.
The fact that you felt compelled to copy+paste instead of adding an API is quite
telling: using an array of nodemasks/nodes is inflexible and really only works if
a test wants to target exactly one node. It's also annoying to extract to a generic
API without ending up with a brittle API. E.g. if the API where to take the a mask
and an array, it would either have to be a macro or take a struct to ensure the
array can hold all possible masks.
I'm planning on adding these in the series to also add MAXNODE_FOR_MASK().
static inline int kvm_get_numa_memory_nodes(unsigned long *nodemask)
{
int r;
*nodemask = 0;
r = get_mempolicy(NULL, nodemask, MAXNODE_FOR_MASK(*nodemask), 0,
MPOL_F_MEMS_ALLOWED);
TEST_ASSERT(!r || errno == ENOSYS || errno == EPERM,
"Unexpected get_mempolicy() failure");
return __builtin_popcountl(*nodemask);
}
/*
* Return the node ID of the next NUMA node in the mask, starting at @from+1.
* Guarantees a node is found, and that the found node is not @from. Pass -1
* to find the first node in the mask.
*/
static inline int kvm_get_next_numa_node(unsigned long nodemask, int from)
{
const unsigned long nr_bits = BITS_PER_TYPE(nodemask);
int to;
to = find_next_bit(&nodemask, nr_bits, from + 1);
if (to == nr_bits)
to = find_next_bit(&nodemask, nr_bits, 0);
TEST_ASSERT(to != nr_bits && to != from,
"Unabled to find second NUMA node (from = %d, to = %d)", from, to);
return to;
}
> The sysfs helpers for finding maxnode are no longer needed.
This is an observation, not a proper changelog sentence.
> Signed-off-by: Shivank Garg <shivankg@amd.com>
> ---
> tools/testing/selftests/kvm/guest_memfd_test.c | 86 +++++++++++++++++---------
> tools/testing/selftests/kvm/include/numaif.h | 52 ----------------
> 2 files changed, 58 insertions(+), 80 deletions(-)
>
> diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
> index 2233d871a38f..aee80dda6229 100644
> --- a/tools/testing/selftests/kvm/guest_memfd_test.c
> +++ b/tools/testing/selftests/kvm/guest_memfd_test.c
> @@ -76,33 +76,53 @@ static void test_mmap_supported(int fd, size_t total_size)
> kvm_munmap(mem, total_size);
> }
>
> +/*
> + * Fill @nids with the first @nr_nids nodes in the allowed mask.
> + * Return false if the mask contains fewer than @nr_nids nodes.
> + */
> +static bool get_numa_node_ids(int *nids, int nr_nids)
> +{
> + unsigned long nodemask = get_numa_mem_nodes();
> + unsigned long nid;
> + int nr_found = 0;
> +
> + for_each_set_bit(nid, &nodemask, BITS_PER_TYPE(nodemask)) {
> + nids[nr_found++] = nid;
> + if (nr_found == nr_nids)
> + return true;
> + }
> +
> + return false;
> +}
> +
> static void test_mbind(int fd, size_t total_size)
> {
> - const unsigned long nodemask_0 = 1; /* nid: 0 */
> - unsigned long nodemask = 0;
> - unsigned long maxnode = BITS_PER_TYPE(nodemask);
> + unsigned long nodemask, bind_nodemask;
> + unsigned long maxnode = BITS_PER_TYPE(nodemask) + 1;
> int policy;
> char *mem;
> + int nid;
> int ret;
>
> - if (!is_multi_numa_node_system())
> + if (!get_numa_node_ids(&nid, 1))
This is not functionally equivalent. The existing test requires multiple NUMA
nodes, whereas this will now succeed if there's exactly one node. That could be
totally fine, but it needs to be isolated and explained in its own patch.
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-01 17:28 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-01 8:22 [PATCH v2 0/4] KVM: guest_memfd: fix NUMA selftests Shivank Garg
2026-09-01 8:22 ` [PATCH v2 1/4] KVM: selftests: fix maxnode arguments in xapic_ipi_test Shivank Garg
2026-09-01 15:04 ` Sean Christopherson
2026-09-01 8:22 ` [PATCH v2 2/4] KVM: selftests: use BITS_PER_TYPE() for NUMA masks Shivank Garg
2026-09-01 8:22 ` [PATCH v2 3/4] KVM: selftests: add get_numa_mem_nodes() Shivank Garg
2026-09-01 15:06 ` Sean Christopherson
2026-09-01 8:22 ` [PATCH v2 4/4] KVM: selftests: use allowed NUMA nodes in guest_memfd_test Shivank Garg
2026-09-01 17:28 ` Sean Christopherson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).