* [PATCH v6 01/12] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 02/12] mm/sparse-vmemmap: allocate shared tail page array dynamically Muchun Song
` (11 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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(), initializes the tail
struct pages, and uses cmpxchg() to install the per-zone shared page.
This removes duplicate allocation logic while handling both early boot
and runtime allocation through the same helper.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v6:
- Move the constant declaration to the top of the function (suggested by
David Hildenbrand)
- Collect Acked-by from David Hildenbrand
v5:
- Move this patch before CONFIG_VMEMMAP_OPTIMIZATION is introduced
v4:
- Update the commit message for the renamed VMEMMAP_OPTIMIZATION config
- Collect Acked-by from Mike Rapoport
v2:
- Collect Acked-by from Qi Zheng
---
mm/hugetlb_vmemmap.c | 29 +-----------------
mm/sparse-vmemmap.c | 70 ++++++++++++++++++++------------------------
mm/sparse.h | 3 ++
3 files changed, 36 insertions(+), 66 deletions(-)
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index f977d0a7e002..76765c97ff68 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -19,7 +19,6 @@
#include <asm/tlbflush.h>
#include "hugetlb_vmemmap.h"
#include "sparse.h"
-#include "internal.h"
/**
* struct vmemmap_remap_walk - walk vmemmap page table
@@ -493,32 +492,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 +508,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 f22d815d7af0..9af349581fbd 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,
@@ -185,34 +170,43 @@ static void * __meminit vmemmap_alloc_block_zero(unsigned long size, int node)
}
#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);
+ const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
+ struct page *page;
+ void *addr;
- if (WARN_ON_ONCE(order < VMEMMAP_OPTIMIZATION_MIN_ORDER))
- return NULL;
- if (WARN_ON_ONCE(order > MAX_FOLIO_ORDER))
+ if (WARN_ON_ONCE(idx >= VMEMMAP_OPTIMIZATION_NR_ORDERS))
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 (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;
+ 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;
}
#else
-static inline struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
+static inline struct page *vmemmap_shared_tail_page(unsigned int order,
+ struct zone *zone)
{
return NULL;
}
@@ -229,7 +223,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 d3a71ef4fad0..6e7aaeaa5594 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -142,6 +142,9 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
+#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
+#endif
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] 16+ messages in thread* [PATCH v6 02/12] mm/sparse-vmemmap: allocate shared tail page array dynamically
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-09-30 14:06 ` [PATCH v6 01/12] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 03/12] mm/sparse-vmemmap: introduce CONFIG_VMEMMAP_OPTIMIZATION Muchun Song
` (10 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
Commit 622026e87c40 ("mm/hugetlb: remove fake head pages") added the
per-zone vmemmap_tails array. Its size depends on MAX_FOLIO_ORDER, which
had been moved to mmzone.h in preparation for the array.
PUD_ORDER is defined by linux/pgtable.h, which cannot be included from
mmzone.h without creating an include cycle. It was therefore open-coded
as PUD_SHIFT - PAGE_SHIFT.
This removed the dependency on PUD_ORDER, but not the underlying
dependency on architecture page-table definitions. PUD_SHIFT is
generally provided by architecture page-table headers, which are not
guaranteed to have been included when mmzone.h is parsed.
The dependency remained hidden because vmemmap_tails was originally
guarded by CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP. Under that condition,
MAX_FOLIO_ORDER resolves to either MAX_PAGE_ORDER or the fixed HugeTLB
limit, rather than the PUD_SHIFT-based definition.
Device DAX, however, does not require CONFIG_HUGETLB_PAGE. When it is
converted to use section-based vmemmap optimization, MAX_FOLIO_ORDER can
resolve to PUD_SHIFT - PAGE_SHIFT while it is being used to size
vmemmap_tails. This would make struct zone depend on architecture
page-table definitions being available when mmzone.h is parsed.
Replace the embedded array with a pointer and allocate it on first use.
This moves the order-count evaluation into sparse-vmemmap.c, after the
architecture page-table definitions are available, and removes the
dependency from mmzone.h.
Removing the compile-time array also removes the original reason for
keeping MAX_FOLIO_ORDER and the vmemmap optimization sizing definitions
in mmzone.h. Follow-up cleanups can place each definition in the header
owned by its respective subsystem.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
v6:
- Move the shared tail array lookup into vmemmap_tails() (suggested by
David Hildenbrand)
v5:
- Add this patch to fix the RISC-V build failure under the configuration
reported by the kernel test robot
---
include/linux/mmzone.h | 7 +------
mm/sparse-vmemmap.c | 39 +++++++++++++++++++++++++++++++++++----
2 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index acd94cecc0d3..68807ff7f946 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -113,11 +113,6 @@
(VMEMMAP_OPTIMIZATION_PAGES * PAGE_SIZE / sizeof(struct page))
#define VMEMMAP_OPTIMIZATION_MIN_ORDER (ilog2(VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES) + 1)
-#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)
-
enum migratetype {
MIGRATE_UNMOVABLE,
MIGRATE_MOVABLE,
@@ -1156,7 +1151,7 @@ struct zone {
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
- struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
+ struct page **vmemmap_tails;
#endif
} ____cacheline_internodealigned_in_smp;
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 9af349581fbd..0ace48268095 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -170,16 +170,47 @@ static void * __meminit vmemmap_alloc_block_zero(unsigned long size, int node)
}
#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#define VMEMMAP_OPTIMIZATION_NR_ORDERS (MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
+
+static __ref struct page **vmemmap_tails(struct zone *zone)
+{
+ const size_t size = array_size(VMEMMAP_OPTIMIZATION_NR_ORDERS,
+ sizeof(*zone->vmemmap_tails));
+ struct page **pages = READ_ONCE(zone->vmemmap_tails);
+
+ if (pages)
+ return pages;
+
+ pages = slab_is_available() ? kzalloc_objs(*pages, VMEMMAP_OPTIMIZATION_NR_ORDERS) :
+ memblock_alloc(size, __alignof__(*pages));
+ if (!pages)
+ return NULL;
+
+ if (cmpxchg(&zone->vmemmap_tails, NULL, pages) != NULL) {
+ if (slab_is_available())
+ kfree(pages);
+ else
+ memblock_free(pages, size);
+ pages = READ_ONCE(zone->vmemmap_tails);
+ }
+
+ return pages;
+}
+
struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zone)
{
const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- struct page *page;
+ struct page *page, **pages;
void *addr;
if (WARN_ON_ONCE(idx >= VMEMMAP_OPTIMIZATION_NR_ORDERS))
return NULL;
- page = READ_ONCE(zone->vmemmap_tails[idx]);
+ pages = vmemmap_tails(zone);
+ if (!pages)
+ return NULL;
+
+ page = READ_ONCE(pages[idx]);
if (page)
return page;
@@ -194,12 +225,12 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
}
page = virt_to_page(addr);
- if (cmpxchg(&zone->vmemmap_tails[idx], NULL, page) != NULL) {
+ if (cmpxchg(&pages[idx], NULL, page) != NULL) {
if (slab_is_available())
__free_page(page);
else
memblock_free(addr, PAGE_SIZE);
- page = READ_ONCE(zone->vmemmap_tails[idx]);
+ page = READ_ONCE(pages[idx]);
}
return page;
--
2.54.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v6 03/12] mm/sparse-vmemmap: introduce CONFIG_VMEMMAP_OPTIMIZATION
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-09-30 14:06 ` [PATCH v6 01/12] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
2026-09-30 14:06 ` [PATCH v6 02/12] mm/sparse-vmemmap: allocate shared tail page array dynamically Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 04/12] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
` (9 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
The section-based vmemmap optimization infrastructure is guarded by
CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP, but it can also be used by
ZONE_DEVICE users that set dev_pagemap::vmemmap_shift. Introduce
CONFIG_VMEMMAP_OPTIMIZATION as a common config for the shared
infrastructure.
Select the new option from HUGETLB_PAGE_OPTIMIZE_VMEMMAP and from
ZONE_DEVICE 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>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v6:
- Collect Acked-by from David Hildenbrand
v5:
- Move this patch after the shared tail-page factoring.
- Select VMEMMAP_OPTIMIZATION from ZONE_DEVICE instead of DEV_DAX,
covering all users of dev_pagemap::vmemmap_shift, reported by
Sashiko.
v4:
- Rename SPARSEMEM_VMEMMAP_OPTIMIZATION to VMEMMAP_OPTIMIZATION
(suggested by Mike Rapoport)
- Collect Acked-by from Mike Rapoport
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 +-
fs/Kconfig | 1 +
include/linux/mm.h | 3 +++
include/linux/mmzone.h | 10 +++++-----
include/linux/page-flags.h | 5 ++---
mm/Kconfig | 5 +++++
mm/sparse-vmemmap.c | 2 +-
mm/sparse.h | 6 +++---
8 files changed, 21 insertions(+), 13 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..72a92cb9b53d 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_VMEMMAP_OPTIMIZATION
#undef CONFIG_NR_CPUS
#undef CONFIG_PARAVIRT_XXL
diff --git a/fs/Kconfig b/fs/Kconfig
index d1c210c6508f..1454b7fe9641 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 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..070ce27e9cd3 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_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 68807ff7f946..ee9cbaaa63f4 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_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. */
@@ -1150,7 +1150,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_VMEMMAP_OPTIMIZATION
struct page **vmemmap_tails;
#endif
} ____cacheline_internodealigned_in_smp;
@@ -2014,7 +2014,7 @@ struct mem_section {
unsigned long section_mem_map;
struct mem_section_usage *usage;
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_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..7080a6a1a79e 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_VMEMMAP_OPTIMIZATION))
return false;
return is_power_of_2(sizeof(struct page));
diff --git a/mm/Kconfig b/mm/Kconfig
index bc7befafb47b..30170a936f1f 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 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.
@@ -1220,6 +1224,7 @@ config ZONE_DMA32
config ZONE_DEVICE
bool "Device memory (pmem, HMM, etc...) hotplug support"
depends on MEMORY_HOTREMOVE
+ select VMEMMAP_OPTIMIZATION if ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
select XARRAY_MULTI
help
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 0ace48268095..6916a3690778 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -169,7 +169,7 @@ static void * __meminit vmemmap_alloc_block_zero(unsigned long size, int node)
return p;
}
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_VMEMMAP_OPTIMIZATION
#define VMEMMAP_OPTIMIZATION_NR_ORDERS (MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
static __ref struct page **vmemmap_tails(struct zone *zone)
diff --git a/mm/sparse.h b/mm/sparse.h
index 6e7aaeaa5594..326ad43bb5c3 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_VMEMMAP_OPTIMIZATION
static inline unsigned int section_compound_order(const struct mem_section *section)
{
return section->compound_page_order;
@@ -75,7 +75,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_VMEMMAP_OPTIMIZATION))
return false;
if (!is_power_of_2(sizeof(struct page)))
@@ -142,7 +142,7 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_VMEMMAP_OPTIMIZATION
struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
#endif
void sparse_init_subsection_map(void);
--
2.54.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v6 04/12] mm/sparse-vmemmap: open-code init_compound_tail()
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (2 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 03/12] mm/sparse-vmemmap: introduce CONFIG_VMEMMAP_OPTIMIZATION Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 05/12] mm/sparse-vmemmap: prepare DAX vmemmap population for compound page orders Muchun Song
` (8 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v4:
- Collect Acked-by from Mike Rapoport
v3:
- Collect Acked-by from David Hildenbrand
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 6916a3690778..f77e6e1e5a8b 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -221,7 +221,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] 16+ messages in thread* [PATCH v6 05/12] mm/sparse-vmemmap: prepare DAX vmemmap population for compound page orders
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (3 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 04/12] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 06/12] mm/sparse-vmemmap: set compound page order for device DAX Muchun Song
` (7 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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
order in section metadata, DAX head and first-tail PFNs can look
optimizable to the generic helpers as well.
Rename the existing VMEMMAP_POPULATE_PAGEREF flag to
VMEMMAP_POPULATE_DAX and pass it through all paths in
vmemmap_populate_compound_pages() during this transition. This 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>
---
v6:
- Clarify that the DAX flag is renamed and used on additional paths
(suggested by David Hildenbrand)
- Make flags const and move it to the top of the function (suggested by
David Hildenbrand)
v3:
- Update the subject and commit message to use compound page order
terminology
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 f77e6e1e5a8b..b4b72f233bf6 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"
@@ -247,13 +247,17 @@ static inline struct page *vmemmap_shared_tail_page(unsigned int order,
#endif
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_compound_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);
@@ -275,7 +279,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;
@@ -290,7 +294,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);
@@ -547,6 +551,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long end, int node,
struct dev_pagemap *pgmap)
{
+ const unsigned long flags = VMEMMAP_POPULATE_DAX;
unsigned long size, addr;
pte_t *pte;
int rc;
@@ -561,8 +566,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));
@@ -570,13 +574,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;
@@ -586,8 +590,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] 16+ messages in thread* [PATCH v6 06/12] mm/sparse-vmemmap: set compound page order for device DAX
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (4 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 05/12] mm/sparse-vmemmap: prepare DAX vmemmap population for compound page orders Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 07/12] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
` (6 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
Device DAX can use vmemmap optimization only when a full section is
populated with a compound-page geometry. Record that geometry as the
compound page order in section metadata before populating the section, so
later vmemmap accounting and population decisions can use the section state
directly.
Clear the compound page 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>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v6:
- Collect Acked-by from David Hildenbrand
v3:
- Update the subject and commit message to use compound page order
terminology
- Use EOPNOTSUPP instead of ENOTSUPP
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 97e0158d2aca..efffa8609b85 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 b4b72f233bf6..26be355aaa37 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_compound_order(ms);
+ const int order = section_compound_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) {
@@ -612,7 +612,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);
@@ -831,8 +831,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_compound_order(ms, 0);
+ }
}
static struct page * __meminit section_activate(int nid, unsigned long pfn,
@@ -842,8 +844,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_compound_order(ms))
+ return ERR_PTR(-EOPNOTSUPP);
+
if (!ms->usage) {
usage = kzalloc(mem_section_usage_size(), GFP_KERNEL);
if (!usage)
@@ -869,6 +876,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_compound_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] 16+ messages in thread* [PATCH v6 07/12] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (5 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 06/12] mm/sparse-vmemmap: set compound page order for device DAX Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 15:07 ` [PATCH] fixup! " Muchun Song
2026-09-30 14:06 ` [PATCH v6 08/12] mm/sparse-vmemmap: move vmemmap optimization helpers to a public header Muchun Song
` (5 subsequent siblings)
12 siblings, 1 reply; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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 supplies the compound page order recorded in section metadata before
vmemmap population. 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.
Each PTE mapping the shared device DAX tail page takes a page reference.
A sufficiently large range could therefore cycle the reference count back
to zero if population were allowed to continue after it became
non-positive. Use try_get_page() so further mappings fail at that point.
The section population error path tears down mappings created for the
failed section, while the warning makes this currently impractical limit
visible.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v6:
- Prevent shared DAX tail-page refcount overflow with try_get_page()
(suggested by Andrew Morton)
- Make order const and move it to the top of the function (suggested by
David Hildenbrand)
- Clarify why optimized tail pages must not be poisoned (suggested by
David Hildenbrand)
v3:
- Move device_zone() after the definition of NODE_DATA() to fix
non-NUMA builds.
- Update the commit message to describe the compound page order stored
in section metadata
- Collect Acked-by from Qi Zheng
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 | 6 ++--
mm/sparse-vmemmap.c | 63 +++++++++++++++++-------------------------
3 files changed, 40 insertions(+), 39 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index ee9cbaaa63f4..cd68c1904c91 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2143,11 +2143,21 @@ static inline int online_device_section(const struct mem_section *section)
return section && ((section->section_mem_map & flags) == flags);
}
+
+static inline struct zone *device_zone(int nid)
+{
+ return &NODE_DATA(nid)->node_zones[ZONE_DEVICE];
+}
#else
static inline int online_device_section(const struct mem_section *section)
{
return 0;
}
+
+static inline struct zone *device_zone(int nid)
+{
+ return NULL;
+}
#endif
static inline int online_section_nr(unsigned long nr)
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index b428da66d279..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,
@@ -554,8 +555,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 26be355aaa37..d40a2f5b5fca 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -225,6 +225,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);
@@ -288,14 +290,18 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
/*
* When a PTE/PMD entry is freed from the init_mm
* there's a free_pages() call to this page allocated
- * above. Thus this get_page() is paired with the
+ * above. Thus this try_get_page() is paired with the
* put_page_testzero() on the freeing path.
* This can only called by certain ZONE_DEVICE path,
* and through vmemmap_populate_compound_pages() when
* slab is available.
+ *
+ * Use try_get_page() to prevent the shared page refcount
+ * from overflowing.
*/
- if (flags & VMEMMAP_POPULATE_DAX)
- get_page(pfn_to_page(ptpfn));
+ if ((flags & VMEMMAP_POPULATE_DAX) &&
+ !try_get_page(pfn_to_page(ptpfn)))
+ return NULL;
}
entry = pfn_pte(ptpfn, PAGE_KERNEL);
set_pte_at(&init_mm, addr, pte, entry);
@@ -529,47 +535,27 @@ 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,
struct dev_pagemap *pgmap)
{
const unsigned long flags = VMEMMAP_POPULATE_DAX;
+ const unsigned int order = pfn_to_section_compound_order(start_pfn);
unsigned long size, addr;
pte_t *pte;
+ struct page *page;
int rc;
- 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;
@@ -585,12 +571,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;
}
@@ -922,13 +908,16 @@ 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.
+ * Poison uninitialized struct pages to catch invalid flag combinations.
+ *
+ * Tail struct pages in a vmemmap-optimized section are initialized and
+ * shared during vmemmap population, so they must not be overwritten here.
*/
- 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] 16+ messages in thread* [PATCH] fixup! mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
2026-09-30 14:06 ` [PATCH v6 07/12] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-30 15:07 ` Muchun Song
0 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 15:07 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
prep_compound_tail() makes PageCompound() true. Calling
__SetPageReserved() afterwards violates the PF_NO_COMPOUND policy and
triggers VM_BUG_ON_PGFLAGS() when CONFIG_DEBUG_VM_PGFLAGS is enabled.
Set PG_reserved before preparing the compound tail.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
mm/sparse-vmemmap.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 4be85e194c78..23b31bc4bf94 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -222,9 +222,9 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
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);
if (zone_is_zone_device(zone))
__SetPageReserved(page);
+ prep_compound_tail(page, NULL, order);
}
page = virt_to_page(addr);
--
2.54.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v6 08/12] mm/sparse-vmemmap: move vmemmap optimization helpers to a public header
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (6 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 07/12] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 09/12] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
` (4 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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 vmemmap-optimization.h.
This is a preparatory change for powerpc, which has its own vmemmap
optimization implementation and needs to use the common vmemmap
optimization interfaces from architecture code.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v6:
- Collect Acked-by from David Hildenbrand
v5:
- Move the VMEMMAP_OPTIMIZATION_* macros and MAX_FOLIO_VMEMMAP_ALIGN to
vmemmap-optimization.h
v4:
- Use the renamed VMEMMAP_OPTIMIZATION config in the public header
- Collect Acked-by from Mike Rapoport
v3:
- Update the subject and commit message to describe common vmemmap
optimization helpers
- Collect Acked-by from Qi Zheng
v2:
- Fix missing header dependencies.
---
MAINTAINERS | 1 +
arch/loongarch/include/asm/pgtable.h | 1 +
arch/riscv/mm/init.c | 1 +
include/linux/mmzone.h | 17 -----
include/linux/vmemmap-optimization.h | 109 +++++++++++++++++++++++++++
mm/hugetlb.c | 2 +-
mm/hugetlb_vmemmap.c | 2 +-
mm/sparse.h | 78 +------------------
8 files changed, 115 insertions(+), 96 deletions(-)
create mode 100644 include/linux/vmemmap-optimization.h
diff --git a/MAINTAINERS b/MAINTAINERS
index fe1d70ed5100..4689a0210060 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12097,6 +12097,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/arch/loongarch/include/asm/pgtable.h b/arch/loongarch/include/asm/pgtable.h
index cf29a4c8ac59..f87603135131 100644
--- a/arch/loongarch/include/asm/pgtable.h
+++ b/arch/loongarch/include/asm/pgtable.h
@@ -72,6 +72,7 @@
#include <linux/mm_types.h>
#include <linux/mmzone.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/fixmap.h>
#include <asm/sparsemem.h>
diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c
index fb37b0b67efe..857f9a55039c 100644
--- a/arch/riscv/mm/init.c
+++ b/arch/riscv/mm/init.c
@@ -22,6 +22,7 @@
#include <linux/hugetlb.h>
#include <linux/kfence.h>
#include <linux/execmem.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/alternative.h>
#include <asm/fixmap.h>
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index cd68c1904c91..65de3bb13eb3 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -96,23 +96,6 @@
#define MAX_FOLIO_NR_PAGES (1UL << MAX_FOLIO_ORDER)
-/*
- * HugeTLB Vmemmap Optimization (HVO) requires struct pages of the head page to
- * be naturally aligned with regard to the folio size.
- *
- * HVO which is only active if the size of struct page is a power of 2.
- */
-#define MAX_FOLIO_VMEMMAP_ALIGN \
- (IS_ENABLED(CONFIG_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. */
-#define VMEMMAP_OPTIMIZATION_PAGES 1
-#define VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES \
- (VMEMMAP_OPTIMIZATION_PAGES * PAGE_SIZE / sizeof(struct page))
-#define VMEMMAP_OPTIMIZATION_MIN_ORDER (ilog2(VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES) + 1)
-
enum migratetype {
MIGRATE_UNMOVABLE,
MIGRATE_MOVABLE,
diff --git a/include/linux/vmemmap-optimization.h b/include/linux/vmemmap-optimization.h
new file mode 100644
index 000000000000..bd0974b262a4
--- /dev/null
+++ b/include/linux/vmemmap-optimization.h
@@ -0,0 +1,109 @@
+/* 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>
+
+/*
+ * HugeTLB Vmemmap Optimization (HVO) requires struct pages of the head page to
+ * be naturally aligned with regard to the folio size.
+ *
+ * HVO which is only active if the size of struct page is a power of 2.
+ */
+#define MAX_FOLIO_VMEMMAP_ALIGN \
+ (IS_ENABLED(CONFIG_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. */
+#define VMEMMAP_OPTIMIZATION_PAGES 1
+#define VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES \
+ (VMEMMAP_OPTIMIZATION_PAGES * PAGE_SIZE / sizeof(struct page))
+#define VMEMMAP_OPTIMIZATION_MIN_ORDER (ilog2(VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES) + 1)
+
+#ifdef CONFIG_VMEMMAP_OPTIMIZATION
+static inline unsigned int section_compound_order(const struct mem_section *section)
+{
+ return section->compound_page_order;
+}
+
+static inline void section_set_compound_order(struct mem_section *section,
+ unsigned int order)
+{
+ VM_WARN_ON(section_compound_order(section) && order &&
+ section_compound_order(section) != order);
+ section->compound_page_order = order;
+}
+
+static inline void section_set_compound_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_compound_order(__nr_to_section(section_nr + i), order);
+}
+
+static inline unsigned int pfn_to_section_compound_order(unsigned long pfn)
+{
+ return section_compound_order(__pfn_to_section(pfn));
+}
+
+struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
+#else
+static inline unsigned int section_compound_order(const struct mem_section *section)
+{
+ return 0;
+}
+
+static inline void section_set_compound_order(struct mem_section *section,
+ unsigned int order)
+{
+}
+
+static inline void section_set_compound_order_range(unsigned long pfn,
+ unsigned long nr_pages, unsigned int order)
+{
+}
+
+static inline unsigned int pfn_to_section_compound_order(unsigned long pfn)
+{
+ return 0;
+}
+#endif /* CONFIG_VMEMMAP_OPTIMIZATION */
+
+static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
+{
+ const unsigned int order = pfn_to_section_compound_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_VMEMMAP_OPTIMIZATION))
+ return false;
+
+ if (!is_power_of_2(sizeof(struct page)))
+ return false;
+
+ return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
+}
+#endif /* _LINUX_VMEMMAP_OPTIMIZATION_H */
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index fd00141b089a..2003439ea13c 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 76765c97ff68..0057fa2a16a8 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"
/**
* struct vmemmap_remap_walk - walk vmemmap page table
diff --git a/mm/sparse.h b/mm/sparse.h
index 326ad43bb5c3..a5111087ee3a 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -9,80 +9,7 @@
#define __MM_SPARSE_H
#include <linux/mmzone.h>
-
-#ifdef CONFIG_VMEMMAP_OPTIMIZATION
-static inline unsigned int section_compound_order(const struct mem_section *section)
-{
- return section->compound_page_order;
-}
-
-static inline void section_set_compound_order(struct mem_section *section,
- unsigned int order)
-{
- VM_WARN_ON(section_compound_order(section) && order &&
- section_compound_order(section) != order);
- section->compound_page_order = order;
-}
-
-static inline void section_set_compound_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_compound_order(__nr_to_section(section_nr + i), order);
-}
-
-static inline unsigned int pfn_to_section_compound_order(unsigned long pfn)
-{
- return section_compound_order(__pfn_to_section(pfn));
-}
-#else
-static inline unsigned int section_compound_order(const struct mem_section *section)
-{
- return 0;
-}
-
-static inline void section_set_compound_order(struct mem_section *section,
- unsigned int order)
-{
-}
-
-static inline void section_set_compound_order_range(unsigned long pfn,
- unsigned long nr_pages, unsigned int order)
-{
-}
-
-static inline unsigned int pfn_to_section_compound_order(unsigned long pfn)
-{
- return 0;
-}
-#endif
-
-static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
-{
- const unsigned int order = pfn_to_section_compound_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_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
@@ -142,9 +69,6 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
-#ifdef CONFIG_VMEMMAP_OPTIMIZATION
-struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
-#endif
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] 16+ messages in thread* [PATCH v6 09/12] powerpc/mm: switch device DAX to shared tail vmemmap pages
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (7 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 08/12] mm/sparse-vmemmap: move vmemmap optimization helpers to a public header Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 15:28 ` [PATCH] fixup! " Muchun Song
2026-09-30 14:06 ` [PATCH v6 10/12] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
` (3 subsequent siblings)
12 siblings, 1 reply; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v6:
- Collect Acked-by from David Hildenbrand
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 80 +++---------------------
include/linux/vmemmap-optimization.h | 6 ++
mm/sparse-vmemmap.c | 6 --
3 files changed, 15 insertions(+), 77 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index cf692b2b5f7b..ee068f24a79f 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_compound_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);
diff --git a/include/linux/vmemmap-optimization.h b/include/linux/vmemmap-optimization.h
index bd0974b262a4..fa9e9abd6656 100644
--- a/include/linux/vmemmap-optimization.h
+++ b/include/linux/vmemmap-optimization.h
@@ -83,6 +83,12 @@ static inline unsigned int pfn_to_section_compound_order(unsigned long pfn)
{
return 0;
}
+
+static inline struct page *vmemmap_shared_tail_page(unsigned int order,
+ struct zone *zone)
+{
+ return NULL;
+}
#endif /* CONFIG_VMEMMAP_OPTIMIZATION */
static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index d40a2f5b5fca..e1f8a03e3d49 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -240,12 +240,6 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
return page;
}
-#else
-static inline struct page *vmemmap_shared_tail_page(unsigned int order,
- struct zone *zone)
-{
- return NULL;
-}
#endif
static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
--
2.54.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH] fixup! powerpc/mm: switch device DAX to shared tail vmemmap pages
2026-09-30 14:06 ` [PATCH v6 09/12] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-30 15:28 ` Muchun Song
0 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 15:28 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
The PowerPC radix path takes one reference for every PTE that maps the
shared tail page. Unlike the generic path, get_page() lets the counter
continue after it becomes non-positive and eventually cycle to zero.
Use try_get_page() so vmemmap population fails before the shared page
reference count can cycle, matching the generic implementation.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index 9ca28e4a610a..a3332f32ffb2 100644
--- a/arch/powerpc/mm/book3s64/radix_pgtable.c
+++ b/arch/powerpc/mm/book3s64/radix_pgtable.c
@@ -1042,13 +1042,17 @@ static pte_t * __meminit radix__vmemmap_pte_populate(pmd_t *pmdp, unsigned long
/*
* When a PTE/PMD entry is freed from the init_mm
* there's a free_pages() call to this page allocated
- * above. Thus this get_page() is paired with the
+ * above. Thus this try_get_page() is paired with the
* put_page_testzero() on the freeing path.
* This can only called by certain ZONE_DEVICE path,
* and through vmemmap_populate_compound_pages() when
* slab is available.
+ *
+ * Use try_get_page() to prevent the shared page refcount
+ * from overflowing.
*/
- get_page(reuse);
+ if (!try_get_page(reuse))
+ return NULL;
p = page_to_virt(reuse);
pr_debug("Tail page reuse vmemmap mapping\n");
}
--
2.54.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v6 10/12] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (8 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 09/12] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 11/12] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
` (2 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v6:
- Collect Acked-by from David Hildenbrand
v3:
- Collect Acked-by from Qi Zheng
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 46 ++----------------------
include/linux/mm.h | 4 +--
mm/mm_init.c | 2 +-
mm/sparse-vmemmap.c | 13 ++-----
4 files changed, 8 insertions(+), 57 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index ee068f24a79f..9ca28e4a610a 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 070ce27e9cd3..30a3365bca82 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -38,6 +38,7 @@
#include <linux/bitops.h>
#include <linux/iommu-debug-pagealloc.h>
#include <linux/kcsan-checks.h>
+#include <linux/vmemmap-optimization.h>
struct mempolicy;
struct anon_vma;
@@ -5167,7 +5168,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 +5187,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 efffa8609b85..56bb4567a494 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 e1f8a03e3d49..4cea94126cf6 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_compound_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;
}
@@ -558,17 +557,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] 16+ messages in thread* [PATCH v6 11/12] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (9 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 10/12] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 14:06 ` [PATCH v6 12/12] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
2026-09-30 19:01 ` [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Andrew Morton
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v6:
- Collect Acked-by from David Hildenbrand
v3:
- Collect Acked-by from Qi Zheng
---
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 4cea94126cf6..4be85e194c78 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_compound_order(ms);
@@ -674,7 +673,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;
}
@@ -685,7 +684,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);
}
@@ -695,8 +694,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 cc28bb41fdb1..b75921c622ed 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -250,8 +250,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 a5111087ee3a..530692cdd516 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] 16+ messages in thread* [PATCH v6 12/12] Documentation/mm: update DAX vmemmap deduplication docs
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (10 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 11/12] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
@ 2026-09-30 14:06 ` Muchun Song
2026-09-30 19:01 ` [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Andrew Morton
12 siblings, 0 replies; 16+ messages in thread
From: Muchun Song @ 2026-09-30 14:06 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, Ritesh Harjani, Shrikanth Hegde, Randy Dunlap,
Muchun Song, Lance Yang
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>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v6:
- Collect Acked-by from David Hildenbrand
v3:
- Collect Acked-by from Qi Zheng
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] 16+ messages in thread* Re: [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization
2026-09-30 14:06 [PATCH v6 00/12] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (11 preceding siblings ...)
2026-09-30 14:06 ` [PATCH v6 12/12] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
@ 2026-09-30 19:01 ` Andrew Morton
12 siblings, 0 replies; 16+ messages in thread
From: Andrew Morton @ 2026-09-30 19:01 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,
Ritesh Harjani, Shrikanth Hegde, Randy Dunlap, Lance Yang
On Wed, 30 Sep 2026 22:06:15 +0800 Muchun Song <songmuchun@bytedance.com> wrote:
> This version is based on mm-new commit a878c908dc92.
>
> 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
> records the compound page order from pgmap->vmemmap_shift in section
> metadata 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.
>
Thanks. I updated mm-unstable to this version.
>
> v6:
> - Use try_get_page() to prevent the shared device DAX tail-page
> reference count from overflowing (suggested by Andrew Morton,
> reported by Sashiko)
> - Move constant declarations to the top of their functions and fold
> the shared tail-page array lookup into vmemmap_tails() (suggested by
> David Hildenbrand)
> - Clarify the DAX population flag description and why optimized tail
> pages must not be poisoned (suggested by David Hildenbrand)
> - Collect Acked-by tags from David Hildenbrand
> - Rebase onto mm/mm-new
Here's how v6 altered mm.git:
arch/powerpc/mm/book3s64/radix_pgtable.c | 8 +++--
mm/sparse-vmemmap.c | 32 ++++++++++++---------
2 files changed, 25 insertions(+), 15 deletions(-)
--- a/arch/powerpc/mm/book3s64/radix_pgtable.c~b
+++ a/arch/powerpc/mm/book3s64/radix_pgtable.c
@@ -1042,13 +1042,17 @@ static pte_t * __meminit radix__vmemmap_
/*
* When a PTE/PMD entry is freed from the init_mm
* there's a free_pages() call to this page allocated
- * above. Thus this get_page() is paired with the
+ * above. Thus this try_get_page() is paired with the
* put_page_testzero() on the freeing path.
* This can only called by certain ZONE_DEVICE path,
* and through vmemmap_populate_compound_pages() when
* slab is available.
+ *
+ * Use try_get_page() to prevent the shared page refcount
+ * from overflowing.
*/
- get_page(reuse);
+ if (!try_get_page(reuse))
+ return NULL;
p = page_to_virt(reuse);
pr_debug("Tail page reuse vmemmap mapping\n");
}
--- a/mm/sparse-vmemmap.c~b
+++ a/mm/sparse-vmemmap.c
@@ -170,10 +170,14 @@ static void * __meminit vmemmap_alloc_bl
#ifdef CONFIG_VMEMMAP_OPTIMIZATION
#define VMEMMAP_OPTIMIZATION_NR_ORDERS (MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
-static __ref struct page **vmemmap_tails_alloc(struct zone *zone)
+static __ref struct page **vmemmap_tails(struct zone *zone)
{
- struct page **pages;
- const size_t size = array_size(VMEMMAP_OPTIMIZATION_NR_ORDERS, sizeof(*pages));
+ const size_t size = array_size(VMEMMAP_OPTIMIZATION_NR_ORDERS,
+ sizeof(*zone->vmemmap_tails));
+ struct page **pages = READ_ONCE(zone->vmemmap_tails);
+
+ if (pages)
+ return pages;
pages = slab_is_available() ? kzalloc_objs(*pages, VMEMMAP_OPTIMIZATION_NR_ORDERS) :
memblock_alloc(size, __alignof__(*pages));
@@ -193,19 +197,19 @@ static __ref struct page **vmemmap_tails
struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zone)
{
- void *addr;
- struct page *page, **pages;
const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
+ struct page *page, **pages;
+ void *addr;
if (WARN_ON_ONCE(idx >= VMEMMAP_OPTIMIZATION_NR_ORDERS))
return NULL;
- pages = READ_ONCE(zone->vmemmap_tails) ? : vmemmap_tails_alloc(zone);
+ pages = vmemmap_tails(zone);
if (!pages)
return NULL;
page = READ_ONCE(pages[idx]);
- if (likely(page))
+ if (page)
return page;
addr = vmemmap_alloc_block(PAGE_SIZE, zone_to_nid(zone));
@@ -218,9 +222,9 @@ struct page __ref *vmemmap_shared_tail_p
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);
if (zone_is_zone_device(zone))
__SetPageReserved(page);
+ prep_compound_tail(page, NULL, order);
}
page = virt_to_page(addr);
@@ -528,12 +532,12 @@ static int __meminit vmemmap_populate_co
unsigned long end, int node,
struct dev_pagemap *pgmap)
{
+ const unsigned long flags = VMEMMAP_POPULATE_DAX;
+ const unsigned int order = pfn_to_section_compound_order(start_pfn);
unsigned long size, addr;
pte_t *pte;
- int rc;
- unsigned long flags = VMEMMAP_POPULATE_DAX;
struct page *page;
- unsigned int order = pfn_to_section_compound_order(start_pfn);
+ int rc;
page = vmemmap_shared_tail_page(order, device_zone(node));
if (!page)
@@ -891,8 +895,10 @@ int __meminit sparse_add_section(int nid
ms = __nr_to_section(section_nr);
/*
- * Poison uninitialized struct pages in order to catch invalid flags
- * combinations.
+ * Poison uninitialized struct pages to catch invalid flag combinations.
+ *
+ * Tail struct pages in a vmemmap-optimized section are initialized and
+ * shared during vmemmap population, so they must not be overwritten here.
*/
if (!section_vmemmap_optimizable(ms))
page_init_poison(memmap, sizeof(struct page) * nr_pages);
_
^ permalink raw reply [flat|nested] 16+ messages in thread