[PATCH] mm/mempolicy: use one node set for interleave weights

From: Gregory Price (Meta)

Date: Fri Oct 30 2026 - 12:12:20 EST


From: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>

From: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>

Switching weighted interleave back to automatic mode while memory is
being onlined can cause a kernel divide error on x86, and a panic if
panic_on_oops is set.

reduce_interleave_weights() scans N_MEMORY three times, but hotplug
can cause N_MEMORY to change at runtime - and torn reads on that
nodemask can cause a total sum of zero bandwidth. A node that
was hot-unplugged and then hotplugged back can then cause this
loop to generate a div/0.

Record the nodes visited while summing their bandwidth, then use
that same set when scaling and reducing the weights. This gives
us a consistent state even if N_MEMORY changes.

Fixes: e341f9c3c841 ("mm/mempolicy: Weighted Interleave Auto-tuning")
Cc: <stable@xxxxxxxxxxxxxxx>
Signed-off-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Gregory Price (Meta) <gourry@xxxxxxxxxx>
---
mm/mempolicy.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)

[gregory: recieved off-list, separately validated and tested)

diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 6f92621e9135..26a1a21be65d 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -207,15 +207,22 @@ static u8 get_il_weight(int node)
*/
static void reduce_interleave_weights(unsigned int *bw, u8 *new_iw)
{
+ nodemask_t nodes = NODE_MASK_NONE;
u64 sum_bw = 0;
unsigned int cast_sum_bw, scaling_factor = 1, iw_gcd = 0;
int nid;

- for_each_node_state(nid, N_MEMORY)
+ /*
+ * N_MEMORY can change while wi_state_lock is held. Remember the
+ * nodes contributing to sum_bw so all passes use the same set.
+ */
+ for_each_node_state(nid, N_MEMORY) {
+ node_set(nid, nodes);
sum_bw += bw[nid];
+ }

/* Scale bandwidths to whole numbers in the range [1, weightiness] */
- for_each_node_state(nid, N_MEMORY) {
+ for_each_node_mask(nid, nodes) {
/*
* Try not to perform 64-bit division.
* If sum_bw < scaling_factor, then sum_bw < U32_MAX.
@@ -234,7 +241,7 @@ static void reduce_interleave_weights(unsigned int *bw, u8 *new_iw)
}

/* 1:2 is strictly better than 16:32. Reduce by the weights' GCD. */
- for_each_node_state(nid, N_MEMORY)
+ for_each_node_mask(nid, nodes)
new_iw[nid] /= iw_gcd;
}

--
2.56.0