Skip to content

Commit e9090d2

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

12 files changed

Lines changed: 2276 additions & 0 deletions

config.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ patches:
9393
lower: '6.17'
9494
- patch: 0003-x86-amd_node-fix-null-pointer-dereference-if-amd_smn.patch
9595
lower: '6.17'
96+
- patches:
97+
- 0001-xen-events-add-_on_node-variants-of-the-lateeoi-bind.patch
98+
- 0002-xen-xenbus-expose-host-NUMA-node-of-a-mapped-ring.patch
99+
- 0003-xen-netback-place-per-queue-kthreads-and-IRQs-near-t.patch
100+
- 0004-xen-blkback-place-per-ring-kthread-and-IRQ-near-the-.patch
101+
- 0005-xen-make-xen_alloc_unpopulated_pages-NUMA-aware.patch
102+
- 0006-xen-xenbus-collapse-xenbus_ring_host_node-to-a-page_.patch
103+
- 0007-xen-xenbus-add-xenbus_setup_ring_node-for-per-node-r.patch
104+
- 0008-xen-netfront-place-per-queue-rings-on-per-queue-node.patch
105+
- 0009-xen-blkfront-place-per-ring-buffers-on-per-hctx-node.patch
106+
lower: '6.18'
96107
images:
97108
- target: kernelsrc
98109
name: kernel-src

configs/x86_64/host.config

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4912,6 +4912,7 @@ CONFIG_XEN_BALLOON=y
49124912
CONFIG_XEN_SCRUB_PAGES_DEFAULT=y
49134913
CONFIG_XEN_DEV_EVTCHN=m
49144914
CONFIG_XEN_BACKEND=y
4915+
CONFIG_XEN_BACKEND_NUMA_AFFINITY=y
49154916
CONFIG_XENFS=y
49164917
CONFIG_XEN_COMPAT_XENFS=y
49174918
CONFIG_XEN_SYS_HYPERVISOR=y
@@ -5991,3 +5992,11 @@ CONFIG_MHP_DEFAULT_ONLINE_TYPE_ONLINE_AUTO=y
59915992
CONFIG_MHP_MEMMAP_ON_MEMORY=y
59925993
CONFIG_XEN_BALLOON_MEMORY_HOTPLUG=y
59935994
CONFIG_XEN_MEMORY_HOTPLUG_LIMIT=512
5995+
5996+
# Enable device memory hotplug support (dependency for Xen PV driver backend
5997+
# NUMA binding)
5998+
CONFIG_ZONE_DEVICE=y
5999+
6000+
# NUMA balancing support
6001+
CONFIG_NUMA_BALANCING=y
6002+
CONFIG_NUMA_BALANCING_DEFAULT_ENABLED=y

