linux-next: manual merge of the mm-nonmm-unstable tree with the mm tree

From: Mark Brown

Date: Wed Sep 02 2026 - 06:28:14 EST


Hi all,

Today's linux-next merge of the mm-nonmm-unstable tree got a conflict in:

mm/mempolicy.c

between commits:

61f04edb01700 ("mm/mempolicy: fix sleeping allocation in alloc_pages_bulk_weighted_interleave()")
5da38ed988084 ("mm/mempolicy: use SRCU for the weighted interleave state")

from the mm tree and commit:

540e583b66d64 ("mm/mempolicy: fix sleeping allocation in alloc_pages_bulk_weighted_interleave()")

from the mm-nonmm-unstable tree.

I fixed it up (see below) and can carry the fix as necessary. This
is now fixed as far as linux-next is concerned, but any non trivial
conflicts should be mentioned to your upstream maintainer when your tree
is submitted for merging. You may also want to consider cooperating
with the maintainer of the conflicting tree to minimise any particularly
complex conflicts.

diff --combined mm/mempolicy.c
index 2ad0a5f18280a,79053ece02cd4..0000000000000
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@@ -112,7 -112,6 +112,7 @@@
#include <linux/printk.h>
#include <linux/leafops.h>
#include <linux/gcd.h>
+#include <linux/srcu.h>

#include <asm/tlbflush.h>
#include <asm/tlb.h>
@@@ -158,7 -157,6 +158,7 @@@ static const int weightiness = 32
*/
struct weighted_interleave_state {
bool mode_auto;
+ struct rcu_head rcu;
u8 iw_table[];
};
static struct weighted_interleave_state __rcu *wi_state;
@@@ -170,24 -168,6 +170,24 @@@ static unsigned int *node_bw_table
*/
static DEFINE_MUTEX(wi_state_lock);

