* [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization
@ 2026-09-08 3:03 Muchun Song
2026-09-08 3:03 ` [PATCH v2 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
` (11 more replies)
0 siblings, 12 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
This series is split out from the earlier, larger series "mm: Generalize
HVO for HugeTLB and device DAX" [1]. While the parent series generalizes
vmemmap optimization across HugeTLB and device DAX, this subset addresses
a single, self-contained step: switching device DAX to the section-based
sparse-vmemmap optimization infrastructure introduced for HugeTLB.
After the HugeTLB conversion, optimized vmemmap state is described by
the memory section and the sparse-vmemmap population path can allocate or
reuse shared tail vmemmap pages based on that metadata. Device DAX still
uses the older DAX-specific population model, including a separate tail
vmemmap page reservation and architecture-specific logic to locate or
populate reusable tail pages.
This series makes device DAX use the same section-based model. Device DAX
sets the section order from pgmap->vmemmap_shift before vmemmap
population, uses the common per-zone shared tail vmemmap page, and drops
the extra reserved tail page. The powerpc radix path is updated to use
the same shared tail-page helper, so the generic and powerpc DAX paths
follow the same reservation model.
The first patches prepare the shared infrastructure by introducing a
generic CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION symbol, factoring out
shared tail-page allocation, and keeping the special shared-tail struct
page initialization local to sparse-vmemmap.
The middle patches move device DAX onto that infrastructure by recording
the device DAX compound page order in the memory section, using that
section metadata to back generic device DAX mappings with the common
per-zone shared tail page, exposing the shared helpers so the powerpc
radix path can use the same model, and dropping the extra DAX-only tail
page reservation and the now-unused section accounting arguments.
The final patch updates the documentation for the new DAX layout.
This is intended to be the third smaller step toward the broader HVO
generalization. The wider HVO consolidation between HugeTLB and device
DAX is left for follow-up series.
v2:
- Add a missing SPARSEMEM_VMEMMAP dependency (suggested by Qi Zheng,
reported by Sashiko)
- Add an explicit ZONE_DEVICE dependency for DEV_DAX
- Add missing dependencies to the new public header
- Explain why optimized and ordinary layouts cannot share a section
(suggested by Qi Zheng)
- Explain why sharing tail vmemmap pages is safe for DEV-DAX (suggested
by Qi Zheng)
- Clarify the removal of duplicated 4K PUD calculations from the docs
(reported by Sashiko)
- Collect Acked-by tags from Qi Zheng
v1: https://lore.kernel.org/all/20260831075342.57563-1-songmuchun@bytedance.com/
[1] https://lore.kernel.org/all/20260513130542.35604-1-songmuchun@bytedance.com/
Muchun Song (11):
mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
mm/sparse-vmemmap: factor out shared vmemmap tail page allocation
mm/sparse-vmemmap: open-code init_compound_tail()
mm/sparse-vmemmap: prepare DAX vmemmap population for section orders
mm/sparse-vmemmap: set section order for device DAX
mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
mm/sparse-vmemmap: move HVO helpers to a public header
powerpc/mm: switch device DAX to shared tail vmemmap pages
mm/sparse-vmemmap: drop the extra tail page from device DAX
reservation
mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments
Documentation/mm: update DAX vmemmap deduplication docs
Documentation/arch/powerpc/vmemmap_dedup.rst | 90 ++-------
Documentation/mm/vmemmap_dedup.rst | 32 +--
MAINTAINERS | 1 +
arch/powerpc/mm/book3s64/radix_pgtable.c | 124 +-----------
arch/x86/entry/vdso/vdso32/fake_32bit_build.h | 2 +-
drivers/dax/Kconfig | 2 +
fs/Kconfig | 1 +
include/linux/mm.h | 6 +-
include/linux/mmzone.h | 23 ++-
include/linux/page-flags.h | 5 +-
include/linux/vmemmap-optimization.h | 91 +++++++++
mm/Kconfig | 4 +
mm/hugetlb.c | 2 +-
mm/hugetlb_vmemmap.c | 30 +--
mm/internal.h | 9 -
mm/memory_hotplug.c | 6 +-
mm/mm_init.c | 17 +-
mm/sparse-vmemmap.c | 184 ++++++++----------
mm/sparse.c | 3 +-
mm/sparse.h | 78 +-------
20 files changed, 250 insertions(+), 460 deletions(-)
create mode 100644 include/linux/vmemmap-optimization.h
base-commit: 9d3243fc689fef444f87e0a703b4c99653137e1b
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 3:03 ` [PATCH v2 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
` (10 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The section-based vmemmap optimization infrastructure is still guarded by
CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP, but it also can be used by device
DAX. Introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION as a common config
for the shared infrastructure.
Select the new option from HUGETLB_PAGE_OPTIMIZE_VMEMMAP and from
DEV_DAX when the architecture opts in to DAX vmemmap optimization, and
use it to guard the generic sparse-vmemmap state and helpers.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v2:
- Fix SPARSEMEM_VMEMMAP_OPTIMIZATION being selected without SPARSEMEM_VMEMMAP
reported by Sashiko.
- Add an explicit DEV_DAX dependency on ZONE_DEVICE
- Collect Acked-by from Qi Zheng
---
arch/x86/entry/vdso/vdso32/fake_32bit_build.h | 2 +-
drivers/dax/Kconfig | 2 ++
fs/Kconfig | 1 +
include/linux/mm.h | 3 +++
include/linux/mmzone.h | 13 +++++++------
include/linux/page-flags.h | 5 ++---
mm/Kconfig | 4 ++++
mm/sparse.h | 4 ++--
8 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/arch/x86/entry/vdso/vdso32/fake_32bit_build.h b/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
index bc3e549795c3..5f8424eade2b 100644
--- a/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
+++ b/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
@@ -11,7 +11,7 @@
#undef CONFIG_PGTABLE_LEVELS
#undef CONFIG_ILLEGAL_POINTER_VALUE
#undef CONFIG_SPARSEMEM_VMEMMAP
-#undef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#undef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
#undef CONFIG_NR_CPUS
#undef CONFIG_PARAVIRT_XXL
diff --git a/drivers/dax/Kconfig b/drivers/dax/Kconfig
index 602f9a0839a9..6250954b0fa7 100644
--- a/drivers/dax/Kconfig
+++ b/drivers/dax/Kconfig
@@ -8,6 +8,8 @@ if DAX
config DEV_DAX
tristate "Device DAX: direct access mapping device"
depends on TRANSPARENT_HUGEPAGE
+ depends on ZONE_DEVICE
+ select SPARSEMEM_VMEMMAP_OPTIMIZATION if ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
help
Support raw access to differentiated (persistence, bandwidth,
latency...) memory via an mmap(2) capable character
diff --git a/fs/Kconfig b/fs/Kconfig
index d1c210c6508f..9b32ce79cc80 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -278,6 +278,7 @@ config HUGETLB_PAGE_OPTIMIZE_VMEMMAP
def_bool HUGETLB_PAGE
depends on ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP
depends on SPARSEMEM_VMEMMAP
+ select SPARSEMEM_VMEMMAP_OPTIMIZATION
config HUGETLB_PMD_PAGE_TABLE_SHARING
def_bool HUGETLB_PAGE
diff --git a/include/linux/mm.h b/include/linux/mm.h
index c49ef99b4413..a2ebe87e7654 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5175,6 +5175,9 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
unsigned long nr_pages;
unsigned long nr_vmemmap_pages;
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
+ return false;
+
if (!pgmap || !is_power_of_2(sizeof(struct page)))
return false;
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index c9ae7991a8b2..e9b54ea0eff0 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -102,9 +102,9 @@
*
* HVO which is only active if the size of struct page is a power of 2.
*/
-#define MAX_FOLIO_VMEMMAP_ALIGN \
- (IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) && \
- is_power_of_2(sizeof(struct page)) ? \
+#define MAX_FOLIO_VMEMMAP_ALIGN \
+ (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION) && \
+ is_power_of_2(sizeof(struct page)) ? \
MAX_FOLIO_NR_PAGES * sizeof(struct page) : 0)
/* The number of retained vmemmap pages with HVO enabled. */
@@ -116,7 +116,8 @@
#define __VMEMMAP_OPTIMIZATION_NR_ORDERS \
(MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
#define VMEMMAP_OPTIMIZATION_NR_ORDERS \
- (__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
+ ((__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 && \
+ IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION)) ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
enum migratetype {
MIGRATE_UNMOVABLE,
@@ -1155,7 +1156,7 @@ struct zone {
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
#endif
} ____cacheline_internodealigned_in_smp;
@@ -2019,7 +2020,7 @@ struct mem_section {
unsigned long section_mem_map;
struct mem_section_usage *usage;
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
/*
* Normally, sections hold regular (order-0) pages. However, for
* sections with HVO enabled, this tracks the compound page order
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 86dd0470da11..462e89e05548 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -208,14 +208,13 @@ enum pageflags {
static __always_inline bool compound_info_has_mask(void)
{
/*
- * Limit mask usage to HugeTLB vmemmap optimization (HVO) where it
- * makes a difference.
+ * Limit mask usage to HVO where it makes a difference.
*
* The approach with mask would work in the wider set of conditions,
* but it requires validating that struct pages are naturally aligned
* for all orders up to the MAX_FOLIO_ORDER, which can be tricky.
*/
- if (!IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP))
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
return false;
return is_power_of_2(sizeof(struct page));
diff --git a/mm/Kconfig b/mm/Kconfig
index 79163b7d795a..288b32dcc350 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -461,6 +461,10 @@ config SPARSEMEM_VMEMMAP
pfn_to_page and page_to_pfn operations. This is the most
efficient option when sufficient kernel resources are available.
+config SPARSEMEM_VMEMMAP_OPTIMIZATION
+ bool
+ depends on SPARSEMEM_VMEMMAP
+
#
# Select this config option from the architecture Kconfig, if it is preferred
# to enable the feature of HugeTLB/dev_dax vmemmap optimization.
diff --git a/mm/sparse.h b/mm/sparse.h
index 049272aba84e..b408d15baf7b 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -10,7 +10,7 @@
#include <linux/mmzone.h>
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
static inline unsigned int section_order(const struct mem_section *section)
{
return section->order;
@@ -72,7 +72,7 @@ static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
static inline bool vmemmap_optimizable_order(unsigned int order)
{
- if (!IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP))
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
return false;
if (!is_power_of_2(sizeof(struct page)))
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-09-08 3:03 ` [PATCH v2 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 3:03 ` [PATCH v2 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
` (9 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
HugeTLB and sparse-vmemmap each have their own helper to allocate the
shared vmemmap tail page used by vmemmap optimization.
Factor that logic into a common vmemmap_shared_tail_page() helper. It
allocates the page through vmemmap_alloc_block(), and uses cmpxchg()
to install the per-zone shared page.
Expose zone->vmemmap_tails under CONFIG_SPARSEMEM_VMEMMAP to match the
shared helper's build condition. This avoids a
!CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION stub; when optimization is
disabled, the array has no entries and the compiler folds away the unused
paths, so no storage or runtime overhead is added.
This removes duplicate allocation logic while still handling both the
early boot and runtime paths through the same helper.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v2:
- Collect Acked-by from Qi Zheng
---
include/linux/mmzone.h | 2 +-
mm/hugetlb_vmemmap.c | 28 +---------------
mm/sparse-vmemmap.c | 74 +++++++++++++++++-------------------------
mm/sparse.h | 1 +
4 files changed, 33 insertions(+), 72 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index e9b54ea0eff0..d3778ba976a5 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1156,7 +1156,7 @@ struct zone {
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
#endif
} ____cacheline_internodealigned_in_smp;
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index eb339c4a71f4..4a57e6c3352c 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -493,32 +493,6 @@ static bool vmemmap_should_optimize_folio(const struct hstate *h, struct folio *
return true;
}
-static struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
-{
- const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- struct page *tail, *p;
- int node = zone_to_nid(zone);
-
- tail = READ_ONCE(zone->vmemmap_tails[idx]);
- if (likely(tail))
- return tail;
-
- tail = alloc_pages_node(node, GFP_KERNEL | __GFP_ZERO, 0);
- if (!tail)
- return NULL;
-
- p = page_to_virt(tail);
- for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++)
- init_compound_tail(p + i, NULL, order, zone);
-
- if (cmpxchg(&zone->vmemmap_tails[idx], NULL, tail)) {
- __free_page(tail);
- tail = READ_ONCE(zone->vmemmap_tails[idx]);
- }
-
- return tail;
-}
-
static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
struct folio *folio,
struct list_head *vmemmap_pages,
@@ -535,7 +509,7 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
return ret;
nid = folio_nid(folio);
- vmemmap_tail = vmemmap_get_tail(h->order, folio_zone(folio));
+ vmemmap_tail = vmemmap_shared_tail_page(h->order, folio_zone(folio));
if (!vmemmap_tail)
return -ENOMEM;
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index e62e6aa07f12..70143dd8b579 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -42,27 +42,13 @@
#include "mm_init.h"
#include "sparse.h"
-/*
- * Allocate a block of memory to be used to back the virtual memory map
- * or to back the page tables that are used to create the mapping.
- * Uses the main allocators if they are available, else bootmem.
- */
-
-static void * __ref __earlyonly_bootmem_alloc(int node,
- unsigned long size,
- unsigned long align,
- unsigned long goal)
-{
- return memmap_alloc(size, align, goal, node, false);
-}
-
-void * __meminit vmemmap_alloc_block(unsigned long size, int node)
+void __ref *vmemmap_alloc_block(unsigned long size, int node)
{
/* If the main allocator is up use that, fallback to bootmem. */
if (slab_is_available()) {
gfp_t gfp_mask = GFP_KERNEL|__GFP_RETRY_MAYFAIL|__GFP_NOWARN;
int order = get_order(size);
- static bool warned __meminitdata;
+ static bool warned;
struct page *page;
page = alloc_pages_node(node, gfp_mask, order);
@@ -76,8 +62,7 @@ void * __meminit vmemmap_alloc_block(unsigned long size, int node)
}
return NULL;
} else
- return __earlyonly_bootmem_alloc(node, size, size,
- __pa(MAX_DMA_ADDRESS));
+ return memmap_alloc(size, size, __pa(MAX_DMA_ADDRESS), node, false);
}
static void * __meminit altmap_alloc_block_buf(unsigned long size,
@@ -184,39 +169,40 @@ static void * __meminit vmemmap_alloc_block_zero(unsigned long size, int node)
return p;
}
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
-static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
+struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zone)
{
- struct page *p, *tail;
- unsigned int idx;
- int node = zone_to_nid(zone);
+ void *addr;
+ struct page *page;
+ const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- if (WARN_ON_ONCE(order < VMEMMAP_OPTIMIZATION_MIN_ORDER))
- return NULL;
- if (WARN_ON_ONCE(order > MAX_FOLIO_ORDER))
+ if (WARN_ON_ONCE(idx >= ARRAY_SIZE(zone->vmemmap_tails)))
return NULL;
- idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- tail = zone->vmemmap_tails[idx];
- if (tail)
- return tail;
- p = vmemmap_alloc_block_zero(PAGE_SIZE, node);
- if (!p)
+ page = READ_ONCE(zone->vmemmap_tails[idx]);
+ if (likely(page))
+ return page;
+
+ addr = vmemmap_alloc_block(PAGE_SIZE, zone_to_nid(zone));
+ if (!addr)
return NULL;
- for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++)
- init_compound_tail(p + i, NULL, order, zone);
- tail = virt_to_page(p);
- zone->vmemmap_tails[idx] = tail;
+ for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++) {
+ page = (struct page *)addr + i;
+ mm_zero_struct_page(page);
+ init_compound_tail(page, NULL, order, zone);
+ }
- return tail;
-}
-#else
-static inline struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
-{
- return NULL;
+ page = virt_to_page(addr);
+ if (cmpxchg(&zone->vmemmap_tails[idx], NULL, page) != NULL) {
+ if (slab_is_available())
+ __free_page(page);
+ else
+ memblock_free(addr, PAGE_SIZE);
+ page = READ_ONCE(zone->vmemmap_tails[idx]);
+ }
+
+ return page;
}
-#endif
static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
struct vmem_altmap *altmap)
@@ -229,7 +215,7 @@ static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
return vmemmap_alloc_block_buf(PAGE_SIZE, node, altmap);
zone = pfn_to_zone(pfn, node);
- page = vmemmap_get_tail(order, zone);
+ page = vmemmap_shared_tail_page(order, zone);
if (!page)
return NULL;
diff --git a/mm/sparse.h b/mm/sparse.h
index b408d15baf7b..59b825df83b9 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -139,6 +139,7 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
+struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
void sparse_init_subsection_map(void);
int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 03/11] mm/sparse-vmemmap: open-code init_compound_tail()
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-09-08 3:03 ` [PATCH v2 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
2026-09-08 3:03 ` [PATCH v2 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-10 7:50 ` David Hildenbrand (Arm)
2026-09-08 3:03 ` [PATCH v2 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders Muchun Song
` (8 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
init_compound_tail() is only used by vmemmap_shared_tail_page(), where
the shared tail page setup intentionally passes NULL as the compound head.
Keeping this helper in mm/internal.h exposes that special case to the rest
of the MM code and can make the NULL head argument look generally valid.
Open-code the initialization at the only call site so the special-case use
stays local to sparse vmemmap optimization.
No functional change intended.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v2:
- Collect Acked-by from Qi Zheng
---
mm/internal.h | 9 ---------
mm/sparse-vmemmap.c | 5 ++++-
2 files changed, 4 insertions(+), 10 deletions(-)
diff --git a/mm/internal.h b/mm/internal.h
index da14c56fb24e..0dca33db068f 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -786,15 +786,6 @@ static inline void prep_compound_tail(struct page *tail,
VM_WARN_ON_ONCE(tail->private);
}
-static inline void init_compound_tail(struct page *tail,
- const struct page *head, unsigned int order, struct zone *zone)
-{
- atomic_set(&tail->_mapcount, -1);
- set_page_node(tail, zone_to_nid(zone));
- set_page_zone(tail, zone_idx(zone));
- prep_compound_tail(tail, head, order);
-}
-
#if defined CONFIG_COMPACTION || defined CONFIG_CMA
/*
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 70143dd8b579..e453ce4675a0 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -189,7 +189,10 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++) {
page = (struct page *)addr + i;
mm_zero_struct_page(page);
- init_compound_tail(page, NULL, order, zone);
+ atomic_set(&page->_mapcount, -1);
+ set_page_node(page, zone_to_nid(zone));
+ set_page_zone(page, zone_idx(zone));
+ prep_compound_tail(page, NULL, order);
}
page = virt_to_page(addr);
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (2 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 3:03 ` [PATCH v2 05/11] mm/sparse-vmemmap: set section order for device DAX Muchun Song
` (7 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
Device DAX still uses vmemmap_populate_compound_pages() to populate its
compound-page vmemmap mappings. That helper allocates the head and first
tail vmemmap pages explicitly, then reuses the first tail page for the
remaining tail page mappings.
Device DAX is being moved to the section-based vmemmap optimization
infrastructure, but it cannot switch to the generic section-based
population path yet. Once a later patch records the DAX compound-page
geometry in the section order, DAX head and first-tail PFNs can look
optimizable to the generic helpers as well.
Add a DAX-specific population flag for this transition. It keeps DAX
head/first-tail allocations on the normal vmemmap allocation path, while
preserving the existing page reference for reused DAX tail mappings.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v2:
- Collect Acked-by from Qi Zheng
---
mm/sparse-vmemmap.c | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index e453ce4675a0..54ae8c284324 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -35,8 +35,8 @@
/*
* Flags for vmemmap_populate_range and friends.
*/
-/* Get a ref on the head page struct page, for ZONE_DEVICE compound pages */
-#define VMEMMAP_POPULATE_PAGEREF 0x0001
+/* Vmemmap population for ZONE_DEVICE compound pages */
+#define VMEMMAP_POPULATE_DAX 0x0001
#include "internal.h"
#include "mm_init.h"
@@ -208,13 +208,17 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
}
static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
- struct vmem_altmap *altmap)
+ struct vmem_altmap *altmap, unsigned long flags)
{
struct zone *zone;
struct page *page;
const unsigned int order = pfn_to_section_order(pfn);
- if (!vmemmap_optimizable_pfn(pfn))
+ /*
+ * Device DAX still relies on vmemmap_populate_compound_pages() for
+ * head/first-tail allocation and tail-page reuse.
+ */
+ if (!vmemmap_optimizable_pfn(pfn) || flags & VMEMMAP_POPULATE_DAX)
return vmemmap_alloc_block_buf(PAGE_SIZE, node, altmap);
zone = pfn_to_zone(pfn, node);
@@ -236,7 +240,7 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
pte_t entry;
if (ptpfn == (unsigned long)-1) {
- void *p = vmemmap_alloc_pte(pfn, node, altmap);
+ void *p = vmemmap_alloc_pte(pfn, node, altmap, flags);
if (!p)
return NULL;
@@ -251,7 +255,7 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
* and through vmemmap_populate_compound_pages() when
* slab is available.
*/
- if (flags & VMEMMAP_POPULATE_PAGEREF)
+ if (flags & VMEMMAP_POPULATE_DAX)
get_page(pfn_to_page(ptpfn));
}
entry = pfn_pte(ptpfn, PAGE_KERNEL);
@@ -511,6 +515,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long size, addr;
pte_t *pte;
int rc;
+ unsigned long flags = VMEMMAP_POPULATE_DAX;
if (reuse_compound_section(start_pfn, pgmap)) {
pte = compound_section_tail_page(start);
@@ -522,8 +527,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
* with just tail struct pages.
*/
return vmemmap_populate_range(start, end, node, NULL,
- pte_pfn(ptep_get(pte)),
- VMEMMAP_POPULATE_PAGEREF);
+ pte_pfn(ptep_get(pte)), flags);
}
size = min(end - start, pgmap_vmemmap_nr(pgmap) * sizeof(struct page));
@@ -531,13 +535,13 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long next, last = addr + size;
/* Populate the head page vmemmap page */
- pte = vmemmap_populate_address(addr, node, NULL, -1, 0);
+ pte = vmemmap_populate_address(addr, node, NULL, -1, flags);
if (!pte)
return -ENOMEM;
/* Populate the tail pages vmemmap page */
next = addr + PAGE_SIZE;
- pte = vmemmap_populate_address(next, node, NULL, -1, 0);
+ pte = vmemmap_populate_address(next, node, NULL, -1, flags);
if (!pte)
return -ENOMEM;
@@ -547,8 +551,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
*/
next += PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
- pte_pfn(ptep_get(pte)),
- VMEMMAP_POPULATE_PAGEREF);
+ pte_pfn(ptep_get(pte)), flags);
if (rc)
return -ENOMEM;
}
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 05/11] mm/sparse-vmemmap: set section order for device DAX
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (3 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 3:03 ` [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
` (6 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
Device DAX can use vmemmap optimization only when a full section is
populated with a compound-page geometry. Record that geometry in the
section order before populating the section, so later vmemmap accounting
and population decisions can use the section state directly.
Clear the section order when the section becomes empty again. Also reject
partial additions to a section that already has optimized vmemmap mappings.
compound_nr_pages() determines how many struct pages to initialize with a
section as the smallest granularity. A section therefore cannot safely mix
optimized and ordinary vmemmap layouts.
Partial additions continue to use ordinary vmemmap population, so they do
not save vmemmap memory. Such additions are uncommon, and the lost saving
is negligible.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v2:
- Explain why optimized and ordinary layouts cannot share a section
(suggested by Qi Zheng)
- Collect Acked-by from Qi Zheng
---
mm/mm_init.c | 15 +++++----------
mm/sparse-vmemmap.c | 16 ++++++++++++----
2 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 9e8ffd01b4f7..7a2e58d631c2 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1049,16 +1049,11 @@ static void zone_device_page_init_from_template(struct page *page,
* of an altmap. See vmemmap_populate_compound_pages().
*/
static inline unsigned long compound_nr_pages(unsigned long pfn,
- struct vmem_altmap *altmap,
struct dev_pagemap *pgmap)
{
- /*
- * If DAX memory is hot-plugged into an unoccupied subsection
- * of an early section, the unoptimized boot memmap is reused.
- * See section_activate().
- */
- if (early_section(__pfn_to_section(pfn)) ||
- !vmemmap_can_optimize(altmap, pgmap))
+ const struct mem_section *ms = __pfn_to_section(pfn);
+
+ if (!section_vmemmap_optimizable(ms))
return pgmap_vmemmap_nr(pgmap);
return VMEMMAP_RESERVE_NR * (PAGE_SIZE / sizeof(struct page));
@@ -1144,7 +1139,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
memcpy(&template, page, sizeof(*page));
if (pfns_per_compound != 1)
memmap_init_compound(page, pfn, zone_idx, nid, pgmap,
- compound_nr_pages(pfn, altmap, pgmap));
+ compound_nr_pages(pfn, pgmap));
pfn += pfns_per_compound;
/* Initialize the remaining head pages from template. */
@@ -1160,7 +1155,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
continue;
memmap_init_compound(page, pfn, zone_idx, nid, pgmap,
- compound_nr_pages(pfn, altmap, pgmap));
+ compound_nr_pages(pfn, pgmap));
}
pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE,
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 54ae8c284324..aed1e7429daa 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -135,14 +135,14 @@ int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages
struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
{
const struct mem_section *ms = __pfn_to_section(pfn);
- const int order = pgmap ? pgmap->vmemmap_shift : section_order(ms);
+ const int order = section_order(ms);
const int vmemmap_pages = pgmap ? VMEMMAP_RESERVE_NR : VMEMMAP_OPTIMIZATION_PAGES;
const unsigned long pages_per_compound = 1UL << order;
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
VM_WARN_ON_ONCE(nr_pages > PAGES_PER_SECTION);
- if (!vmemmap_can_optimize(altmap, pgmap) && !section_vmemmap_optimizable(ms))
+ if (!section_vmemmap_optimizable(ms))
return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
if (order < PFN_SECTION_SHIFT) {
@@ -573,7 +573,7 @@ struct page * __meminit __populate_section_memmap(unsigned long pfn,
!IS_ALIGNED(nr_pages, PAGES_PER_SUBSECTION)))
return NULL;
- if (vmemmap_can_optimize(altmap, pgmap))
+ if (pgmap && section_vmemmap_optimizable(__pfn_to_section(pfn)))
r = vmemmap_populate_compound_pages(pfn, start, end, nid, pgmap);
else
r = vmemmap_populate(start, end, nid, altmap);
@@ -792,8 +792,10 @@ static void section_deactivate(unsigned long pfn, unsigned long nr_pages,
else if (memmap)
free_map_bootmem(memmap);
- if (empty)
+ if (empty) {
ms->section_mem_map = (unsigned long)NULL;
+ section_set_order(ms, 0);
+ }
}
static struct page * __meminit section_activate(int nid, unsigned long pfn,
@@ -803,8 +805,13 @@ static struct page * __meminit section_activate(int nid, unsigned long pfn,
struct mem_section *ms = __pfn_to_section(pfn);
struct mem_section_usage *usage = NULL;
struct page *memmap;
+ unsigned int order;
int rc;
+ order = vmemmap_can_optimize(altmap, pgmap) ? pgmap->vmemmap_shift : 0;
+ if (nr_pages < PAGES_PER_SECTION && section_order(ms))
+ return ERR_PTR(-ENOTSUPP);
+
if (!ms->usage) {
usage = kzalloc(mem_section_usage_size(), GFP_KERNEL);
if (!usage)
@@ -830,6 +837,7 @@ static struct page * __meminit section_activate(int nid, unsigned long pfn,
if (nr_pages < PAGES_PER_SECTION && early_section(ms))
return pfn_to_page(pfn);
+ section_set_order_range(pfn, nr_pages, order);
memmap = populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
if (!memmap) {
section_deactivate(pfn, nr_pages, altmap, pgmap);
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (4 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 05/11] mm/sparse-vmemmap: set section order for device DAX Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 4:09 ` Muchun Song
2026-09-08 7:46 ` Qi Zheng
2026-09-08 3:03 ` [PATCH v2 07/11] mm/sparse-vmemmap: move HVO helpers to a public header Muchun Song
` (5 subsequent siblings)
11 siblings, 2 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
HugeTLB vmemmap optimization now uses per-zone shared tail vmemmap pages.
Device DAX has not been switched to that mechanism yet.
Switch device DAX to vmemmap_shared_tail_page() as well. This aligns DAX
with HugeTLB by using the common per-zone shared tail vmemmap page.
The optimization is enabled only for DEV-DAX through pgmap->vmemmap_shift,
which is assigned when a DEV-DAX device is initialized. Unlike FS-DAX,
DEV-DAX does not modify tail struct pages, so sharing them is safe.
Since the shared tail page can now back ZONE_DEVICE vmemmap mappings,
initialize its entries with PG_reserved for device zones. Also skip
poisoning vmemmap-optimizable sections while their struct pages may be
shared.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
v2:
- Explain why sharing tail vmemmap pages is safe for DEV-DAX
(suggested by Qi Zheng)
---
include/linux/mmzone.h | 10 +++++++++
mm/memory_hotplug.c | 5 +++--
mm/sparse-vmemmap.c | 47 ++++++++++++++----------------------------
3 files changed, 28 insertions(+), 34 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index d3778ba976a5..03ed9232f186 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1686,11 +1686,21 @@ static inline bool zone_is_zone_device(const struct zone *zone)
{
return zone_idx(zone) == ZONE_DEVICE;
}
+
+static inline struct zone *device_zone(int nid)
+{
+ return &NODE_DATA(nid)->node_zones[ZONE_DEVICE];
+}
#else
static inline bool zone_is_zone_device(const struct zone *zone)
{
return false;
}
+
+static inline struct zone *device_zone(int nid)
+{
+ return NULL;
+}
#endif
/*
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index b428da66d279..0db0379826df 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -554,8 +554,9 @@ void remove_pfn_range_from_zone(struct zone *zone,
/* Select all remaining pages up to the next section boundary */
cur_nr_pages =
min(end_pfn - pfn, SECTION_ALIGN_UP(pfn + 1) - pfn);
- page_init_poison(pfn_to_page(pfn),
- sizeof(struct page) * cur_nr_pages);
+ if (!section_vmemmap_optimizable(__pfn_to_section(pfn)))
+ page_init_poison(pfn_to_page(pfn),
+ sizeof(struct page) * cur_nr_pages);
}
/*
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index aed1e7429daa..0201877a7f80 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -193,6 +193,8 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
set_page_node(page, zone_to_nid(zone));
set_page_zone(page, zone_idx(zone));
prep_compound_tail(page, NULL, order);
+ if (zone_is_zone_device(zone))
+ __SetPageReserved(page);
}
page = virt_to_page(addr);
@@ -490,23 +492,6 @@ static bool __meminit reuse_compound_section(unsigned long start_pfn,
return !IS_ALIGNED(offset, nr_pages) && nr_pages > PAGES_PER_SUBSECTION;
}
-static pte_t * __meminit compound_section_tail_page(unsigned long addr)
-{
- pte_t *pte;
-
- addr -= PAGE_SIZE;
-
- /*
- * Assuming sections are populated sequentially, the previous section's
- * page data can be reused.
- */
- pte = pte_offset_kernel(pmd_off_k(addr), addr);
- if (!pte)
- return NULL;
-
- return pte;
-}
-
static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long start,
unsigned long end, int node,
@@ -516,21 +501,18 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
pte_t *pte;
int rc;
unsigned long flags = VMEMMAP_POPULATE_DAX;
+ struct page *page;
+ unsigned int order = pfn_to_section_order(start_pfn);
- if (reuse_compound_section(start_pfn, pgmap)) {
- pte = compound_section_tail_page(start);
- if (!pte)
- return -ENOMEM;
+ page = vmemmap_shared_tail_page(order, device_zone(node));
+ if (!page)
+ return -ENOMEM;
- /*
- * Reuse the page that was populated in the prior iteration
- * with just tail struct pages.
- */
+ if (reuse_compound_section(start_pfn, pgmap))
return vmemmap_populate_range(start, end, node, NULL,
- pte_pfn(ptep_get(pte)), flags);
- }
+ page_to_pfn(page), flags);
- size = min(end - start, pgmap_vmemmap_nr(pgmap) * sizeof(struct page));
+ size = min(end - start, (1UL << order) * sizeof(struct page));
for (addr = start; addr < end; addr += size) {
unsigned long next, last = addr + size;
@@ -546,12 +528,12 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
return -ENOMEM;
/*
- * Reuse the previous page for the rest of tail pages
+ * Reuse the shared page for the rest of tail pages
* See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
next += PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
- pte_pfn(ptep_get(pte)), flags);
+ page_to_pfn(page), flags);
if (rc)
return -ENOMEM;
}
@@ -883,13 +865,14 @@ int __meminit sparse_add_section(int nid, unsigned long start_pfn,
if (IS_ERR(memmap))
return PTR_ERR(memmap);
+ ms = __nr_to_section(section_nr);
/*
* Poison uninitialized struct pages in order to catch invalid flags
* combinations.
*/
- page_init_poison(memmap, sizeof(struct page) * nr_pages);
+ if (!section_vmemmap_optimizable(ms))
+ page_init_poison(memmap, sizeof(struct page) * nr_pages);
- ms = __nr_to_section(section_nr);
__section_mark_present(ms, section_nr);
/* Align memmap to section boundary in the subsection case */
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 07/11] mm/sparse-vmemmap: move HVO helpers to a public header
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (5 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 8:19 ` Qi Zheng
2026-09-08 3:03 ` [PATCH v2 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
` (4 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The vmemmap optimization helpers currently live in mm/sparse.h,
which is an internal MM header. That works for MM code, but
prevents powerpc from using the same interfaces without including a
private header.
Move the declarations and inline helpers to
include/linux/vmemmap-optimization.h. This is a preparatory change for
powerpc, which has its own vmemmap optimization implementation and
needs to use the HVO interfaces from architecture code.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
v2:
- Fix missing header dependencies.
---
MAINTAINERS | 1 +
include/linux/vmemmap-optimization.h | 91 ++++++++++++++++++++++++++++
mm/hugetlb.c | 2 +-
mm/hugetlb_vmemmap.c | 2 +-
mm/memory_hotplug.c | 1 +
mm/sparse.h | 73 +---------------------
6 files changed, 96 insertions(+), 74 deletions(-)
create mode 100644 include/linux/vmemmap-optimization.h
diff --git a/MAINTAINERS b/MAINTAINERS
index c7aaa2e4790e..2985286cbbbf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12096,6 +12096,7 @@ F: Documentation/mm/hugetlbfs_reserv.rst
F: Documentation/mm/vmemmap_dedup.rst
F: fs/hugetlbfs/
F: include/linux/hugetlb.h
+F: include/linux/vmemmap-optimization.h
F: include/trace/events/hugetlbfs.h
F: mm/hugetlb.c
F: mm/hugetlb_cgroup.c
diff --git a/include/linux/vmemmap-optimization.h b/include/linux/vmemmap-optimization.h
new file mode 100644
index 000000000000..492dc662aae7
--- /dev/null
+++ b/include/linux/vmemmap-optimization.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * vmemmap-optimization.h
+ *
+ * Generic vmemmap optimization declarations.
+ *
+ * Author: Muchun Song <songmuchun@bytedance.com>
+ */
+#ifndef _LINUX_VMEMMAP_OPTIMIZATION_H
+#define _LINUX_VMEMMAP_OPTIMIZATION_H
+
+#include <linux/align.h>
+#include <linux/log2.h>
+#include <linux/mmdebug.h>
+#include <linux/mmzone.h>
+
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
+static inline unsigned int section_order(const struct mem_section *section)
+{
+ return section->order;
+}
+
+static inline void section_set_order(struct mem_section *section, unsigned int order)
+{
+ VM_WARN_ON(section_order(section) && order && section_order(section) != order);
+ section->order = order;
+}
+
+static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
+ unsigned int order)
+{
+ unsigned long section_nr = pfn_to_section_nr(pfn);
+
+ if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
+ return;
+
+ for (unsigned long i = 0; i < nr_pages / PAGES_PER_SECTION; i++)
+ section_set_order(__nr_to_section(section_nr + i), order);
+}
+
+static inline unsigned int pfn_to_section_order(unsigned long pfn)
+{
+ return section_order(__pfn_to_section(pfn));
+}
+#else
+static inline unsigned int section_order(const struct mem_section *section)
+{
+ return 0;
+}
+
+static inline void section_set_order(struct mem_section *section, unsigned int order)
+{
+}
+
+static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
+ unsigned int order)
+{
+}
+
+static inline unsigned int pfn_to_section_order(unsigned long pfn)
+{
+ return 0;
+}
+#endif /* CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION */
+
+static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
+{
+ const unsigned int order = pfn_to_section_order(pfn);
+ const unsigned long nr_pages = 1UL << order;
+
+ if (!is_power_of_2(sizeof(struct page)))
+ return false;
+
+ return (pfn & (nr_pages - 1)) >= VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES;
+}
+
+static inline bool vmemmap_optimizable_order(unsigned int order)
+{
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
+ return false;
+
+ if (!is_power_of_2(sizeof(struct page)))
+ return false;
+
+ return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
+}
+
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
+#endif /* CONFIG_SPARSEMEM_VMEMMAP */
+#endif /* _LINUX_VMEMMAP_OPTIMIZATION_H */
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index fda525bccf50..dff915e6c48b 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -38,6 +38,7 @@
#include <linux/mm_inline.h>
#include <linux/padata.h>
#include <linux/pgalloc.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/page.h>
#include <asm/tlb.h>
@@ -52,7 +53,6 @@
#include "hugetlb_cma.h"
#include "hugetlb_internal.h"
#include "mm_init.h"
-#include "sparse.h"
#include <linux/page-isolation.h>
#define HUGE_BOOTMEM_ZONES_VALID BIT(0)
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 4a57e6c3352c..25c4e7d2664c 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -15,10 +15,10 @@
#include <linux/mmdebug.h>
#include <linux/pagewalk.h>
#include <linux/pgalloc.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/tlbflush.h>
#include "hugetlb_vmemmap.h"
-#include "sparse.h"
#include "internal.h"
/**
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 0db0379826df..d7a59167bec4 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -43,6 +43,7 @@
#include "mm_init.h"
#include "page_alloc.h"
#include "shuffle.h"
+#include "sparse.h"
enum {
MEMMAP_ON_MEMORY_DISABLE = 0,
diff --git a/mm/sparse.h b/mm/sparse.h
index 59b825df83b9..e511d99fc26b 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -9,77 +9,7 @@
#define __MM_SPARSE_H
#include <linux/mmzone.h>
-
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
-static inline unsigned int section_order(const struct mem_section *section)
-{
- return section->order;
-}
-
-static inline void section_set_order(struct mem_section *section, unsigned int order)
-{
- VM_WARN_ON(section_order(section) && order && section_order(section) != order);
- section->order = order;
-}
-
-static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
- unsigned int order)
-{
- unsigned long section_nr = pfn_to_section_nr(pfn);
-
- if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
- return;
-
- for (unsigned long i = 0; i < nr_pages / PAGES_PER_SECTION; i++)
- section_set_order(__nr_to_section(section_nr + i), order);
-}
-
-static inline unsigned int pfn_to_section_order(unsigned long pfn)
-{
- return section_order(__pfn_to_section(pfn));
-}
-#else
-static inline unsigned int section_order(const struct mem_section *section)
-{
- return 0;
-}
-
-static inline void section_set_order(struct mem_section *section, unsigned int order)
-{
-}
-
-static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
- unsigned int order)
-{
-}
-
-static inline unsigned int pfn_to_section_order(unsigned long pfn)
-{
- return 0;
-}
-#endif
-
-static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
-{
- const unsigned int order = pfn_to_section_order(pfn);
- const unsigned long nr_pages = 1UL << order;
-
- if (!is_power_of_2(sizeof(struct page)))
- return false;
-
- return (pfn & (nr_pages - 1)) >= VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES;
-}
-
-static inline bool vmemmap_optimizable_order(unsigned int order)
-{
- if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
- return false;
-
- if (!is_power_of_2(sizeof(struct page)))
- return false;
-
- return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
-}
+#include <linux/vmemmap-optimization.h>
/*
* mm/sparse.c
@@ -139,7 +69,6 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
-struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
void sparse_init_subsection_map(void);
int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (6 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 07/11] mm/sparse-vmemmap: move HVO helpers to a public header Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 3:03 ` [PATCH v2 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
` (3 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The powerpc radix compound vmemmap population path still finds a reusable
tail page by walking the vmemmap page tables.
Switch it to the common vmemmap_shared_tail_page() helper instead, so it
can use the shared vmemmap page directly to simplify the code.
This removes the powerpc-specific tail-page lookup and its fallback path
and aligns the device DAX vmemmap optimization path with HugeTLB.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 80 +++---------------------
1 file changed, 9 insertions(+), 71 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index cf692b2b5f7b..831c231a4a18 100644
--- a/arch/powerpc/mm/book3s64/radix_pgtable.c
+++ b/arch/powerpc/mm/book3s64/radix_pgtable.c
@@ -19,6 +19,7 @@
#include <linux/string_helpers.h>
#include <linux/memory.h>
#include <linux/kfence.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/pgalloc.h>
#include <asm/mmu_context.h>
@@ -1250,59 +1251,6 @@ static pte_t * __meminit radix__vmemmap_populate_address(unsigned long addr, int
return pte;
}
-static pte_t * __meminit vmemmap_compound_tail_page(unsigned long addr,
- unsigned long pfn_offset, int node)
-{
- pgd_t *pgd;
- p4d_t *p4d;
- pud_t *pud;
- pmd_t *pmd;
- pte_t *pte;
- unsigned long map_addr;
-
- /* the second vmemmap page which we use for duplication */
- map_addr = addr - pfn_offset * sizeof(struct page) + PAGE_SIZE;
- pgd = pgd_offset_k(map_addr);
- p4d = p4d_offset(pgd, map_addr);
- pud = vmemmap_pud_alloc(p4d, node, map_addr);
- if (!pud)
- return NULL;
- pmd = vmemmap_pmd_alloc(pud, node, map_addr);
- if (!pmd)
- return NULL;
- if (pmd_leaf(*pmd))
- /*
- * The second page is mapped as a hugepage due to a nearby request.
- * Force our mapping to page size without deduplication
- */
- return NULL;
- pte = vmemmap_pte_alloc(pmd, node, map_addr);
- if (!pte)
- return NULL;
- /*
- * Check if there exist a mapping to the left
- */
- if (pte_none(*pte)) {
- /*
- * Populate the head page vmemmap page.
- * It can fall in different pmd, hence
- * vmemmap_populate_address()
- */
- pte = radix__vmemmap_populate_address(map_addr - PAGE_SIZE, node, NULL, NULL);
- if (!pte)
- return NULL;
- /*
- * Populate the tail pages vmemmap page
- */
- pte = radix__vmemmap_pte_populate(pmd, map_addr, node, NULL, NULL);
- if (!pte)
- return NULL;
- vmemmap_verify(pte, node, map_addr, map_addr + PAGE_SIZE);
- return pte;
- }
- return pte;
-}
-
int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long start,
unsigned long end, int node,
@@ -1320,6 +1268,12 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
+ struct page *tail_page;
+ unsigned int order = pfn_to_section_order(start_pfn);
+
+ tail_page = vmemmap_shared_tail_page(order, device_zone(node));
+ if (!tail_page)
+ return -ENOMEM;
for (addr = start; addr < end; addr = next) {
@@ -1349,10 +1303,9 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
next = addr + PAGE_SIZE;
continue;
} else {
- unsigned long nr_pages = pgmap_vmemmap_nr(pgmap);
+ unsigned long nr_pages = 1UL << order;
unsigned long addr_pfn = page_to_pfn((struct page *)addr);
unsigned long pfn_offset = addr_pfn - ALIGN_DOWN(addr_pfn, nr_pages);
- pte_t *tail_page_pte;
/*
* if the address is aligned to huge page size it is the
@@ -1377,23 +1330,8 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
next = addr + 2 * PAGE_SIZE;
continue;
}
- /*
- * get the 2nd mapping details
- * Also create it if that doesn't exist
- */
- tail_page_pte = vmemmap_compound_tail_page(addr, pfn_offset, node);
- if (!tail_page_pte) {
-
- pte = radix__vmemmap_pte_populate(pmd, addr, node, NULL, NULL);
- if (!pte)
- return -ENOMEM;
- vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
-
- next = addr + PAGE_SIZE;
- continue;
- }
- pte = radix__vmemmap_pte_populate(pmd, addr, node, NULL, pte_page(*tail_page_pte));
+ pte = radix__vmemmap_pte_populate(pmd, addr, node, NULL, tail_page);
if (!pte)
return -ENOMEM;
vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (7 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 9:09 ` Qi Zheng
2026-09-08 3:03 ` [PATCH v2 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
` (2 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The device DAX vmemmap population still reserves one extra tail vmemmap
page after the head page.
Drop that extra reservation and let the shared tail page cover all tail
vmemmap pages after the head page, so DAX follows the same reservation
model as HugeTLB.
This reduces the reserved vmemmap pages for optimized DAX mappings to
one and removes the now-unneeded first-tail population from the generic
and powerpc paths to simplify the code as well.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 46 ++----------------------
include/linux/mm.h | 3 +-
mm/mm_init.c | 2 +-
mm/sparse-vmemmap.c | 13 ++-----
4 files changed, 7 insertions(+), 57 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index 831c231a4a18..e7e751c48dd2 100644
--- a/arch/powerpc/mm/book3s64/radix_pgtable.c
+++ b/arch/powerpc/mm/book3s64/radix_pgtable.c
@@ -1218,39 +1218,6 @@ int __meminit radix__vmemmap_populate(unsigned long start, unsigned long end, in
return 0;
}
-static pte_t * __meminit radix__vmemmap_populate_address(unsigned long addr, int node,
- struct vmem_altmap *altmap,
- struct page *reuse)
-{
- pgd_t *pgd;
- p4d_t *p4d;
- pud_t *pud;
- pmd_t *pmd;
- pte_t *pte;
-
- pgd = pgd_offset_k(addr);
- p4d = p4d_offset(pgd, addr);
- pud = vmemmap_pud_alloc(p4d, node, addr);
- if (!pud)
- return NULL;
- pmd = vmemmap_pmd_alloc(pud, node, addr);
- if (!pmd)
- return NULL;
- if (pmd_leaf(*pmd))
- /*
- * The second page is mapped as a hugepage due to a nearby request.
- * Force our mapping to page size without deduplication
- */
- return NULL;
- pte = vmemmap_pte_alloc(pmd, node, addr);
- if (!pte)
- return NULL;
- radix__vmemmap_pte_populate(pmd, addr, node, NULL, NULL);
- vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
-
- return pte;
-}
-
int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long start,
unsigned long end, int node,
@@ -1297,7 +1264,7 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
if (!pte_none(*pte)) {
/*
* This could be because we already have a compound
- * page whose VMEMMAP_RESERVE_NR pages were mapped and
+ * page whose retained vmemmap page was mapped and
* this request fall in those pages.
*/
next = addr + PAGE_SIZE;
@@ -1318,16 +1285,7 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
return -ENOMEM;
vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
- /*
- * Populate the tail pages vmemmap page
- * It can fall in different pmd, hence
- * vmemmap_populate_address()
- */
- pte = radix__vmemmap_populate_address(addr + PAGE_SIZE, node, NULL, NULL);
- if (!pte)
- return -ENOMEM;
-
- next = addr + 2 * PAGE_SIZE;
+ next = addr + PAGE_SIZE;
continue;
}
diff --git a/include/linux/mm.h b/include/linux/mm.h
index a2ebe87e7654..969594074fd2 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5167,7 +5167,6 @@ static inline void vmem_altmap_free(struct vmem_altmap *altmap,
}
#endif
-#define VMEMMAP_RESERVE_NR 2
#ifdef CONFIG_ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
struct dev_pagemap *pgmap)
@@ -5187,7 +5186,7 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
* For vmemmap optimization with DAX we need minimum 2 vmemmap
* pages. See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
- return !altmap && (nr_vmemmap_pages > VMEMMAP_RESERVE_NR);
+ return !altmap && (nr_vmemmap_pages > VMEMMAP_OPTIMIZATION_PAGES);
}
/*
* If we don't have an architecture override, use the generic rule
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 7a2e58d631c2..629420a83891 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1056,7 +1056,7 @@ static inline unsigned long compound_nr_pages(unsigned long pfn,
if (!section_vmemmap_optimizable(ms))
return pgmap_vmemmap_nr(pgmap);
- return VMEMMAP_RESERVE_NR * (PAGE_SIZE / sizeof(struct page));
+ return VMEMMAP_OPTIMIZATION_PAGES * (PAGE_SIZE / sizeof(struct page));
}
static void __ref memmap_init_compound(struct page *head,
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 0201877a7f80..e655d9d1348f 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -136,7 +136,6 @@ int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages
{
const struct mem_section *ms = __pfn_to_section(pfn);
const int order = section_order(ms);
- const int vmemmap_pages = pgmap ? VMEMMAP_RESERVE_NR : VMEMMAP_OPTIMIZATION_PAGES;
const unsigned long pages_per_compound = 1UL << order;
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
@@ -147,13 +146,13 @@ int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages
if (order < PFN_SECTION_SHIFT) {
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
- return vmemmap_pages * nr_pages / pages_per_compound;
+ return VMEMMAP_OPTIMIZATION_PAGES * nr_pages / pages_per_compound;
}
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
if (IS_ALIGNED(pfn, pages_per_compound))
- return vmemmap_pages;
+ return VMEMMAP_OPTIMIZATION_PAGES;
return 0;
}
@@ -521,17 +520,11 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
if (!pte)
return -ENOMEM;
- /* Populate the tail pages vmemmap page */
- next = addr + PAGE_SIZE;
- pte = vmemmap_populate_address(next, node, NULL, -1, flags);
- if (!pte)
- return -ENOMEM;
-
/*
* Reuse the shared page for the rest of tail pages
* See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
- next += PAGE_SIZE;
+ next = addr + PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
page_to_pfn(page), flags);
if (rc)
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (8 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 9:23 ` Qi Zheng
2026-09-08 3:03 ` [PATCH v2 11/11] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
2026-09-09 1:45 ` [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Andrew Morton
11 siblings, 1 reply; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
section_nr_vmemmap_pages() no longer uses the altmap or pgmap
arguments, so drop them from the helper and its callers.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
mm/sparse-vmemmap.c | 10 ++++------
mm/sparse.c | 3 +--
mm/sparse.h | 6 ++----
3 files changed, 7 insertions(+), 12 deletions(-)
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index e655d9d1348f..4950ac2a1c01 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -131,8 +131,7 @@ void __meminit vmemmap_verify(pte_t *pte, int node,
start, end - 1);
}
-int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
- struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages)
{
const struct mem_section *ms = __pfn_to_section(pfn);
const int order = section_order(ms);
@@ -637,7 +636,7 @@ static struct page * __meminit populate_section_memmap(unsigned long pfn,
struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
pgmap);
- memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+ memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages));
return page;
}
@@ -648,7 +647,7 @@ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
unsigned long start = (unsigned long) pfn_to_page(pfn);
unsigned long end = start + nr_pages * sizeof(struct page);
- memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+ memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages));
vmemmap_free(start, end, altmap);
}
@@ -658,8 +657,7 @@ static void free_map_bootmem(struct page *memmap)
unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
unsigned long pfn = page_to_pfn(memmap);
- memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
- NULL, NULL));
+ memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION));
vmemmap_free(start, end, NULL);
}
diff --git a/mm/sparse.c b/mm/sparse.c
index 9349ed6326c0..adf057f54c0f 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -251,8 +251,7 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
nid, NULL, NULL);
if (!map)
panic("Failed to allocate memmap for section %lu\n", pnum);
- memmap_boot_pages_add(section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
- NULL, NULL));
+ memmap_boot_pages_add(section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION));
sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
SECTION_IS_EARLY);
usage = (void *)usage + mem_section_usage_size();
diff --git a/mm/sparse.h b/mm/sparse.h
index e511d99fc26b..5d0f407bde7e 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -70,12 +70,10 @@ static inline void sparse_sections_init(void) {}
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
void sparse_init_subsection_map(void);
-int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
- struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
+int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages);
#else
static inline void sparse_init_subsection_map(void) {}
-static inline int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
- struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+static inline int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages)
{
return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
}
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 11/11] Documentation/mm: update DAX vmemmap deduplication docs
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (9 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
@ 2026-09-08 3:03 ` Muchun Song
2026-09-08 9:29 ` Qi Zheng
2026-09-09 1:45 ` [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Andrew Morton
11 siblings, 1 reply; 23+ messages in thread
From: Muchun Song @ 2026-09-08 3:03 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
Device DAX now uses the common per-zone shared tail page for vmemmap
deduplication. The old documentation still described a DAX-specific
layout with a separately populated tail vmemmap page and half the HugeTLB
savings.
Update the generic and powerpc documentation to describe the shared layout.
In the powerpc document, keep the radix and 64K-specific details, drop the
duplicated 4K PUD arithmetic, and replace the repeated device-dax diagrams
with a single parameterized PMD/PUD diagram.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
v2:
- Clarify the commit message to state that the 4K PUD arithmetic is
intentionally dropped reported by Sashiko.
---
Documentation/arch/powerpc/vmemmap_dedup.rst | 90 ++++----------------
Documentation/mm/vmemmap_dedup.rst | 32 +------
2 files changed, 21 insertions(+), 101 deletions(-)
diff --git a/Documentation/arch/powerpc/vmemmap_dedup.rst b/Documentation/arch/powerpc/vmemmap_dedup.rst
index dc4db59fdf87..8286acbca9bc 100644
--- a/Documentation/arch/powerpc/vmemmap_dedup.rst
+++ b/Documentation/arch/powerpc/vmemmap_dedup.rst
@@ -19,82 +19,28 @@ With 1G PUD level mapping, we require 16384 struct pages and a single 64K
vmemmap page can contain 1024 struct pages (64K/sizeof(struct page)). Hence we
require 16 64K pages in vmemmap to map the struct page for 1G PUD level mapping.
-Here's how things look like on device-dax after the sections are populated::
- +-----------+ ---virt_to_page---> +-----------+ mapping to +-----------+
- | | | 0 | -------------> | 0 |
- | | +-----------+ +-----------+
- | | | 1 | -------------> | 1 |
- | | +-----------+ +-----------+
- | | | 2 | ----------------^ ^ ^ ^ ^ ^
- | | +-----------+ | | | | |
- | | | 3 | ------------------+ | | | |
- | | +-----------+ | | | |
- | | | 4 | --------------------+ | | |
- | PUD | +-----------+ | | |
- | level | | . | ----------------------+ | |
- | mapping | +-----------+ | |
- | | | . | ------------------------+ |
- | | +-----------+ |
- | | | 15 | --------------------------+
- | | +-----------+
- | |
- | |
- | |
- +-----------+
-
-
With 4K page size, 2M PMD level mapping requires 512 struct pages and a single
4K vmemmap page contains 64 struct pages(4K/sizeof(struct page)). Hence we
require 8 4K pages in vmemmap to map the struct page for 2M pmd level mapping.
-Here's how things look like on device-dax after the sections are populated::
-
- +-----------+ ---virt_to_page---> +-----------+ mapping to +-----------+
- | | | 0 | -------------> | 0 |
- | | +-----------+ +-----------+
- | | | 1 | -------------> | 1 |
- | | +-----------+ +-----------+
- | | | 2 | ----------------^ ^ ^ ^ ^ ^
- | | +-----------+ | | | | |
- | | | 3 | ------------------+ | | | |
- | | +-----------+ | | | |
- | | | 4 | --------------------+ | | |
- | PMD | +-----------+ | | |
- | level | | 5 | ----------------------+ | |
- | mapping | +-----------+ | |
- | | | 6 | ------------------------+ |
- | | +-----------+ |
- | | | 7 | --------------------------+
- | | +-----------+
- | |
- | |
- | |
- +-----------+
-
-With 1G PUD level mapping, we require 262144 struct pages and a single 4K
-vmemmap page can contain 64 struct pages (4K/sizeof(struct page)). Hence we
-require 4096 4K pages in vmemmap to map the struct pages for 1G PUD level
-mapping.
-
-Here's how things look like on device-dax after the sections are populated::
-
- +-----------+ ---virt_to_page---> +-----------+ mapping to +-----------+
- | | | 0 | -------------> | 0 |
- | | +-----------+ +-----------+
- | | | 1 | -------------> | 1 |
- | | +-----------+ +-----------+
- | | | 2 | ----------------^ ^ ^ ^ ^ ^
- | | +-----------+ | | | | |
- | | | 3 | ------------------+ | | | |
- | | +-----------+ | | | |
- | | | 4 | --------------------+ | | |
- | PUD | +-----------+ | | |
- | level | | . | ----------------------+ | |
- | mapping | +-----------+ | |
- | | | . | ------------------------+ |
- | | +-----------+ |
- | | | 4095 | --------------------------+
- | | +-----------+
+Here's how things look on device-dax after vmemmap-optimized sections are
+populated. ``N`` is the number of vmemmap pages required by the DAX mapping
+above::
+
+ Device DAX vmemmap pages (N pages) backing page frames
+ +-----------+ ---virt_to_page---> +-----------+ mapping to +-------------+
+ | | | 0 | -------------> | 0 |
+ | | +-----------+ +-------------+
+ | | | 1 | ------+
+ | | +-----------+ |
+ | | | 2 | ------+
+ | | +-----------+ |
+ | | | . | ------+ +-------------+
+ | PMD/PUD | +-----------+ | | A single, |
+ | level | | . | ------+------> | per-zone |
+ | mapping | +-----------+ | | shared tail |
+ | | | N - 1 | ------+ | page |
+ | | +-----------+ +-------------+
| |
| |
| |
diff --git a/Documentation/mm/vmemmap_dedup.rst b/Documentation/mm/vmemmap_dedup.rst
index 9fa8642ded48..8c287ae3f86c 100644
--- a/Documentation/mm/vmemmap_dedup.rst
+++ b/Documentation/mm/vmemmap_dedup.rst
@@ -1,4 +1,3 @@
-
.. SPDX-License-Identifier: GPL-2.0
=========================================
@@ -192,32 +191,7 @@ to 4 on HugeTLB pages.
There's no remapping of vmemmap given that device-dax memory is not part of
System RAM ranges initialized at boot. Thus the tail page deduplication
-happens at a later stage when we populate the sections. HugeTLB reuses the
-the head vmemmap page representing, whereas device-dax reuses the tail
-vmemmap page. This results in only half of the savings compared to HugeTLB.
-
-Deduplicated tail pages are not mapped read-only.
+happens at a later stage when we populate the sections.
-Here's how things look like on device-dax after the sections are populated::
-
- +-----------+ ---virt_to_page---> +-----------+ mapping to +-----------+
- | | | 0 | -------------> | 0 |
- | | +-----------+ +-----------+
- | | | 1 | -------------> | 1 |
- | | +-----------+ +-----------+
- | | | 2 | ----------------^ ^ ^ ^ ^ ^
- | | +-----------+ | | | | |
- | | | 3 | ------------------+ | | | |
- | | +-----------+ | | | |
- | | | 4 | --------------------+ | | |
- | PMD | +-----------+ | | |
- | level | | 5 | ----------------------+ | |
- | mapping | +-----------+ | |
- | | | 6 | ------------------------+ |
- | | +-----------+ |
- | | | 7 | --------------------------+
- | | +-----------+
- | |
- | |
- | |
- +-----------+
+Deduplicated tail pages are not mapped read-only. The mapping layout is the same
+as HugeTLB.
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
2026-09-08 3:03 ` [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-08 4:09 ` Muchun Song
2026-09-08 7:46 ` Qi Zheng
1 sibling, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 4:09 UTC (permalink / raw)
To: Muchun Song
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Lorenzo Stoakes,
Mike Rapoport, Qi Zheng, Nicholas Piggin, Christophe Leroy,
Randy Dunlap, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
On 2026/9/8 11:03, Muchun Song wrote:
> HugeTLB vmemmap optimization now uses per-zone shared tail vmemmap pages.
> Device DAX has not been switched to that mechanism yet.
>
> Switch device DAX to vmemmap_shared_tail_page() as well. This aligns DAX
> with HugeTLB by using the common per-zone shared tail vmemmap page.
>
> The optimization is enabled only for DEV-DAX through pgmap->vmemmap_shift,
> which is assigned when a DEV-DAX device is initialized. Unlike FS-DAX,
> DEV-DAX does not modify tail struct pages, so sharing them is safe.
>
> Since the shared tail page can now back ZONE_DEVICE vmemmap mappings,
> initialize its entries with PG_reserved for device zones. Also skip
> poisoning vmemmap-optimizable sections while their struct pages may be
> shared.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> v2:
> - Explain why sharing tail vmemmap pages is safe for DEV-DAX
> (suggested by Qi Zheng)
> ---
> include/linux/mmzone.h | 10 +++++++++
> mm/memory_hotplug.c | 5 +++--
> mm/sparse-vmemmap.c | 47 ++++++++++++++----------------------------
> 3 files changed, 28 insertions(+), 34 deletions(-)
>
> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
> index d3778ba976a5..03ed9232f186 100644
> --- a/include/linux/mmzone.h
> +++ b/include/linux/mmzone.h
> @@ -1686,11 +1686,21 @@ static inline bool zone_is_zone_device(const struct zone *zone)
> {
> return zone_idx(zone) == ZONE_DEVICE;
> }
> +
> +static inline struct zone *device_zone(int nid)
> +{
> + return &NODE_DATA(nid)->node_zones[ZONE_DEVICE];
> +}
> #else
> static inline bool zone_is_zone_device(const struct zone *zone)
> {
> return false;
> }
> +
> +static inline struct zone *device_zone(int nid)
> +{
> + return NULL;
> +}
> #endif
>
> /*
> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
> index b428da66d279..0db0379826df 100644
> --- a/mm/memory_hotplug.c
> +++ b/mm/memory_hotplug.c
> @@ -554,8 +554,9 @@ void remove_pfn_range_from_zone(struct zone *zone,
> /* Select all remaining pages up to the next section boundary */
> cur_nr_pages =
> min(end_pfn - pfn, SECTION_ALIGN_UP(pfn + 1) - pfn);
> - page_init_poison(pfn_to_page(pfn),
> - sizeof(struct page) * cur_nr_pages);
> + if (!section_vmemmap_optimizable(__pfn_to_section(pfn)))
> + page_init_poison(pfn_to_page(pfn),
> + sizeof(struct page) * cur_nr_pages);
> }
>
> /*
> diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
> index aed1e7429daa..0201877a7f80 100644
> --- a/mm/sparse-vmemmap.c
> +++ b/mm/sparse-vmemmap.c
> @@ -193,6 +193,8 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
> set_page_node(page, zone_to_nid(zone));
> set_page_zone(page, zone_idx(zone));
> prep_compound_tail(page, NULL, order);
> + if (zone_is_zone_device(zone))
> + __SetPageReserved(page);
Sashiko said: "
Could setting PG_reserved here cause a use-after-free
during device hot-remove?
When a DEV-DAX device is removed and vmemmap_free is called,
remove_pte_table calls free_vmemmap_pages. In free_vmemmap_pages,
the PG_reserved flag redirects execution:
arch/x86/mm/init_64.c:free_vmemmap_pages() {
...
else if (PageReserved(page))
free_reserved_pages(page, order);
}
Does this forcefully drop the refcount and free the shared
page to the buddy allocator, leaving a dangling pointer in
zone->vmemmap_tails[idx] that breaks other DAX devices
sharing this page?
"
It is a false positive.
The struct page marked reserved here is an entry stored
inside the shared vmemmap backing page; after mapping, it
represents a Device DAX tail page. It is not the struct
page describing the backing page itself.
remove_pte_table() passes pte_page(*pte) to free_vmemmap_pages(),
which is the latter. The runtime-allocated backing page is
not reserved, so removal takes the __free_pages() path. Each
reused PTE takes a reference via get_page(), and removal
drops one such reference, while the allocation reference
retained by zone->vmemmap_tails[] remains. Therefore removing
one DAX device does not free the shared backing page or leave
a dangling pointer.
> }
>
> page = virt_to_page(addr);
> @@ -490,23 +492,6 @@ static bool __meminit reuse_compound_section(unsigned long start_pfn,
> return !IS_ALIGNED(offset, nr_pages) && nr_pages > PAGES_PER_SUBSECTION;
> }
>
> -static pte_t * __meminit compound_section_tail_page(unsigned long addr)
> -{
> - pte_t *pte;
> -
> - addr -= PAGE_SIZE;
> -
> - /*
> - * Assuming sections are populated sequentially, the previous section's
> - * page data can be reused.
> - */
> - pte = pte_offset_kernel(pmd_off_k(addr), addr);
> - if (!pte)
> - return NULL;
> -
> - return pte;
> -}
> -
> static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
> unsigned long start,
> unsigned long end, int node,
> @@ -516,21 +501,18 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
> pte_t *pte;
> int rc;
> unsigned long flags = VMEMMAP_POPULATE_DAX;
> + struct page *page;
> + unsigned int order = pfn_to_section_order(start_pfn);
>
> - if (reuse_compound_section(start_pfn, pgmap)) {
> - pte = compound_section_tail_page(start);
> - if (!pte)
> - return -ENOMEM;
> + page = vmemmap_shared_tail_page(order, device_zone(node));
> + if (!page)
> + return -ENOMEM;
>
> - /*
> - * Reuse the page that was populated in the prior iteration
> - * with just tail struct pages.
> - */
> + if (reuse_compound_section(start_pfn, pgmap))
> return vmemmap_populate_range(start, end, node, NULL,
> - pte_pfn(ptep_get(pte)), flags);
> - }
> + page_to_pfn(page), flags);
>
> - size = min(end - start, pgmap_vmemmap_nr(pgmap) * sizeof(struct page));
> + size = min(end - start, (1UL << order) * sizeof(struct page));
> for (addr = start; addr < end; addr += size) {
> unsigned long next, last = addr + size;
>
> @@ -546,12 +528,12 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
> return -ENOMEM;
>
> /*
> - * Reuse the previous page for the rest of tail pages
> + * Reuse the shared page for the rest of tail pages
> * See layout diagram in Documentation/mm/vmemmap_dedup.rst
> */
> next += PAGE_SIZE;
> rc = vmemmap_populate_range(next, last, node, NULL,
> - pte_pfn(ptep_get(pte)), flags);
> + page_to_pfn(page), flags);
Another report is like:
"
When this shared tail page is used to back the
DEV-DAX tail pages, could concurrent initialization
corrupt the compound_head pointer for the entire
node?
Looking at memmap_init_zone_device which calls
memmap_init_compound, the head page template is copied
unconditionally into the tail pages:
mm/mm_init.c:memmap_init_compound() {
for (pfn = head_pfn + 2; pfn < end_pfn; pfn++)
zone_device_page_init_from_template(pfn_to_page(pfn), pfn,
&template);
}
Since page_to_pfn(page) here points to a node-wide
shared physical page, would this template copy overwrite
the compound_head for all DEV-DAX tail pages on the node
to point to the latest initialized PMD's head page?
"
This is also a false positive.
memmap_init_compound() does not initialize the full
compound range when the section uses vmemmap optimization.
compound_nr_pages() limits it to:
2 * PAGE_SIZE / sizeof(struct page)
i.e. exactly the entries in the retained vmemmap page.
The shared mappings start at addr + PAGE_SIZE, so the
template-copy loop stops before reaching them.
Thanks
Muchun
> if (rc)
> return -ENOMEM;
> }
> @@ -883,13 +865,14 @@ int __meminit sparse_add_section(int nid, unsigned long start_pfn,
> if (IS_ERR(memmap))
> return PTR_ERR(memmap);
>
> + ms = __nr_to_section(section_nr);
> /*
> * Poison uninitialized struct pages in order to catch invalid flags
> * combinations.
> */
> - page_init_poison(memmap, sizeof(struct page) * nr_pages);
> + if (!section_vmemmap_optimizable(ms))
> + page_init_poison(memmap, sizeof(struct page) * nr_pages);
>
> - ms = __nr_to_section(section_nr);
> __section_mark_present(ms, section_nr);
>
> /* Align memmap to section boundary in the subsection case */
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
2026-09-08 3:03 ` [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
2026-09-08 4:09 ` Muchun Song
@ 2026-09-08 7:46 ` Qi Zheng
2026-09-08 8:34 ` Muchun Song
1 sibling, 1 reply; 23+ messages in thread
From: Qi Zheng @ 2026-09-08 7:46 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On 9/8/26 11:03 AM, Muchun Song wrote:
> HugeTLB vmemmap optimization now uses per-zone shared tail vmemmap pages.
> Device DAX has not been switched to that mechanism yet.
>
> Switch device DAX to vmemmap_shared_tail_page() as well. This aligns DAX
> with HugeTLB by using the common per-zone shared tail vmemmap page.
>
> The optimization is enabled only for DEV-DAX through pgmap->vmemmap_shift,
> which is assigned when a DEV-DAX device is initialized. Unlike FS-DAX,
> DEV-DAX does not modify tail struct pages, so sharing them is safe.
Got it — adding this description makes things significantly clearer.
>
> Since the shared tail page can now back ZONE_DEVICE vmemmap mappings,
> initialize its entries with PG_reserved for device zones. Also skip
> poisoning vmemmap-optimizable sections while their struct pages may be
> shared.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> v2:
> - Explain why sharing tail vmemmap pages is safe for DEV-DAX
> (suggested by Qi Zheng)
> ---
> include/linux/mmzone.h | 10 +++++++++
> mm/memory_hotplug.c | 5 +++--
> mm/sparse-vmemmap.c | 47 ++++++++++++++----------------------------
> 3 files changed, 28 insertions(+), 34 deletions(-)
>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 07/11] mm/sparse-vmemmap: move HVO helpers to a public header
2026-09-08 3:03 ` [PATCH v2 07/11] mm/sparse-vmemmap: move HVO helpers to a public header Muchun Song
@ 2026-09-08 8:19 ` Qi Zheng
0 siblings, 0 replies; 23+ messages in thread
From: Qi Zheng @ 2026-09-08 8:19 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On 9/8/26 11:03 AM, Muchun Song wrote:
> The vmemmap optimization helpers currently live in mm/sparse.h,
> which is an internal MM header. That works for MM code, but
> prevents powerpc from using the same interfaces without including a
> private header.
>
> Move the declarations and inline helpers to
> include/linux/vmemmap-optimization.h. This is a preparatory change for
> powerpc, which has its own vmemmap optimization implementation and
> needs to use the HVO interfaces from architecture code.
Make sense.
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> v2:
> - Fix missing header dependencies.
> ---
> MAINTAINERS | 1 +
> include/linux/vmemmap-optimization.h | 91 ++++++++++++++++++++++++++++
> mm/hugetlb.c | 2 +-
> mm/hugetlb_vmemmap.c | 2 +-
> mm/memory_hotplug.c | 1 +
> mm/sparse.h | 73 +---------------------
> 6 files changed, 96 insertions(+), 74 deletions(-)
> create mode 100644 include/linux/vmemmap-optimization.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c7aaa2e4790e..2985286cbbbf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12096,6 +12096,7 @@ F: Documentation/mm/hugetlbfs_reserv.rst
> F: Documentation/mm/vmemmap_dedup.rst
> F: fs/hugetlbfs/
> F: include/linux/hugetlb.h
> +F: include/linux/vmemmap-optimization.h
> F: include/trace/events/hugetlbfs.h
> F: mm/hugetlb.c
> F: mm/hugetlb_cgroup.c
> diff --git a/include/linux/vmemmap-optimization.h b/include/linux/vmemmap-optimization.h
> new file mode 100644
> index 000000000000..492dc662aae7
> --- /dev/null
> +++ b/include/linux/vmemmap-optimization.h
> @@ -0,0 +1,91 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * vmemmap-optimization.h
> + *
> + * Generic vmemmap optimization declarations.
> + *
> + * Author: Muchun Song <songmuchun@bytedance.com>
> + */
> +#ifndef _LINUX_VMEMMAP_OPTIMIZATION_H
> +#define _LINUX_VMEMMAP_OPTIMIZATION_H
> +
> +#include <linux/align.h>
> +#include <linux/log2.h>
> +#include <linux/mmdebug.h>
> +#include <linux/mmzone.h>
> +
> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> +static inline unsigned int section_order(const struct mem_section *section)
> +{
> + return section->order;
> +}
> +
> +static inline void section_set_order(struct mem_section *section, unsigned int order)
> +{
> + VM_WARN_ON(section_order(section) && order && section_order(section) != order);
> + section->order = order;
> +}
> +
> +static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
> + unsigned int order)
> +{
> + unsigned long section_nr = pfn_to_section_nr(pfn);
> +
> + if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
> + return;
> +
> + for (unsigned long i = 0; i < nr_pages / PAGES_PER_SECTION; i++)
> + section_set_order(__nr_to_section(section_nr + i), order);
> +}
> +
> +static inline unsigned int pfn_to_section_order(unsigned long pfn)
> +{
> + return section_order(__pfn_to_section(pfn));
> +}
> +#else
> +static inline unsigned int section_order(const struct mem_section *section)
> +{
> + return 0;
> +}
> +
> +static inline void section_set_order(struct mem_section *section, unsigned int order)
> +{
> +}
> +
> +static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
> + unsigned int order)
> +{
> +}
> +
> +static inline unsigned int pfn_to_section_order(unsigned long pfn)
> +{
> + return 0;
> +}
> +#endif /* CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION */
> +
> +static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
> +{
> + const unsigned int order = pfn_to_section_order(pfn);
> + const unsigned long nr_pages = 1UL << order;
> +
> + if (!is_power_of_2(sizeof(struct page)))
> + return false;
> +
> + return (pfn & (nr_pages - 1)) >= VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES;
> +}
> +
> +static inline bool vmemmap_optimizable_order(unsigned int order)
> +{
> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
> + return false;
> +
> + if (!is_power_of_2(sizeof(struct page)))
> + return false;
> +
> + return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
> +}
> +
> +#ifdef CONFIG_SPARSEMEM_VMEMMAP
> +struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
> +#endif /* CONFIG_SPARSEMEM_VMEMMAP */
> +#endif /* _LINUX_VMEMMAP_OPTIMIZATION_H */
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index fda525bccf50..dff915e6c48b 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -38,6 +38,7 @@
> #include <linux/mm_inline.h>
> #include <linux/padata.h>
> #include <linux/pgalloc.h>
> +#include <linux/vmemmap-optimization.h>
>
> #include <asm/page.h>
> #include <asm/tlb.h>
> @@ -52,7 +53,6 @@
> #include "hugetlb_cma.h"
> #include "hugetlb_internal.h"
> #include "mm_init.h"
> -#include "sparse.h"
> #include <linux/page-isolation.h>
>
> #define HUGE_BOOTMEM_ZONES_VALID BIT(0)
> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
> index 4a57e6c3352c..25c4e7d2664c 100644
> --- a/mm/hugetlb_vmemmap.c
> +++ b/mm/hugetlb_vmemmap.c
> @@ -15,10 +15,10 @@
> #include <linux/mmdebug.h>
> #include <linux/pagewalk.h>
> #include <linux/pgalloc.h>
> +#include <linux/vmemmap-optimization.h>
>
> #include <asm/tlbflush.h>
> #include "hugetlb_vmemmap.h"
> -#include "sparse.h"
> #include "internal.h"
>
> /**
> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
> index 0db0379826df..d7a59167bec4 100644
> --- a/mm/memory_hotplug.c
> +++ b/mm/memory_hotplug.c
> @@ -43,6 +43,7 @@
> #include "mm_init.h"
> #include "page_alloc.h"
> #include "shuffle.h"
> +#include "sparse.h"
>
> enum {
> MEMMAP_ON_MEMORY_DISABLE = 0,
> diff --git a/mm/sparse.h b/mm/sparse.h
> index 59b825df83b9..e511d99fc26b 100644
> --- a/mm/sparse.h
> +++ b/mm/sparse.h
> @@ -9,77 +9,7 @@
> #define __MM_SPARSE_H
>
> #include <linux/mmzone.h>
> -
> -#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> -static inline unsigned int section_order(const struct mem_section *section)
> -{
> - return section->order;
> -}
> -
> -static inline void section_set_order(struct mem_section *section, unsigned int order)
> -{
> - VM_WARN_ON(section_order(section) && order && section_order(section) != order);
> - section->order = order;
> -}
> -
> -static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
> - unsigned int order)
> -{
> - unsigned long section_nr = pfn_to_section_nr(pfn);
> -
> - if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
> - return;
> -
> - for (unsigned long i = 0; i < nr_pages / PAGES_PER_SECTION; i++)
> - section_set_order(__nr_to_section(section_nr + i), order);
> -}
> -
> -static inline unsigned int pfn_to_section_order(unsigned long pfn)
> -{
> - return section_order(__pfn_to_section(pfn));
> -}
> -#else
> -static inline unsigned int section_order(const struct mem_section *section)
> -{
> - return 0;
> -}
> -
> -static inline void section_set_order(struct mem_section *section, unsigned int order)
> -{
> -}
> -
> -static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
> - unsigned int order)
> -{
> -}
> -
> -static inline unsigned int pfn_to_section_order(unsigned long pfn)
> -{
> - return 0;
> -}
> -#endif
> -
> -static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
> -{
> - const unsigned int order = pfn_to_section_order(pfn);
> - const unsigned long nr_pages = 1UL << order;
> -
> - if (!is_power_of_2(sizeof(struct page)))
> - return false;
> -
> - return (pfn & (nr_pages - 1)) >= VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES;
> -}
> -
> -static inline bool vmemmap_optimizable_order(unsigned int order)
> -{
> - if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
> - return false;
> -
> - if (!is_power_of_2(sizeof(struct page)))
> - return false;
> -
> - return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
> -}
> +#include <linux/vmemmap-optimization.h>
>
> /*
> * mm/sparse.c
> @@ -139,7 +69,6 @@ static inline void sparse_sections_init(void) {}
> * mm/sparse-vmemmap.c
> */
> #ifdef CONFIG_SPARSEMEM_VMEMMAP
> -struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
> void sparse_init_subsection_map(void);
> int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
> struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
2026-09-08 7:46 ` Qi Zheng
@ 2026-09-08 8:34 ` Muchun Song
0 siblings, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 8:34 UTC (permalink / raw)
To: Qi Zheng
Cc: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Lorenzo Stoakes,
Mike Rapoport, Nicholas Piggin, Christophe Leroy, Randy Dunlap
> On Sep 8, 2026, at 15:46, Qi Zheng <qi.zheng@linux.dev> wrote:
>
>
>
> On 9/8/26 11:03 AM, Muchun Song wrote:
>> HugeTLB vmemmap optimization now uses per-zone shared tail vmemmap pages.
>> Device DAX has not been switched to that mechanism yet.
>> Switch device DAX to vmemmap_shared_tail_page() as well. This aligns DAX
>> with HugeTLB by using the common per-zone shared tail vmemmap page.
>> The optimization is enabled only for DEV-DAX through pgmap->vmemmap_shift,
>> which is assigned when a DEV-DAX device is initialized. Unlike FS-DAX,
>> DEV-DAX does not modify tail struct pages, so sharing them is safe.
>
> Got it — adding this description makes things significantly clearer.
Aha, also makes Sashiko happy. This time, it is no longer confused
with FS-DAX.
>
>> Since the shared tail page can now back ZONE_DEVICE vmemmap mappings,
>> initialize its entries with PG_reserved for device zones. Also skip
>> poisoning vmemmap-optimizable sections while their struct pages may be
>> shared.
>> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
>> ---
>> v2:
>> - Explain why sharing tail vmemmap pages is safe for DEV-DAX
>> (suggested by Qi Zheng)
>> ---
>> include/linux/mmzone.h | 10 +++++++++
>> mm/memory_hotplug.c | 5 +++--
>> mm/sparse-vmemmap.c | 47 ++++++++++++++----------------------------
>> 3 files changed, 28 insertions(+), 34 deletions(-)
>
> Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks.
>
> Thanks,
> Qi
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation
2026-09-08 3:03 ` [PATCH v2 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
@ 2026-09-08 9:09 ` Qi Zheng
2026-09-08 9:12 ` Muchun Song
0 siblings, 1 reply; 23+ messages in thread
From: Qi Zheng @ 2026-09-08 9:09 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On 9/8/26 11:03 AM, Muchun Song wrote:
> The device DAX vmemmap population still reserves one extra tail vmemmap
> page after the head page.
>
> Drop that extra reservation and let the shared tail page cover all tail
> vmemmap pages after the head page, so DAX follows the same reservation
> model as HugeTLB.
>
> This reduces the reserved vmemmap pages for optimized DAX mappings to
> one and removes the now-unneeded first-tail population from the generic
> and powerpc paths to simplify the code as well.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> arch/powerpc/mm/book3s64/radix_pgtable.c | 46 ++----------------------
> include/linux/mm.h | 3 +-
> mm/mm_init.c | 2 +-
> mm/sparse-vmemmap.c | 13 ++-----
> 4 files changed, 7 insertions(+), 57 deletions(-)
Looks like sashiko's response [1] is a false positive.
[1].
https://sashiko.dev/#/patchset/20260908030335.96549-1-songmuchun%40bytedance.com
__SetPageReserved() in vmemmap_shared_tail_page() marks the struct page
entries stored inside the shared vmemmap backing page, i.e. the metadata
templates that later represent DEV-DAX tail pages. It does not mark the
struct page of the backing page itself.
On hot-remove, free_vmemmap_pages() is called with pte_page(*pte), which
is the backing page. That page is not reserved, so the teardown goes
through __free_pages(), not free_reserved_pages().
Right?
If so:
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
>
> diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
> index 831c231a4a18..e7e751c48dd2 100644
> --- a/arch/powerpc/mm/book3s64/radix_pgtable.c
> +++ b/arch/powerpc/mm/book3s64/radix_pgtable.c
> @@ -1218,39 +1218,6 @@ int __meminit radix__vmemmap_populate(unsigned long start, unsigned long end, in
> return 0;
> }
>
> -static pte_t * __meminit radix__vmemmap_populate_address(unsigned long addr, int node,
> - struct vmem_altmap *altmap,
> - struct page *reuse)
> -{
> - pgd_t *pgd;
> - p4d_t *p4d;
> - pud_t *pud;
> - pmd_t *pmd;
> - pte_t *pte;
> -
> - pgd = pgd_offset_k(addr);
> - p4d = p4d_offset(pgd, addr);
> - pud = vmemmap_pud_alloc(p4d, node, addr);
> - if (!pud)
> - return NULL;
> - pmd = vmemmap_pmd_alloc(pud, node, addr);
> - if (!pmd)
> - return NULL;
> - if (pmd_leaf(*pmd))
> - /*
> - * The second page is mapped as a hugepage due to a nearby request.
> - * Force our mapping to page size without deduplication
> - */
> - return NULL;
> - pte = vmemmap_pte_alloc(pmd, node, addr);
> - if (!pte)
> - return NULL;
> - radix__vmemmap_pte_populate(pmd, addr, node, NULL, NULL);
> - vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
> -
> - return pte;
> -}
> -
> int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
> unsigned long start,
> unsigned long end, int node,
> @@ -1297,7 +1264,7 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
> if (!pte_none(*pte)) {
> /*
> * This could be because we already have a compound
> - * page whose VMEMMAP_RESERVE_NR pages were mapped and
> + * page whose retained vmemmap page was mapped and
> * this request fall in those pages.
> */
> next = addr + PAGE_SIZE;
> @@ -1318,16 +1285,7 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
> return -ENOMEM;
> vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
>
> - /*
> - * Populate the tail pages vmemmap page
> - * It can fall in different pmd, hence
> - * vmemmap_populate_address()
> - */
> - pte = radix__vmemmap_populate_address(addr + PAGE_SIZE, node, NULL, NULL);
> - if (!pte)
> - return -ENOMEM;
> -
> - next = addr + 2 * PAGE_SIZE;
> + next = addr + PAGE_SIZE;
> continue;
> }
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index a2ebe87e7654..969594074fd2 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -5167,7 +5167,6 @@ static inline void vmem_altmap_free(struct vmem_altmap *altmap,
> }
> #endif
>
> -#define VMEMMAP_RESERVE_NR 2
> #ifdef CONFIG_ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
> static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
> struct dev_pagemap *pgmap)
> @@ -5187,7 +5186,7 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
> * For vmemmap optimization with DAX we need minimum 2 vmemmap
> * pages. See layout diagram in Documentation/mm/vmemmap_dedup.rst
> */
> - return !altmap && (nr_vmemmap_pages > VMEMMAP_RESERVE_NR);
> + return !altmap && (nr_vmemmap_pages > VMEMMAP_OPTIMIZATION_PAGES);
> }
> /*
> * If we don't have an architecture override, use the generic rule
> diff --git a/mm/mm_init.c b/mm/mm_init.c
> index 7a2e58d631c2..629420a83891 100644
> --- a/mm/mm_init.c
> +++ b/mm/mm_init.c
> @@ -1056,7 +1056,7 @@ static inline unsigned long compound_nr_pages(unsigned long pfn,
> if (!section_vmemmap_optimizable(ms))
> return pgmap_vmemmap_nr(pgmap);
>
> - return VMEMMAP_RESERVE_NR * (PAGE_SIZE / sizeof(struct page));
> + return VMEMMAP_OPTIMIZATION_PAGES * (PAGE_SIZE / sizeof(struct page));
> }
>
> static void __ref memmap_init_compound(struct page *head,
> diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
> index 0201877a7f80..e655d9d1348f 100644
> --- a/mm/sparse-vmemmap.c
> +++ b/mm/sparse-vmemmap.c
> @@ -136,7 +136,6 @@ int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages
> {
> const struct mem_section *ms = __pfn_to_section(pfn);
> const int order = section_order(ms);
> - const int vmemmap_pages = pgmap ? VMEMMAP_RESERVE_NR : VMEMMAP_OPTIMIZATION_PAGES;
> const unsigned long pages_per_compound = 1UL << order;
>
> VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
> @@ -147,13 +146,13 @@ int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages
>
> if (order < PFN_SECTION_SHIFT) {
> VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
> - return vmemmap_pages * nr_pages / pages_per_compound;
> + return VMEMMAP_OPTIMIZATION_PAGES * nr_pages / pages_per_compound;
> }
>
> VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
>
> if (IS_ALIGNED(pfn, pages_per_compound))
> - return vmemmap_pages;
> + return VMEMMAP_OPTIMIZATION_PAGES;
>
> return 0;
> }
> @@ -521,17 +520,11 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
> if (!pte)
> return -ENOMEM;
>
> - /* Populate the tail pages vmemmap page */
> - next = addr + PAGE_SIZE;
> - pte = vmemmap_populate_address(next, node, NULL, -1, flags);
> - if (!pte)
> - return -ENOMEM;
> -
> /*
> * Reuse the shared page for the rest of tail pages
> * See layout diagram in Documentation/mm/vmemmap_dedup.rst
> */
> - next += PAGE_SIZE;
> + next = addr + PAGE_SIZE;
> rc = vmemmap_populate_range(next, last, node, NULL,
> page_to_pfn(page), flags);
> if (rc)
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation
2026-09-08 9:09 ` Qi Zheng
@ 2026-09-08 9:12 ` Muchun Song
0 siblings, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-08 9:12 UTC (permalink / raw)
To: Qi Zheng
Cc: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Lorenzo Stoakes,
Mike Rapoport, Nicholas Piggin, Christophe Leroy, Randy Dunlap
> On Sep 8, 2026, at 17:09, Qi Zheng <qi.zheng@linux.dev> wrote:
>
>
>
> On 9/8/26 11:03 AM, Muchun Song wrote:
>> The device DAX vmemmap population still reserves one extra tail vmemmap
>> page after the head page.
>> Drop that extra reservation and let the shared tail page cover all tail
>> vmemmap pages after the head page, so DAX follows the same reservation
>> model as HugeTLB.
>> This reduces the reserved vmemmap pages for optimized DAX mappings to
>> one and removes the now-unneeded first-tail population from the generic
>> and powerpc paths to simplify the code as well.
>> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
>> ---
>> arch/powerpc/mm/book3s64/radix_pgtable.c | 46 ++----------------------
>> include/linux/mm.h | 3 +-
>> mm/mm_init.c | 2 +-
>> mm/sparse-vmemmap.c | 13 ++-----
>> 4 files changed, 7 insertions(+), 57 deletions(-)
>
> Looks like sashiko's response [1] is a false positive.
>
> [1]. https://sashiko.dev/#/patchset/20260908030335.96549-1-songmuchun%40bytedance.com
>
> __SetPageReserved() in vmemmap_shared_tail_page() marks the struct page
> entries stored inside the shared vmemmap backing page, i.e. the metadata
> templates that later represent DEV-DAX tail pages. It does not mark the
> struct page of the backing page itself.
>
> On hot-remove, free_vmemmap_pages() is called with pte_page(*pte), which
> is the backing page. That page is not reserved, so the teardown goes
> through __free_pages(), not free_reserved_pages().
>
> Right?
Absolutely right.
>
> If so:
>
> Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments
2026-09-08 3:03 ` [PATCH v2 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
@ 2026-09-08 9:23 ` Qi Zheng
0 siblings, 0 replies; 23+ messages in thread
From: Qi Zheng @ 2026-09-08 9:23 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On 9/8/26 11:03 AM, Muchun Song wrote:
> section_nr_vmemmap_pages() no longer uses the altmap or pgmap
> arguments, so drop them from the helper and its callers.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> mm/sparse-vmemmap.c | 10 ++++------
> mm/sparse.c | 3 +--
> mm/sparse.h | 6 ++----
> 3 files changed, 7 insertions(+), 12 deletions(-)
>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 11/11] Documentation/mm: update DAX vmemmap deduplication docs
2026-09-08 3:03 ` [PATCH v2 11/11] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
@ 2026-09-08 9:29 ` Qi Zheng
0 siblings, 0 replies; 23+ messages in thread
From: Qi Zheng @ 2026-09-08 9:29 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On 9/8/26 11:03 AM, Muchun Song wrote:
> Device DAX now uses the common per-zone shared tail page for vmemmap
> deduplication. The old documentation still described a DAX-specific
> layout with a separately populated tail vmemmap page and half the HugeTLB
> savings.
>
> Update the generic and powerpc documentation to describe the shared layout.
> In the powerpc document, keep the radix and 64K-specific details, drop the
> duplicated 4K PUD arithmetic, and replace the repeated device-dax diagrams
> with a single parameterized PMD/PUD diagram.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> v2:
> - Clarify the commit message to state that the 4K PUD arithmetic is
> intentionally dropped reported by Sashiko.
> ---
> Documentation/arch/powerpc/vmemmap_dedup.rst | 90 ++++----------------
> Documentation/mm/vmemmap_dedup.rst | 32 +------
> 2 files changed, 21 insertions(+), 101 deletions(-)
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (10 preceding siblings ...)
2026-09-08 3:03 ` [PATCH v2 11/11] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
@ 2026-09-09 1:45 ` Andrew Morton
2026-09-10 6:48 ` Muchun Song
11 siblings, 1 reply; 23+ messages in thread
From: Andrew Morton @ 2026-09-09 1:45 UTC (permalink / raw)
To: Muchun Song
Cc: David Hildenbrand, Oscar Salvador, Madhavan Srinivasan,
Michael Ellerman, Jonathan Corbet, linux-mm, linux-kernel,
linuxppc-dev, linux-doc, Muchun Song, Lorenzo Stoakes,
Mike Rapoport, Qi Zheng, Nicholas Piggin, Christophe Leroy,
Randy Dunlap
On Tue, 8 Sep 2026 11:03:24 +0800 Muchun Song <songmuchun@bytedance.com> wrote:
> This series is split out from the earlier, larger series "mm: Generalize
> HVO for HugeTLB and device DAX" [1]. While the parent series generalizes
> vmemmap optimization across HugeTLB and device DAX, this subset addresses
> a single, self-contained step: switching device DAX to the section-based
> sparse-vmemmap optimization infrastructure introduced for HugeTLB.
>
> ...
>
> This is intended to be the third smaller step toward the broader HVO
> generalization. The wider HVO consolidation between HugeTLB and device
> DAX is left for follow-up series.
Thanks both, I'll add it to mm.git's mm-new branch.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization
2026-09-09 1:45 ` [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Andrew Morton
@ 2026-09-10 6:48 ` Muchun Song
0 siblings, 0 replies; 23+ messages in thread
From: Muchun Song @ 2026-09-10 6:48 UTC (permalink / raw)
To: Andrew Morton
Cc: Muchun Song, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Lorenzo Stoakes,
Mike Rapoport, Qi Zheng, Nicholas Piggin, Christophe Leroy,
Randy Dunlap
> On Sep 9, 2026, at 09:45, Andrew Morton <akpm@linux-foundation.org> wrote:
>
> On Tue, 8 Sep 2026 11:03:24 +0800 Muchun Song <songmuchun@bytedance.com> wrote:
>
>> This series is split out from the earlier, larger series "mm: Generalize
>> HVO for HugeTLB and device DAX" [1]. While the parent series generalizes
>> vmemmap optimization across HugeTLB and device DAX, this subset addresses
>> a single, self-contained step: switching device DAX to the section-based
>> sparse-vmemmap optimization infrastructure introduced for HugeTLB.
>>
>> ...
>>
>> This is intended to be the third smaller step toward the broader HVO
>> generalization. The wider HVO consolidation between HugeTLB and device
>> DAX is left for follow-up series.
>
> Thanks both, I'll add it to mm.git's mm-new branch.
Hi Andrew,
I've updated series [1] to v6 to address David's review comments from
yesterday. Since the current series depends on this v6 version, I suggest
dropping these two series from mm-new. Once the v6 version of the dependent
series is merged in, I will re-update the current series to v3 based on the
new mm-new branch, and also fix the compilation error under !CONFIG_NUMA [2]
in that version.
[1] https://lore.kernel.org/all/20260910063256.64386-1-songmuchun@bytedance.com/
[2] https://lore.kernel.org/all/202609100517.0DrzxrW4-lkp@intel.com/
Thanks,
Muchun
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 03/11] mm/sparse-vmemmap: open-code init_compound_tail()
2026-09-08 3:03 ` [PATCH v2 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
@ 2026-09-10 7:50 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 23+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-10 7:50 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, Oscar Salvador, Madhavan Srinivasan,
Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On 9/8/26 05:03, Muchun Song wrote:
> init_compound_tail() is only used by vmemmap_shared_tail_page(), where
> the shared tail page setup intentionally passes NULL as the compound head.
>
> Keeping this helper in mm/internal.h exposes that special case to the rest
> of the MM code and can make the NULL head argument look generally valid.
> Open-code the initialization at the only call site so the special-case use
> stays local to sparse vmemmap optimization.
>
> No functional change intended.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Acked-by: Qi Zheng <qi.zheng@linux.dev>
> ---
> v2:
> - Collect Acked-by from Qi Zheng
> ---
> mm/internal.h | 9 ---------
> mm/sparse-vmemmap.c | 5 ++++-
> 2 files changed, 4 insertions(+), 10 deletions(-)
>
> diff --git a/mm/internal.h b/mm/internal.h
> index da14c56fb24e..0dca33db068f 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -786,15 +786,6 @@ static inline void prep_compound_tail(struct page *tail,
> VM_WARN_ON_ONCE(tail->private);
> }
>
> -static inline void init_compound_tail(struct page *tail,
> - const struct page *head, unsigned int order, struct zone *zone)
> -{
> - atomic_set(&tail->_mapcount, -1);
> - set_page_node(tail, zone_to_nid(zone));
> - set_page_zone(tail, zone_idx(zone));
> - prep_compound_tail(tail, head, order);
> -}
> -
> #if defined CONFIG_COMPACTION || defined CONFIG_CMA
>
> /*
> diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
> index 70143dd8b579..e453ce4675a0 100644
> --- a/mm/sparse-vmemmap.c
> +++ b/mm/sparse-vmemmap.c
> @@ -189,7 +189,10 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
> for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++) {
> page = (struct page *)addr + i;
> mm_zero_struct_page(page);
> - init_compound_tail(page, NULL, order, zone);
> + atomic_set(&page->_mapcount, -1);
> + set_page_node(page, zone_to_nid(zone));
> + set_page_zone(page, zone_idx(zone));
> + prep_compound_tail(page, NULL, order);
> }
>
> page = virt_to_page(addr);
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-09-10 7:53 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-08 3:03 [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-09-08 3:03 ` [PATCH v2 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
2026-09-08 3:03 ` [PATCH v2 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
2026-09-08 3:03 ` [PATCH v2 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
2026-09-10 7:50 ` David Hildenbrand (Arm)
2026-09-08 3:03 ` [PATCH v2 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders Muchun Song
2026-09-08 3:03 ` [PATCH v2 05/11] mm/sparse-vmemmap: set section order for device DAX Muchun Song
2026-09-08 3:03 ` [PATCH v2 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
2026-09-08 4:09 ` Muchun Song
2026-09-08 7:46 ` Qi Zheng
2026-09-08 8:34 ` Muchun Song
2026-09-08 3:03 ` [PATCH v2 07/11] mm/sparse-vmemmap: move HVO helpers to a public header Muchun Song
2026-09-08 8:19 ` Qi Zheng
2026-09-08 3:03 ` [PATCH v2 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
2026-09-08 3:03 ` [PATCH v2 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
2026-09-08 9:09 ` Qi Zheng
2026-09-08 9:12 ` Muchun Song
2026-09-08 3:03 ` [PATCH v2 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
2026-09-08 9:23 ` Qi Zheng
2026-09-08 3:03 ` [PATCH v2 11/11] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
2026-09-08 9:29 ` Qi Zheng
2026-09-09 1:45 ` [PATCH v2 00/11] mm: Switch device DAX to section-based vmemmap optimization Andrew Morton
2026-09-10 6:48 ` Muchun Song
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®