Skip to content

Commit 1322d19

Browse files
committed
implement NUMA binding for PV I/O drivers on PVH
Signed-off-by: Steven Noonan <steven@edera.dev>
1 parent c648fe2 commit 1322d19

9 files changed

Lines changed: 1581 additions & 0 deletions

config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ patches:
9191
lower: '6.17'
9292
- patch: 0003-x86-amd_node-fix-null-pointer-dereference-if-amd_smn.patch
9393
lower: '6.17'
94+
- patches:
95+
- 0001-xen-xenbus-expose-host-NUMA-node-of-a-mapped-ring.patch
96+
- 0002-xen-netback-place-per-queue-kthreads-and-IRQs-near-t.patch
97+
- 0003-xen-blkback-place-per-ring-kthread-and-IRQ-near-the-.patch
98+
- 0004-xen-make-xen_alloc_unpopulated_pages-NUMA-aware.patch
99+
- 0005-xen-xenbus-collapse-xenbus_ring_host_node-to-a-page_.patch
100+
- 0006-xen-xenbus-add-xenbus_setup_ring_node-for-per-node-r.patch
101+
- 0007-xen-netfront-place-per-queue-rings-on-per-queue-node.patch
102+
- 0008-xen-blkfront-place-per-ring-buffers-on-per-hctx-node.patch
103+
lower: '6.18'
94104
images:
95105
- target: kernelsrc
96106
name: kernel-src
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
From 8386520a291966d3febe01bfb529d9d91d574d80 Mon Sep 17 00:00:00 2001
2+
From: Steven Noonan <steven@uplinklabs.net>
3+
Date: Tue, 19 May 2026 18:35:49 -0700
4+
Subject: [PATCH 1/8] xen/xenbus: expose host NUMA node of a mapped ring
5+
6+
Xenbus backends in PVH dom0 today have no visibility into which host
7+
NUMA node the foreign frame backing a grant-mapped ring lives on.
8+
xen_alloc_unpopulated_pages() registers its pool with NUMA_NO_NODE,
9+
so page_to_nid() of any grant-mapped page reports NUMA_NO_NODE and
10+
backends have nothing to drive kthread or IRQ placement off of. The
11+
result is that every backend kthread for every queue piles onto one
12+
host node regardless of the guest's vNUMA layout.
13+
14+
Add xenbus_ring_host_node(dev, vaddr) returning the Linux node id of
15+
the host node hosting the first ring page, or NUMA_NO_NODE when the
16+
information is unavailable. The value is resolved once at map time
17+
using the new XENMEM_get_mfn_pxms hypercall and cached on the
18+
xenbus_map_node, so the public helper is a cheap list lookup.
19+
20+
Three NUMA identifier namespaces are involved in this code path: host
21+
PXM (firmware), Xen-internal nid (assigned in SRAT scan order), and
22+
dom0 Linux node id. The hypercall returns host PXM, matching what
23+
dom0's own SRAT already uses; pxm_to_node() converts to a Linux node
24+
id, which is what backends will feed to cpumask_of_node() and
25+
kthread_create_on_node(). Keeping the Xen-side ABI in PXM-space lets
26+
callers translate with one standard lookup instead of maintaining
27+
their own Xen-nid -> Linux-node table.
28+
29+
Older or non-Edera hypervisors lack the new hypercall. The first
30+
call there returns -ENOSYS, which latches a global "unsupported"
31+
flag; every subsequent xenbus_ring_host_node() returns NUMA_NO_NODE
32+
without issuing further hypercalls. Backend patches that key off the
33+
helper see NUMA_NO_NODE and silently fall back to today's
34+
NUMA-oblivious behaviour. This makes the kernel-side change safe to
35+
ship without a lockstep hypervisor update.
36+
37+
Gated by CONFIG_XEN_BACKEND_NUMA_AFFINITY (depends on XEN_BACKEND,
38+
NUMA, ACPI_NUMA; default y). Disabling the option compiles the
39+
query out and the public helper becomes a stub returning
40+
NUMA_NO_NODE.
41+
42+
PV dom0 is not a NUMA-affinity target: the PV map path explicitly
43+
sets host_node = NUMA_NO_NODE and the helper handles the
44+
pv.area->addr lookup path symmetrically with hvm.addr, returning
45+
NUMA_NO_NODE in both cases when no match is found.
46+
47+
The foreign MFN is sourced from dev_bus_addr in the gnttab map
48+
result. Xen sets that field unconditionally for host_map operations
49+
(see grant_table.c in the hypervisor), so it is reliable in PVH dom0
50+
without IOMMU isolation. Sampling only the first ring page's MFN is
51+
sufficient for the common case where a ring is contiguous on one
52+
host node; multi-node huge-page grants are a future concern.
53+
54+
Signed-off-by: Steven Noonan <steven@uplinklabs.net>
55+
---
56+
drivers/xen/Kconfig | 15 ++++
57+
drivers/xen/xenbus/xenbus_client.c | 111 +++++++++++++++++++++++++++++
58+
include/xen/interface/memory.h | 26 +++++++
59+
include/xen/xenbus.h | 13 ++++
60+
4 files changed, 165 insertions(+)
61+
62+
diff --git a/drivers/xen/Kconfig b/drivers/xen/Kconfig
63+
index f9a35ed266ec..ec1752a66b3e 100644
64+
--- a/drivers/xen/Kconfig
65+
+++ b/drivers/xen/Kconfig
66+
@@ -96,6 +96,21 @@ config XEN_BACKEND
67+
Support for backend device drivers that provide I/O services
68+
to other virtual machines.
69+
70+
+config XEN_BACKEND_NUMA_AFFINITY
71+
+ bool "NUMA affinity for Xen backend drivers"
72+
+ depends on XEN_BACKEND && NUMA && ACPI_NUMA
73+
+ default y
74+
+ help
75+
+ Allow Xen backend drivers (netback, blkback, gntdev consumers)
76+
+ to discover the host NUMA node that hosts a grant-mapped ring
77+
+ page, and to place their service threads and IRQs on that node.
78+
+
79+
+ Requires hypervisor support for the XENMEM_get_mfn_pxms
80+
+ hypercall. Without that support the feature is silently a
81+
+ no-op, equivalent to NUMA-oblivious behaviour.
82+
+
83+
+ If unsure, say Y.
84+
+
85+
config XENFS
86+
tristate "Xen filesystem"
87+
select XEN_PRIVCMD
88+
diff --git a/drivers/xen/xenbus/xenbus_client.c b/drivers/xen/xenbus/xenbus_client.c
89+
index 2dc874fb5506..8e0695ba39a3 100644
90+
--- a/drivers/xen/xenbus/xenbus_client.c
91+
+++ b/drivers/xen/xenbus/xenbus_client.c
92+
@@ -31,15 +31,18 @@
93+
*/
94+
95+
#include <linux/mm.h>
96+
+#include <linux/numa.h>
97+
#include <linux/slab.h>
98+
#include <linux/types.h>
99+
#include <linux/spinlock.h>
100+
#include <linux/vmalloc.h>
101+
#include <linux/export.h>
102+
+#include <asm/xen/hypercall.h>
103+
#include <asm/xen/hypervisor.h>
104+
#include <xen/page.h>
105+
#include <xen/interface/xen.h>
106+
#include <xen/interface/event_channel.h>
107+
+#include <xen/interface/memory.h>
108+
#include <xen/balloon.h>
109+
#include <xen/events.h>
110+
#include <xen/grant_table.h>
111+
@@ -47,6 +50,10 @@
112+
#include <xen/xen.h>
113+
#include <xen/features.h>
114+
115+
+#ifdef CONFIG_XEN_BACKEND_NUMA_AFFINITY
116+
+#include <acpi/acpi_numa.h>
117+
+#endif
118+
+
119+
#include "xenbus.h"
120+
121+
#define XENBUS_PAGES(_grants) (DIV_ROUND_UP(_grants, XEN_PFN_PER_PAGE))
122+
@@ -67,6 +74,7 @@ struct xenbus_map_node {
123+
};
124+
grant_handle_t handles[XENBUS_MAX_RING_GRANTS];
125+
unsigned int nr_handles;
126+
+ int host_node; /* Linux node id of foreign frame, or NUMA_NO_NODE */
127+
};
128+
129+
struct map_ring_valloc {
130+
@@ -85,6 +93,72 @@ struct map_ring_valloc {
131+
static DEFINE_SPINLOCK(xenbus_valloc_lock);
132+
static LIST_HEAD(xenbus_valloc_pages);
133+
134+
+#ifdef CONFIG_XEN_BACKEND_NUMA_AFFINITY
135+
+/*
136+
+ * Tri-state cache for XENMEM_get_mfn_pxms availability. -ENOSYS from
137+
+ * the first attempt latches "unsupported", short-circuiting future
138+
+ * calls. Any positive ACK (including the legitimate XEN_INVALID_NUMA_ID
139+
+ * answer for an MFN Xen does not know about) latches "supported".
140+
+ *
141+
+ * Lock-free: at most one transition each direction, and "unsupported"
142+
+ * is a stable terminal state once entered. A racing reader might
143+
+ * issue one redundant hypercall before observing the cached state,
144+
+ * which is harmless.
145+
+ */
146+
+#define XEN_MFN_PXM_UNKNOWN 0
147+
+#define XEN_MFN_PXM_SUPPORTED 1
148+
+#define XEN_MFN_PXM_UNSUPPORTED 2
149+
+
150+
+static int xen_mfn_pxm_state = XEN_MFN_PXM_UNKNOWN;
151+
+
152+
+/*
153+
+ * Resolve one foreign MFN to a Linux node id. Returns NUMA_NO_NODE
154+
+ * for any failure mode: hypercall unsupported, MFN unknown to Xen,
155+
+ * PXM not registered with the dom0 ACPI namespace.
156+
+ *
157+
+ * Three NUMA identifier namespaces are involved here. Xen returns
158+
+ * host PXM (firmware-supplied). pxm_to_node() translates to a Linux
159+
+ * dom0 node id. Callers then use the result against Linux helpers
160+
+ * like cpumask_of_node() and kthread_create_on_node().
161+
+ */
162+
+static int xenbus_query_mfn_node(unsigned long mfn)
163+
+{
164+
+ struct xen_get_mfn_pxms req;
165+
+ xen_pfn_t mfn_arg = mfn;
166+
+ uint32_t pxm = XEN_INVALID_NUMA_ID;
167+
+ int rc;
168+
+
169+
+ if (READ_ONCE(xen_mfn_pxm_state) == XEN_MFN_PXM_UNSUPPORTED)
170+
+ return NUMA_NO_NODE;
171+
+
172+
+ memset(&req, 0, sizeof(req));
173+
+ set_xen_guest_handle(req.mfns, &mfn_arg);
174+
+ set_xen_guest_handle(req.pxms, &pxm);
175+
+ req.nr_mfns = 1;
176+
+
177+
+ rc = HYPERVISOR_memory_op(XENMEM_get_mfn_pxms, &req);
178+
+ if (rc < 0) {
179+
+ if (rc == -ENOSYS) {
180+
+ WRITE_ONCE(xen_mfn_pxm_state, XEN_MFN_PXM_UNSUPPORTED);
181+
+ pr_info("xenbus: hypervisor lacks XENMEM_get_mfn_pxms, backend NUMA affinity disabled\n");
182+
+ }
183+
+ return NUMA_NO_NODE;
184+
+ }
185+
+
186+
+ WRITE_ONCE(xen_mfn_pxm_state, XEN_MFN_PXM_SUPPORTED);
187+
+
188+
+ if (pxm == XEN_INVALID_NUMA_ID)
189+
+ return NUMA_NO_NODE;
190+
+
191+
+ return pxm_to_node(pxm);
192+
+}
193+
+#else
194+
+static int xenbus_query_mfn_node(unsigned long mfn)
195+
+{
196+
+ return NUMA_NO_NODE;
197+
+}
198+
+#endif /* CONFIG_XEN_BACKEND_NUMA_AFFINITY */
199+
+
200+
struct xenbus_ring_ops {
201+
int (*map)(struct xenbus_device *dev, struct map_ring_valloc *info,
202+
grant_ref_t *gnt_refs, unsigned int nr_grefs,
203+
@@ -678,6 +752,8 @@ static int xenbus_map_ring_hvm(struct xenbus_device *dev,
204+
bool leaked = false;
205+
unsigned int nr_pages = XENBUS_PAGES(nr_grefs);
206+
207+
+ node->host_node = NUMA_NO_NODE;
208+
+
209+
err = xen_alloc_unpopulated_pages(nr_pages, node->hvm.pages);
210+
if (err)
211+
goto out_err;
212+
@@ -693,6 +769,17 @@ static int xenbus_map_ring_hvm(struct xenbus_device *dev,
213+
if (err)
214+
goto out_free_ballooned_pages;
215+
216+
+ /*
217+
+ * Xen unconditionally fills dev_bus_addr with the foreign frame's
218+
+ * machine address on a successful host_map (see grant_table.c in
219+
+ * the hypervisor). Pick up the first ring page's MFN and resolve
220+
+ * it now while we still have the map info; the result is cached on
221+
+ * the xenbus_map_node so backends can look it up cheaply later.
222+
+ */
223+
+ if (nr_grefs > 0)
224+
+ node->host_node = xenbus_query_mfn_node(
225+
+ PFN_DOWN(info->map[0].dev_bus_addr));
226+
+
227+
addr = vmap(node->hvm.pages, nr_pages, VM_MAP | VM_IOREMAP,
228+
PAGE_KERNEL);
229+
if (!addr) {
230+
@@ -743,6 +830,27 @@ int xenbus_unmap_ring_vfree(struct xenbus_device *dev, void *vaddr)
231+
}
232+
EXPORT_SYMBOL_GPL(xenbus_unmap_ring_vfree);
233+
234+
+int xenbus_ring_host_node(struct xenbus_device *dev, void *vaddr)
235+
+{
236+
+ struct xenbus_map_node *node;
237+
+ int node_id = NUMA_NO_NODE;
238+
+
239+
+ spin_lock(&xenbus_valloc_lock);
240+
+ list_for_each_entry(node, &xenbus_valloc_pages, next) {
241+
+ void *addr = xen_pv_domain() ? node->pv.area->addr
242+
+ : node->hvm.addr;
243+
+
244+
+ if (addr == vaddr) {
245+
+ node_id = node->host_node;
246+
+ break;
247+
+ }
248+
+ }
249+
+ spin_unlock(&xenbus_valloc_lock);
250+
+
251+
+ return node_id;
252+
+}
253+
+EXPORT_SYMBOL_GPL(xenbus_ring_host_node);
254+
+
255+
#ifdef CONFIG_XEN_PV
256+
static int map_ring_apply(pte_t *pte, unsigned long addr, void *data)
257+
{
258+
@@ -763,6 +871,9 @@ static int xenbus_map_ring_pv(struct xenbus_device *dev,
259+
bool leaked = false;
260+
int err = -ENOMEM;
261+
262+
+ /* PV dom0 is not a NUMA-affinity target; leave the value unset. */
263+
+ node->host_node = NUMA_NO_NODE;
264+
+
265+
area = get_vm_area(XEN_PAGE_SIZE * nr_grefs, VM_IOREMAP);
266+
if (!area)
267+
return -ENOMEM;
268+
diff --git a/include/xen/interface/memory.h b/include/xen/interface/memory.h
269+
index 1a371a825c55..1998a12a9465 100644
270+
--- a/include/xen/interface/memory.h
271+
+++ b/include/xen/interface/memory.h
272+
@@ -325,4 +325,30 @@ struct xen_mem_acquire_resource {
273+
};
274+
DEFINE_GUEST_HANDLE_STRUCT(xen_mem_acquire_resource);
275+
276+
+/*
277+
+ * XENMEM_get_mfn_pxms: resolve a batch of host MFNs to their firmware
278+
+ * proximity-domain identifiers (host PXM on x86 ACPI).
279+
+ *
280+
+ * Returned values are in the host PXM namespace (the same value space
281+
+ * dom0's own SRAT uses), not Xen's internal node id. Callers convert
282+
+ * to a Linux node id with pxm_to_node(). Slots Xen has no node info
283+
+ * for receive XEN_INVALID_NUMA_ID rather than failing the whole batch.
284+
+ *
285+
+ * Restricted to the hardware domain. On hypervisors that do not
286+
+ * provide this op (older or non-Edera Xen, or builds without
287+
+ * CONFIG_NUMA), the hypercall returns -ENOSYS; callers treat that as
288+
+ * "feature unavailable" and fall back to NUMA-oblivious behaviour.
289+
+ */
290+
+#define XENMEM_get_mfn_pxms 40
291+
+
292+
+#define XEN_INVALID_NUMA_ID (~(uint32_t)0)
293+
+
294+
+struct xen_get_mfn_pxms {
295+
+ GUEST_HANDLE(xen_pfn_t) mfns;
296+
+ GUEST_HANDLE(uint32_t) pxms;
297+
+ uint32_t nr_mfns;
298+
+ uint32_t flags;
299+
+};
300+
+DEFINE_GUEST_HANDLE_STRUCT(xen_get_mfn_pxms);
301+
+
302+
#endif /* __XEN_PUBLIC_MEMORY_H__ */
303+
diff --git a/include/xen/xenbus.h b/include/xen/xenbus.h
304+
index 7dab04cf4a36..18b902bf79ef 100644
305+
--- a/include/xen/xenbus.h
306+
+++ b/include/xen/xenbus.h
307+
@@ -225,6 +225,19 @@ int xenbus_map_ring_valloc(struct xenbus_device *dev, grant_ref_t *gnt_refs,
308+
309+
int xenbus_unmap_ring_vfree(struct xenbus_device *dev, void *vaddr);
310+
311+
+/*
312+
+ * Return the host NUMA node (Linux node id) of the foreign frame
313+
+ * backing the first page of a mapping previously established by
314+
+ * xenbus_map_ring_valloc(). Returns NUMA_NO_NODE if the hypervisor
315+
+ * cannot provide the information, the mapping is not found, or the
316+
+ * kernel was built without CONFIG_XEN_BACKEND_NUMA_AFFINITY.
317+
+ *
318+
+ * Intended for backends placing their service threads and IRQs on
319+
+ * the node hosting the ring. The value is resolved at map time and
320+
+ * cached on the mapping, so this call is a cheap lookup.
321+
+ */
322+
+int xenbus_ring_host_node(struct xenbus_device *dev, void *vaddr);
323+
+
324+
int xenbus_alloc_evtchn(struct xenbus_device *dev, evtchn_port_t *port);
325+
int xenbus_free_evtchn(struct xenbus_device *dev, evtchn_port_t port);
326+
327+
--
328+
2.54.0
329+

0 commit comments

Comments
 (0)