* [PATCH 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-08-31 9:44 ` Qi Zheng
2026-08-31 7:53 ` [PATCH 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-08-31 7:53 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>
---
arch/x86/entry/vdso/vdso32/fake_32bit_build.h | 2 +-
drivers/dax/Kconfig | 1 +
fs/Kconfig | 1 +
include/linux/mm.h | 3 +++
include/linux/mmzone.h | 13 +++++++------
include/linux/page-flags.h | 5 ++---
mm/Kconfig | 3 +++
mm/sparse.h | 4 ++--
8 files changed, 20 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..85ad4c135cdd 100644
--- a/drivers/dax/Kconfig
+++ b/drivers/dax/Kconfig
@@ -8,6 +8,7 @@ if DAX
config DEV_DAX
tristate "Device DAX: direct access mapping device"
depends on TRANSPARENT_HUGEPAGE
+ 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 a9fbe26536f4..edadd7549b72 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5188,6 +5188,9 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
unsigned long nr_pages;
unsigned long nr_vmemmap_pages;
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
+ return false;
+
if (!pgmap || !is_power_of_2(sizeof(struct page)))
return false;
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index c9ae7991a8b2..e9b54ea0eff0 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -102,9 +102,9 @@
*
* HVO which is only active if the size of struct page is a power of 2.
*/
-#define MAX_FOLIO_VMEMMAP_ALIGN \
- (IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) && \
- is_power_of_2(sizeof(struct page)) ? \
+#define MAX_FOLIO_VMEMMAP_ALIGN \
+ (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION) && \
+ is_power_of_2(sizeof(struct page)) ? \
MAX_FOLIO_NR_PAGES * sizeof(struct page) : 0)
/* The number of retained vmemmap pages with HVO enabled. */
@@ -116,7 +116,8 @@
#define __VMEMMAP_OPTIMIZATION_NR_ORDERS \
(MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
#define VMEMMAP_OPTIMIZATION_NR_ORDERS \
- (__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
+ ((__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 && \
+ IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION)) ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
enum migratetype {
MIGRATE_UNMOVABLE,
@@ -1155,7 +1156,7 @@ struct zone {
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
#endif
} ____cacheline_internodealigned_in_smp;
@@ -2019,7 +2020,7 @@ struct mem_section {
unsigned long section_mem_map;
struct mem_section_usage *usage;
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
/*
* Normally, sections hold regular (order-0) pages. However, for
* sections with HVO enabled, this tracks the compound page order
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index ae2ebaed6d4d..de3c06062bc6 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 c1ddf59c0d71..b5f8372cd164 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -461,6 +461,9 @@ 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
+
#
# Select this config option from the architecture Kconfig, if it is preferred
# to enable the feature of HugeTLB/dev_dax vmemmap optimization.
diff --git a/mm/sparse.h b/mm/sparse.h
index 049272aba84e..b408d15baf7b 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -10,7 +10,7 @@
#include <linux/mmzone.h>
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
static inline unsigned int section_order(const struct mem_section *section)
{
return section->order;
@@ -72,7 +72,7 @@ static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
static inline bool vmemmap_optimizable_order(unsigned int order)
{
- if (!IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP))
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
return false;
if (!is_power_of_2(sizeof(struct page)))
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
2026-08-31 7:53 ` [PATCH 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
@ 2026-08-31 9:44 ` Qi Zheng
2026-08-31 9:49 ` Muchun Song
0 siblings, 1 reply; 17+ messages in thread
From: Qi Zheng @ 2026-08-31 9:44 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin, Christophe Leroy,
Randy Dunlap
On 8/31/26 3:53 PM, Muchun Song wrote:
> 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>
> ---
> arch/x86/entry/vdso/vdso32/fake_32bit_build.h | 2 +-
> drivers/dax/Kconfig | 1 +
> fs/Kconfig | 1 +
> include/linux/mm.h | 3 +++
> include/linux/mmzone.h | 13 +++++++------
> include/linux/page-flags.h | 5 ++---
> mm/Kconfig | 3 +++
> mm/sparse.h | 4 ++--
> 8 files changed, 20 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..85ad4c135cdd 100644
> --- a/drivers/dax/Kconfig
> +++ b/drivers/dax/Kconfig
> @@ -8,6 +8,7 @@ if DAX
> config DEV_DAX
> tristate "Device DAX: direct access mapping device"
> depends on TRANSPARENT_HUGEPAGE
> + 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 a9fbe26536f4..edadd7549b72 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -5188,6 +5188,9 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
> unsigned long nr_pages;
> unsigned long nr_vmemmap_pages;
>
> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
> + return false;
> +
> if (!pgmap || !is_power_of_2(sizeof(struct page)))
> return false;
>
> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
> index c9ae7991a8b2..e9b54ea0eff0 100644
> --- a/include/linux/mmzone.h
> +++ b/include/linux/mmzone.h
> @@ -102,9 +102,9 @@
> *
> * HVO which is only active if the size of struct page is a power of 2.
> */
> -#define MAX_FOLIO_VMEMMAP_ALIGN \
> - (IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) && \
> - is_power_of_2(sizeof(struct page)) ? \
> +#define MAX_FOLIO_VMEMMAP_ALIGN \
> + (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION) && \
> + is_power_of_2(sizeof(struct page)) ? \
> MAX_FOLIO_NR_PAGES * sizeof(struct page) : 0)
>
> /* The number of retained vmemmap pages with HVO enabled. */
> @@ -116,7 +116,8 @@
> #define __VMEMMAP_OPTIMIZATION_NR_ORDERS \
> (MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
> #define VMEMMAP_OPTIMIZATION_NR_ORDERS \
> - (__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
> + ((__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 && \
> + IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION)) ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
>
> enum migratetype {
> MIGRATE_UNMOVABLE,
> @@ -1155,7 +1156,7 @@ struct zone {
> /* Zone statistics */
> atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
> atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
> #endif
> } ____cacheline_internodealigned_in_smp;
> @@ -2019,7 +2020,7 @@ struct mem_section {
> unsigned long section_mem_map;
>
> struct mem_section_usage *usage;
> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> /*
> * Normally, sections hold regular (order-0) pages. However, for
> * sections with HVO enabled, this tracks the compound page order
> diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
> index ae2ebaed6d4d..de3c06062bc6 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 c1ddf59c0d71..b5f8372cd164 100644
> --- a/mm/Kconfig
> +++ b/mm/Kconfig
> @@ -461,6 +461,9 @@ 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
As sashiko was concerned about [1], it seems we need to add
depends on SPARSEMEM_VMEMMAP
here.
Apart from that, LGTM. With this fix included:
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
[1].
https://sashiko.dev/#/patchset/20260831075342.57563-1-songmuchun%40bytedance.com
> +
> #
> # Select this config option from the architecture Kconfig, if it is preferred
> # to enable the feature of HugeTLB/dev_dax vmemmap optimization.
> diff --git a/mm/sparse.h b/mm/sparse.h
> index 049272aba84e..b408d15baf7b 100644
> --- a/mm/sparse.h
> +++ b/mm/sparse.h
> @@ -10,7 +10,7 @@
>
> #include <linux/mmzone.h>
>
> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
> static inline unsigned int section_order(const struct mem_section *section)
> {
> return section->order;
> @@ -72,7 +72,7 @@ static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
>
> static inline bool vmemmap_optimizable_order(unsigned int order)
> {
> - if (!IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP))
> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
> return false;
>
> if (!is_power_of_2(sizeof(struct page)))
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
2026-08-31 9:44 ` Qi Zheng
@ 2026-08-31 9:49 ` Muchun Song
0 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-08-31 9:49 UTC (permalink / raw)
To: Qi Zheng
Cc: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet, linux-mm,
linux-kernel, linuxppc-dev, linux-doc, Lorenzo Stoakes,
Mike Rapoport, Nicholas Piggin, Christophe Leroy, Randy Dunlap
> On Aug 31, 2026, at 17:44, Qi Zheng <qi.zheng@linux.dev> wrote:
>
>
>
> On 8/31/26 3:53 PM, Muchun Song wrote:
>> 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>
>> ---
>> arch/x86/entry/vdso/vdso32/fake_32bit_build.h | 2 +-
>> drivers/dax/Kconfig | 1 +
>> fs/Kconfig | 1 +
>> include/linux/mm.h | 3 +++
>> include/linux/mmzone.h | 13 +++++++------
>> include/linux/page-flags.h | 5 ++---
>> mm/Kconfig | 3 +++
>> mm/sparse.h | 4 ++--
>> 8 files changed, 20 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..85ad4c135cdd 100644
>> --- a/drivers/dax/Kconfig
>> +++ b/drivers/dax/Kconfig
>> @@ -8,6 +8,7 @@ if DAX
>> config DEV_DAX
>> tristate "Device DAX: direct access mapping device"
>> depends on TRANSPARENT_HUGEPAGE
>> + 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 a9fbe26536f4..edadd7549b72 100644
>> --- a/include/linux/mm.h
>> +++ b/include/linux/mm.h
>> @@ -5188,6 +5188,9 @@ static inline bool __vmemmap_can_optimize(struct vmem_altmap *altmap,
>> unsigned long nr_pages;
>> unsigned long nr_vmemmap_pages;
>> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
>> + return false;
>> +
>> if (!pgmap || !is_power_of_2(sizeof(struct page)))
>> return false;
>> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
>> index c9ae7991a8b2..e9b54ea0eff0 100644
>> --- a/include/linux/mmzone.h
>> +++ b/include/linux/mmzone.h
>> @@ -102,9 +102,9 @@
>> *
>> * HVO which is only active if the size of struct page is a power of 2.
>> */
>> -#define MAX_FOLIO_VMEMMAP_ALIGN \
>> - (IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP) && \
>> - is_power_of_2(sizeof(struct page)) ? \
>> +#define MAX_FOLIO_VMEMMAP_ALIGN \
>> + (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION) && \
>> + is_power_of_2(sizeof(struct page)) ? \
>> MAX_FOLIO_NR_PAGES * sizeof(struct page) : 0)
>> /* The number of retained vmemmap pages with HVO enabled. */
>> @@ -116,7 +116,8 @@
>> #define __VMEMMAP_OPTIMIZATION_NR_ORDERS \
>> (MAX_FOLIO_ORDER - VMEMMAP_OPTIMIZATION_MIN_ORDER + 1)
>> #define VMEMMAP_OPTIMIZATION_NR_ORDERS \
>> - (__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
>> + ((__VMEMMAP_OPTIMIZATION_NR_ORDERS > 0 && \
>> + IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION)) ? __VMEMMAP_OPTIMIZATION_NR_ORDERS : 0)
>> enum migratetype {
>> MIGRATE_UNMOVABLE,
>> @@ -1155,7 +1156,7 @@ struct zone {
>> /* Zone statistics */
>> atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
>> atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
>> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
>> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
>> struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
>> #endif
>> } ____cacheline_internodealigned_in_smp;
>> @@ -2019,7 +2020,7 @@ struct mem_section {
>> unsigned long section_mem_map;
>> struct mem_section_usage *usage;
>> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
>> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
>> /*
>> * Normally, sections hold regular (order-0) pages. However, for
>> * sections with HVO enabled, this tracks the compound page order
>> diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
>> index ae2ebaed6d4d..de3c06062bc6 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 c1ddf59c0d71..b5f8372cd164 100644
>> --- a/mm/Kconfig
>> +++ b/mm/Kconfig
>> @@ -461,6 +461,9 @@ 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
>
> As sashiko was concerned about [1], it seems we need to add
>
> depends on SPARSEMEM_VMEMMAP
>
> here.
You are right. Besides that, we also need DEV_DAX to depend on
ZONE_DEVICE explicitly.
>
> Apart from that, LGTM. With this fix included:
>
> Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks.
>
> Thanks,
> Qi
>
> [1]. https://sashiko.dev/#/patchset/20260831075342.57563-1-songmuchun%40bytedance.com
>
>> +
>> #
>> # Select this config option from the architecture Kconfig, if it is preferred
>> # to enable the feature of HugeTLB/dev_dax vmemmap optimization.
>> diff --git a/mm/sparse.h b/mm/sparse.h
>> index 049272aba84e..b408d15baf7b 100644
>> --- a/mm/sparse.h
>> +++ b/mm/sparse.h
>> @@ -10,7 +10,7 @@
>> #include <linux/mmzone.h>
>> -#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
>> +#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
>> static inline unsigned int section_order(const struct mem_section *section)
>> {
>> return section->order;
>> @@ -72,7 +72,7 @@ static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
>> static inline bool vmemmap_optimizable_order(unsigned int order)
>> {
>> - if (!IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP))
>> + if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
>> return false;
>> if (!is_power_of_2(sizeof(struct page)))
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-08-31 7:53 ` [PATCH 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-09-01 3:05 ` Qi Zheng
2026-08-31 7:53 ` [PATCH 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-08-31 7:53 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>
---
include/linux/mmzone.h | 2 +-
mm/hugetlb_vmemmap.c | 28 +---------------
mm/sparse-vmemmap.c | 74 +++++++++++++++++-------------------------
mm/sparse.h | 1 +
4 files changed, 33 insertions(+), 72 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index e9b54ea0eff0..d3778ba976a5 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1156,7 +1156,7 @@ struct zone {
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
struct page *vmemmap_tails[VMEMMAP_OPTIMIZATION_NR_ORDERS];
#endif
} ____cacheline_internodealigned_in_smp;
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index eb339c4a71f4..4a57e6c3352c 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -493,32 +493,6 @@ static bool vmemmap_should_optimize_folio(const struct hstate *h, struct folio *
return true;
}
-static struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
-{
- const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- struct page *tail, *p;
- int node = zone_to_nid(zone);
-
- tail = READ_ONCE(zone->vmemmap_tails[idx]);
- if (likely(tail))
- return tail;
-
- tail = alloc_pages_node(node, GFP_KERNEL | __GFP_ZERO, 0);
- if (!tail)
- return NULL;
-
- p = page_to_virt(tail);
- for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++)
- init_compound_tail(p + i, NULL, order, zone);
-
- if (cmpxchg(&zone->vmemmap_tails[idx], NULL, tail)) {
- __free_page(tail);
- tail = READ_ONCE(zone->vmemmap_tails[idx]);
- }
-
- return tail;
-}
-
static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
struct folio *folio,
struct list_head *vmemmap_pages,
@@ -535,7 +509,7 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
return ret;
nid = folio_nid(folio);
- vmemmap_tail = vmemmap_get_tail(h->order, folio_zone(folio));
+ vmemmap_tail = vmemmap_shared_tail_page(h->order, folio_zone(folio));
if (!vmemmap_tail)
return -ENOMEM;
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index e62e6aa07f12..70143dd8b579 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -42,27 +42,13 @@
#include "mm_init.h"
#include "sparse.h"
-/*
- * Allocate a block of memory to be used to back the virtual memory map
- * or to back the page tables that are used to create the mapping.
- * Uses the main allocators if they are available, else bootmem.
- */
-
-static void * __ref __earlyonly_bootmem_alloc(int node,
- unsigned long size,
- unsigned long align,
- unsigned long goal)
-{
- return memmap_alloc(size, align, goal, node, false);
-}
-
-void * __meminit vmemmap_alloc_block(unsigned long size, int node)
+void __ref *vmemmap_alloc_block(unsigned long size, int node)
{
/* If the main allocator is up use that, fallback to bootmem. */
if (slab_is_available()) {
gfp_t gfp_mask = GFP_KERNEL|__GFP_RETRY_MAYFAIL|__GFP_NOWARN;
int order = get_order(size);
- static bool warned __meminitdata;
+ static bool warned;
struct page *page;
page = alloc_pages_node(node, gfp_mask, order);
@@ -76,8 +62,7 @@ void * __meminit vmemmap_alloc_block(unsigned long size, int node)
}
return NULL;
} else
- return __earlyonly_bootmem_alloc(node, size, size,
- __pa(MAX_DMA_ADDRESS));
+ return memmap_alloc(size, size, __pa(MAX_DMA_ADDRESS), node, false);
}
static void * __meminit altmap_alloc_block_buf(unsigned long size,
@@ -184,39 +169,40 @@ static void * __meminit vmemmap_alloc_block_zero(unsigned long size, int node)
return p;
}
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
-static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
+struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zone)
{
- struct page *p, *tail;
- unsigned int idx;
- int node = zone_to_nid(zone);
+ void *addr;
+ struct page *page;
+ const unsigned int idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- if (WARN_ON_ONCE(order < VMEMMAP_OPTIMIZATION_MIN_ORDER))
- return NULL;
- if (WARN_ON_ONCE(order > MAX_FOLIO_ORDER))
+ if (WARN_ON_ONCE(idx >= ARRAY_SIZE(zone->vmemmap_tails)))
return NULL;
- idx = order - VMEMMAP_OPTIMIZATION_MIN_ORDER;
- tail = zone->vmemmap_tails[idx];
- if (tail)
- return tail;
- p = vmemmap_alloc_block_zero(PAGE_SIZE, node);
- if (!p)
+ page = READ_ONCE(zone->vmemmap_tails[idx]);
+ if (likely(page))
+ return page;
+
+ addr = vmemmap_alloc_block(PAGE_SIZE, zone_to_nid(zone));
+ if (!addr)
return NULL;
- for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++)
- init_compound_tail(p + i, NULL, order, zone);
- tail = virt_to_page(p);
- zone->vmemmap_tails[idx] = tail;
+ for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++) {
+ page = (struct page *)addr + i;
+ mm_zero_struct_page(page);
+ init_compound_tail(page, NULL, order, zone);
+ }
- return tail;
-}
-#else
-static inline struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
-{
- return NULL;
+ page = virt_to_page(addr);
+ if (cmpxchg(&zone->vmemmap_tails[idx], NULL, page) != NULL) {
+ if (slab_is_available())
+ __free_page(page);
+ else
+ memblock_free(addr, PAGE_SIZE);
+ page = READ_ONCE(zone->vmemmap_tails[idx]);
+ }
+
+ return page;
}
-#endif
static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
struct vmem_altmap *altmap)
@@ -229,7 +215,7 @@ static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
return vmemmap_alloc_block_buf(PAGE_SIZE, node, altmap);
zone = pfn_to_zone(pfn, node);
- page = vmemmap_get_tail(order, zone);
+ page = vmemmap_shared_tail_page(order, zone);
if (!page)
return NULL;
diff --git a/mm/sparse.h b/mm/sparse.h
index b408d15baf7b..59b825df83b9 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -139,6 +139,7 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
+struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
void sparse_init_subsection_map(void);
int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation
2026-08-31 7:53 ` [PATCH 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
@ 2026-09-01 3:05 ` Qi Zheng
0 siblings, 0 replies; 17+ messages in thread
From: Qi Zheng @ 2026-09-01 3:05 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin, Christophe Leroy,
Randy Dunlap
On 8/31/26 3:53 PM, Muchun Song 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.
>
> 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>
> ---
> 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(-)
>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 03/11] mm/sparse-vmemmap: open-code init_compound_tail()
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
2026-08-31 7:53 ` [PATCH 01/11] mm/sparse-vmemmap: introduce CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION Muchun Song
2026-08-31 7:53 ` [PATCH 02/11] mm/sparse-vmemmap: factor out shared vmemmap tail page allocation Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-09-01 3:18 ` Qi Zheng
2026-08-31 7:53 ` [PATCH 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders Muchun Song
` (7 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Muchun Song @ 2026-08-31 7:53 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>
---
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 e16f1250b25c..5ddb49680b06 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -783,15 +783,6 @@ static inline void prep_compound_tail(struct page *tail,
VM_WARN_ON_ONCE(tail->private);
}
-static inline void init_compound_tail(struct page *tail,
- const struct page *head, unsigned int order, struct zone *zone)
-{
- atomic_set(&tail->_mapcount, -1);
- set_page_node(tail, zone_to_nid(zone));
- set_page_zone(tail, zone_idx(zone));
- prep_compound_tail(tail, head, order);
-}
-
#if defined CONFIG_COMPACTION || defined CONFIG_CMA
/*
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 70143dd8b579..e453ce4675a0 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -189,7 +189,10 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++) {
page = (struct page *)addr + i;
mm_zero_struct_page(page);
- init_compound_tail(page, NULL, order, zone);
+ atomic_set(&page->_mapcount, -1);
+ set_page_node(page, zone_to_nid(zone));
+ set_page_zone(page, zone_idx(zone));
+ prep_compound_tail(page, NULL, order);
}
page = virt_to_page(addr);
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 03/11] mm/sparse-vmemmap: open-code init_compound_tail()
2026-08-31 7:53 ` [PATCH 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
@ 2026-09-01 3:18 ` Qi Zheng
0 siblings, 0 replies; 17+ messages in thread
From: Qi Zheng @ 2026-09-01 3:18 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin, Christophe Leroy,
Randy Dunlap
On 8/31/26 3:53 PM, Muchun Song wrote:
> init_compound_tail() is only used by vmemmap_shared_tail_page(), where
> the shared tail page setup intentionally passes NULL as the compound head.
>
> Keeping this helper in mm/internal.h exposes that special case to the rest
> of the MM code and can make the NULL head argument look generally valid.
> Open-code the initialization at the only call site so the special-case use
> stays local to sparse vmemmap optimization.
>
> No functional change intended.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> mm/internal.h | 9 ---------
> mm/sparse-vmemmap.c | 5 ++++-
> 2 files changed, 4 insertions(+), 10 deletions(-)
>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (2 preceding siblings ...)
2026-08-31 7:53 ` [PATCH 03/11] mm/sparse-vmemmap: open-code init_compound_tail() Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-09-03 6:36 ` Qi Zheng
2026-08-31 7:53 ` [PATCH 05/11] mm/sparse-vmemmap: set section order for device DAX Muchun Song
` (6 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: Muchun Song @ 2026-08-31 7:53 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
Device DAX still uses vmemmap_populate_compound_pages() to populate its
compound-page vmemmap mappings. That helper allocates the head and first
tail vmemmap pages explicitly, then reuses the first tail page for the
remaining tail page mappings.
Device DAX is being moved to the section-based vmemmap optimization
infrastructure, but it cannot switch to the generic section-based
population path yet. Once a later patch records the DAX compound-page
geometry in the section order, DAX head and first-tail PFNs can look
optimizable to the generic helpers as well.
Add a DAX-specific population flag for this transition. It keeps DAX
head/first-tail allocations on the normal vmemmap allocation path, while
preserving the existing page reference for reused DAX tail mappings.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
mm/sparse-vmemmap.c | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index e453ce4675a0..54ae8c284324 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -35,8 +35,8 @@
/*
* Flags for vmemmap_populate_range and friends.
*/
-/* Get a ref on the head page struct page, for ZONE_DEVICE compound pages */
-#define VMEMMAP_POPULATE_PAGEREF 0x0001
+/* Vmemmap population for ZONE_DEVICE compound pages */
+#define VMEMMAP_POPULATE_DAX 0x0001
#include "internal.h"
#include "mm_init.h"
@@ -208,13 +208,17 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
}
static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
- struct vmem_altmap *altmap)
+ struct vmem_altmap *altmap, unsigned long flags)
{
struct zone *zone;
struct page *page;
const unsigned int order = pfn_to_section_order(pfn);
- if (!vmemmap_optimizable_pfn(pfn))
+ /*
+ * Device DAX still relies on vmemmap_populate_compound_pages() for
+ * head/first-tail allocation and tail-page reuse.
+ */
+ if (!vmemmap_optimizable_pfn(pfn) || flags & VMEMMAP_POPULATE_DAX)
return vmemmap_alloc_block_buf(PAGE_SIZE, node, altmap);
zone = pfn_to_zone(pfn, node);
@@ -236,7 +240,7 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
pte_t entry;
if (ptpfn == (unsigned long)-1) {
- void *p = vmemmap_alloc_pte(pfn, node, altmap);
+ void *p = vmemmap_alloc_pte(pfn, node, altmap, flags);
if (!p)
return NULL;
@@ -251,7 +255,7 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
* and through vmemmap_populate_compound_pages() when
* slab is available.
*/
- if (flags & VMEMMAP_POPULATE_PAGEREF)
+ if (flags & VMEMMAP_POPULATE_DAX)
get_page(pfn_to_page(ptpfn));
}
entry = pfn_pte(ptpfn, PAGE_KERNEL);
@@ -511,6 +515,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long size, addr;
pte_t *pte;
int rc;
+ unsigned long flags = VMEMMAP_POPULATE_DAX;
if (reuse_compound_section(start_pfn, pgmap)) {
pte = compound_section_tail_page(start);
@@ -522,8 +527,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
* with just tail struct pages.
*/
return vmemmap_populate_range(start, end, node, NULL,
- pte_pfn(ptep_get(pte)),
- VMEMMAP_POPULATE_PAGEREF);
+ pte_pfn(ptep_get(pte)), flags);
}
size = min(end - start, pgmap_vmemmap_nr(pgmap) * sizeof(struct page));
@@ -531,13 +535,13 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long next, last = addr + size;
/* Populate the head page vmemmap page */
- pte = vmemmap_populate_address(addr, node, NULL, -1, 0);
+ pte = vmemmap_populate_address(addr, node, NULL, -1, flags);
if (!pte)
return -ENOMEM;
/* Populate the tail pages vmemmap page */
next = addr + PAGE_SIZE;
- pte = vmemmap_populate_address(next, node, NULL, -1, 0);
+ pte = vmemmap_populate_address(next, node, NULL, -1, flags);
if (!pte)
return -ENOMEM;
@@ -547,8 +551,7 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
*/
next += PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
- pte_pfn(ptep_get(pte)),
- VMEMMAP_POPULATE_PAGEREF);
+ pte_pfn(ptep_get(pte)), flags);
if (rc)
return -ENOMEM;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders
2026-08-31 7:53 ` [PATCH 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders Muchun Song
@ 2026-09-03 6:36 ` Qi Zheng
0 siblings, 0 replies; 17+ messages in thread
From: Qi Zheng @ 2026-09-03 6:36 UTC (permalink / raw)
To: Muchun Song, Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Nicholas Piggin, Christophe Leroy,
Randy Dunlap
On 8/31/26 3:53 PM, Muchun Song wrote:
> Device DAX still uses vmemmap_populate_compound_pages() to populate its
> compound-page vmemmap mappings. That helper allocates the head and first
> tail vmemmap pages explicitly, then reuses the first tail page for the
> remaining tail page mappings.
>
> Device DAX is being moved to the section-based vmemmap optimization
> infrastructure, but it cannot switch to the generic section-based
> population path yet. Once a later patch records the DAX compound-page
> geometry in the section order, DAX head and first-tail PFNs can look
> optimizable to the generic helpers as well.
>
> Add a DAX-specific population flag for this transition. It keeps DAX
> head/first-tail allocations on the normal vmemmap allocation path, while
> preserving the existing page reference for reused DAX tail mappings.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> mm/sparse-vmemmap.c | 27 +++++++++++++++------------
> 1 file changed, 15 insertions(+), 12 deletions(-)
>
Acked-by: Qi Zheng <qi.zheng@linux.dev>
Thanks,
Qi
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 05/11] mm/sparse-vmemmap: set section order for device DAX
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (3 preceding siblings ...)
2026-08-31 7:53 ` [PATCH 04/11] mm/sparse-vmemmap: prepare DAX vmemmap population for section orders Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-08-31 7:53 ` [PATCH 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-08-31 7:53 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
Device DAX can use vmemmap optimization only when a full section is
populated with a compound-page geometry. Record that geometry in the
section order before populating the section, so later vmemmap accounting
and population decisions can use the section state directly.
Clear the section order when the section becomes empty again. Also reject
partial additions to a section that already has optimized vmemmap mappings,
because a section 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>
---
mm/mm_init.c | 13 ++++---------
mm/sparse-vmemmap.c | 16 ++++++++++++----
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 33ff95141adb..c4cd61978ce8 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1042,16 +1042,11 @@ static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
* 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));
@@ -1121,7 +1116,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
continue;
memmap_init_compound(page, pfn, zone_idx, nid, pgmap,
- compound_nr_pages(pfn, altmap, pgmap));
+ compound_nr_pages(pfn, pgmap));
}
pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE,
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 54ae8c284324..aed1e7429daa 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -135,14 +135,14 @@ int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages
struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
{
const struct mem_section *ms = __pfn_to_section(pfn);
- const int order = pgmap ? pgmap->vmemmap_shift : section_order(ms);
+ const int order = section_order(ms);
const int vmemmap_pages = pgmap ? VMEMMAP_RESERVE_NR : VMEMMAP_OPTIMIZATION_PAGES;
const unsigned long pages_per_compound = 1UL << order;
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
VM_WARN_ON_ONCE(nr_pages > PAGES_PER_SECTION);
- if (!vmemmap_can_optimize(altmap, pgmap) && !section_vmemmap_optimizable(ms))
+ if (!section_vmemmap_optimizable(ms))
return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
if (order < PFN_SECTION_SHIFT) {
@@ -573,7 +573,7 @@ struct page * __meminit __populate_section_memmap(unsigned long pfn,
!IS_ALIGNED(nr_pages, PAGES_PER_SUBSECTION)))
return NULL;
- if (vmemmap_can_optimize(altmap, pgmap))
+ if (pgmap && section_vmemmap_optimizable(__pfn_to_section(pfn)))
r = vmemmap_populate_compound_pages(pfn, start, end, nid, pgmap);
else
r = vmemmap_populate(start, end, nid, altmap);
@@ -792,8 +792,10 @@ static void section_deactivate(unsigned long pfn, unsigned long nr_pages,
else if (memmap)
free_map_bootmem(memmap);
- if (empty)
+ if (empty) {
ms->section_mem_map = (unsigned long)NULL;
+ section_set_order(ms, 0);
+ }
}
static struct page * __meminit section_activate(int nid, unsigned long pfn,
@@ -803,8 +805,13 @@ static struct page * __meminit section_activate(int nid, unsigned long pfn,
struct mem_section *ms = __pfn_to_section(pfn);
struct mem_section_usage *usage = NULL;
struct page *memmap;
+ unsigned int order;
int rc;
+ order = vmemmap_can_optimize(altmap, pgmap) ? pgmap->vmemmap_shift : 0;
+ if (nr_pages < PAGES_PER_SECTION && section_order(ms))
+ return ERR_PTR(-ENOTSUPP);
+
if (!ms->usage) {
usage = kzalloc(mem_section_usage_size(), GFP_KERNEL);
if (!usage)
@@ -830,6 +837,7 @@ static struct page * __meminit section_activate(int nid, unsigned long pfn,
if (nr_pages < PAGES_PER_SECTION && early_section(ms))
return pfn_to_page(pfn);
+ section_set_order_range(pfn, nr_pages, order);
memmap = populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
if (!memmap) {
section_deactivate(pfn, nr_pages, altmap, pgmap);
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (4 preceding siblings ...)
2026-08-31 7:53 ` [PATCH 05/11] mm/sparse-vmemmap: set section order for device DAX Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-08-31 7:53 ` [PATCH 07/11] mm/sparse-vmemmap: move HVO helpers to a public header Muchun Song
` (4 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-08-31 7:53 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.
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>
---
include/linux/mmzone.h | 10 +++++++++
mm/memory_hotplug.c | 5 +++--
mm/sparse-vmemmap.c | 47 ++++++++++++++----------------------------
3 files changed, 28 insertions(+), 34 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index d3778ba976a5..03ed9232f186 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1686,11 +1686,21 @@ static inline bool zone_is_zone_device(const struct zone *zone)
{
return zone_idx(zone) == ZONE_DEVICE;
}
+
+static inline struct zone *device_zone(int nid)
+{
+ return &NODE_DATA(nid)->node_zones[ZONE_DEVICE];
+}
#else
static inline bool zone_is_zone_device(const struct zone *zone)
{
return false;
}
+
+static inline struct zone *device_zone(int nid)
+{
+ return NULL;
+}
#endif
/*
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 226ab9cb078a..d28bafb6fd53 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -554,8 +554,9 @@ void remove_pfn_range_from_zone(struct zone *zone,
/* Select all remaining pages up to the next section boundary */
cur_nr_pages =
min(end_pfn - pfn, SECTION_ALIGN_UP(pfn + 1) - pfn);
- page_init_poison(pfn_to_page(pfn),
- sizeof(struct page) * cur_nr_pages);
+ if (!section_vmemmap_optimizable(__pfn_to_section(pfn)))
+ page_init_poison(pfn_to_page(pfn),
+ sizeof(struct page) * cur_nr_pages);
}
/*
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index aed1e7429daa..0201877a7f80 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -193,6 +193,8 @@ struct page __ref *vmemmap_shared_tail_page(unsigned int order, struct zone *zon
set_page_node(page, zone_to_nid(zone));
set_page_zone(page, zone_idx(zone));
prep_compound_tail(page, NULL, order);
+ if (zone_is_zone_device(zone))
+ __SetPageReserved(page);
}
page = virt_to_page(addr);
@@ -490,23 +492,6 @@ static bool __meminit reuse_compound_section(unsigned long start_pfn,
return !IS_ALIGNED(offset, nr_pages) && nr_pages > PAGES_PER_SUBSECTION;
}
-static pte_t * __meminit compound_section_tail_page(unsigned long addr)
-{
- pte_t *pte;
-
- addr -= PAGE_SIZE;
-
- /*
- * Assuming sections are populated sequentially, the previous section's
- * page data can be reused.
- */
- pte = pte_offset_kernel(pmd_off_k(addr), addr);
- if (!pte)
- return NULL;
-
- return pte;
-}
-
static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long start,
unsigned long end, int node,
@@ -516,21 +501,18 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
pte_t *pte;
int rc;
unsigned long flags = VMEMMAP_POPULATE_DAX;
+ struct page *page;
+ unsigned int order = pfn_to_section_order(start_pfn);
- if (reuse_compound_section(start_pfn, pgmap)) {
- pte = compound_section_tail_page(start);
- if (!pte)
- return -ENOMEM;
+ page = vmemmap_shared_tail_page(order, device_zone(node));
+ if (!page)
+ return -ENOMEM;
- /*
- * Reuse the page that was populated in the prior iteration
- * with just tail struct pages.
- */
+ if (reuse_compound_section(start_pfn, pgmap))
return vmemmap_populate_range(start, end, node, NULL,
- pte_pfn(ptep_get(pte)), flags);
- }
+ page_to_pfn(page), flags);
- size = min(end - start, pgmap_vmemmap_nr(pgmap) * sizeof(struct page));
+ size = min(end - start, (1UL << order) * sizeof(struct page));
for (addr = start; addr < end; addr += size) {
unsigned long next, last = addr + size;
@@ -546,12 +528,12 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
return -ENOMEM;
/*
- * Reuse the previous page for the rest of tail pages
+ * Reuse the shared page for the rest of tail pages
* See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
next += PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
- pte_pfn(ptep_get(pte)), flags);
+ page_to_pfn(page), flags);
if (rc)
return -ENOMEM;
}
@@ -883,13 +865,14 @@ int __meminit sparse_add_section(int nid, unsigned long start_pfn,
if (IS_ERR(memmap))
return PTR_ERR(memmap);
+ ms = __nr_to_section(section_nr);
/*
* Poison uninitialized struct pages in order to catch invalid flags
* combinations.
*/
- page_init_poison(memmap, sizeof(struct page) * nr_pages);
+ if (!section_vmemmap_optimizable(ms))
+ page_init_poison(memmap, sizeof(struct page) * nr_pages);
- ms = __nr_to_section(section_nr);
__section_mark_present(ms, section_nr);
/* Align memmap to section boundary in the subsection case */
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 07/11] mm/sparse-vmemmap: move HVO helpers to a public header
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (5 preceding siblings ...)
2026-08-31 7:53 ` [PATCH 06/11] mm/sparse-vmemmap: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-08-31 7:53 ` [PATCH 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
` (3 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-08-31 7:53 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The vmemmap optimization helpers currently live in mm/sparse.h,
which is an internal MM header. That works for MM code, but
prevents powerpc from using the same interfaces without including a
private header.
Move the declarations and inline helpers to
include/linux/vmemmap-optimization.h. This is a preparatory change for
powerpc, which has its own vmemmap optimization implementation and
needs to use the HVO interfaces from architecture code.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
MAINTAINERS | 1 +
include/linux/vmemmap-optimization.h | 88 ++++++++++++++++++++++++++++
mm/hugetlb.c | 2 +-
mm/hugetlb_vmemmap.c | 2 +-
mm/memory_hotplug.c | 1 +
mm/sparse.h | 73 +----------------------
6 files changed, 93 insertions(+), 74 deletions(-)
create mode 100644 include/linux/vmemmap-optimization.h
diff --git a/MAINTAINERS b/MAINTAINERS
index a3167bcbe589..ccc7f75bbeb0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12099,6 +12099,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..171553fabd47
--- /dev/null
+++ b/include/linux/vmemmap-optimization.h
@@ -0,0 +1,88 @@
+/* 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/mmzone.h>
+
+#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
+static inline unsigned int section_order(const struct mem_section *section)
+{
+ return section->order;
+}
+
+static inline void section_set_order(struct mem_section *section, unsigned int order)
+{
+ VM_WARN_ON(section_order(section) && order && section_order(section) != order);
+ section->order = order;
+}
+
+static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
+ unsigned int order)
+{
+ unsigned long section_nr = pfn_to_section_nr(pfn);
+
+ if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
+ return;
+
+ for (unsigned long i = 0; i < nr_pages / PAGES_PER_SECTION; i++)
+ section_set_order(__nr_to_section(section_nr + i), order);
+}
+
+static inline unsigned int pfn_to_section_order(unsigned long pfn)
+{
+ return section_order(__pfn_to_section(pfn));
+}
+#else
+static inline unsigned int section_order(const struct mem_section *section)
+{
+ return 0;
+}
+
+static inline void section_set_order(struct mem_section *section, unsigned int order)
+{
+}
+
+static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
+ unsigned int order)
+{
+}
+
+static inline unsigned int pfn_to_section_order(unsigned long pfn)
+{
+ return 0;
+}
+#endif /* CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION */
+
+static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
+{
+ const unsigned int order = pfn_to_section_order(pfn);
+ const unsigned long nr_pages = 1UL << order;
+
+ if (!is_power_of_2(sizeof(struct page)))
+ return false;
+
+ return (pfn & (nr_pages - 1)) >= VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES;
+}
+
+static inline bool vmemmap_optimizable_order(unsigned int order)
+{
+ if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
+ return false;
+
+ if (!is_power_of_2(sizeof(struct page)))
+ return false;
+
+ return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
+}
+
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
+#endif /* CONFIG_SPARSEMEM_VMEMMAP */
+#endif /* _LINUX_VMEMMAP_OPTIMIZATION_H */
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 8fa1bafa03d9..5aa9c0eaf2d0 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -38,6 +38,7 @@
#include <linux/mm_inline.h>
#include <linux/padata.h>
#include <linux/pgalloc.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/page.h>
#include <asm/tlb.h>
@@ -52,7 +53,6 @@
#include "hugetlb_cma.h"
#include "hugetlb_internal.h"
#include "mm_init.h"
-#include "sparse.h"
#include <linux/page-isolation.h>
#define HUGE_BOOTMEM_ZONES_VALID BIT(0)
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 4a57e6c3352c..25c4e7d2664c 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -15,10 +15,10 @@
#include <linux/mmdebug.h>
#include <linux/pagewalk.h>
#include <linux/pgalloc.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/tlbflush.h>
#include "hugetlb_vmemmap.h"
-#include "sparse.h"
#include "internal.h"
/**
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index d28bafb6fd53..4f3387632883 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -43,6 +43,7 @@
#include "mm_init.h"
#include "page_alloc.h"
#include "shuffle.h"
+#include "sparse.h"
enum {
MEMMAP_ON_MEMORY_DISABLE = 0,
diff --git a/mm/sparse.h b/mm/sparse.h
index 59b825df83b9..e511d99fc26b 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -9,77 +9,7 @@
#define __MM_SPARSE_H
#include <linux/mmzone.h>
-
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION
-static inline unsigned int section_order(const struct mem_section *section)
-{
- return section->order;
-}
-
-static inline void section_set_order(struct mem_section *section, unsigned int order)
-{
- VM_WARN_ON(section_order(section) && order && section_order(section) != order);
- section->order = order;
-}
-
-static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
- unsigned int order)
-{
- unsigned long section_nr = pfn_to_section_nr(pfn);
-
- if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
- return;
-
- for (unsigned long i = 0; i < nr_pages / PAGES_PER_SECTION; i++)
- section_set_order(__nr_to_section(section_nr + i), order);
-}
-
-static inline unsigned int pfn_to_section_order(unsigned long pfn)
-{
- return section_order(__pfn_to_section(pfn));
-}
-#else
-static inline unsigned int section_order(const struct mem_section *section)
-{
- return 0;
-}
-
-static inline void section_set_order(struct mem_section *section, unsigned int order)
-{
-}
-
-static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
- unsigned int order)
-{
-}
-
-static inline unsigned int pfn_to_section_order(unsigned long pfn)
-{
- return 0;
-}
-#endif
-
-static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
-{
- const unsigned int order = pfn_to_section_order(pfn);
- const unsigned long nr_pages = 1UL << order;
-
- if (!is_power_of_2(sizeof(struct page)))
- return false;
-
- return (pfn & (nr_pages - 1)) >= VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES;
-}
-
-static inline bool vmemmap_optimizable_order(unsigned int order)
-{
- if (!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION))
- return false;
-
- if (!is_power_of_2(sizeof(struct page)))
- return false;
-
- return order >= VMEMMAP_OPTIMIZATION_MIN_ORDER;
-}
+#include <linux/vmemmap-optimization.h>
/*
* mm/sparse.c
@@ -139,7 +69,6 @@ static inline void sparse_sections_init(void) {}
* mm/sparse-vmemmap.c
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
-struct page *vmemmap_shared_tail_page(unsigned int order, struct zone *zone);
void sparse_init_subsection_map(void);
int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (6 preceding siblings ...)
2026-08-31 7:53 ` [PATCH 07/11] mm/sparse-vmemmap: move HVO helpers to a public header Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-08-31 7:53 ` [PATCH 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-08-31 7:53 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The powerpc radix compound vmemmap population path still finds a reusable
tail page by walking the vmemmap page tables.
Switch it to the common vmemmap_shared_tail_page() helper instead, so it
can use the shared vmemmap page directly to simplify the code.
This removes the powerpc-specific tail-page lookup and its fallback path
and aligns the device DAX vmemmap optimization path with HugeTLB.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 80 +++---------------------
1 file changed, 9 insertions(+), 71 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index cf692b2b5f7b..831c231a4a18 100644
--- a/arch/powerpc/mm/book3s64/radix_pgtable.c
+++ b/arch/powerpc/mm/book3s64/radix_pgtable.c
@@ -19,6 +19,7 @@
#include <linux/string_helpers.h>
#include <linux/memory.h>
#include <linux/kfence.h>
+#include <linux/vmemmap-optimization.h>
#include <asm/pgalloc.h>
#include <asm/mmu_context.h>
@@ -1250,59 +1251,6 @@ static pte_t * __meminit radix__vmemmap_populate_address(unsigned long addr, int
return pte;
}
-static pte_t * __meminit vmemmap_compound_tail_page(unsigned long addr,
- unsigned long pfn_offset, int node)
-{
- pgd_t *pgd;
- p4d_t *p4d;
- pud_t *pud;
- pmd_t *pmd;
- pte_t *pte;
- unsigned long map_addr;
-
- /* the second vmemmap page which we use for duplication */
- map_addr = addr - pfn_offset * sizeof(struct page) + PAGE_SIZE;
- pgd = pgd_offset_k(map_addr);
- p4d = p4d_offset(pgd, map_addr);
- pud = vmemmap_pud_alloc(p4d, node, map_addr);
- if (!pud)
- return NULL;
- pmd = vmemmap_pmd_alloc(pud, node, map_addr);
- if (!pmd)
- return NULL;
- if (pmd_leaf(*pmd))
- /*
- * The second page is mapped as a hugepage due to a nearby request.
- * Force our mapping to page size without deduplication
- */
- return NULL;
- pte = vmemmap_pte_alloc(pmd, node, map_addr);
- if (!pte)
- return NULL;
- /*
- * Check if there exist a mapping to the left
- */
- if (pte_none(*pte)) {
- /*
- * Populate the head page vmemmap page.
- * It can fall in different pmd, hence
- * vmemmap_populate_address()
- */
- pte = radix__vmemmap_populate_address(map_addr - PAGE_SIZE, node, NULL, NULL);
- if (!pte)
- return NULL;
- /*
- * Populate the tail pages vmemmap page
- */
- pte = radix__vmemmap_pte_populate(pmd, map_addr, node, NULL, NULL);
- if (!pte)
- return NULL;
- vmemmap_verify(pte, node, map_addr, map_addr + PAGE_SIZE);
- return pte;
- }
- return pte;
-}
-
int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long start,
unsigned long end, int node,
@@ -1320,6 +1268,12 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
+ struct page *tail_page;
+ unsigned int order = pfn_to_section_order(start_pfn);
+
+ tail_page = vmemmap_shared_tail_page(order, device_zone(node));
+ if (!tail_page)
+ return -ENOMEM;
for (addr = start; addr < end; addr = next) {
@@ -1349,10 +1303,9 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
next = addr + PAGE_SIZE;
continue;
} else {
- unsigned long nr_pages = pgmap_vmemmap_nr(pgmap);
+ unsigned long nr_pages = 1UL << order;
unsigned long addr_pfn = page_to_pfn((struct page *)addr);
unsigned long pfn_offset = addr_pfn - ALIGN_DOWN(addr_pfn, nr_pages);
- pte_t *tail_page_pte;
/*
* if the address is aligned to huge page size it is the
@@ -1377,23 +1330,8 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
next = addr + 2 * PAGE_SIZE;
continue;
}
- /*
- * get the 2nd mapping details
- * Also create it if that doesn't exist
- */
- tail_page_pte = vmemmap_compound_tail_page(addr, pfn_offset, node);
- if (!tail_page_pte) {
-
- pte = radix__vmemmap_pte_populate(pmd, addr, node, NULL, NULL);
- if (!pte)
- return -ENOMEM;
- vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
-
- next = addr + PAGE_SIZE;
- continue;
- }
- pte = radix__vmemmap_pte_populate(pmd, addr, node, NULL, pte_page(*tail_page_pte));
+ pte = radix__vmemmap_pte_populate(pmd, addr, node, NULL, tail_page);
if (!pte)
return -ENOMEM;
vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (7 preceding siblings ...)
2026-08-31 7:53 ` [PATCH 08/11] powerpc/mm: switch device DAX to shared tail vmemmap pages Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-08-31 7:53 ` [PATCH 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
2026-08-31 7:53 ` [PATCH 11/11] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-08-31 7:53 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
The device DAX vmemmap population still reserves one extra tail vmemmap
page after the head page.
Drop that extra reservation and let the shared tail page cover all tail
vmemmap pages after the head page, so DAX follows the same reservation
model as HugeTLB.
This reduces the reserved vmemmap pages for optimized DAX mappings to
one and removes the now-unneeded first-tail population from the generic
and powerpc paths to simplify the code as well.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
arch/powerpc/mm/book3s64/radix_pgtable.c | 46 ++----------------------
include/linux/mm.h | 3 +-
mm/mm_init.c | 2 +-
mm/sparse-vmemmap.c | 13 ++-----
4 files changed, 7 insertions(+), 57 deletions(-)
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index 831c231a4a18..e7e751c48dd2 100644
--- a/arch/powerpc/mm/book3s64/radix_pgtable.c
+++ b/arch/powerpc/mm/book3s64/radix_pgtable.c
@@ -1218,39 +1218,6 @@ int __meminit radix__vmemmap_populate(unsigned long start, unsigned long end, in
return 0;
}
-static pte_t * __meminit radix__vmemmap_populate_address(unsigned long addr, int node,
- struct vmem_altmap *altmap,
- struct page *reuse)
-{
- pgd_t *pgd;
- p4d_t *p4d;
- pud_t *pud;
- pmd_t *pmd;
- pte_t *pte;
-
- pgd = pgd_offset_k(addr);
- p4d = p4d_offset(pgd, addr);
- pud = vmemmap_pud_alloc(p4d, node, addr);
- if (!pud)
- return NULL;
- pmd = vmemmap_pmd_alloc(pud, node, addr);
- if (!pmd)
- return NULL;
- if (pmd_leaf(*pmd))
- /*
- * The second page is mapped as a hugepage due to a nearby request.
- * Force our mapping to page size without deduplication
- */
- return NULL;
- pte = vmemmap_pte_alloc(pmd, node, addr);
- if (!pte)
- return NULL;
- radix__vmemmap_pte_populate(pmd, addr, node, NULL, NULL);
- vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
-
- return pte;
-}
-
int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
unsigned long start,
unsigned long end, int node,
@@ -1297,7 +1264,7 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
if (!pte_none(*pte)) {
/*
* This could be because we already have a compound
- * page whose VMEMMAP_RESERVE_NR pages were mapped and
+ * page whose retained vmemmap page was mapped and
* this request fall in those pages.
*/
next = addr + PAGE_SIZE;
@@ -1318,16 +1285,7 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
return -ENOMEM;
vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
- /*
- * Populate the tail pages vmemmap page
- * It can fall in different pmd, hence
- * vmemmap_populate_address()
- */
- pte = radix__vmemmap_populate_address(addr + PAGE_SIZE, node, NULL, NULL);
- if (!pte)
- return -ENOMEM;
-
- next = addr + 2 * PAGE_SIZE;
+ next = addr + PAGE_SIZE;
continue;
}
diff --git a/include/linux/mm.h b/include/linux/mm.h
index edadd7549b72..edc7b9ce9e79 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5180,7 +5180,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)
@@ -5200,7 +5199,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 c4cd61978ce8..d520fd8de0df 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1049,7 +1049,7 @@ static inline unsigned long compound_nr_pages(unsigned long pfn,
if (!section_vmemmap_optimizable(ms))
return pgmap_vmemmap_nr(pgmap);
- return VMEMMAP_RESERVE_NR * (PAGE_SIZE / sizeof(struct page));
+ return VMEMMAP_OPTIMIZATION_PAGES * (PAGE_SIZE / sizeof(struct page));
}
static void __ref memmap_init_compound(struct page *head,
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 0201877a7f80..e655d9d1348f 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -136,7 +136,6 @@ int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages
{
const struct mem_section *ms = __pfn_to_section(pfn);
const int order = section_order(ms);
- const int vmemmap_pages = pgmap ? VMEMMAP_RESERVE_NR : VMEMMAP_OPTIMIZATION_PAGES;
const unsigned long pages_per_compound = 1UL << order;
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
@@ -147,13 +146,13 @@ int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages
if (order < PFN_SECTION_SHIFT) {
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
- return vmemmap_pages * nr_pages / pages_per_compound;
+ return VMEMMAP_OPTIMIZATION_PAGES * nr_pages / pages_per_compound;
}
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
if (IS_ALIGNED(pfn, pages_per_compound))
- return vmemmap_pages;
+ return VMEMMAP_OPTIMIZATION_PAGES;
return 0;
}
@@ -521,17 +520,11 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn,
if (!pte)
return -ENOMEM;
- /* Populate the tail pages vmemmap page */
- next = addr + PAGE_SIZE;
- pte = vmemmap_populate_address(next, node, NULL, -1, flags);
- if (!pte)
- return -ENOMEM;
-
/*
* Reuse the shared page for the rest of tail pages
* See layout diagram in Documentation/mm/vmemmap_dedup.rst
*/
- next += PAGE_SIZE;
+ next = addr + PAGE_SIZE;
rc = vmemmap_populate_range(next, last, node, NULL,
page_to_pfn(page), flags);
if (rc)
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (8 preceding siblings ...)
2026-08-31 7:53 ` [PATCH 09/11] mm/sparse-vmemmap: drop the extra tail page from device DAX reservation Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
2026-08-31 7:53 ` [PATCH 11/11] Documentation/mm: update DAX vmemmap deduplication docs Muchun Song
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-08-31 7:53 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Oscar Salvador,
Madhavan Srinivasan, Michael Ellerman, Jonathan Corbet
Cc: linux-mm, linux-kernel, linuxppc-dev, linux-doc, Muchun Song,
Lorenzo Stoakes, Mike Rapoport, Qi Zheng, Nicholas Piggin,
Christophe Leroy, Randy Dunlap, Muchun Song
section_nr_vmemmap_pages() no longer uses the altmap or pgmap
arguments, so drop them from the helper and its callers.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
mm/sparse-vmemmap.c | 10 ++++------
mm/sparse.c | 3 +--
mm/sparse.h | 6 ++----
3 files changed, 7 insertions(+), 12 deletions(-)
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index e655d9d1348f..4950ac2a1c01 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -131,8 +131,7 @@ void __meminit vmemmap_verify(pte_t *pte, int node,
start, end - 1);
}
-int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
- struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages)
{
const struct mem_section *ms = __pfn_to_section(pfn);
const int order = section_order(ms);
@@ -637,7 +636,7 @@ static struct page * __meminit populate_section_memmap(unsigned long pfn,
struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
pgmap);
- memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+ memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages));
return page;
}
@@ -648,7 +647,7 @@ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
unsigned long start = (unsigned long) pfn_to_page(pfn);
unsigned long end = start + nr_pages * sizeof(struct page);
- memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+ memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages));
vmemmap_free(start, end, altmap);
}
@@ -658,8 +657,7 @@ static void free_map_bootmem(struct page *memmap)
unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
unsigned long pfn = page_to_pfn(memmap);
- memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
- NULL, NULL));
+ memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION));
vmemmap_free(start, end, NULL);
}
diff --git a/mm/sparse.c b/mm/sparse.c
index 9349ed6326c0..adf057f54c0f 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -251,8 +251,7 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
nid, NULL, NULL);
if (!map)
panic("Failed to allocate memmap for section %lu\n", pnum);
- memmap_boot_pages_add(section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
- NULL, NULL));
+ memmap_boot_pages_add(section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION));
sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
SECTION_IS_EARLY);
usage = (void *)usage + mem_section_usage_size();
diff --git a/mm/sparse.h b/mm/sparse.h
index e511d99fc26b..5d0f407bde7e 100644
--- a/mm/sparse.h
+++ b/mm/sparse.h
@@ -70,12 +70,10 @@ static inline void sparse_sections_init(void) {}
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
void sparse_init_subsection_map(void);
-int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
- struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
+int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages);
#else
static inline void sparse_init_subsection_map(void) {}
-static inline int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
- struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+static inline int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages)
{
return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 11/11] Documentation/mm: update DAX vmemmap deduplication docs
2026-08-31 7:53 [PATCH 00/11] mm: Switch device DAX to section-based vmemmap optimization Muchun Song
` (9 preceding siblings ...)
2026-08-31 7:53 ` [PATCH 10/11] mm/sparse-vmemmap: drop unused section_nr_vmemmap_pages() arguments Muchun Song
@ 2026-08-31 7:53 ` Muchun Song
10 siblings, 0 replies; 17+ messages in thread
From: Muchun Song @ 2026-08-31 7:53 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
and keep the powerpc-specific size calculations while avoiding duplicate
diagrams.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
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 related [flat|nested] 17+ messages in thread