Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,18 @@ patches:
- patch: 0003-x86-amd_node-fix-null-pointer-dereference-if-amd_smn.patch
lower: '6.17'
- patches:
- 0001-xen-events-add-_on_node-variants-of-the-lateeoi-bind.patch
- 0002-xen-xenbus-expose-host-NUMA-node-of-a-mapped-ring.patch
- 0003-xen-netback-place-per-queue-kthreads-and-IRQs-near-t.patch
- 0004-xen-blkback-place-per-ring-kthread-and-IRQ-near-the-.patch
- 0005-xen-make-xen_alloc_unpopulated_pages-NUMA-aware.patch
- 0006-xen-xenbus-collapse-xenbus_ring_host_node-to-a-page_.patch
- 0001-xen-evtchn-diagnose-ring_overflow-with-post-barrier-.patch
- 0002-xen-add-xen_mfn_to_node-to-resolve-a-foreign-frame-s.patch
- 0003-xen-make-xen_alloc_unpopulated_pages-NUMA-aware.patch
- 0004-xen-grant-table-add-gnttab_alloc_pages_node.patch
- 0005-xen-events-add-_on_node-variants-of-the-lateeoi-bind.patch
- 0006-xen-xenbus-home-ring-mappings-on-the-foreign-frame-s.patch
- 0007-xen-xenbus-add-xenbus_setup_ring_node-for-per-node-r.patch
- 0008-xen-netfront-place-per-queue-rings-on-per-queue-node.patch
- 0009-xen-blkfront-place-per-ring-buffers-on-per-hctx-node.patch
- 0008-xen-netback-place-per-queue-kthreads-and-IRQs-near-t.patch
- 0009-xen-blkback-place-per-ring-kthread-and-IRQ-near-the-.patch
- 0010-xen-netfront-place-per-queue-rings-on-per-queue-node.patch
- 0011-xen-blkfront-place-per-ring-buffers-on-per-hctx-node.patch
- 0012-xen-gntdev-home-grant-map-placeholder-pages-on-the-f.patch
lower: '6.18'
- patches:
- 0001-xen-xlate_mmu-relocate-remap_pfn-utils.patch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
From 35760e39333ee8b3786ca54eb8e23ad72358b035 Mon Sep 17 00:00:00 2001
From: Steven Noonan <steven@edera.dev>
Date: Mon, 20 Jul 2026 13:49:47 -0700
Subject: [PATCH 01/12] xen/evtchn: diagnose ring_overflow with post-barrier
cons re-read

We've been seeing /dev/xen/evtchn fds enter the ring_overflow=1
state under sustained event load (specifically: many ports bound
to a single fd, all firing concurrently during a daemon-side
metrics-collection storm). Once the flag latches, every read
returns -EFBIG and the fd is dead for event delivery until reset.

The per-port masking invariant (each bound port can occupy at
most one ring slot, because lateeoi_ack_dynirq masks the channel
at Xen level before evtchn_interrupt runs and userspace can't
unmask until it reads + writes the port back) combined with the
ring being sized to nr_evtchns means overflow should not be
reachable. No "Interrupt for port N, but apparently not enabled"
WARNs precede the EFBIG state on hosts where this happens, so
the masking invariant is holding.

One plausible candidate: the producer's READ_ONCE(ring_cons) in
evtchn_interrupt has no acquire pairing with the consumer's
WRITE_ONCE in evtchn_read -- they take ring_prod_lock and
ring_cons_mutex respectively, neither of which synchronizes
ring_cons between them. The smp_wmb/smp_rmb pair already
present covers ring entry visibility against ring_prod, not
ring_cons. A producer that fires right after the consumer
drains can observe a stale ring_cons and compute
(prod - stale_cons) >= ring_size when the ring actually has
room. On x86-TSO the staleness window is bounded to
store-buffer drain time but is non-zero; on weakly-ordered
hardware it's wider.

