mm/memory-tiers: unregister empty tiers outside memory_tier_lock

When offlining a node's last memory empties its tier,
clear_node_memory_tier() calls device_unregister() while holding
memory_tier_lock. A concurrent sysfs read can hold a kernfs active
reference and block in nodelist_show() on that mutex. device_unregister()
then waits in kernfs_drain() for the read to finish and release its active
reference, while the reader waits for the offlining task to release the
mutex.

Return the node's former tier from clear_node_memory_tier(), making the
helper more similar to set_node_memory_tier(), which also returns a
memory_tier pointer. Let the caller unlink an empty tier under
memory_tier_lock before rebuilding demotion targets, then unregister its
device after dropping the lock.

Fixes: 9832fb8783 ("mm/demotion: expose memory tier details via sysfs")
Cc: stable@vger.kernel.org
Reported-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Link: https://patch.msgid.link/20261031-fixes_core-v3-46-d6437171ecc3@kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
This commit is contained in:
David Hildenbrand (Arm) 2026-10-31 10:40:15 +01:00
parent e8abc61d8a
commit 500624f28e

View File

@ -572,21 +572,14 @@ static struct memory_tier *set_node_memory_tier(int node)
return memtier;
}
static void destroy_memory_tier(struct memory_tier *memtier)
static struct memory_tier *clear_node_memory_tier(int node)
{
list_del(&memtier->list);
device_unregister(&memtier->dev);
}
static bool clear_node_memory_tier(int node)
{
bool cleared = false;
pg_data_t *pgdat;
struct memory_tier *memtier;
pgdat = NODE_DATA(node);
if (!pgdat)
return false;
return NULL;
/*
* Make sure that anybody looking at NODE_DATA who finds
@ -604,14 +597,10 @@ static bool clear_node_memory_tier(int node)
synchronize_rcu();
memtype = node_memory_types[node].memtype;
node_clear(node, memtype->nodes);
if (nodes_empty(memtype->nodes)) {
if (nodes_empty(memtype->nodes))
list_del_init(&memtype->tier_sibling);
if (list_empty(&memtier->memory_types))
destroy_memory_tier(memtier);
}
cleared = true;
}
return cleared;
return memtier;
}
static void release_memtype(struct kref *kref)
@ -884,13 +873,21 @@ static int __meminit memtier_hotplug_callback(struct notifier_block *self,
{
struct memory_tier *memtier;
struct node_notify *nn = _arg;
bool empty = false;
switch (action) {
case NODE_REMOVED_LAST_MEMORY:
mutex_lock(&memory_tier_lock);
if (clear_node_memory_tier(nn->nid))
memtier = clear_node_memory_tier(nn->nid);
if (memtier) {
empty = list_empty(&memtier->memory_types);
if (empty)
list_del(&memtier->list);
establish_demotion_targets();
}
mutex_unlock(&memory_tier_lock);
if (empty)
device_unregister(&memtier->dev);
break;
case NODE_ADDED_FIRST_MEMORY:
mutex_lock(&memory_tier_lock);