From: "Gupta, Pankaj" <pankaj.gupta@amd.com>
To: Muchun Song <songmuchun@bytedance.com>,
Andrew Morton <akpm@linux-foundation.org>,
Dan Williams <djbw@kernel.org>,
David Hildenbrand <david@kernel.org>
Cc: linux-mm@kvack.org, nvdimm@lists.linux.dev,
linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-cxl@vger.kernel.org,
Vishal Verma <vishal.l.verma@intel.com>,
Dave Jiang <dave.jiang@intel.com>,
Alison Schofield <alison.schofield@intel.com>,
Mike Rapoport <rppt@kernel.org>,
Oscar Salvador <osalvador@suse.de>, Ira Weiny <iweiny@kernel.org>,
Jan Kara <jack@suse.cz>, Matthew Wilcox <willy@infradead.org>,
Lorenzo Stoakes <ljs@kernel.org>,
Vlastimil Babka <vbabka@kernel.org>,
Michal Hocko <mhocko@suse.com>, Qi Zheng <qi.zheng@linux.dev>,
muchun.song@linux.dev
Subject: Re: [PATCH 3/4] mm: add shared read-only vmemmap support for FS-DAX
Date: Mon, 21 Sep 2026 08:42:19 +0200 [thread overview]
Message-ID: <4bc1f4de-40ad-46f0-8d37-4c4e0cad0ad6@amd.com> (raw)
In-Reply-To: <20260903122128.12264-4-songmuchun@bytedance.com>
> FS-DAX registers persistent-memory ranges as ZONE_DEVICE memory, and the
> kernel normally allocates and initializes vmemmap storage for every
> advertised PFN up front. Sparse pmem images and workloads that only use the
> DAX direct-access path may never need writable per-PFN state for most of
> that range, but still pay the memory and initialization cost.
>
> Add an opt-in dev_pagemap mode that populates FS-DAX vmemmap PTEs from a
> shared read-only metadata page. The shared page is initialized with the
> common ZONE_DEVICE and dev_pagemap state, so every PFN still has a valid
> struct page representation while private metadata allocation is deferred.
>
> This relies on sizeof(struct page) being a power of two, so each vmemmap
> page contains a naturally aligned and repeatable set of struct page slots.
> It also requires architecture support for runtime vmemmap remapping,
> because shared mappings must be replaced with private writable pages before
> a PFN can enter userspace mappings.
>
> The initial implementation is deliberately limited to a single
> memory-block-aligned range. That is not a fundamental requirement, but keeps
> the registration and teardown paths simple; support for multiple ranges or
> less strict alignment can be added later.
>
> Provide vmemmap_materialize_page() to replace shared mappings in the
> requested metadata range with private writable copies. A later patch will
> call it from the FS-DAX fault path.
>
> No caller enables the mode yet.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> include/linux/memremap.h | 11 ++++++-
> mm/memremap.c | 38 ++++++++++++++++++++++--
> mm/mm_init.c | 11 +++++++
> mm/sparse-vmemmap.c | 64 +++++++++++++++++++++++++++++++++++++---
> 4 files changed, 116 insertions(+), 8 deletions(-)
>
> diff --git a/include/linux/memremap.h b/include/linux/memremap.h
> index e3c2ccf872a8..21c9b6aeef67 100644
> --- a/include/linux/memremap.h
> +++ b/include/linux/memremap.h
> @@ -9,6 +9,7 @@
>
> struct resource;
> struct device;
> +struct page;
>
> /**
> * struct vmem_altmap - pre-allocated storage for vmemmap_populate
> @@ -108,7 +109,8 @@ struct dev_pagemap_ops {
> void (*folio_split)(struct folio *head, struct folio *tail);
> };
>
> -#define PGMAP_ALTMAP_VALID (1 << 0)
> +#define PGMAP_ALTMAP_VALID BIT(0)
> +#define PGMAP_VMEMMAP_OPTIMIZATION BIT(1)
I liked the overall idea.
Minor naming suggestion I have:
'PGMAP_VMEMMAP_OPTIMIZATION' feel too generic to me.
Maybe something on the lines to reflect the actual optimization:
|PGMAP_SHARED_VMEMMAP or PGMAP_VMEMMAP_ON_DEMAND or some_other_name?|
|Thanks,|
|Pankaj|
>
> /**
> * struct dev_pagemap - metadata for ZONE_DEVICE mappings
> @@ -122,6 +124,7 @@ struct dev_pagemap_ops {
> * A zero value (default) uses base pages as the vmemmap metadata
> * representation. A bigger value will set up compound struct pages
> * of the requested order value.
> + * @vmemmap_shared_page: shared read-only vmemmap page for optimized FS-DAX
> * @ops: method table
> * @owner: an opaque pointer identifying the entity that manages this
> * instance. Used by various helpers to make sure that no
> @@ -137,6 +140,7 @@ struct dev_pagemap {
> enum memory_type type;
> unsigned int flags;
> unsigned long vmemmap_shift;
> + struct page *vmemmap_shared_page;
> const struct dev_pagemap_ops *ops;
> void *owner;
> int nr_range;
> @@ -232,6 +236,7 @@ void *devm_memremap_pages(struct device *dev, struct dev_pagemap *pgmap);
> void devm_memunmap_pages(struct device *dev, struct dev_pagemap *pgmap);
> struct dev_pagemap *get_dev_pagemap(unsigned long pfn);
> bool pgmap_pfn_valid(struct dev_pagemap *pgmap, unsigned long pfn);
> +int vmemmap_materialize_page(struct page *page, unsigned int order);
>
> unsigned long memremap_compat_align(void);
>
> @@ -307,4 +312,8 @@ static inline void put_dev_pagemap(struct dev_pagemap *pgmap)
> percpu_ref_put(&pgmap->ref);
> }
>
> +static inline bool pgmap_vmemmap_optimizable(struct dev_pagemap *pgmap)
> +{
> + return pgmap && pgmap->vmemmap_shared_page != NULL;
> +}
> #endif /* _LINUX_MEMREMAP_H_ */
> diff --git a/mm/memremap.c b/mm/memremap.c
> index accba23aef28..a53d09b84eaa 100644
> --- a/mm/memremap.c
> +++ b/mm/memremap.c
> @@ -3,6 +3,7 @@
> #include <linux/device.h>
> #include <linux/io.h>
> #include <linux/kasan.h>
> +#include <linux/memory.h>
> #include <linux/memory_hotplug.h>
> #include <linux/memremap.h>
> #include <linux/swap.h>
> @@ -83,6 +84,30 @@ static unsigned long pfn_len(struct dev_pagemap *pgmap, unsigned long range_id)
> pfn_first(pgmap, range_id)) >> pgmap->vmemmap_shift;
> }
>
> +static int pgmap_vmemmap_shared_page_alloc(struct dev_pagemap *pgmap, int nid)
> +{
> + const struct range *range = &pgmap->range;
> +
> + if (!is_power_of_2(sizeof(struct page)) ||
> + !IS_ENABLED(CONFIG_ARCH_SUPPORTS_VMEMMAP_REMAP) ||
> + !(pgmap->flags & PGMAP_VMEMMAP_OPTIMIZATION))
> + return 0;
> +
> + if (pgmap->nr_range != 1 ||
> + !IS_ALIGNED(range->start | range_len(range), MIN_MEMORY_BLOCK_SIZE))
> + return 0;
> +
> + pgmap->vmemmap_shared_page = alloc_pages_node(nid, GFP_KERNEL, 0);
> +
> + return pgmap->vmemmap_shared_page ? 0 : -ENOMEM;
> +}
> +
> +static inline void pgmap_vmemmap_shared_page_free(struct dev_pagemap *pgmap)
> +{
> + if (pgmap->vmemmap_shared_page)
> + put_page(pgmap->vmemmap_shared_page);
> +}
> +
> static void pageunmap_range(struct dev_pagemap *pgmap, int range_id)
> {
> struct range *range = &pgmap->ranges[range_id];
> @@ -93,8 +118,9 @@ static void pageunmap_range(struct dev_pagemap *pgmap, int range_id)
>
> /* pages are dead and unused, undo the arch mapping */
> mem_hotplug_begin();
> - remove_pfn_range_from_zone(page_zone(first_page), PHYS_PFN(range->start),
> - PHYS_PFN(range_len(range)));
> + if (!pgmap_vmemmap_optimizable(pgmap))
> + remove_pfn_range_from_zone(page_zone(first_page), PHYS_PFN(range->start),
> + PHYS_PFN(range_len(range)));
> if (pgmap->type == MEMORY_DEVICE_PRIVATE) {
> __remove_pages(PHYS_PFN(range->start),
> PHYS_PFN(range_len(range)), NULL, pgmap);
> @@ -123,6 +149,7 @@ void memunmap_pages(struct dev_pagemap *pgmap)
>
> for (i = 0; i < pgmap->nr_range; i++)
> pageunmap_range(pgmap, i);
> + pgmap_vmemmap_shared_page_free(pgmap);
> percpu_ref_exit(&pgmap->ref);
>
> WARN_ONCE(pgmap->altmap.alloc, "failed to free all reserved pages\n");
> @@ -310,6 +337,9 @@ void *memremap_pages(struct dev_pagemap *pgmap, int nid)
> break;
> case MEMORY_DEVICE_FS_DAX:
> params.pgprot = pgprot_decrypted(params.pgprot);
> + error = pgmap_vmemmap_shared_page_alloc(pgmap, nid);
> + if (error)
> + return ERR_PTR(error);
> break;
> case MEMORY_DEVICE_GENERIC:
> break;
> @@ -324,8 +354,10 @@ void *memremap_pages(struct dev_pagemap *pgmap, int nid)
> init_completion(&pgmap->done);
> error = percpu_ref_init(&pgmap->ref, dev_pagemap_percpu_release, 0,
> GFP_KERNEL);
> - if (error)
> + if (error) {
> + pgmap_vmemmap_shared_page_free(pgmap);
> return ERR_PTR(error);
> + }
>
> /*
> * Clear the pgmap nr_range as it will be incremented for each
> diff --git a/mm/mm_init.c b/mm/mm_init.c
> index 2ed17cc707ed..7dd03b8a8d28 100644
> --- a/mm/mm_init.c
> +++ b/mm/mm_init.c
> @@ -33,6 +33,7 @@
> #include <linux/vmstat.h>
> #include <linux/kexec_handover.h>
> #include <linux/hugetlb.h>
> +#include <linux/memremap.h>
> #include "internal.h"
> #include "mm_init.h"
> #include "page_alloc.h"
> @@ -1133,6 +1134,15 @@ void __ref memmap_init_zone_device(struct zone *zone,
> if (!nr_pages)
> return;
>
> + if (pgmap_vmemmap_optimizable(pgmap)) {
> + struct page *page = page_address(pgmap->vmemmap_shared_page);
> +
> + for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++)
> + __init_zone_device_page(page + i, start_pfn + i,
> + ZONE_DEVICE, nid, pgmap);
> + goto pageblock_init;
> + }
> +
> /*
> * Seed the reusable head-page template from the first real struct
> * page. The normal page-init and refcount helpers must operate on
> @@ -1164,6 +1174,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
> compound_nr_pages(pfn, altmap, pgmap));
> }
>
> +pageblock_init:
> pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE,
> /* isolate */ false, /* atomic */ false);
>
> diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
> index e62e6aa07f12..4dc7f020ed10 100644
> --- a/mm/sparse-vmemmap.c
> +++ b/mm/sparse-vmemmap.c
> @@ -37,6 +37,8 @@
> */
> /* Get a ref on the head page struct page, for ZONE_DEVICE compound pages */
> #define VMEMMAP_POPULATE_PAGEREF 0x0001
> +/* Read-only shared vmemmap mappings for FS-DAX base pages */
> +#define VMEMMAP_POPULATE_FSDAX_SHARED 0x0002
>
> #include "internal.h"
> #include "mm_init.h"
> @@ -262,10 +264,11 @@ 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_PAGEREF | VMEMMAP_POPULATE_FSDAX_SHARED))
> get_page(pfn_to_page(ptpfn));
> }
> - entry = pfn_pte(ptpfn, PAGE_KERNEL);
> + entry = pfn_pte(ptpfn, flags & VMEMMAP_POPULATE_FSDAX_SHARED ?
> + PAGE_KERNEL_RO : PAGE_KERNEL);
> set_pte_at(&init_mm, addr, pte, entry);
> } else if (WARN_ON_ONCE(vmemmap_optimizable_pfn(pfn)))
> return NULL;
> @@ -379,6 +382,54 @@ int __meminit vmemmap_populate_basepages(unsigned long start, unsigned long end,
> return vmemmap_populate_range(start, end, node, altmap, -1, 0);
> }
>
> +#ifdef CONFIG_ZONE_DEVICE
> +static int __vmemmap_materialize_page(struct page *page)
> +{
> + unsigned long addr = PAGE_ALIGN_DOWN((unsigned long)page);
> + struct dev_pagemap *pgmap = page_pgmap(page);
> + struct page *candidate, *template = pgmap->vmemmap_shared_page;
> + pte_t *pte = virt_to_kpte(addr);
> +
> + if (pte_page(ptep_get(pte)) != template)
> + return 0;
> +
> + candidate = alloc_pages_node(page_to_nid(page), GFP_KERNEL, 0);
> + if (!candidate)
> + return -ENOMEM;
> + copy_page(page_address(candidate), page_address(template));
> +
> + spin_lock(&init_mm.page_table_lock);
> + if (pte_page(ptep_get(pte)) != template) {
> + __free_page(candidate);
> + goto out;
> + }
> + /* Make the copied struct page contents visible before the PTE update. */
> + smp_wmb();
> + set_pte_at(&init_mm, addr, pte, mk_pte(candidate, PAGE_KERNEL));
> + flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
> + put_page(template);
> +out:
> + spin_unlock(&init_mm.page_table_lock);
> +
> + return 0;
> +}
> +
> +int vmemmap_materialize_page(struct page *page, unsigned int order)
> +{
> + struct dev_pagemap *pgmap = page_pgmap(page);
> + unsigned long end = (unsigned long)(page + (1UL << order));
> +
> + if (!pgmap_vmemmap_optimizable(pgmap))
> + return 0;
> +
> + for (unsigned long addr = (unsigned long)page; addr < end; addr += PAGE_SIZE)
> + if (__vmemmap_materialize_page((struct page *)addr))
> + return -ENOMEM;
> +
> + return 0;
> +}
> +#endif /* CONFIG_ZONE_DEVICE */
> +
> /*
> * Write protect the mirrored tail page structs for HVO. This will be
> * called from the hugetlb code when gathering and initializing the
> @@ -581,7 +632,11 @@ 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_vmemmap_optimizable(pgmap))
> + r = vmemmap_populate_range(start, end, nid, NULL,
> + page_to_pfn(pgmap->vmemmap_shared_page),
> + VMEMMAP_POPULATE_FSDAX_SHARED);
> + else if (vmemmap_can_optimize(altmap, pgmap))
> r = vmemmap_populate_compound_pages(pfn, start, end, nid, pgmap);
> else
> r = vmemmap_populate(start, end, nid, altmap);
> @@ -887,7 +942,8 @@ int __meminit sparse_add_section(int nid, unsigned long start_pfn,
> * Poison uninitialized struct pages in order to catch invalid flags
> * combinations.
> */
> - page_init_poison(memmap, sizeof(struct page) * nr_pages);
> + if (!pgmap_vmemmap_optimizable(pgmap))
> + page_init_poison(memmap, sizeof(struct page) * nr_pages);
>
> ms = __nr_to_section(section_nr);
> __section_mark_present(ms, section_nr);
next prev parent reply other threads:[~2026-09-21 6:42 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-03 12:21 [PATCH 0/4] mm: Reduce struct page overhead for FS-DAX pmem Muchun Song
2026-09-03 12:21 ` [PATCH 1/4] mm: generalize vmemmap remap architecture support Muchun Song
2026-09-21 12:25 ` Oscar Salvador (SUSE)
2026-09-03 12:21 ` [PATCH 2/4] nvdimm/pmem: avoid HWPoison flag updates for clean pages Muchun Song
2026-09-21 7:33 ` Gupta, Pankaj
2026-09-21 12:53 ` Oscar Salvador (SUSE)
2026-09-03 12:21 ` [PATCH 3/4] mm: add shared read-only vmemmap support for FS-DAX Muchun Song
2026-09-21 6:42 ` Gupta, Pankaj [this message]
2026-09-21 9:33 ` Muchun Song
2026-09-21 11:05 ` Gupta, Pankaj
2026-09-21 13:09 ` Oscar Salvador (SUSE)
2026-09-03 12:21 ` [PATCH 4/4] fsdax: materialize pmem vmemmap metadata on faults Muchun Song
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4bc1f4de-40ad-46f0-8d37-4c4e0cad0ad6@amd.com \
--to=pankaj.gupta@amd.com \
--cc=akpm@linux-foundation.org \
--cc=alison.schofield@intel.com \
--cc=dave.jiang@intel.com \
--cc=david@kernel.org \
--cc=djbw@kernel.org \
--cc=iweiny@kernel.org \
--cc=jack@suse.cz \
--cc=linux-cxl@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=muchun.song@linux.dev \
--cc=nvdimm@lists.linux.dev \
--cc=osalvador@suse.de \
--cc=qi.zheng@linux.dev \
--cc=rppt@kernel.org \
--cc=songmuchun@bytedance.com \
--cc=vbabka@kernel.org \
--cc=vishal.l.verma@intel.com \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®