Add an observe-only diagnostic at the overflow point: before
setting ring_overflow, issue an smp_mb() and re-read ring_cons.
Emit a rate-limited pr_warn with prod, both cons values, both
depths, ring_size, nr_evtchns, and the triggering port's
enabled/unbinding flags, tagging the log with " SPURIOUS" when
the post-barrier depth is below ring_size (i.e., the original
overflow detection was based on a stale view).

This does not change runtime behavior other than emitting a log
line at the overflow boundary (which already required entering
a path that wedges the fd, so the printk cost is irrelevant).
If the diagnostic confirms the stale-cons hypothesis, the real
fix is to switch ring_cons access to smp_load_acquire /
smp_store_release pairing.

Signed-off-by: Steven Noonan <steven@edera.dev>
---
drivers/xen/evtchn.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/drivers/xen/evtchn.c b/drivers/xen/evtchn.c
index 7e4a13e632dc..85619b041ee8 100644
--- a/drivers/xen/evtchn.c
+++ b/drivers/xen/evtchn.c
@@ -189,8 +189,37 @@ static irqreturn_t evtchn_interrupt(int irq, void *data)
kill_fasync(&u->evtchn_async_queue,
SIGIO, POLL_IN);
}
- } else
+ } else {
+ unsigned int cons_now;
+
+ /*
+ * Diagnostic: the READ_ONCE of ring_cons above has no
+ * acquire pairing with the WRITE_ONCE in evtchn_read --
+ * the producer holds ring_prod_lock and the consumer
+ * holds ring_cons_mutex, so they don't synchronize on
+ * ring_cons. On weakly-ordered hardware (and within
+ * the x86 store-buffer drain window) the producer can
+ * observe a stale ring_cons and conclude the ring is
+ * full when it actually has room. Re-read with a full
+ * barrier before declaring overflow and tag the log as
+ * SPURIOUS when the post-barrier read shows the ring
+ * had room -- that pins the cause on the cons
+ * visibility race rather than a real fill-to-capacity
+ * or a broken per-port masking invariant.
+ */
+ smp_mb();
+ cons_now = READ_ONCE(u->ring_cons);
+
+ pr_warn_ratelimited(
+ "xen-evtchn overflow on %s: port=%u prod=%u cons=%u cons_after_mb=%u depth=%u depth_after_mb=%u ring_size=%u nr_evtchns=%u enabled=%d unbinding=%d%s\n",
+ u->name, evtchn->port, prod, cons, cons_now,
+ prod - cons, prod - cons_now,
+ u->ring_size, u->nr_evtchns,
+ (int)evtchn->enabled, (int)evtchn->unbinding,
+ (prod - cons_now) < u->ring_size ? " SPURIOUS" : "");
+
u->ring_overflow = 1;
+ }

spin_unlock(&u->ring_prod_lock);

--
2.55.0

Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
From b675cdf0b8448a460ff207a708aa272532b8a9a3 Mon Sep 17 00:00:00 2001
From: Steven Noonan <steven@edera.dev>
Date: Mon, 20 Jul 2026 13:51:31 -0700
Subject: [PATCH 02/12] xen: add xen_mfn_to_node to resolve a foreign frame's
host NUMA node

Dom0 code that grant-maps foreign pages -- xenbus ring mappings,
gntdev -- has no way to learn which host NUMA node backs a foreign
frame, so nothing downstream (kthread and IRQ placement, placeholder
page homing) can be made node-local to the guest being served. Every
backend kthread for every queue piles onto whatever node it happens
to land on, regardless of the guest's vNUMA layout.

Add xen_mfn_to_node(), resolving one host MFN to a Linux node id via
the new XENMEM_get_mfn_pxms hypercall, along with the hypercall's
interface definition. It lives in grant-table.c because its callers
are the grant-mapping paths that need to home placeholder pages.