configs/x86_64/zone.config

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,6 +1726,7 @@ CONFIG_XEN_MEMORY_HOTPLUG_LIMIT=512
17261726
CONFIG_XEN_SCRUB_PAGES_DEFAULT=y
17271727
CONFIG_XEN_DEV_EVTCHN=y
17281728
CONFIG_XEN_BACKEND=y
1729+
CONFIG_XEN_BACKEND_NUMA_AFFINITY=y
17291730
CONFIG_XENFS=y
17301731
CONFIG_XEN_COMPAT_XENFS=y
17311732
CONFIG_XEN_SYS_HYPERVISOR=y
@@ -2391,3 +2392,11 @@ CONFIG_DEBUG_INFO_NONE=n
23912392
CONFIG_DEBUG_INFO_BTF=y
23922393
CONFIG_DEBUG_INFO_BTF_MODULES=y
23932394
CONFIG_DEBUG_INFO_COMPRESSED_ZSTD=y
2395+
2396+
# Enable device memory hotplug support (dependency for Xen PV driver backend
2397+
# NUMA binding)
2398+
CONFIG_ZONE_DEVICE=y
2399+
2400+
# NUMA balancing support
2401+
CONFIG_NUMA_BALANCING=y
2402+
CONFIG_NUMA_BALANCING_DEFAULT_ENABLED=y
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
From bd2dbbb1f3a05a5d77d18548d41cdb25c0b81912 Mon Sep 17 00:00:00 2001
2+
From: Steven Noonan <steven@edera.dev>
3+
Date: Tue, 19 May 2026 23:00:20 -0700
4+
Subject: [PATCH 1/9] xen/events: add _on_node variants of the lateeoi bind
5+
helpers
6+
7+
xen_allocate_irq_dynamic() unconditionally calls
8+
irq_alloc_desc_from(0, -1), so every Xen evtchn IRQ descriptor is
9+
allocated with NUMA_NO_NODE. This means /proc/irq/N/node always
10+
reads -1 even when the caller (netback, blkback, netfront, blkfront)
11+
already knows the right node for the IRQ.
12+
13+
irqbalance treats node=-1 as "no NUMA preference" and distributes
14+
the IRQ across all CPUs for load balance, ignoring affinity_hint.
15+
With irqbalance running, the per-queue NUMA placement we install via
16+
irq_set_affinity_and_hint() gets overwritten almost immediately.
17+
18+
Add _on_node variants of the four bind helpers Xen front/back ends
19+
use:
20+
21+
bind_evtchn_to_irq_lateeoi_on_node(evtchn, node)
22+
bind_evtchn_to_irqhandler_lateeoi_on_node(..., node)
23+
bind_interdomain_evtchn_to_irq_lateeoi_on_node(..., node)
24+
bind_interdomain_evtchn_to_irqhandler_lateeoi_on_node(..., node)
25+
26+
Each passes the caller's node through to the internal chip helpers
27+
and on to a new xen_allocate_irq_dynamic_node(node). The existing
28+
public functions become thin wrappers passing NUMA_NO_NODE, so every
29+
caller that hasn't been updated keeps today's behaviour.
30+
31+
After this change, /proc/irq/N/node reflects the node the caller
32+
asked for, and irqbalance respects affinity_hint as a NUMA-local
33+
subset rather than treating the IRQ as floating.
34+
35+
Signed-off-by: Steven Noonan <steven@edera.dev>
36+
---
37+
drivers/xen/events/events_base.c | 86 ++++++++++++++++++++++++++------
38+
include/xen/events.h | 15 ++++++
39+
2 files changed, 85 insertions(+), 16 deletions(-)
40+
41+
diff --git a/drivers/xen/events/events_base.c b/drivers/xen/events/events_base.c
42+
index 9478fae014e5..6368ff561472 100644
43+
--- a/drivers/xen/events/events_base.c
44+
+++ b/drivers/xen/events/events_base.c
45+
@@ -28,6 +28,7 @@
46+
#include <linux/interrupt.h>
47+
#include <linux/irq.h>
48+
#include <linux/moduleparam.h>
49+
+#include <linux/numa.h>
50+
#include <linux/string.h>
51+
#include <linux/memblock.h>
52+
#include <linux/slab.h>
53+
@@ -729,9 +730,9 @@ static struct irq_info *xen_irq_init(unsigned int irq)
54+
return info;
55+
}
56+
57+
-static struct irq_info *xen_allocate_irq_dynamic(void)
58+
+static struct irq_info *xen_allocate_irq_dynamic_node(int node)
59+
{
60+
- int irq = irq_alloc_desc_from(0, -1);
61+
+ int irq = irq_alloc_desc_from(0, node);
62+
struct irq_info *info = NULL;
63+
64+
if (irq >= 0) {
65+
@@ -743,6 +744,11 @@ static struct irq_info *xen_allocate_irq_dynamic(void)
66+
return info;
67+
}
68+
69+
+static struct irq_info *xen_allocate_irq_dynamic(void)
70+
+{
71+
+ return xen_allocate_irq_dynamic_node(NUMA_NO_NODE);
72+
+}
73+
+
74+
static struct irq_info *xen_allocate_irq_gsi(unsigned int gsi)
75+
{
76+
int irq;
77+
@@ -1184,7 +1190,8 @@ int xen_pirq_from_irq(unsigned irq)
78+
EXPORT_SYMBOL_GPL(xen_pirq_from_irq);
79+
80+
static int bind_evtchn_to_irq_chip(evtchn_port_t evtchn, struct irq_chip *chip,
81+
- struct xenbus_device *dev, bool shared)
82+
+ struct xenbus_device *dev, bool shared,
83+
+ int node)
84+
{
85+
int ret = -ENOMEM;
86+
struct irq_info *info;
87+
@@ -1197,7 +1204,7 @@ static int bind_evtchn_to_irq_chip(evtchn_port_t evtchn, struct irq_chip *chip,
88+
info = evtchn_to_info(evtchn);
89+
90+
if (!info) {
91+
- info = xen_allocate_irq_dynamic();
92+
+ info = xen_allocate_irq_dynamic_node(node);
93+
if (!info)
94+
goto out;
95+
96+
@@ -1232,16 +1239,25 @@ static int bind_evtchn_to_irq_chip(evtchn_port_t evtchn, struct irq_chip *chip,
97+
98+
int bind_evtchn_to_irq(evtchn_port_t evtchn)
99+
{
100+
- return bind_evtchn_to_irq_chip(evtchn, &xen_dynamic_chip, NULL, false);
101+
+ return bind_evtchn_to_irq_chip(evtchn, &xen_dynamic_chip, NULL, false,
102+
+ NUMA_NO_NODE);
103+
}
104+
EXPORT_SYMBOL_GPL(bind_evtchn_to_irq);
105+
106+
int bind_evtchn_to_irq_lateeoi(evtchn_port_t evtchn)
107+
{
108+
- return bind_evtchn_to_irq_chip(evtchn, &xen_lateeoi_chip, NULL, false);
109+
+ return bind_evtchn_to_irq_chip(evtchn, &xen_lateeoi_chip, NULL, false,
110+
+ NUMA_NO_NODE);
111+
}
112+
EXPORT_SYMBOL_GPL(bind_evtchn_to_irq_lateeoi);
113+
114+
+int bind_evtchn_to_irq_lateeoi_on_node(evtchn_port_t evtchn, int node)
115+
+{
116+
+ return bind_evtchn_to_irq_chip(evtchn, &xen_lateeoi_chip, NULL, false,
117+
+ node);
118+
+}
119+
+EXPORT_SYMBOL_GPL(bind_evtchn_to_irq_lateeoi_on_node);
120+
+
121+
static int bind_ipi_to_irq(unsigned int ipi, unsigned int cpu)
122+
{
123+
struct evtchn_bind_ipi bind_ipi;
124+
@@ -1291,7 +1307,7 @@ static int bind_ipi_to_irq(unsigned int ipi, unsigned int cpu)
125+
static int bind_interdomain_evtchn_to_irq_chip(struct xenbus_device *dev,
126+
evtchn_port_t remote_port,
127+
struct irq_chip *chip,
128+
- bool shared)
129+
+ bool shared, int node)
130+
{
131+
struct evtchn_bind_interdomain bind_interdomain;
132+
int err;
133+
@@ -1303,17 +1319,28 @@ static int bind_interdomain_evtchn_to_irq_chip(struct xenbus_device *dev,
134+
&bind_interdomain);
135+
136+
return err ? : bind_evtchn_to_irq_chip(bind_interdomain.local_port,
137+
- chip, dev, shared);
138+
+ chip, dev, shared, node);
139+
}
140+
141+
int bind_interdomain_evtchn_to_irq_lateeoi(struct xenbus_device *dev,
142+
evtchn_port_t remote_port)
143+
{
144+
return bind_interdomain_evtchn_to_irq_chip(dev, remote_port,
145+
- &xen_lateeoi_chip, false);
146+
+ &xen_lateeoi_chip, false,
147+
+ NUMA_NO_NODE);
148+
}
149+
EXPORT_SYMBOL_GPL(bind_interdomain_evtchn_to_irq_lateeoi);
150+
151+
+int bind_interdomain_evtchn_to_irq_lateeoi_on_node(struct xenbus_device *dev,
152+
+ evtchn_port_t remote_port,
153+
+ int node)
154+
+{
155+
+ return bind_interdomain_evtchn_to_irq_chip(dev, remote_port,
156+
+ &xen_lateeoi_chip, false,
157+
+ node);
158+
+}
159+
+EXPORT_SYMBOL_GPL(bind_interdomain_evtchn_to_irq_lateeoi_on_node);
160+
+
161+
static int find_virq(unsigned int virq, unsigned int cpu, evtchn_port_t *evtchn,
162+
bool percpu)
163+
{
164+
@@ -1432,12 +1459,12 @@ static int bind_evtchn_to_irqhandler_chip(evtchn_port_t evtchn,
165+
irq_handler_t handler,
166+
unsigned long irqflags,
167+
const char *devname, void *dev_id,
168+
- struct irq_chip *chip)
169+
+ struct irq_chip *chip, int node)
170+
{
171+
int irq, retval;
172+
173+
irq = bind_evtchn_to_irq_chip(evtchn, chip, NULL,
174+
- irqflags & IRQF_SHARED);
175+
+ irqflags & IRQF_SHARED, node);
176+
if (irq < 0)
177+
return irq;
178+
retval = request_irq(irq, handler, irqflags, devname, dev_id);
179+
@@ -1456,7 +1483,8 @@ int bind_evtchn_to_irqhandler(evtchn_port_t evtchn,
180+
{
181+
return bind_evtchn_to_irqhandler_chip(evtchn, handler, irqflags,
182+
devname, dev_id,
183+
- &xen_dynamic_chip);
184+
+ &xen_dynamic_chip,
185+
+ NUMA_NO_NODE);
186+
}
187+
EXPORT_SYMBOL_GPL(bind_evtchn_to_irqhandler);
188+
189+
@@ -1467,19 +1495,34 @@ int bind_evtchn_to_irqhandler_lateeoi(evtchn_port_t evtchn,
190+
{
191+
return bind_evtchn_to_irqhandler_chip(evtchn, handler, irqflags,
192+
devname, dev_id,
193+
- &xen_lateeoi_chip);
194+
+ &xen_lateeoi_chip,
195+
+ NUMA_NO_NODE);
196+
}
197+
EXPORT_SYMBOL_GPL(bind_evtchn_to_irqhandler_lateeoi);
198+
199+
+int bind_evtchn_to_irqhandler_lateeoi_on_node(evtchn_port_t evtchn,
200+
+ irq_handler_t handler,
201+
+ unsigned long irqflags,
202+
+ const char *devname,
203+
+ void *dev_id, int node)
204+
+{
205+
+ return bind_evtchn_to_irqhandler_chip(evtchn, handler, irqflags,
206+
+ devname, dev_id,
207+
+ &xen_lateeoi_chip, node);
208+
+}
209+
+EXPORT_SYMBOL_GPL(bind_evtchn_to_irqhandler_lateeoi_on_node);
210+
+
211+
static int bind_interdomain_evtchn_to_irqhandler_chip(
212+
struct xenbus_device *dev, evtchn_port_t remote_port,
213+
irq_handler_t handler, unsigned long irqflags,
214+
- const char *devname, void *dev_id, struct irq_chip *chip)
215+
+ const char *devname, void *dev_id, struct irq_chip *chip,
216+
+ int node)
217+
{
218+
int irq, retval;
219+
220+
irq = bind_interdomain_evtchn_to_irq_chip(dev, remote_port, chip,
221+
- irqflags & IRQF_SHARED);
222+
+ irqflags & IRQF_SHARED,
223+
+ node);
224+
if (irq < 0)
225+
return irq;
226+
227+
@@ -1501,10 +1544,21 @@ int bind_interdomain_evtchn_to_irqhandler_lateeoi(struct xenbus_device *dev,
228+
{
229+
return bind_interdomain_evtchn_to_irqhandler_chip(dev,
230+
remote_port, handler, irqflags, devname,
231+
- dev_id, &xen_lateeoi_chip);
232+
+ dev_id, &xen_lateeoi_chip, NUMA_NO_NODE);
233+
}
234+
EXPORT_SYMBOL_GPL(bind_interdomain_evtchn_to_irqhandler_lateeoi);
235+
236+
+int bind_interdomain_evtchn_to_irqhandler_lateeoi_on_node(
237+
+ struct xenbus_device *dev, evtchn_port_t remote_port,
238+
+ irq_handler_t handler, unsigned long irqflags,
239+
+ const char *devname, void *dev_id, int node)
240+
+{
241+
+ return bind_interdomain_evtchn_to_irqhandler_chip(dev,
242+
+ remote_port, handler, irqflags, devname,
243+
+ dev_id, &xen_lateeoi_chip, node);
244+
+}
245+
+EXPORT_SYMBOL_GPL(bind_interdomain_evtchn_to_irqhandler_lateeoi_on_node);
246+
+
247+
int bind_virq_to_irqhandler(unsigned int virq, unsigned int cpu,
248+
irq_handler_t handler,
249+
unsigned long irqflags, const char *devname, void *dev_id)
250+
diff --git a/include/xen/events.h b/include/xen/events.h
251+
index de5da58a0205..1abc068557b4 100644
252+
--- a/include/xen/events.h
253+
+++ b/include/xen/events.h
254+
@@ -18,6 +18,7 @@ unsigned xen_evtchn_nr_channels(void);
255+
256+
int bind_evtchn_to_irq(evtchn_port_t evtchn);
257+
int bind_evtchn_to_irq_lateeoi(evtchn_port_t evtchn);
258+
+int bind_evtchn_to_irq_lateeoi_on_node(evtchn_port_t evtchn, int node);
259+
int bind_evtchn_to_irqhandler(evtchn_port_t evtchn,
260+
irq_handler_t handler,
261+
unsigned long irqflags, const char *devname,
262+
@@ -26,6 +27,10 @@ int bind_evtchn_to_irqhandler_lateeoi(evtchn_port_t evtchn,
263+
irq_handler_t handler,
264+
unsigned long irqflags, const char *devname,
265+
void *dev_id);
266+
+int bind_evtchn_to_irqhandler_lateeoi_on_node(evtchn_port_t evtchn,
267+
+ irq_handler_t handler,
268+
+ unsigned long irqflags, const char *devname,
269+
+ void *dev_id, int node);
270+
int bind_virq_to_irq(unsigned int virq, unsigned int cpu, bool percpu);
271+
int bind_virq_to_irqhandler(unsigned int virq, unsigned int cpu,
272+
irq_handler_t handler,
273+
@@ -39,12 +44,22 @@ int bind_ipi_to_irqhandler(enum ipi_vector ipi,
274+
void *dev_id);
275+
int bind_interdomain_evtchn_to_irq_lateeoi(struct xenbus_device *dev,
276+
evtchn_port_t remote_port);
277+
+int bind_interdomain_evtchn_to_irq_lateeoi_on_node(struct xenbus_device *dev,
278+
+ evtchn_port_t remote_port,
279+
+ int node);
280+
int bind_interdomain_evtchn_to_irqhandler_lateeoi(struct xenbus_device *dev,
281+
evtchn_port_t remote_port,
282+
irq_handler_t handler,
283+
unsigned long irqflags,
284+
const char *devname,
285+
void *dev_id);
286+
+int bind_interdomain_evtchn_to_irqhandler_lateeoi_on_node(
287+
+ struct xenbus_device *dev,
288+
+ evtchn_port_t remote_port,
289+
+ irq_handler_t handler,
290+
+ unsigned long irqflags,
291+
+ const char *devname,
292+
+ void *dev_id, int node);
293+
294+
/*
295+
* Common unbind function for all event sources. Takes IRQ to unbind from.
296+
--
297+
2.54.0
298+

0 commit comments

Comments
 (0)