From nobody Thu Apr 2 15:35:59 2026 Received: from out-174.mta1.migadu.com (out-174.mta1.migadu.com [95.215.58.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4ECBA1C5486 for ; Fri, 27 Mar 2026 12:44:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774615488; cv=none; b=ZCMrRrxI/FNzPqxYZXlu0oQ8XHyJ+BrB22e8jn4pVYxBoO3yjcYfRdIn5ASDeOW4EOIEKrv5VZC70GPcDknuAspVZ9+SP7sA6+VD/TVMGpW2CjWGCqa0YfrmOQtYnRM1VfKcyoNjgXxpyivs+7O0AgC1kC5NTq3zoi6kvcpzt78= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774615488; c=relaxed/simple; bh=xowaL81NGKRtgX6RTWK6OKDnVsOHauBgdYm+8wmDBFM=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=ozkrUDqV4eCKkpFkW2eqP1fP5zzhIggaW3crVm8KkuLKucyyJ66Lrk+GEOzVeJzvlSkZdR9raRmsvgnegynGnroyfTtSEhowLW9JxwE3GO8v6K829/DlOMXN4lXw62N4wbLT4DAESfpMBJGz8N3d5WAmTsKowggy5TiWbUzzNOw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=Kclo86X3; arc=none smtp.client-ip=95.215.58.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="Kclo86X3" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1774615483; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=KbK7ntUxSzrFQGj/27aTab0fONy8ZoPaESF8ksut9hU=; b=Kclo86X3wPXq0hyPYxYklKXZMxUuwSaF1OL1vIZqmGsI5pKDM4aLkX0oUC0noIhMgE/kwR gMs9v4lQILgCEDUxRMnWZghAtynZPpEVyiEEHT05DSihJOipWiJn6alDMisXQcl+142F4s pCwNA8SKVWSvut/OywWAEYdqU4FtlR4= From: Hao Li To: david@kernel.org, osalvador@suse.de, akpm@linux-foundation.org Cc: vbabka@suse.cz, harry.yoo@oracle.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, linux-cxl@vger.kernel.org, Hao Li Subject: [PATCH] mm/memory_hotplug: maintain N_NORMAL_MEMORY during hotplug Date: Fri, 27 Mar 2026 20:42:47 +0800 Message-ID: <20260327124412.469833-1-hao.li@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" N_NORMAL_MEMORY is initialized from zone population at boot, but memory hotplug currently only updates N_MEMORY. As a result, a node that gains normal memory via hotplug can remain invisible to users iterating over N_NORMAL_MEMORY, while a node that loses its last normal memory can stay incorrectly marked as such. Restore N_NORMAL_MEMORY maintenance directly in online_pages() and offline_pages(). Set the bit when a node that currently lacks normal memory onlines pages into a zone <=3D ZONE_NORMAL, and clear it when offlining removes the last present pages from zones <=3D ZONE_NORMAL. This restores the intended semantics without bringing back the old status_change_nid_normal notifier plumbing which was removed in 8d2882a8edb8. Current users that benefit include list_lru, zswap, nfsd filecache, hugetlb_cgroup, and has_normal_memory sysfs reporting. Signed-off-by: Hao Li --- This patch also prepares for a subsequent SLUB change that makes can_free_to_pcs() rely on N_NORMAL_MEMORY to decide whether an object can be freed to the sheaf. --- mm/memory_hotplug.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c index bc805029da51..5498744aa1f1 100644 --- a/mm/memory_hotplug.c +++ b/mm/memory_hotplug.c @@ -1155,6 +1155,7 @@ int online_pages(unsigned long pfn, unsigned long nr_= pages, int need_zonelists_rebuild =3D 0; unsigned long flags; int ret; + bool need_set_normal_memory =3D false; =20 /* * {on,off}lining is constrained to full memory sections (or more @@ -1180,6 +1181,9 @@ int online_pages(unsigned long pfn, unsigned long nr_= pages, if (ret) goto failed_addition; } + /* Adding normal memory to the node for the first time */ + if (!node_state(nid, N_NORMAL_MEMORY) && zone_idx(zone) <=3D ZONE_NORMAL) + need_set_normal_memory =3D true; =20 ret =3D memory_notify(MEM_GOING_ONLINE, &mem_arg); ret =3D notifier_to_errno(ret); @@ -1209,6 +1213,8 @@ int online_pages(unsigned long pfn, unsigned long nr_= pages, =20 if (node_arg.nid >=3D 0) node_set_state(nid, N_MEMORY); + if (need_set_normal_memory) + node_set_state(nid, N_NORMAL_MEMORY); if (need_zonelists_rebuild) build_all_zonelists(NULL); =20 @@ -1908,6 +1914,9 @@ int offline_pages(unsigned long start_pfn, unsigned l= ong nr_pages, unsigned long flags; char *reason; int ret; + bool need_clear_normal_memory =3D false; + unsigned long node_normal_pages =3D 0; + enum zone_type zt; =20 /* * {on,off}lining is constrained to full memory sections (or more @@ -1977,6 +1986,13 @@ int offline_pages(unsigned long start_pfn, unsigned = long nr_pages, goto failed_removal_isolated; } } + /* + * Check whether this operation removes the node's last normal memory. + */ + for (zt =3D 0; zt <=3D ZONE_NORMAL; zt++) + node_normal_pages +=3D pgdat->node_zones[zt].present_pages; + if (nr_pages >=3D node_normal_pages && zone_idx(zone) <=3D ZONE_NORMAL) + need_clear_normal_memory =3D true; =20 ret =3D memory_notify(MEM_GOING_OFFLINE, &mem_arg); ret =3D notifier_to_errno(ret); @@ -2055,6 +2071,12 @@ int offline_pages(unsigned long start_pfn, unsigned = long nr_pages, /* reinitialise watermarks and update pcp limits */ init_per_zone_wmark_min(); =20 + /* + * Clear N_NORMAL_MEMORY first to avoid the transient state + * "!N_MEMORY && N_NORMAL_MEMORY". + */ + if (need_clear_normal_memory) + node_clear_state(node, N_NORMAL_MEMORY); /* * Make sure to mark the node as memory-less before rebuilding the zone * list. Otherwise this node would still appear in the fallback lists. --=20 2.50.1