Three NUMA identifier namespaces are involved. Xen returns host PXM
(firmware-supplied), matching what dom0's own SRAT already uses;
pxm_to_node() converts to a Linux dom0 node id, which is what callers
feed to Linux helpers like cpumask_of_node() and
kthread_create_on_node(). Keeping the Xen-side ABI in PXM-space lets
callers translate with one standard lookup instead of maintaining
their own Xen-nid -> Linux-node table.

The query is meaningful only in the hardware domain: Xen restricts
XENMEM_get_mfn_pxms to it, and a guest has no view of host topology
to localize against in any case. xen_mfn_to_node returns
NUMA_NO_NODE immediately when !xen_initial_domain(), without issuing
the hypercall.

Older or non-Edera hypervisors lack the hypercall. The first call
returns -ENOSYS, which latches a global "unsupported" flag; every
subsequent call returns NUMA_NO_NODE without entering the
hypervisor. -EPERM (XSM policy, a late hardware domain) latches the
same way, so no boot pays more than one refused attempt. Callers
seeing NUMA_NO_NODE fall back to NUMA-oblivious behaviour, making the
kernel-side change safe to ship without a lockstep hypervisor update.

Gated by CONFIG_XEN_BACKEND_NUMA_AFFINITY, which depends on
XEN_BACKEND, NUMA, ACPI_NUMA, and XEN_UNPOPULATED_ALLOC and defaults
to y. XEN_UNPOPULATED_ALLOC is a hard dependency rather than a soft
one because the placement work the rest of this series performs is
only meaningful when grant-map placeholders come from a per-node
pool: with the generic balloon allocator, page_to_nid() reflects only
where dom0's free RAM happened to be, and per-ring NUMA placement
would commit to wrong nodes confidently rather than silently no-op.
Disabling the option compiles the query out; the header supplies a
NUMA_NO_NODE stub so callers need no ifdefs.

Sampling policy is left to callers. The hypercall accepts a batch of
MFNs, though the consumers in this series query only a ring's first
frame.

Signed-off-by: Steven Noonan <steven@edera.dev>
---
drivers/xen/Kconfig | 25 ++++++++++
drivers/xen/grant-table.c | 86 ++++++++++++++++++++++++++++++++++
include/xen/interface/memory.h | 26 ++++++++++
include/xen/xen.h | 16 +++++++
4 files changed, 153 insertions(+)

diff --git a/drivers/xen/Kconfig b/drivers/xen/Kconfig
index f9a35ed266ec..147e6acb231b 100644
--- a/drivers/xen/Kconfig
+++ b/drivers/xen/Kconfig
@@ -96,6 +96,31 @@ config XEN_BACKEND
Support for backend device drivers that provide I/O services
to other virtual machines.

+config XEN_BACKEND_NUMA_AFFINITY
+ bool "NUMA affinity for Xen backend drivers"
+ depends on XEN_BACKEND && NUMA && ACPI_NUMA && XEN_UNPOPULATED_ALLOC
+ default y
+ help
+ Allow Xen backend drivers (netback, blkback, gntdev consumers)
+ to discover the host NUMA node that hosts a grant-mapped ring
+ page, and to place their service threads and IRQs on that node.
+
+ XEN_UNPOPULATED_ALLOC provides the per-node placeholder-page pool
+ the relocation logic in xenbus_map_ring_valloc() draws from.
+ Without it, placeholders come from the generic balloon allocator,
+ whose page_to_nid() reflects only where dom0's free RAM happened
+ to be -- not the host node of the foreign frame the placeholder
+ will end up backing. In that configuration the per-ring
+ placement decisions would be confidently wrong rather than just
+ absent, so the Kconfig hard-depends on XEN_UNPOPULATED_ALLOC
+ rather than silently degrading.
+
+ Requires hypervisor support for the XENMEM_get_mfn_pxms
+ hypercall. Without that support the feature is silently a
+ no-op, equivalent to NUMA-oblivious behaviour.
+
+ If unsure, say Y.
+
config XENFS
tristate "Xen filesystem"
select XEN_PRIVCMD
diff --git a/drivers/xen/grant-table.c b/drivers/xen/grant-table.c
index 478d2ad725ac..f8b86a8679de 100644
--- a/drivers/xen/grant-table.c
+++ b/drivers/xen/grant-table.c
@@ -67,6 +67,10 @@

