[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH for-4.22 4/5] tests/numa: add unit tests for NUMA setup logic


  • To: xen-devel@xxxxxxxxxxxxxxxxxxxx
  • From: Roger Pau Monne <roger.pau@xxxxxxxxxx>
  • Date: Mon, 1 Jun 2026 17:43:31 +0200
  • Arc-authentication-results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=citrix.com; dmarc=pass action=none header.from=citrix.com; dkim=pass header.d=citrix.com; arc=none
  • Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=baK8cjPkKPNArYKcbk9tpmKQdKKjM8clHoyqodwv1j4=; b=PbEP1Z58ExsHi25tScLMeLt0XCtVpJIuW/mWyyL41fLBvd9cuujiQM2kE7W+N88dCJSHEjnE8R4pe1vyCKrW0M8rPmnoB/TOgQe7rBWBAf1aQ4H1+2OG2wnXn6KMB67aoFFpyFAcvb2si7hLX82k8PlRLThklvusDF3I43SAUsU42lfxfHOIGPj2FgJQlmfmXRAuO2dp2hCJx8MSaXi8dbbo+m+vv+KJdnwzsHTBL/C4nnmaYpTcnPqzN0d963a6niXCZix1E+7Lga0He3gQwjzVU+Tn6Ul2r94Ut2I+TeqCn6p5JfJV4qHY1sudgCZG1kGwPO11ZV6tNY8NGbS+bA==
  • Arc-seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=uQx0a8Rr5iutAkcp2r6oOMB/S/zHyey9ohjai1lCRRpMdX6UcBn9VIrUaOJOdpqExaRHAIm7/9zv+aPLv3lDPbo74OGT7Lm6hArFCoRCrChlW5qpD7/SEfhNAzo4Dz7GfTwphH+Uszjz3ROHuttRv8UV8DTnietW0Uec3v7SnXF7QQ2FWbHp9gEn+RWkI74G4IfIdSC6bYAhluaocOiq5OqRG3hXgeYgbtvI6x5jYAOmleQmfpbXgBdBiP3oKPm79ebfrihDdNEaTqIrFFBVplIr9JtXWpYkTIxCgpAiOFB1IPu0QAo/Y+vSNfSCeArG7lXtd+t0wXvztNHNLXtPhg==
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=selector1 header.d=citrix.com header.i="@citrix.com" header.h="From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck"
  • Authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=citrix.com;
  • Cc: Oleksii Kurochko <oleksii.kurochko@xxxxxxxxx>, Roger Pau Monne <roger.pau@xxxxxxxxxx>, Anthony PERARD <anthony.perard@xxxxxxxxxx>
  • Delivery-date: Mon, 01 Jun 2026 15:44:18 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

NUMA setup, like PDX, requires certain amount of logic to configure the
internal structures and parameters for NUMA operation.  Introduce some very
basic testing that allows building and testing NUMA setup logic in as a
user-space unit test.  This allows feeding synthetic memory affinity and
map to the logic, allowing to reproduce bugs that would otherwise need
access to real systems with such a configuration.

For the time being introduce a single test case, based on a known working
NUMA setup for an AMD Turin system.  Also the testing after setup is
currently limited to ensuring the start and end RAM region addresses fall
into a correctly setup memory block.

Signed-off-by: Roger Pau Monné <roger.pau@xxxxxxxxxx>
---
 tools/tests/Makefile         |   1 +
 tools/tests/numa/.gitignore  |   2 +
 tools/tests/numa/Makefile    |  47 ++++++++
 tools/tests/numa/harness.h   | 184 +++++++++++++++++++++++++++++
 tools/tests/numa/test-numa.c | 222 +++++++++++++++++++++++++++++++++++
 5 files changed, 456 insertions(+)
 create mode 100644 tools/tests/numa/.gitignore
 create mode 100644 tools/tests/numa/Makefile
 create mode 100644 tools/tests/numa/harness.h
 create mode 100644 tools/tests/numa/test-numa.c