+/* Readers that sleep while walking iw_table hold this instead */
+DEFINE_STATIC_SRCU_FAST(wi_srcu);
+
+static void wi_state_free_rcu(struct rcu_head *head)
+{
+ struct weighted_interleave_state *state =
+ container_of(head, struct weighted_interleave_state, rcu);
+
+ kfree_rcu(state, rcu);
+}
+
+/* Retire through both flavors: sleeping readers use SRCU, the rest RCU */
+static void wi_state_retire(struct weighted_interleave_state *state)
+{
+ if (state)
+ call_srcu(&wi_srcu, &state->rcu, wi_state_free_rcu);
+}
+
static u8 get_il_weight(int node)
{
struct weighted_interleave_state *state;
@@@ -286,7 -266,10 +286,7 @@@ int mempolicy_set_node_perf(unsigned in
rcu_assign_pointer(wi_state, new_wi_state);

mutex_unlock(&wi_state_lock);
- if (old_wi_state) {
- synchronize_rcu();
- kfree(old_wi_state);
- }
+ wi_state_retire(old_wi_state);
out:
kfree(old_bw);
return 0;
@@@ -679,8 -662,6 +679,8 @@@ static void queue_folios_pmd(pmd_t *pmd
return;
}
folio = pmd_folio(pmdval);
+ if (folio_is_zone_device(folio))
+ return;
if (is_huge_zero_folio(folio)) {
walk->action = ACTION_CONTINUE;
return;
@@@ -2199,15 -2180,34 +2199,15 @@@ unsigned int mempolicy_slab_node(void
}
}

-static unsigned int read_once_policy_nodemask(struct mempolicy *pol,
- nodemask_t *mask)
-{
- /*
- * barrier stabilizes the nodemask locally so that it can be iterated
- * over safely without concern for changes. Allocators validate node
- * selection does not violate mems_allowed, so this is safe.
- */
- barrier();
- memcpy(mask, &pol->nodes, sizeof(nodemask_t));
- barrier();
- return nodes_weight(*mask);
-}
-
static unsigned int weighted_interleave_nid(struct mempolicy *pol, pgoff_t ilx)
{
struct weighted_interleave_state *state;
- nodemask_t nodemask;
- unsigned int target, nr_nodes;
+ unsigned int target, nnodes = 0;
u8 *table = NULL;
unsigned int weight_total = 0;
u8 weight;
int nid = 0;

- nr_nodes = read_once_policy_nodemask(pol, &nodemask);
- if (!nr_nodes)
- return numa_node_id();
-
rcu_read_lock();

state = rcu_dereference(wi_state);
@@@ -2215,40 -2215,22 +2215,40 @@@
if (state)
table = state->iw_table;

- /* calculate the total weight */
- for_each_node_mask(nid, nodemask)
+ /* calculate the total weight and the node count */
+ for_each_node_mask(nid, pol->nodes) {
weight_total += table ? table[nid] : 1;
+ nnodes++;
+ }
+
+ /* the mask is empty */
+ if (!weight_total) {
+ rcu_read_unlock();
+ return numa_node_id();
+ }

/* Calculate the node offset based on totals */
target = ilx % weight_total;
- nid = first_node(nodemask);
- while (target) {
+ nid = first_node(pol->nodes);
+
+ /*
+ * The target was calculated in a separate loop, and a concurrent
+ * rebind can change the total number of nodes. Clamp this loop to
+ * a single pass (nnodes) to keep the walk bounded by node count.
+ */
+ while (target && nnodes-- && nid < MAX_NUMNODES) {
/* detect system default usage */
weight = table ? table[nid] : 1;
if (target < weight)
break;
target -= weight;
- nid = next_node_in(nid, nodemask);
+ nid = next_node_in(nid, pol->nodes);
}
rcu_read_unlock();
+
+ /* the mask emptied under the walk */
+ if (nid >= MAX_NUMNODES)
+ return numa_node_id();
return nid;
}

@@@ -2259,21 -2241,18 +2259,21 @@@
*/
static unsigned int interleave_nid(struct mempolicy *pol, pgoff_t ilx)
{
- nodemask_t nodemask;
unsigned int target, nnodes;
int i;
int nid;

- nnodes = read_once_policy_nodemask(pol, &nodemask);
+ nnodes = nodes_weight(pol->nodes);
if (!nnodes)
return numa_node_id();
target = ilx % nnodes;
- nid = first_node(nodemask);
- for (i = 0; i < target; i++)
- nid = next_node(nid, nodemask);
+ nid = first_node(pol->nodes);
+ for (i = 0; i < target && nid < MAX_NUMNODES; i++)
+ nid = next_node_in(nid, pol->nodes);
+
+ /* the mask emptied under the walk */
+ if (nid >= MAX_NUMNODES)
+ return numa_node_id();
return nid;
}

@@@ -2613,7 -2592,6 +2613,7 @@@ static unsigned long alloc_pages_bulk_i
struct mempolicy *pol, unsigned long nr_pages,
struct page **page_array)
{
+ unsigned int cpuset_mems_cookie;
int nodes;
unsigned long nr_pages_per_node;
int delta;
@@@ -2621,16 -2599,7 +2621,16 @@@
unsigned long nr_allocated;
unsigned long total_allocated = 0;

- nodes = nodes_weight(pol->nodes);
+ /* count the nodes, retry if a rebind happened during the read */
+ do {
+ cpuset_mems_cookie = read_mems_allowed_begin();
+ nodes = nodes_weight(pol->nodes);
+ } while (read_mems_allowed_retry(cpuset_mems_cookie));
+
+ /* if the nodemask has become invalid, we cannot do anything */
+ if (!nodes)
+ return 0;
+
nr_pages_per_node = nr_pages / nodes;
delta = nr_pages - nodes * nr_pages_per_node;

@@@ -2665,10 -2634,10 +2665,10 @@@ static unsigned long alloc_pages_bulk_w
unsigned long nr_allocated = 0;
unsigned long rounds;
unsigned long node_pages, delta;
- u8 *weights, weight;
+ struct srcu_ctr __percpu *scp;
+ u8 *table, weight;
unsigned int weight_total = 0;
unsigned long rem_pages = nr_pages;
- nodemask_t nodes;
int nnodes, node;
int resume_node = MAX_NUMNODES - 1;
u8 resume_weight = 0;
@@@ -2678,10 -2647,10 +2678,10 @@@
if (!nr_pages)
return 0;

- /* read the nodes onto the stack, retry if done during rebind */
+ /* count the nodes, retry if a rebind happened during the read */
do {
cpuset_mems_cookie = read_mems_allowed_begin();
- nnodes = read_once_policy_nodemask(pol, &nodes);
+ nnodes = nodes_weight(pol->nodes);
} while (read_mems_allowed_retry(cpuset_mems_cookie));

/* if the nodemask has become invalid, we cannot do anything */
@@@ -2691,7 -2660,7 +2691,7 @@@
/* Continue allocating from most recent node and adjust the nr_pages */
node = me->il_prev;
weight = me->il_weight;
- if (weight && node_isset(node, nodes)) {
+ if (weight && node_isset(node, pol->nodes)) {
node_pages = min(rem_pages, weight);
nr_allocated = __alloc_pages_bulk(gfp, node, NULL, node_pages,
page_array);
@@@ -2709,18 -2678,25 +2709,18 @@@
me->il_weight = 0;
prev_node = node;

- /* create a local copy of node weights to operate on outside rcu */
- weights = kmalloc(nr_node_ids, gfp & GFP_RECLAIM_MASK);
- if (!weights)
- return total_allocated;
-
- rcu_read_lock();
- state = rcu_dereference(wi_state);
- if (state) {
- memcpy(weights, state->iw_table, nr_node_ids * sizeof(u8));
- rcu_read_unlock();
- } else {
- rcu_read_unlock();
- for (i = 0; i < nr_node_ids; i++)
- weights[i] = 1;
- }
+ /* The page allocator may sleep, pin the weight table with SRCU */
+ scp = srcu_read_lock_fast(&wi_srcu);
+ state = srcu_dereference(wi_state, &wi_srcu);
+ table = state ? state->iw_table : NULL;

/* calculate total, detect system default usage */
- for_each_node_mask(node, nodes)
- weight_total += weights[node];
+ for_each_node_mask(node, pol->nodes)
+ weight_total += table ? table[node] : 1;
+
+ /* the mask emptied since it was counted */
+ if (!weight_total)
+ goto out;

/*
* Calculate rounds/partial rounds to minimize __alloc_pages_bulk calls.
@@@ -2731,15 -2707,11 +2731,15 @@@
*/
rounds = rem_pages / weight_total;
delta = rem_pages % weight_total;
- resume_node = next_node_in(prev_node, nodes);
- resume_weight = weights[resume_node];
+ resume_node = next_node_in(prev_node, pol->nodes);
+ if (resume_node >= MAX_NUMNODES)
+ goto out;
+ resume_weight = table ? table[resume_node] : 1;
for (i = 0; i < nnodes; i++) {
- node = next_node_in(prev_node, nodes);
- weight = weights[node];
+ node = next_node_in(prev_node, pol->nodes);
+ if (node >= MAX_NUMNODES)
+ break;
+ weight = table ? table[node] : 1;
node_pages = weight * rounds;
/* If a delta exists, add this node's portion of the delta */
if (delta > weight) {
@@@ -2755,8 -2727,6 +2755,8 @@@
/* node_pages can be 0 if an allocation fails and rounds == 0 */
if (!node_pages)
break;
+ /* a rebind can invalidate the counts: never overrun page_array */
+ node_pages = min(node_pages, nr_pages - total_allocated);
nr_allocated = __alloc_pages_bulk(gfp, node, NULL, node_pages,
page_array);
page_array += nr_allocated;
@@@ -2767,8 -2737,7 +2767,8 @@@
}
me->il_prev = resume_node;
me->il_weight = resume_weight;
- kfree(weights);
+out:
+ srcu_read_unlock_fast(&wi_srcu, scp);
return total_allocated;
}

@@@ -3694,7 -3663,10 +3694,7 @@@ static ssize_t node_store(struct kobjec

rcu_assign_pointer(wi_state, new_wi_state);
mutex_unlock(&wi_state_lock);
- if (old_wi_state) {
- synchronize_rcu();
- kfree(old_wi_state);
- }
+ wi_state_retire(old_wi_state);
return count;
}

@@@ -3760,7 -3732,10 +3760,7 @@@ static ssize_t weighted_interleave_auto
update_wi_state:
rcu_assign_pointer(wi_state, new_wi_state);
mutex_unlock(&wi_state_lock);
- if (old_wi_state) {
- synchronize_rcu();
- kfree(old_wi_state);
- }
+ wi_state_retire(old_wi_state);
return count;
}

@@@ -3804,7 -3779,10 +3804,7 @@@ static void wi_state_free(void
rcu_assign_pointer(wi_state, NULL);
mutex_unlock(&wi_state_lock);

- if (old_wi_state) {
- synchronize_rcu();
- kfree(old_wi_state);
- }
+ wi_state_retire(old_wi_state);
}

static struct kobj_attribute wi_auto_attr = {

Attachment: signature.asc
Description: PGP signature