* [PATCH v3 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-14 7:48 ` Mike Rapoport
2026-09-11 5:02 ` [PATCH v3 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
` (9 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The section-based vmemmap optimization infrastructure is still guarded by
CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP, but it also can be used by device
DAX. Introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION as a common config
for the shared infrastructure.
Select the new option from HUGETLB_PAGE_OPTIMIZE_VMEMMAP and from
DEV_DAX when the architecture opts in to DAX vmemmap optimization, and
use it to guard the generic sparse-vmemmap state and helpers.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v2:
- Fix SPARSEMEM_VMEMMAP_OPTIMIZATION being selected without SPARSEMEM_VMEMMAP
reported by Sashiko.
- Add an explicit DEV_DAX dependency on ZONE_DEVICE
- Collect Acked-by from Qi Zheng
---
arch/x86/entry/vdso/vdso32/fake_32bit_build.h | 2 +-
drivers/dax/Kconfig | 2 ++
fs/Kconfig | 1 +
include/linux/mm.h | 3 +++
include/linux/mmzone.h | 13 +++++++------
include/linux/page-flags.h | 5 ++---
mm/Kconfig | 4 ++++
mm/sparse.h | 4 ++--
8 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/arch/x86/entry/vdso/vdso32/fake_32bit_build.h b/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
index bc3e549795c3..5f8424eade2b 100644
--- a/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
+++ b/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
@@ -11,7 +11,7 @@
#undef CONFIG_PGTABLE_LEVELS
#undef CONFIG_ILLEGAL_POINTER_VALUE
#undef CONFIG_SPARSEMEM_VMEMMAP
-#undef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#undef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
#undef CONFIG_NR_CPUS
#undef CONFIG_PARAVIRT_XXL
diff --git a/drivers/dax/Kconfig b/drivers/dax/Kconfig
index 602f9a0839a9..6250954b0fa7 100644
--- a/drivers/dax/Kconfig
+++ b/drivers/dax/Kconfig
@@ -8,6 +8,8 @@ if DAX
config DEV_DAX
tristate "Device DAX: direct access mapping device"
depends on TRANSPARENT_HUGEPAGE
+ depends on ZONE_DEVICE
+ select SPARSEMEM_VMEMMAP_OPTIMIZATION if ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
help
Support raw access to differentiated (persistence, bandwidth,
latency...) memory via an mmap(2) capable character
diff --git a/fs/Kconfig b/fs/Kconfig
index d1c210c6508f..9b32ce79cc80 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -278,6 +278,7 @@ config HUGETLB_PAGE_OPTIMIZE_VMEMMAP
def_bool HUGETLB_PAGE
depends on ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP
depends on SPARSEMEM_VMEMMAP
+ select SPARSEMEM_VMEMMAP_OPTIMIZATION
config HUGETLB_PMD_PAGE_TABLE_SHARING
def_bool HUGETLB_PAGE
diff --git a/include/linux/mm.h b/include/linux/mm.h
index c49ef99b4413..a2ebe87e7654 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5175,6 +5175,9 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
unsigned long nr_pages;
unsigned long nr_vmemmap_pages;
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
+ return false;
+
if (!pgmap || !is_power_of_2(sizeof(struct page)))
return false;
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index acd94cecc0d3..97511f651ebc 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -102,9 +102,9 @@
*
* HVO which is only active if the size of struct page is a power of 2.
*/
-#define MAX_FOLIO_VMEMMAP_ALIGN \
- (IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) && \
- is_power_of_2(sizeof(struct page)) ? \
+#define MAX_FOLIO_VMEMMAP_ALIGN \
+ (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION) && \
+ is_power_of_2(sizeof(struct page)) ? \
MAX_FOLIO_NR_PAGES * sizeof(struct page) : 0)
/* The number of retained vmemmap pages with HVO enabled. */
@@ -116,7 +116,8 @@
#define __VMEMMAP_OPTIMIZATION_NR_ORDERS \
(MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
#define VMEMMAP_OPTIMIZATION_NR_ORDERS \
- (__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
+ ((__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 && \
+ IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION)) ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
enum migratetype {
MIGRATE_UNMOVABLE,
@@ -1155,7 +1156,7 @@ struct zone {
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
#endif
} ____cacheline_internodealigned_in_smp;
@@ -2019,7 +2020,7 @@ struct mem_section {
unsigned long section_mem_map;
struct mem_section_usage *usage;
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
/*
* Normally, sections hold regular (order-0) pages. However, for
* sections with HVO enabled, this tracks the compound page order
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 86dd0470da11..462e89e05548 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -208,14 +208,13 @@ enum pageflags {
static __always_inline bool compound_info_has_mask(void)
{
/*
- * Limit mask usage to HugeTLB vmemmap optimization (HVO) where it
- * makes a difference.
+ * Limit mask usage to HVO where it makes a difference.
*
* The approach with mask would work in the wider set of conditions,
* but it requires validating that struct pages are naturally aligned
* for all orders up to the MAX_FOLIO_ORDER, which can be tricky.
*/
- if (!IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP))
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
return false;
return is_power_of_2(sizeof(struct page));
diff --git a/mm/Kconfig b/mm/Kconfig
index bc7befafb47b..c180d40cd671 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -461,6 +461,10 @@ config SPARSEMEM_VMEMMAP
pfn_to_page and page_to_pfn operations. This is the most
efficient option when sufficient kernel resources are available.
+config SPARSEMEM_VMEMMAP_OPTIMIZATION
+ bool
+ depends on SPARSEMEM_VMEMMAP
+
#
# Select this config option from the architecture Kconfig, if it is preferred
# to enable the feature of HugeTLB/dev_dax vmemmap optimization.
diff --git a/mm/sparse.h b/mm/sparse.h
index d3a71ef4fad0..3151d4db7575 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -10,7 +10,7 @@
#include <linux/mmzone.h>
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
static inline unsigned int section_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_SPARSEMEM_VMEMMAP_OPTIMIZATION))
return false;
if (!is_power_of_2(sizeof(struct page)))
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v3 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
2026-09-11 5:02 ` [PATCH v3 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
@ 2026-09-14 7:48 ` Mike Rapoport
2026-09-14 7:51 ` Muchun Song
0 siblings, 1 reply; 17+ messages in thread
From: Mike Rapoport @ 2026-09-14 7:48 UTC (permalink / raw)
To: Muchun Song
Cc: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
Hi,
> The section-based vmemmap optimization infrastructure is still guarded by
> CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP, but it also can be used by device
> DAX. Introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION as a common config
CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION is a bit mouthful :)
I think that dropping _SPARSEMEM won't hurt readability.
> for the shared infrastructure.
>
> Select the new option from HUGETLB_PAGE_OPTIMIZE_VMEMMAP and from
> DEV_DAX when the architecture opts in to DAX vmemmap optimization, and
> use it to guard the generic sparse-vmemmap state and helpers.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Acked-by: Qi Zheng <qi.zheng@linux.dev>
>
> diff --git a/arch/x86/entry/vdso/vdso32/fake_32bit_build.h b/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
> index bc3e549795c3f..5f8424eade2bd 100644
> --- a/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
> +++ b/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
> @@ -11,7 +11,7 @@
> #undef CONFIG_PGTABLE_LEVELS
> #undef CONFIG_ILLEGAL_POINTER_VALUE
> #undef CONFIG_SPARSEMEM_VMEMMAP
> -#undef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> +#undef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> #undef CONFIG_NR_CPUS
> #undef CONFIG_PARAVIRT_XXL
>
> diff --git a/drivers/dax/Kconfig b/drivers/dax/Kconfig
> index 602f9a0839a91..6250954b0fa7a 100644
> --- a/drivers/dax/Kconfig
> +++ b/drivers/dax/Kconfig
> @@ -8,6 +8,8 @@ if DAX
> config DEV_DAX
> tristate "Device DAX: direct access mapping device"
> depends on TRANSPARENT_HUGEPAGE
> + depends on ZONE_DEVICE
> + select SPARSEMEM_VMEMMAP_OPTIMIZATION if ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
> help
> Support raw access to differentiated (persistence, bandwidth,
> latency...) memory via an mmap(2) capable character
> diff --git a/fs/Kconfig b/fs/Kconfig
> index d1c210c6508f0..9b32ce79cc805 100644
> --- a/fs/Kconfig
> +++ b/fs/Kconfig
> @@ -278,6 +278,7 @@ config HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> def_bool HUGETLB_PAGE
> depends on ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP
> depends on SPARSEMEM_VMEMMAP
> + select SPARSEMEM_VMEMMAP_OPTIMIZATION
>
> config HUGETLB_PMD_PAGE_TABLE_SHARING
> def_bool HUGETLB_PAGE
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index c49ef99b4413b..a2ebe87e76546 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -5175,6 +5175,9 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
> unsigned long nr_pages;
> unsigned long nr_vmemmap_pages;
>
> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
> + return false;
> +
> if (!pgmap || !is_power_of_2(sizeof(struct page)))
> return false;
>
> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
> index acd94cecc0d39..97511f651ebc0 100644
> --- a/include/linux/mmzone.h
> +++ b/include/linux/mmzone.h
> @@ -102,9 +102,9 @@
> *
> * HVO which is only active if the size of struct page is a power of 2.
> */
> -#define MAX_FOLIO_VMEMMAP_ALIGN \
> - (IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) && \
> - is_power_of_2(sizeof(struct page)) ? \
> +#define MAX_FOLIO_VMEMMAP_ALIGN \
> + (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION) && \
> + is_power_of_2(sizeof(struct page)) ? \
> MAX_FOLIO_NR_PAGES * sizeof(struct page) : 0)
>
> /* The number of retained vmemmap pages with HVO enabled. */
> @@ -116,7 +116,8 @@
> #define __VMEMMAP_OPTIMIZATION_NR_ORDERS \
> (MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
> #define VMEMMAP_OPTIMIZATION_NR_ORDERS \
> - (__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
> + ((__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 && \
> + IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION)) ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
>
> enum migratetype {
> MIGRATE_UNMOVABLE,
> @@ -1155,7 +1156,7 @@ struct zone {
> /* Zone statistics */
> atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
> atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
> #endif
> } ____cacheline_internodealigned_in_smp;
> @@ -2019,7 +2020,7 @@ struct mem_section {
> unsigned long section_mem_map;
>
> struct mem_section_usage *usage;
> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> /*
> * Normally, sections hold regular (order-0) pages. However, for
> * sections with HVO enabled, this tracks the compound page order
> diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
> index 86dd0470da117..462e89e055485 100644
> --- a/include/linux/page-flags.h
> +++ b/include/linux/page-flags.h
> @@ -208,14 +208,13 @@ enum pageflags {
> static __always_inline bool compound_info_has_mask(void)
> {
> /*
> - * Limit mask usage to HugeTLB vmemmap optimization (HVO) where it
> - * makes a difference.
> + * Limit mask usage to HVO where it makes a difference.
> *
> * The approach with mask would work in the wider set of conditions,
> * but it requires validating that struct pages are naturally aligned
> * for all orders up to the MAX_FOLIO_ORDER, which can be tricky.
> */
> - if (!IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP))
> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
> return false;
>
> return is_power_of_2(sizeof(struct page));
> diff --git a/mm/Kconfig b/mm/Kconfig
> index bc7befafb47b5..c180d40cd6712 100644
> --- a/mm/Kconfig
> +++ b/mm/Kconfig
> @@ -461,6 +461,10 @@ config SPARSEMEM_VMEMMAP
> pfn_to_page and page_to_pfn operations. This is the most
> efficient option when sufficient kernel resources are available.
>
> +config SPARSEMEM_VMEMMAP_OPTIMIZATION
> + bool
> + depends on SPARSEMEM_VMEMMAP
> +
> #
> # Select this config option from the architecture Kconfig, if it is preferred
> # to enable the feature of HugeTLB/dev_dax vmemmap optimization.
> diff --git a/mm/sparse.h b/mm/sparse.h
> index d3a71ef4fad0f..3151d4db75753 100644
> --- a/mm/sparse.h
> +++ b/mm/sparse.h
> @@ -10,7 +10,7 @@
>
> #include <linux/mmzone.h>
>
> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> static inline unsigned int section_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_SPARSEMEM_VMEMMAP_OPTIMIZATION))
> return false;
>
> if (!is_power_of_2(sizeof(struct page)))
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v3 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
2026-09-14 7:48 ` Mike Rapoport
@ 2026-09-14 7:51 ` Muchun Song
0 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-09-14 7:51 UTC (permalink / raw)
To: Mike Rapoport
Cc: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Lorenzo Stoakes, Qi Zheng,
Nicholas Piggin, Christophe Leroy, Randy Dunlap
> On Sep 14, 2026, at 15:48, Mike Rapoport <rppt@kernel.org> wrote:
>
> Hi,
Hi,
>
>> The section-based vmemmap optimization infrastructure is still guarded by
>> CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP, but it also can be used by device
>> DAX. Introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION as a common config
>
> CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION is a bit mouthful :)
>
> I think that dropping _SPARSEMEM won't hurt readability.
Yes, I'll simplify it in v4.
>
>> for the shared infrastructure.
>>
>> Select the new option from HUGETLB_PAGE_OPTIMIZE_VMEMMAP and from
>> DEV_DAX when the architecture opts in to DAX vmemmap optimization, and
>> use it to guard the generic sparse-vmemmap state and helpers.
>>
>> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
>> Acked-by: Qi Zheng <qi.zheng@linux.dev>
>>
>> diff --git a/arch/x86/entry/vdso/vdso32/fake_32bit_build.h b/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
>> index bc3e549795c3f..5f8424eade2bd 100644
>> --- a/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
>> +++ b/arch/x86/entry/vdso/vdso32/fake_32bit_build.h
>> @@ -11,7 +11,7 @@
>> #undef CONFIG_PGTABLE_LEVELS
>> #undef CONFIG_ILLEGAL_POINTER_VALUE
>> #undef CONFIG_SPARSEMEM_VMEMMAP
>> -#undef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
>> +#undef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
>> #undef CONFIG_NR_CPUS
>> #undef CONFIG_PARAVIRT_XXL
>>
>> diff --git a/drivers/dax/Kconfig b/drivers/dax/Kconfig
>> index 602f9a0839a91..6250954b0fa7a 100644
>> --- a/drivers/dax/Kconfig
>> +++ b/drivers/dax/Kconfig
>> @@ -8,6 +8,8 @@ if DAX
>> config DEV_DAX
>> tristate "Device DAX: direct access mapping device"
>> depends on TRANSPARENT_HUGEPAGE
>> + depends on ZONE_DEVICE
>> + select SPARSEMEM_VMEMMAP_OPTIMIZATION if ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
>> help
>> Support raw access to differentiated (persistence, bandwidth,
>> latency...) memory via an mmap(2) capable character
>> diff --git a/fs/Kconfig b/fs/Kconfig
>> index d1c210c6508f0..9b32ce79cc805 100644
>> --- a/fs/Kconfig
>> +++ b/fs/Kconfig
>> @@ -278,6 +278,7 @@ config HUGETLB_PAGE_OPTIMIZE_VMEMMAP
>> def_bool HUGETLB_PAGE
>> depends on ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP
>> depends on SPARSEMEM_VMEMMAP
>> + select SPARSEMEM_VMEMMAP_OPTIMIZATION
>>
>> config HUGETLB_PMD_PAGE_TABLE_SHARING
>> def_bool HUGETLB_PAGE
>> diff --git a/include/linux/mm.h b/include/linux/mm.h
>> index c49ef99b4413b..a2ebe87e76546 100644
>> --- a/include/linux/mm.h
>> +++ b/include/linux/mm.h
>> @@ -5175,6 +5175,9 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
>> unsigned long nr_pages;
>> unsigned long nr_vmemmap_pages;
>>
>> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
>> + return false;
>> +
>> if (!pgmap || !is_power_of_2(sizeof(struct page)))
>> return false;
>>
>> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
>> index acd94cecc0d39..97511f651ebc0 100644
>> --- a/include/linux/mmzone.h
>> +++ b/include/linux/mmzone.h
>> @@ -102,9 +102,9 @@
>> *
>> * HVO which is only active if the size of struct page is a power of 2.
>> */
>> -#define MAX_FOLIO_VMEMMAP_ALIGN \
>> - (IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) && \
>> - is_power_of_2(sizeof(struct page)) ? \
>> +#define MAX_FOLIO_VMEMMAP_ALIGN \
>> + (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION) && \
>> + is_power_of_2(sizeof(struct page)) ? \
>> MAX_FOLIO_NR_PAGES * sizeof(struct page) : 0)
>>
>> /* The number of retained vmemmap pages with HVO enabled. */
>> @@ -116,7 +116,8 @@
>> #define __VMEMMAP_OPTIMIZATION_NR_ORDERS \
>> (MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
>> #define VMEMMAP_OPTIMIZATION_NR_ORDERS \
>> - (__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
>> + ((__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 && \
>> + IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION)) ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
>>
>> enum migratetype {
>> MIGRATE_UNMOVABLE,
>> @@ -1155,7 +1156,7 @@ struct zone {
>> /* Zone statistics */
>> atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
>> atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
>> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
>> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
>> struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
>> #endif
>> } ____cacheline_internodealigned_in_smp;
>> @@ -2019,7 +2020,7 @@ struct mem_section {
>> unsigned long section_mem_map;
>>
>> struct mem_section_usage *usage;
>> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
>> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
>> /*
>> * Normally, sections hold regular (order-0) pages. However, for
>> * sections with HVO enabled, this tracks the compound page order
>> diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
>> index 86dd0470da117..462e89e055485 100644
>> --- a/include/linux/page-flags.h
>> +++ b/include/linux/page-flags.h
>> @@ -208,14 +208,13 @@ enum pageflags {
>> static __always_inline bool compound_info_has_mask(void)
>> {
>> /*
>> - * Limit mask usage to HugeTLB vmemmap optimization (HVO) where it
>> - * makes a difference.
>> + * Limit mask usage to HVO where it makes a difference.
>> *
>> * The approach with mask would work in the wider set of conditions,
>> * but it requires validating that struct pages are naturally aligned
>> * for all orders up to the MAX_FOLIO_ORDER, which can be tricky.
>> */
>> - if (!IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP))
>> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
>> return false;
>>
>> return is_power_of_2(sizeof(struct page));
>> diff --git a/mm/Kconfig b/mm/Kconfig
>> index bc7befafb47b5..c180d40cd6712 100644
>> --- a/mm/Kconfig
>> +++ b/mm/Kconfig
>> @@ -461,6 +461,10 @@ config SPARSEMEM_VMEMMAP
>> pfn_to_page and page_to_pfn operations. This is the most
>> efficient option when sufficient kernel resources are available.
>>
>> +config SPARSEMEM_VMEMMAP_OPTIMIZATION
>> + bool
>> + depends on SPARSEMEM_VMEMMAP
>> +
>> #
>> # Select this config option from the architecture Kconfig, if it is preferred
>> # to enable the feature of HugeTLB/dev_dax vmemmap optimization.
>> diff --git a/mm/sparse.h b/mm/sparse.h
>> index d3a71ef4fad0f..3151d4db75753 100644
>> --- a/mm/sparse.h
>> +++ b/mm/sparse.h
>> @@ -10,7 +10,7 @@
>>
>> #include <linux/mmzone.h>
>>
>> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
>> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
>> static inline unsigned int section_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_SPARSEMEM_VMEMMAP_OPTIMIZATION))
>> return false;
>>
>> if (!is_power_of_2(sizeof(struct page)))
>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Thank you Mike.
>
> --
> Sincerely yours,
> Mike.
>
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v3 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-09-11 5:02 ` [PATCH v3 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-14 7:48 ` Mike Rapoport
2026-09-11 5:02 ` [PATCH v3 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
` (8 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
HugeTLB and sparse-vmemmap each have their own helper to allocate the
shared vmemmap tail page used by vmemmap optimization.
Factor that logic into a common vmemmap_shared_tail_page() helper. It
allocates the page through vmemmap_alloc_block(), and uses cmpxchg()
to install the per-zone shared page.
Expose zone->vmemmap_tails under CONFIG_SPARSEMEM_VMEMMAP to match the
shared helper's build condition. This avoids a
!CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION stub; when optimization is
disabled, the array has no entries and the compiler folds away the unused
paths, so no storage or runtime overhead is added.
This removes duplicate allocation logic while still handling both the
early boot and runtime paths through the same helper.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v2:
- Collect Acked-by from Qi Zheng
---
include/linux/mmzone.h | 2 +-
mm/hugetlb_vmemmap.c | 28 +---------------
mm/sparse-vmemmap.c | 74 +++++++++++++++++-------------------------
mm/sparse.h | 1 +
4 files changed, 33 insertions(+), 72 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 97511f651ebc..1a18c1c40c8c 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1156,7 +1156,7 @@ struct zone {
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
#endif
} ____cacheline_internodealigned_in_smp;
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index f977d0a7e002..5ddf06b83c96 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -493,32 +493,6 @@ static bool vmemmap_should_optimize_folio(const struct hstate *h, struct folio *
return true;
}
-static struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
-{
- const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- struct page *tail, *p;
- int node = zone_to_nid(zone);
-
- tail = READ_ONCE(zone->vmemmap_tails[idx]);
- if (likely(tail))
- return tail;
-
- tail = alloc_pages_node(node, GFP_KERNEL | __GFP_ZERO, 0);
- if (!tail)
- return NULL;
-
- p = page_to_virt(tail);
- for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++)
- init_compound_tail(p + i, NULL, order, zone);
-
- if (cmpxchg(&zone->vmemmap_tails[idx], NULL, tail)) {
- __free_page(tail);
- tail = READ_ONCE(zone->vmemmap_tails[idx]);
- }
-
- return tail;
-}
-
static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
struct folio *folio,
struct list_head *vmemmap_pages,
@@ -535,7 +509,7 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
return ret;
nid = folio_nid(folio);
- vmemmap_tail = vmemmap_get_tail(h->order, folio_zone(folio));
+ vmemmap_tail = vmemmap_shared_tail_page(h->order, folio_zone(folio));
if (!vmemmap_tail)
return -ENOMEM;
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index f22d815d7af0..7388a5b5cce3 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -42,27 +42,13 @@
#include "mm_init.h"
#include "sparse.h"
-/*
- * Allocate a block of memory to be used to back the virtual memory map
- * or to back the page tables that are used to create the mapping.
- * Uses the main allocators if they are available, else bootmem.
- */
-
-static void * __ref __earlyonly_bootmem_alloc(int node,
- unsigned long size,
- unsigned long align,
- unsigned long goal)
-{
- return memmap_alloc(size, align, goal, node, false);
-}
-
-void * __meminit vmemmap_alloc_block(unsigned long size, int node)
+void __ref *vmemmap_alloc_block(unsigned long size, int node)
{
/* If the main allocator is up use that, fallback to bootmem. */
if (slab_is_available()) {
gfp_t gfp_mask = GFP_KERNEL|__GFP_RETRY_MAYFAIL|__GFP_NOWARN;
int order = get_order(size);
- static bool warned __meminitdata;
+ static bool warned;
struct page *page;
page = alloc_pages_node(node, gfp_mask, order);
@@ -76,8 +62,7 @@ void * __meminit vmemmap_alloc_block(unsigned long size, int node)
}
return NULL;
} else
- return __earlyonly_bootmem_alloc(node, size, size,
- __pa(MAX_DMA_ADDRESS));
+ return memmap_alloc(size, size, __pa(MAX_DMA_ADDRESS), node, false);
}
static void * __meminit altmap_alloc_block_buf(unsigned long size,
@@ -184,39 +169,40 @@ static void * __meminit vmemmap_alloc_block_zero(unsigned long size, int node)
return p;
}
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
-static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
+struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zone)
{
- struct page *p, *tail;
- unsigned int idx;
- int node = zone_to_nid(zone);
+ void *addr;
+ struct page *page;
+ const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- if (WARN_ON_ONCE(order < VMEMMAP_OPTIMIZATION_MIN_ORDER))
- return NULL;
- if (WARN_ON_ONCE(order > MAX_FOLIO_ORDER))
+ if (WARN_ON_ONCE(idx >= ARRAY_SIZE(zone->vmemmap_tails)))
return NULL;
- idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- tail = zone->vmemmap_tails[idx];
- if (tail)
- return tail;
- p = vmemmap_alloc_block_zero(PAGE_SIZE, node);
- if (!p)
+ page = READ_ONCE(zone->vmemmap_tails[idx]);
+ if (likely(page))
+ return page;
+
+ addr = vmemmap_alloc_block(PAGE_SIZE, zone_to_nid(zone));
+ if (!addr)
return NULL;
- for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++)
- init_compound_tail(p + i, NULL, order, zone);
- tail = virt_to_page(p);
- zone->vmemmap_tails[idx] = tail;
+ for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++) {
+ page = (struct page *)addr + i;
+ mm_zero_struct_page(page);
+ init_compound_tail(page, NULL, order, zone);
+ }
- return tail;
-}
-#else
-static inline struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
-{
- return NULL;
+ page = virt_to_page(addr);
+ if (cmpxchg(&zone->vmemmap_tails[idx], NULL, page) != NULL) {
+ if (slab_is_available())
+ __free_page(page);
+ else
+ memblock_free(addr, PAGE_SIZE);
+ page = READ_ONCE(zone->vmemmap_tails[idx]);
+ }
+
+ return page;
}
-#endif
static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
struct vmem_altmap *altmap)
@@ -229,7 +215,7 @@ static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
return vmemmap_alloc_block_buf(PAGE_SIZE, node, altmap);
zone = pfn_to_zone(pfn, node);
- page = vmemmap_get_tail(order, zone);
+ page = vmemmap_shared_tail_page(order, zone);
if (!page)
return NULL;
diff --git a/mm/sparse.h b/mm/sparse.h
index 3151d4db7575..a250bea4088e 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -142,6 +142,7 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
+struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
void sparse_init_subsection_map(void);
int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v3 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation
2026-09-11 5:02 ` [PATCH v3 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
@ 2026-09-14 7:48 ` Mike Rapoport
0 siblings, 0 replies; 17+ messages in thread
From: Mike Rapoport @ 2026-09-14 7:48 UTC (permalink / raw)
To: Muchun Song
Cc: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On Fri, 11 Sep 2026 13:02:19 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> HugeTLB and sparse-vmemmap each have their own helper to allocate the
> shared vmemmap tail page used by vmemmap optimization.
>
> Factor that logic into a common vmemmap_shared_tail_page() helper. It
> allocates the page through vmemmap_alloc_block(), and uses cmpxchg()
> to install the per-zone shared page.
>
> [...]
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v3 03/11] mm/sparse-vmemmap: open-code init_compound_tail()
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-09-11 5:02 ` [PATCH v3 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
2026-09-11 5:02 ` [PATCH v3 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-14 7:48 ` Mike Rapoport
2026-09-11 5:02 ` [PATCH v3 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for compound page orders Muchun Song
` (7 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
init_compound_tail() is only used by vmemmap_shared_tail_page(), where
the shared tail page setup intentionally passes NULL as the compound head.
Keeping this helper in mm/internal.h exposes that special case to the rest
of the MM code and can make the NULL head argument look generally valid.
Open-code the initialization at the only call site so the special-case use
stays local to sparse vmemmap optimization.
No functional change intended.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
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 7388a5b5cce3..861e09b2b096 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -189,7 +189,10 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++) {
page = (struct page *)addr + i;
mm_zero_struct_page(page);
- init_compound_tail(page, NULL, order, zone);
+ atomic_set(&page->_mapcount, -1);
+ set_page_node(page, zone_to_nid(zone));
+ set_page_zone(page, zone_idx(zone));
+ prep_compound_tail(page, NULL, order);
}
page = virt_to_page(addr);
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v3 03/11] mm/sparse-vmemmap: open-code init_compound_tail()
2026-09-11 5:02 ` [PATCH v3 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
@ 2026-09-14 7:48 ` Mike Rapoport
0 siblings, 0 replies; 17+ messages in thread
From: Mike Rapoport @ 2026-09-14 7:48 UTC (permalink / raw)
To: Muchun Song
Cc: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On Fri, 11 Sep 2026 13:02:20 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> init_compound_tail() is only used by vmemmap_shared_tail_page(), where
> the shared tail page setup intentionally passes NULL as the compound head.
>
> Keeping this helper in mm/internal.h exposes that special case to the rest
> of the MM code and can make the NULL head argument look generally valid.
> Open-code the initialization at the only call site so the special-case use
> stays local to sparse vmemmap optimization.
>
> [...]
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v3 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for compound page orders
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (2 preceding siblings ...)
2026-09-11 5:02 ` [PATCH v3 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-11 5:02 ` [PATCH v3 05/11] mm/sparse-vmemmap: set compound page order for device DAX Muchun Song
` (6 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
Device DAX still uses vmemmap_populate_compound_pages() to populate its
compound-page vmemmap mappings. That helper allocates the head and first
tail vmemmap pages explicitly, then reuses the first tail page for the
remaining tail page mappings.
Device DAX is being moved to the section-based vmemmap optimization
infrastructure, but it cannot switch to the generic section-based
population path yet. Once a later patch records the DAX compound page
order in section metadata, DAX head and first-tail PFNs can look
optimizable to the generic helpers as well.
Add a DAX-specific population flag for this transition. It keeps DAX
head/first-tail allocations on the normal vmemmap allocation path, while
preserving the existing page reference for reused DAX tail mappings.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
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 861e09b2b096..aa89c16f7fc7 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -35,8 +35,8 @@
/*
* Flags for vmemmap_populate_range and friends.
*/
-/* Get a ref on the head page struct page, for ZONE_DEVICE compound pages */
-#define VMEMMAP_POPULATE_PAGEREF 0x0001
+/* Vmemmap population for ZONE_DEVICE compound pages */
+#define VMEMMAP_POPULATE_DAX 0x0001
#include "internal.h"
#include "mm_init.h"
@@ -208,13 +208,17 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
}
static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
- struct vmem_altmap *altmap)
+ struct vmem_altmap *altmap, unsigned long flags)
{
struct zone *zone;
struct page *page;
const unsigned int order = pfn_to_section_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);
@@ -236,7 +240,7 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
pte_t entry;
if (ptpfn == (unsigned long)-1) {
- void *p = vmemmap_alloc_pte(pfn, node, altmap);
+ void *p = vmemmap_alloc_pte(pfn, node, altmap, flags);
if (!p)
return NULL;
@@ -251,7 +255,7 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
* and through vmemmap_populate_compound_pages() when
* slab is available.
*/
- if (flags & VMEMMAP_POPULATE_PAGEREF)
+ if (flags & VMEMMAP_POPULATE_DAX)
get_page(pfn_to_page(ptpfn));
}
entry = pfn_pte(ptpfn, PAGE_KERNEL);
@@ -511,6 +515,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long size, addr;
pte_t *pte;
int rc;
+ unsigned long flags = VMEMMAP_POPULATE_DAX;
if (reuse_compound_section(start_pfn, pgmap)) {
pte = compound_section_tail_page(start);
@@ -522,8 +527,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
* with just tail struct pages.
*/
return vmemmap_populate_range(start, end, node, NULL,
- pte_pfn(ptep_get(pte)),
- VMEMMAP_POPULATE_PAGEREF);
+ pte_pfn(ptep_get(pte)), flags);
}
size = min(end - start, pgmap_vmemmap_nr(pgmap) * sizeof(struct page));
@@ -531,13 +535,13 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long next, last = addr + size;
/* Populate the head page vmemmap page */
- pte = vmemmap_populate_address(addr, node, NULL, -1, 0);
+ pte = vmemmap_populate_address(addr, node, NULL, -1, flags);
if (!pte)
return -ENOMEM;
/* Populate the tail pages vmemmap page */
next = addr + PAGE_SIZE;
- pte = vmemmap_populate_address(next, node, NULL, -1, 0);
+ pte = vmemmap_populate_address(next, node, NULL, -1, flags);
if (!pte)
return -ENOMEM;
@@ -547,8 +551,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
*/
next += PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
- pte_pfn(ptep_get(pte)),
- VMEMMAP_POPULATE_PAGEREF);
+ pte_pfn(ptep_get(pte)), flags);
if (rc)
return -ENOMEM;
}
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v3 05/11] mm/sparse-vmemmap: set compound page order for device DAX
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (3 preceding siblings ...)
2026-09-11 5:02 ` [PATCH v3 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for compound page orders Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-11 5:02 ` [PATCH v3 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
` (5 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
Device DAX can use vmemmap optimization only when a full section is
populated with a compound-page geometry. Record that geometry 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>
---
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 aa89c16f7fc7..02da1321197d 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) {
@@ -573,7 +573,7 @@ struct page * __meminit __populate_section_memmap(unsigned long pfn,
!IS_ALIGNED(nr_pages, PAGES_PER_SUBSECTION)))
return NULL;
- if (vmemmap_can_optimize(altmap, pgmap))
+ if (pgmap && section_vmemmap_optimizable(__pfn_to_section(pfn)))
r = vmemmap_populate_compound_pages(pfn, start, end, nid, pgmap);
else
r = vmemmap_populate(start, end, nid, altmap);
@@ -792,8 +792,10 @@ static void section_deactivate(unsigned long pfn, unsigned long nr_pages,
else if (memmap)
free_map_bootmem(memmap);
- if (empty)
+ if (empty) {
ms->section_mem_map = (unsigned long)NULL;
+ section_set_compound_order(ms, 0);
+ }
}
static struct page * __meminit section_activate(int nid, unsigned long pfn,
@@ -803,8 +805,13 @@ static struct page * __meminit section_activate(int nid, unsigned long pfn,
struct mem_section *ms = __pfn_to_section(pfn);
struct mem_section_usage *usage = NULL;
struct page *memmap;
+ unsigned int order;
int rc;
+ order = vmemmap_can_optimize(altmap, pgmap) ? pgmap->vmemmap_shift : 0;
+ if (nr_pages < PAGES_PER_SECTION && section_compound_order(ms))
+ return ERR_PTR(-EOPNOTSUPP);
+
if (!ms->usage) {
usage = kzalloc(mem_section_usage_size(), GFP_KERNEL);
if (!usage)
@@ -830,6 +837,7 @@ static struct page * __meminit section_activate(int nid, unsigned long pfn,
if (nr_pages < PAGES_PER_SECTION && early_section(ms))
return pfn_to_page(pfn);
+ section_set_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] 17+ messages in thread* [PATCH v3 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (4 preceding siblings ...)
2026-09-11 5:02 ` [PATCH v3 05/11] mm/sparse-vmemmap: set compound page order for device DAX Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-11 5:02 ` [PATCH v3 07/11] mm/sparse-vmemmap: move vmemmap optimization helpers to a public header Muchun Song
` (4 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
HugeTLB vmemmap optimization now uses per-zone shared tail vmemmap pages.
Device DAX has not been switched to that mechanism yet.
Switch device DAX to vmemmap_shared_tail_page() as well. This aligns DAX
with HugeTLB by using the common per-zone shared tail vmemmap page.
The optimization is enabled only for DEV-DAX through pgmap->vmemmap_shift,
which 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.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
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 | 47 ++++++++++++++----------------------------
3 files changed, 29 insertions(+), 34 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 1a18c1c40c8c..f8a2d823f7de 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2149,11 +2149,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 02da1321197d..7b5b9ceec697 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -193,6 +193,8 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
set_page_node(page, zone_to_nid(zone));
set_page_zone(page, zone_idx(zone));
prep_compound_tail(page, NULL, order);
+ if (zone_is_zone_device(zone))
+ __SetPageReserved(page);
}
page = virt_to_page(addr);
@@ -490,23 +492,6 @@ static bool __meminit reuse_compound_section(unsigned long start_pfn,
return !IS_ALIGNED(offset, nr_pages) && nr_pages > PAGES_PER_SUBSECTION;
}
-static pte_t * __meminit compound_section_tail_page(unsigned long addr)
-{
- pte_t *pte;
-
- addr -= PAGE_SIZE;
-
- /*
- * Assuming sections are populated sequentially, the previous section's
- * page data can be reused.
- */
- pte = pte_offset_kernel(pmd_off_k(addr), addr);
- if (!pte)
- return NULL;
-
- return pte;
-}
-
static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long start,
unsigned long end, int node,
@@ -516,21 +501,18 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
pte_t *pte;
int rc;
unsigned long flags = VMEMMAP_POPULATE_DAX;
+ struct page *page;
+ unsigned int order = pfn_to_section_compound_order(start_pfn);
- if (reuse_compound_section(start_pfn, pgmap)) {
- pte = compound_section_tail_page(start);
- if (!pte)
- return -ENOMEM;
+ page = vmemmap_shared_tail_page(order, device_zone(node));
+ if (!page)
+ return -ENOMEM;
- /*
- * Reuse the page that was populated in the prior iteration
- * with just tail struct pages.
- */
+ if (reuse_compound_section(start_pfn, pgmap))
return vmemmap_populate_range(start, end, node, NULL,
- pte_pfn(ptep_get(pte)), flags);
- }
+ page_to_pfn(page), flags);
- size = min(end - start, pgmap_vmemmap_nr(pgmap) * sizeof(struct page));
+ size = min(end - start, (1UL << order) * sizeof(struct page));
for (addr = start; addr < end; addr += size) {
unsigned long next, last = addr + size;
@@ -546,12 +528,12 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
return -ENOMEM;
/*
- * Reuse the previous page for the rest of tail pages
+ * Reuse the shared page for the rest of tail pages
* See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
next += PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
- pte_pfn(ptep_get(pte)), flags);
+ page_to_pfn(page), flags);
if (rc)
return -ENOMEM;
}
@@ -883,13 +865,14 @@ int __meminit sparse_add_section(int nid, unsigned long start_pfn,
if (IS_ERR(memmap))
return PTR_ERR(memmap);
+ ms = __nr_to_section(section_nr);
/*
* Poison uninitialized struct pages in order to catch invalid flags
* combinations.
*/
- page_init_poison(memmap, sizeof(struct page) * nr_pages);
+ if (!section_vmemmap_optimizable(ms))
+ page_init_poison(memmap, sizeof(struct page) * nr_pages);
- ms = __nr_to_section(section_nr);
__section_mark_present(ms, section_nr);
/* Align memmap to section boundary in the subsection case */
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v3 07/11] mm/sparse-vmemmap: move vmemmap optimization helpers to a public header
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (5 preceding siblings ...)
2026-09-11 5:02 ` [PATCH v3 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-14 7:48 ` Mike Rapoport
2026-09-11 5:02 ` [PATCH v3 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
` (3 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The vmemmap optimization helpers currently live in mm/sparse.h,
which is an internal MM header. That works for MM code, but
prevents powerpc from using the same interfaces without including a
private header.
Move the declarations and inline helpers to
include/linux/vmemmap-optimization.h. This is a preparatory change for
powerpc, which has its own vmemmap optimization implementation and needs
to use the common vmemmap optimization interfaces from architecture code.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
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 +
include/linux/vmemmap-optimization.h | 94 ++++++++++++++++++++++++++++
mm/hugetlb.c | 2 +-
mm/hugetlb_vmemmap.c | 2 +-
mm/sparse.h | 76 +---------------------
5 files changed, 98 insertions(+), 77 deletions(-)
create mode 100644 include/linux/vmemmap-optimization.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 3ef018673b5e..e4412c3d8d45 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12096,6 +12096,7 @@ F: Documentation/mm/hugetlbfs_reserv.rst
F: Documentation/mm/vmemmap_dedup.rst
F: fs/hugetlbfs/
F: include/linux/hugetlb.h
+F: include/linux/vmemmap-optimization.h
F: include/trace/events/hugetlbfs.h
F: mm/hugetlb.c
F: mm/hugetlb_cgroup.c
diff --git a/include/linux/vmemmap-optimization.h b/include/linux/vmemmap-optimization.h
new file mode 100644
index 000000000000..2327929467f3
--- /dev/null
+++ b/include/linux/vmemmap-optimization.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * vmemmap-optimization.h
+ *
+ * Generic vmemmap optimization declarations.
+ *
+ * Author: Muchun Song <songmuchun@bytedance.com>
+ */
+#ifndef _LINUX_VMEMMAP_OPTIMIZATION_H
+#define _LINUX_VMEMMAP_OPTIMIZATION_H
+
+#include <linux/align.h>
+#include <linux/log2.h>
+#include <linux/mmdebug.h>
+#include <linux/mmzone.h>
+
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
+static inline unsigned int section_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 /* CONFIG_SPARSEMEM_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_SPARSEMEM_VMEMMAP_OPTIMIZATION))
+ return false;
+
+ if (!is_power_of_2(sizeof(struct page)))
+ return false;
+
+ return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
+}
+
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
+#endif /* CONFIG_SPARSEMEM_VMEMMAP */
+#endif /* _LINUX_VMEMMAP_OPTIMIZATION_H */
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 0d101ef934fc..7af55e1544e9 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 5ddf06b83c96..a27f46fffb54 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -15,10 +15,10 @@
#include <linux/mmdebug.h>
#include <linux/pagewalk.h>
#include <linux/pgalloc.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/tlbflush.h>
#include "hugetlb_vmemmap.h"
-#include "sparse.h"
#include "internal.h"
/**
diff --git a/mm/sparse.h b/mm/sparse.h
index a250bea4088e..a5111087ee3a 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -9,80 +9,7 @@
#define __MM_SPARSE_H
#include <linux/mmzone.h>
-
-#ifdef CONFIG_SPARSEMEM_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_SPARSEMEM_VMEMMAP_OPTIMIZATION))
- return false;
-
- if (!is_power_of_2(sizeof(struct page)))
- return false;
-
- return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
-}
+#include <linux/vmemmap-optimization.h>
/*
* mm/sparse.c
@@ -142,7 +69,6 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
-struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
void sparse_init_subsection_map(void);
int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH v3 07/11] mm/sparse-vmemmap: move vmemmap optimization helpers to a public header
2026-09-11 5:02 ` [PATCH v3 07/11] mm/sparse-vmemmap: move vmemmap optimization helpers to a public header Muchun Song
@ 2026-09-14 7:48 ` Mike Rapoport
0 siblings, 0 replies; 17+ messages in thread
From: Mike Rapoport @ 2026-09-14 7:48 UTC (permalink / raw)
To: Muchun Song
Cc: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap
On Fri, 11 Sep 2026 13:02:24 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> The vmemmap optimization helpers currently live in mm/sparse.h,
> which is an internal MM header. That works for MM code, but
> prevents powerpc from using the same interfaces without including a
> private header.
>
> Move the declarations and inline helpers to
> include/linux/vmemmap-optimization.h. This is a preparatory change for
> powerpc, which has its own vmemmap optimization implementation and needs
> to use the common vmemmap optimization interfaces from architecture code.
>
> [...]
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v3 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (6 preceding siblings ...)
2026-09-11 5:02 ` [PATCH v3 07/11] mm/sparse-vmemmap: move vmemmap optimization helpers to a public header Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-11 5:02 ` [PATCH v3 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
` (2 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The powerpc radix compound vmemmap population path still finds a reusable
tail page by walking the vmemmap page tables.
Switch it to the common vmemmap_shared_tail_page() helper instead, so it
can use the shared vmemmap page directly to simplify the code.
This removes the powerpc-specific tail-page lookup and its fallback path
and aligns the device DAX vmemmap optimization path with HugeTLB.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 80 +++---------------------
1 file changed, 9 insertions(+), 71 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index cf692b2b5f7b..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);
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v3 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (7 preceding siblings ...)
2026-09-11 5:02 ` [PATCH v3 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-11 5:02 ` [PATCH v3 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
2026-09-11 5:02 ` [PATCH v3 11/11] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The device DAX vmemmap population still reserves one extra tail vmemmap
page after the head page.
Drop that extra reservation and let the shared tail page cover all tail
vmemmap pages after the head page, so DAX follows the same reservation
model as HugeTLB.
This reduces the reserved vmemmap pages for optimized DAX mappings to
one and removes the now-unneeded first-tail population from the generic
and powerpc paths to simplify the code as well.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
v3:
- Collect Acked-by from Qi Zheng
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 46 ++----------------------
include/linux/mm.h | 3 +-
mm/mm_init.c | 2 +-
mm/sparse-vmemmap.c | 13 ++-----
4 files changed, 7 insertions(+), 57 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index 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 a2ebe87e7654..969594074fd2 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5167,7 +5167,6 @@ static inline void vmem_altmap_free(struct vmem_altmap *altmap,
}
#endif
-#define VMEMMAP_RESERVE_NR 2
#ifdef CONFIG_ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
struct dev_pagemap *pgmap)
@@ -5187,7 +5186,7 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
* For vmemmap optimization with DAX we need minimum 2 vmemmap
* pages. See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
- return !altmap && (nr_vmemmap_pages > VMEMMAP_RESERVE_NR);
+ return !altmap && (nr_vmemmap_pages > VMEMMAP_OPTIMIZATION_PAGES);
}
/*
* If we don't have an architecture override, use the generic rule
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 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 7b5b9ceec697..39f0cfeefc6a 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;
}
@@ -521,17 +520,11 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
if (!pte)
return -ENOMEM;
- /* Populate the tail pages vmemmap page */
- next = addr + PAGE_SIZE;
- pte = vmemmap_populate_address(next, node, NULL, -1, flags);
- if (!pte)
- return -ENOMEM;
-
/*
* Reuse the shared page for the rest of tail pages
* See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
- next += PAGE_SIZE;
+ next = addr + PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
page_to_pfn(page), flags);
if (rc)
--
2.54.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v3 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (8 preceding siblings ...)
2026-09-11 5:02 ` [PATCH v3 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
2026-09-11 5:02 ` [PATCH v3 11/11] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
section_nr_vmemmap_pages() no longer uses the altmap or pgmap
arguments, so drop them from the helper and its callers.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
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 39f0cfeefc6a..96506f594924 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);
@@ -637,7 +636,7 @@ static struct page * __meminit populate_section_memmap(unsigned long pfn,
struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
pgmap);
- memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+ memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages));
return page;
}
@@ -648,7 +647,7 @@ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
unsigned long start = (unsigned long) pfn_to_page(pfn);
unsigned long end = start + nr_pages * sizeof(struct page);
- memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+ memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages));
vmemmap_free(start, end, altmap);
}
@@ -658,8 +657,7 @@ static void free_map_bootmem(struct page *memmap)
unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
unsigned long pfn = page_to_pfn(memmap);
- memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
- NULL, NULL));
+ memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION));
vmemmap_free(start, end, NULL);
}
diff --git a/mm/sparse.c b/mm/sparse.c
index 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] 17+ messages in thread* [PATCH v3 11/11] Documentation/mm: update DAX vmemmap deduplication docs
2026-09-11 5:02 [PATCH v3 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (9 preceding siblings ...)
2026-09-11 5:02 ` [PATCH v3 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
@ 2026-09-11 5:02 ` Muchun Song
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-09-11 5:02 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
Device DAX now uses the common per-zone shared tail page for vmemmap
deduplication. The old documentation still described a DAX-specific
layout with a separately populated tail vmemmap page and half the HugeTLB
savings.
Update the generic and powerpc documentation to describe the shared layout.
In the powerpc document, keep the radix and 64K-specific details, drop the
duplicated 4K PUD arithmetic, and replace the repeated device-dax diagrams
with a single parameterized PMD/PUD diagram.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
---
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] 17+ messages in thread