#include <asm/sync_bitops.h>

+#ifdef CONFIG_XEN_BACKEND_NUMA_AFFINITY
+#include <acpi/acpi_numa.h>
+#endif
+
#define GNTTAB_LIST_END 0xffffffff

static grant_ref_t **gnttab_list;
@@ -881,6 +885,88 @@ int gnttab_pages_set_private(int nr_pages, struct page **pages)
}
EXPORT_SYMBOL_GPL(gnttab_pages_set_private);

+#ifdef CONFIG_XEN_BACKEND_NUMA_AFFINITY
+/*
+ * Tri-state cache for XENMEM_get_mfn_pxms availability. -ENOSYS
+ * (hypervisor without the hypercall) or -EPERM (Xen restricts the
+ * query to the hardware domain) from the first attempt latches
+ * "unsupported", short-circuiting future calls. Any positive ACK
+ * (including the legitimate XEN_INVALID_NUMA_ID answer for an MFN Xen
+ * does not know about) latches "supported".
+ *
+ * Lock-free: at most one transition each direction, and "unsupported"
+ * is a stable terminal state once entered. A racing reader might
+ * issue one redundant hypercall before observing the cached state,
+ * which is harmless.
+ */
+#define XEN_MFN_PXM_UNKNOWN 0
+#define XEN_MFN_PXM_SUPPORTED 1
+#define XEN_MFN_PXM_UNSUPPORTED 2
+
+static int xen_mfn_pxm_state = XEN_MFN_PXM_UNKNOWN;
+
+/*
+ * Resolve one foreign MFN to a Linux node id. Returns NUMA_NO_NODE
+ * for any failure mode: hypercall unsupported, MFN unknown to Xen,
+ * PXM not registered with the dom0 ACPI namespace.
+ *
+ * Three NUMA identifier namespaces are involved here. Xen returns
+ * host PXM (firmware-supplied). pxm_to_node() translates to a Linux
+ * dom0 node id. Callers then use the result against Linux helpers
+ * like cpumask_of_node() and kthread_create_on_node().
+ */
+int xen_mfn_to_node(unsigned long mfn)
+{
+ struct xen_get_mfn_pxms req;
+ xen_pfn_t mfn_arg = mfn;
+ uint32_t pxm = XEN_INVALID_NUMA_ID;
+ int rc;
+
+ /*
+ * Xen answers this query only for the hardware domain. A domU
+ * mapping a foreign ring (a driver domain, say) would just earn
+ * an -EPERM per connect; skip the doomed hypercall and stay
+ * node-oblivious, which is the only honest answer for a guest
+ * with no view of host topology anyway.
+ */
+ if (!xen_initial_domain())
+ return NUMA_NO_NODE;
+
+ if (READ_ONCE(xen_mfn_pxm_state) == XEN_MFN_PXM_UNSUPPORTED)
+ return NUMA_NO_NODE;
+
+ memset(&req, 0, sizeof(req));
+ set_xen_guest_handle(req.mfns, &mfn_arg);
+ set_xen_guest_handle(req.pxms, &pxm);
+ req.nr_mfns = 1;
+
+ rc = HYPERVISOR_memory_op(XENMEM_get_mfn_pxms, &req);
+ if (rc < 0) {
+ /*
+ * Both errnos are stable properties of this boot: -ENOSYS
+ * means the hypervisor lacks the hypercall (no CONFIG_NUMA
+ * or too old), -EPERM that it refuses us despite the
+ * initial-domain check above (XSM policy, or a late
+ * hardware domain). Latch either so we never retry.
+ */
+ if (rc == -ENOSYS || rc == -EPERM) {
+ WRITE_ONCE(xen_mfn_pxm_state, XEN_MFN_PXM_UNSUPPORTED);
+ pr_info("XENMEM_get_mfn_pxms unavailable (%d), NUMA affinity for grant mappings disabled\n",
+ rc);
+ }
+ return NUMA_NO_NODE;
+ }
+
+ WRITE_ONCE(xen_mfn_pxm_state, XEN_MFN_PXM_SUPPORTED);
+
+ if (pxm == XEN_INVALID_NUMA_ID)
+ return NUMA_NO_NODE;
+
+ return pxm_to_node(pxm);
+}
+EXPORT_SYMBOL_GPL(xen_mfn_to_node);
+#endif /* CONFIG_XEN_BACKEND_NUMA_AFFINITY */
+
/**
* gnttab_alloc_pages - alloc pages suitable for grant mapping into
* @nr_pages: number of pages to alloc
diff --git a/include/xen/interface/memory.h b/include/xen/interface/memory.h
index 1a371a825c55..1998a12a9465 100644
--- a/include/xen/interface/memory.h
+++ b/include/xen/interface/memory.h
@@ -325,4 +325,30 @@ struct xen_mem_acquire_resource {
};
DEFINE_GUEST_HANDLE_STRUCT(xen_mem_acquire_resource);

+/*
+ * XENMEM_get_mfn_pxms: resolve a batch of host MFNs to their firmware
+ * proximity-domain identifiers (host PXM on x86 ACPI).
+ *
+ * Returned values are in the host PXM namespace (the same value space
+ * dom0's own SRAT uses), not Xen's internal node id. Callers convert
+ * to a Linux node id with pxm_to_node(). Slots Xen has no node info
+ * for receive XEN_INVALID_NUMA_ID rather than failing the whole batch.
+ *
+ * Restricted to the hardware domain. On hypervisors that do not
+ * provide this op (older or non-Edera Xen, or builds without
+ * CONFIG_NUMA), the hypercall returns -ENOSYS; callers treat that as
+ * "feature unavailable" and fall back to NUMA-oblivious behaviour.
+ */
+#define XENMEM_get_mfn_pxms 40
+
+#define XEN_INVALID_NUMA_ID (~(uint32_t)0)
+
+struct xen_get_mfn_pxms {
+ GUEST_HANDLE(xen_pfn_t) mfns;
+ GUEST_HANDLE(uint32_t) pxms;
+ uint32_t nr_mfns;
+ uint32_t flags;
+};
+DEFINE_GUEST_HANDLE_STRUCT(xen_get_mfn_pxms);
+
#endif /* __XEN_PUBLIC_MEMORY_H__ */
diff --git a/include/xen/xen.h b/include/xen/xen.h
index f280c5dcf923..f7f3dd7a3abe 100644
--- a/include/xen/xen.h
+++ b/include/xen/xen.h
@@ -89,6 +89,22 @@ static inline void xen_free_unpopulated_pages(unsigned int nr_pages,
}
#endif

+/*
+ * Resolve a foreign frame's host MFN to the Linux node id of the memory
+ * backing it, via XENMEM_get_mfn_pxms. NUMA_NO_NODE for any failure
+ * mode (hypercall unsupported or refused, MFN unknown to Xen, PXM not
+ * in the ACPI namespace) -- callers degrade to node-oblivious behaviour.
+ */
+#include <linux/numa.h>
+#ifdef CONFIG_XEN_BACKEND_NUMA_AFFINITY
+int xen_mfn_to_node(unsigned long mfn);
+#else
+static inline int xen_mfn_to_node(unsigned long mfn)
+{
+ return NUMA_NO_NODE;
+}
+#endif
+
#if defined(CONFIG_XEN_DOM0) && defined(CONFIG_ACPI) && defined(CONFIG_X86)
bool __init xen_processor_present(uint32_t acpi_id);
#else
--
2.55.0

Loading
Loading