diff --git a/tools/tests/Makefile b/tools/tests/Makefile
index 6477a4386dda..fc0ed8091510 100644
--- a/tools/tests/Makefile
+++ b/tools/tests/Makefile
@@ -4,6 +4,7 @@ include $(XEN_ROOT)/tools/Rules.mk
 SUBDIRS-y :=
 SUBDIRS-y += domid
 SUBDIRS-y += mem-claim
+SUBDIRS-y += numa
 SUBDIRS-y += paging-mempool
 SUBDIRS-y += pdx
 SUBDIRS-y += rangeset
diff --git a/tools/tests/numa/.gitignore b/tools/tests/numa/.gitignore
new file mode 100644
index 000000000000..0710a767f400
--- /dev/null
+++ b/tools/tests/numa/.gitignore
@@ -0,0 +1,2 @@
+/numa.h
+/test-numa
diff --git a/tools/tests/numa/Makefile b/tools/tests/numa/Makefile
new file mode 100644
index 000000000000..5235f9d6297f
--- /dev/null
+++ b/tools/tests/numa/Makefile
@@ -0,0 +1,47 @@
+XEN_ROOT=$(CURDIR)/../../..
+include $(XEN_ROOT)/tools/Rules.mk
+
+TARGETS := test-numa
+
+.PHONY: all
+all: $(TARGETS)
+
+.PHONY: run
+run: $(TARGETS)
+ifeq ($(CC),$(HOSTCC))
+       set -e;             \
+       for test in $? ; do \
+               ./$$test ;  \
+       done
+else
+       $(warning HOSTCC != CC, will not run test)
+endif
+
+.PHONY: clean
+clean:
+       $(RM) -- *.o $(TARGETS) $(DEPS_RM) numa.h
+
+.PHONY: distclean
+distclean: clean
+       $(RM) -- *~
+
+.PHONY: install
+install: all
+       $(INSTALL_DIR) $(DESTDIR)$(LIBEXEC)/tests
+       $(INSTALL_PROG) $(TARGETS) $(DESTDIR)$(LIBEXEC)/tests
+
+.PHONY: uninstall
+uninstall:
+       $(RM) -- $(patsubst %,$(DESTDIR)$(LIBEXEC)/tests/%,$(TARGETS))
+
+numa.h: $(XEN_ROOT)/xen/include/xen/numa.h
+       sed -e '/^#[[:space:]]*include/d' <$< >$@
+
+CFLAGS += -D__XEN_TOOLS__
+CFLAGS += $(APPEND_CFLAGS)
+CFLAGS += $(CFLAGS_xeninclude)
+
+test-numa: test-numa.c numa.h
+       $(CC) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_$*.o) -o $@ $< $(APPEND_CFLAGS)
+
+-include $(DEPS_INCLUDE)
diff --git a/tools/tests/numa/harness.h b/tools/tests/numa/harness.h
new file mode 100644
index 000000000000..71ff9c792191
--- /dev/null
+++ b/tools/tests/numa/harness.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Unit tests for NUMA setup.
+ *
+ * Copyright (C) 2026 Cloud Software Group
+ */
+
+#ifndef _TEST_HARNESS_
+#define _TEST_HARNESS_
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xen-tools/bitops.h>
+#include <xen-tools/common-macros.h>
+
+#define CONFIG_DEBUG
+#define CONFIG_NUMA
+#define CONFIG_NR_NUMA_NODES 64
+#define NR_CPUS 256
+#define MAX_RANGES 128
+#define PADDR_BITS 52
+
+#define __init
+#define __initdata
+#define __ro_after_init
+#define __read_mostly
+
+#define printk printf
+#define XENLOG_INFO ""
+#define XENLOG_DEBUG ""
+#define XENLOG_WARNING ""
+#define KERN_INFO ""
+#define KERN_ERR ""
+#define KERN_WARNING ""
+#define KERN_DEBUG ""
+
+#define PAGE_SHIFT    12
+/* Some libcs define PAGE_SIZE in limits.h. */
+#undef  PAGE_SIZE
+#define PAGE_SIZE     (1L << PAGE_SHIFT)
+#define MAX_ORDER     18 /* 2 * PAGETABLE_ORDER (9) */
+
+#define PFN_DOWN(x)   ((x) >> PAGE_SHIFT)
+#define PFN_UP(x)     (((x) + PAGE_SIZE-1) >> PAGE_SHIFT)
+
+#define paddr_to_pfn(pa)  ((unsigned long)((pa) >> PAGE_SHIFT))
+#define mfn_to_pdx(mfn)   (mfn)
+#define paddr_to_pdx(pa)  ((pa) >> PAGE_SHIFT)
+#define mfn_to_maddr(mfn) ((mfn) << PAGE_SHIFT)
+
+#define ASSERT assert
+#define ASSERT_UNREACHABLE() assert(0)
+
+/* For the purposes of the testing assume arch NID == Xen NID. */
+#define numa_node_to_arch_nid(n) (n)
+
+typedef uint64_t paddr_t;
+#define PRIpaddr "016" PRIx64
+
+typedef unsigned long mfn_t;
+typedef uint8_t nodeid_t;
+
+#define __set_bit set_bit
+#define __clear_bit clear_bit
+
+static inline unsigned int find_next_bit(
+    const unsigned long *addr, unsigned int size, unsigned int off)
+{
+    unsigned int i;
+
+    ASSERT(size <= BITS_PER_LONG);
+
+    for ( i = off; i < size; i++ )
+        if ( !!(*addr & (1UL << i)) )
+            return i;
+
+    return size;
+}
+
+#define find_first_bit(b, s) find_next_bit(b, s, 0)
+
+/* Minimal cpumask support. */
+typedef struct cpumask{ DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;
+
+#define cpumask_clear_cpu(c, m) clear_bit((c), (m)->bits)
+
+/* Define the nodemask helpers used. */
+typedef struct nodemask{ DECLARE_BITMAP(bits, CONFIG_NR_NUMA_NODES); } 
nodemask_t;
+
+#define node_set(node, dst) set_bit((node), (dst).bits)
+
+#define first_node(n) __first_node(&(n), CONFIG_NR_NUMA_NODES)
+static inline int __first_node(const nodemask_t *srcp, unsigned int s)
+{
+    return min(s, find_next_bit(srcp->bits, s, 0));
+}
+
+#define next_node(n, m) __next_node((n), &(m), CONFIG_NR_NUMA_NODES)
+static inline int __next_node(unsigned int n, const nodemask_t *srcp,
+                              unsigned int s)
+{
+    return min(s, find_next_bit(srcp->bits, s, n + 1));
+}
+
+#define nodes_or(dst, src1, src2) \
+    bitmap_or((dst).bits, (src1).bits, (src2).bits, CONFIG_NR_NUMA_NODES)
+
+static inline bool nodemask_test(unsigned int node, const nodemask_t *dst)
+{
+    return test_bit(node, dst->bits);
+}
+
+#define node_set_online(node)     set_bit((node), node_online_map.bits)
+
+#define cycle_node(n, src) __cycle_node((n), &(src), MAX_NUMNODES)
+static inline int __cycle_node(int n, const nodemask_t *maskp,
+                               unsigned int nbits)
+{
+    unsigned int nxt = __next_node(n, maskp, nbits);
+
+    if ( nxt == nbits )
+        nxt = __first_node(maskp, nbits);
+
+    return nxt;
+}
+
+#define for_each_node_mask(node, mask)                  \
+    for ( (node) = first_node(mask);                    \
+          (node) < MAX_NUMNODES;                        \
+          (node) = next_node((node), (mask)) )
+
+/*
+ * Dummy helper to satisfy allocate_cachealigned_memnodemap(), the memory
+ * allocation is instead done in vmap_contig().
+ */
+static inline mfn_t alloc_boot_pages(unsigned long nr, unsigned long align)
+{
+    return 0;
+}
+
+static inline void *vmap_contig(mfn_t mfn, unsigned int nr)
+{
+    assert(!mfn);
+    return calloc(PAGE_SIZE, nr);
+}
+
+static inline void panic(const char *msg)
+{
+    printf("%s\n", msg);
+    abort();
+}
+
+/* Dummy implementations to satisfy the build. */
+static inline bool arch_numa_disabled(void)
+{
+    return false;
+}
+
+static inline void numa_fw_bad(void) { }
+
+static inline bool arch_numa_unavailable(void)
+{
+    return false;
+}
+
+#include "numa.h"
+
+#endif
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/tools/tests/numa/test-numa.c b/tools/tests/numa/test-numa.c
new file mode 100644
index 000000000000..bced68d4d7f1
--- /dev/null
+++ b/tools/tests/numa/test-numa.c
@@ -0,0 +1,222 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Unit tests for NUMA setup.
+ *
+ * Copyright (C) 2026 Cloud Software Group
+ */
+
+#include "harness.h"
+
+static paddr_t mem_hotplug;
+unsigned int __read_mostly nr_cpu_ids = NR_CPUS;
+
+#include "../../xen/common/numa.c"
+
+static void numa_reset_state(void)
+{
+    bitmap_clear(processor_nodes_parsed.bits, CONFIG_NR_NUMA_NODES);
+    bitmap_clear(memory_nodes_parsed.bits, CONFIG_NR_NUMA_NODES);
+    bitmap_clear(memblk_hotplug, NR_NODE_MEMBLKS);
+    memset(numa_nodes, 0, sizeof(numa_nodes));
+    memset(node_memblk_range, 0, sizeof(node_memblk_range));
+    memset(memblk_nodeid, 0, sizeof(memblk_nodeid));
+    memset(node_data, 0, sizeof(node_data));
+    memset(node_to_cpumask, 0, sizeof(node_to_cpumask));
+    memset(cpu_to_node, NUMA_NO_NODE, sizeof(cpu_to_node));
+    num_node_memblks = 0;
+    memnode_shift = 0;
+    memnodemapsize = 0;
+    if ( memnodemap != _memnodemap )
+        free(memnodemap);
+    memnodemap = NULL;
+    bitmap_clear(node_online_map.bits, CONFIG_NR_NUMA_NODES);
+    node_set(1, node_online_map);
+}
+
+struct mem_affinity {
+    /* Ranges are defined as [start, end]. */
+    paddr_t start, end;
+    unsigned int nid;
+};
+
+struct mem_range {
+    /* Ranges are defined as [start, end]. */
+    paddr_t start, end;
+};
+
+const static struct mem_range *ram;
+
+int arch_get_ram_range(unsigned int idx, paddr_t *start, paddr_t *end)
+{
+    if ( idx >= MAX_RANGES || !ram[idx].end )
+        return -ENOENT;
+
+    *start = ram[idx].start;
+    *end = ram[idx].end + 1;
+
+    return 0;
+}
+
+static void print_ranges(const struct mem_affinity *r)
+{
+    unsigned int i;
+
+    printf("Affinity ranges:\n");
+    for ( i = 0; i < MAX_RANGES; i++ )
+    {
+        if ( !r[i].end )
+            break;
+
+        printf(" NID %u [%" PRIpaddr ", %" PRIpaddr "]\n",
+               r[i].nid, r[i].start, r[i].end);
+    }
+
+    printf("RAM ranges:\n");
+    for ( i = 0; i < MAX_RANGES; i++ )
+    {
+        if ( !ram[i].end )
+            break;
+
+        printf(" [%" PRIpaddr ", %" PRIpaddr "]\n",
+               ram[i].start, ram[i].end);
+    }
+}
+
+static bool test_paddr(paddr_t addr)
+{
+    mfn_t mfn = PFN_DOWN(addr);
+    unsigned int idx = mfn >> memnode_shift;
+    unsigned int nid;
+
+    if ( idx >= memnodemapsize )
+    {
+        printf("Fail: MFN %lx -> IDX %u outside of memnodemap range\n",
+               mfn, idx);
+        return false;
+    }
+
+    nid = memnodemap[idx];
+    if ( nid >= MAX_NUMNODES )
+    {
+        printf("Fail: MFN %lx -> NID %u >= MAX_NUMNODES (%u)\n",
+               mfn, nid, MAX_NUMNODES);
+        return false;
+    }
+
+    if ( !node_data[nid].node_spanned_pages )
+    {
+        printf("Fail: MFN %lx -> NID %u without spanned pages\n",
+               mfn, nid);
+        return false;
+
+    }
+
+    if ( !node_data[nid].node_spanned_pages )
+    {
+        printf("Fail: MFN %lx -> NID %u without spanned pages\n",
+               mfn, nid);
+        return false;
+
+    }
+
+    if ( !node_data[nid].node_spanned_pages )
+    {
+        printf("Fail: MFN %lx outside NID range [%013lx, %013lx]\n",
+               mfn, node_data[nid].node_start_pfn,
+               node_data[nid].node_start_pfn +
+               node_data[nid].node_spanned_pages - 1);
+        return false;
+    }
+
+    return true;
+}
+
+int main(int argc, char **argv)
+{
+    static const struct {
+        struct mem_affinity affinity[MAX_RANGES];
+        struct mem_range ram[MAX_RANGES];
+    } tests[] = {
+        /* AMD Turin system. */
+        {
+            .affinity = {
+                { .nid = 0, .start = 0x00000000000ULL, .end = 0x0000009ffffULL 
},
+                { .nid = 0, .start = 0x000000c0000ULL, .end = 0x000afffffffULL 
},
+                { .nid = 0, .start = 0x00100000000ULL, .end = 0x0c04fffffffULL 
},
+                { .nid = 1, .start = 0x0c050000000ULL, .end = 0x0fc4fffffffULL 
},
+                { .nid = 1, .start = 0x10000000000ULL, .end = 0x183ffffffffULL 
},
+            },
+            .ram = {
+                { .start = 0x00000000000ULL, .end = 0x0000009ffffULL },
+                { .start = 0x00000100000ULL, .end = 0x0007590ffffULL },
+                { .start = 0x000759d1000ULL, .end = 0x00075a0ffffULL },
+                { .start = 0x00076000000ULL, .end = 0x00094c73fffULL },
+                { .start = 0x0009b5ff000ULL, .end = 0x0009fff9fffULL },
+                { .start = 0x0009ffff000ULL, .end = 0x0009fffffffULL },
+                { .start = 0x00100010000ULL, .end = 0x0fc4fffffffULL },
+                { .start = 0x10000000000ULL, .end = 0x183f7ffffffULL },
+                { .start = 0x183f8800000ULL, .end = 0x183faabffffULL },
+            },
+        },
+    };
+    int ret_code = EXIT_SUCCESS;
+
+    /* Dummy firmware interface provider name, use TST for TEST. */
+    numa_fw_nid_name = "TST";
+
+    for ( unsigned int i = 0 ; i < ARRAY_SIZE(tests); i++ )
+    {
+        paddr_t min = ~(paddr_t)0, max = 0;
+        unsigned int j;
+
+        numa_reset_state();
+
+        ram = tests[i].ram;
+
+        for ( j = 0;
+              j < ARRAY_SIZE(tests[i].affinity) && tests[i].affinity[j].end;
+              j++ )
+        {
+            const struct mem_affinity *affinity = &tests[i].affinity[j];
+            paddr_t length = affinity->end - affinity->start + 1;
+
+            if ( !numa_update_node_memblks(affinity->nid, affinity->nid,
+                                           affinity->start, length, false) )
+            {
+                printf("Fail to add NID %u [%" PRIpaddr ", %" PRIpaddr "]\n",
+                        affinity->nid, affinity->start, affinity->end);
+                ret_code = EXIT_FAILURE;
+                continue;
+            }
+
+            min = min(min, affinity->start);
+            max = max(max, affinity->end);
+        }
+
+        if ( !numa_process_nodes(min, max + 1) )
+        {
+                printf("Unable to process nodes\n");
+                print_ranges(tests[i].affinity);
+                ret_code = EXIT_FAILURE;
+                continue;
+        }
+
+        for ( j = 0;
+              j < ARRAY_SIZE(tests[i].ram) && tests[i].ram[j].end;
+              j++ )
+            if ( !test_paddr(tests[i].ram[j].start) ||
+                 !test_paddr(tests[i].ram[j].end) )
+                ret_code = EXIT_FAILURE;
+    }
+
+    return ret_code;
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
-- 
2.53.0




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.