* [PATCH v10 0/8] mm: optimize zone-device memmap initialization
@ 2026-08-10 12:20 Li Zhe
2026-08-10 12:20 ` [PATCH v10 1/8] mm: fix stale ZONE_DEVICE refcount comment Li Zhe
` (7 more replies)
0 siblings, 8 replies; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
memmap_init_zone_device() can take a noticeable amount of time when large
pmem namespaces are bound or rebound, because it initializes nearly
identical struct page descriptors one PFN at a time. This series reduces
that ZONE_DEVICE memmap initialization overhead by reusing prepared
struct page templates and, on x86, using memcpy_nontemporal() for the
template copy path.
The main target is large fsdax/devdax pmem configurations, where the
cost of initializing the memmap shows up directly in nd_pmem/dax_pmem
bind and rebind latency. This matters because the cost is paid in the
synchronous probe/bind path for large DAX/PMEM ZONE_DEVICE mappings.
Userspace workflows such as provisioning or reconfiguring
nd_pmem/dax_pmem namespaces, bringing hot-added PMEM-backed capacity
online, and recovering or rebinding a device after driver or device
changes all wait for this initialization to finish. Reducing this cost
will yield benefits as lower user-visible provisioning, hot-add,
recovery, and rebind latency for large DAX/PMEM devices.
Patches 1-3 are preparatory cleanups and helper extraction. Patches 4-5
add the template-copy path for head pages and compound tails. Patch 6
introduces memcpy_nontemporal(). Patch 7 switches the ZONE_DEVICE
template-copy path over to memcpy_nontemporal(). Patch 8 extends the x86
fixed-size memcpy_flushcache() inline cases used by the x86
memcpy_nontemporal() backend for struct page sized copies.
Architectures without a specialized memcpy_nontemporal() backend fall
back to memcpy(), so the generic template-copy optimization remains
available without arch-specific support. On x86, memcpy_nontemporal()
maps to the existing memcpy_flushcache() backend and can use the
fixed-size MOVNTI paths added by this series for struct page sized
copies.
memcpy_nontemporal() is only a copy primitive. It does not imply a drain
or a publication barrier. Callers that use it before a producer-consumer
or device-visible handoff must provide the required ordering. The
ZONE_DEVICE template-copy path uses it only while initializing struct
page metadata, so the copy primitive itself does not grow a separate
drain contract.
The numbers below measure the time spent in memmap_init_zone_device()
during driver bind/rebind. They are not measurements of the full
nd_pmem or dax_pmem bind/rebind operation.
Tested in a VM with a 100 GB fsdax namespace device configured with
map=dev and a 100 GB devdax namespace (align=2097152) on Intel Ice Lake
server.
Test procedure:
Rebind the nd_pmem and dax_pmem drivers 30 times and collect the memmap
initialization time from the pr_debug() output of
memmap_init_zone_device().
Base(v7.2-rc1):
Average of nd_pmem rebinds: 244.28 ms
Average of dax_pmem rebinds: 273.31 ms
With this series applied:
Average of nd_pmem rebinds: 96.79 ms
Average of dax_pmem rebinds: 119.04 ms
This reduces the average memmap initialization time measured during
rebind by about 60.4% for nd_pmem and 56.4% for dax_pmem.
As an additional x86_64 data point, I also ran a smaller set of
measurements on the same physical host with a 100 GB PMEM region created
via the memmap= kernel command line, configured as fsdax and devdax
namespaces with map=dev and 2 MiB alignment.
For brevity, the individual patches keep only the VM results rather than
including a second set of physical-host measurements throughout the
series. The physical-host numbers below are included only as
supplemental evidence that the same optimization also provides a similar
benefit on a non-virtualized system.
Test procedure:
Reconfigure the namespace mode, rebind the nd_pmem or dax_pmem driver
once, and collect the memmap initialization time from the pr_debug()
output of memmap_init_zone_device().
Base (v7.2-rc1):
nd_pmem / fsdax: 179 ms
dax_pmem / devdax: 264 ms
With this series applied:
nd_pmem / fsdax: 82 ms
dax_pmem / devdax: 113 ms
This reduces the measured memmap initialization time during rebind by
about 54.2% for nd_pmem and 57.2% for dax_pmem on that setup, which is
broadly consistent with the VM results above.
As another supplemental data point, I measured the test_hmm.ko module on
the same physical x86_64 host, using the test_hmm.ko setup from the
previous discussion that times ten 64 GB
memremap_pages()/memunmap_pages() iterations during module insertion[1].
By default, module insertion initializes two DEVICE_PRIVATE dmirror
devices, so two avg memremap values are reported; each value is the
average for one 64 GB chunk.
This is not the primary target workload of the series, but it exercises
the same large ZONE_DEVICE memmap initialization path and shows the same
direction of improvement.
Base (v7.2-rc1):
avg memremap reported during module insertion: 116689362 ns, 116539263 ns
With this series applied:
avg memremap reported during module insertion: 54607108 ns, 54458236 ns
This corresponds to about a 53.2% reduction based on the mean of the
reported values, which is again consistent with the pmem bind/rebind
results above.
I also tested the generic template-copy part on an arm64 QEMU virt VM
with 64 KB pages and a 100 GB ACPI NVDIMM sparse backend. This setup
does not use the x86 MOVNTI fast paths, so it exercises the
architecture-independent part of the optimization.
For devdax, 2 MiB alignment is rejected in this 64 KB page setup, so the
devdax namespace was tested with the supported default 512 MiB
alignment.
Base (v7.2-rc1):
Average of rebinds for nd_pmem driver: 25.60 ms
Average of rebinds for dax_pmem driver: 25.60 ms
With this series applied:
Average of rebinds for nd_pmem driver: 11.07 ms
Average of rebinds for dax_pmem driver: 13.20 ms
This reduces the average memmap initialization time measured during
rebind by about 56.8% for nd_pmem and 48.4% for dax_pmem on that arm64
VM setup. Since this arm64 setup does not use the x86 MOVNTI fast paths,
the result also suggests that the generic template-copy optimization can
benefit architectures without an architecture-specific
memcpy_nontemporal() backend.
[1] https://lore.kernel.org/all/aiEoByaQdRR3xtM5@nvdebian.thelocal/
Li Zhe (8):
mm: fix stale ZONE_DEVICE refcount comment
mm: factor zone-device page init helpers out of
__init_zone_device_page
mm: add a set_page_section_from_pfn() helper
mm: add a template-based fast path for zone-device page init
mm: extend the template fast path to zone-device compound tails
string: introduce memcpy_nontemporal()
mm: use memcpy_nontemporal() in zone-device template copies
x86/string: extend memcpy_flushcache() fixed-size fastpaths
arch/x86/include/asm/string_64.h | 83 +++++++++++++++----
include/linux/mm.h | 15 +++-
include/linux/string.h | 13 +++
mm/mm_init.c | 132 +++++++++++++++++++++++++------
4 files changed, 201 insertions(+), 42 deletions(-)
---
v9: https://lore.kernel.org/all/20260803070929.86075-1-lizhe.67@bytedance.com/
v8: https://lore.kernel.org/all/20260727123429.5673-1-lizhe.67@bytedance.com/
v7: https://lore.kernel.org/all/20260720120259.1545-1-lizhe.67@bytedance.com/
v6: https://lore.kernel.org/all/20260709112520.24857-1-lizhe.67@bytedance.com/
v5: https://lore.kernel.org/all/20260701090553.62691-1-lizhe.67@bytedance.com/
v4: https://lore.kernel.org/all/20260603080152.64728-1-lizhe.67@bytedance.com/
v3: https://lore.kernel.org/all/20260527033636.28231-1-lizhe.67@bytedance.com/
v2: https://lore.kernel.org/all/20260521040124.10608-1-lizhe.67@bytedance.com/
v1: https://lore.kernel.org/all/20260515082045.63029-1-lizhe.67@bytedance.com/
Changelogs:
v9->v10:
- Drop incorrect Suggested-by trailers from patches 4 and 5. Suggested
by Muchun Song.
- Fold Borislav Petkov's x86 memcpy_flushcache() cleanup: route the
existing 4/8/16-byte fixed-size cases through the same helper style and
keep the "memory" clobber consistently across the inline MOVNTI cases.
For changelogs of earlier revisions, please refer to the v9 cover letter.
--
2.20.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v10 1/8] mm: fix stale ZONE_DEVICE refcount comment
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
@ 2026-08-10 12:20 ` Li Zhe
2026-08-16 12:04 ` Mike Rapoport
2026-08-10 12:20 ` [PATCH v10 2/8] mm: factor zone-device page init helpers out of __init_zone_device_page Li Zhe
` (6 subsequent siblings)
7 siblings, 1 reply; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
The comment in __init_zone_device_page() still uses the old
MEMORY_TYPE_* names and implies that FS_DAX pages regain a
refcount of 1 in the free path. That no longer matches the code.
Update the comment to describe the current policy correctly:
MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free path,
while the remaining ZONE_DEVICE types start from 0 here and raise the
count again when the allocator or driver hands the page out.
No functional change intended.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Alistair Popple <apopple@nvidia.com>
Reviewed-by: Muchun Song <muchun.song@linux.dev>
---
mm/mm_init.c | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 0f64909e8d20..95808ab5cfdb 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1030,13 +1030,9 @@ static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
page->zone_device_data = NULL;
/*
- * ZONE_DEVICE pages other than MEMORY_TYPE_GENERIC are released
- * directly to the driver page allocator which will set the page count
- * to 1 when allocating the page.
- *
- * MEMORY_TYPE_GENERIC and MEMORY_TYPE_FS_DAX pages automatically have
- * their refcount reset to one whenever they are freed (ie. after
- * their refcount drops to 0).
+ * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
+ * path. The remaining ZONE_DEVICE types start from 0 here and raise
+ * the count again when the allocator or driver hands the page out.
*/
switch (pgmap->type) {
case MEMORY_DEVICE_FS_DAX:
--
2.20.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v10 2/8] mm: factor zone-device page init helpers out of __init_zone_device_page
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
2026-08-10 12:20 ` [PATCH v10 1/8] mm: fix stale ZONE_DEVICE refcount comment Li Zhe
@ 2026-08-10 12:20 ` Li Zhe
2026-08-16 12:04 ` Mike Rapoport
2026-08-10 12:20 ` [PATCH v10 3/8] mm: add a set_page_section_from_pfn() helper Li Zhe
` (5 subsequent siblings)
7 siblings, 1 reply; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
memmap_init_zone_device() currently mixes refcount policy and core
ZONE_DEVICE page setup in a single helper.
Factor the refcount-reset predicate into pagemap_requires_refcount_reset(),
move the common page initialization into __zone_device_page_init(), and
wrap the existing slow path in zone_device_page_init_slow().
This keeps the slow-path behaviour unchanged and gives later patches
reusable helper boundaries.
No functional change intended.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
mm/mm_init.c | 56 ++++++++++++++++++++++++++++++++++------------------
1 file changed, 37 insertions(+), 19 deletions(-)
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 95808ab5cfdb..a70acb7431a6 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1005,11 +1005,37 @@ static void __init memmap_init(void)
}
#ifdef CONFIG_ZONE_DEVICE
-static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
+/*
+ * Return true when memmap_init_zone_device() must initialize the page
+ * refcount to 0. MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in
+ * the free path, while the remaining ZONE_DEVICE types start from 0 here
+ * and raise the count again when the allocator or driver hands the page
+ * out.
+ */
+static inline bool pagemap_requires_refcount_reset(const struct dev_pagemap *pgmap)
+{
+ /*
+ * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
+ * path. The remaining ZONE_DEVICE types start from 0 here and raise
+ * the count again when the allocator or driver hands the page out.
+ */
+ switch (pgmap->type) {
+ case MEMORY_DEVICE_FS_DAX:
+ case MEMORY_DEVICE_PRIVATE:
+ case MEMORY_DEVICE_COHERENT:
+ case MEMORY_DEVICE_PCI_P2PDMA:
+ return true;
+ case MEMORY_DEVICE_GENERIC:
+ return false;
+ }
+
+ return false;
+}
+
+static void __ref __zone_device_page_init(struct page *page, unsigned long pfn,
unsigned long zone_idx, int nid,
struct dev_pagemap *pgmap)
{
-
__init_single_page(page, pfn, zone_idx, nid);
/*
@@ -1028,23 +1054,15 @@ static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
*/
page_folio(page)->pgmap = pgmap;
page->zone_device_data = NULL;
+}
- /*
- * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
- * path. The remaining ZONE_DEVICE types start from 0 here and raise
- * the count again when the allocator or driver hands the page out.
- */
- switch (pgmap->type) {
- case MEMORY_DEVICE_FS_DAX:
- case MEMORY_DEVICE_PRIVATE:
- case MEMORY_DEVICE_COHERENT:
- case MEMORY_DEVICE_PCI_P2PDMA:
+static void __ref zone_device_page_init_slow(struct page *page,
+ unsigned long pfn, unsigned long zone_idx, int nid,
+ struct dev_pagemap *pgmap)
+{
+ __zone_device_page_init(page, pfn, zone_idx, nid, pgmap);
+ if (pagemap_requires_refcount_reset(pgmap))
set_page_count(page, 0);
- break;
-
- case MEMORY_DEVICE_GENERIC:
- break;
- }
}
/*
@@ -1090,7 +1108,7 @@ static void __ref memmap_init_compound(struct page *head,
for (pfn = head_pfn + 1; pfn < end_pfn; pfn++) {
struct page *page = pfn_to_page(pfn);
- __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
+ zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
prep_compound_tail(page, head, order);
set_page_count(page, 0);
}
@@ -1126,7 +1144,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
struct page *page = pfn_to_page(pfn);
- __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
+ zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
cond_resched();
--
2.20.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v10 3/8] mm: add a set_page_section_from_pfn() helper
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
2026-08-10 12:20 ` [PATCH v10 1/8] mm: fix stale ZONE_DEVICE refcount comment Li Zhe
2026-08-10 12:20 ` [PATCH v10 2/8] mm: factor zone-device page init helpers out of __init_zone_device_page Li Zhe
@ 2026-08-10 12:20 ` Li Zhe
2026-08-10 12:20 ` [PATCH v10 4/8] mm: add a template-based fast path for zone-device page init Li Zhe
` (4 subsequent siblings)
7 siblings, 0 replies; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
Callers that want to update section bits from a PFN currently need to
open-code:
set_page_section(page, pfn_to_section_nr(pfn));
and guard that sequence with #ifdef SECTION_IN_PAGE_FLAGS.
Add set_page_section_from_pfn() to wrap that update in one place. When
section bits are stored in page flags, the helper derives the section
number from the PFN and updates the page flags. Otherwise keep it as a
no-op so callers can use one helper without open-coding
SECTION_IN_PAGE_FLAGS.
Convert set_page_links() to use the new helper so later ZONE_DEVICE
fast-path patches can also update section bits without open-coding
SECTION_IN_PAGE_FLAGS at each callsite.
This keeps the PFN-to-section translation local to the configurations
that actually store section bits in struct page flags, and avoids
exposing that detail to generic callers.
No functional change intended.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: Muchun Song <muchun.song@linux.dev>
Reviewed-by: Balbir Singh <balbirs@nvidia.com>
---
include/linux/mm.h | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 485df9c2dbdd..43343bfce493 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2541,11 +2541,22 @@ static inline void set_page_section(struct page *page, unsigned long section)
page->flags.f |= (section & SECTIONS_MASK) << SECTIONS_PGSHIFT;
}
+static inline void set_page_section_from_pfn(struct page *page,
+ unsigned long pfn)
+{
+ set_page_section(page, pfn_to_section_nr(pfn));
+}
+
static inline unsigned long memdesc_section(memdesc_flags_t mdf)
{
return (mdf.f >> SECTIONS_PGSHIFT) & SECTIONS_MASK;
}
#else /* !SECTION_IN_PAGE_FLAGS */
+static inline void set_page_section_from_pfn(struct page *page,
+ unsigned long pfn)
+{
+}
+
static inline unsigned long memdesc_section(memdesc_flags_t mdf)
{
return 0;
@@ -2768,9 +2779,7 @@ static inline void set_page_links(struct page *page, enum zone_type zone,
{
set_page_zone(page, zone);
set_page_node(page, node);
-#ifdef SECTION_IN_PAGE_FLAGS
- set_page_section(page, pfn_to_section_nr(pfn));
-#endif
+ set_page_section_from_pfn(page, pfn);
}
/**
--
2.20.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v10 4/8] mm: add a template-based fast path for zone-device page init
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
` (2 preceding siblings ...)
2026-08-10 12:20 ` [PATCH v10 3/8] mm: add a set_page_section_from_pfn() helper Li Zhe
@ 2026-08-10 12:20 ` Li Zhe
2026-08-16 12:04 ` Mike Rapoport
2026-08-10 12:20 ` [PATCH v10 5/8] mm: extend the template fast path to zone-device compound tails Li Zhe
` (3 subsequent siblings)
7 siblings, 1 reply; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
memmap_init_zone_device() repeats nearly identical head-page
initialization for each PFN. Prepare one reusable ZONE_DEVICE head-page
template through the existing slow path, refresh the PFN-dependent
fields in that template before each copy, and memcpy it into each
destination page.
Use the template path unconditionally. The page_ref_set tracepoint is
primarily a debugging aid, while this code is still initializing struct
pages before they are handed out. From the perspective of users of those
pages, the initialization-time refcount transitions are not part of the
observable page lifetime.
This means page_ref_set will no longer observe every initialization-time
refcount assignment for copied ZONE_DEVICE head pages. The impact is
controlled because the final initialized struct page state is unchanged,
and keeping a separate non-template path only for this local tracepoint
observability would add complexity to the common path.
This patch accelerates head-page initialization. The pfns_per_compound
== 1 case gets the full benefit here, compound tails are handled in the
next patch.
Tested in a VM with a 100 GB fsdax namespace device configured with
map=dev on Intel Ice Lake server. This test exercises the nd_pmem rebind
path (pfns_per_compound == 1).
Test procedure:
Rebind the nd_pmem driver 30 times and collect the memmap initialization
time from the pr_debug() output of memmap_init_zone_device().
Base(v7.2-rc1):
Average of rebinds for nd_pmem driver: 244.28 ms
With this patch and its prerequisites applied:
Average of rebinds for nd_pmem driver: 215.55 ms
This reduces the average memmap initialization time measured during rebind
from 244.28 ms to 215.55 ms, or about 11%.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
mm/mm_init.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 46 insertions(+), 1 deletion(-)
diff --git a/mm/mm_init.c b/mm/mm_init.c
index a70acb7431a6..56a36a71ba89 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1065,6 +1065,35 @@ static void __ref zone_device_page_init_slow(struct page *page,
set_page_count(page, 0);
}
+/*
+ * 'template' is a reusable page prototype rather than a strictly immutable
+ * object. Most ZONE_DEVICE fields stay constant across the pages covered by
+ * the current template, but section bits and page->virtual may still depend
+ * on the PFN. Refresh those PFN-dependent fields in the template before
+ * copying it into @page.
+ */
+static inline void zone_device_page_update_template(struct page *template,
+ unsigned long pfn)
+{
+ set_page_section_from_pfn(template, pfn);
+#ifdef WANT_PAGE_VIRTUAL
+ if (!is_highmem_idx(ZONE_DEVICE))
+ set_page_address(template, __va(pfn << PAGE_SHIFT));
+#endif
+}
+
+static void zone_device_page_init_from_template(struct page *page,
+ unsigned long pfn, struct page *template)
+{
+ /*
+ * 'template' carries the invariant portion of a ZONE_DEVICE struct
+ * page. Update the PFN-dependent fields in place before copying it
+ * to the destination page.
+ */
+ zone_device_page_update_template(template, pfn);
+ memcpy(page, template, sizeof(*page));
+}
+
/*
* With compound page geometry and when struct pages are stored in ram most
* tail pages are reused. Consequently, the amount of unique struct pages to
@@ -1127,6 +1156,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
unsigned long zone_idx = zone_idx(zone);
unsigned long start = jiffies;
int nid = pgdat->node_id;
+ struct page template;
if (WARN_ON_ONCE(!pgmap || zone_idx != ZONE_DEVICE))
return;
@@ -1144,7 +1174,22 @@ void __ref memmap_init_zone_device(struct zone *zone,
for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
struct page *page = pfn_to_page(pfn);
- zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
+ if (pfn == start_pfn) {
+ /*
+ * Seed the reusable head-page template from the
+ * first real struct page. This initializes the
+ * first page through the existing slow path and
+ * then reuses that final state as the template
+ * for subsequent pages.
+ */
+ zone_device_page_init_slow(page, pfn, zone_idx,
+ nid, pgmap);
+ /* init template page */
+ memcpy(&template, page, sizeof(*page));
+ } else {
+ zone_device_page_init_from_template(page, pfn,
+ &template);
+ }
if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
cond_resched();
--
2.20.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v10 5/8] mm: extend the template fast path to zone-device compound tails
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
` (3 preceding siblings ...)
2026-08-10 12:20 ` [PATCH v10 4/8] mm: add a template-based fast path for zone-device page init Li Zhe
@ 2026-08-10 12:20 ` Li Zhe
2026-08-10 12:20 ` [PATCH v10 6/8] string: introduce memcpy_nontemporal() Li Zhe
` (2 subsequent siblings)
7 siblings, 0 replies; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
The template fast path from the previous patch only accelerates head
pages. Compound tails in memmap_init_compound() still go through the
zone_device_page_init_slow() one by one.
Build separate head and tail templates and reuse one prepared tail
template across the tail pages in a compound range. Head pages preserve
the existing refcount policy, while compound tails always start with a
refcount of 0 after prep_compound_tail().
This extends the template-copy fast path to pfns_per_compound > 1
without changing the existing zone_device_page_init_slow() helper.
Tail-page PFN-dependent fields are refreshed in the reusable tail
template before each copy.
Do not keep a separate non-template fallback for compound tails either.
These pages are still under memmap initialization, and the
initialization-time refcount updates are not part of the observable
lifetime of pages handed out later.
The impact is controlled for the same reason as for head pages. The
first tail page still seeds the reusable tail template through the
normal tail initialization sequence, and the copied tail pages have the
same final initialized state except for the PFN-dependent fields
refreshed before each copy.
Tested in a VM with a 100 GB devdax namespace (align=2097152) on Intel
Ice Lake server. This test exercises the dax_pmem rebind path and
measures memmap initialization latency.
Test procedure:
Unbind and rebind the dax_pmem driver 30 times, collect memmap
initialization time from the pr_debug() output of memmap_init_zone_device().
Base(v7.2-rc1):
Average of rebinds for dax_pmem driver: 273.31 ms
With this patch and its prerequisites applied:
Average of rebinds for dax_pmem driver: 244.37 ms
This reduces the average memmap initialization time measured during rebind
from 273.31 ms to 244.37 ms, or about 10.6%.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
mm/mm_init.c | 31 ++++++++++++++++++++++++++++---
1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 56a36a71ba89..9691fa2a060d 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1065,6 +1065,16 @@ static void __ref zone_device_page_init_slow(struct page *page,
set_page_count(page, 0);
}
+static inline void zone_device_tail_page_init(struct page *page,
+ unsigned long pfn, unsigned long zone_idx, int nid,
+ struct dev_pagemap *pgmap, const struct page *head,
+ unsigned int order)
+{
+ zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
+ prep_compound_tail(page, head, order);
+ set_page_count(page, 0);
+}
+
/*
* 'template' is a reusable page prototype rather than a strictly immutable
* object. Most ZONE_DEVICE fields stay constant across the pages covered by
@@ -1126,6 +1136,7 @@ static void __ref memmap_init_compound(struct page *head,
{
unsigned long pfn, end_pfn = head_pfn + nr_pages;
unsigned int order = pgmap->vmemmap_shift;
+ struct page template;
/*
* We have to initialize the pages, including setting up page links.
@@ -1134,12 +1145,26 @@ static void __ref memmap_init_compound(struct page *head,
* the pages in the same go.
*/
__SetPageHead(head);
+
for (pfn = head_pfn + 1; pfn < end_pfn; pfn++) {
struct page *page = pfn_to_page(pfn);
- zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
- prep_compound_tail(page, head, order);
- set_page_count(page, 0);
+ if (pfn == head_pfn + 1) {
+ /*
+ * All tails of the same compound page share the
+ * state established by prep_compound_tail(). Reuse
+ * one tail template for the whole range and
+ * refresh only the PFN-dependent fields in that
+ * template before each copy.
+ */
+ zone_device_tail_page_init(page, pfn, zone_idx, nid,
+ pgmap, head, order);
+ /* init template page */
+ memcpy(&template, page, sizeof(*page));
+ } else {
+ zone_device_page_init_from_template(page, pfn,
+ &template);
+ }
}
prep_compound_head(head, order);
}
--
2.20.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v10 6/8] string: introduce memcpy_nontemporal()
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
` (4 preceding siblings ...)
2026-08-10 12:20 ` [PATCH v10 5/8] mm: extend the template fast path to zone-device compound tails Li Zhe
@ 2026-08-10 12:20 ` Li Zhe
2026-08-10 12:20 ` [PATCH v10 7/8] mm: use memcpy_nontemporal() in zone-device template copies Li Zhe
2026-08-10 12:20 ` [PATCH v10 8/8] x86/string: extend memcpy_flushcache() fixed-size fastpaths Li Zhe
7 siblings, 0 replies; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
Introduce memcpy_nontemporal() for write-once copy sites that want a
named non-temporal copy primitive.
On x86_64, override the helper in arch/x86/include/asm/string_64.h using
the usual self-macro pattern, next to the existing memcpy_flushcache()
backend that memcpy_nontemporal() wraps.
include/linux/string.h provides the generic memcpy_nontemporal()
fallback as
#define memcpy_nontemporal(dst, src, len) \
((void)memcpy(dst, src, len))
instead of an inline wrapper, so architectures without a specialized
backend keep the usual memcpy() FORTIFY coverage when the compiler can
still see object sizes at the original call site. It also makes the
memcpy_nontemporal() API uniformly void, matching memcpy_flushcache()
and the x86 backend, so callers cannot accidentally depend on a return
value on fallback architectures.
memcpy_nontemporal() is only a copy primitive. It does not imply a drain
or a publication barrier. Callers that use it before a producer-consumer
or device-visible handoff must provide the required ordering at that
handoff point.
The immediate user is the ZONE_DEVICE template-copy path. It populates
struct page descriptors in a write-once pattern, so a regular cached
memcpy() can incur avoidable write-allocate traffic and cache pollution
for data with little near-term reuse.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
arch/x86/include/asm/string_64.h | 12 ++++++++++++
include/linux/string.h | 13 +++++++++++++
2 files changed, 25 insertions(+)
diff --git a/arch/x86/include/asm/string_64.h b/arch/x86/include/asm/string_64.h
index 4635616863f5..21ae515ae35a 100644
--- a/arch/x86/include/asm/string_64.h
+++ b/arch/x86/include/asm/string_64.h
@@ -100,6 +100,18 @@ static __always_inline void memcpy_flushcache(void *dst, const void *src, size_t
}
__memcpy_flushcache(dst, src, cnt);
}
+
+#define memcpy_nontemporal memcpy_nontemporal
+/*
+ * Reuse the existing x86 flushcache backend as the non-temporal copy
+ * primitive.
+ */
+static __always_inline void memcpy_nontemporal(void *dst, const void *src,
+ size_t cnt)
+{
+ memcpy_flushcache(dst, src, cnt);
+}
+
#endif
#endif /* __KERNEL__ */
diff --git a/include/linux/string.h b/include/linux/string.h
index 5702daca4326..6cb5cdd01158 100644
--- a/include/linux/string.h
+++ b/include/linux/string.h
@@ -278,6 +278,19 @@ static inline void memcpy_flushcache(void *dst, const void *src, size_t cnt)
}
#endif
+#ifndef memcpy_nontemporal
+/*
+ * memcpy_nontemporal() requests a non-temporal copy when the
+ * architecture has a suitable backend. Architectures without a
+ * specialized backend fall back to memcpy(). Keep this as a
+ * function-like macro so the compiler can still see the original
+ * memcpy() call site and preserve the usual FORTIFY coverage when
+ * object sizes remain visible there, while keeping the API void.
+ */
+#define memcpy_nontemporal(dst, src, len) \
+ ((void)memcpy(dst, src, len))
+#endif
+
void *memchr_inv(const void *s, int c, size_t n);
char *strreplace(char *str, char old, char new);
--
2.20.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v10 7/8] mm: use memcpy_nontemporal() in zone-device template copies
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
` (5 preceding siblings ...)
2026-08-10 12:20 ` [PATCH v10 6/8] string: introduce memcpy_nontemporal() Li Zhe
@ 2026-08-10 12:20 ` Li Zhe
2026-08-10 12:20 ` [PATCH v10 8/8] x86/string: extend memcpy_flushcache() fixed-size fastpaths Li Zhe
7 siblings, 0 replies; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
The template fast path currently uses memcpy() for the actual struct
page copy. Switch zone_device_page_init_from_template() to
memcpy_nontemporal().
ZONE_DEVICE memmap initialization is largely write-once: each struct
page is populated once, and most destination cachelines are not expected
to be reused immediately afterwards. On x86, a regular cached memcpy()
can therefore incur write-allocate traffic by pulling destination
cachelines into the cache before writeback, and can populate the cache
with data that has little near-term reuse. Using memcpy_nontemporal()
lets this path request nontemporal stores for that copy pattern, which
can reduce cache pollution and avoid part of the associated
write-allocate overhead, while architectures without a specialized
backend still fall back to memcpy().
Do not add a KASAN/KMSAN-specific fallback around this call site. As
Muchun pointed out, special KASAN handling for memcpy_flushcache() or
memcpy_nontemporal(), if needed, belongs in the low-level helper rather
than in this ZONE_DEVICE caller.
No separate drain is added here. memcpy_nontemporal() is used only as
the copy primitive while memmap_init_zone_device() is still initializing
the struct page array. The ordinary stores that follow in this path,
such as compound-page setup, are part of the same CPU's initialization
sequence; they are not used as a publication store that tells another CPU
or device to consume data written by the non-temporal copy.
Therefore this call site does not need a helper-level drain for
correctness. Callers that use memcpy_nontemporal() as part of a
producer-consumer or device-visible handoff must add the required
ordering themselves.
Tested in a VM with a 100 GB fsdax namespace device configured with
map=dev and a 100 GB devdax namespace (align=2097152) on Intel Ice Lake
server.
Test procedure:
Rebind the nd_pmem and dax_pmem driver 30 times and collect the memmap
initialization time from the pr_debug() output of
memmap_init_zone_device().
Base(v7.2-rc1):
Average of rebinds for nd_pmem driver: 244.28 ms
Average of rebinds for dax_pmem driver: 273.31 ms
With this patch and its prerequisites applied:
Average of rebinds for nd_pmem driver: 150.83 ms
Average of rebinds for dax_pmem driver: 153.55 ms
This reduces the average memmap initialization time measured during rebind
by about 38.3% for nd_pmem and 43.8% for dax_pmem.
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
mm/mm_init.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 9691fa2a060d..bb2007806a28 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1101,7 +1101,7 @@ static void zone_device_page_init_from_template(struct page *page,
* to the destination page.
*/
zone_device_page_update_template(template, pfn);
- memcpy(page, template, sizeof(*page));
+ memcpy_nontemporal(page, template, sizeof(*page));
}
/*
--
2.20.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v10 8/8] x86/string: extend memcpy_flushcache() fixed-size fastpaths
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
` (6 preceding siblings ...)
2026-08-10 12:20 ` [PATCH v10 7/8] mm: use memcpy_nontemporal() in zone-device template copies Li Zhe
@ 2026-08-10 12:20 ` Li Zhe
2026-08-11 19:46 ` Borislav Petkov
7 siblings, 1 reply; 16+ messages in thread
From: Li Zhe @ 2026-08-10 12:20 UTC (permalink / raw)
To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx
Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
lizhe.67
The x86 memcpy_nontemporal() helper maps to memcpy_flushcache(), and the
ZONE_DEVICE template-copy path uses it to copy one struct page at a
time.
The relevant copy size is sizeof(struct page). On x86_64, the base struct
page layout is 64 bytes. Adding either the KMSAN metadata pointers or an
out-of-flags last_cpupid field can make it 80 bytes after alignment, and
enabling both can make it 96 bytes.
memcpy_flushcache() currently only has inline fixed-size cases for 4, 8,
and 16 bytes. As a result, these constant-sized struct page copies fall
through to __memcpy_flushcache() even though the compiler knows the copy
size at the call site.
Add fixed-size MOVNTI cases up to 96 bytes so the ZONE_DEVICE
template-copy path can keep these struct page copies in the inline
memcpy_flushcache() path.
This matters for ZONE_DEVICE memmap initialization because the copy
happens once per initialized struct page. For a 100 GB fsdax namespace
with map=dev, this is about 25 million struct page copies during nd_pmem
binding or rebinding.
Tested in a VM with a 100 GB fsdax namespace device configured with
map=dev and a 100 GB devdax namespace (align=2097152) on Intel Ice Lake
server.
Test procedure:
Rebind the nd_pmem and dax_pmem drivers 30 times and collect the memmap
initialization time from the pr_debug() output of
memmap_init_zone_device().
With memcpy_nontemporal() used by the ZONE_DEVICE template-copy path:
Average of rebinds for nd_pmem driver: 150.83 ms
Average of rebinds for dax_pmem driver: 153.55 ms
With this x86 fixed-size fastpath patch applied:
Average of rebinds for nd_pmem driver: 96.79 ms
Average of rebinds for dax_pmem driver: 119.04 ms
This further reduces the average memmap initialization time measured
during rebind by about 35.8% for nd_pmem and 22.5% for dax_pmem.
Suggested-by: Borislav Petkov <bp@alien8.de>
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
arch/x86/include/asm/string_64.h | 71 +++++++++++++++++++++++++-------
1 file changed, 56 insertions(+), 15 deletions(-)
diff --git a/arch/x86/include/asm/string_64.h b/arch/x86/include/asm/string_64.h
index 21ae515ae35a..831d3dda3b38 100644
--- a/arch/x86/include/asm/string_64.h
+++ b/arch/x86/include/asm/string_64.h
@@ -82,23 +82,64 @@ int strcmp(const char *cs, const char *ct);
#ifdef CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE
#define __HAVE_ARCH_MEMCPY_FLUSHCACHE 1
void __memcpy_flushcache(void *dst, const void *src, size_t cnt);
-static __always_inline void memcpy_flushcache(void *dst, const void *src, size_t cnt)
+
+static __always_inline void movnti_4(void *dst, const void *src)
+{
+ asm volatile("movntil %1, %0"
+ : "=m"(*(u32 *)dst)
+ : "r"(*(const u32 *)src)
+ : "memory");
+}
+
+static __always_inline void movnti_8(void *dst, const void *src)
+{
+ asm volatile("movntiq %1, %0"
+ : "=m"(*(u64 *)dst)
+ : "r"(*(const u64 *)src)
+ : "memory");
+}
+
+static __always_inline void movnti_16(void *dst, const void *src)
+{
+ movnti_8(dst, src);
+ movnti_8(dst + 8, src + 8);
+}
+
+static __always_inline void movnti_32(void *dst, const void *src)
+{
+ movnti_16(dst, src);
+ movnti_16(dst + 16, src + 16);
+}
+
+static __always_inline void movnti_64(void *dst, const void *src)
+{
+ movnti_32(dst, src);
+ movnti_32(dst + 32, src + 32);
+}
+
+static __always_inline void memcpy_flushcache(void *dst, const void *src,
+ size_t cnt)
{
- if (__builtin_constant_p(cnt)) {
- switch (cnt) {
- case 4:
- asm ("movntil %1, %0" : "=m"(*(u32 *)dst) : "r"(*(u32 *)src));
- return;
- case 8:
- asm ("movntiq %1, %0" : "=m"(*(u64 *)dst) : "r"(*(u64 *)src));
- return;
- case 16:
- asm ("movntiq %1, %0" : "=m"(*(u64 *)dst) : "r"(*(u64 *)src));
- asm ("movntiq %1, %0" : "=m"(*(u64 *)(dst + 8)) : "r"(*(u64 *)(src + 8)));
- return;
- }
+ if (!__builtin_constant_p(cnt))
+ return __memcpy_flushcache(dst, src, cnt);
+
+ /*
+ * The relevant fixed-size copies here are the x86_64 struct page sizes:
+ * 64, 80, and 96 bytes. Keep 32-byte and 48-byte copies inline as well
+ * instead of sending those nearby fixed-size cases back to
+ * __memcpy_flushcache().
+ */
+ switch (cnt) {
+ case 4: movnti_4(dst, src); break;
+ case 8: movnti_8(dst, src); break;
+ case 16: movnti_16(dst, src); break;
+ case 32: movnti_32(dst, src); break;
+ case 48: movnti_32(dst, src); movnti_16(dst + 32, src + 32); break;
+ case 64: movnti_64(dst, src); break;
+ case 80: movnti_64(dst, src); movnti_16(dst + 64, src + 64); break;
+ case 96: movnti_64(dst, src); movnti_32(dst + 64, src + 64); break;
+ default: __memcpy_flushcache(dst, src, cnt); break;
}
- __memcpy_flushcache(dst, src, cnt);
}
#define memcpy_nontemporal memcpy_nontemporal
--
2.20.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v10 8/8] x86/string: extend memcpy_flushcache() fixed-size fastpaths
2026-08-10 12:20 ` [PATCH v10 8/8] x86/string: extend memcpy_flushcache() fixed-size fastpaths Li Zhe
@ 2026-08-11 19:46 ` Borislav Petkov
0 siblings, 0 replies; 16+ messages in thread
From: Borislav Petkov @ 2026-08-11 19:46 UTC (permalink / raw)
To: Li Zhe
Cc: akpm, apopple, arnd, balbirs, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx, linux-arch, linux-hardening,
linux-kernel, linux-mm, x86
On Mon, Aug 10, 2026 at 08:20:57PM +0800, Li Zhe wrote:
> The x86 memcpy_nontemporal() helper maps to memcpy_flushcache(), and the
> ZONE_DEVICE template-copy path uses it to copy one struct page at a
> time.
>
> The relevant copy size is sizeof(struct page). On x86_64, the base struct
> page layout is 64 bytes. Adding either the KMSAN metadata pointers or an
> out-of-flags last_cpupid field can make it 80 bytes after alignment, and
> enabling both can make it 96 bytes.
>
> memcpy_flushcache() currently only has inline fixed-size cases for 4, 8,
> and 16 bytes. As a result, these constant-sized struct page copies fall
> through to __memcpy_flushcache() even though the compiler knows the copy
> size at the call site.
>
> Add fixed-size MOVNTI cases up to 96 bytes so the ZONE_DEVICE
> template-copy path can keep these struct page copies in the inline
> memcpy_flushcache() path.
>
> This matters for ZONE_DEVICE memmap initialization because the copy
> happens once per initialized struct page. For a 100 GB fsdax namespace
> with map=dev, this is about 25 million struct page copies during nd_pmem
> binding or rebinding.
>
> Tested in a VM with a 100 GB fsdax namespace device configured with
> map=dev and a 100 GB devdax namespace (align=2097152) on Intel Ice Lake
> server.
>
> Test procedure:
> Rebind the nd_pmem and dax_pmem drivers 30 times and collect the memmap
> initialization time from the pr_debug() output of
> memmap_init_zone_device().
>
> With memcpy_nontemporal() used by the ZONE_DEVICE template-copy path:
> Average of rebinds for nd_pmem driver: 150.83 ms
> Average of rebinds for dax_pmem driver: 153.55 ms
>
> With this x86 fixed-size fastpath patch applied:
> Average of rebinds for nd_pmem driver: 96.79 ms
> Average of rebinds for dax_pmem driver: 119.04 ms
>
> This further reduces the average memmap initialization time measured
> during rebind by about 35.8% for nd_pmem and 22.5% for dax_pmem.
>
> Suggested-by: Borislav Petkov <bp@alien8.de>
> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
> ---
> arch/x86/include/asm/string_64.h | 71 +++++++++++++++++++++++++-------
> 1 file changed, 56 insertions(+), 15 deletions(-)
Acked-by: Borislav Petkov (AMD) <bp@alien8.de>
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v10 1/8] mm: fix stale ZONE_DEVICE refcount comment
2026-08-10 12:20 ` [PATCH v10 1/8] mm: fix stale ZONE_DEVICE refcount comment Li Zhe
@ 2026-08-16 12:04 ` Mike Rapoport
0 siblings, 0 replies; 16+ messages in thread
From: Mike Rapoport @ 2026-08-16 12:04 UTC (permalink / raw)
To: Li Zhe
Cc: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx, linux-arch, linux-hardening,
linux-kernel, linux-mm, x86
On Mon, 10 Aug 2026 20:20:50 +0800, Li Zhe <lizhe.67@bytedance.com> wrote:
> The comment in __init_zone_device_page() still uses the old
> MEMORY_TYPE_* names and implies that FS_DAX pages regain a
> refcount of 1 in the free path. That no longer matches the code.
>
> Update the comment to describe the current policy correctly:
> MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free path,
> while the remaining ZONE_DEVICE types start from 0 here and raise the
> count again when the allocator or driver hands the page out.
>
> [...]
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v10 2/8] mm: factor zone-device page init helpers out of __init_zone_device_page
2026-08-10 12:20 ` [PATCH v10 2/8] mm: factor zone-device page init helpers out of __init_zone_device_page Li Zhe
@ 2026-08-16 12:04 ` Mike Rapoport
2026-08-17 7:11 ` Li Zhe
0 siblings, 1 reply; 16+ messages in thread
From: Mike Rapoport @ 2026-08-16 12:04 UTC (permalink / raw)
To: Li Zhe
Cc: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx, linux-arch, linux-hardening,
linux-kernel, linux-mm, x86
Hi,
> memmap_init_zone_device() currently mixes refcount policy and core
> ZONE_DEVICE page setup in a single helper.
>
> Factor the refcount-reset predicate into pagemap_requires_refcount_reset(),
> move the common page initialization into __zone_device_page_init(), and
> wrap the existing slow path in zone_device_page_init_slow().
>
> This keeps the slow-path behaviour unchanged and gives later patches
> reusable helper boundaries.
>
> No functional change intended.
>
> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
>
> diff --git a/mm/mm_init.c b/mm/mm_init.c
> index 95808ab5cfdb..a70acb7431a6 100644
> --- a/mm/mm_init.c
> +++ b/mm/mm_init.c
> @@ -1005,11 +1005,37 @@ static void __init memmap_init(void)
> }
>
> #ifdef CONFIG_ZONE_DEVICE
> -static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
> +/*
> + * Return true when memmap_init_zone_device() must initialize the page
> + * refcount to 0. MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in
> + * the free path, while the remaining ZONE_DEVICE types start from 0 here
> + * and raise the count again when the allocator or driver hands the page
> + * out.
> + */
> +static inline bool pagemap_requires_refcount_reset(const struct dev_pagemap *pgmap)
> +{
> + /*
> + * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
> + * path. The remaining ZONE_DEVICE types start from 0 here and raise
> + * the count again when the allocator or driver hands the page out.
> + */
> + switch (pgmap->type) {
> + case MEMORY_DEVICE_FS_DAX:
> + case MEMORY_DEVICE_PRIVATE:
> + case MEMORY_DEVICE_COHERENT:
> + case MEMORY_DEVICE_PCI_P2PDMA:
> + return true;
> + case MEMORY_DEVICE_GENERIC:
> + return false;
> + }
> +
> + return false;
> +}
> +
> +static void __ref __zone_device_page_init(struct page *page, unsigned long pfn,
> unsigned long zone_idx, int nid,
> struct dev_pagemap *pgmap)
> {
> -
> __init_single_page(page, pfn, zone_idx, nid);
>
> /*
> @@ -1028,23 +1054,15 @@ static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
> */
> page_folio(page)->pgmap = pgmap;
> page->zone_device_data = NULL;
> +}
>
> - /*
> - * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
> - * path. The remaining ZONE_DEVICE types start from 0 here and raise
> - * the count again when the allocator or driver hands the page out.
> - */
> - switch (pgmap->type) {
> - case MEMORY_DEVICE_FS_DAX:
> - case MEMORY_DEVICE_PRIVATE:
> - case MEMORY_DEVICE_COHERENT:
> - case MEMORY_DEVICE_PCI_P2PDMA:
> +static void __ref zone_device_page_init_slow(struct page *page,
> + unsigned long pfn, unsigned long zone_idx, int nid,
> + struct dev_pagemap *pgmap)
> +{
> + __zone_device_page_init(page, pfn, zone_idx, nid, pgmap);
> + if (pagemap_requires_refcount_reset(pgmap))
> set_page_count(page, 0);
> - break;
> -
> - case MEMORY_DEVICE_GENERIC:
> - break;
> - }
> }
>
> /*
> @@ -1090,7 +1108,7 @@ static void __ref memmap_init_compound(struct page *head,
> for (pfn = head_pfn + 1; pfn < end_pfn; pfn++) {
> struct page *page = pfn_to_page(pfn);
>
> - __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
> + zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
> prep_compound_tail(page, head, order);
> set_page_count(page, 0);
> }
> @@ -1126,7 +1144,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
> for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
> struct page *page = pfn_to_page(pfn);
>
> - __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
> + zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
It looks like all callers of __init_zone_device_page() are now calling
zone_device_page_init_slow() that with series evolution became a thin
wrapper for __zone_device_page_init().
Maybe we can just rename __init_zone_device_page() to
__zone_device_page_init() and replace the open coded setting of the page
count with the call to pagemap_requires_refcount_reset() an be done
here?
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v10 4/8] mm: add a template-based fast path for zone-device page init
2026-08-10 12:20 ` [PATCH v10 4/8] mm: add a template-based fast path for zone-device page init Li Zhe
@ 2026-08-16 12:04 ` Mike Rapoport
2026-08-17 7:14 ` Li Zhe
0 siblings, 1 reply; 16+ messages in thread
From: Mike Rapoport @ 2026-08-16 12:04 UTC (permalink / raw)
To: Li Zhe
Cc: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, rppt, tglx, linux-arch, linux-hardening,
linux-kernel, linux-mm, x86
Hi,
> memmap_init_zone_device() repeats nearly identical head-page
> initialization for each PFN. Prepare one reusable ZONE_DEVICE head-page
> template through the existing slow path, refresh the PFN-dependent
> fields in that template before each copy, and memcpy it into each
> destination page.
>
> Use the template path unconditionally. The page_ref_set tracepoint is
> primarily a debugging aid, while this code is still initializing struct
> pages before they are handed out. From the perspective of users of those
> pages, the initialization-time refcount transitions are not part of the
> observable page lifetime.
>
> This means page_ref_set will no longer observe every initialization-time
> refcount assignment for copied ZONE_DEVICE head pages. The impact is
> controlled because the final initialized struct page state is unchanged,
> and keeping a separate non-template path only for this local tracepoint
> observability would add complexity to the common path.
>
> This patch accelerates head-page initialization. The pfns_per_compound
> == 1 case gets the full benefit here, compound tails are handled in the
> next patch.
>
> Tested in a VM with a 100 GB fsdax namespace device configured with
> map=dev on Intel Ice Lake server. This test exercises the nd_pmem rebind
> path (pfns_per_compound == 1).
>
> Test procedure:
> Rebind the nd_pmem driver 30 times and collect the memmap initialization
> time from the pr_debug() output of memmap_init_zone_device().
>
> Base(v7.2-rc1):
> Average of rebinds for nd_pmem driver: 244.28 ms
>
> With this patch and its prerequisites applied:
> Average of rebinds for nd_pmem driver: 215.55 ms
>
> This reduces the average memmap initialization time measured during rebind
> from 244.28 ms to 215.55 ms, or about 11%.
>
> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
>
> diff --git a/mm/mm_init.c b/mm/mm_init.c
> index a70acb7431a6..56a36a71ba89 100644
> --- a/mm/mm_init.c
> +++ b/mm/mm_init.c
> @@ -1065,6 +1065,35 @@ static void __ref zone_device_page_init_slow(struct page *page,
> set_page_count(page, 0);
> }
>
> +/*
> + * 'template' is a reusable page prototype rather than a strictly immutable
> + * object. Most ZONE_DEVICE fields stay constant across the pages covered by
> + * the current template, but section bits and page->virtual may still depend
> + * on the PFN. Refresh those PFN-dependent fields in the template before
> + * copying it into @page.
> + */
> +static inline void zone_device_page_update_template(struct page *template,
> + unsigned long pfn)
> +{
> + set_page_section_from_pfn(template, pfn);
> +#ifdef WANT_PAGE_VIRTUAL
> + if (!is_highmem_idx(ZONE_DEVICE))
> + set_page_address(template, __va(pfn << PAGE_SHIFT));
> +#endif
> +}
> +
> +static void zone_device_page_init_from_template(struct page *page,
> + unsigned long pfn, struct page *template)
> +{
> + /*
> + * 'template' carries the invariant portion of a ZONE_DEVICE struct
> + * page. Update the PFN-dependent fields in place before copying it
> + * to the destination page.
> + */
> + zone_device_page_update_template(template, pfn);
Looks like it's the only user of zone_device_page_update_template().
I'd just fold it here and drop the comment.
> + memcpy(page, template, sizeof(*page));
> +}
> +
> /*
> * With compound page geometry and when struct pages are stored in ram most
> * tail pages are reused. Consequently, the amount of unique struct pages to
> @@ -1127,6 +1156,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
> unsigned long zone_idx = zone_idx(zone);
> unsigned long start = jiffies;
> int nid = pgdat->node_id;
> + struct page template;
>
> if (WARN_ON_ONCE(!pgmap || zone_idx != ZONE_DEVICE))
> return;
> @@ -1144,7 +1174,22 @@ void __ref memmap_init_zone_device(struct zone *zone,
> for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
> struct page *page = pfn_to_page(pfn);
>
> - zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
> + if (pfn == start_pfn) {
> + /*
> + * Seed the reusable head-page template from the
> + * first real struct page. This initializes the
> + * first page through the existing slow path and
> + * then reuses that final state as the template
> + * for subsequent pages.
> + */
> + zone_device_page_init_slow(page, pfn, zone_idx,
> + nid, pgmap);
> + /* init template page */
> + memcpy(&template, page, sizeof(*page));
> + } else {
> + zone_device_page_init_from_template(page, pfn,
> + &template);
> + }
Why can't we init the template for the first page being initialized and
then call zone_device_page_init_from_template() unconditionally?
The same applies to the tail pages initialization.
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v10 2/8] mm: factor zone-device page init helpers out of __init_zone_device_page
2026-08-16 12:04 ` Mike Rapoport
@ 2026-08-17 7:11 ` Li Zhe
2026-08-17 11:15 ` Mike Rapoport
0 siblings, 1 reply; 16+ messages in thread
From: Li Zhe @ 2026-08-17 7:11 UTC (permalink / raw)
To: Mike Rapoport
Cc: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, tglx, linux-arch, linux-hardening, linux-kernel,
linux-mm, x86
On 8/16/26 8:04 PM, Mike Rapoport wrote:
> Hi,
>
>> memmap_init_zone_device() currently mixes refcount policy and core
>> ZONE_DEVICE page setup in a single helper.
>>
>> Factor the refcount-reset predicate into pagemap_requires_refcount_reset(),
>> move the common page initialization into __zone_device_page_init(), and
>> wrap the existing slow path in zone_device_page_init_slow().
>>
>> This keeps the slow-path behaviour unchanged and gives later patches
>> reusable helper boundaries.
>>
>> No functional change intended.
>>
>> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
>> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
>>
>> diff --git a/mm/mm_init.c b/mm/mm_init.c
>> index 95808ab5cfdb..a70acb7431a6 100644
>> --- a/mm/mm_init.c
>> +++ b/mm/mm_init.c
>> @@ -1005,11 +1005,37 @@ static void __init memmap_init(void)
>> }
>>
>> #ifdef CONFIG_ZONE_DEVICE
>> -static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
>> +/*
>> + * Return true when memmap_init_zone_device() must initialize the page
>> + * refcount to 0. MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in
>> + * the free path, while the remaining ZONE_DEVICE types start from 0 here
>> + * and raise the count again when the allocator or driver hands the page
>> + * out.
>> + */
>> +static inline bool pagemap_requires_refcount_reset(const struct dev_pagemap *pgmap)
>> +{
>> + /*
>> + * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
>> + * path. The remaining ZONE_DEVICE types start from 0 here and raise
>> + * the count again when the allocator or driver hands the page out.
>> + */
>> + switch (pgmap->type) {
>> + case MEMORY_DEVICE_FS_DAX:
>> + case MEMORY_DEVICE_PRIVATE:
>> + case MEMORY_DEVICE_COHERENT:
>> + case MEMORY_DEVICE_PCI_P2PDMA:
>> + return true;
>> + case MEMORY_DEVICE_GENERIC:
>> + return false;
>> + }
>> +
>> + return false;
>> +}
>> +
>> +static void __ref __zone_device_page_init(struct page *page, unsigned long pfn,
>> unsigned long zone_idx, int nid,
>> struct dev_pagemap *pgmap)
>> {
>> -
>> __init_single_page(page, pfn, zone_idx, nid);
>>
>> /*
>> @@ -1028,23 +1054,15 @@ static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
>> */
>> page_folio(page)->pgmap = pgmap;
>> page->zone_device_data = NULL;
>> +}
>>
>> - /*
>> - * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
>> - * path. The remaining ZONE_DEVICE types start from 0 here and raise
>> - * the count again when the allocator or driver hands the page out.
>> - */
>> - switch (pgmap->type) {
>> - case MEMORY_DEVICE_FS_DAX:
>> - case MEMORY_DEVICE_PRIVATE:
>> - case MEMORY_DEVICE_COHERENT:
>> - case MEMORY_DEVICE_PCI_P2PDMA:
>> +static void __ref zone_device_page_init_slow(struct page *page,
>> + unsigned long pfn, unsigned long zone_idx, int nid,
>> + struct dev_pagemap *pgmap)
>> +{
>> + __zone_device_page_init(page, pfn, zone_idx, nid, pgmap);
>> + if (pagemap_requires_refcount_reset(pgmap))
>> set_page_count(page, 0);
>> - break;
>> -
>> - case MEMORY_DEVICE_GENERIC:
>> - break;
>> - }
>> }
>>
>> /*
>> @@ -1090,7 +1108,7 @@ static void __ref memmap_init_compound(struct page *head,
>> for (pfn = head_pfn + 1; pfn < end_pfn; pfn++) {
>> struct page *page = pfn_to_page(pfn);
>>
>> - __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
>> + zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
>> prep_compound_tail(page, head, order);
>> set_page_count(page, 0);
>> }
>> @@ -1126,7 +1144,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
>> for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
>> struct page *page = pfn_to_page(pfn);
>>
>> - __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
>> + zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
> It looks like all callers of __init_zone_device_page() are now calling
> zone_device_page_init_slow() that with series evolution became a thin
> wrapper for __zone_device_page_init().
>
> Maybe we can just rename __init_zone_device_page() to
> __zone_device_page_init() and replace the open coded setting of the page
> count with the call to pagemap_requires_refcount_reset() an be done
> here?
Yes, that would make patch 2 simpler.
The reason I kept the refcount policy outside __zone_device_page_init()
is the compound-tail initialization added later in patch 5. Tail pages
still need the same basic ZONE_DEVICE struct page initialization as head
pages, including __init_single_page(), PageReserved, pgmap and
zone_device_data setup. After that, the tail-page path calls
prep_compound_tail() and sets the tail refcount to 0 unconditionally.
Folding pagemap_requires_refcount_reset() into __zone_device_page_init()
would make that helper include the head-page refcount policy even for
callers that will immediately apply the tail-page refcount rule
afterwards.
That preserves the final state, but I was trying to keep the common
ZONE_DEVICE page setup separate from the head-page refcount policy for
this reason.
Would you still prefer the simpler renamed helper here, or should I keep
the common ZONE_DEVICE page setup separate from the refcount policy and
make the naming clearer in v11?
Thanks,
Zhe
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v10 4/8] mm: add a template-based fast path for zone-device page init
2026-08-16 12:04 ` Mike Rapoport
@ 2026-08-17 7:14 ` Li Zhe
0 siblings, 0 replies; 16+ messages in thread
From: Li Zhe @ 2026-08-17 7:14 UTC (permalink / raw)
To: Mike Rapoport
Cc: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, tglx, linux-arch, linux-hardening, linux-kernel,
linux-mm, x86
On 8/16/26 8:04 PM, Mike Rapoport wrote:
> Hi,
>
>> memmap_init_zone_device() repeats nearly identical head-page
>> initialization for each PFN. Prepare one reusable ZONE_DEVICE head-page
>> template through the existing slow path, refresh the PFN-dependent
>> fields in that template before each copy, and memcpy it into each
>> destination page.
>>
>> Use the template path unconditionally. The page_ref_set tracepoint is
>> primarily a debugging aid, while this code is still initializing struct
>> pages before they are handed out. From the perspective of users of those
>> pages, the initialization-time refcount transitions are not part of the
>> observable page lifetime.
>>
>> This means page_ref_set will no longer observe every initialization-time
>> refcount assignment for copied ZONE_DEVICE head pages. The impact is
>> controlled because the final initialized struct page state is unchanged,
>> and keeping a separate non-template path only for this local tracepoint
>> observability would add complexity to the common path.
>>
>> This patch accelerates head-page initialization. The pfns_per_compound
>> == 1 case gets the full benefit here, compound tails are handled in the
>> next patch.
>>
>> Tested in a VM with a 100 GB fsdax namespace device configured with
>> map=dev on Intel Ice Lake server. This test exercises the nd_pmem rebind
>> path (pfns_per_compound == 1).
>>
>> Test procedure:
>> Rebind the nd_pmem driver 30 times and collect the memmap initialization
>> time from the pr_debug() output of memmap_init_zone_device().
>>
>> Base(v7.2-rc1):
>> Average of rebinds for nd_pmem driver: 244.28 ms
>>
>> With this patch and its prerequisites applied:
>> Average of rebinds for nd_pmem driver: 215.55 ms
>>
>> This reduces the average memmap initialization time measured during rebind
>> from 244.28 ms to 215.55 ms, or about 11%.
>>
>> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
>>
>> diff --git a/mm/mm_init.c b/mm/mm_init.c
>> index a70acb7431a6..56a36a71ba89 100644
>> --- a/mm/mm_init.c
>> +++ b/mm/mm_init.c
>> @@ -1065,6 +1065,35 @@ static void __ref zone_device_page_init_slow(struct page *page,
>> set_page_count(page, 0);
>> }
>>
>> +/*
>> + * 'template' is a reusable page prototype rather than a strictly immutable
>> + * object. Most ZONE_DEVICE fields stay constant across the pages covered by
>> + * the current template, but section bits and page->virtual may still depend
>> + * on the PFN. Refresh those PFN-dependent fields in the template before
>> + * copying it into @page.
>> + */
>> +static inline void zone_device_page_update_template(struct page *template,
>> + unsigned long pfn)
>> +{
>> + set_page_section_from_pfn(template, pfn);
>> +#ifdef WANT_PAGE_VIRTUAL
>> + if (!is_highmem_idx(ZONE_DEVICE))
>> + set_page_address(template, __va(pfn << PAGE_SHIFT));
>> +#endif
>> +}
>> +
>> +static void zone_device_page_init_from_template(struct page *page,
>> + unsigned long pfn, struct page *template)
>> +{
>> + /*
>> + * 'template' carries the invariant portion of a ZONE_DEVICE struct
>> + * page. Update the PFN-dependent fields in place before copying it
>> + * to the destination page.
>> + */
>> + zone_device_page_update_template(template, pfn);
> Looks like it's the only user of zone_device_page_update_template().
> I'd just fold it here and drop the comment.
Thanks. I will fold zone_device_page_update_template() into
zone_device_page_init_from_template() and drop the redundant comment.
>
>> + memcpy(page, template, sizeof(*page));
>> +}
>> +
>> /*
>> * With compound page geometry and when struct pages are stored in ram most
>> * tail pages are reused. Consequently, the amount of unique struct pages to
>> @@ -1127,6 +1156,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
>> unsigned long zone_idx = zone_idx(zone);
>> unsigned long start = jiffies;
>> int nid = pgdat->node_id;
>> + struct page template;
>>
>> if (WARN_ON_ONCE(!pgmap || zone_idx != ZONE_DEVICE))
>> return;
>> @@ -1144,7 +1174,22 @@ void __ref memmap_init_zone_device(struct zone *zone,
>> for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
>> struct page *page = pfn_to_page(pfn);
>>
>> - zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
>> + if (pfn == start_pfn) {
>> + /*
>> + * Seed the reusable head-page template from the
>> + * first real struct page. This initializes the
>> + * first page through the existing slow path and
>> + * then reuses that final state as the template
>> + * for subsequent pages.
>> + */
>> + zone_device_page_init_slow(page, pfn, zone_idx,
>> + nid, pgmap);
>> + /* init template page */
>> + memcpy(&template, page, sizeof(*page));
>> + } else {
>> + zone_device_page_init_from_template(page, pfn,
>> + &template);
>> + }
> Why can't we init the template for the first page being initialized and
> then call zone_device_page_init_from_template() unconditionally?
>
> The same applies to the tail pages initialization.
The concern is that this would initialize a stack-resident struct page
template through the normal page-init and refcount helpers.
An earlier version did initialize the template directly:
https://lore.kernel.org/all/20260527033636.28231-5-lizhe.67@bytedance.com/
but Sashiko pointed out that this is unsafe:
https://sashiko.dev/#/patchset/20260527033636.28231-1-lizhe.67@bytedance.com
For example, set_page_count() may call into the page_ref_set tracepoint
path when the tracepoint is enabled. That path can use helpers such as
page_to_pfn(), which only make sense for real memmap pages. Passing a
stack-resident struct page template there can produce a bogus PFN and may
lead to a kernel panic.
That is why the current version seeds the reusable template from the
first real struct page instead of running the normal page-init helpers on
the stack template.
I also kept the first iteration inside the main loop because Alistair
previously suggested avoiding an unrolled first-page path:
https://lore.kernel.org/all/akxpS3WOP7oUBNKF@nvdebian.thelocal/
The tail-page path follows the same pattern for the same reason.
Thanks,
Zhe
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v10 2/8] mm: factor zone-device page init helpers out of __init_zone_device_page
2026-08-17 7:11 ` Li Zhe
@ 2026-08-17 11:15 ` Mike Rapoport
0 siblings, 0 replies; 16+ messages in thread
From: Mike Rapoport @ 2026-08-17 11:15 UTC (permalink / raw)
To: Li Zhe
Cc: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
muchun.song, tglx, linux-arch, linux-hardening, linux-kernel,
linux-mm, x86
On Mon, Aug 17, 2026 at 03:11:56PM +0800, Li Zhe wrote:
> On 8/16/26 8:04 PM, Mike Rapoport wrote:
> > Hi,
> >
> >> memmap_init_zone_device() currently mixes refcount policy and core
> >> ZONE_DEVICE page setup in a single helper.
> >>
> >> Factor the refcount-reset predicate into pagemap_requires_refcount_reset(),
> >> move the common page initialization into __zone_device_page_init(), and
> >> wrap the existing slow path in zone_device_page_init_slow().
> >>
> >> This keeps the slow-path behaviour unchanged and gives later patches
> >> reusable helper boundaries.
> >>
> >> No functional change intended.
> >>
> >> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
> >> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> >>
> >> diff --git a/mm/mm_init.c b/mm/mm_init.c
> >> index 95808ab5cfdb..a70acb7431a6 100644
> >> --- a/mm/mm_init.c
> >> +++ b/mm/mm_init.c
> >> @@ -1005,11 +1005,37 @@ static void __init memmap_init(void)
> >> }
> >>
> >> #ifdef CONFIG_ZONE_DEVICE
> >> -static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
> >> +/*
> >> + * Return true when memmap_init_zone_device() must initialize the page
> >> + * refcount to 0. MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in
> >> + * the free path, while the remaining ZONE_DEVICE types start from 0 here
> >> + * and raise the count again when the allocator or driver hands the page
> >> + * out.
> >> + */
> >> +static inline bool pagemap_requires_refcount_reset(const struct dev_pagemap *pgmap)
> >> +{
> >> + /*
> >> + * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
> >> + * path. The remaining ZONE_DEVICE types start from 0 here and raise
> >> + * the count again when the allocator or driver hands the page out.
> >> + */
> >> + switch (pgmap->type) {
> >> + case MEMORY_DEVICE_FS_DAX:
> >> + case MEMORY_DEVICE_PRIVATE:
> >> + case MEMORY_DEVICE_COHERENT:
> >> + case MEMORY_DEVICE_PCI_P2PDMA:
> >> + return true;
> >> + case MEMORY_DEVICE_GENERIC:
> >> + return false;
> >> + }
> >> +
> >> + return false;
> >> +}
> >> +
> >> +static void __ref __zone_device_page_init(struct page *page, unsigned long pfn,
> >> unsigned long zone_idx, int nid,
> >> struct dev_pagemap *pgmap)
> >> {
> >> -
> >> __init_single_page(page, pfn, zone_idx, nid);
> >>
> >> /*
> >> @@ -1028,23 +1054,15 @@ static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
> >> */
> >> page_folio(page)->pgmap = pgmap;
> >> page->zone_device_data = NULL;
> >> +}
> >>
> >> - /*
> >> - * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
> >> - * path. The remaining ZONE_DEVICE types start from 0 here and raise
> >> - * the count again when the allocator or driver hands the page out.
> >> - */
> >> - switch (pgmap->type) {
> >> - case MEMORY_DEVICE_FS_DAX:
> >> - case MEMORY_DEVICE_PRIVATE:
> >> - case MEMORY_DEVICE_COHERENT:
> >> - case MEMORY_DEVICE_PCI_P2PDMA:
> >> +static void __ref zone_device_page_init_slow(struct page *page,
> >> + unsigned long pfn, unsigned long zone_idx, int nid,
> >> + struct dev_pagemap *pgmap)
> >> +{
> >> + __zone_device_page_init(page, pfn, zone_idx, nid, pgmap);
> >> + if (pagemap_requires_refcount_reset(pgmap))
> >> set_page_count(page, 0);
> >> - break;
> >> -
> >> - case MEMORY_DEVICE_GENERIC:
> >> - break;
> >> - }
> >> }
> >>
> >> /*
> >> @@ -1090,7 +1108,7 @@ static void __ref memmap_init_compound(struct page *head,
> >> for (pfn = head_pfn + 1; pfn < end_pfn; pfn++) {
> >> struct page *page = pfn_to_page(pfn);
> >>
> >> - __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
> >> + zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
> >> prep_compound_tail(page, head, order);
> >> set_page_count(page, 0);
> >> }
> >> @@ -1126,7 +1144,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
> >> for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
> >> struct page *page = pfn_to_page(pfn);
> >>
> >> - __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
> >> + zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
> > It looks like all callers of __init_zone_device_page() are now calling
> > zone_device_page_init_slow() that with series evolution became a thin
> > wrapper for __zone_device_page_init().
> >
> > Maybe we can just rename __init_zone_device_page() to
> > __zone_device_page_init() and replace the open coded setting of the page
> > count with the call to pagemap_requires_refcount_reset() an be done
> > here?
>
>
> Yes, that would make patch 2 simpler.
>
> The reason I kept the refcount policy outside __zone_device_page_init()
> is the compound-tail initialization added later in patch 5. Tail pages
> still need the same basic ZONE_DEVICE struct page initialization as head
> pages, including __init_single_page(), PageReserved, pgmap and
> zone_device_data setup. After that, the tail-page path calls
> prep_compound_tail() and sets the tail refcount to 0 unconditionally.
>
> Folding pagemap_requires_refcount_reset() into __zone_device_page_init()
> would make that helper include the head-page refcount policy even for
> callers that will immediately apply the tail-page refcount rule
> afterwards.
But you still call zone_device_page_init_slow() from
zone_device_tail_page_init() so it's anyway there.
I'd suggest combining this and the next patch into one, keeping
__init_zone_device_page() name without renaming it at all and open-coding
the initialization of the first head and the first tail page to properly
set refcount and call other prep methods.
I'd also pull the initialization of the first page in both cases out of the
loop and make the loop only use the _template version.
> That preserves the final state, but I was trying to keep the common
> ZONE_DEVICE page setup separate from the head-page refcount policy for
> this reason.
>
> Would you still prefer the simpler renamed helper here, or should I keep
> the common ZONE_DEVICE page setup separate from the refcount policy and
> make the naming clearer in v11?
>
> Thanks,
> Zhe
>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-08-17 11:16 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 12:20 [PATCH v10 0/8] mm: optimize zone-device memmap initialization Li Zhe
2026-08-10 12:20 ` [PATCH v10 1/8] mm: fix stale ZONE_DEVICE refcount comment Li Zhe
2026-08-16 12:04 ` Mike Rapoport
2026-08-10 12:20 ` [PATCH v10 2/8] mm: factor zone-device page init helpers out of __init_zone_device_page Li Zhe
2026-08-16 12:04 ` Mike Rapoport
2026-08-17 7:11 ` Li Zhe
2026-08-17 11:15 ` Mike Rapoport
2026-08-10 12:20 ` [PATCH v10 3/8] mm: add a set_page_section_from_pfn() helper Li Zhe
2026-08-10 12:20 ` [PATCH v10 4/8] mm: add a template-based fast path for zone-device page init Li Zhe
2026-08-16 12:04 ` Mike Rapoport
2026-08-17 7:14 ` Li Zhe
2026-08-10 12:20 ` [PATCH v10 5/8] mm: extend the template fast path to zone-device compound tails Li Zhe
2026-08-10 12:20 ` [PATCH v10 6/8] string: introduce memcpy_nontemporal() Li Zhe
2026-08-10 12:20 ` [PATCH v10 7/8] mm: use memcpy_nontemporal() in zone-device template copies Li Zhe
2026-08-10 12:20 ` [PATCH v10 8/8] x86/string: extend memcpy_flushcache() fixed-size fastpaths Li Zhe
2026-08-11 19:46 ` Borislav Petkov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox